Skip to main content

grant/config/
user.rs

1use super::sql_utils::escape_sql_string;
2use anyhow::{anyhow, Result};
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
6pub struct User {
7    pub name: String,
8    // password is optional
9    pub password: Option<String>,
10    // Need to update password at anytime? by default is false
11    pub update_password: Option<bool>,
12    pub roles: Vec<String>,
13}
14
15impl User {
16    /// Validate and format username for SQL (must be valid identifier)
17    fn format_username(name: &str) -> Result<String> {
18        if name.is_empty() {
19            return Err(anyhow!("Username cannot be empty"));
20        }
21
22        // Check if username contains only valid characters (alphanumeric, underscore, no spaces)
23        if !name.chars().all(|c| c.is_alphanumeric() || c == '_') {
24            return Err(anyhow!("Username '{}' contains invalid characters. Only alphanumeric and underscore allowed.", name));
25        }
26
27        // Ensure username doesn't start with a number
28        if name.chars().next().unwrap().is_numeric() {
29            return Err(anyhow!("Username '{}' cannot start with a number", name));
30        }
31
32        Ok(name.to_string())
33    }
34
35    pub fn to_sql_create(&self) -> Result<String> {
36        let username = Self::format_username(&self.name)?;
37        let password = match &self.password {
38            Some(p) => format!(" WITH PASSWORD '{}'", escape_sql_string(p)),
39            None => "".to_string(),
40        };
41
42        Ok(format!("CREATE USER {}{};", username, password))
43    }
44
45    pub fn to_sql_update(&self) -> Result<String> {
46        let username = Self::format_username(&self.name)?;
47        let password = match &self.password {
48            Some(p) => format!(" WITH PASSWORD '{}'", escape_sql_string(p)),
49            None => "".to_string(),
50        };
51
52        Ok(format!("ALTER USER {}{};", username, password))
53    }
54
55    pub fn to_sql_drop(&self) -> Result<String> {
56        let username = Self::format_username(&self.name)?;
57        Ok(format!("DROP USER IF EXISTS {};", username))
58    }
59
60    pub fn validate(&self) -> Result<()> {
61        // Use the format_username validation
62        Self::format_username(&self.name)?;
63        Ok(())
64    }
65
66    pub fn get_name(&self) -> &str {
67        &self.name
68    }
69
70    pub fn get_password(&self) -> &str {
71        self.password.as_ref().map(|s| s.as_str()).unwrap_or("")
72    }
73
74    pub fn get_roles(&self) -> &[String] {
75        &self.roles
76    }
77}
78
79// Test
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn test_user_to_sql_create() {
86        let user = User {
87            name: "test".to_string(),
88            password: Some("test".to_string()),
89            update_password: Some(true),
90            roles: vec!["test".to_string()],
91        };
92
93        let sql = user.to_sql_create().unwrap();
94        assert_eq!(sql, "CREATE USER test WITH PASSWORD 'test';");
95    }
96
97    #[test]
98    fn test_user_to_sql_update() {
99        let user = User {
100            name: "test".to_string(),
101            password: Some("test".to_string()),
102            update_password: Some(true),
103            roles: vec!["test".to_string()],
104        };
105
106        let sql = user.to_sql_update().unwrap();
107        assert_eq!(sql, "ALTER USER test WITH PASSWORD 'test';");
108    }
109
110    #[test]
111    fn test_user_to_sql_drop() {
112        let user = User {
113            name: "test".to_string(),
114            password: Some("test".to_string()),
115            update_password: Some(true),
116            roles: vec!["test".to_string()],
117        };
118
119        let sql = user.to_sql_drop().unwrap();
120        assert_eq!(sql, "DROP USER IF EXISTS test;");
121    }
122
123    #[test]
124    fn test_sql_injection_prevention_password() {
125        let user = User {
126            name: "test".to_string(),
127            password: Some("'; DROP TABLE users; --".to_string()),
128            update_password: Some(true),
129            roles: vec!["test".to_string()],
130        };
131
132        let sql = user.to_sql_create().unwrap();
133        assert_eq!(
134            sql,
135            "CREATE USER test WITH PASSWORD '''; DROP TABLE users; --';"
136        );
137    }
138
139    #[test]
140    fn test_invalid_username_with_spaces() {
141        let user = User {
142            name: "test user".to_string(),
143            password: Some("password".to_string()),
144            update_password: Some(true),
145            roles: vec!["test".to_string()],
146        };
147
148        assert!(user.to_sql_create().is_err());
149        assert!(user.validate().is_err());
150    }
151
152    #[test]
153    fn test_invalid_username_starting_with_number() {
154        let user = User {
155            name: "1test".to_string(),
156            password: Some("password".to_string()),
157            update_password: Some(true),
158            roles: vec!["test".to_string()],
159        };
160
161        assert!(user.to_sql_create().is_err());
162        assert!(user.validate().is_err());
163    }
164
165    #[test]
166    fn test_user_validate() {
167        let user = User {
168            name: "test".to_string(),
169            password: Some("test".to_string()),
170            update_password: Some(true),
171            roles: vec!["test".to_string()],
172        };
173
174        assert!(user.validate().is_ok());
175    }
176
177    #[test]
178    fn test_user_validate_empty_name() {
179        let user = User {
180            name: "".to_string(),
181            password: Some("test".to_string()),
182            update_password: Some(true),
183            roles: vec!["test".to_string()],
184        };
185
186        assert!(user.validate().is_err());
187    }
188
189    #[test]
190    fn test_user_validate_empty_password() {
191        let user = User {
192            name: "test".to_string(),
193            password: None,
194            update_password: Some(true),
195            roles: vec!["test".to_string()],
196        };
197
198        assert!(user.validate().is_ok());
199    }
200
201    #[test]
202    fn test_user_validate_empty_roles() {
203        let user = User {
204            name: "test".to_string(),
205            password: Some("test".to_string()),
206            update_password: Some(true),
207            roles: vec![],
208        };
209
210        assert!(user.validate().is_ok());
211    }
212
213    #[test]
214    fn test_user_get_name() {
215        let user = User {
216            name: "test".to_string(),
217            password: Some("test".to_string()),
218            update_password: Some(true),
219            roles: vec!["test".to_string()],
220        };
221
222        assert_eq!(user.get_name(), "test");
223    }
224
225    #[test]
226    fn test_user_get_password() {
227        let user = User {
228            name: "test".to_string(),
229            password: Some("test".to_string()),
230            update_password: Some(true),
231            roles: vec!["test".to_string()],
232        };
233
234        assert_eq!(user.get_password(), "test");
235    }
236
237    #[test]
238    fn test_user_get_roles() {
239        let user = User {
240            name: "test".to_string(),
241            password: Some("test".to_string()),
242            update_password: Some(true),
243            roles: vec!["test".to_string()],
244        };
245
246        assert_eq!(user.get_roles(), vec!["test".to_string()]);
247    }
248}