grant/config/
user.rs

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