1use crate::providers::in_memory::{InMemoryError, InMemoryStats};
46use crate::resource::{
47 ListQuery, RequestContext, Resource, ResourceProvider,
48 conditional_provider::VersionedResource,
49 version::{ConditionalResult, ScimVersion},
50};
51use crate::storage::{StorageKey, StorageProvider};
52use log::{debug, info, trace, warn};
53use serde_json::{Value, json};
54
55
56#[derive(Debug, Clone)]
62pub struct StandardResourceProvider<S: StorageProvider> {
63 storage: S,
65}
66
67impl<S: StorageProvider> StandardResourceProvider<S> {
68 pub fn new(storage: S) -> Self {
70 Self { storage }
71 }
72
73 fn effective_tenant_id(&self, context: &RequestContext) -> String {
77 context.tenant_id().unwrap_or("default").to_string()
78 }
79
80 async fn generate_resource_id(&self, _tenant_id: &str, _resource_type: &str) -> String {
82 uuid::Uuid::new_v4().to_string()
84 }
85
86 async fn check_username_duplicate(
88 &self,
89 tenant_id: &str,
90 username: &str,
91 exclude_id: Option<&str>,
92 ) -> Result<(), InMemoryError> {
93 let prefix = StorageKey::prefix(tenant_id, "User");
94 let matches = self
95 .storage
96 .find_by_attribute(prefix, "userName", username)
97 .await
98 .map_err(|e| InMemoryError::Internal {
99 message: format!("Storage error during username check: {}", e),
100 })?;
101
102 for (key, _data) in matches {
103 if Some(key.resource_id()) != exclude_id {
105 return Err(InMemoryError::DuplicateAttribute {
106 resource_type: "User".to_string(),
107 attribute: "userName".to_string(),
108 value: username.to_string(),
109 tenant_id: tenant_id.to_string(),
110 });
111 }
112 }
113
114 Ok(())
115 }
116
117 fn add_scim_metadata(&self, mut resource: Resource) -> Resource {
119 if let Err(_e) = resource.create_meta("https://example.com/scim/v2") {
121 return resource;
122 }
123
124 if let Some(meta) = resource.get_meta().cloned() {
126 let resource_json = resource.to_json().unwrap_or_default();
128 let content_bytes = resource_json.to_string().as_bytes().to_vec();
129 let scim_version = ScimVersion::from_content(&content_bytes);
130 let version = scim_version.to_http_header();
131
132 if let Ok(meta_with_version) = meta.with_version(version) {
133 resource.set_meta(meta_with_version);
134 }
135 }
136
137 resource
138 }
139
140 pub async fn clear(&self) {
175 if let Err(e) = self.storage.clear().await {
177 warn!("Failed to clear storage: {:?}", e);
178 }
179 }
180
181 pub async fn get_stats(&self) -> InMemoryStats {
221 let tenants = self.storage.list_tenants().await.unwrap_or_default();
223 let resource_types = self.storage.list_all_resource_types().await.unwrap_or_default();
224
225 let mut total_resources = 0;
226
227 for tenant_id in &tenants {
229 for resource_type in &resource_types {
230 let prefix = StorageKey::prefix(tenant_id, resource_type);
231 if let Ok(count) = self.storage.count(prefix).await {
232 total_resources += count;
233 }
234 }
235 }
236
237 InMemoryStats {
238 tenant_count: tenants.len(),
239 total_resources,
240 resource_type_count: resource_types.len(),
241 resource_types,
242 }
243 }
244
245 pub async fn list_resources_in_tenant(
247 &self,
248 tenant_id: &str,
249 resource_type: &str,
250 ) -> Vec<Resource> {
251 let prefix = StorageKey::prefix(tenant_id, resource_type);
252 match self.storage.list(prefix, 0, usize::MAX).await {
253 Ok(storage_results) => {
254 let mut resources = Vec::new();
255 for (_key, data) in storage_results {
256 match Resource::from_json(resource_type.to_string(), data) {
257 Ok(resource) => resources.push(resource),
258 Err(e) => {
259 warn!(
260 "Failed to deserialize resource in list_resources_in_tenant: {}",
261 e
262 );
263 }
264 }
265 }
266 resources
267 }
268 Err(e) => {
269 warn!("Storage error in list_resources_in_tenant: {}", e);
270 Vec::new()
271 }
272 }
273 }
274
275 async fn count_resources_for_tenant(&self, tenant_id: &str, resource_type: &str) -> usize {
277 let prefix = StorageKey::prefix(tenant_id, resource_type);
278 match self.storage.count(prefix).await {
279 Ok(count) => count,
280 Err(e) => {
281 warn!("Storage error in count_resources_for_tenant: {}", e);
282 0
283 }
284 }
285 }
286}
287
288impl<S: StorageProvider> ResourceProvider for StandardResourceProvider<S> {
293 type Error = InMemoryError;
294
295 async fn create_resource(
296 &self,
297 resource_type: &str,
298 mut data: Value,
299 context: &RequestContext,
300 ) -> Result<Resource, Self::Error> {
301 let tenant_id = self.effective_tenant_id(context);
302
303 info!(
304 "Creating {} resource for tenant '{}' (request: '{}')",
305 resource_type, tenant_id, context.request_id
306 );
307 trace!(
308 "Create data: {}",
309 serde_json::to_string(&data).unwrap_or_else(|_| "invalid json".to_string())
310 );
311
312 context
314 .validate_operation("create")
315 .map_err(|e| InMemoryError::Internal { message: e })?;
316
317 if let Some(tenant_context) = &context.tenant_context {
319 if resource_type == "User" {
320 if let Some(max_users) = tenant_context.permissions.max_users {
321 let current_count = self.count_resources_for_tenant(&tenant_id, "User").await;
322 if current_count >= max_users {
323 return Err(InMemoryError::Internal {
324 message: format!(
325 "User limit exceeded: {}/{}",
326 current_count, max_users
327 ),
328 });
329 }
330 }
331 } else if resource_type == "Group" {
332 if let Some(max_groups) = tenant_context.permissions.max_groups {
333 let current_count = self.count_resources_for_tenant(&tenant_id, "Group").await;
334 if current_count >= max_groups {
335 return Err(InMemoryError::Internal {
336 message: format!(
337 "Group limit exceeded: {}/{}",
338 current_count, max_groups
339 ),
340 });
341 }
342 }
343 }
344 }
345
346 if data.get("id").is_none() {
348 let id = self.generate_resource_id(&tenant_id, resource_type).await;
349 if let Some(obj) = data.as_object_mut() {
350 obj.insert("id".to_string(), json!(id));
351 }
352 }
353
354 let resource = Resource::from_json(resource_type.to_string(), data).map_err(|e| {
356 InMemoryError::InvalidData {
357 message: format!("Failed to create resource: {}", e),
358 }
359 })?;
360
361 if resource_type == "User" {
363 if let Some(username) = resource.get_username() {
364 self.check_username_duplicate(&tenant_id, username, None)
365 .await?;
366 }
367 }
368
369 let resource_with_meta = self.add_scim_metadata(resource);
371 let resource_id = resource_with_meta.get_id().unwrap_or("unknown").to_string();
372
373 let key = StorageKey::new(&tenant_id, resource_type, &resource_id);
375 let stored_data = self
376 .storage
377 .put(
378 key,
379 resource_with_meta
380 .to_json()
381 .map_err(|e| InMemoryError::Internal {
382 message: format!("Failed to serialize resource: {}", e),
383 })?,
384 )
385 .await
386 .map_err(|e| InMemoryError::Internal {
387 message: format!("Storage error during create: {}", e),
388 })?;
389
390 Resource::from_json(resource_type.to_string(), stored_data).map_err(|e| {
392 InMemoryError::InvalidData {
393 message: format!("Failed to deserialize stored resource: {}", e),
394 }
395 })
396 }
397
398 async fn get_resource(
399 &self,
400 resource_type: &str,
401 id: &str,
402 context: &RequestContext,
403 ) -> Result<Option<Resource>, Self::Error> {
404 let tenant_id = self.effective_tenant_id(context);
405
406 debug!(
407 "Getting {} resource with ID '{}' for tenant '{}' (request: '{}')",
408 resource_type, id, tenant_id, context.request_id
409 );
410
411 context
413 .validate_operation("read")
414 .map_err(|e| InMemoryError::Internal { message: e })?;
415
416 let key = StorageKey::new(&tenant_id, resource_type, id);
417 let resource_data = self
418 .storage
419 .get(key)
420 .await
421 .map_err(|e| InMemoryError::Internal {
422 message: format!("Storage error during get: {}", e),
423 })?;
424
425 let resource = match resource_data {
426 Some(data) => {
427 let resource =
428 Resource::from_json(resource_type.to_string(), data).map_err(|e| {
429 InMemoryError::InvalidData {
430 message: format!("Failed to deserialize resource: {}", e),
431 }
432 })?;
433 trace!("Resource found and returned");
434 Some(resource)
435 }
436 None => {
437 debug!("Resource not found");
438 None
439 }
440 };
441
442 Ok(resource)
443 }
444
445 async fn update_resource(
446 &self,
447 resource_type: &str,
448 id: &str,
449 mut data: Value,
450 context: &RequestContext,
451 ) -> Result<Resource, Self::Error> {
452 let tenant_id = self.effective_tenant_id(context);
453
454 info!(
455 "Updating {} resource with ID '{}' for tenant '{}' (request: '{}')",
456 resource_type, id, tenant_id, context.request_id
457 );
458 trace!(
459 "Update data: {}",
460 serde_json::to_string(&data).unwrap_or_else(|_| "invalid json".to_string())
461 );
462
463 context
465 .validate_operation("update")
466 .map_err(|e| InMemoryError::Internal { message: e })?;
467
468 if let Some(obj) = data.as_object_mut() {
470 obj.insert("id".to_string(), json!(id));
471 }
472
473 let resource = Resource::from_json(resource_type.to_string(), data).map_err(|e| {
475 InMemoryError::InvalidData {
476 message: format!("Failed to update resource: {}", e),
477 }
478 })?;
479
480 if resource_type == "User" {
482 if let Some(username) = resource.get_username() {
483 self.check_username_duplicate(&tenant_id, username, Some(id))
484 .await?;
485 }
486 }
487
488 let key = StorageKey::new(&tenant_id, resource_type, id);
490 let exists =
491 self.storage
492 .exists(key.clone())
493 .await
494 .map_err(|e| InMemoryError::Internal {
495 message: format!("Storage error during existence check: {}", e),
496 })?;
497
498 if !exists {
499 return Err(InMemoryError::ResourceNotFound {
500 resource_type: resource_type.to_string(),
501 id: id.to_string(),
502 tenant_id,
503 });
504 }
505
506 let resource_with_meta = self.add_scim_metadata(resource);
508
509 let stored_data = self
511 .storage
512 .put(
513 key,
514 resource_with_meta
515 .to_json()
516 .map_err(|e| InMemoryError::Internal {
517 message: format!("Failed to serialize resource: {}", e),
518 })?,
519 )
520 .await
521 .map_err(|e| InMemoryError::Internal {
522 message: format!("Storage error during update: {}", e),
523 })?;
524
525 Resource::from_json(resource_type.to_string(), stored_data).map_err(|e| {
527 InMemoryError::InvalidData {
528 message: format!("Failed to deserialize updated resource: {}", e),
529 }
530 })
531 }
532
533 async fn delete_resource(
534 &self,
535 resource_type: &str,
536 id: &str,
537 context: &RequestContext,
538 ) -> Result<(), Self::Error> {
539 let tenant_id = self.effective_tenant_id(context);
540
541 info!(
542 "Deleting {} resource with ID '{}' for tenant '{}' (request: '{}')",
543 resource_type, id, tenant_id, context.request_id
544 );
545
546 context
548 .validate_operation("delete")
549 .map_err(|e| InMemoryError::Internal { message: e })?;
550
551 let key = StorageKey::new(&tenant_id, resource_type, id);
553 let removed = self
554 .storage
555 .delete(key)
556 .await
557 .map_err(|e| InMemoryError::Internal {
558 message: format!("Storage error during delete: {}", e),
559 })?;
560
561 if !removed {
562 warn!(
563 "Attempted to delete non-existent {} resource with ID '{}' for tenant '{}'",
564 resource_type, id, tenant_id
565 );
566 return Err(InMemoryError::ResourceNotFound {
567 resource_type: resource_type.to_string(),
568 id: id.to_string(),
569 tenant_id,
570 });
571 }
572
573 debug!(
574 "Successfully deleted {} resource with ID '{}' for tenant '{}'",
575 resource_type, id, tenant_id
576 );
577 Ok(())
578 }
579
580 async fn list_resources(
581 &self,
582 resource_type: &str,
583 query: Option<&ListQuery>,
584 context: &RequestContext,
585 ) -> Result<Vec<Resource>, Self::Error> {
586 let tenant_id = self.effective_tenant_id(context);
587
588 debug!(
589 "Listing {} resources for tenant '{}' (request: '{}')",
590 resource_type, tenant_id, context.request_id
591 );
592
593 context
595 .validate_operation("list")
596 .map_err(|e| InMemoryError::Internal { message: e })?;
597
598 let prefix = StorageKey::prefix(&tenant_id, resource_type);
600 let storage_results = self
601 .storage
602 .list(prefix, 0, usize::MAX) .await
604 .map_err(|e| InMemoryError::Internal {
605 message: format!("Storage error during list: {}", e),
606 })?;
607
608 let mut resources = Vec::new();
610 for (_key, data) in storage_results {
611 match Resource::from_json(resource_type.to_string(), data) {
612 Ok(resource) => resources.push(resource),
613 Err(e) => {
614 warn!("Failed to deserialize resource during list: {}", e);
615 }
617 }
618 }
619
620 let mut filtered_resources = resources;
622
623 if let Some(q) = query {
624 if let Some(start_index) = q.start_index {
626 let start = (start_index.saturating_sub(1)) as usize; if start < filtered_resources.len() {
628 filtered_resources = filtered_resources.into_iter().skip(start).collect();
629 } else {
630 filtered_resources = Vec::new();
631 }
632 }
633
634 if let Some(count) = q.count {
635 filtered_resources.truncate(count as usize);
636 }
637 }
638
639 debug!(
640 "Found {} {} resources for tenant '{}' (after filtering)",
641 filtered_resources.len(),
642 resource_type,
643 tenant_id
644 );
645
646 Ok(filtered_resources)
647 }
648
649 async fn find_resource_by_attribute(
650 &self,
651 resource_type: &str,
652 attribute: &str,
653 value: &Value,
654 context: &RequestContext,
655 ) -> Result<Option<Resource>, Self::Error> {
656 let tenant_id = self.effective_tenant_id(context);
657
658 let prefix = StorageKey::prefix(&tenant_id, resource_type);
660 let value_str = match value {
661 Value::String(s) => s.clone(),
662 _ => value.to_string().trim_matches('"').to_string(),
663 };
664
665 let matches = self
666 .storage
667 .find_by_attribute(prefix, attribute, &value_str)
668 .await
669 .map_err(|e| InMemoryError::Internal {
670 message: format!("Storage error during find by attribute: {}", e),
671 })?;
672
673 for (_key, data) in matches {
675 match Resource::from_json(resource_type.to_string(), data) {
676 Ok(resource) => return Ok(Some(resource)),
677 Err(e) => {
678 warn!("Failed to deserialize resource during find: {}", e);
679 continue;
680 }
681 }
682 }
683
684 Ok(None)
685 }
686
687 async fn resource_exists(
688 &self,
689 resource_type: &str,
690 id: &str,
691 context: &RequestContext,
692 ) -> Result<bool, Self::Error> {
693 let tenant_id = self.effective_tenant_id(context);
694
695 let key = StorageKey::new(&tenant_id, resource_type, id);
696 self.storage
697 .exists(key)
698 .await
699 .map_err(|e| InMemoryError::Internal {
700 message: format!("Storage error during exists check: {}", e),
701 })
702 }
703
704 async fn patch_resource(
705 &self,
706 resource_type: &str,
707 id: &str,
708 patch_request: &Value,
709 context: &RequestContext,
710 ) -> Result<Resource, Self::Error> {
711 let _tenant_id = self.effective_tenant_id(context);
712
713 if let Some(etag_value) = patch_request.get("etag") {
715 if let Some(etag_str) = etag_value.as_str() {
716 let tenant_id = self.effective_tenant_id(context);
718 let key = StorageKey::new(&tenant_id, resource_type, id);
719
720 match self.storage.get(key).await {
721 Ok(Some(current_data)) => {
722 let current_resource = Resource::from_json(resource_type.to_string(), current_data)
724 .map_err(|e| InMemoryError::InvalidData {
725 message: format!("Failed to deserialize current resource: {}", e),
726 })?;
727
728 if let Some(current_version) = current_resource.get_meta().and_then(|m| m.version.as_ref()) {
730 let current_etag = current_version.as_str();
731 let normalized_current = current_etag.trim_start_matches("W/").trim_matches('"');
734 let normalized_provided = etag_str.trim_start_matches("W/").trim_matches('"');
735
736 if normalized_current != normalized_provided {
737 return Err(InMemoryError::PreconditionFailed {
738 message: format!("ETag mismatch. Expected '{}', got '{}'", normalized_current, normalized_provided),
739 });
740 }
741 }
742 }
743 Ok(None) => {
744 return Err(InMemoryError::NotFound {
745 resource_type: resource_type.to_string(),
746 id: id.to_string(),
747 });
748 }
749 Err(_) => {
750 return Err(InMemoryError::Internal {
751 message: "Failed to retrieve resource for ETag validation".to_string(),
752 });
753 }
754 }
755 }
756 }
757
758 let operations = patch_request
760 .get("Operations")
761 .and_then(|ops| ops.as_array())
762 .ok_or(InMemoryError::InvalidInput {
763 message: "PATCH request must contain Operations array".to_string(),
764 })?;
765
766 if operations.is_empty() {
768 return Err(InMemoryError::InvalidInput {
769 message: "Operations array cannot be empty".to_string(),
770 });
771 }
772
773 let tenant_id = self.effective_tenant_id(context);
775 let key = StorageKey::new(&tenant_id, resource_type, id);
776
777 match self.storage.get(key.clone()).await {
778 Ok(Some(mut current_data)) => {
779 for operation in operations {
781 self.apply_patch_operation(&mut current_data, operation)?;
782 }
783
784 let new_version = ScimVersion::from_content(
786 serde_json::to_string(¤t_data).unwrap().as_bytes(),
787 );
788 if let Some(obj) = current_data.as_object_mut() {
789 obj.insert("version".to_string(), json!(new_version.to_string()));
790 }
791
792 self.storage
794 .put(key, current_data.clone())
795 .await
796 .map_err(|_| InMemoryError::Internal {
797 message: "Failed to store patched resource".to_string(),
798 })?;
799
800 let updated_resource = Resource::from_json(resource_type.to_string(), current_data)
802 .map_err(|e| InMemoryError::InvalidInput {
803 message: format!("Failed to deserialize patched resource: {}", e),
804 })?;
805
806 Ok(updated_resource)
807 }
808 Ok(None) => Err(InMemoryError::NotFound {
809 resource_type: resource_type.to_string(),
810 id: id.to_string(),
811 }),
812 Err(_) => Err(InMemoryError::Internal {
813 message: "Failed to retrieve resource for patch".to_string(),
814 }),
815 }
816 }
817
818 fn apply_patch_operation(
820 &self,
821 resource_data: &mut Value,
822 operation: &Value,
823 ) -> Result<(), Self::Error> {
824 let op =
825 operation
826 .get("op")
827 .and_then(|v| v.as_str())
828 .ok_or(InMemoryError::InvalidInput {
829 message: "PATCH operation must have 'op' field".to_string(),
830 })?;
831
832 let path = operation.get("path").and_then(|v| v.as_str());
833 let value = operation.get("value");
834
835 if let Some(path_str) = path {
837 if self.is_readonly_attribute(path_str) {
838 return Err(InMemoryError::InvalidInput {
839 message: format!("Cannot modify readonly attribute: {}", path_str),
840 });
841 }
842 }
843
844 match op.to_lowercase().as_str() {
845 "add" => self.apply_add_operation(resource_data, path, value),
846 "remove" => self.apply_remove_operation(resource_data, path),
847 "replace" => self.apply_replace_operation(resource_data, path, value),
848 _ => Err(InMemoryError::InvalidInput {
849 message: format!("Unsupported PATCH operation: {}", op),
850 }),
851 }
852 }
853
854}
855
856impl<S: StorageProvider> StandardResourceProvider<S> {
857 fn is_readonly_attribute(&self, path: &str) -> bool {
859 match path.to_lowercase().as_str() {
861 "meta.created" => true,
863 "meta.resourcetype" => true,
864 "meta.location" => true,
865 "id" => true,
866 path if path.starts_with("meta.") && (path.ends_with(".created") || path.ends_with(".resourcetype") || path.ends_with(".location")) => true,
868 _ => false,
869 }
870 }
871
872 fn apply_add_operation(
874 &self,
875 resource_data: &mut Value,
876 path: Option<&str>,
877 value: Option<&Value>,
878 ) -> Result<(), InMemoryError> {
879 let value = value.ok_or(InMemoryError::InvalidInput {
880 message: "ADD operation requires a value".to_string(),
881 })?;
882
883 match path {
884 Some(path_str) => {
885 self.set_value_at_path(resource_data, path_str, value.clone())?;
886 }
887 None => {
888 if let (Some(current_obj), Some(value_obj)) =
890 (resource_data.as_object_mut(), value.as_object())
891 {
892 for (key, val) in value_obj {
893 current_obj.insert(key.clone(), val.clone());
894 }
895 }
896 }
897 }
898 Ok(())
899 }
900
901 fn apply_remove_operation(
903 &self,
904 resource_data: &mut Value,
905 path: Option<&str>,
906 ) -> Result<(), InMemoryError> {
907 if let Some(path_str) = path {
908 self.remove_value_at_path(resource_data, path_str)?;
909 }
910 Ok(())
911 }
912
913 fn apply_replace_operation(
915 &self,
916 resource_data: &mut Value,
917 path: Option<&str>,
918 value: Option<&Value>,
919 ) -> Result<(), InMemoryError> {
920 let value = value.ok_or(InMemoryError::InvalidInput {
921 message: "REPLACE operation requires a value".to_string(),
922 })?;
923
924 match path {
925 Some(path_str) => {
926 self.set_value_at_path(resource_data, path_str, value.clone())?;
927 }
928 None => {
929 *resource_data = value.clone();
931 }
932 }
933 Ok(())
934 }
935
936 fn set_value_at_path(
938 &self,
939 data: &mut Value,
940 path: &str,
941 value: Value,
942 ) -> Result<(), InMemoryError> {
943 if !self.is_valid_scim_path(path) {
945 return Err(InMemoryError::InvalidInput {
946 message: format!("Invalid SCIM path: '{}'", path),
947 });
948 }
949
950 let parts: Vec<&str> = path.split('.').collect();
951
952 if parts.len() == 1 {
953 if let Some(obj) = data.as_object_mut() {
955 let attribute_name = parts[0];
956
957 if Self::is_multivalued_attribute(attribute_name) {
959 if let Some(existing) = obj.get_mut(attribute_name) {
960 if let Some(existing_array) = existing.as_array_mut() {
961 if value.is_array() {
963 obj.insert(attribute_name.to_string(), value);
964 } else {
965 existing_array.push(value);
967 }
968 return Ok(());
969 }
970 }
971 let new_array = if value.is_array() {
973 value
974 } else {
975 json!([value])
976 };
977 obj.insert(attribute_name.to_string(), new_array);
978 } else {
979 obj.insert(attribute_name.to_string(), value);
981 }
982 }
983 return Ok(());
984 }
985
986 let mut current = data;
988
989 for part in &parts[..parts.len() - 1] {
990 if let Some(obj) = current.as_object_mut() {
991 let entry = obj
992 .entry(part.to_string())
993 .or_insert_with(|| Value::Object(serde_json::Map::new()));
994 current = entry;
995 } else {
996 return Err(InMemoryError::InvalidInput {
997 message: format!(
998 "Cannot navigate path '{}' - intermediate value is not an object",
999 path
1000 ),
1001 });
1002 }
1003 }
1004
1005 if let Some(obj) = current.as_object_mut() {
1007 obj.insert(parts.last().unwrap().to_string(), value);
1008 } else {
1009 return Err(InMemoryError::InvalidInput {
1010 message: format!(
1011 "Cannot set value at path '{}' - target is not an object",
1012 path
1013 ),
1014 });
1015 }
1016
1017 Ok(())
1018 }
1019
1020 fn remove_value_at_path(&self, data: &mut Value, path: &str) -> Result<(), InMemoryError> {
1022 if !self.is_valid_scim_path(path) {
1024 return Err(InMemoryError::InvalidInput {
1025 message: format!("Invalid SCIM path: '{}'", path),
1026 });
1027 }
1028
1029 let parts: Vec<&str> = path.split('.').collect();
1030
1031 if parts.len() == 1 {
1032 if let Some(obj) = data.as_object_mut() {
1034 obj.remove(parts[0]);
1035 }
1036 return Ok(());
1037 }
1038
1039 let mut current = data;
1041
1042 for part in &parts[..parts.len() - 1] {
1043 if let Some(obj) = current.as_object_mut() {
1044 match obj.get_mut(*part) {
1046 Some(value) => current = value,
1047 None => return Ok(()), }
1049 } else {
1050 return Err(InMemoryError::InvalidInput {
1051 message: format!(
1052 "Cannot navigate path '{}' - intermediate value is not an object",
1053 path
1054 ),
1055 });
1056 }
1057 }
1058
1059 if let Some(obj) = current.as_object_mut() {
1061 obj.remove(*parts.last().unwrap());
1062 }
1063
1064 Ok(())
1065 }
1066
1067 fn is_multivalued_attribute(attribute_name: &str) -> bool {
1069 matches!(
1070 attribute_name,
1071 "emails" | "phoneNumbers" | "addresses" | "groups" | "members"
1072 )
1073 }
1074
1075 fn is_valid_scim_path(&self, path: &str) -> bool {
1077 let actual_path = if path.contains(':') && path.contains("urn:ietf:params:scim:schemas:") {
1079 if let Some(colon_pos) = path.rfind(':') {
1081 let after_colon = &path[colon_pos + 1..];
1082 if let Some(dot_pos) = after_colon.find('.') {
1084 &after_colon[dot_pos + 1..]
1085 } else {
1086 after_colon
1087 }
1088 } else {
1089 path
1090 }
1091 } else {
1092 path
1093 };
1094
1095 if actual_path.contains('[') {
1097 if let Some(bracket_start) = actual_path.find('[') {
1099 let before_bracket = &actual_path[..bracket_start];
1100 if !self.is_valid_simple_path(before_bracket) {
1101 return false;
1102 }
1103 let remaining = &actual_path[bracket_start..];
1105 if !remaining.ends_with(']') || remaining.matches('[').count() != remaining.matches(']').count() {
1106 return false;
1107 }
1108 return true;
1109 }
1110 }
1111
1112 let parts: Vec<&str> = actual_path.split('.').collect();
1114
1115 if !self.is_valid_simple_path(parts[0]) {
1117 return false;
1118 }
1119
1120 if parts.len() > 1 {
1122 if parts[0] == "name" {
1124 return matches!(parts[1], "formatted" | "familyName" | "givenName" | "middleName" | "honorificPrefix" | "honorificSuffix");
1125 }
1126 if parts[0] == "meta" {
1127 return matches!(parts[1], "resourceType" | "created" | "lastModified" | "location" | "version");
1128 }
1129 }
1132
1133 true
1134 }
1135
1136 fn is_valid_simple_path(&self, attribute: &str) -> bool {
1138 let user_attributes = [
1140 "id", "externalId", "userName", "name", "displayName", "nickName", "profileUrl",
1141 "title", "userType", "preferredLanguage", "locale", "timezone", "active",
1142 "password", "emails", "phoneNumbers", "addresses", "groups", "entitlements",
1143 "roles", "x509Certificates", "meta"
1144 ];
1145
1146 let group_attributes = [
1148 "id", "externalId", "displayName", "members", "meta"
1149 ];
1150
1151 let enterprise_attributes = [
1153 "employeeNumber", "costCenter", "organization", "division", "department", "manager"
1154 ];
1155
1156 user_attributes.contains(&attribute) ||
1158 group_attributes.contains(&attribute) ||
1159 enterprise_attributes.contains(&attribute)
1160 }
1161}
1162
1163impl<S: StorageProvider> StandardResourceProvider<S> {
1165 pub async fn conditional_update(
1166 &self,
1167 resource_type: &str,
1168 id: &str,
1169 data: Value,
1170 expected_version: &ScimVersion,
1171 context: &RequestContext,
1172 ) -> Result<ConditionalResult<VersionedResource>, InMemoryError> {
1173 let tenant_id = self.effective_tenant_id(context);
1174 let key = StorageKey::new(&tenant_id, resource_type, id);
1175
1176 match self.storage.get(key.clone()).await {
1178 Ok(Some(current_data)) => {
1179 let current_resource =
1181 Resource::from_json(resource_type.to_string(), current_data.clone()).map_err(
1182 |e| InMemoryError::InvalidInput {
1183 message: format!("Failed to deserialize stored resource: {}", e),
1184 },
1185 )?;
1186
1187 let current_version = VersionedResource::new(current_resource.clone())
1189 .version()
1190 .clone();
1191 if ¤t_version != expected_version {
1192 use crate::resource::version::VersionConflict;
1193 return Ok(ConditionalResult::VersionMismatch(VersionConflict::new(
1194 expected_version.clone(),
1195 current_version,
1196 "Resource was modified by another client",
1197 )));
1198 }
1199
1200 let mut updated_resource = Resource::from_json(resource_type.to_string(), data)
1202 .map_err(|e| InMemoryError::InvalidInput {
1203 message: format!("Failed to create updated resource: {}", e),
1204 })?;
1205
1206 if let Some(original_id) = current_resource.get_id() {
1208 updated_resource.set_id(original_id).map_err(|e| {
1209 InMemoryError::InvalidInput {
1210 message: format!("Failed to set ID: {}", e),
1211 }
1212 })?;
1213 }
1214
1215 let updated_data =
1217 updated_resource
1218 .to_json()
1219 .map_err(|e| InMemoryError::InvalidInput {
1220 message: format!("Failed to serialize updated resource: {}", e),
1221 })?;
1222
1223 self.storage
1224 .put(key, updated_data)
1225 .await
1226 .map_err(|_| InMemoryError::Internal {
1227 message: "Failed to store updated resource".to_string(),
1228 })?;
1229
1230 Ok(ConditionalResult::Success(VersionedResource::new(
1231 updated_resource,
1232 )))
1233 }
1234 Ok(None) => Err(InMemoryError::NotFound {
1235 resource_type: resource_type.to_string(),
1236 id: id.to_string(),
1237 }),
1238 Err(_) => Err(InMemoryError::Internal {
1239 message: "Failed to retrieve resource for conditional update".to_string(),
1240 }),
1241 }
1242 }
1243
1244 pub async fn conditional_delete(
1245 &self,
1246 resource_type: &str,
1247 id: &str,
1248 expected_version: &ScimVersion,
1249 context: &RequestContext,
1250 ) -> Result<ConditionalResult<()>, InMemoryError> {
1251 let tenant_id = self.effective_tenant_id(context);
1252 let key = StorageKey::new(&tenant_id, resource_type, id);
1253
1254 match self.storage.get(key.clone()).await {
1256 Ok(Some(current_data)) => {
1257 let current_resource = Resource::from_json(resource_type.to_string(), current_data)
1259 .map_err(|e| InMemoryError::InvalidInput {
1260 message: format!("Failed to deserialize stored resource: {}", e),
1261 })?;
1262
1263 let current_version = VersionedResource::new(current_resource.clone())
1265 .version()
1266 .clone();
1267 if ¤t_version != expected_version {
1268 use crate::resource::version::VersionConflict;
1269 return Ok(ConditionalResult::VersionMismatch(VersionConflict::new(
1270 expected_version.clone(),
1271 current_version,
1272 "Resource was modified by another client",
1273 )));
1274 }
1275
1276 self.storage
1278 .delete(key)
1279 .await
1280 .map_err(|_| InMemoryError::Internal {
1281 message: "Failed to delete resource".to_string(),
1282 })?;
1283
1284 Ok(ConditionalResult::Success(()))
1285 }
1286 Ok(None) => Err(InMemoryError::NotFound {
1287 resource_type: resource_type.to_string(),
1288 id: id.to_string(),
1289 }),
1290 Err(_) => Err(InMemoryError::Internal {
1291 message: "Failed to retrieve resource for conditional delete".to_string(),
1292 }),
1293 }
1294 }
1295
1296 pub async fn conditional_patch_resource(
1297 &self,
1298 resource_type: &str,
1299 id: &str,
1300 patch_request: &Value,
1301 expected_version: &ScimVersion,
1302 context: &RequestContext,
1303 ) -> Result<ConditionalResult<VersionedResource>, InMemoryError> {
1304 let tenant_id = self.effective_tenant_id(context);
1305 let key = StorageKey::new(&tenant_id, resource_type, id);
1306
1307 match self.storage.get(key.clone()).await {
1309 Ok(Some(current_data)) => {
1310 let current_resource =
1312 Resource::from_json(resource_type.to_string(), current_data.clone()).map_err(
1313 |e| InMemoryError::InvalidInput {
1314 message: format!("Failed to deserialize stored resource: {}", e),
1315 },
1316 )?;
1317
1318 let current_version = VersionedResource::new(current_resource.clone())
1320 .version()
1321 .clone();
1322 if ¤t_version != expected_version {
1323 use crate::resource::version::VersionConflict;
1324 return Ok(ConditionalResult::VersionMismatch(VersionConflict::new(
1325 expected_version.clone(),
1326 current_version,
1327 "Resource was modified by another client",
1328 )));
1329 }
1330
1331 let mut patched_data = current_data;
1333
1334 if let Some(operations) = patch_request.get("Operations") {
1336 if let Some(ops_array) = operations.as_array() {
1337 for operation in ops_array {
1338 self.apply_patch_operation(&mut patched_data, operation)?;
1339 }
1340 }
1341 }
1342
1343 let patched_resource = Resource::from_json(resource_type.to_string(), patched_data)
1345 .map_err(|e| InMemoryError::InvalidInput {
1346 message: format!("Failed to deserialize patched resource: {}", e),
1347 })?;
1348
1349 let patched_json =
1351 patched_resource
1352 .to_json()
1353 .map_err(|e| InMemoryError::InvalidInput {
1354 message: format!("Failed to serialize patched resource: {}", e),
1355 })?;
1356
1357 self.storage
1358 .put(key, patched_json)
1359 .await
1360 .map_err(|_| InMemoryError::Internal {
1361 message: "Failed to store patched resource".to_string(),
1362 })?;
1363
1364 Ok(ConditionalResult::Success(VersionedResource::new(
1365 patched_resource,
1366 )))
1367 }
1368 Ok(None) => Err(InMemoryError::NotFound {
1369 resource_type: resource_type.to_string(),
1370 id: id.to_string(),
1371 }),
1372 Err(_) => Err(InMemoryError::Internal {
1373 message: "Failed to retrieve resource for conditional patch".to_string(),
1374 }),
1375 }
1376 }
1377
1378 pub async fn get_versioned_resource(
1379 &self,
1380 resource_type: &str,
1381 id: &str,
1382 context: &RequestContext,
1383 ) -> Result<Option<VersionedResource>, InMemoryError> {
1384 match self.get_resource(resource_type, id, context).await? {
1385 Some(resource) => Ok(Some(VersionedResource::new(resource))),
1386 None => Ok(None),
1387 }
1388 }
1389
1390 pub async fn create_versioned_resource(
1391 &self,
1392 resource_type: &str,
1393 data: Value,
1394 context: &RequestContext,
1395 ) -> Result<VersionedResource, InMemoryError> {
1396 let resource = self.create_resource(resource_type, data, context).await?;
1397 Ok(VersionedResource::new(resource))
1398 }
1399}