mockforge_plugin_core/
auth.rs

1//! Authentication plugin interface
2//!
3//! This module defines the AuthPlugin trait and related types for implementing
4//! custom authentication methods in MockForge. Authentication plugins can handle
5//! various authentication schemes like SAML, LDAP, custom OAuth flows, etc.
6
7use crate::{PluginCapabilities, PluginContext, PluginResult, Result};
8use axum::http::{HeaderMap, Method, Uri};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11
12/// Authentication plugin trait
13///
14/// Implement this trait to create custom authentication methods for MockForge.
15/// Authentication plugins are called during the request processing pipeline
16/// to validate incoming requests.
17#[async_trait::async_trait]
18pub trait AuthPlugin: Send + Sync {
19    /// Get plugin capabilities (permissions and limits)
20    fn capabilities(&self) -> PluginCapabilities;
21
22    /// Initialize the plugin with configuration
23    async fn initialize(&self, config: &AuthPluginConfig) -> Result<()>;
24
25    /// Authenticate a request
26    ///
27    /// This method is called for each incoming request that requires authentication.
28    /// The plugin should examine the request headers, body, and other context
29    /// to determine if the request is authenticated.
30    ///
31    /// # Arguments
32    /// * `context` - Plugin execution context
33    /// * `request` - HTTP request information
34    /// * `config` - Plugin configuration
35    ///
36    /// # Returns
37    /// Authentication result indicating success/failure and user claims
38    async fn authenticate(
39        &self,
40        context: &PluginContext,
41        request: &AuthRequest,
42        config: &AuthPluginConfig,
43    ) -> Result<PluginResult<AuthResponse>>;
44
45    /// Validate plugin configuration
46    fn validate_config(&self, config: &AuthPluginConfig) -> Result<()>;
47
48    /// Get supported authentication schemes
49    fn supported_schemes(&self) -> Vec<String>;
50
51    /// Cleanup plugin resources
52    async fn cleanup(&self) -> Result<()>;
53}
54
55/// Authentication plugin configuration
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct AuthPluginConfig {
58    /// Plugin-specific configuration
59    pub config: HashMap<String, serde_json::Value>,
60    /// Enable/disable the plugin
61    pub enabled: bool,
62    /// Plugin priority (lower numbers = higher priority)
63    pub priority: i32,
64    /// Custom settings
65    pub settings: HashMap<String, serde_json::Value>,
66}
67
68impl Default for AuthPluginConfig {
69    fn default() -> Self {
70        Self {
71            config: HashMap::new(),
72            enabled: true,
73            priority: 100,
74            settings: HashMap::new(),
75        }
76    }
77}
78
79/// Authentication request information
80#[derive(Debug, Clone)]
81pub struct AuthRequest {
82    /// HTTP method
83    pub method: Method,
84    /// Request URI
85    pub uri: Uri,
86    /// Request headers
87    pub headers: HeaderMap,
88    /// Request body (if available)
89    pub body: Option<Vec<u8>>,
90    /// Query parameters
91    pub query_params: HashMap<String, String>,
92    /// Client IP address
93    pub client_ip: Option<String>,
94    /// User agent
95    pub user_agent: Option<String>,
96    /// Timestamp when request was received
97    pub timestamp: chrono::DateTime<chrono::Utc>,
98}
99
100impl AuthRequest {
101    /// Create from axum request components
102    pub fn from_axum(method: Method, uri: Uri, headers: HeaderMap, body: Option<Vec<u8>>) -> Self {
103        let query_params = uri
104            .query()
105            .map(|q| url::form_urlencoded::parse(q.as_bytes()).into_owned().collect())
106            .unwrap_or_default();
107
108        let client_ip = headers
109            .get("x-forwarded-for")
110            .or_else(|| headers.get("x-real-ip"))
111            .and_then(|h| h.to_str().ok())
112            .map(|s| s.to_string());
113
114        let user_agent =
115            headers.get("user-agent").and_then(|h| h.to_str().ok()).map(|s| s.to_string());
116
117        Self {
118            method,
119            uri,
120            headers,
121            body,
122            query_params,
123            client_ip,
124            user_agent,
125            timestamp: chrono::Utc::now(),
126        }
127    }
128
129    /// Get authorization header value
130    pub fn authorization_header(&self) -> Option<&str> {
131        self.headers.get("authorization").and_then(|h| h.to_str().ok())
132    }
133
134    /// Get bearer token from authorization header
135    pub fn bearer_token(&self) -> Option<&str> {
136        self.authorization_header().and_then(|auth| auth.strip_prefix("Bearer "))
137    }
138
139    /// Get basic auth credentials from authorization header
140    pub fn basic_credentials(&self) -> Option<(String, String)> {
141        self.authorization_header()
142            .and_then(|auth| auth.strip_prefix("Basic "))
143            .and_then(|encoded| {
144                base64::Engine::decode(&base64::engine::general_purpose::STANDARD, encoded).ok()
145            })
146            .and_then(|decoded| String::from_utf8(decoded).ok())
147            .and_then(|creds| {
148                let parts: Vec<&str> = creds.splitn(2, ':').collect();
149                if parts.len() == 2 {
150                    Some((parts[0].to_string(), parts[1].to_string()))
151                } else {
152                    None
153                }
154            })
155    }
156
157    /// Get custom header value
158    pub fn header(&self, name: &str) -> Option<&str> {
159        self.headers.get(name).and_then(|h| h.to_str().ok())
160    }
161
162    /// Get query parameter value
163    pub fn query_param(&self, name: &str) -> Option<&str> {
164        self.query_params.get(name).map(|s| s.as_str())
165    }
166}
167
168/// Authentication response
169#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct AuthResponse {
171    /// Authentication successful
172    pub authenticated: bool,
173    /// User identity information
174    pub identity: Option<UserIdentity>,
175    /// Authentication claims/tokens
176    pub claims: HashMap<String, serde_json::Value>,
177    /// Additional metadata
178    pub metadata: HashMap<String, serde_json::Value>,
179    /// Error message (if authentication failed)
180    pub error_message: Option<String>,
181}
182
183impl AuthResponse {
184    /// Create successful authentication response
185    pub fn success(identity: UserIdentity, claims: HashMap<String, serde_json::Value>) -> Self {
186        Self {
187            authenticated: true,
188            identity: Some(identity),
189            claims,
190            metadata: HashMap::new(),
191            error_message: None,
192        }
193    }
194
195    /// Create failed authentication response
196    pub fn failure<S: Into<String>>(error_message: S) -> Self {
197        Self {
198            authenticated: false,
199            identity: None,
200            claims: HashMap::new(),
201            metadata: HashMap::new(),
202            error_message: Some(error_message.into()),
203        }
204    }
205
206    /// Add metadata
207    pub fn with_metadata<S: Into<String>>(mut self, key: S, value: serde_json::Value) -> Self {
208        self.metadata.insert(key.into(), value);
209        self
210    }
211
212    /// Check if authentication was successful
213    pub fn is_authenticated(&self) -> bool {
214        self.authenticated
215    }
216
217    /// Get user identity (if authenticated)
218    pub fn identity(&self) -> Option<&UserIdentity> {
219        self.identity.as_ref()
220    }
221
222    /// Get authentication claims
223    pub fn claims(&self) -> &HashMap<String, serde_json::Value> {
224        &self.claims
225    }
226
227    /// Get error message (if authentication failed)
228    pub fn error_message(&self) -> Option<&str> {
229        self.error_message.as_deref()
230    }
231}
232
233/// User identity information
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct UserIdentity {
236    /// Unique user identifier
237    pub user_id: String,
238    /// Username/login
239    pub username: Option<String>,
240    /// Email address
241    pub email: Option<String>,
242    /// Display name
243    pub display_name: Option<String>,
244    /// User roles/permissions
245    pub roles: Vec<String>,
246    /// User groups
247    pub groups: Vec<String>,
248    /// Additional attributes
249    pub attributes: HashMap<String, serde_json::Value>,
250}
251
252impl UserIdentity {
253    /// Create basic user identity
254    pub fn new<S: Into<String>>(user_id: S) -> Self {
255        Self {
256            user_id: user_id.into(),
257            username: None,
258            email: None,
259            display_name: None,
260            roles: Vec::new(),
261            groups: Vec::new(),
262            attributes: HashMap::new(),
263        }
264    }
265
266    /// Set username
267    pub fn with_username<S: Into<String>>(mut self, username: S) -> Self {
268        self.username = Some(username.into());
269        self
270    }
271
272    /// Set email
273    pub fn with_email<S: Into<String>>(mut self, email: S) -> Self {
274        self.email = Some(email.into());
275        self
276    }
277
278    /// Set display name
279    pub fn with_display_name<S: Into<String>>(mut self, display_name: S) -> Self {
280        self.display_name = Some(display_name.into());
281        self
282    }
283
284    /// Add role
285    pub fn with_role<S: Into<String>>(mut self, role: S) -> Self {
286        self.roles.push(role.into());
287        self
288    }
289
290    /// Add multiple roles
291    pub fn with_roles(mut self, roles: Vec<String>) -> Self {
292        self.roles.extend(roles);
293        self
294    }
295
296    /// Add group
297    pub fn with_group<S: Into<String>>(mut self, group: S) -> Self {
298        self.groups.push(group.into());
299        self
300    }
301
302    /// Add attribute
303    pub fn with_attribute<S: Into<String>>(mut self, key: S, value: serde_json::Value) -> Self {
304        self.attributes.insert(key.into(), value);
305        self
306    }
307
308    /// Check if user has a specific role
309    pub fn has_role(&self, role: &str) -> bool {
310        self.roles.iter().any(|r| r == role)
311    }
312
313    /// Check if user is in a specific group
314    pub fn in_group(&self, group: &str) -> bool {
315        self.groups.iter().any(|g| g == group)
316    }
317}
318
319/// Authentication plugin registry entry
320pub struct AuthPluginEntry {
321    /// Plugin ID
322    pub plugin_id: crate::PluginId,
323    /// Plugin instance
324    pub plugin: Box<dyn AuthPlugin>,
325    /// Plugin configuration
326    pub config: AuthPluginConfig,
327    /// Plugin capabilities
328    pub capabilities: PluginCapabilities,
329}
330
331impl AuthPluginEntry {
332    /// Create new plugin entry
333    pub fn new(
334        plugin_id: crate::PluginId,
335        plugin: Box<dyn AuthPlugin>,
336        config: AuthPluginConfig,
337    ) -> Self {
338        let capabilities = plugin.capabilities();
339        Self {
340            plugin_id,
341            plugin,
342            config,
343            capabilities,
344        }
345    }
346
347    /// Check if plugin is enabled
348    pub fn is_enabled(&self) -> bool {
349        self.config.enabled
350    }
351
352    /// Get plugin priority
353    pub fn priority(&self) -> i32 {
354        self.config.priority
355    }
356}
357
358/// Helper trait for creating authentication plugins
359pub trait AuthPluginFactory: Send + Sync {
360    /// Create a new authentication plugin instance
361    fn create_plugin(&self) -> Result<Box<dyn AuthPlugin>>;
362}
363
364#[cfg(test)]
365mod tests {
366
367    #[test]
368    fn test_module_compiles() {
369        // Basic compilation test
370    }
371}