Skip to main content

grant/
inspect.rs

1use crate::config::Config;
2use crate::connection::{DbConnection, UserDatabaseRole, UserSchemaRole, UserTableRole};
3use anyhow::{anyhow, Result};
4use ascii_table::AsciiTable;
5use indoc::indoc;
6use log::info;
7
8pub fn inspect(config: &Config) -> Result<()> {
9    let mut conn = DbConnection::new(config)?;
10
11    let users_in_db = conn.get_users()?;
12    let current_db_name = conn
13        .get_current_database()
14        .map(|s| s.to_string())
15        .ok_or_else(|| anyhow!("Could not determine current database"))?;
16
17    let user_database_privileges = conn
18        .get_user_database_privileges()?
19        .into_iter()
20        .filter(|p| p.database_name == current_db_name)
21        .collect::<Vec<_>>();
22    let user_schema_privileges = conn.get_user_schema_privileges()?;
23    let user_table_privileges = conn.get_user_table_privileges()?;
24
25    let mut users = users_in_db
26        .iter()
27        .map(|u| {
28            vec![
29                u.name.clone(),
30                u.user_super.to_string(),
31                get_user_database_privileges(&user_database_privileges, &u.name),
32                get_user_schema_privileges(&user_schema_privileges, &u.name),
33                get_user_table_privileges(&user_table_privileges, &u.name),
34            ]
35        })
36        .collect::<Vec<_>>();
37
38    users.insert(
39        0,
40        vec![
41            "User".to_string(),
42            "Super".to_string(),
43            "Current Database".to_string(),
44            "Schemas".to_string(),
45            "Tables".to_string(),
46        ],
47    );
48    users.insert(
49        1,
50        vec![
51            "---".to_string(),
52            "---".to_string(),
53            "---".to_string(),
54            "---".to_string(),
55        ],
56    );
57
58    // Get the terminal with
59    let term_width = term_size::dimensions().map(|(w, _)| w).unwrap_or(120) - 5;
60
61    // Print the table in max size
62    let mut table = AsciiTable::default();
63    table.set_max_width(term_width);
64
65    info!(
66        "Current users in {}:\n{}",
67        config.connection.url,
68        table.format(users)
69    );
70
71    info!(indoc! { r#"
72        == Legend ==
73
74        Database:
75            A = ALL Privileges
76            C = CREATE
77            T = TEMP
78
79        Schema:
80            A = ALL Privileges
81            C = CREATE
82            U = USAGE
83
84        Table:
85            A = ALL Privileges
86            S = SELECT
87            U = UPDATE
88            I = INSERT
89            D = DELETE
90            R = REFERENCES
91    "#});
92
93    Ok(())
94}
95
96/// Get current user database privileges
97fn get_user_database_privileges(privileges: &[UserDatabaseRole], user: &str) -> String {
98    privileges
99        .iter()
100        .filter(|p| p.name == *user) // is current user
101        .filter(|p| p.has_create || p.has_temp) // has at least create or temp
102        .map(|p| p.perm_to_string(true))
103        .collect::<Vec<_>>()
104        .join(", ")
105}
106
107/// Get current user schema privileges
108fn get_user_schema_privileges(privileges: &[UserSchemaRole], user: &str) -> String {
109    privileges
110        .iter()
111        .filter(|p| p.name == *user)
112        .filter(|p| p.has_create || p.has_usage)
113        .map(|p| p.perm_to_string(true))
114        .collect::<Vec<_>>()
115        .join(", ")
116}
117
118/// Get current user schema.table privileges
119fn get_user_table_privileges(privileges: &[UserTableRole], user: &str) -> String {
120    privileges
121        .iter()
122        .filter(|p| p.name == *user) // is current user
123        .filter(|p| {
124            p.has_select || p.has_insert || p.has_update || p.has_delete || p.has_references
125        }) // has at least create or select
126        .map(|p| p.perm_to_string(true))
127        .collect::<Vec<_>>()
128        .join(", ")
129}