1use crate::errors::{AuthError, Result};
74use crate::server::oidc::oidc_session_management::SessionManager;
75
76use async_trait::async_trait;
77use chrono::{DateTime, Duration, Utc};
78use serde::{Deserialize, Serialize};
79use std::collections::{HashMap, HashSet};
80use std::sync::Arc;
81use uuid::Uuid;
82
83#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct RarConfig {
86 pub max_authorization_details: usize,
88
89 pub supported_types: Vec<String>,
91
92 pub require_explicit_consent: bool,
94
95 pub max_resource_depth: usize,
97
98 pub default_lifetime: Duration,
100
101 pub enable_resource_discovery: bool,
103
104 pub validation_rules: Vec<RarValidationRule>,
106
107 pub type_action_mapping: HashMap<String, Vec<String>>,
109}
110
111impl Default for RarConfig {
112 fn default() -> Self {
113 let mut type_action_mapping = HashMap::new();
114 type_action_mapping.insert(
115 "file_access".to_string(),
116 vec![
117 "read".to_string(),
118 "write".to_string(),
119 "delete".to_string(),
120 ],
121 );
122 type_action_mapping.insert(
123 "api_access".to_string(),
124 vec![
125 "read".to_string(),
126 "write".to_string(),
127 "execute".to_string(),
128 ],
129 );
130 type_action_mapping.insert(
131 "database_access".to_string(),
132 vec![
133 "select".to_string(),
134 "insert".to_string(),
135 "update".to_string(),
136 "delete".to_string(),
137 ],
138 );
139
140 Self {
141 max_authorization_details: 10,
142 supported_types: vec![
143 "file_access".to_string(),
144 "api_access".to_string(),
145 "database_access".to_string(),
146 "payment_initiation".to_string(),
147 "account_information".to_string(),
148 ],
149 require_explicit_consent: true,
150 max_resource_depth: 5,
151 default_lifetime: Duration::try_hours(1).unwrap_or(Duration::zero()),
152 enable_resource_discovery: false,
153 validation_rules: Vec::new(),
154 type_action_mapping,
155 }
156 }
157}
158
159impl RarConfig {
160 pub fn empty() -> Self {
165 Self {
166 supported_types: Vec::new(),
167 type_action_mapping: HashMap::new(),
168 ..Default::default()
169 }
170 }
171
172 pub fn with_type(mut self, type_name: &str, actions: &[&str]) -> Self {
184 let name = type_name.to_string();
185 if !self.supported_types.contains(&name) {
186 self.supported_types.push(name.clone());
187 }
188 self.type_action_mapping.insert(
189 name,
190 actions.iter().map(|a| a.to_string()).collect(),
191 );
192 self
193 }
194
195 pub fn max_details(mut self, max: usize) -> Self {
197 self.max_authorization_details = max;
198 self
199 }
200
201 pub fn resource_discovery(mut self, enabled: bool) -> Self {
203 self.enable_resource_discovery = enabled;
204 self
205 }
206}
207
208#[derive(Debug, Clone, Serialize, Deserialize, Default)]
210pub struct RarAuthorizationRequest {
211 pub client_id: String,
213
214 pub response_type: String,
216
217 pub redirect_uri: Option<String>,
219
220 pub authorization_details: Vec<AuthorizationDetail>,
222
223 pub scope: Option<String>,
225
226 pub state: Option<String>,
228
229 pub code_challenge: Option<String>,
231
232 pub code_challenge_method: Option<String>,
234
235 pub custom_parameters: HashMap<String, serde_json::Value>,
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, Default)]
241pub struct AuthorizationDetail {
242 #[serde(rename = "type")]
244 pub type_: String,
245
246 pub locations: Option<Vec<String>>,
248
249 pub actions: Option<Vec<String>>,
251
252 pub datatypes: Option<Vec<String>>,
254
255 pub identifier: Option<String>,
257
258 pub privileges: Option<Vec<String>>,
260
261 #[serde(flatten)]
263 pub additional_fields: HashMap<String, serde_json::Value>,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct RarValidationRule {
269 pub id: String,
271
272 pub applicable_type: String,
274
275 pub required_fields: Vec<String>,
277
278 pub field_constraints: HashMap<String, Vec<String>>,
280
281 pub validation_expression: Option<String>,
283}
284
285#[derive(Debug, Clone, Serialize, Deserialize)]
287pub struct RarValidationResult {
288 pub valid: bool,
290
291 pub errors: HashMap<usize, Vec<String>>,
293
294 pub warnings: HashMap<usize, Vec<String>>,
296
297 pub normalized_details: Vec<AuthorizationDetail>,
299}
300
301#[derive(Debug, Clone, Serialize, Deserialize)]
303pub struct RarAuthorizationDecision {
304 pub id: Uuid,
306
307 pub request_id: String,
309
310 pub client_id: String,
312
313 pub subject: String,
315
316 pub timestamp: DateTime<Utc>,
318
319 pub decision: RarDecisionType,
321
322 pub detail_decisions: Vec<RarDetailDecision>,
324
325 pub granted_permissions: RarPermissionGrant,
327
328 pub expires_at: DateTime<Utc>,
330
331 pub conditions: Vec<RarCondition>,
333}
334
335#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337#[serde(rename_all = "snake_case")]
338pub enum RarDecisionType {
339 Granted,
341
342 PartiallyGranted,
344
345 Denied,
347
348 RequiresApproval,
350
351 RequiresStepUp,
353}
354
355#[derive(Debug, Clone, Serialize, Deserialize)]
357pub struct RarDetailDecision {
358 pub detail_index: usize,
360
361 pub detail_type: String,
363
364 pub decision: RarDecisionType,
366
367 pub granted_actions: Vec<String>,
369
370 pub granted_locations: Vec<String>,
372
373 pub granted_privileges: Vec<String>,
375
376 pub reason: Option<String>,
378
379 pub restrictions: Vec<RarRestriction>,
381}
382
383#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct RarPermissionGrant {
386 pub resource_access: HashMap<String, Vec<RarResourceAccess>>,
388
389 pub effective_scopes: Vec<String>,
391
392 pub max_privilege_level: String,
394
395 pub resource_count: usize,
397
398 pub metadata: HashMap<String, serde_json::Value>,
400}
401
402#[derive(Debug, Clone, Serialize, Deserialize)]
404pub struct RarResourceAccess {
405 pub resource: String,
407
408 pub actions: Vec<String>,
410
411 pub datatypes: Vec<String>,
413
414 pub privilege: Option<String>,
416
417 pub restrictions: Vec<RarRestriction>,
419}
420
421#[derive(Debug, Clone, Serialize, Deserialize)]
423#[serde(tag = "type", rename_all = "snake_case")]
424pub enum RarCondition {
425 TimeRestriction {
427 start_time: Option<DateTime<Utc>>,
428 end_time: DateTime<Utc>,
429 },
430
431 LocationRestriction {
433 allowed_locations: Vec<String>,
434 location_type: String, },
436
437 UsageLimit {
439 max_uses: u32,
440 current_uses: u32,
441 reset_period: Option<Duration>,
442 },
443
444 ApprovalRequired {
446 approver_roles: Vec<String>,
447 approval_timeout: Duration,
448 },
449
450 Custom {
452 condition_type: String,
453 parameters: HashMap<String, serde_json::Value>,
454 },
455}
456
457#[derive(Debug, Clone, Serialize, Deserialize)]
459#[serde(tag = "type", rename_all = "snake_case")]
460pub enum RarRestriction {
461 RateLimit {
463 requests_per_minute: u32,
464 burst_limit: u32,
465 },
466
467 DataVolumeLimit { max_bytes: u64, period: Duration },
469
470 IpRestriction {
472 allowed_ips: Vec<String>,
473 allowed_cidrs: Vec<String>,
474 },
475
476 TimeOfDayRestriction {
478 allowed_hours: Vec<u8>, timezone: String,
480 },
481
482 Custom {
484 restriction_type: String,
485 parameters: HashMap<String, serde_json::Value>,
486 },
487}
488
489#[derive(Debug, Clone, Serialize, Deserialize)]
491pub struct RarResourceDiscoveryRequest {
492 pub client_id: String,
494
495 pub resource_type: String,
497
498 pub search_criteria: HashMap<String, serde_json::Value>,
500
501 pub max_results: Option<usize>,
503}
504
505#[derive(Debug, Clone, Serialize, Deserialize)]
507pub struct RarResourceDiscoveryResponse {
508 pub resources: Vec<RarDiscoveredResource>,
510
511 pub has_more: bool,
513
514 pub continuation_token: Option<String>,
516}
517
518#[derive(Debug, Clone, Serialize, Deserialize)]
520pub struct RarDiscoveredResource {
521 pub identifier: String,
523
524 pub location: String,
526
527 pub resource_type: String,
529
530 pub available_actions: Vec<String>,
532
533 pub metadata: HashMap<String, serde_json::Value>,
535
536 pub required_privileges: Vec<String>,
538}
539
540#[async_trait]
542pub trait RarAuthorizationProcessor: Send + Sync {
543 async fn process_authorization_detail(
545 &self,
546 detail: &AuthorizationDetail,
547 client_id: &str,
548 subject: &str,
549 ) -> Result<RarDetailDecision>;
550
551 async fn is_client_authorized(&self, client_id: &str, resource_type: &str) -> Result<bool>;
553
554 fn get_supported_actions(&self, resource_type: &str) -> Vec<String>;
556}
557
558#[derive(Debug, Clone)]
560pub struct RarSessionContext {
561 pub session_id: String,
563
564 pub is_new_session: bool,
566
567 pub session_state: crate::server::oidc::oidc_session_management::OidcSessionState,
569
570 pub browser_session_id: String,
572
573 pub metadata: HashMap<String, String>,
575}
576
577#[derive(Debug, Clone)]
579pub struct RarSessionAuthorizationContext {
580 pub session_id: String,
582
583 pub subject: String,
585
586 pub client_id: String,
588
589 pub session_state: crate::server::oidc::oidc_session_management::OidcSessionState,
591
592 pub active_authorizations: Vec<String>,
594
595 pub created_at: DateTime<Utc>,
597
598 pub last_activity: DateTime<Utc>,
600}
601
602pub struct RarManager {
604 config: RarConfig,
606
607 session_manager: Arc<SessionManager>,
609
610 processors: HashMap<String, Arc<dyn RarAuthorizationProcessor>>,
612
613 decisions: Arc<tokio::sync::RwLock<HashMap<String, RarAuthorizationDecision>>>,
615
616 resource_cache: Arc<tokio::sync::RwLock<HashMap<String, Vec<RarDiscoveredResource>>>>,
618}
619
620impl RarManager {
621 pub fn new(config: RarConfig, session_manager: Arc<SessionManager>) -> Self {
623 Self {
624 config,
625 session_manager,
626 processors: HashMap::new(),
627 decisions: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
628 resource_cache: Arc::new(tokio::sync::RwLock::new(HashMap::new())),
629 }
630 }
631
632 pub fn register_processor(
634 &mut self,
635 resource_type: String,
636 processor: Arc<dyn RarAuthorizationProcessor>,
637 ) {
638 self.processors.insert(resource_type, processor);
639 }
640
641 pub async fn validate_authorization_request(
643 &self,
644 request: &RarAuthorizationRequest,
645 ) -> Result<RarValidationResult> {
646 let mut errors = HashMap::new();
647 let mut warnings = HashMap::new();
648 let mut normalized_details = Vec::new();
649
650 if request.authorization_details.len() > self.config.max_authorization_details {
652 errors.insert(
653 0,
654 vec![format!(
655 "Too many authorization details: {} > {}",
656 request.authorization_details.len(),
657 self.config.max_authorization_details
658 )],
659 );
660 }
661
662 for (index, detail) in request.authorization_details.iter().enumerate() {
664 let mut detail_errors = Vec::new();
665 let mut detail_warnings = Vec::new();
666
667 if !self.config.supported_types.contains(&detail.type_) {
669 detail_errors.push(format!("Unsupported type: {}", detail.type_));
670 } else {
671 if let Some(actions) = &detail.actions
673 && let Some(supported_actions) =
674 self.config.type_action_mapping.get(&detail.type_)
675 {
676 for action in actions {
677 if !supported_actions.contains(action) {
678 detail_warnings.push(format!(
679 "Action '{}' not typically supported for type '{}'",
680 action, detail.type_
681 ));
682 }
683 }
684 }
685
686 for rule in &self.config.validation_rules {
688 if rule.applicable_type == detail.type_ {
689 self.apply_validation_rule(rule, detail, &mut detail_errors);
690 }
691 }
692
693 let normalized = self.normalize_authorization_detail(detail).await?;
695 normalized_details.push(normalized);
696 }
697
698 if !detail_errors.is_empty() {
699 errors.insert(index, detail_errors);
700 }
701 if !detail_warnings.is_empty() {
702 warnings.insert(index, detail_warnings);
703 }
704 }
705
706 let valid = errors.is_empty();
707
708 Ok(RarValidationResult {
709 valid,
710 errors,
711 warnings,
712 normalized_details,
713 })
714 }
715
716 pub async fn process_authorization_request(
718 &self,
719 request: RarAuthorizationRequest,
720 subject: &str,
721 ) -> Result<RarAuthorizationDecision> {
722 let validation_result = self.validate_authorization_request(&request).await?;
724 if !validation_result.valid {
725 return Err(AuthError::InvalidRequest(
726 "Invalid authorization request".to_string(),
727 ));
728 }
729
730 let session_context = self
732 .establish_authorization_session(&request.client_id, subject)
733 .await?;
734
735 let decision_id = Uuid::new_v4();
736 let mut detail_decisions = Vec::new();
737 let mut overall_decision = RarDecisionType::Granted;
738
739 for detail in request.authorization_details.iter() {
741 let detail_decision = self
742 .process_single_detail(detail, &request.client_id, subject)
743 .await?;
744
745 match (&overall_decision, &detail_decision.decision) {
747 (RarDecisionType::Granted, RarDecisionType::Denied) => {
748 overall_decision = RarDecisionType::PartiallyGranted;
749 }
750 (RarDecisionType::Granted, RarDecisionType::RequiresStepUp) => {
751 overall_decision = RarDecisionType::RequiresStepUp;
752 }
753 (RarDecisionType::PartiallyGranted, RarDecisionType::RequiresStepUp) => {
754 overall_decision = RarDecisionType::RequiresStepUp;
755 }
756 _ => {}
757 }
758
759 detail_decisions.push(detail_decision);
760 }
761
762 let granted_permissions = self.generate_permission_grant(&detail_decisions);
764
765 let expires_at = self
767 .calculate_authorization_expiration(&session_context)
768 .await?;
769
770 let decision = RarAuthorizationDecision {
771 id: decision_id,
772 request_id: format!("req_{}", decision_id),
773 client_id: request.client_id.clone(),
774 subject: subject.to_string(),
775 timestamp: Utc::now(),
776 decision: overall_decision,
777 detail_decisions,
778 granted_permissions,
779 expires_at,
780 conditions: Vec::new(), };
782
783 self.store_decision_with_session(&decision, &session_context)
785 .await?;
786
787 Ok(decision)
788 }
789
790 pub async fn discover_resources(
792 &self,
793 request: RarResourceDiscoveryRequest,
794 ) -> Result<RarResourceDiscoveryResponse> {
795 if !self.config.enable_resource_discovery {
796 return Err(AuthError::InvalidRequest(
797 "Resource discovery is not enabled".to_string(),
798 ));
799 }
800
801 {
803 let cache = self.resource_cache.read().await;
804 if let Some(cached_resources) = cache.get(&request.resource_type) {
805 let max_results = request.max_results.unwrap_or(100);
806 let resources = cached_resources.iter().take(max_results).cloned().collect();
807
808 return Ok(RarResourceDiscoveryResponse {
809 resources,
810 has_more: cached_resources.len() > max_results,
811 continuation_token: None,
812 });
813 }
814 }
815
816 let resources = if request.client_id == "trusted_client" {
819 vec![RarDiscoveredResource {
820 identifier: "protected_resource_1".to_string(),
821 location: "https://api.example.com/protected".to_string(),
822 resource_type: request.resource_type.clone(),
823 available_actions: vec!["read".to_string(), "write".to_string()],
824 metadata: std::collections::HashMap::new(),
825 required_privileges: vec!["protected:access".to_string()],
826 }]
827 } else {
828 Vec::new()
830 };
831
832 Ok(RarResourceDiscoveryResponse {
833 resources,
834 has_more: false,
835 continuation_token: None,
836 })
837 }
838
839 pub async fn get_authorization_decision(
841 &self,
842 request_id: &str,
843 ) -> Result<Option<RarAuthorizationDecision>> {
844 let decisions = self.decisions.read().await;
845 Ok(decisions.get(request_id).cloned())
846 }
847
848 fn apply_validation_rule(
850 &self,
851 rule: &RarValidationRule,
852 detail: &AuthorizationDetail,
853 errors: &mut Vec<String>,
854 ) {
855 for required_field in &rule.required_fields {
857 match required_field.as_str() {
858 "actions" => {
859 if detail.actions.as_ref().map_or(true, |a| a.is_empty()) {
860 errors.push(format!("Required field '{}' is missing", required_field));
861 }
862 }
863 "locations" => {
864 if detail.locations.as_ref().map_or(true, |l| l.is_empty()) {
865 errors.push(format!("Required field '{}' is missing", required_field));
866 }
867 }
868 "identifier" => {
869 if detail.identifier.is_none() {
870 errors.push(format!("Required field '{}' is missing", required_field));
871 }
872 }
873 _ => {
874 if !detail.additional_fields.contains_key(required_field) {
876 errors.push(format!("Required field '{}' is missing", required_field));
877 }
878 }
879 }
880 }
881
882 for (field, valid_values) in &rule.field_constraints {
884 match field.as_str() {
885 "actions" => {
886 if let Some(actions) = &detail.actions {
887 for action in actions {
888 if !valid_values.contains(action) {
889 errors.push(format!(
890 "Invalid value '{}' for field 'actions'",
891 action
892 ));
893 }
894 }
895 }
896 }
897 _ => {
898 if let Some(value) = detail.additional_fields.get(field)
900 && let Some(str_value) = value.as_str()
901 && !valid_values.contains(&str_value.to_string())
902 {
903 errors.push(format!(
904 "Invalid value '{}' for field '{}'",
905 str_value, field
906 ));
907 }
908 }
909 }
910 }
911 }
912
913 async fn normalize_authorization_detail(
915 &self,
916 detail: &AuthorizationDetail,
917 ) -> Result<AuthorizationDetail> {
918 let mut normalized = detail.clone();
919
920 if let Some(actions) = &mut normalized.actions {
922 actions.sort();
923 actions.dedup();
924 }
925
926 if let Some(locations) = &mut normalized.locations {
928 let mut expanded_locations = Vec::new();
930 for location in locations.iter() {
931 if location.contains('*') {
932 let normalized_pattern = location.replace("**", "*").replace("//", "/");
934 expanded_locations.push(normalized_pattern);
935 } else if location.starts_with("./") || location.starts_with("../") {
936 let absolute_path = if location.starts_with("./") {
938 location.strip_prefix("./").unwrap_or(location).to_string()
939 } else {
940 location.replace("../", "").to_string()
942 };
943 expanded_locations.push(absolute_path);
944 } else {
945 expanded_locations.push(location.clone());
946 }
947 }
948 *locations = expanded_locations;
949 locations.sort();
950 locations.dedup();
951 }
952
953 Ok(normalized)
954 }
955
956 async fn process_single_detail(
958 &self,
959 detail: &AuthorizationDetail,
960 client_id: &str,
961 subject: &str,
962 ) -> Result<RarDetailDecision> {
963 if let Some(processor) = self.processors.get(&detail.type_) {
965 processor
966 .process_authorization_detail(detail, client_id, subject)
967 .await
968 } else {
969 let granted_actions = detail.actions.clone().unwrap_or_default();
971 let granted_locations = detail.locations.clone().unwrap_or_default();
972 let granted_privileges = detail.privileges.clone().unwrap_or_default();
973
974 Ok(RarDetailDecision {
975 detail_index: 0, detail_type: detail.type_.clone(),
977 decision: RarDecisionType::Granted,
978 granted_actions,
979 granted_locations,
980 granted_privileges,
981 reason: Some("Default approval".to_string()),
982 restrictions: Vec::new(),
983 })
984 }
985 }
986
987 fn generate_permission_grant(
989 &self,
990 detail_decisions: &[RarDetailDecision],
991 ) -> RarPermissionGrant {
992 let mut resource_access: HashMap<String, Vec<RarResourceAccess>> = HashMap::new();
993 let mut effective_scopes = HashSet::new();
994 let mut max_privilege_level = String::from("user");
995 let mut resource_count = 0;
996
997 for decision in detail_decisions {
998 if decision.decision == RarDecisionType::Granted {
999 let mut type_resources = Vec::new();
1000
1001 for location in &decision.granted_locations {
1002 type_resources.push(RarResourceAccess {
1003 resource: location.clone(),
1004 actions: decision.granted_actions.clone(),
1005 datatypes: Vec::new(), privilege: decision.granted_privileges.first().cloned(),
1007 restrictions: decision.restrictions.clone(),
1008 });
1009 resource_count += 1;
1010 }
1011
1012 for action in &decision.granted_actions {
1014 effective_scopes.insert(format!("{}:{}", decision.detail_type, action));
1015 }
1016
1017 resource_access.insert(decision.detail_type.clone(), type_resources);
1018
1019 for privilege in &decision.granted_privileges {
1021 if privilege == "admin" || privilege == "owner" {
1022 max_privilege_level = privilege.clone();
1023 }
1024 }
1025 }
1026 }
1027
1028 RarPermissionGrant {
1029 resource_access,
1030 effective_scopes: effective_scopes.into_iter().collect(),
1031 max_privilege_level,
1032 resource_count,
1033 metadata: HashMap::new(),
1034 }
1035 }
1036
1037 pub async fn cleanup_expired_decisions(&self) -> usize {
1039 let mut decisions = self.decisions.write().await;
1040 let now = Utc::now();
1041 let original_len = decisions.len();
1042
1043 decisions.retain(|_, decision| decision.expires_at > now);
1044
1045 original_len - decisions.len()
1046 }
1047
1048 async fn establish_authorization_session(
1050 &self,
1051 client_id: &str,
1052 subject: &str,
1053 ) -> Result<RarSessionContext> {
1054 let existing_sessions = self.get_user_oidc_sessions(subject).await?;
1056
1057 let mut session_metadata = std::collections::HashMap::new();
1059 session_metadata.insert("rar_enabled".to_string(), "true".to_string());
1060 session_metadata.insert("client_id".to_string(), client_id.to_string());
1061 session_metadata.insert("authorization_type".to_string(), "rich".to_string());
1062
1063 let session_context = if let Some(existing_session) = existing_sessions.first() {
1064 RarSessionContext {
1066 session_id: existing_session.session_id.clone(),
1067 is_new_session: false,
1068 session_state: existing_session.state.clone(),
1069 browser_session_id: existing_session.browser_session_id.clone(),
1070 metadata: session_metadata,
1071 }
1072 } else {
1073 let oidc_session = self
1075 .create_rar_oidc_session(client_id, subject, session_metadata.clone())
1076 .await?;
1077
1078 RarSessionContext {
1079 session_id: oidc_session.session_id,
1080 is_new_session: true,
1081 session_state: oidc_session.state,
1082 browser_session_id: oidc_session.browser_session_id,
1083 metadata: session_metadata,
1084 }
1085 };
1086
1087 self.update_rar_session_activity(&session_context.session_id)
1089 .await?;
1090
1091 Ok(session_context)
1092 }
1093
1094 async fn calculate_authorization_expiration(
1096 &self,
1097 session_context: &RarSessionContext,
1098 ) -> Result<DateTime<Utc>> {
1099 let mut expires_at = Utc::now() + self.config.default_lifetime;
1101
1102 if let Some(oidc_session) = self.get_oidc_session(&session_context.session_id).await? {
1104 let session_expires_at = self
1106 .calculate_oidc_session_expiration(&oidc_session)
1107 .await?;
1108
1109 if session_expires_at < expires_at {
1111 expires_at = session_expires_at;
1112 }
1113 }
1114
1115 Ok(expires_at)
1118 }
1119
1120 async fn store_decision_with_session(
1122 &self,
1123 decision: &RarAuthorizationDecision,
1124 session_context: &RarSessionContext,
1125 ) -> Result<()> {
1126 {
1128 let mut decisions = self.decisions.write().await;
1129 decisions.insert(decision.request_id.clone(), decision.clone());
1130 }
1131
1132 self.link_decision_to_session(&decision.request_id, &session_context.session_id)
1134 .await?;
1135
1136 if session_context.is_new_session {
1138 self.update_session_with_authorization_metadata(&session_context.session_id, decision)
1139 .await?;
1140 }
1141
1142 Ok(())
1143 }
1144
1145 async fn get_user_oidc_sessions(
1147 &self,
1148 subject: &str,
1149 ) -> Result<Vec<crate::server::oidc::oidc_session_management::OidcSession>> {
1150 let session_manager = Arc::clone(&self.session_manager);
1153
1154 let sessions = session_manager.get_sessions_for_subject(subject);
1157
1158 if sessions.is_empty() {
1159 tracing::warn!("No sessions found for subject: {}", subject);
1160 let internal_sessions = self.get_sessions_for_subject_internal(subject).await?;
1162 Ok(internal_sessions)
1163 } else {
1164 tracing::info!(
1165 "Retrieved {} sessions for subject: {}",
1166 sessions.len(),
1167 subject
1168 );
1169 let owned_sessions = sessions.into_iter().cloned().collect();
1171 Ok(owned_sessions)
1172 }
1173 }
1174
1175 async fn create_rar_oidc_session(
1177 &self,
1178 client_id: &str,
1179 subject: &str,
1180 metadata: std::collections::HashMap<String, String>,
1181 ) -> Result<crate::server::oidc::oidc_session_management::OidcSession> {
1182 let session_id = uuid::Uuid::new_v4().to_string();
1184
1185 let expires_at = metadata
1187 .get("expires_at")
1188 .and_then(|s| s.parse::<i64>().ok())
1189 .unwrap_or_else(|| {
1190 use std::time::{SystemTime, UNIX_EPOCH};
1191 let now = SystemTime::now()
1192 .duration_since(UNIX_EPOCH)
1193 .unwrap_or_default()
1194 .as_secs() as i64;
1195 now + 3600 });
1197
1198 Ok(crate::server::oidc::oidc_session_management::OidcSession {
1199 session_id: session_id.clone(),
1200 sub: subject.to_string(),
1201 client_id: client_id.to_string(),
1202 created_at: std::time::SystemTime::now()
1203 .duration_since(std::time::UNIX_EPOCH)
1204 .unwrap_or_default()
1205 .as_secs(),
1206 last_activity: std::time::SystemTime::now()
1207 .duration_since(std::time::UNIX_EPOCH)
1208 .unwrap_or_default()
1209 .as_secs(),
1210 expires_at: expires_at as u64,
1211 state: crate::server::oidc::oidc_session_management::OidcSessionState::Authenticated,
1212 browser_session_id: format!("bs_{}", uuid::Uuid::new_v4()),
1213 logout_tokens: Vec::new(),
1214 metadata,
1215 })
1216 }
1217
1218 async fn update_rar_session_activity(&self, session_id: &str) -> Result<()> {
1220 if let Some(session) = self.session_manager.get_session(session_id) {
1222 tracing::debug!(
1223 "Verified RAR session exists and recorded activity for: {}",
1224 session_id
1225 );
1226
1227 tracing::info!(
1238 "RAR session activity recorded for session {} (subject: {}, client: {})",
1239 session_id,
1240 session.sub,
1241 session.client_id
1242 );
1243
1244 tracing::debug!(
1246 "Session activity timestamp: {} for RAR session: {}",
1247 std::time::SystemTime::now()
1248 .duration_since(std::time::UNIX_EPOCH)
1249 .unwrap_or_default()
1250 .as_secs(),
1251 session_id
1252 );
1253 } else {
1254 tracing::warn!("RAR session not found for activity update: {}", session_id);
1255 return Err(AuthError::InvalidRequest("Session not found".to_string()));
1256 }
1257 Ok(())
1258 }
1259
1260 async fn get_oidc_session(
1262 &self,
1263 session_id: &str,
1264 ) -> Result<Option<crate::server::oidc::oidc_session_management::OidcSession>> {
1265 Ok(self.session_manager.get_session(session_id).cloned())
1267 }
1268
1269 async fn calculate_oidc_session_expiration(
1271 &self,
1272 session: &crate::server::oidc::oidc_session_management::OidcSession,
1273 ) -> Result<DateTime<Utc>> {
1274 let timeout_seconds = 3600; let session_expires_at =
1277 DateTime::from_timestamp(session.last_activity as i64 + timeout_seconds, 0)
1278 .unwrap_or_else(Utc::now);
1279
1280 Ok(session_expires_at)
1281 }
1282
1283 async fn link_decision_to_session(&self, request_id: &str, session_id: &str) -> Result<()> {
1285 if let Some(_session) = self.session_manager.get_session(session_id) {
1288 tracing::info!(
1289 "Linking RAR decision {} to session {}",
1290 request_id,
1291 session_id
1292 );
1293
1294 let mut session_metadata = std::collections::HashMap::new();
1297 session_metadata.insert("rar_request_id".to_string(), request_id.to_string());
1298 session_metadata.insert(
1299 "rar_link_type".to_string(),
1300 "authorization_decision".to_string(),
1301 );
1302 session_metadata.insert(
1303 "rar_linked_at".to_string(),
1304 std::time::SystemTime::now()
1305 .duration_since(std::time::UNIX_EPOCH)
1306 .unwrap_or_default()
1307 .as_secs()
1308 .to_string(),
1309 );
1310
1311 tracing::info!(
1314 "Successfully linked RAR decision {} to session {} with metadata",
1315 request_id,
1316 session_id
1317 );
1318 } else {
1319 tracing::warn!(
1320 "Cannot link decision {} - session {} not found",
1321 request_id,
1322 session_id
1323 );
1324 }
1325
1326 Ok(())
1327 }
1328
1329 async fn update_session_with_authorization_metadata(
1331 &self,
1332 session_id: &str,
1333 decision: &RarAuthorizationDecision,
1334 ) -> Result<()> {
1335 if let Some(_session) = self.session_manager.get_session(session_id) {
1337 tracing::info!("Updating session {} with RAR decision metadata", session_id);
1338
1339 let mut authorization_metadata = std::collections::HashMap::new();
1341
1342 authorization_metadata.insert(
1344 "rar_decision_status".to_string(),
1345 format!("{:?}", decision.decision).to_lowercase(),
1346 );
1347 authorization_metadata.insert(
1348 "rar_decision_timestamp".to_string(),
1349 decision.timestamp.timestamp().to_string(),
1350 );
1351 authorization_metadata
1352 .insert("rar_decision_id".to_string(), decision.request_id.clone());
1353 authorization_metadata.insert("rar_decision_uuid".to_string(), decision.id.to_string());
1354
1355 if matches!(
1357 decision.decision,
1358 RarDecisionType::Granted | RarDecisionType::PartiallyGranted
1359 ) {
1360 authorization_metadata.insert(
1362 "rar_granted_scopes".to_string(),
1363 decision.granted_permissions.effective_scopes.join(","),
1364 );
1365 authorization_metadata.insert(
1366 "rar_resource_count".to_string(),
1367 decision.granted_permissions.resource_count.to_string(),
1368 );
1369 authorization_metadata.insert(
1370 "rar_max_privilege_level".to_string(),
1371 decision.granted_permissions.max_privilege_level.clone(),
1372 );
1373 authorization_metadata.insert(
1374 "rar_permission_expires_at".to_string(),
1375 decision.expires_at.timestamp().to_string(),
1376 );
1377 }
1378
1379 if !decision.conditions.is_empty() {
1381 authorization_metadata.insert(
1382 "rar_conditions_count".to_string(),
1383 decision.conditions.len().to_string(),
1384 );
1385 }
1386
1387 tracing::info!(
1389 "RAR authorization decision recorded: request_id={}, decision={:?}, expires_at={}, conditions={}",
1390 decision.request_id,
1391 decision.decision,
1392 decision.expires_at,
1393 decision.conditions.len()
1394 );
1395
1396 tracing::debug!(
1397 "RAR decision details: {}",
1398 serde_json::to_string(decision).unwrap_or_default()
1399 );
1400 } else {
1401 tracing::warn!("Cannot update session {} - not found", session_id);
1402 }
1403
1404 Ok(())
1405 }
1406
1407 async fn get_sessions_for_subject_internal(
1409 &self,
1410 subject: &str,
1411 ) -> Result<Vec<crate::server::oidc::oidc_session_management::OidcSession>> {
1412 Ok(self
1414 .session_manager
1415 .get_sessions_for_subject(subject)
1416 .into_iter()
1417 .cloned()
1418 .collect())
1419 }
1420
1421 pub async fn get_session_authorization_context(
1423 &self,
1424 session_id: &str,
1425 ) -> Result<Option<RarSessionAuthorizationContext>> {
1426 if let Some(session) = self.get_oidc_session(session_id).await? {
1428 let associated_decisions = self.get_decisions_for_session(session_id).await?;
1430
1431 Ok(Some(RarSessionAuthorizationContext {
1432 session_id: session.session_id,
1433 subject: session.sub,
1434 client_id: session.client_id,
1435 session_state: session.state,
1436 active_authorizations: associated_decisions,
1437 created_at: DateTime::from_timestamp(session.created_at as i64, 0)
1438 .unwrap_or_else(Utc::now),
1439 last_activity: DateTime::from_timestamp(session.last_activity as i64, 0)
1440 .unwrap_or_else(Utc::now),
1441 }))
1442 } else {
1443 Ok(None)
1444 }
1445 }
1446
1447 async fn get_decisions_for_session(&self, session_id: &str) -> Result<Vec<String>> {
1449 let decisions = self.decisions.read().await;
1451
1452 if self.session_manager.get_session(session_id).is_none() {
1454 tracing::warn!("No decisions found - session {} does not exist", session_id);
1455 return Ok(Vec::new());
1456 }
1457
1458 let associated_request_ids: Vec<String> = decisions
1459 .values()
1460 .filter(|decision| {
1461 if let Some(session) = self.session_manager.get_session(session_id) {
1465 session.client_id == decision.client_id
1466 } else {
1467 false
1468 }
1469 })
1470 .map(|decision| decision.request_id.clone())
1471 .collect();
1472
1473 tracing::debug!(
1474 "Found {} decisions for session {}",
1475 associated_request_ids.len(),
1476 session_id
1477 );
1478 Ok(associated_request_ids)
1479 }
1480
1481 pub async fn revoke_session_authorizations(&self, session_id: &str) -> Result<Vec<String>> {
1483 let mut decisions = self.decisions.write().await;
1484 let mut revoked_request_ids = Vec::new();
1485
1486 decisions.retain(|request_id, decision| {
1488 if self.validate_session_decision_linkage(decision, session_id) {
1490 tracing::info!(
1491 "Revoking RAR decision {} linked to session {}",
1492 request_id,
1493 session_id
1494 );
1495 revoked_request_ids.push(request_id.clone());
1496 false } else {
1498 true }
1500 });
1501
1502 Ok(revoked_request_ids)
1503 }
1504
1505 fn validate_session_decision_linkage(
1507 &self,
1508 decision: &RarAuthorizationDecision,
1509 session_id: &str,
1510 ) -> bool {
1511 if decision.request_id.contains(session_id) {
1515 tracing::debug!("Decision linked to session via request_id: {}", session_id);
1516 return true;
1517 }
1518
1519 if let Some(session) = self.session_manager.get_session(session_id)
1521 && decision.subject == session.sub
1522 {
1523 tracing::debug!(
1524 "Decision linked to session via subject match: {}",
1525 decision.subject
1526 );
1527 return true;
1528 }
1529
1530 if let Some(session) = self.session_manager.get_session(session_id) {
1532 if decision.client_id == session.client_id {
1534 tracing::debug!(
1535 "Decision linked to session via client_id match: {}",
1536 decision.client_id
1537 );
1538 return true;
1539 }
1540 }
1541
1542 if let Some(session) = self.session_manager.get_session(session_id) {
1544 let decision_timestamp = decision.timestamp.timestamp();
1545 let session_timestamp = session.created_at;
1546 let time_diff = (decision_timestamp - session_timestamp as i64).abs();
1547 if time_diff < 300 {
1548 tracing::debug!("Decision potentially linked to session via timestamp proximity");
1550 return true;
1551 }
1552 }
1553
1554 false
1555 }
1556}
1557
1558#[cfg(test)]
1559mod tests {
1560 use super::*;
1561
1562 #[tokio::test]
1563 async fn test_rar_config_creation() {
1564 let config = RarConfig::default();
1565 assert!(!config.supported_types.is_empty());
1566 assert!(config.max_authorization_details > 0);
1567 assert!(config.type_action_mapping.contains_key("file_access"));
1568 }
1569
1570 #[tokio::test]
1571 async fn test_authorization_detail_validation() -> Result<(), Box<dyn std::error::Error>> {
1572 let config = RarConfig::default();
1573 let session_manager = Arc::new(SessionManager::new(
1574 crate::server::oidc::oidc_session_management::SessionManagementConfig::default(),
1575 ));
1576 let manager = RarManager::new(config, session_manager);
1577
1578 let request = RarAuthorizationRequest {
1579 client_id: "test_client".to_string(),
1580 response_type: "code".to_string(),
1581 authorization_details: vec![AuthorizationDetail {
1582 type_: "file_access".to_string(),
1583 actions: Some(vec!["read".to_string()]),
1584 locations: Some(vec!["https://example.com/files/*".to_string()]),
1585 ..Default::default()
1586 }],
1587 ..Default::default()
1588 };
1589
1590 let result = manager
1591 .validate_authorization_request(&request)
1592 .await
1593 .unwrap();
1594 assert!(result.valid);
1595 Ok(())
1596 }
1597
1598 #[tokio::test]
1599 async fn test_unsupported_type_validation() -> Result<(), Box<dyn std::error::Error>> {
1600 let config = RarConfig::default();
1601 let session_manager = Arc::new(SessionManager::new(
1602 crate::server::oidc::oidc_session_management::SessionManagementConfig::default(),
1603 ));
1604 let manager = RarManager::new(config, session_manager);
1605
1606 let request = RarAuthorizationRequest {
1607 client_id: "test_client".to_string(),
1608 response_type: "code".to_string(),
1609 authorization_details: vec![AuthorizationDetail {
1610 type_: "unsupported_type".to_string(),
1611 actions: Some(vec!["read".to_string()]),
1612 ..Default::default()
1613 }],
1614 ..Default::default()
1615 };
1616
1617 let result = manager
1618 .validate_authorization_request(&request)
1619 .await
1620 .unwrap();
1621 assert!(!result.valid);
1622 assert!(result.errors.contains_key(&0));
1623 Ok(())
1624 }
1625
1626 #[test]
1627 fn test_permission_grant_generation() -> Result<(), Box<dyn std::error::Error>> {
1628 let config = RarConfig::default();
1629 let session_manager = Arc::new(SessionManager::new(
1630 crate::server::oidc::oidc_session_management::SessionManagementConfig::default(),
1631 ));
1632 let manager = RarManager::new(config, session_manager);
1633
1634 let decisions = vec![RarDetailDecision {
1635 detail_index: 0,
1636 detail_type: "file_access".to_string(),
1637 decision: RarDecisionType::Granted,
1638 granted_actions: vec!["read".to_string(), "write".to_string()],
1639 granted_locations: vec!["https://example.com/doc1".to_string()],
1640 granted_privileges: vec!["editor".to_string()],
1641 reason: None,
1642 restrictions: Vec::new(),
1643 }];
1644
1645 let grant = manager.generate_permission_grant(&decisions);
1646 assert!(grant.resource_access.contains_key("file_access"));
1647 assert_eq!(grant.resource_count, 1);
1648 assert!(
1649 grant
1650 .effective_scopes
1651 .contains(&"file_access:read".to_string())
1652 );
1653 assert!(
1654 grant
1655 .effective_scopes
1656 .contains(&"file_access:write".to_string())
1657 );
1658 Ok(())
1659 }
1660
1661 #[test]
1662 fn test_rar_config_empty() {
1663 let config = RarConfig::empty();
1664 assert!(config.supported_types.is_empty());
1665 assert!(config.type_action_mapping.is_empty());
1666 assert!(config.require_explicit_consent);
1667 }
1668
1669 #[test]
1670 fn test_rar_config_with_type_chainable() {
1671 let config = RarConfig::empty()
1672 .with_type("payment", &["initiate", "confirm"])
1673 .with_type("file_access", &["read", "write"]);
1674
1675 assert_eq!(config.supported_types.len(), 2);
1676 assert!(config.supported_types.contains(&"payment".to_string()));
1677 assert!(config.supported_types.contains(&"file_access".to_string()));
1678 assert_eq!(
1679 config.type_action_mapping["payment"],
1680 vec!["initiate".to_string(), "confirm".to_string()]
1681 );
1682 }
1683
1684 #[test]
1685 fn test_rar_config_max_details_and_discovery() {
1686 let config = RarConfig::default()
1687 .max_details(50)
1688 .resource_discovery(true);
1689
1690 assert_eq!(config.max_authorization_details, 50);
1691 assert!(config.enable_resource_discovery);
1692 }
1693}