1use 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#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct RarConfig {
90 pub max_authorization_details: usize,
92
93 pub supported_types: Vec<String>,
95
96 pub require_explicit_consent: bool,
98
99 pub max_resource_depth: usize,
101
102 pub default_lifetime: Duration,
104
105 pub enable_resource_discovery: bool,
107
108 pub validation_rules: Vec<RarValidationRule>,
110
111 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#[derive(Debug, Clone, Serialize, Deserialize, Default)]
165pub struct RarAuthorizationRequest {
166 pub client_id: String,
168
169 pub response_type: String,
171
172 pub redirect_uri: Option<String>,
174
175 pub authorization_details: Vec<AuthorizationDetail>,
177
178 pub scope: Option<String>,
180
181 pub state: Option<String>,
183
184 pub code_challenge: Option<String>,
186
187 pub code_challenge_method: Option<String>,
189
190 pub custom_parameters: HashMap<String, serde_json::Value>,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, Default)]
196pub struct AuthorizationDetail {
197 #[serde(rename = "type")]
199 pub type_: String,
200
201 pub locations: Option<Vec<String>>,
203
204 pub actions: Option<Vec<String>>,
206
207 pub datatypes: Option<Vec<String>>,
209
210 pub identifier: Option<String>,
212
213 pub privileges: Option<Vec<String>>,
215
216 #[serde(flatten)]
218 pub additional_fields: HashMap<String, serde_json::Value>,
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct RarValidationRule {
224 pub id: String,
226
227 pub applicable_type: String,
229
230 pub required_fields: Vec<String>,
232
233 pub field_constraints: HashMap<String, Vec<String>>,
235
236 pub validation_expression: Option<String>,
238}
239
240#[derive(Debug, Clone, Serialize, Deserialize)]
242pub struct RarValidationResult {
243 pub valid: bool,
245
246 pub errors: HashMap<usize, Vec<String>>,
248
249 pub warnings: HashMap<usize, Vec<String>>,
251
252 pub normalized_details: Vec<AuthorizationDetail>,
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub struct RarAuthorizationDecision {
259 pub id: Uuid,
261
262 pub request_id: String,
264
265 pub client_id: String,
267
268 pub subject: String,
270
271 pub timestamp: DateTime<Utc>,
273
274 pub decision: RarDecisionType,
276
277 pub detail_decisions: Vec<RarDetailDecision>,
279
280 pub granted_permissions: RarPermissionGrant,
282
283 pub expires_at: DateTime<Utc>,
285
286 pub conditions: Vec<RarCondition>,
288}
289
290#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292#[serde(rename_all = "snake_case")]
293pub enum RarDecisionType {
294 Granted,
296
297 PartiallyGranted,
299
300 Denied,
302
303 RequiresApproval,
305
306 RequiresStepUp,
308}
309
310#[derive(Debug, Clone, Serialize, Deserialize)]
312pub struct RarDetailDecision {
313 pub detail_index: usize,
315
316 pub detail_type: String,
318
319 pub decision: RarDecisionType,
321
322 pub granted_actions: Vec<String>,
324
325 pub granted_locations: Vec<String>,
327
328 pub granted_privileges: Vec<String>,
330
331 pub reason: Option<String>,
333
334 pub restrictions: Vec<RarRestriction>,
336}
337
338#[derive(Debug, Clone, Serialize, Deserialize)]
340pub struct RarPermissionGrant {
341 pub resource_access: HashMap<String, Vec<RarResourceAccess>>,
343
344 pub effective_scopes: Vec<String>,
346
347 pub max_privilege_level: String,
349
350 pub resource_count: usize,
352
353 pub metadata: HashMap<String, serde_json::Value>,
355}
356
357#[derive(Debug, Clone, Serialize, Deserialize)]
359pub struct RarResourceAccess {
360 pub resource: String,
362
363 pub actions: Vec<String>,
365
366 pub datatypes: Vec<String>,
368
369 pub privilege: Option<String>,
371
372 pub restrictions: Vec<RarRestriction>,
374}
375
376#[derive(Debug, Clone, Serialize, Deserialize)]
378#[serde(tag = "type", rename_all = "snake_case")]
379pub enum RarCondition {
380 TimeRestriction {
382 start_time: Option<DateTime<Utc>>,
383 end_time: DateTime<Utc>,
384 },
385
386 LocationRestriction {
388 allowed_locations: Vec<String>,
389 location_type: String, },
391
392 UsageLimit {
394 max_uses: u32,
395 current_uses: u32,
396 reset_period: Option<Duration>,
397 },
398
399 ApprovalRequired {
401 approver_roles: Vec<String>,
402 approval_timeout: Duration,
403 },
404
405 Custom {
407 condition_type: String,
408 parameters: HashMap<String, serde_json::Value>,
409 },
410}
411
412#[derive(Debug, Clone, Serialize, Deserialize)]
414#[serde(tag = "type", rename_all = "snake_case")]
415pub enum RarRestriction {
416 RateLimit {
418 requests_per_minute: u32,
419 burst_limit: u32,
420 },
421
422 DataVolumeLimit { max_bytes: u64, period: Duration },
424
425 IpRestriction {
427 allowed_ips: Vec<String>,
428 allowed_cidrs: Vec<String>,
429 },
430
431 TimeOfDayRestriction {
433 allowed_hours: Vec<u8>, timezone: String,
435 },
436
437 Custom {
439 restriction_type: String,
440 parameters: HashMap<String, serde_json::Value>,
441 },
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize)]
446pub struct RarResourceDiscoveryRequest {
447 pub client_id: String,
449
450 pub resource_type: String,
452
453 pub search_criteria: HashMap<String, serde_json::Value>,
455
456 pub max_results: Option<usize>,
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
462pub struct RarResourceDiscoveryResponse {
463 pub resources: Vec<RarDiscoveredResource>,
465
466 pub has_more: bool,
468
469 pub continuation_token: Option<String>,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize)]
475pub struct RarDiscoveredResource {
476 pub identifier: String,
478
479 pub location: String,
481
482 pub resource_type: String,
484
485 pub available_actions: Vec<String>,
487
488 pub metadata: HashMap<String, serde_json::Value>,
490
491 pub required_privileges: Vec<String>,
493}
494
495#[async_trait]
497pub trait RarAuthorizationProcessor: Send + Sync {
498 async fn process_authorization_detail(
500 &self,
501 detail: &AuthorizationDetail,
502 client_id: &str,
503 subject: &str,
504 ) -> Result<RarDetailDecision>;
505
506 async fn is_client_authorized(&self, client_id: &str, resource_type: &str) -> Result<bool>;
508
509 fn get_supported_actions(&self, resource_type: &str) -> Vec<String>;
511}
512
513#[derive(Debug, Clone)]
515pub struct RarSessionContext {
516 pub session_id: String,
518
519 pub is_new_session: bool,
521
522 pub session_state: crate::server::oidc::oidc_session_management::SessionState,
524
525 pub browser_session_id: String,
527
528 pub metadata: HashMap<String, String>,
530}
531
532#[derive(Debug, Clone)]
534pub struct RarSessionAuthorizationContext {
535 pub session_id: String,
537
538 pub subject: String,
540
541 pub client_id: String,
543
544 pub session_state: crate::server::oidc::oidc_session_management::SessionState,
546
547 pub active_authorizations: Vec<String>,
549
550 pub created_at: DateTime<Utc>,
552
553 pub last_activity: DateTime<Utc>,
555}
556
557pub struct RarManager {
559 config: RarConfig,
561
562 session_manager: Arc<SessionManager>,
564
565 processors: HashMap<String, Arc<dyn RarAuthorizationProcessor>>,
567
568 decisions: Arc<tokio::sync::RwLock<HashMap<String, RarAuthorizationDecision>>>,
570
571 resource_cache: Arc<tokio::sync::RwLock<HashMap<String, Vec<RarDiscoveredResource>>>>,
573}
574
575impl RarManager {
576 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 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 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 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 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 if !self.config.supported_types.contains(&detail.type_) {
624 detail_errors.push(format!("Unsupported type: {}", detail.type_));
625 } else {
626 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 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 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 pub async fn process_authorization_request(
673 &self,
674 request: RarAuthorizationRequest,
675 subject: &str,
676 ) -> Result<RarAuthorizationDecision> {
677 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 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 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 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 let granted_permissions = self.generate_permission_grant(&detail_decisions);
719
720 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(), };
737
738 self.store_decision_with_session(&decision, &session_context)
740 .await?;
741
742 Ok(decision)
743 }
744
745 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 {
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 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 Vec::new()
785 };
786
787 Ok(RarResourceDiscoveryResponse {
788 resources,
789 has_more: false,
790 continuation_token: None,
791 })
792 }
793
794 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 fn apply_validation_rule(
805 &self,
806 rule: &RarValidationRule,
807 detail: &AuthorizationDetail,
808 errors: &mut Vec<String>,
809 ) {
810 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 if !detail.additional_fields.contains_key(required_field) {
831 errors.push(format!("Required field '{}' is missing", required_field));
832 }
833 }
834 }
835 }
836
837 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 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 async fn normalize_authorization_detail(
870 &self,
871 detail: &AuthorizationDetail,
872 ) -> Result<AuthorizationDetail> {
873 let mut normalized = detail.clone();
874
875 if let Some(actions) = &mut normalized.actions {
877 actions.sort();
878 actions.dedup();
879 }
880
881 if let Some(locations) = &mut normalized.locations {
883 let mut expanded_locations = Vec::new();
885 for location in locations.iter() {
886 if location.contains('*') {
887 let normalized_pattern = location.replace("**", "*").replace("//", "/");
889 expanded_locations.push(normalized_pattern);
890 } else if location.starts_with("./") || location.starts_with("../") {
891 let absolute_path = if location.starts_with("./") {
893 location.strip_prefix("./").unwrap_or(location).to_string()
894 } else {
895 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 async fn process_single_detail(
913 &self,
914 detail: &AuthorizationDetail,
915 client_id: &str,
916 subject: &str,
917 ) -> Result<RarDetailDecision> {
918 if let Some(processor) = self.processors.get(&detail.type_) {
920 processor
921 .process_authorization_detail(detail, client_id, subject)
922 .await
923 } else {
924 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, 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 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(), privilege: decision.granted_privileges.first().cloned(),
962 restrictions: decision.restrictions.clone(),
963 });
964 resource_count += 1;
965 }
966
967 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 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 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 async fn establish_authorization_session(
1005 &self,
1006 client_id: &str,
1007 subject: &str,
1008 ) -> Result<RarSessionContext> {
1009 let existing_sessions = self.get_user_oidc_sessions(subject).await?;
1011
1012 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 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 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 self.update_rar_session_activity(&session_context.session_id)
1044 .await?;
1045
1046 Ok(session_context)
1047 }
1048
1049 async fn calculate_authorization_expiration(
1051 &self,
1052 session_context: &RarSessionContext,
1053 ) -> Result<DateTime<Utc>> {
1054 let mut expires_at = Utc::now() + self.config.default_lifetime;
1056
1057 if let Some(oidc_session) = self.get_oidc_session(&session_context.session_id).await? {
1059 let session_expires_at = self
1061 .calculate_oidc_session_expiration(&oidc_session)
1062 .await?;
1063
1064 if session_expires_at < expires_at {
1066 expires_at = session_expires_at;
1067 }
1068 }
1069
1070 Ok(expires_at)
1073 }
1074
1075 async fn store_decision_with_session(
1077 &self,
1078 decision: &RarAuthorizationDecision,
1079 session_context: &RarSessionContext,
1080 ) -> Result<()> {
1081 {
1083 let mut decisions = self.decisions.write().await;
1084 decisions.insert(decision.request_id.clone(), decision.clone());
1085 }
1086
1087 self.link_decision_to_session(&decision.request_id, &session_context.session_id)
1089 .await?;
1090
1091 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 async fn get_user_oidc_sessions(
1102 &self,
1103 subject: &str,
1104 ) -> Result<Vec<crate::server::oidc::oidc_session_management::OidcSession>> {
1105 let session_manager = Arc::clone(&self.session_manager);
1108
1109 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 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 let owned_sessions = sessions.into_iter().cloned().collect();
1126 Ok(owned_sessions)
1127 }
1128 }
1129
1130 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 let session_id = uuid::Uuid::new_v4().to_string();
1139
1140 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 });
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 async fn update_rar_session_activity(&self, session_id: &str) -> Result<()> {
1175 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 tracing::info!(
1193 "RAR session activity recorded for session {} (subject: {}, client: {})",
1194 session_id,
1195 session.sub,
1196 session.client_id
1197 );
1198
1199 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 async fn get_oidc_session(
1217 &self,
1218 session_id: &str,
1219 ) -> Result<Option<crate::server::oidc::oidc_session_management::OidcSession>> {
1220 Ok(self.session_manager.get_session(session_id).cloned())
1222 }
1223
1224 async fn calculate_oidc_session_expiration(
1226 &self,
1227 session: &crate::server::oidc::oidc_session_management::OidcSession,
1228 ) -> Result<DateTime<Utc>> {
1229 let timeout_seconds = 3600; 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 async fn link_decision_to_session(&self, request_id: &str, session_id: &str) -> Result<()> {
1240 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 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 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 async fn update_session_with_authorization_metadata(
1286 &self,
1287 session_id: &str,
1288 decision: &RarAuthorizationDecision,
1289 ) -> Result<()> {
1290 if let Some(_session) = self.session_manager.get_session(session_id) {
1292 tracing::info!("Updating session {} with RAR decision metadata", session_id);
1293
1294 let mut authorization_metadata = std::collections::HashMap::new();
1296
1297 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 if matches!(
1312 decision.decision,
1313 RarDecisionType::Granted | RarDecisionType::PartiallyGranted
1314 ) {
1315 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 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 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 async fn get_sessions_for_subject_internal(
1364 &self,
1365 subject: &str,
1366 ) -> Result<Vec<crate::server::oidc::oidc_session_management::OidcSession>> {
1367 Ok(self
1369 .session_manager
1370 .get_sessions_for_subject(subject)
1371 .into_iter()
1372 .cloned()
1373 .collect())
1374 }
1375
1376 pub async fn get_session_authorization_context(
1378 &self,
1379 session_id: &str,
1380 ) -> Result<Option<RarSessionAuthorizationContext>> {
1381 if let Some(session) = self.get_oidc_session(session_id).await? {
1383 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 async fn get_decisions_for_session(&self, session_id: &str) -> Result<Vec<String>> {
1404 let decisions = self.decisions.read().await;
1406
1407 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 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 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 decisions.retain(|request_id, decision| {
1443 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 } else {
1453 true }
1455 });
1456
1457 Ok(revoked_request_ids)
1458 }
1459
1460 fn validate_session_decision_linkage(
1462 &self,
1463 decision: &RarAuthorizationDecision,
1464 session_id: &str,
1465 ) -> bool {
1466 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 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 if let Some(session) = self.session_manager.get_session(session_id) {
1486 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 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 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