1use std::collections::{BTreeMap, BTreeSet};
18
19use indoc::indoc;
20use itertools::Itertools;
21use sqlx::{MySqlConnection, mysql::MySqlRow, prelude::*};
22
23use crate::{
24 core::{
25 common::{UnixUser, rev_yn, yn},
26 database_privileges::{
27 DATABASE_PRIVILEGE_FIELDS, DatabasePrivilegeChange, DatabasePrivilegeRow,
28 DatabasePrivilegesDiff,
29 },
30 protocol::{
31 DiffDoesNotApplyError, GetAllDatabasesPrivilegeDataError,
32 GetDatabasesPrivilegeDataError, ListAllPrivilegesResponse, ListPrivilegesResponse,
33 ModifyDatabasePrivilegesError, ModifyPrivilegesResponse,
34 },
35 types::{MySQLDatabase, MySQLUser},
36 },
37 server::{
38 common::{create_user_group_matching_regex, try_get_with_binary_fallback},
39 input_sanitization::{quote_identifier, validate_name, validate_ownership_by_unix_user},
40 sql::database_operations::unsafe_database_exists,
41 },
42};
43
44#[inline]
47fn get_mysql_row_priv_field(row: &MySqlRow, position: usize) -> Result<bool, sqlx::Error> {
48 let field = DATABASE_PRIVILEGE_FIELDS[position];
49 let value = row.try_get(position)?;
50 match rev_yn(value) {
51 Some(val) => Ok(val),
52 _ => {
53 log::warn!(r#"Invalid value for privilege "{}": '{}'"#, field, value);
54 Ok(false)
55 }
56 }
57}
58
59impl FromRow<'_, MySqlRow> for DatabasePrivilegeRow {
60 fn from_row(row: &MySqlRow) -> Result<Self, sqlx::Error> {
61 Ok(Self {
62 db: try_get_with_binary_fallback(row, "Db")?.into(),
63 user: try_get_with_binary_fallback(row, "User")?.into(),
64 select_priv: get_mysql_row_priv_field(row, 2)?,
65 insert_priv: get_mysql_row_priv_field(row, 3)?,
66 update_priv: get_mysql_row_priv_field(row, 4)?,
67 delete_priv: get_mysql_row_priv_field(row, 5)?,
68 create_priv: get_mysql_row_priv_field(row, 6)?,
69 drop_priv: get_mysql_row_priv_field(row, 7)?,
70 alter_priv: get_mysql_row_priv_field(row, 8)?,
71 index_priv: get_mysql_row_priv_field(row, 9)?,
72 create_tmp_table_priv: get_mysql_row_priv_field(row, 10)?,
73 lock_tables_priv: get_mysql_row_priv_field(row, 11)?,
74 references_priv: get_mysql_row_priv_field(row, 12)?,
75 })
76 }
77}
78
79async fn unsafe_get_database_privileges(
82 database_name: &str,
83 connection: &mut MySqlConnection,
84) -> Result<Vec<DatabasePrivilegeRow>, sqlx::Error> {
85 let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!(
86 "SELECT {} FROM `db` WHERE `Db` = ?",
87 DATABASE_PRIVILEGE_FIELDS
88 .iter()
89 .map(|field| quote_identifier(field))
90 .join(","),
91 ))
92 .bind(database_name)
93 .fetch_all(connection)
94 .await;
95
96 if let Err(e) = &result {
97 log::error!(
98 "Failed to get database privileges for '{}': {}",
99 &database_name,
100 e
101 );
102 }
103
104 result
105}
106
107pub async fn unsafe_get_database_privileges_for_db_user_pair(
110 database_name: &MySQLDatabase,
111 user_name: &MySQLUser,
112 connection: &mut MySqlConnection,
113) -> Result<Option<DatabasePrivilegeRow>, sqlx::Error> {
114 let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!(
115 "SELECT {} FROM `db` WHERE `Db` = ? AND `User` = ?",
116 DATABASE_PRIVILEGE_FIELDS
117 .iter()
118 .map(|field| quote_identifier(field))
119 .join(","),
120 ))
121 .bind(database_name.as_str())
122 .bind(user_name.as_str())
123 .fetch_optional(connection)
124 .await;
125
126 if let Err(e) = &result {
127 log::error!(
128 "Failed to get database privileges for '{}.{}': {}",
129 &database_name,
130 &user_name,
131 e
132 );
133 }
134
135 result
136}
137
138pub async fn get_databases_privilege_data(
139 database_names: Vec<MySQLDatabase>,
140 unix_user: &UnixUser,
141 connection: &mut MySqlConnection,
142) -> ListPrivilegesResponse {
143 let mut results = BTreeMap::new();
144
145 for database_name in database_names.iter() {
146 if let Err(err) = validate_name(database_name) {
147 results.insert(
148 database_name.to_owned(),
149 Err(GetDatabasesPrivilegeDataError::SanitizationError(err)),
150 );
151 continue;
152 }
153
154 if let Err(err) = validate_ownership_by_unix_user(database_name, unix_user) {
155 results.insert(
156 database_name.to_owned(),
157 Err(GetDatabasesPrivilegeDataError::OwnershipError(err)),
158 );
159 continue;
160 }
161
162 if !unsafe_database_exists(database_name, connection)
163 .await
164 .unwrap()
165 {
166 results.insert(
167 database_name.to_owned(),
168 Err(GetDatabasesPrivilegeDataError::DatabaseDoesNotExist),
169 );
170 continue;
171 }
172
173 let result = unsafe_get_database_privileges(database_name, connection)
174 .await
175 .map_err(|e| GetDatabasesPrivilegeDataError::MySqlError(e.to_string()));
176
177 results.insert(database_name.to_owned(), result);
178 }
179
180 debug_assert!(database_names.len() == results.len());
181
182 results
183}
184
185pub async fn get_all_database_privileges(
187 unix_user: &UnixUser,
188 connection: &mut MySqlConnection,
189) -> ListAllPrivilegesResponse {
190 let result = sqlx::query_as::<_, DatabasePrivilegeRow>(&format!(
191 indoc! {r#"
192 SELECT {} FROM `db` WHERE `db` IN
193 (SELECT DISTINCT `SCHEMA_NAME` AS `database`
194 FROM `information_schema`.`SCHEMATA`
195 WHERE `SCHEMA_NAME` NOT IN ('information_schema', 'performance_schema', 'mysql', 'sys')
196 AND `SCHEMA_NAME` REGEXP ?)
197 "#},
198 DATABASE_PRIVILEGE_FIELDS
199 .iter()
200 .map(|field| quote_identifier(field))
201 .join(","),
202 ))
203 .bind(create_user_group_matching_regex(unix_user))
204 .fetch_all(connection)
205 .await
206 .map_err(|e| GetAllDatabasesPrivilegeDataError::MySqlError(e.to_string()));
207
208 if let Err(e) = &result {
209 log::error!("Failed to get all database privileges: {:?}", e);
210 }
211
212 result
213}
214
215async fn unsafe_apply_privilege_diff(
216 database_privilege_diff: &DatabasePrivilegesDiff,
217 connection: &mut MySqlConnection,
218) -> Result<(), sqlx::Error> {
219 let result = match database_privilege_diff {
220 DatabasePrivilegesDiff::New(p) => {
221 let tables = DATABASE_PRIVILEGE_FIELDS
222 .iter()
223 .map(|field| quote_identifier(field))
224 .join(",");
225
226 let question_marks =
227 std::iter::repeat_n("?", DATABASE_PRIVILEGE_FIELDS.len()).join(",");
228
229 sqlx::query(
230 format!("INSERT INTO `db` ({}) VALUES ({})", tables, question_marks).as_str(),
231 )
232 .bind(p.db.to_string())
233 .bind(p.user.to_string())
234 .bind(yn(p.select_priv))
235 .bind(yn(p.insert_priv))
236 .bind(yn(p.update_priv))
237 .bind(yn(p.delete_priv))
238 .bind(yn(p.create_priv))
239 .bind(yn(p.drop_priv))
240 .bind(yn(p.alter_priv))
241 .bind(yn(p.index_priv))
242 .bind(yn(p.create_tmp_table_priv))
243 .bind(yn(p.lock_tables_priv))
244 .bind(yn(p.references_priv))
245 .execute(connection)
246 .await
247 .map(|_| ())
248 }
249 DatabasePrivilegesDiff::Modified(p) => {
250 let changes = DATABASE_PRIVILEGE_FIELDS
251 .iter()
252 .skip(2) .map(|field| {
254 format!(
255 "{} = COALESCE(?, {})",
256 quote_identifier(field),
257 quote_identifier(field)
258 )
259 })
260 .join(",");
261
262 fn change_to_yn(change: DatabasePrivilegeChange) -> &'static str {
263 match change {
264 DatabasePrivilegeChange::YesToNo => "N",
265 DatabasePrivilegeChange::NoToYes => "Y",
266 }
267 }
268
269 sqlx::query(
270 format!("UPDATE `db` SET {} WHERE `Db` = ? AND `User` = ?", changes).as_str(),
271 )
272 .bind(p.select_priv.map(change_to_yn))
273 .bind(p.insert_priv.map(change_to_yn))
274 .bind(p.update_priv.map(change_to_yn))
275 .bind(p.delete_priv.map(change_to_yn))
276 .bind(p.create_priv.map(change_to_yn))
277 .bind(p.drop_priv.map(change_to_yn))
278 .bind(p.alter_priv.map(change_to_yn))
279 .bind(p.index_priv.map(change_to_yn))
280 .bind(p.create_tmp_table_priv.map(change_to_yn))
281 .bind(p.lock_tables_priv.map(change_to_yn))
282 .bind(p.references_priv.map(change_to_yn))
283 .bind(p.db.to_string())
284 .bind(p.user.to_string())
285 .execute(connection)
286 .await
287 .map(|_| ())
288 }
289 DatabasePrivilegesDiff::Deleted(p) => {
290 sqlx::query("DELETE FROM `db` WHERE `Db` = ? AND `User` = ?")
291 .bind(p.db.to_string())
292 .bind(p.user.to_string())
293 .execute(connection)
294 .await
295 .map(|_| ())
296 }
297 DatabasePrivilegesDiff::Noop { .. } => Ok(()),
298 };
299
300 if let Err(e) = &result {
301 log::error!("Failed to apply database privilege diff: {}", e);
302 }
303
304 result
305}
306
307async fn validate_diff(
308 diff: &DatabasePrivilegesDiff,
309 connection: &mut MySqlConnection,
310) -> Result<(), ModifyDatabasePrivilegesError> {
311 let privilege_row = unsafe_get_database_privileges_for_db_user_pair(
312 diff.get_database_name(),
313 diff.get_user_name(),
314 connection,
315 )
316 .await;
317
318 let privilege_row = match privilege_row {
319 Ok(privilege_row) => privilege_row,
320 Err(e) => return Err(ModifyDatabasePrivilegesError::MySqlError(e.to_string())),
321 };
322
323 match diff {
324 DatabasePrivilegesDiff::New(_) => {
325 if privilege_row.is_some() {
326 Err(ModifyDatabasePrivilegesError::DiffDoesNotApply(
327 DiffDoesNotApplyError::RowAlreadyExists(
328 diff.get_database_name().to_owned(),
329 diff.get_user_name().to_owned(),
330 ),
331 ))
332 } else {
333 Ok(())
334 }
335 }
336 DatabasePrivilegesDiff::Modified(_) if privilege_row.is_none() => {
337 Err(ModifyDatabasePrivilegesError::DiffDoesNotApply(
338 DiffDoesNotApplyError::RowDoesNotExist(
339 diff.get_database_name().to_owned(),
340 diff.get_user_name().to_owned(),
341 ),
342 ))
343 }
344 DatabasePrivilegesDiff::Modified(row_diff) => {
345 let row = privilege_row.unwrap();
346
347 let error_exists = DATABASE_PRIVILEGE_FIELDS
348 .iter()
349 .skip(2) .any(
351 |field| match row_diff.get_privilege_change_by_name(field).unwrap() {
352 Some(DatabasePrivilegeChange::YesToNo) => {
353 !row.get_privilege_by_name(field).unwrap()
354 }
355 Some(DatabasePrivilegeChange::NoToYes) => {
356 row.get_privilege_by_name(field).unwrap()
357 }
358 None => false,
359 },
360 );
361
362 if error_exists {
363 Err(ModifyDatabasePrivilegesError::DiffDoesNotApply(
364 DiffDoesNotApplyError::RowPrivilegeChangeDoesNotApply(row_diff.to_owned(), row),
365 ))
366 } else {
367 Ok(())
368 }
369 }
370 DatabasePrivilegesDiff::Deleted(_) => {
371 if privilege_row.is_none() {
372 Err(ModifyDatabasePrivilegesError::DiffDoesNotApply(
373 DiffDoesNotApplyError::RowDoesNotExist(
374 diff.get_database_name().to_owned(),
375 diff.get_user_name().to_owned(),
376 ),
377 ))
378 } else {
379 Ok(())
380 }
381 }
382 DatabasePrivilegesDiff::Noop { .. } => {
383 log::warn!(
384 "Server got sent a noop database privilege diff to validate, is the client buggy?"
385 );
386 Ok(())
387 }
388 }
389}
390
391pub async fn apply_privilege_diffs(
393 database_privilege_diffs: BTreeSet<DatabasePrivilegesDiff>,
394 unix_user: &UnixUser,
395 connection: &mut MySqlConnection,
396) -> ModifyPrivilegesResponse {
397 let mut results: BTreeMap<(MySQLDatabase, MySQLUser), _> = BTreeMap::new();
398
399 for diff in database_privilege_diffs {
400 let key = (
401 diff.get_database_name().to_owned(),
402 diff.get_user_name().to_owned(),
403 );
404 if let Err(err) = validate_name(diff.get_database_name()) {
405 results.insert(
406 key,
407 Err(ModifyDatabasePrivilegesError::DatabaseSanitizationError(
408 err,
409 )),
410 );
411 continue;
412 }
413
414 if let Err(err) = validate_ownership_by_unix_user(diff.get_database_name(), unix_user) {
415 results.insert(
416 key,
417 Err(ModifyDatabasePrivilegesError::DatabaseOwnershipError(err)),
418 );
419 continue;
420 }
421
422 if let Err(err) = validate_name(diff.get_user_name()) {
423 results.insert(
424 key,
425 Err(ModifyDatabasePrivilegesError::UserSanitizationError(err)),
426 );
427 continue;
428 }
429
430 if let Err(err) = validate_ownership_by_unix_user(diff.get_user_name(), unix_user) {
431 results.insert(
432 key,
433 Err(ModifyDatabasePrivilegesError::UserOwnershipError(err)),
434 );
435 continue;
436 }
437
438 if !unsafe_database_exists(diff.get_database_name(), connection)
439 .await
440 .unwrap()
441 {
442 results.insert(
443 key,
444 Err(ModifyDatabasePrivilegesError::DatabaseDoesNotExist),
445 );
446 continue;
447 }
448
449 if let Err(err) = validate_diff(&diff, connection).await {
450 results.insert(key, Err(err));
451 continue;
452 }
453
454 let result = unsafe_apply_privilege_diff(&diff, connection)
455 .await
456 .map_err(|e| ModifyDatabasePrivilegesError::MySqlError(e.to_string()));
457
458 results.insert(key, result);
459 }
460
461 results
462}