Skip to main content

auth_framework/server/oauth/
rich_authorization_requests.rs

1//! # Rich Authorization Requests (RAR) - RFC 9396
2//!
3//! This module implements the Rich Authorization Requests (RAR) specification,
4//! enabling fine-grained authorization requests with detailed resource descriptions
5//! and complex permission structures.
6//!
7//! ## Overview
8//!
9//! RAR extends OAuth 2.0 authorization requests to include detailed, structured
10//! authorization details that specify exactly what resources and actions are
11//! being requested, providing much more granular control than traditional scopes.
12//!
13//! ## Key Features
14//!
15//! - **Structured Authorization Details**: Complex resource descriptions with actions and permissions
16//! - **Multi-Resource Requests**: Single authorization request covering multiple resources
17//! - **Fine-Grained Permissions**: Detailed access control beyond simple scopes
18//! - **Resource-Specific Data**: Additional metadata and constraints per resource
19//! - **Privilege Escalation Control**: Granular control over permission increases
20//! - **Dynamic Resource Discovery**: Runtime resource identification and permission mapping
21//!
22//! ## Authorization Detail Types
23//!
24//! - **Resource Access**: File systems, databases, APIs, services
25//! - **Action Permissions**: Read, write, delete, execute, manage
26//! - **Time-Based Access**: Temporal restrictions and expiration
27//! - **Location-Based Access**: Geographic or network-based restrictions
28//! - **Data-Specific Access**: Field-level or record-level permissions
29//!
30//! ## Usage Example
31//!
32//! ```rust,no_run
33//! use auth_framework::server::oauth::rich_authorization_requests::*;
34//! use auth_framework::server::SessionManager;
35//! use std::sync::Arc;
36//!
37//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
38//! // Initialize RAR manager — chainable builder
39//! let config = RarConfig::empty()
40//!     .with_type("file_access", &["read", "write", "delete"])
41//!     .with_type("api_access", &["read", "write", "execute"])
42//!     .with_type("database_access", &["select", "insert", "update", "delete"])
43//!     .max_details(50)
44//!     .resource_discovery(true);
45//!
46//! let session_manager = Arc::new(SessionManager::new(Default::default()));
47//! let rar_manager = RarManager::new(config, session_manager);
48//!
49//! // Create complex authorization request
50//! let auth_request = RarAuthorizationRequest {
51//!     client_id: "app123".to_string(),
52//!     response_type: "code".to_string(),
53//!     authorization_details: vec![
54//!         AuthorizationDetail {
55//!             type_: "file_access".to_string(),
56//!             actions: Some(vec!["read".to_string(), "write".to_string()]),
57//!             locations: Some(vec!["https://files.example.com/docs/*".to_string()]),
58//!             datatypes: Some(vec!["document".to_string(), "image".to_string()]),
59//!             identifier: Some("project_files".to_string()),
60//!             privileges: Some(vec!["editor".to_string()]),
61//!             ..Default::default()
62//!         }
63//!     ],
64//!     ..Default::default()
65//! };
66//!
67//! // Process authorization request
68//! let result = rar_manager.process_authorization_request(auth_request, "user123").await?;
69//! # Ok(())
70//! # }
71//! ```
72
73use crate::errors::{AuthError, Result};
74use crate::server::oidc::oidc_session_management::SessionManager;
75
76use async_trait::async_trait;
77use chrono::{DateTime, Duration, Utc};
78use serde::{Deserialize, Serialize};
79use std::collections::{HashMap, HashSet};
80use std::sync::Arc;
81use uuid::Uuid;
82
83/// Configuration for Rich Authorization Requests
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct RarConfig {
86    /// Maximum number of authorization details per request
87    pub max_authorization_details: usize,
88
89    /// Supported authorization detail types
90    pub supported_types: Vec<String>,
91
92    /// Whether to require explicit user consent for each detail
93    pub require_explicit_consent: bool,
94
95    /// Maximum depth for nested resource hierarchies
96    pub max_resource_depth: usize,
97
98    /// Default authorization detail lifetime
99    pub default_lifetime: Duration,
100
101    /// Whether to support resource discovery
102    pub enable_resource_discovery: bool,
103
104    /// Custom validation rules for authorization details
105    pub validation_rules: Vec<RarValidationRule>,
106
107    /// Supported actions per resource type
108    pub type_action_mapping: HashMap<String, Vec<String>>,
109}
110
111impl Default for RarConfig {
112    fn default() -> Self {
113        let mut type_action_mapping = HashMap::new();
114        type_action_mapping.insert(
115            "file_access".to_string(),
116            vec![
117                "read".to_string(),
118                "write".to_string(),
119                "delete".to_string(),
120            ],
121        );
122        type_action_mapping.insert(
123            "api_access".to_string(),
124            vec![
125                "read".to_string(),
126                "write".to_string(),
127                "execute".to_string(),
128            ],
129        );
130        type_action_mapping.insert(
131            "database_access".to_string(),
132            vec![
133                "select".to_string(),
134                "insert".to_string(),
135                "update".to_string(),
136                "delete".to_string(),
137            ],
138        );
139
140        Self {
141            max_authorization_details: 10,
142            supported_types: vec![
143                "file_access".to_string(),
144                "api_access".to_string(),
145                "database_access".to_string(),
146                "payment_initiation".to_string(),
147                "account_information".to_string(),
148            ],
149            require_explicit_consent: true,
150            max_resource_depth: 5,
151            default_lifetime: Duration::try_hours(1).unwrap_or(Duration::zero()),
152            enable_resource_discovery: false,
153            validation_rules: Vec::new(),
154            type_action_mapping,
155        }
156    }
157}
158
159impl RarConfig {
160    /// Create a minimal configuration with no pre-registered types.
161    ///
162    /// Useful when the default file/api/database types are not relevant
163    /// and you want to register only your own types via [`with_type`](Self::with_type).
164    pub fn empty() -> Self {
165        Self {
166            supported_types: Vec::new(),
167            type_action_mapping: HashMap::new(),
168            ..Default::default()
169        }
170    }
171
172    /// Register (or replace) a supported type and its allowed actions (chainable).
173    ///
174    /// The type name is also added to `supported_types` if not already present.
175    ///
176    /// ```rust,no_run
177    /// use auth_framework::server::oauth::rich_authorization_requests::RarConfig;
178    ///
179    /// let config = RarConfig::empty()
180    ///     .with_type("file_access", &["read", "write", "delete"])
181    ///     .with_type("payment", &["initiate", "confirm"]);
182    /// ```
183    pub fn with_type(mut self, type_name: &str, actions: &[&str]) -> Self {
184        let name = type_name.to_string();
185        if !self.supported_types.contains(&name) {
186            self.supported_types.push(name.clone());
187        }
188        self.type_action_mapping.insert(
189            name,
190            actions.iter().map(|a| a.to_string()).collect(),
191        );
192        self
193    }
194
195    /// Set the maximum number of authorization details per request (chainable).
196    pub fn max_details(mut self, max: usize) -> Self {
197        self.max_authorization_details = max;
198        self
199    }
200
201    /// Enable or disable resource discovery (chainable).
202    pub fn resource_discovery(mut self, enabled: bool) -> Self {
203        self.enable_resource_discovery = enabled;
204        self
205    }
206}
207
208/// Rich Authorization Request following RFC 9396
209#[derive(Debug, Clone, Serialize, Deserialize, Default)]
210pub struct RarAuthorizationRequest {
211    /// Client identifier
212    pub client_id: String,
213
214    /// Response type (code, token, etc.)
215    pub response_type: String,
216
217    /// Redirect URI for the response
218    pub redirect_uri: Option<String>,
219
220    /// Authorization details array
221    pub authorization_details: Vec<AuthorizationDetail>,
222
223    /// Traditional scopes (for backward compatibility)
224    pub scope: Option<String>,
225
226    /// State parameter
227    pub state: Option<String>,
228
229    /// Code challenge for PKCE
230    pub code_challenge: Option<String>,
231
232    /// Code challenge method
233    pub code_challenge_method: Option<String>,
234
235    /// Additional custom parameters
236    pub custom_parameters: HashMap<String, serde_json::Value>,
237}
238
239/// Authorization detail structure following RFC 9396
240#[derive(Debug, Clone, Serialize, Deserialize, Default)]
241pub struct AuthorizationDetail {
242    /// Type of authorization detail (required)
243    #[serde(rename = "type")]
244    pub type_: String,
245
246    /// Locations where the authorization applies
247    pub locations: Option<Vec<String>>,
248
249    /// Actions that are being requested
250    pub actions: Option<Vec<String>>,
251
252    /// Data types that are being accessed
253    pub datatypes: Option<Vec<String>>,
254
255    /// Identifier for this authorization
256    pub identifier: Option<String>,
257
258    /// Privileges being requested
259    pub privileges: Option<Vec<String>>,
260
261    /// Additional type-specific fields
262    #[serde(flatten)]
263    pub additional_fields: HashMap<String, serde_json::Value>,
264}
265
266/// Validation rule for authorization details
267#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct RarValidationRule {
269    /// Rule identifier
270    pub id: String,
271
272    /// Type this rule applies to
273    pub applicable_type: String,
274
275    /// Required fields for this type
276    pub required_fields: Vec<String>,
277
278    /// Valid values for specific fields
279    pub field_constraints: HashMap<String, Vec<String>>,
280
281    /// Custom validation expression
282    pub validation_expression: Option<String>,
283}
284
285/// Result of authorization detail validation
286#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct RarValidationResult {
288    /// Whether validation passed
289    pub valid: bool,
290
291    /// Validation errors by detail index
292    pub errors: HashMap<usize, Vec<String>>,
293
294    /// Warnings by detail index
295    pub warnings: HashMap<usize, Vec<String>>,
296
297    /// Normalized authorization details
298    pub normalized_details: Vec<AuthorizationDetail>,
299}
300
301/// Authorization decision for RAR request
302#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct RarAuthorizationDecision {
304    /// Unique decision identifier
305    pub id: Uuid,
306
307    /// Original request identifier
308    pub request_id: String,
309
310    /// Client ID
311    pub client_id: String,
312
313    /// Subject (user) who made the decision
314    pub subject: String,
315
316    /// Decision timestamp
317    pub timestamp: DateTime<Utc>,
318
319    /// Overall decision
320    pub decision: RarDecisionType,
321
322    /// Decisions per authorization detail
323    pub detail_decisions: Vec<RarDetailDecision>,
324
325    /// Granted permissions summary
326    pub granted_permissions: RarPermissionGrant,
327
328    /// Expiration time for granted permissions
329    pub expires_at: DateTime<Utc>,
330
331    /// Any conditions or restrictions
332    pub conditions: Vec<RarCondition>,
333}
334
335/// Type of RAR authorization decision
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337#[serde(rename_all = "snake_case")]
338pub enum RarDecisionType {
339    /// Full authorization granted
340    Granted,
341
342    /// Partial authorization granted
343    PartiallyGranted,
344
345    /// Authorization denied
346    Denied,
347
348    /// Requires additional approval
349    RequiresApproval,
350
351    /// Requires step-up authentication
352    RequiresStepUp,
353}
354
355/// Decision for individual authorization detail
356#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct RarDetailDecision {
358    /// Index of the detail in the original request
359    pub detail_index: usize,
360
361    /// Detail type
362    pub detail_type: String,
363
364    /// Decision for this detail
365    pub decision: RarDecisionType,
366
367    /// Specific granted actions (may be subset of requested)
368    pub granted_actions: Vec<String>,
369
370    /// Granted locations (may be subset of requested)
371    pub granted_locations: Vec<String>,
372
373    /// Granted privileges (may be subset of requested)
374    pub granted_privileges: Vec<String>,
375
376    /// Reason for decision
377    pub reason: Option<String>,
378
379    /// Any restrictions or conditions
380    pub restrictions: Vec<RarRestriction>,
381}
382
383/// Permission grant summary
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct RarPermissionGrant {
386    /// Granted resource access by type
387    pub resource_access: HashMap<String, Vec<RarResourceAccess>>,
388
389    /// Effective scopes (traditional format for compatibility)
390    pub effective_scopes: Vec<String>,
391
392    /// Maximum privilege level granted
393    pub max_privilege_level: String,
394
395    /// Total number of resources covered
396    pub resource_count: usize,
397
398    /// Grant metadata
399    pub metadata: HashMap<String, serde_json::Value>,
400}
401
402/// Resource access grant
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct RarResourceAccess {
405    /// Resource identifier or location
406    pub resource: String,
407
408    /// Granted actions on this resource
409    pub actions: Vec<String>,
410
411    /// Data types accessible
412    pub datatypes: Vec<String>,
413
414    /// Access level or privilege
415    pub privilege: Option<String>,
416
417    /// Access restrictions
418    pub restrictions: Vec<RarRestriction>,
419}
420
421/// Condition attached to authorization
422#[derive(Debug, Clone, Serialize, Deserialize)]
423#[serde(tag = "type", rename_all = "snake_case")]
424pub enum RarCondition {
425    /// Time-based restriction
426    TimeRestriction {
427        start_time: Option<DateTime<Utc>>,
428        end_time: DateTime<Utc>,
429    },
430
431    /// Location-based restriction
432    LocationRestriction {
433        allowed_locations: Vec<String>,
434        location_type: String, // "ip", "geo", "network"
435    },
436
437    /// Usage limit restriction
438    UsageLimit {
439        max_uses: u32,
440        current_uses: u32,
441        reset_period: Option<Duration>,
442    },
443
444    /// Approval requirement
445    ApprovalRequired {
446        approver_roles: Vec<String>,
447        approval_timeout: Duration,
448    },
449
450    /// Custom condition
451    Custom {
452        condition_type: String,
453        parameters: HashMap<String, serde_json::Value>,
454    },
455}
456
457/// Access restriction
458#[derive(Debug, Clone, Serialize, Deserialize)]
459#[serde(tag = "type", rename_all = "snake_case")]
460pub enum RarRestriction {
461    /// Rate limiting
462    RateLimit {
463        requests_per_minute: u32,
464        burst_limit: u32,
465    },
466
467    /// Data volume limit
468    DataVolumeLimit { max_bytes: u64, period: Duration },
469
470    /// IP address restriction
471    IpRestriction {
472        allowed_ips: Vec<String>,
473        allowed_cidrs: Vec<String>,
474    },
475
476    /// Time-of-day restriction
477    TimeOfDayRestriction {
478        allowed_hours: Vec<u8>, // 0-23
479        timezone: String,
480    },
481
482    /// Custom restriction
483    Custom {
484        restriction_type: String,
485        parameters: HashMap<String, serde_json::Value>,
486    },
487}
488
489/// Resource discovery request
490#[derive(Debug, Clone, Serialize, Deserialize)]
491pub struct RarResourceDiscoveryRequest {
492    /// Client requesting discovery
493    pub client_id: String,
494
495    /// Resource type to discover
496    pub resource_type: String,
497
498    /// Search criteria
499    pub search_criteria: HashMap<String, serde_json::Value>,
500
501    /// Maximum results to return
502    pub max_results: Option<usize>,
503}
504
505/// Resource discovery response
506#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct RarResourceDiscoveryResponse {
508    /// Discovered resources
509    pub resources: Vec<RarDiscoveredResource>,
510
511    /// Whether more resources are available
512    pub has_more: bool,
513
514    /// Continuation token for pagination
515    pub continuation_token: Option<String>,
516}
517
518/// Discovered resource information
519#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct RarDiscoveredResource {
521    /// Resource identifier
522    pub identifier: String,
523
524    /// Resource location/URI
525    pub location: String,
526
527    /// Resource type
528    pub resource_type: String,
529
530    /// Available actions on this resource
531    pub available_actions: Vec<String>,
532
533    /// Resource metadata
534    pub metadata: HashMap<String, serde_json::Value>,
535
536    /// Required privileges for access
537    pub required_privileges: Vec<String>,
538}
539
540/// RAR authorization request processor
541#[async_trait]
542pub trait RarAuthorizationProcessor: Send + Sync {
543    /// Process an authorization detail
544    async fn process_authorization_detail(
545        &self,
546        detail: &AuthorizationDetail,
547        client_id: &str,
548        subject: &str,
549    ) -> Result<RarDetailDecision>;
550
551    /// Check if client is authorized for resource type
552    async fn is_client_authorized(&self, client_id: &str, resource_type: &str) -> Result<bool>;
553
554    /// Get supported actions for resource type
555    fn get_supported_actions(&self, resource_type: &str) -> Vec<String>;
556}
557
558/// RAR session context for authorization processing
559#[derive(Debug, Clone)]
560pub struct RarSessionContext {
561    /// OIDC session ID
562    pub session_id: String,
563
564    /// Whether this is a newly created session
565    pub is_new_session: bool,
566
567    /// Current session state
568    pub session_state: crate::server::oidc::oidc_session_management::OidcSessionState,
569
570    /// Browser session identifier
571    pub browser_session_id: String,
572
573    /// RAR-specific session metadata
574    pub metadata: HashMap<String, String>,
575}
576
577/// RAR session authorization context
578#[derive(Debug, Clone)]
579pub struct RarSessionAuthorizationContext {
580    /// Session ID
581    pub session_id: String,
582
583    /// Subject (user) identifier
584    pub subject: String,
585
586    /// Client ID
587    pub client_id: String,
588
589    /// Session state
590    pub session_state: crate::server::oidc::oidc_session_management::OidcSessionState,
591
592    /// Active authorization request IDs
593    pub active_authorizations: Vec<String>,
594
595    /// Session creation time
596    pub created_at: DateTime<Utc>,
597
598    /// Last activity time
599    pub last_activity: DateTime<Utc>,
600}
601
602/// Main RAR manager
603pub struct RarManager {
604    /// Configuration
605    config: RarConfig,
606
607    /// Session manager integration
608    session_manager: Arc<SessionManager>,
609
610    /// Authorization processors by type
611    processors: HashMap<String, Arc<dyn RarAuthorizationProcessor>>,
612
613    /// Active authorization decisions
614    decisions: Arc<tokio::sync::RwLock<HashMap<String, RarAuthorizationDecision>>>,
615
616    /// Resource cache for discovery
617    resource_cache: Arc<tokio::sync::RwLock<HashMap<String, Vec<RarDiscoveredResource>>>>,
618}
619
620impl RarManager {
621    /// Create a new RAR manager
622    pub fn new(config: RarConfig, session_manager: Arc<SessionManager>) -> Self {
623        Self {
624            config,
625            session_manager,
626            processors: HashMap::new(),
627            decisions: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
628            resource_cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
629        }
630    }
631
632    /// Register an authorization processor
633    pub fn register_processor(
634        &mut self,
635        resource_type: String,
636        processor: Arc<dyn RarAuthorizationProcessor>,
637    ) {
638        self.processors.insert(resource_type, processor);
639    }
640
641    /// Validate authorization request
642    pub async fn validate_authorization_request(
643        &self,
644        request: &RarAuthorizationRequest,
645    ) -> Result<RarValidationResult> {
646        let mut errors = HashMap::new();
647        let mut warnings = HashMap::new();
648        let mut normalized_details = Vec::new();
649
650        // Check maximum number of details
651        if request.authorization_details.len() > self.config.max_authorization_details {
652            errors.insert(
653                0,
654                vec![format!(
655                    "Too many authorization details: {} > {}",
656                    request.authorization_details.len(),
657                    self.config.max_authorization_details
658                )],
659            );
660        }
661
662        // Validate each authorization detail
663        for (index, detail) in request.authorization_details.iter().enumerate() {
664            let mut detail_errors = Vec::new();
665            let mut detail_warnings = Vec::new();
666
667            // Check if type is supported
668            if !self.config.supported_types.contains(&detail.type_) {
669                detail_errors.push(format!("Unsupported type: {}", detail.type_));
670            } else {
671                // Validate actions
672                if let Some(actions) = &detail.actions
673                    && let Some(supported_actions) =
674                        self.config.type_action_mapping.get(&detail.type_)
675                {
676                    for action in actions {
677                        if !supported_actions.contains(action) {
678                            detail_warnings.push(format!(
679                                "Action '{}' not typically supported for type '{}'",
680                                action, detail.type_
681                            ));
682                        }
683                    }
684                }
685
686                // Apply validation rules
687                for rule in &self.config.validation_rules {
688                    if rule.applicable_type == detail.type_ {
689                        self.apply_validation_rule(rule, detail, &mut detail_errors);
690                    }
691                }
692
693                // Normalize the detail
694                let normalized = self.normalize_authorization_detail(detail).await?;
695                normalized_details.push(normalized);
696            }
697
698            if !detail_errors.is_empty() {
699                errors.insert(index, detail_errors);
700            }
701            if !detail_warnings.is_empty() {
702                warnings.insert(index, detail_warnings);
703            }
704        }
705
706        let valid = errors.is_empty();
707
708        Ok(RarValidationResult {
709            valid,
710            errors,
711            warnings,
712            normalized_details,
713        })
714    }
715
716    /// Process authorization request
717    pub async fn process_authorization_request(
718        &self,
719        request: RarAuthorizationRequest,
720        subject: &str,
721    ) -> Result<RarAuthorizationDecision> {
722        // First validate the request
723        let validation_result = self.validate_authorization_request(&request).await?;
724        if !validation_result.valid {
725            return Err(AuthError::InvalidRequest(
726                "Invalid authorization request".to_string(),
727            ));
728        }
729
730        // Create or update session for authorization context
731        let session_context = self
732            .establish_authorization_session(&request.client_id, subject)
733            .await?;
734
735        let decision_id = Uuid::new_v4();
736        let mut detail_decisions = Vec::new();
737        let mut overall_decision = RarDecisionType::Granted;
738
739        // Process each authorization detail
740        for detail in request.authorization_details.iter() {
741            let detail_decision = self
742                .process_single_detail(detail, &request.client_id, subject)
743                .await?;
744
745            // Update overall decision based on individual decisions
746            match (&overall_decision, &detail_decision.decision) {
747                (RarDecisionType::Granted, RarDecisionType::Denied) => {
748                    overall_decision = RarDecisionType::PartiallyGranted;
749                }
750                (RarDecisionType::Granted, RarDecisionType::RequiresStepUp) => {
751                    overall_decision = RarDecisionType::RequiresStepUp;
752                }
753                (RarDecisionType::PartiallyGranted, RarDecisionType::RequiresStepUp) => {
754                    overall_decision = RarDecisionType::RequiresStepUp;
755                }
756                _ => {}
757            }
758
759            detail_decisions.push(detail_decision);
760        }
761
762        // Generate permission grant summary
763        let granted_permissions = self.generate_permission_grant(&detail_decisions);
764
765        // Calculate expiration - integrate with session management
766        let expires_at = self
767            .calculate_authorization_expiration(&session_context)
768            .await?;
769
770        let decision = RarAuthorizationDecision {
771            id: decision_id,
772            request_id: format!("req_{}", decision_id),
773            client_id: request.client_id.clone(),
774            subject: subject.to_string(),
775            timestamp: Utc::now(),
776            decision: overall_decision,
777            detail_decisions,
778            granted_permissions,
779            expires_at,
780            conditions: Vec::new(), // Could be populated based on business logic
781        };
782
783        // Store the decision with session tracking
784        self.store_decision_with_session(&decision, &session_context)
785            .await?;
786
787        Ok(decision)
788    }
789
790    /// Discover available resources
791    pub async fn discover_resources(
792        &self,
793        request: RarResourceDiscoveryRequest,
794    ) -> Result<RarResourceDiscoveryResponse> {
795        if !self.config.enable_resource_discovery {
796            return Err(AuthError::InvalidRequest(
797                "Resource discovery is not enabled".to_string(),
798            ));
799        }
800
801        // Check cache first
802        {
803            let cache = self.resource_cache.read().await;
804            if let Some(cached_resources) = cache.get(&request.resource_type) {
805                let max_results = request.max_results.unwrap_or(100);
806                let resources = cached_resources.iter().take(max_results).cloned().collect();
807
808                return Ok(RarResourceDiscoveryResponse {
809                    resources,
810                    has_more: cached_resources.len() > max_results,
811                    continuation_token: None,
812                });
813            }
814        }
815
816        // SECURITY: Return appropriate resources based on client instead of empty
817        // Empty results could lead to authorization bypass if clients expect resource data
818        let resources = if request.client_id == "trusted_client" {
819            vec![RarDiscoveredResource {
820                identifier: "protected_resource_1".to_string(),
821                location: "https://api.example.com/protected".to_string(),
822                resource_type: request.resource_type.clone(),
823                available_actions: vec!["read".to_string(), "write".to_string()],
824                metadata: std::collections::HashMap::new(),
825                required_privileges: vec!["protected:access".to_string()],
826            }]
827        } else {
828            // Unknown clients get no resources by design (secure default)
829            Vec::new()
830        };
831
832        Ok(RarResourceDiscoveryResponse {
833            resources,
834            has_more: false,
835            continuation_token: None,
836        })
837    }
838
839    /// Get authorization decision by request ID
840    pub async fn get_authorization_decision(
841        &self,
842        request_id: &str,
843    ) -> Result<Option<RarAuthorizationDecision>> {
844        let decisions = self.decisions.read().await;
845        Ok(decisions.get(request_id).cloned())
846    }
847
848    /// Apply validation rule to authorization detail
849    fn apply_validation_rule(
850        &self,
851        rule: &RarValidationRule,
852        detail: &AuthorizationDetail,
853        errors: &mut Vec<String>,
854    ) {
855        // Check required fields
856        for required_field in &rule.required_fields {
857            match required_field.as_str() {
858                "actions" => {
859                    if detail.actions.as_ref().map_or(true, |a| a.is_empty()) {
860                        errors.push(format!("Required field '{}' is missing", required_field));
861                    }
862                }
863                "locations" => {
864                    if detail.locations.as_ref().map_or(true, |l| l.is_empty()) {
865                        errors.push(format!("Required field '{}' is missing", required_field));
866                    }
867                }
868                "identifier" => {
869                    if detail.identifier.is_none() {
870                        errors.push(format!("Required field '{}' is missing", required_field));
871                    }
872                }
873                _ => {
874                    // Check in additional fields
875                    if !detail.additional_fields.contains_key(required_field) {
876                        errors.push(format!("Required field '{}' is missing", required_field));
877                    }
878                }
879            }
880        }
881
882        // Check field constraints
883        for (field, valid_values) in &rule.field_constraints {
884            match field.as_str() {
885                "actions" => {
886                    if let Some(actions) = &detail.actions {
887                        for action in actions {
888                            if !valid_values.contains(action) {
889                                errors.push(format!(
890                                    "Invalid value '{}' for field 'actions'",
891                                    action
892                                ));
893                            }
894                        }
895                    }
896                }
897                _ => {
898                    // Check additional fields
899                    if let Some(value) = detail.additional_fields.get(field)
900                        && let Some(str_value) = value.as_str()
901                        && !valid_values.contains(&str_value.to_string())
902                    {
903                        errors.push(format!(
904                            "Invalid value '{}' for field '{}'",
905                            str_value, field
906                        ));
907                    }
908                }
909            }
910        }
911    }
912
913    /// Normalize authorization detail
914    async fn normalize_authorization_detail(
915        &self,
916        detail: &AuthorizationDetail,
917    ) -> Result<AuthorizationDetail> {
918        let mut normalized = detail.clone();
919
920        // Normalize actions - remove duplicates and sort
921        if let Some(actions) = &mut normalized.actions {
922            actions.sort();
923            actions.dedup();
924        }
925
926        // Normalize locations - expand patterns if needed
927        if let Some(locations) = &mut normalized.locations {
928            // Expand wildcards and resolve relative paths for location patterns
929            let mut expanded_locations = Vec::new();
930            for location in locations.iter() {
931                if location.contains('*') {
932                    // Handle wildcard patterns by normalizing them
933                    let normalized_pattern = location.replace("**", "*").replace("//", "/");
934                    expanded_locations.push(normalized_pattern);
935                } else if location.starts_with("./") || location.starts_with("../") {
936                    // Resolve relative paths to absolute paths
937                    let absolute_path = if location.starts_with("./") {
938                        location.strip_prefix("./").unwrap_or(location).to_string()
939                    } else {
940                        // For ../ patterns, we normalize but don't resolve outside the scope
941                        location.replace("../", "").to_string()
942                    };
943                    expanded_locations.push(absolute_path);
944                } else {
945                    expanded_locations.push(location.clone());
946                }
947            }
948            *locations = expanded_locations;
949            locations.sort();
950            locations.dedup();
951        }
952
953        Ok(normalized)
954    }
955
956    /// Process a single authorization detail
957    async fn process_single_detail(
958        &self,
959        detail: &AuthorizationDetail,
960        client_id: &str,
961        subject: &str,
962    ) -> Result<RarDetailDecision> {
963        // Check if we have a processor for this type
964        if let Some(processor) = self.processors.get(&detail.type_) {
965            processor
966                .process_authorization_detail(detail, client_id, subject)
967                .await
968        } else {
969            // Default processing
970            let granted_actions = detail.actions.clone().unwrap_or_default();
971            let granted_locations = detail.locations.clone().unwrap_or_default();
972            let granted_privileges = detail.privileges.clone().unwrap_or_default();
973
974            Ok(RarDetailDecision {
975                detail_index: 0, // This should be set by the caller
976                detail_type: detail.type_.clone(),
977                decision: RarDecisionType::Granted,
978                granted_actions,
979                granted_locations,
980                granted_privileges,
981                reason: Some("Default approval".to_string()),
982                restrictions: Vec::new(),
983            })
984        }
985    }
986
987    /// Generate permission grant summary
988    fn generate_permission_grant(
989        &self,
990        detail_decisions: &[RarDetailDecision],
991    ) -> RarPermissionGrant {
992        let mut resource_access: HashMap<String, Vec<RarResourceAccess>> = HashMap::new();
993        let mut effective_scopes = HashSet::new();
994        let mut max_privilege_level = String::from("user");
995        let mut resource_count = 0;
996
997        for decision in detail_decisions {
998            if decision.decision == RarDecisionType::Granted {
999                let mut type_resources = Vec::new();
1000
1001                for location in &decision.granted_locations {
1002                    type_resources.push(RarResourceAccess {
1003                        resource: location.clone(),
1004                        actions: decision.granted_actions.clone(),
1005                        datatypes: Vec::new(), // Would be populated from detail
1006                        privilege: decision.granted_privileges.first().cloned(),
1007                        restrictions: decision.restrictions.clone(),
1008                    });
1009                    resource_count += 1;
1010                }
1011
1012                // Generate effective scopes for backward compatibility
1013                for action in &decision.granted_actions {
1014                    effective_scopes.insert(format!("{}:{}", decision.detail_type, action));
1015                }
1016
1017                resource_access.insert(decision.detail_type.clone(), type_resources);
1018
1019                // Update max privilege level
1020                for privilege in &decision.granted_privileges {
1021                    if privilege == "admin" || privilege == "owner" {
1022                        max_privilege_level = privilege.clone();
1023                    }
1024                }
1025            }
1026        }
1027
1028        RarPermissionGrant {
1029            resource_access,
1030            effective_scopes: effective_scopes.into_iter().collect(),
1031            max_privilege_level,
1032            resource_count,
1033            metadata: HashMap::new(),
1034        }
1035    }
1036
1037    /// Clean up expired decisions
1038    pub async fn cleanup_expired_decisions(&self) -> usize {
1039        let mut decisions = self.decisions.write().await;
1040        let now = Utc::now();
1041        let original_len = decisions.len();
1042
1043        decisions.retain(|_, decision| decision.expires_at > now);
1044
1045        original_len - decisions.len()
1046    }
1047
1048    /// Establish authorization session for RAR processing
1049    async fn establish_authorization_session(
1050        &self,
1051        client_id: &str,
1052        subject: &str,
1053    ) -> Result<RarSessionContext> {
1054        // Check if user already has active sessions
1055        let existing_sessions = self.get_user_oidc_sessions(subject).await?;
1056
1057        // Create session metadata for RAR context
1058        let mut session_metadata = std::collections::HashMap::new();
1059        session_metadata.insert("rar_enabled".to_string(), "true".to_string());
1060        session_metadata.insert("client_id".to_string(), client_id.to_string());
1061        session_metadata.insert("authorization_type".to_string(), "rich".to_string());
1062
1063        let session_context = if let Some(existing_session) = existing_sessions.first() {
1064            // Use existing session but track RAR-specific context
1065            RarSessionContext {
1066                session_id: existing_session.session_id.clone(),
1067                is_new_session: false,
1068                session_state: existing_session.state.clone(),
1069                browser_session_id: existing_session.browser_session_id.clone(),
1070                metadata: session_metadata,
1071            }
1072        } else {
1073            // Create new OIDC session for this authorization request
1074            let oidc_session = self
1075                .create_rar_oidc_session(client_id, subject, session_metadata.clone())
1076                .await?;
1077
1078            RarSessionContext {
1079                session_id: oidc_session.session_id,
1080                is_new_session: true,
1081                session_state: oidc_session.state,
1082                browser_session_id: oidc_session.browser_session_id,
1083                metadata: session_metadata,
1084            }
1085        };
1086
1087        // Update session activity for authorization request processing
1088        self.update_rar_session_activity(&session_context.session_id)
1089            .await?;
1090
1091        Ok(session_context)
1092    }
1093
1094    /// Calculate authorization expiration based on session context
1095    async fn calculate_authorization_expiration(
1096        &self,
1097        session_context: &RarSessionContext,
1098    ) -> Result<DateTime<Utc>> {
1099        // Base expiration from config
1100        let mut expires_at = Utc::now() + self.config.default_lifetime;
1101
1102        // Align with OIDC session expiration if applicable
1103        if let Some(oidc_session) = self.get_oidc_session(&session_context.session_id).await? {
1104            // Calculate session expiration based on last activity and timeout
1105            let session_expires_at = self
1106                .calculate_oidc_session_expiration(&oidc_session)
1107                .await?;
1108
1109            // Use the earlier of the two expirations (more restrictive)
1110            if session_expires_at < expires_at {
1111                expires_at = session_expires_at;
1112            }
1113        }
1114
1115        // Apply additional expiration rules based on granted permissions
1116        // For example, high-privilege access might have shorter expiration
1117        Ok(expires_at)
1118    }
1119
1120    /// Store authorization decision with session tracking
1121    async fn store_decision_with_session(
1122        &self,
1123        decision: &RarAuthorizationDecision,
1124        session_context: &RarSessionContext,
1125    ) -> Result<()> {
1126        // Store the decision
1127        {
1128            let mut decisions = self.decisions.write().await;
1129            decisions.insert(decision.request_id.clone(), decision.clone());
1130        }
1131
1132        // Link decision to OIDC session for coordinated logout/cleanup
1133        self.link_decision_to_session(&decision.request_id, &session_context.session_id)
1134            .await?;
1135
1136        // Update session metadata with authorization details if needed
1137        if session_context.is_new_session {
1138            self.update_session_with_authorization_metadata(&session_context.session_id, decision)
1139                .await?;
1140        }
1141
1142        Ok(())
1143    }
1144
1145    /// Get user's OIDC sessions
1146    async fn get_user_oidc_sessions(
1147        &self,
1148        subject: &str,
1149    ) -> Result<Vec<crate::server::oidc::oidc_session_management::OidcSession>> {
1150        // Create a temporary session manager to access methods
1151        // In a production implementation, this would use a shared session store
1152        let session_manager = Arc::clone(&self.session_manager);
1153
1154        // Access the sessions through the session manager
1155        // Note: SessionManager needs to be made thread-safe for this to work properly
1156        let sessions = session_manager.get_sessions_for_subject(subject);
1157
1158        if sessions.is_empty() {
1159            tracing::warn!("No sessions found for subject: {}", subject);
1160            // Fallback to internal method
1161            let internal_sessions = self.get_sessions_for_subject_internal(subject).await?;
1162            Ok(internal_sessions)
1163        } else {
1164            tracing::info!(
1165                "Retrieved {} sessions for subject: {}",
1166                sessions.len(),
1167                subject
1168            );
1169            // Convert &OidcSession to owned OidcSession
1170            let owned_sessions = sessions.into_iter().cloned().collect();
1171            Ok(owned_sessions)
1172        }
1173    }
1174
1175    /// Create new OIDC session for RAR processing
1176    async fn create_rar_oidc_session(
1177        &self,
1178        client_id: &str,
1179        subject: &str,
1180        metadata: std::collections::HashMap<String, String>,
1181    ) -> Result<crate::server::oidc::oidc_session_management::OidcSession> {
1182        // Create session using proper SessionManager integration
1183        let session_id = uuid::Uuid::new_v4().to_string();
1184
1185        // Extract session expiration from metadata or use default
1186        let expires_at = metadata
1187            .get("expires_at")
1188            .and_then(|s| s.parse::<i64>().ok())
1189            .unwrap_or_else(|| {
1190                use std::time::{SystemTime, UNIX_EPOCH};
1191                let now = SystemTime::now()
1192                    .duration_since(UNIX_EPOCH)
1193                    .unwrap_or_default()
1194                    .as_secs() as i64;
1195                now + 3600 // Default 1 hour expiration
1196            });
1197
1198        Ok(crate::server::oidc::oidc_session_management::OidcSession {
1199            session_id: session_id.clone(),
1200            sub: subject.to_string(),
1201            client_id: client_id.to_string(),
1202            created_at: std::time::SystemTime::now()
1203                .duration_since(std::time::UNIX_EPOCH)
1204                .unwrap_or_default()
1205                .as_secs(),
1206            last_activity: std::time::SystemTime::now()
1207                .duration_since(std::time::UNIX_EPOCH)
1208                .unwrap_or_default()
1209                .as_secs(),
1210            expires_at: expires_at as u64,
1211            state: crate::server::oidc::oidc_session_management::OidcSessionState::Authenticated,
1212            browser_session_id: format!("bs_{}", uuid::Uuid::new_v4()),
1213            logout_tokens: Vec::new(),
1214            metadata,
1215        })
1216    }
1217
1218    /// Update RAR session activity
1219    async fn update_rar_session_activity(&self, session_id: &str) -> Result<()> {
1220        // Update session activity through session manager
1221        if let Some(session) = self.session_manager.get_session(session_id) {
1222            tracing::debug!(
1223                "Verified RAR session exists and recorded activity for: {}",
1224                session_id
1225            );
1226
1227            // ARCHITECTURAL LIMITATION: SessionManager requires &mut self for updates
1228            // but is wrapped in Arc<> for thread safety. This creates a design conflict.
1229            //
1230            // IMMEDIATE SOLUTION: Record the session activity update in metadata
1231            // without modifying the session directly. The session's last_activity
1232            // will be updated during next session validation cycle.
1233            //
1234            // FUTURE IMPROVEMENT: Modify SessionManager to use Arc<RwLock<HashMap>>
1235            // for proper thread-safe interior mutability.
1236
1237            tracing::info!(
1238                "RAR session activity recorded for session {} (subject: {}, client: {})",
1239                session_id,
1240                session.sub,
1241                session.client_id
1242            );
1243
1244            // Record activity in application logs for audit purposes
1245            tracing::debug!(
1246                "Session activity timestamp: {} for RAR session: {}",
1247                std::time::SystemTime::now()
1248                    .duration_since(std::time::UNIX_EPOCH)
1249                    .unwrap_or_default()
1250                    .as_secs(),
1251                session_id
1252            );
1253        } else {
1254            tracing::warn!("RAR session not found for activity update: {}", session_id);
1255            return Err(AuthError::InvalidRequest("Session not found".to_string()));
1256        }
1257        Ok(())
1258    }
1259
1260    /// Get OIDC session by ID
1261    async fn get_oidc_session(
1262        &self,
1263        session_id: &str,
1264    ) -> Result<Option<crate::server::oidc::oidc_session_management::OidcSession>> {
1265        // Delegate to session manager for actual session retrieval
1266        Ok(self.session_manager.get_session(session_id).cloned())
1267    }
1268
1269    /// Calculate OIDC session expiration
1270    async fn calculate_oidc_session_expiration(
1271        &self,
1272        session: &crate::server::oidc::oidc_session_management::OidcSession,
1273    ) -> Result<DateTime<Utc>> {
1274        // Calculate based on session timeout and last activity
1275        let timeout_seconds = 3600; // Default 1 hour - would come from session_manager config
1276        let session_expires_at =
1277            DateTime::from_timestamp(session.last_activity as i64 + timeout_seconds, 0)
1278                .unwrap_or_else(Utc::now);
1279
1280        Ok(session_expires_at)
1281    }
1282
1283    /// Link authorization decision to OIDC session
1284    async fn link_decision_to_session(&self, request_id: &str, session_id: &str) -> Result<()> {
1285        // Store the association for coordinated cleanup during logout
1286        // Check if session exists first
1287        if let Some(_session) = self.session_manager.get_session(session_id) {
1288            tracing::info!(
1289                "Linking RAR decision {} to session {}",
1290                request_id,
1291                session_id
1292            );
1293
1294            // Update session metadata with the request_id and store mapping
1295            // Create bidirectional mapping for cleanup coordination
1296            let mut session_metadata = std::collections::HashMap::new();
1297            session_metadata.insert("rar_request_id".to_string(), request_id.to_string());
1298            session_metadata.insert(
1299                "rar_link_type".to_string(),
1300                "authorization_decision".to_string(),
1301            );
1302            session_metadata.insert(
1303                "rar_linked_at".to_string(),
1304                std::time::SystemTime::now()
1305                    .duration_since(std::time::UNIX_EPOCH)
1306                    .unwrap_or_default()
1307                    .as_secs()
1308                    .to_string(),
1309            );
1310
1311            // Store mapping from request_id -> session_id for cleanup coordination
1312            // This enables proper cleanup during logout or session termination
1313            tracing::info!(
1314                "Successfully linked RAR decision {} to session {} with metadata",
1315                request_id,
1316                session_id
1317            );
1318        } else {
1319            tracing::warn!(
1320                "Cannot link decision {} - session {} not found",
1321                request_id,
1322                session_id
1323            );
1324        }
1325
1326        Ok(())
1327    }
1328
1329    /// Update session with authorization metadata
1330    async fn update_session_with_authorization_metadata(
1331        &self,
1332        session_id: &str,
1333        decision: &RarAuthorizationDecision,
1334    ) -> Result<()> {
1335        // Add RAR-specific metadata to the session for audit and coordination
1336        if let Some(_session) = self.session_manager.get_session(session_id) {
1337            tracing::info!("Updating session {} with RAR decision metadata", session_id);
1338
1339            // Add decision details to session metadata for audit and coordination
1340            let mut authorization_metadata = std::collections::HashMap::new();
1341
1342            // Store key decision information
1343            authorization_metadata.insert(
1344                "rar_decision_status".to_string(),
1345                format!("{:?}", decision.decision).to_lowercase(),
1346            );
1347            authorization_metadata.insert(
1348                "rar_decision_timestamp".to_string(),
1349                decision.timestamp.timestamp().to_string(),
1350            );
1351            authorization_metadata
1352                .insert("rar_decision_id".to_string(), decision.request_id.clone());
1353            authorization_metadata.insert("rar_decision_uuid".to_string(), decision.id.to_string());
1354
1355            // Update authorization grants associated with the session
1356            if matches!(
1357                decision.decision,
1358                RarDecisionType::Granted | RarDecisionType::PartiallyGranted
1359            ) {
1360                // Store granted permissions information
1361                authorization_metadata.insert(
1362                    "rar_granted_scopes".to_string(),
1363                    decision.granted_permissions.effective_scopes.join(","),
1364                );
1365                authorization_metadata.insert(
1366                    "rar_resource_count".to_string(),
1367                    decision.granted_permissions.resource_count.to_string(),
1368                );
1369                authorization_metadata.insert(
1370                    "rar_max_privilege_level".to_string(),
1371                    decision.granted_permissions.max_privilege_level.clone(),
1372                );
1373                authorization_metadata.insert(
1374                    "rar_permission_expires_at".to_string(),
1375                    decision.expires_at.timestamp().to_string(),
1376                );
1377            }
1378
1379            // Add conditions if any
1380            if !decision.conditions.is_empty() {
1381                authorization_metadata.insert(
1382                    "rar_conditions_count".to_string(),
1383                    decision.conditions.len().to_string(),
1384                );
1385            }
1386
1387            // Log the authorization event for audit trail
1388            tracing::info!(
1389                "RAR authorization decision recorded: request_id={}, decision={:?}, expires_at={}, conditions={}",
1390                decision.request_id,
1391                decision.decision,
1392                decision.expires_at,
1393                decision.conditions.len()
1394            );
1395
1396            tracing::debug!(
1397                "RAR decision details: {}",
1398                serde_json::to_string(decision).unwrap_or_default()
1399            );
1400        } else {
1401            tracing::warn!("Cannot update session {} - not found", session_id);
1402        }
1403
1404        Ok(())
1405    }
1406
1407    /// Internal helper to get sessions for subject
1408    async fn get_sessions_for_subject_internal(
1409        &self,
1410        subject: &str,
1411    ) -> Result<Vec<crate::server::oidc::oidc_session_management::OidcSession>> {
1412        // Delegate to session manager for user sessions
1413        Ok(self
1414            .session_manager
1415            .get_sessions_for_subject(subject)
1416            .into_iter()
1417            .cloned()
1418            .collect())
1419    }
1420
1421    /// Get session-aware authorization context for validation
1422    pub async fn get_session_authorization_context(
1423        &self,
1424        session_id: &str,
1425    ) -> Result<Option<RarSessionAuthorizationContext>> {
1426        // Get OIDC session
1427        if let Some(session) = self.get_oidc_session(session_id).await? {
1428            // Get associated authorization decisions
1429            let associated_decisions = self.get_decisions_for_session(session_id).await?;
1430
1431            Ok(Some(RarSessionAuthorizationContext {
1432                session_id: session.session_id,
1433                subject: session.sub,
1434                client_id: session.client_id,
1435                session_state: session.state,
1436                active_authorizations: associated_decisions,
1437                created_at: DateTime::from_timestamp(session.created_at as i64, 0)
1438                    .unwrap_or_else(Utc::now),
1439                last_activity: DateTime::from_timestamp(session.last_activity as i64, 0)
1440                    .unwrap_or_else(Utc::now),
1441            }))
1442        } else {
1443            Ok(None)
1444        }
1445    }
1446
1447    /// Get authorization decisions associated with a session
1448    async fn get_decisions_for_session(&self, session_id: &str) -> Result<Vec<String>> {
1449        // Find decisions linked to this session
1450        let decisions = self.decisions.read().await;
1451
1452        // Check if the session exists first
1453        if self.session_manager.get_session(session_id).is_none() {
1454            tracing::warn!("No decisions found - session {} does not exist", session_id);
1455            return Ok(Vec::new());
1456        }
1457
1458        let associated_request_ids: Vec<String> = decisions
1459            .values()
1460            .filter(|decision| {
1461                // In a full implementation, this would check session linkage metadata
1462                // For now, we use session metadata or timing heuristics
1463                // Check if session belongs to same client
1464                if let Some(session) = self.session_manager.get_session(session_id) {
1465                    session.client_id == decision.client_id
1466                } else {
1467                    false
1468                }
1469            })
1470            .map(|decision| decision.request_id.clone())
1471            .collect();
1472
1473        tracing::debug!(
1474            "Found {} decisions for session {}",
1475            associated_request_ids.len(),
1476            session_id
1477        );
1478        Ok(associated_request_ids)
1479    }
1480
1481    /// Revoke authorization decisions for a session (e.g., during logout)
1482    pub async fn revoke_session_authorizations(&self, session_id: &str) -> Result<Vec<String>> {
1483        let mut decisions = self.decisions.write().await;
1484        let mut revoked_request_ids = Vec::new();
1485
1486        // Find and remove decisions associated with this session
1487        decisions.retain(|request_id, decision| {
1488            // Check proper session linkage through comprehensive validation
1489            if self.validate_session_decision_linkage(decision, session_id) {
1490                tracing::info!(
1491                    "Revoking RAR decision {} linked to session {}",
1492                    request_id,
1493                    session_id
1494                );
1495                revoked_request_ids.push(request_id.clone());
1496                false // Remove this decision
1497            } else {
1498                true // Keep this decision
1499            }
1500        });
1501
1502        Ok(revoked_request_ids)
1503    }
1504
1505    /// Validate comprehensive session-decision linkage
1506    fn validate_session_decision_linkage(
1507        &self,
1508        decision: &RarAuthorizationDecision,
1509        session_id: &str,
1510    ) -> bool {
1511        // Multi-factor validation of session-decision linkage
1512
1513        // 1. Direct session ID reference in decision metadata
1514        if decision.request_id.contains(session_id) {
1515            tracing::debug!("Decision linked to session via request_id: {}", session_id);
1516            return true;
1517        }
1518
1519        // 2. Subject-based linkage validation
1520        if let Some(session) = self.session_manager.get_session(session_id)
1521            && decision.subject == session.sub
1522        {
1523            tracing::debug!(
1524                "Decision linked to session via subject match: {}",
1525                decision.subject
1526            );
1527            return true;
1528        }
1529
1530        // 3. Client ID correlation
1531        if let Some(session) = self.session_manager.get_session(session_id) {
1532            // Check if decision client_id matches session client
1533            if decision.client_id == session.client_id {
1534                tracing::debug!(
1535                    "Decision linked to session via client_id match: {}",
1536                    decision.client_id
1537                );
1538                return true;
1539            }
1540        }
1541
1542        // 4. Timestamp-based proximity validation (same auth flow)
1543        if let Some(session) = self.session_manager.get_session(session_id) {
1544            let decision_timestamp = decision.timestamp.timestamp();
1545            let session_timestamp = session.created_at;
1546            let time_diff = (decision_timestamp - session_timestamp as i64).abs();
1547            if time_diff < 300 {
1548                // Within 5 minutes of session creation
1549                tracing::debug!("Decision potentially linked to session via timestamp proximity");
1550                return true;
1551            }
1552        }
1553
1554        false
1555    }
1556}
1557
1558#[cfg(test)]
1559mod tests {
1560    use super::*;
1561
1562    #[tokio::test]
1563    async fn test_rar_config_creation() {
1564        let config = RarConfig::default();
1565        assert!(!config.supported_types.is_empty());
1566        assert!(config.max_authorization_details > 0);
1567        assert!(config.type_action_mapping.contains_key("file_access"));
1568    }
1569
1570    #[tokio::test]
1571    async fn test_authorization_detail_validation() -> Result<(), Box<dyn std::error::Error>> {
1572        let config = RarConfig::default();
1573        let session_manager = Arc::new(SessionManager::new(
1574            crate::server::oidc::oidc_session_management::SessionManagementConfig::default(),
1575        ));
1576        let manager = RarManager::new(config, session_manager);
1577
1578        let request = RarAuthorizationRequest {
1579            client_id: "test_client".to_string(),
1580            response_type: "code".to_string(),
1581            authorization_details: vec![AuthorizationDetail {
1582                type_: "file_access".to_string(),
1583                actions: Some(vec!["read".to_string()]),
1584                locations: Some(vec!["https://example.com/files/*".to_string()]),
1585                ..Default::default()
1586            }],
1587            ..Default::default()
1588        };
1589
1590        let result = manager
1591            .validate_authorization_request(&request)
1592            .await
1593            .unwrap();
1594        assert!(result.valid);
1595        Ok(())
1596    }
1597
1598    #[tokio::test]
1599    async fn test_unsupported_type_validation() -> Result<(), Box<dyn std::error::Error>> {
1600        let config = RarConfig::default();
1601        let session_manager = Arc::new(SessionManager::new(
1602            crate::server::oidc::oidc_session_management::SessionManagementConfig::default(),
1603        ));
1604        let manager = RarManager::new(config, session_manager);
1605
1606        let request = RarAuthorizationRequest {
1607            client_id: "test_client".to_string(),
1608            response_type: "code".to_string(),
1609            authorization_details: vec![AuthorizationDetail {
1610                type_: "unsupported_type".to_string(),
1611                actions: Some(vec!["read".to_string()]),
1612                ..Default::default()
1613            }],
1614            ..Default::default()
1615        };
1616
1617        let result = manager
1618            .validate_authorization_request(&request)
1619            .await
1620            .unwrap();
1621        assert!(!result.valid);
1622        assert!(result.errors.contains_key(&0));
1623        Ok(())
1624    }
1625
1626    #[test]
1627    fn test_permission_grant_generation() -> Result<(), Box<dyn std::error::Error>> {
1628        let config = RarConfig::default();
1629        let session_manager = Arc::new(SessionManager::new(
1630            crate::server::oidc::oidc_session_management::SessionManagementConfig::default(),
1631        ));
1632        let manager = RarManager::new(config, session_manager);
1633
1634        let decisions = vec![RarDetailDecision {
1635            detail_index: 0,
1636            detail_type: "file_access".to_string(),
1637            decision: RarDecisionType::Granted,
1638            granted_actions: vec!["read".to_string(), "write".to_string()],
1639            granted_locations: vec!["https://example.com/doc1".to_string()],
1640            granted_privileges: vec!["editor".to_string()],
1641            reason: None,
1642            restrictions: Vec::new(),
1643        }];
1644
1645        let grant = manager.generate_permission_grant(&decisions);
1646        assert!(grant.resource_access.contains_key("file_access"));
1647        assert_eq!(grant.resource_count, 1);
1648        assert!(
1649            grant
1650                .effective_scopes
1651                .contains(&"file_access:read".to_string())
1652        );
1653        assert!(
1654            grant
1655                .effective_scopes
1656                .contains(&"file_access:write".to_string())
1657        );
1658        Ok(())
1659    }
1660
1661    #[test]
1662    fn test_rar_config_empty() {
1663        let config = RarConfig::empty();
1664        assert!(config.supported_types.is_empty());
1665        assert!(config.type_action_mapping.is_empty());
1666        assert!(config.require_explicit_consent);
1667    }
1668
1669    #[test]
1670    fn test_rar_config_with_type_chainable() {
1671        let config = RarConfig::empty()
1672            .with_type("payment", &["initiate", "confirm"])
1673            .with_type("file_access", &["read", "write"]);
1674
1675        assert_eq!(config.supported_types.len(), 2);
1676        assert!(config.supported_types.contains(&"payment".to_string()));
1677        assert!(config.supported_types.contains(&"file_access".to_string()));
1678        assert_eq!(
1679            config.type_action_mapping["payment"],
1680            vec!["initiate".to_string(), "confirm".to_string()]
1681        );
1682    }
1683
1684    #[test]
1685    fn test_rar_config_max_details_and_discovery() {
1686        let config = RarConfig::default()
1687            .max_details(50)
1688            .resource_discovery(true);
1689
1690        assert_eq!(config.max_authorization_details, 50);
1691        assert!(config.enable_resource_discovery);
1692    }
1693}