1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use crate::config::Config;
use crate::connection::{DbConnection, UserDatabaseRole, UserSchemaRole, UserTableRole};
use anyhow::Result;
use ascii_table::AsciiTable;
use indoc::indoc;
use log::info;

pub fn inspect(config: &Config) -> Result<()> {
    let mut conn = DbConnection::new(config);

    let users_in_db = conn.get_users()?;
    let user_database_privileges = conn
        .get_user_database_privileges()
        .unwrap()
        .into_iter()
        .filter(|p| p.database_name == conn.get_current_database().unwrap())
        .collect::<Vec<_>>();
    let user_schema_privileges = conn.get_user_schema_privileges()?;
    let user_table_privileges = conn.get_user_table_privileges()?;

    let mut users = users_in_db
        .iter()
        .map(|u| {
            vec![
                u.name.clone(),
                u.user_super.to_string(),
                get_user_database_privileges(&user_database_privileges, &u.name).unwrap(),
                get_user_schema_privileges(&user_schema_privileges, &u.name).unwrap(),
                get_user_table_privileges(&user_table_privileges, &u.name).unwrap(),
            ]
        })
        .collect::<Vec<_>>();

    users.insert(
        0,
        vec![
            "User".to_string(),
            "Super".to_string(),
            "Current Database".to_string(),
            "Schemas".to_string(),
            "Tables".to_string(),
        ],
    );
    users.insert(
        1,
        vec![
            "---".to_string(),
            "---".to_string(),
            "---".to_string(),
            "---".to_string(),
        ],
    );

    // Get the terminal with
    let term_width = term_size::dimensions().map(|(w, _)| w).unwrap_or(120) - 5;

    // Print the table in max size
    let mut table = AsciiTable::default();
    table.set_max_width(term_width);

    info!(
        "Current users in {}:\n{}",
        config.connection.url,
        table.format(users)
    );

    info!(indoc! { r#"
        == Legend ==

        Database:
            A = ALL Privileges
            C = CREATE
            T = TEMP

        Schema:
            A = ALL Privileges
            C = CREATE
            U = USAGE

        Table:
            A = ALL Privileges
            S = SELECT
            U = UPDATE
            I = INSERT
            D = DELETE
            R = REFERENCES
    "#});

    Ok(())
}

/// Get current user database privileges
fn get_user_database_privileges(privileges: &[UserDatabaseRole], user: &str) -> Result<String> {
    let privileges = privileges
        .iter()
        .filter(|p| p.name == *user) // is current user
        .filter(|p| p.has_create || p.has_temp) // has at least create or temp
        .map(|p| p.perm_to_string(true))
        .collect::<Vec<_>>()
        .join(", ");

    Ok(privileges)
}

/// Get current user schema privileges
fn get_user_schema_privileges(privileges: &[UserSchemaRole], user: &str) -> Result<String> {
    let privileges = privileges
        .iter()
        .filter(|p| p.name == *user)
        .filter(|p| p.has_create || p.has_usage)
        .map(|p| p.perm_to_string(true))
        .collect::<Vec<_>>()
        .join(", ");

    Ok(privileges)
}

/// Get current user schema.table privileges
fn get_user_table_privileges(privileges: &[UserTableRole], user: &str) -> Result<String> {
    let privileges = privileges
        .iter()
        .filter(|p| p.name == *user) // is current user
        .filter(|p| {
            p.has_select || p.has_insert || p.has_update || p.has_delete || p.has_references
        }) // has at least create or select
        .map(|p| p.perm_to_string(true))
        .collect::<Vec<_>>()
        .join(", ");

    Ok(privileges)
}