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()
104            .map(|u| u.has_role(role))
105            .unwrap_or(false)
106    }
107
108    /// Checks if the current user has any of the specified roles.
109    pub fn has_any_role(roles: &[&str]) -> bool {
110        Self::get_user()
111            .map(|u| u.has_any_role(roles))
112            .unwrap_or(false)
113    }
114
115    /// Checks if the current user has the specified authority.
116    ///
117    /// # Example
118    /// ```ignore
119    /// if SecurityContext::has_authority("users:write") {
120    ///     // Write access logic
121    /// }
122    /// ```
123    pub fn has_authority(authority: &str) -> bool {
124        Self::get_user()
125            .map(|u| u.has_authority(authority))
126            .unwrap_or(false)
127    }
128
129    /// Checks if the current user has any of the specified authorities.
130    pub fn has_any_authority(authorities: &[&str]) -> bool {
131        Self::get_user()
132            .map(|u| u.has_any_authority(authorities))
133            .unwrap_or(false)
134    }
135
136    /// Runs a closure with the given user set in the security context.
137    ///
138    /// # Spring Security Equivalent
139    /// `SecurityContextHolder.setContext(context)`
140    ///
141    /// This is primarily used internally by the security middleware.
142    ///
143    /// # Example
144    /// ```ignore
145    /// let user = User::new("admin".into(), "password".into());
146    ///
147    /// SecurityContext::run_with(Some(user), async {
148    ///     // Code here can access SecurityContext::get_user()
149    ///     let username = SecurityContext::get_username();
150    /// }).await;
151    /// ```
152    pub async fn run_with<F, R>(user: Option<User>, f: F) -> R
153    where
154        F: std::future::Future<Output = R>,
155    {
156        SECURITY_CONTEXT
157            .scope(RefCell::new(user), f)
158            .await
159    }
160
161    /// Sets the user in the current security context.
162    ///
163    /// # Warning
164    /// This should only be called from within a `run_with` scope.
165    /// Calling it outside a scope will have no effect.
166    pub fn set_user(user: Option<User>) {
167        let _ = SECURITY_CONTEXT.try_with(|ctx| {
168            *ctx.borrow_mut() = user;
169        });
170    }
171
172    /// Clears the security context.
173    ///
174    /// # Spring Security Equivalent
175    /// `SecurityContextHolder.clearContext()`
176    pub fn clear() {
177        Self::set_user(None);
178    }
179}
180
181/// Guard that clears the security context when dropped.
182///
183/// Useful for ensuring the context is cleared even if a panic occurs.
184pub struct SecurityContextGuard;
185
186impl Drop for SecurityContextGuard {
187    fn drop(&mut self) {
188        SecurityContext::clear();
189    }
190}