1
//! This module contains serialization and deserialization logic for
2
//! editing database privileges in a text editor.
3

            
4
use super::base::{
5
    DATABASE_PRIVILEGE_FIELDS, DatabasePrivilegeRow, db_priv_field_human_readable_name,
6
};
7
use crate::core::{
8
    common::{rev_yn, yn},
9
    types::MySQLDatabase,
10
};
11
use anyhow::{Context, anyhow};
12
use itertools::Itertools;
13
use std::cmp::max;
14

            
15
/// Generates a single row of the privileges table for the editor.
16
3
pub fn format_privileges_line_for_editor(
17
3
    privs: &DatabasePrivilegeRow,
18
3
    username_len: usize,
19
3
    database_name_len: usize,
20
3
) -> String {
21
    DATABASE_PRIVILEGE_FIELDS
22
3
        .into_iter()
23
39
        .map(|field| match field {
24
39
            "Db" => format!("{:width$}", privs.db, width = database_name_len),
25
36
            "User" => format!("{:width$}", privs.user, width = username_len),
26
33
            privilege => format!(
27
                "{:width$}",
28
33
                yn(privs.get_privilege_by_name(privilege).unwrap()),
29
33
                width = db_priv_field_human_readable_name(privilege).len()
30
            ),
31
39
        })
32
3
        .join(" ")
33
3
        .trim()
34
3
        .to_string()
35
3
}
36

            
37
const 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

            
46
/// Generates the content for the privilege editor.
47
///
48
/// The unix user is used in case there are no privileges to edit,
49
/// so that the user can see an example line based on their username.
50
1
pub fn generate_editor_content_from_privilege_data(
51
1
    privilege_data: &[DatabasePrivilegeRow],
52
1
    unix_user: &str,
53
1
    database_name: Option<&MySQLDatabase>,
54
1
) -> String {
55
1
    let example_user = format!("{}_user", unix_user);
56
1
    let example_db = database_name
57
1
        .unwrap_or(&format!("{}_db", unix_user).into())
58
1
        .to_string();
59

            
60
    // NOTE: `.max()`` fails when the iterator is empty.
61
    //       In this case, we know that the only fields in the
62
    //       editor will be the example user and example db name.
63
    //       Hence, it's put as the fallback value, despite not really
64
    //       being a "fallback" in the normal sense.
65
1
    let longest_username = max(
66
1
        privilege_data
67
1
            .iter()
68
2
            .map(|p| p.user.len())
69
1
            .max()
70
1
            .unwrap_or(example_user.len()),
71
1
        "User".len(),
72
    );
73

            
74
1
    let longest_database_name = max(
75
1
        privilege_data
76
1
            .iter()
77
2
            .map(|p| p.db.len())
78
1
            .max()
79
1
            .unwrap_or(example_db.len()),
80
1
        "Database".len(),
81
    );
82

            
83
1
    let mut header: Vec<_> = DATABASE_PRIVILEGE_FIELDS
84
1
        .into_iter()
85
1
        .map(db_priv_field_human_readable_name)
86
1
        .collect();
87

            
88
    // Pad the first two columns with spaces to align the privileges.
89
1
    header[0] = format!("{:width$}", header[0], width = longest_database_name);
90
1
    header[1] = format!("{:width$}", header[1], width = longest_username);
91

            
92
1
    let example_line = format_privileges_line_for_editor(
93
1
        &DatabasePrivilegeRow {
94
1
            db: example_db.into(),
95
1
            user: example_user.into(),
96
1
            select_priv: true,
97
1
            insert_priv: true,
98
1
            update_priv: true,
99
1
            delete_priv: true,
100
1
            create_priv: false,
101
1
            drop_priv: false,
102
1
            alter_priv: false,
103
1
            index_priv: false,
104
1
            create_tmp_table_priv: false,
105
1
            lock_tables_priv: false,
106
1
            references_priv: false,
107
1
        },
108
1
        longest_username,
109
1
        longest_database_name,
110
    );
111

            
112
1
    format!(
113
        "{}\n{}\n{}",
114
        EDITOR_COMMENT,
115
1
        header.join(" "),
116
1
        if privilege_data.is_empty() {
117
            format!("# {}", example_line)
118
        } else {
119
1
            privilege_data
120
1
                .iter()
121
2
                .map(|privs| {
122
2
                    format_privileges_line_for_editor(
123
2
                        privs,
124
2
                        longest_username,
125
2
                        longest_database_name,
126
                    )
127
2
                })
128
1
                .join("\n")
129
        }
130
    )
131
1
}
132

            
133
#[derive(Debug)]
134
enum PrivilegeRowParseResult {
135
    PrivilegeRow(DatabasePrivilegeRow),
136
    ParserError(anyhow::Error),
137
    TooFewFields(usize),
138
    TooManyFields(usize),
139
    Header,
140
    Comment,
141
    Empty,
142
}
143

            
144
#[inline]
145
22
fn parse_privilege_cell_from_editor(yn: &str, name: &str) -> anyhow::Result<bool> {
146
22
    rev_yn(yn)
147
22
        .ok_or_else(|| anyhow!("Expected Y or N, found {}", yn))
148
22
        .context(format!("Could not parse {} privilege", name))
149
22
}
150

            
151
#[inline]
152
3
fn editor_row_is_header(row: &str) -> bool {
153
3
    row.split_ascii_whitespace()
154
3
        .zip(DATABASE_PRIVILEGE_FIELDS.iter())
155
15
        .map(|(field, priv_name)| (field, db_priv_field_human_readable_name(priv_name)))
156
15
        .all(|(field, header_field)| field == header_field)
157
3
}
158

            
159
/// Parse a single row of the privileges table from the editor.
160
10
fn parse_privilege_row_from_editor(row: &str) -> PrivilegeRowParseResult {
161
10
    if row.starts_with('#') || row.starts_with("//") {
162
6
        return PrivilegeRowParseResult::Comment;
163
4
    }
164

            
165
4
    if row.trim().is_empty() {
166
1
        return PrivilegeRowParseResult::Empty;
167
3
    }
168

            
169
3
    let parts: Vec<&str> = row.trim().split_ascii_whitespace().collect();
170

            
171
3
    match parts.len() {
172
3
        n if (n < DATABASE_PRIVILEGE_FIELDS.len()) => {
173
            return PrivilegeRowParseResult::TooFewFields(n);
174
        }
175
3
        n if (n > DATABASE_PRIVILEGE_FIELDS.len()) => {
176
            return PrivilegeRowParseResult::TooManyFields(n);
177
        }
178
3
        _ => {}
179
    }
180

            
181
3
    if editor_row_is_header(row) {
182
1
        return PrivilegeRowParseResult::Header;
183
2
    }
184

            
185
2
    let row = DatabasePrivilegeRow {
186
2
        db: (*parts.first().unwrap()).into(),
187
2
        user: (*parts.get(1).unwrap()).into(),
188
2
        select_priv: match parse_privilege_cell_from_editor(
189
2
            parts.get(2).unwrap(),
190
2
            DATABASE_PRIVILEGE_FIELDS[2],
191
2
        ) {
192
2
            Ok(p) => p,
193
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
194
        },
195
2
        insert_priv: match parse_privilege_cell_from_editor(
196
2
            parts.get(3).unwrap(),
197
2
            DATABASE_PRIVILEGE_FIELDS[3],
198
2
        ) {
199
2
            Ok(p) => p,
200
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
201
        },
202
2
        update_priv: match parse_privilege_cell_from_editor(
203
2
            parts.get(4).unwrap(),
204
2
            DATABASE_PRIVILEGE_FIELDS[4],
205
2
        ) {
206
2
            Ok(p) => p,
207
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
208
        },
209
2
        delete_priv: match parse_privilege_cell_from_editor(
210
2
            parts.get(5).unwrap(),
211
2
            DATABASE_PRIVILEGE_FIELDS[5],
212
2
        ) {
213
2
            Ok(p) => p,
214
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
215
        },
216
2
        create_priv: match parse_privilege_cell_from_editor(
217
2
            parts.get(6).unwrap(),
218
2
            DATABASE_PRIVILEGE_FIELDS[6],
219
2
        ) {
220
2
            Ok(p) => p,
221
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
222
        },
223
2
        drop_priv: match parse_privilege_cell_from_editor(
224
2
            parts.get(7).unwrap(),
225
2
            DATABASE_PRIVILEGE_FIELDS[7],
226
2
        ) {
227
2
            Ok(p) => p,
228
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
229
        },
230
2
        alter_priv: match parse_privilege_cell_from_editor(
231
2
            parts.get(8).unwrap(),
232
2
            DATABASE_PRIVILEGE_FIELDS[8],
233
2
        ) {
234
2
            Ok(p) => p,
235
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
236
        },
237
2
        index_priv: match parse_privilege_cell_from_editor(
238
2
            parts.get(9).unwrap(),
239
2
            DATABASE_PRIVILEGE_FIELDS[9],
240
2
        ) {
241
2
            Ok(p) => p,
242
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
243
        },
244
2
        create_tmp_table_priv: match parse_privilege_cell_from_editor(
245
2
            parts.get(10).unwrap(),
246
2
            DATABASE_PRIVILEGE_FIELDS[10],
247
2
        ) {
248
2
            Ok(p) => p,
249
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
250
        },
251
2
        lock_tables_priv: match parse_privilege_cell_from_editor(
252
2
            parts.get(11).unwrap(),
253
2
            DATABASE_PRIVILEGE_FIELDS[11],
254
2
        ) {
255
2
            Ok(p) => p,
256
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
257
        },
258
2
        references_priv: match parse_privilege_cell_from_editor(
259
2
            parts.get(12).unwrap(),
260
2
            DATABASE_PRIVILEGE_FIELDS[12],
261
2
        ) {
262
2
            Ok(p) => p,
263
            Err(e) => return PrivilegeRowParseResult::ParserError(e),
264
        },
265
    };
266

            
267
2
    PrivilegeRowParseResult::PrivilegeRow(row)
268
10
}
269

            
270
// TODO: return better errors
271

            
272
1
pub fn parse_privilege_data_from_editor_content(
273
1
    content: String,
274
1
) -> anyhow::Result<Vec<DatabasePrivilegeRow>> {
275
1
    content
276
1
        .trim()
277
1
        .split('\n')
278
10
        .map(|line| line.trim())
279
1
        .map(parse_privilege_row_from_editor)
280
10
        .map(|result| match result {
281
2
            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
1
            PrivilegeRowParseResult::Header => Ok(None),
294
6
            PrivilegeRowParseResult::Comment => Ok(None),
295
1
            PrivilegeRowParseResult::Empty => Ok(None),
296
10
        })
297
10
        .filter_map(|result| result.transpose())
298
1
        .collect::<anyhow::Result<Vec<DatabasePrivilegeRow>>>()
299
1
}
300

            
301
#[cfg(test)]
302
mod tests {
303
    use super::*;
304

            
305
    #[test]
306
1
    fn ensure_generated_and_parsed_editor_content_is_equal() {
307
1
        let permissions = vec![
308
1
            DatabasePrivilegeRow {
309
1
                db: "db".into(),
310
1
                user: "user".into(),
311
1
                select_priv: true,
312
1
                insert_priv: true,
313
1
                update_priv: true,
314
1
                delete_priv: true,
315
1
                create_priv: true,
316
1
                drop_priv: true,
317
1
                alter_priv: true,
318
1
                index_priv: true,
319
1
                create_tmp_table_priv: true,
320
1
                lock_tables_priv: true,
321
1
                references_priv: true,
322
1
            },
323
1
            DatabasePrivilegeRow {
324
1
                db: "db".into(),
325
1
                user: "user".into(),
326
1
                select_priv: false,
327
1
                insert_priv: false,
328
1
                update_priv: false,
329
1
                delete_priv: false,
330
1
                create_priv: false,
331
1
                drop_priv: false,
332
1
                alter_priv: false,
333
1
                index_priv: false,
334
1
                create_tmp_table_priv: false,
335
1
                lock_tables_priv: false,
336
1
                references_priv: false,
337
1
            },
338
        ];
339

            
340
1
        let content = generate_editor_content_from_privilege_data(&permissions, "user", None);
341

            
342
1
        let parsed_permissions = parse_privilege_data_from_editor_content(content).unwrap();
343

            
344
1
        assert_eq!(permissions, parsed_permissions);
345
1
    }
346
}