Skip to main content

actix_security_core/http/security/
user.rs

1//! User model for authentication and authorization.
2//!
3//! # Spring Equivalent
4//! `UserDetails` interface
5
6use std::fmt;
7
8/// Represents an authenticated user with roles and authorities.
9///
10/// # Spring Equivalent
11/// `UserDetails` / `User`
12///
13/// # Example
14/// ```
15/// use actix_security_core::http::security::User;
16///
17/// let user = User::new("admin".into(), "password".into())
18///     .roles(&["ADMIN".into(), "USER".into()])
19///     .authorities(&["users:read".into(), "users:write".into()]);
20///
21/// assert!(user.has_role("ADMIN"));
22/// assert!(user.has_authority("users:read"));
23/// ```
24#[derive(Clone, Debug)]
25pub struct User {
26    username: String,
27    password: String,
28    roles: Vec<String>,
29    authorities: Vec<String>,
30}
31
32impl User {
33    /// Creates a new user with username and plain-text password.
34    ///
35    /// # Note
36    /// For production use, prefer `with_encoded_password` with a proper
37    /// password encoder like Argon2.
38    pub fn new(username: String, password: String) -> Self {
39        User {
40            username,
41            password,
42            roles: Vec::new(),
43            authorities: Vec::new(),
44        }
45    }
46
47    /// Creates a new user with username and pre-encoded password.
48    ///
49    /// # Spring Security Equivalent
50    /// `User.withUsername().password("{bcrypt}$2a$...").build()`
51    ///
52    /// # Example
53    /// ```
54    /// use actix_security_core::http::security::{User, Argon2PasswordEncoder, PasswordEncoder};
55    ///
56    /// let encoder = Argon2PasswordEncoder::new();
57    /// let encoded = encoder.encode("secret");
58    ///
59    /// let user = User::with_encoded_password("admin", encoded)
60    ///     .roles(&["ADMIN".into()]);
61    /// ```
62    pub fn with_encoded_password(username: &str, encoded_password: String) -> Self {
63        User {
64            username: username.to_string(),
65            password: encoded_password,
66            roles: Vec::new(),
67            authorities: Vec::new(),
68        }
69    }
70
71    /// Returns the username.
72    pub fn get_username(&self) -> &str {
73        &self.username
74    }
75
76    /// Returns the password (for authentication checks).
77    pub fn get_password(&self) -> &str {
78        &self.password
79    }
80
81    /// Returns the user's roles.
82    pub fn get_roles(&self) -> &[String] {
83        &self.roles
84    }
85
86    /// Returns the user's authorities.
87    pub fn get_authorities(&self) -> &[String] {
88        &self.authorities
89    }
90
91    /// Adds roles to the user (builder pattern).
92    pub fn roles(mut self, roles: &[String]) -> Self {
93        for role in roles {
94            if !self.roles.contains(role) {
95                self.roles.push(role.clone());
96            }
97        }
98        self
99    }
100
101    /// Adds authorities to the user (builder pattern).
102    pub fn authorities(mut self, authorities: &[String]) -> Self {
103        for authority in authorities {
104            if !self.authorities.contains(authority) {
105                self.authorities.push(authority.clone());
106            }
107        }
108        self
109    }
110
111    /// Checks if the user has a specific role.
112    pub fn has_role(&self, role: &str) -> bool {
113        self.roles.iter().any(|r| r == role)
114    }
115
116    /// Checks if the user has ANY of the specified roles (OR logic).
117    pub fn has_any_role(&self, roles: &[&str]) -> bool {
118        roles.iter().any(|role| self.has_role(role))
119    }
120
121    /// Checks if the user has ALL of the specified roles (AND logic).
122    pub fn has_all_roles(&self, roles: &[&str]) -> bool {
123        roles.iter().all(|role| self.has_role(role))
124    }
125
126    /// Checks if the user has a specific authority.
127    pub fn has_authority(&self, authority: &str) -> bool {
128        self.authorities.iter().any(|a| a == authority)
129    }
130
131    /// Checks if the user has ANY of the specified authorities (OR logic).
132    pub fn has_any_authority(&self, authorities: &[&str]) -> bool {
133        authorities.iter().any(|auth| self.has_authority(auth))
134    }
135
136    /// Checks if the user has ALL of the specified authorities (AND logic).
137    pub fn has_all_authorities(&self, authorities: &[&str]) -> bool {
138        authorities.iter().all(|auth| self.has_authority(auth))
139    }
140
141    // Legacy methods for backward compatibility with Vec<String> parameters
142
143    /// Checks if the user has ANY of the specified roles (legacy).
144    pub fn has_roles(&self, roles: &[String]) -> bool {
145        roles.iter().any(|role| self.roles.contains(role))
146    }
147
148    /// Checks if the user has ANY of the specified authorities (legacy).
149    #[doc(hidden)]
150    pub fn has_authorities(&self, authorities: &[String]) -> bool {
151        authorities
152            .iter()
153            .any(|auth| self.authorities.contains(auth))
154    }
155}
156
157impl fmt::Display for User {
158    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
159        write!(
160            f,
161            "User {{ username: {}, roles: {:?}, authorities: {:?} }}",
162            self.username, self.roles, self.authorities
163        )
164    }
165}
166
167#[cfg(test)]
168mod tests {
169    use super::*;
170
171    // =============================================================================
172    // User Creation Tests
173    // =============================================================================
174
175    #[test]
176    fn test_user_new() {
177        let user = User::new("alice".to_string(), "secret".to_string());
178        assert_eq!(user.get_username(), "alice");
179        assert_eq!(user.get_password(), "secret");
180        assert!(user.get_roles().is_empty());
181        assert!(user.get_authorities().is_empty());
182    }
183
184    #[test]
185    fn test_user_with_encoded_password() {
186        let user = User::with_encoded_password("bob", "encoded_hash".to_string());
187        assert_eq!(user.get_username(), "bob");
188        assert_eq!(user.get_password(), "encoded_hash");
189    }
190
191    // =============================================================================
192    // Builder Pattern Tests
193    // =============================================================================
194
195    #[test]
196    fn test_user_roles_builder() {
197        let user = User::new("admin".to_string(), "pass".to_string())
198            .roles(&["ADMIN".into(), "USER".into()]);
199
200        assert_eq!(user.get_roles().len(), 2);
201        assert!(user.get_roles().contains(&"ADMIN".to_string()));
202        assert!(user.get_roles().contains(&"USER".to_string()));
203    }
204
205    #[test]
206    fn test_user_authorities_builder() {
207        let user = User::new("admin".to_string(), "pass".to_string())
208            .authorities(&["users:read".into(), "users:write".into()]);
209
210        assert_eq!(user.get_authorities().len(), 2);
211        assert!(user.get_authorities().contains(&"users:read".to_string()));
212        assert!(user.get_authorities().contains(&"users:write".to_string()));
213    }
214
215    #[test]
216    fn test_user_chained_builder() {
217        let user = User::new("admin".to_string(), "pass".to_string())
218            .roles(&["ADMIN".into()])
219            .authorities(&["api:read".into()]);
220
221        assert_eq!(user.get_roles().len(), 1);
222        assert_eq!(user.get_authorities().len(), 1);
223    }
224
225    #[test]
226    fn test_user_roles_no_duplicates() {
227        let user = User::new("admin".to_string(), "pass".to_string())
228            .roles(&["ADMIN".into(), "USER".into()])
229            .roles(&["ADMIN".into(), "MANAGER".into()]);
230
231        // Should have 3 unique roles: ADMIN, USER, MANAGER (no duplicate ADMIN)
232        assert_eq!(user.get_roles().len(), 3);
233    }
234
235    #[test]
236    fn test_user_authorities_no_duplicates() {
237        let user = User::new("admin".to_string(), "pass".to_string())
238            .authorities(&["read".into(), "write".into()])
239            .authorities(&["read".into(), "delete".into()]);
240
241        // Should have 3 unique authorities
242        assert_eq!(user.get_authorities().len(), 3);
243    }
244
245    // =============================================================================
246    // Role Check Tests
247    // =============================================================================
248
249    #[test]
250    fn test_has_role() {
251        let user = User::new("admin".to_string(), "pass".to_string())
252            .roles(&["ADMIN".into(), "USER".into()]);
253
254        assert!(user.has_role("ADMIN"));
255        assert!(user.has_role("USER"));
256        assert!(!user.has_role("MANAGER"));
257    }
258
259    #[test]
260    fn test_has_any_role() {
261        let user = User::new("user".to_string(), "pass".to_string()).roles(&["USER".into()]);
262
263        assert!(user.has_any_role(&["ADMIN", "USER"]));
264        assert!(user.has_any_role(&["USER", "GUEST"]));
265        assert!(!user.has_any_role(&["ADMIN", "MANAGER"]));
266    }
267
268    #[test]
269    fn test_has_all_roles() {
270        let user = User::new("admin".to_string(), "pass".to_string()).roles(&[
271            "ADMIN".into(),
272            "USER".into(),
273            "MANAGER".into(),
274        ]);
275
276        assert!(user.has_all_roles(&["ADMIN", "USER"]));
277        assert!(user.has_all_roles(&["ADMIN", "USER", "MANAGER"]));
278        assert!(!user.has_all_roles(&["ADMIN", "SUPERADMIN"]));
279    }
280
281    #[test]
282    fn test_has_any_role_empty() {
283        let user = User::new("guest".to_string(), "pass".to_string());
284
285        assert!(!user.has_any_role(&["ADMIN", "USER"]));
286    }
287
288    #[test]
289    fn test_has_all_roles_empty_requirement() {
290        let user = User::new("user".to_string(), "pass".to_string()).roles(&["USER".into()]);
291
292        // Empty requirement should return true (vacuously true)
293        assert!(user.has_all_roles(&[]));
294    }
295
296    // =============================================================================
297    // Authority Check Tests
298    // =============================================================================
299
300    #[test]
301    fn test_has_authority() {
302        let user = User::new("admin".to_string(), "pass".to_string())
303            .authorities(&["users:read".into(), "users:write".into()]);
304
305        assert!(user.has_authority("users:read"));
306        assert!(user.has_authority("users:write"));
307        assert!(!user.has_authority("users:delete"));
308    }
309
310    #[test]
311    fn test_has_any_authority() {
312        let user =
313            User::new("reader".to_string(), "pass".to_string()).authorities(&["read".into()]);
314
315        assert!(user.has_any_authority(&["read", "write"]));
316        assert!(!user.has_any_authority(&["write", "delete"]));
317    }
318
319    #[test]
320    fn test_has_all_authorities() {
321        let user = User::new("admin".to_string(), "pass".to_string()).authorities(&[
322            "read".into(),
323            "write".into(),
324            "delete".into(),
325        ]);
326
327        assert!(user.has_all_authorities(&["read", "write"]));
328        assert!(user.has_all_authorities(&["read", "write", "delete"]));
329        assert!(!user.has_all_authorities(&["read", "admin"]));
330    }
331
332    // =============================================================================
333    // Legacy Method Tests
334    // =============================================================================
335
336    #[test]
337    fn test_has_roles_legacy() {
338        let user = User::new("user".to_string(), "pass".to_string())
339            .roles(&["USER".into(), "READER".into()]);
340
341        assert!(user.has_roles(&["USER".to_string()]));
342        assert!(user.has_roles(&["ADMIN".to_string(), "USER".to_string()]));
343        assert!(!user.has_roles(&["ADMIN".to_string()]));
344    }
345
346    #[test]
347    fn test_has_authorities_legacy() {
348        let user =
349            User::new("user".to_string(), "pass".to_string()).authorities(&["api:read".into()]);
350
351        assert!(user.has_authorities(&["api:read".to_string()]));
352        assert!(user.has_authorities(&["api:write".to_string(), "api:read".to_string()]));
353        assert!(!user.has_authorities(&["api:write".to_string()]));
354    }
355
356    // =============================================================================
357    // Display Tests
358    // =============================================================================
359
360    #[test]
361    fn test_display() {
362        let user = User::new("admin".to_string(), "secret".to_string())
363            .roles(&["ADMIN".into()])
364            .authorities(&["read".into()]);
365
366        let display = format!("{}", user);
367        assert!(display.contains("admin"));
368        assert!(display.contains("ADMIN"));
369        assert!(display.contains("read"));
370        // Should not contain password
371        assert!(!display.contains("secret"));
372    }
373
374    // =============================================================================
375    // Clone Tests
376    // =============================================================================
377
378    #[test]
379    fn test_user_clone() {
380        let original = User::new("admin".to_string(), "pass".to_string())
381            .roles(&["ADMIN".into()])
382            .authorities(&["read".into()]);
383
384        let cloned = original.clone();
385
386        assert_eq!(cloned.get_username(), original.get_username());
387        assert_eq!(cloned.get_password(), original.get_password());
388        assert_eq!(cloned.get_roles(), original.get_roles());
389        assert_eq!(cloned.get_authorities(), original.get_authorities());
390    }
391
392    // =============================================================================
393    // Edge Case Tests
394    // =============================================================================
395
396    #[test]
397    fn test_empty_username() {
398        let user = User::new("".to_string(), "pass".to_string());
399        assert_eq!(user.get_username(), "");
400    }
401
402    #[test]
403    fn test_special_characters_in_role() {
404        let user = User::new("user".to_string(), "pass".to_string())
405            .roles(&["ROLE:ADMIN".into(), "users:write".into()]);
406
407        assert!(user.has_role("ROLE:ADMIN"));
408        assert!(user.has_role("users:write"));
409    }
410
411    #[test]
412    fn test_case_sensitive_roles() {
413        let user = User::new("user".to_string(), "pass".to_string()).roles(&["ADMIN".into()]);
414
415        assert!(user.has_role("ADMIN"));
416        assert!(!user.has_role("admin")); // Case sensitive
417        assert!(!user.has_role("Admin")); // Case sensitive
418    }
419
420    #[test]
421    fn test_case_sensitive_authorities() {
422        let user =
423            User::new("user".to_string(), "pass".to_string()).authorities(&["users:read".into()]);
424
425        assert!(user.has_authority("users:read"));
426        assert!(!user.has_authority("USERS:READ")); // Case sensitive
427    }
428}