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