1use super::base::{
5 DATABASE_PRIVILEGE_FIELDS, DatabasePrivilegeRow, db_priv_field_human_readable_name,
6};
7use crate::core::{
8 common::{rev_yn, yn},
9 types::MySQLDatabase,
10};
11use anyhow::{Context, anyhow};
12use itertools::Itertools;
13use std::cmp::max;
14
15pub fn format_privileges_line_for_editor(
17 privs: &DatabasePrivilegeRow,
18 username_len: usize,
19 database_name_len: usize,
20) -> String {
21 DATABASE_PRIVILEGE_FIELDS
22 .into_iter()
23 .map(|field| match field {
24 "Db" => format!("{:width$}", privs.db, width = database_name_len),
25 "User" => format!("{:width$}", privs.user, width = username_len),
26 privilege => format!(
27 "{:width$}",
28 yn(privs.get_privilege_by_name(privilege).unwrap()),
29 width = db_priv_field_human_readable_name(privilege).len()
30 ),
31 })
32 .join(" ")
33 .trim()
34 .to_string()
35}
36
37const EDITOR_COMMENT: &str = r#"
38# Welcome to the privilege editor.
39# Each line defines what privileges a single user has on a single database.
40# The first two columns respectively represent the database name and the user, and the remaining columns are the privileges.
41# If the user should have a certain privilege, write 'Y', otherwise write 'N'.
42#
43# Lines starting with '#' are comments and will be ignored.
44"#;
45
46pub fn generate_editor_content_from_privilege_data(
51 privilege_data: &[DatabasePrivilegeRow],
52 unix_user: &str,
53 database_name: Option<&MySQLDatabase>,
54) -> String {
55 let example_user = format!("{}_user", unix_user);
56 let example_db = database_name
57 .unwrap_or(&format!("{}_db", unix_user).into())
58 .to_string();
59
60 let longest_username = max(
66 privilege_data
67 .iter()
68 .map(|p| p.user.len())
69 .max()
70 .unwrap_or(example_user.len()),
71 "User".len(),
72 );
73
74 let longest_database_name = max(
75 privilege_data
76 .iter()
77 .map(|p| p.db.len())
78 .max()
79 .unwrap_or(example_db.len()),
80 "Database".len(),
81 );
82
83 let mut header: Vec<_> = DATABASE_PRIVILEGE_FIELDS
84 .into_iter()
85 .map(db_priv_field_human_readable_name)
86 .collect();
87
88 header[0] = format!("{:width$}", header[0], width = longest_database_name);
90 header[1] = format!("{:width$}", header[1], width = longest_username);
91
92 let example_line = format_privileges_line_for_editor(
93 &DatabasePrivilegeRow {
94 db: example_db.into(),
95 user: example_user.into(),
96 select_priv: true,
97 insert_priv: true,
98 update_priv: true,
99 delete_priv: true,
100 create_priv: false,
101 drop_priv: false,
102 alter_priv: false,
103 index_priv: false,
104 create_tmp_table_priv: false,
105 lock_tables_priv: false,
106 references_priv: false,
107 },
108 longest_username,
109 longest_database_name,
110 );
111
112 format!(
113 "{}\n{}\n{}",
114 EDITOR_COMMENT,
115 header.join(" "),
116 if privilege_data.is_empty() {
117 format!("# {}", example_line)
118 } else {
119 privilege_data
120 .iter()
121 .map(|privs| {
122 format_privileges_line_for_editor(
123 privs,
124 longest_username,
125 longest_database_name,
126 )
127 })
128 .join("\n")
129 }
130 )
131}
132
133#[derive(Debug)]
134enum PrivilegeRowParseResult {
135 PrivilegeRow(DatabasePrivilegeRow),
136 ParserError(anyhow::Error),
137 TooFewFields(usize),
138 TooManyFields(usize),
139 Header,
140 Comment,
141 Empty,
142}
143
144#[inline]
145fn parse_privilege_cell_from_editor(yn: &str, name: &str) -> anyhow::Result<bool> {
146 rev_yn(yn)
147 .ok_or_else(|| anyhow!("Expected Y or N, found {}", yn))
148 .context(format!("Could not parse {} privilege", name))
149}
150
151#[inline]
152fn editor_row_is_header(row: &str) -> bool {
153 row.split_ascii_whitespace()
154 .zip(DATABASE_PRIVILEGE_FIELDS.iter())
155 .map(|(field, priv_name)| (field, db_priv_field_human_readable_name(priv_name)))
156 .all(|(field, header_field)| field == header_field)
157}
158
159fn parse_privilege_row_from_editor(row: &str) -> PrivilegeRowParseResult {
161 if row.starts_with('#') || row.starts_with("//") {
162 return PrivilegeRowParseResult::Comment;
163 }
164
165 if row.trim().is_empty() {
166 return PrivilegeRowParseResult::Empty;
167 }
168
169 let parts: Vec<&str> = row.trim().split_ascii_whitespace().collect();
170
171 match parts.len() {
172 n if (n < DATABASE_PRIVILEGE_FIELDS.len()) => {
173 return PrivilegeRowParseResult::TooFewFields(n);
174 }
175 n if (n > DATABASE_PRIVILEGE_FIELDS.len()) => {
176 return PrivilegeRowParseResult::TooManyFields(n);
177 }
178 _ => {}
179 }
180
181 if editor_row_is_header(row) {
182 return PrivilegeRowParseResult::Header;
183 }
184
185 let row = DatabasePrivilegeRow {
186 db: (*parts.first().unwrap()).into(),
187 user: (*parts.get(1).unwrap()).into(),
188 select_priv: match parse_privilege_cell_from_editor(
189 parts.get(2).unwrap(),
190 DATABASE_PRIVILEGE_FIELDS[2],
191 ) {
192 Ok(p) => p,
193 Err(e) => return PrivilegeRowParseResult::ParserError(e),
194 },
195 insert_priv: match parse_privilege_cell_from_editor(
196 parts.get(3).unwrap(),
197 DATABASE_PRIVILEGE_FIELDS[3],
198 ) {
199 Ok(p) => p,
200 Err(e) => return PrivilegeRowParseResult::ParserError(e),
201 },
202 update_priv: match parse_privilege_cell_from_editor(
203 parts.get(4).unwrap(),
204 DATABASE_PRIVILEGE_FIELDS[4],
205 ) {
206 Ok(p) => p,
207 Err(e) => return PrivilegeRowParseResult::ParserError(e),
208 },
209 delete_priv: match parse_privilege_cell_from_editor(
210 parts.get(5).unwrap(),
211 DATABASE_PRIVILEGE_FIELDS[5],
212 ) {
213 Ok(p) => p,
214 Err(e) => return PrivilegeRowParseResult::ParserError(e),
215 },
216 create_priv: match parse_privilege_cell_from_editor(
217 parts.get(6).unwrap(),
218 DATABASE_PRIVILEGE_FIELDS[6],
219 ) {
220 Ok(p) => p,
221 Err(e) => return PrivilegeRowParseResult::ParserError(e),
222 },
223 drop_priv: match parse_privilege_cell_from_editor(
224 parts.get(7).unwrap(),
225 DATABASE_PRIVILEGE_FIELDS[7],
226 ) {
227 Ok(p) => p,
228 Err(e) => return PrivilegeRowParseResult::ParserError(e),
229 },
230 alter_priv: match parse_privilege_cell_from_editor(
231 parts.get(8).unwrap(),
232 DATABASE_PRIVILEGE_FIELDS[8],
233 ) {
234 Ok(p) => p,
235 Err(e) => return PrivilegeRowParseResult::ParserError(e),
236 },
237 index_priv: match parse_privilege_cell_from_editor(
238 parts.get(9).unwrap(),
239 DATABASE_PRIVILEGE_FIELDS[9],
240 ) {
241 Ok(p) => p,
242 Err(e) => return PrivilegeRowParseResult::ParserError(e),
243 },
244 create_tmp_table_priv: match parse_privilege_cell_from_editor(
245 parts.get(10).unwrap(),
246 DATABASE_PRIVILEGE_FIELDS[10],
247 ) {
248 Ok(p) => p,
249 Err(e) => return PrivilegeRowParseResult::ParserError(e),
250 },
251 lock_tables_priv: match parse_privilege_cell_from_editor(
252 parts.get(11).unwrap(),
253 DATABASE_PRIVILEGE_FIELDS[11],
254 ) {
255 Ok(p) => p,
256 Err(e) => return PrivilegeRowParseResult::ParserError(e),
257 },
258 references_priv: match parse_privilege_cell_from_editor(
259 parts.get(12).unwrap(),
260 DATABASE_PRIVILEGE_FIELDS[12],
261 ) {
262 Ok(p) => p,
263 Err(e) => return PrivilegeRowParseResult::ParserError(e),
264 },
265 };
266
267 PrivilegeRowParseResult::PrivilegeRow(row)
268}
269
270pub fn parse_privilege_data_from_editor_content(
273 content: String,
274) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
275 content
276 .trim()
277 .split('\n')
278 .map(|line| line.trim())
279 .map(parse_privilege_row_from_editor)
280 .map(|result| match result {
281 PrivilegeRowParseResult::PrivilegeRow(row) => Ok(Some(row)),
282 PrivilegeRowParseResult::ParserError(e) => Err(e),
283 PrivilegeRowParseResult::TooFewFields(n) => Err(anyhow!(
284 "Too few fields in line. Expected to find {} fields, found {}",
285 DATABASE_PRIVILEGE_FIELDS.len(),
286 n
287 )),
288 PrivilegeRowParseResult::TooManyFields(n) => Err(anyhow!(
289 "Too many fields in line. Expected to find {} fields, found {}",
290 DATABASE_PRIVILEGE_FIELDS.len(),
291 n
292 )),
293 PrivilegeRowParseResult::Header => Ok(None),
294 PrivilegeRowParseResult::Comment => Ok(None),
295 PrivilegeRowParseResult::Empty => Ok(None),
296 })
297 .filter_map(|result| result.transpose())
298 .collect::<anyhow::Result<Vec<DatabasePrivilegeRow>>>()
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn ensure_generated_and_parsed_editor_content_is_equal() {
307 let permissions = vec![
308 DatabasePrivilegeRow {
309 db: "db".into(),
310 user: "user".into(),
311 select_priv: true,
312 insert_priv: true,
313 update_priv: true,
314 delete_priv: true,
315 create_priv: true,
316 drop_priv: true,
317 alter_priv: true,
318 index_priv: true,
319 create_tmp_table_priv: true,
320 lock_tables_priv: true,
321 references_priv: true,
322 },
323 DatabasePrivilegeRow {
324 db: "db".into(),
325 user: "user".into(),
326 select_priv: false,
327 insert_priv: false,
328 update_priv: false,
329 delete_priv: false,
330 create_priv: false,
331 drop_priv: false,
332 alter_priv: false,
333 index_priv: false,
334 create_tmp_table_priv: false,
335 lock_tables_priv: false,
336 references_priv: false,
337 },
338 ];
339
340 let content = generate_editor_content_from_privilege_data(&permissions, "user", None);
341
342 let parsed_permissions = parse_privilege_data_from_editor_content(content).unwrap();
343
344 assert_eq!(permissions, parsed_permissions);
345 }
346}