Skip to main content

actix_security_core/http/security/
context.rs

1//! Security Context for accessing the current authenticated user.
2//!
3//! # Spring Security Equivalent
4//! `org.springframework.security.core.context.SecurityContextHolder`
5//!
6//! # Overview
7//! The SecurityContext provides access to the current authenticated user
8//! from anywhere in your application, not just from request handlers.
9//!
10//! # Usage
11//! ```ignore
12//! use actix_security_core::http::security::context::SecurityContext;
13//!
14//! // In a service layer function
15//! fn get_current_username() -> Option<String> {
16//!     SecurityContext::get_user()
17//!         .map(|user| user.get_username().to_string())
18//! }
19//!
20//! // Check if current user has a role
21//! fn is_admin() -> bool {
22//!     SecurityContext::get_user()
23//!         .map(|user| user.has_role("ADMIN"))
24//!         .unwrap_or(false)
25//! }
26//! ```
27//!
28//! # Thread Safety
29//! The context uses task-local storage, making it safe for async code.
30//! Each async task has its own isolated security context.
31
32use std::cell::RefCell;
33
34use crate::http::security::User;
35
36tokio::task_local! {
37    static SECURITY_CONTEXT: RefCell<Option<User>>;
38}
39
40/// Holder for the current security context.
41///
42/// # Spring Security Equivalent
43/// `SecurityContextHolder`
44///
45/// Provides static methods to access the current authenticated user
46/// from anywhere in the application.
47pub struct SecurityContext;
48
49impl SecurityContext {
50    /// Gets the current authenticated user.
51    ///
52    /// # Spring Security Equivalent
53    /// ```java
54    /// SecurityContextHolder.getContext().getAuthentication().getPrincipal()
55    /// ```
56    ///
57    /// # Returns
58    /// - `Some(User)` if a user is authenticated in the current context
59    /// - `None` if no user is authenticated
60    ///
61    /// # Example
62    /// ```ignore
63    /// use actix_security_core::http::security::context::SecurityContext;
64    ///
65    /// if let Some(user) = SecurityContext::get_user() {
66    ///     println!("Current user: {}", user.get_username());
67    /// }
68    /// ```
69    pub fn get_user() -> Option<User> {
70        SECURITY_CONTEXT
71            .try_with(|ctx| ctx.borrow().clone())
72            .ok()
73            .flatten()
74    }
75
76    /// Gets the current username if authenticated.
77    ///
78    /// # Example
79    /// ```ignore
80    /// let username = SecurityContext::get_username();
81    /// ```
82    pub fn get_username() -> Option<String> {
83        Self::get_user().map(|u| u.get_username().to_string())
84    }
85
86    /// Checks if the current user is authenticated.
87    ///
88    /// # Spring Security Equivalent
89    /// `SecurityContextHolder.getContext().getAuthentication().isAuthenticated()`
90    pub fn is_authenticated() -> bool {
91        Self::get_user().is_some()
92    }
93
94    /// Checks if the current user has the specified role.
95    ///
96    /// # Example
97    /// ```ignore
98    /// if SecurityContext::has_role("ADMIN") {
99    ///     // Admin-only logic
100    /// }
101    /// ```
102    pub fn has_role(role: &str) -> bool {
103        Self::get_user().map(|u| u.has_role(role)).unwrap_or(false)
104    }
105
106    /// Checks if the current user has any of the specified roles.
107    pub fn has_any_role(roles: &[&str]) -> bool {
108        Self::get_user()
109            .map(|u| u.has_any_role(roles))
110            .unwrap_or(false)
111    }
112
113    /// Checks if the current user has the specified authority.
114    ///
115    /// # Example
116    /// ```ignore
117    /// if SecurityContext::has_authority("users:write") {
118    ///     // Write access logic
119    /// }
120    /// ```
121    pub fn has_authority(authority: &str) -> bool {
122        Self::get_user()
123            .map(|u| u.has_authority(authority))
124            .unwrap_or(false)
125    }
126
127    /// Checks if the current user has any of the specified authorities.
128    pub fn has_any_authority(authorities: &[&str]) -> bool {
129        Self::get_user()
130            .map(|u| u.has_any_authority(authorities))
131            .unwrap_or(false)
132    }
133
134    /// Runs a closure with the given user set in the security context.
135    ///
136    /// # Spring Security Equivalent
137    /// `SecurityContextHolder.setContext(context)`
138    ///
139    /// This is primarily used internally by the security middleware.
140    ///
141    /// # Example
142    /// ```ignore
143    /// let user = User::new("admin".into(), "password".into());
144    ///
145    /// SecurityContext::run_with(Some(user), async {
146    ///     // Code here can access SecurityContext::get_user()
147    ///     let username = SecurityContext::get_username();
148    /// }).await;
149    /// ```
150    pub async fn run_with<F, R>(user: Option<User>, f: F) -> R
151    where
152        F: std::future::Future<Output = R>,
153    {
154        SECURITY_CONTEXT.scope(RefCell::new(user), f).await
155    }
156
157    /// Sets the user in the current security context.
158    ///
159    /// # Warning
160    /// This should only be called from within a `run_with` scope.
161    /// Calling it outside a scope will have no effect.
162    pub fn set_user(user: Option<User>) {
163        let _ = SECURITY_CONTEXT.try_with(|ctx| {
164            *ctx.borrow_mut() = user;
165        });
166    }
167
168    /// Clears the security context.
169    ///
170    /// # Spring Security Equivalent
171    /// `SecurityContextHolder.clearContext()`
172    pub fn clear() {
173        Self::set_user(None);
174    }
175}
176
177/// Guard that clears the security context when dropped.
178///
179/// Useful for ensuring the context is cleared even if a panic occurs.
180pub struct SecurityContextGuard;
181
182impl Drop for SecurityContextGuard {
183    fn drop(&mut self) {
184        SecurityContext::clear();
185    }
186}