From 4e125b50434c79a707fb61075612f65430c3edf8 Mon Sep 17 00:00:00 2001 From: Merlijn <32853531+ToxicMushroom@users.noreply.github.com> Date: Thu, 10 Oct 2024 02:13:45 +0200 Subject: [PATCH] Scim add EntryReference (#3079) Allow references to be displayed as a complex object --- proto/src/scim_v1/server.rs | 11 +++ server/core/src/actors/v1_scim.rs | 2 +- server/lib/src/entry.rs | 90 +++++++++++++++++------ server/lib/src/server/mod.rs | 63 ++++++++++++++++ server/lib/src/valueset/address.rs | 14 ++-- server/lib/src/valueset/apppwd.rs | 7 +- server/lib/src/valueset/auditlogstring.rs | 7 +- server/lib/src/valueset/binary.rs | 9 ++- server/lib/src/valueset/bool.rs | 6 +- server/lib/src/valueset/certificate.rs | 7 +- server/lib/src/valueset/cid.rs | 8 +- server/lib/src/valueset/cred.rs | 25 ++++--- server/lib/src/valueset/datetime.rs | 9 ++- server/lib/src/valueset/eckey.rs | 7 +- server/lib/src/valueset/hexstring.rs | 7 +- server/lib/src/valueset/image/mod.rs | 18 ++--- server/lib/src/valueset/iname.rs | 7 +- server/lib/src/valueset/index.rs | 11 +-- server/lib/src/valueset/iutf8.rs | 7 +- server/lib/src/valueset/json.rs | 13 ++-- server/lib/src/valueset/jws.rs | 12 +-- server/lib/src/valueset/key_internal.rs | 7 +- server/lib/src/valueset/mod.rs | 63 +++++++++++++++- server/lib/src/valueset/nsuniqueid.rs | 7 +- server/lib/src/valueset/oauth.rs | 17 +++-- server/lib/src/valueset/restricted.rs | 7 +- server/lib/src/valueset/secret.rs | 11 ++- server/lib/src/valueset/session.rs | 20 ++--- server/lib/src/valueset/spn.rs | 8 +- server/lib/src/valueset/ssh.rs | 8 +- server/lib/src/valueset/syntax.rs | 12 +-- server/lib/src/valueset/totp.rs | 10 +-- server/lib/src/valueset/uihint.rs | 8 +- server/lib/src/valueset/uint32.rs | 8 +- server/lib/src/valueset/url.rs | 8 +- server/lib/src/valueset/utf8.rs | 8 +- server/lib/src/valueset/uuid.rs | 30 +++++--- 37 files changed, 386 insertions(+), 186 deletions(-) diff --git a/proto/src/scim_v1/server.rs b/proto/src/scim_v1/server.rs index 87571d8f8..0034e6ef6 100644 --- a/proto/src/scim_v1/server.rs +++ b/proto/src/scim_v1/server.rs @@ -180,6 +180,14 @@ pub struct ScimOAuth2ClaimMap { pub values: BTreeSet, } +#[serde_as] +#[derive(Serialize, Debug, Clone, PartialEq, Eq, ToSchema)] +#[serde(rename_all = "camelCase")] +pub struct ScimReference { + pub uuid: Uuid, + pub value: String, +} + /// This is a strongly typed ScimValue for Kanidm. It is for serialisation only /// since on a deserialisation path we can not know the intent of the sender /// to how we deserialise strings. Additionally during deserialisation we need @@ -196,6 +204,9 @@ pub enum ScimValueKanidm { DateTime(#[serde_as(as = "Rfc3339")] OffsetDateTime), Reference(Url), Uuid(Uuid), + EntryReference(ScimReference), + EntryReferences(Vec), + // Other strong outbound types. ArrayString(Vec), ArrayDateTime(#[serde_as(as = "Vec")] Vec), diff --git a/server/core/src/actors/v1_scim.rs b/server/core/src/actors/v1_scim.rs index cadb41e29..d03e19ed3 100644 --- a/server/core/src/actors/v1_scim.rs +++ b/server/core/src/actors/v1_scim.rs @@ -227,6 +227,6 @@ impl QueryServerReadV1 { idms_prox_read .qs_read .impersonate_search_ext_uuid(target_uuid, &ident) - .and_then(|entry| entry.to_scim_kanidm()) + .and_then(|entry| entry.to_scim_kanidm(idms_prox_read.qs_read)) } } diff --git a/server/lib/src/entry.rs b/server/lib/src/entry.rs index b9b614d33..3161d97df 100644 --- a/server/lib/src/entry.rs +++ b/server/lib/src/entry.rs @@ -29,23 +29,6 @@ pub use std::collections::BTreeSet as Set; use std::collections::{BTreeMap as Map, BTreeMap, BTreeSet}; use std::sync::Arc; -use compact_jwt::JwsEs256Signer; -use hashbrown::{HashMap, HashSet}; -use kanidm_proto::internal::ImageValue; -use kanidm_proto::internal::{ - ConsistencyError, Filter as ProtoFilter, OperationError, SchemaError, UiHint, -}; -use kanidm_proto::v1::Entry as ProtoEntry; -use ldap3_proto::simple::{LdapPartialAttribute, LdapSearchResultEntry}; -use openssl::ec::EcKey; -use openssl::pkey::{Private, Public}; -use time::OffsetDateTime; -use tracing::trace; -use uuid::Uuid; -use webauthn_rs::prelude::{ - AttestationCaList, AttestedPasskey as AttestedPasskeyV4, Passkey as PasskeyV4, -}; - use crate::be::dbentry::{DbEntry, DbEntryVers}; use crate::be::dbvalue::DbValueSetV2; use crate::be::{IdxKey, IdxSlope}; @@ -58,13 +41,30 @@ use crate::prelude::*; use crate::repl::cid::Cid; use crate::repl::entry::EntryChangeState; use crate::repl::proto::{ReplEntryV1, ReplIncrementalEntryV1}; +use compact_jwt::JwsEs256Signer; +use hashbrown::{HashMap, HashSet}; +use kanidm_proto::internal::ImageValue; +use kanidm_proto::internal::{ + ConsistencyError, Filter as ProtoFilter, OperationError, SchemaError, UiHint, +}; +use kanidm_proto::scim_v1::server::ScimReference; +use kanidm_proto::v1::Entry as ProtoEntry; +use ldap3_proto::simple::{LdapPartialAttribute, LdapSearchResultEntry}; +use openssl::ec::EcKey; +use openssl::pkey::{Private, Public}; +use time::OffsetDateTime; +use tracing::trace; +use uuid::Uuid; +use webauthn_rs::prelude::{ + AttestationCaList, AttestedPasskey as AttestedPasskeyV4, Passkey as PasskeyV4, +}; use crate::schema::{SchemaAttribute, SchemaClass, SchemaTransaction}; use crate::value::{ ApiToken, CredentialType, IndexType, IntentTokenState, Oauth2Session, PartialValue, Session, SyntaxType, Value, }; -use crate::valueset::{self, ValueSet}; +use crate::valueset::{self, ScimResolveStatus, ScimValueIntermediate, ValueSet}; pub type EntryInitNew = Entry; pub type EntryInvalidNew = Entry; @@ -2230,15 +2230,32 @@ impl Entry { Ok(ProtoEntry { attrs: attrs? }) } - pub fn to_scim_kanidm(&self) -> Result { - let attrs = self + pub fn to_scim_kanidm( + &self, + mut read_txn: QueryServerReadTransaction, + ) -> Result { + let result: Result, OperationError> = self .attrs .iter() // We want to skip some attributes as they are already in the header. .filter(|(k, _vs)| **k != Attribute::Uuid) - .filter_map(|(k, vs)| vs.to_scim_value().map(|scim_value| (k.clone(), scim_value))) + .filter_map(|(k, vs)| { + let opt_resolve_status = vs.to_scim_value(); + let res_opt_scim_value = match opt_resolve_status { + None => Ok(None), + Some(ScimResolveStatus::Resolved(scim_value_kani)) => Ok(Some(scim_value_kani)), + Some(ScimResolveStatus::NeedsResolution(scim_value_interim)) => { + resolve_scim_interim(scim_value_interim, &mut read_txn) + } + }; + res_opt_scim_value + .transpose() + .map(|scim_res| scim_res.map(|scim_value| (k.clone(), scim_value))) + }) .collect(); + let attrs = result?; + let id = self.get_uuid(); // Not sure how I want to handle this yet, I think we need some schema changes @@ -2353,6 +2370,37 @@ impl Entry { } } +fn resolve_scim_interim( + scim_value_intermediate: ScimValueIntermediate, + read_txn: &mut QueryServerReadTransaction, +) -> Result, OperationError> { + match scim_value_intermediate { + ScimValueIntermediate::Refer(uuid) => { + if let Some(option) = read_txn.uuid_to_spn(uuid)? { + Ok(Some(ScimValueKanidm::EntryReference(ScimReference { + uuid, + value: option.to_proto_string_clone(), + }))) + } else { + // TODO: didn't have spn, fallback to uuid.to_string ? + Ok(None) + } + } + ScimValueIntermediate::ReferMany(uuids) => { + let mut scim_references = vec![]; + for uuid in uuids { + if let Some(option) = read_txn.uuid_to_spn(uuid)? { + scim_references.push(ScimReference { + uuid, + value: option.to_proto_string_clone(), + }) + } + } + Ok(Some(ScimValueKanidm::EntryReferences(scim_references))) + } + } +} + // impl Entry { impl Entry { /// This internally adds an AVA to the entry. If the entry was newly added, then true is returned. diff --git a/server/lib/src/server/mod.rs b/server/lib/src/server/mod.rs index 1f17996ef..513b86e5e 100644 --- a/server/lib/src/server/mod.rs +++ b/server/lib/src/server/mod.rs @@ -2268,6 +2268,7 @@ impl<'a> QueryServerWriteTransaction<'a> { #[cfg(test)] mod tests { use crate::prelude::*; + use kanidm_proto::scim_v1::server::ScimReference; #[qs_test] async fn test_name_to_uuid(server: &QueryServer) { @@ -2613,4 +2614,66 @@ mod tests { server_txn.commit().expect("should not fail"); // Commit. } + + #[qs_test] + async fn test_scim_entry_structure(server: &QueryServer) { + let mut read_txn = server.read().await.unwrap(); + + // Query entry (A buitin one ?) + let entry = read_txn + .internal_search_uuid(UUID_IDM_PEOPLE_SELF_NAME_WRITE) + .unwrap(); + + // Convert entry into scim + let reduced = entry.as_ref().clone().into_reduced(); + let scim_entry = reduced.to_scim_kanidm(read_txn).unwrap(); + + // Assert scim entry attributes are as expected + assert_eq!(scim_entry.header.id, UUID_IDM_PEOPLE_SELF_NAME_WRITE); + let name_scim = scim_entry.attrs.get(&Attribute::Name).unwrap(); + match name_scim { + ScimValueKanidm::String(name) => { + assert_eq!(name.clone(), "idm_people_self_name_write") + } + _ => { + panic!("expected String, actual {:?}", name_scim); + } + } + + // such as returning a new struct type for `members` attributes or `managed_by` + let entry_managed_by_scim = scim_entry.attrs.get(&Attribute::EntryManagedBy).unwrap(); + match entry_managed_by_scim { + ScimValueKanidm::EntryReferences(managed_by) => { + assert_eq!( + managed_by.first().unwrap().clone(), + ScimReference { + uuid: UUID_IDM_ADMINS, + value: "idm_admins@example.com".to_string() + } + ) + } + _ => { + panic!( + "expected EntryReference, actual {:?}", + entry_managed_by_scim + ); + } + } + + let members_scim = scim_entry.attrs.get(&Attribute::Member).unwrap(); + match members_scim { + ScimValueKanidm::EntryReferences(members) => { + assert_eq!( + members.first().unwrap().clone(), + ScimReference { + uuid: UUID_IDM_ALL_PERSONS, + value: "idm_all_persons@example.com".to_string() + } + ) + } + _ => { + panic!("expected EntryReferences, actual {:?}", members_scim); + } + } + } } diff --git a/server/lib/src/valueset/address.rs b/server/lib/src/valueset/address.rs index 625c869b0..c183cfa78 100644 --- a/server/lib/src/valueset/address.rs +++ b/server/lib/src/valueset/address.rs @@ -7,7 +7,7 @@ use crate::prelude::*; use crate::schema::SchemaAttribute; use crate::utils::trigraph_iter; use crate::value::{Address, VALIDATE_EMAIL_RE}; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; use kanidm_proto::scim_v1::server::{ScimAddress, ScimMail}; @@ -137,8 +137,8 @@ impl ValueSetT for ValueSetAddress { Box::new(self.set.iter().map(|a| a.formatted.clone())) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.set .iter() .map(|a| ScimAddress { @@ -150,7 +150,7 @@ impl ValueSetT for ValueSetAddress { country: a.country.clone(), }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -427,8 +427,8 @@ impl ValueSetT for ValueSetEmailAddress { } } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.set .iter() .map(|mail| { @@ -439,7 +439,7 @@ impl ValueSetT for ValueSetEmailAddress { } }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/apppwd.rs b/server/lib/src/valueset/apppwd.rs index e33e6cc72..ec0f77797 100644 --- a/server/lib/src/valueset/apppwd.rs +++ b/server/lib/src/valueset/apppwd.rs @@ -2,6 +2,7 @@ use crate::be::dbvalue::{DbValueApplicationPassword, DbValueSetV2}; use crate::credential::{apppwd::ApplicationPassword, Password}; use crate::prelude::*; use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; use std::collections::BTreeMap; use kanidm_proto::scim_v1::server::ScimApplicationPassword; @@ -183,8 +184,8 @@ impl ValueSetT for ValueSetApplicationPassword { })) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .values() .flatten() @@ -194,7 +195,7 @@ impl ValueSetT for ValueSetApplicationPassword { label: app_pwd.label.clone(), }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/auditlogstring.rs b/server/lib/src/valueset/auditlogstring.rs index d5d053f54..5795a23ee 100644 --- a/server/lib/src/valueset/auditlogstring.rs +++ b/server/lib/src/valueset/auditlogstring.rs @@ -1,6 +1,7 @@ use crate::prelude::*; use crate::repl::cid::Cid; use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; use kanidm_proto::scim_v1::server::ScimAuditString; use std::collections::BTreeMap; @@ -109,8 +110,8 @@ impl ValueSetT for ValueSetAuditLogString { Box::new(self.map.iter().map(|(d, s)| format!("{d}-{s}"))) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(cid, strdata)| { @@ -121,7 +122,7 @@ impl ValueSetT for ValueSetAuditLogString { } }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/binary.rs b/server/lib/src/valueset/binary.rs index cacd996fb..1aa4daa40 100644 --- a/server/lib/src/valueset/binary.rs +++ b/server/lib/src/valueset/binary.rs @@ -1,3 +1,4 @@ +use crate::valueset::ScimResolveStatus; use base64urlsafedata::Base64UrlSafeData; use std::collections::btree_map::Entry as BTreeEntry; use std::collections::BTreeMap; @@ -107,7 +108,7 @@ impl ValueSetT for ValueSetPrivateBinary { Box::new(self.set.iter().map(|_| "private_binary".to_string())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { None } @@ -277,8 +278,8 @@ impl ValueSetT for ValueSetPublicBinary { Box::new(self.map.keys().cloned()) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(tag, bin)| ScimBinary { @@ -286,7 +287,7 @@ impl ValueSetT for ValueSetPublicBinary { value: bin.clone(), }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/bool.rs b/server/lib/src/valueset/bool.rs index 8ff1b2dd2..e39c73940 100644 --- a/server/lib/src/valueset/bool.rs +++ b/server/lib/src/valueset/bool.rs @@ -1,8 +1,8 @@ -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetBool { @@ -105,7 +105,7 @@ impl ValueSetT for ValueSetBool { Box::new(self.set.iter().map(|b| b.to_string())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { if self.len() == 1 { // Because self.len == 1 we know this has to yield a value. let b = self.set.iter().copied().next().unwrap_or_default(); diff --git a/server/lib/src/valueset/certificate.rs b/server/lib/src/valueset/certificate.rs index 543b4f9bc..e246cb62d 100644 --- a/server/lib/src/valueset/certificate.rs +++ b/server/lib/src/valueset/certificate.rs @@ -1,6 +1,7 @@ use crate::be::dbvalue::DbValueCertificate; use crate::prelude::*; use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; use kanidm_proto::scim_v1::server::ScimCertificate; use std::collections::BTreeMap; @@ -191,7 +192,7 @@ impl ValueSetT for ValueSetCertificate { })) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let vals: Vec = self .map .iter() @@ -216,7 +217,7 @@ impl ValueSetT for ValueSetCertificate { if vals.is_empty() { None } else { - Some(ScimValueKanidm::from(vals)) + Some(ScimValueKanidm::from(vals).into()) } } @@ -300,7 +301,7 @@ raBy6edj7W0EIH+yQxkDEwIhAI0nVKaI6duHLAvtKW6CfEQFG6jKg7dyk37YYiRD let vs: ValueSet = ValueSetCertificate::new(Box::new(cert)).unwrap(); - let scim_value = vs.to_scim_value().unwrap(); + let scim_value = vs.to_scim_value().unwrap().assume_resolved(); let cert = match scim_value { ScimValueKanidm::ArrayCertificate(mut set) => set.pop().unwrap(), diff --git a/server/lib/src/valueset/cid.rs b/server/lib/src/valueset/cid.rs index 3e2db9f3d..105dccabe 100644 --- a/server/lib/src/valueset/cid.rs +++ b/server/lib/src/valueset/cid.rs @@ -1,10 +1,10 @@ -use smolset::SmolSet; - use crate::be::dbvalue::DbCidV1; use crate::prelude::*; use crate::repl::cid::Cid; use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; + +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetCid { @@ -115,7 +115,7 @@ impl ValueSetT for ValueSetCid { Box::new(self.set.iter().map(|c| c.to_string())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().map(|cid| cid.to_string()); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/cred.rs b/server/lib/src/valueset/cred.rs index ffdb7f92b..6947ce97e 100644 --- a/server/lib/src/valueset/cred.rs +++ b/server/lib/src/valueset/cred.rs @@ -1,3 +1,4 @@ +use crate::valueset::ScimResolveStatus; use smolset::SmolSet; use std::collections::btree_map::Entry as BTreeEntry; use std::collections::BTreeMap; @@ -141,7 +142,7 @@ impl ValueSetT for ValueSetCredential { Box::new(self.map.keys().cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { // Currently I think we don't need to yield cred info as that's part of the // cred update session instead. None @@ -366,8 +367,8 @@ impl ValueSetT for ValueSetIntentToken { Box::new(self.map.keys().cloned()) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(token_id, intent_token_state)| { @@ -392,7 +393,7 @@ impl ValueSetT for ValueSetIntentToken { } }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -609,7 +610,7 @@ impl ValueSetT for ValueSetPasskey { Box::new(self.map.values().map(|(t, _)| t).cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { None } @@ -782,7 +783,7 @@ impl ValueSetT for ValueSetAttestedPasskey { Box::new(self.map.values().map(|(t, _)| t).cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { None } @@ -947,10 +948,10 @@ impl ValueSetT for ValueSetCredentialType { Box::new(self.set.iter().map(|ct| ct.to_string())) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.set.iter().map(|ct| ct.to_string()).collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -1089,15 +1090,15 @@ impl ValueSetT for ValueSetWebauthnAttestationCaList { } } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.ca_list .cas() .values() .flat_map(|att_ca| att_ca.aaguids().values()) .map(|device| device.description_en().to_string()) .collect::>(), - )) + ))) } fn to_partialvalue_iter(&self) -> Box + '_> { diff --git a/server/lib/src/valueset/datetime.rs b/server/lib/src/valueset/datetime.rs index 0108ff973..bfb4a6fe8 100644 --- a/server/lib/src/valueset/datetime.rs +++ b/server/lib/src/valueset/datetime.rs @@ -1,10 +1,11 @@ -use smolset::SmolSet; -use time::OffsetDateTime; - use crate::prelude::*; use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; +use smolset::SmolSet; +use time::OffsetDateTime; + #[derive(Debug, Clone)] pub struct ValueSetDateTime { set: SmolSet<[OffsetDateTime; 1]>, @@ -123,7 +124,7 @@ impl ValueSetT for ValueSetDateTime { })) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().copied(); if self.len() == 1 { let v = iter.next().unwrap_or(OffsetDateTime::UNIX_EPOCH); diff --git a/server/lib/src/valueset/eckey.rs b/server/lib/src/valueset/eckey.rs index 2e1e5a288..9c66062a7 100644 --- a/server/lib/src/valueset/eckey.rs +++ b/server/lib/src/valueset/eckey.rs @@ -1,13 +1,14 @@ +use crate::valueset::ScimResolveStatus; use std::iter::{self}; +use super::ValueSet; use crate::be::dbvalue::DbValueSetV2; use crate::prelude::*; use crate::value::{PartialValue, SyntaxType, Value}; + use openssl::ec::EcKey; use openssl::pkey::{Private, Public}; -use super::ValueSet; - #[derive(Debug, Clone)] struct EcKeyPrivate { priv_key: EcKey, @@ -128,7 +129,7 @@ impl ValueSetT for ValueSetEcKeyPrivate { Box::new(iter::once(String::from("hidden"))) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { None } diff --git a/server/lib/src/valueset/hexstring.rs b/server/lib/src/valueset/hexstring.rs index ac299b34c..5ee930a60 100644 --- a/server/lib/src/valueset/hexstring.rs +++ b/server/lib/src/valueset/hexstring.rs @@ -1,9 +1,10 @@ -use std::collections::BTreeSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; +use std::collections::BTreeSet; + #[derive(Debug, Clone)] pub struct ValueSetHexString { set: BTreeSet, @@ -127,7 +128,7 @@ impl ValueSetT for ValueSetHexString { Box::new(self.set.iter().cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().cloned(); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/image/mod.rs b/server/lib/src/valueset/image/mod.rs index dbe744463..819d63683 100644 --- a/server/lib/src/valueset/image/mod.rs +++ b/server/lib/src/valueset/image/mod.rs @@ -1,16 +1,16 @@ #![allow(dead_code)] +use crate::valueset::ScimResolveStatus; use std::fmt::Display; -use hashbrown::HashSet; -use image::codecs::gif::GifDecoder; -use image::codecs::webp::WebPDecoder; -use image::ImageDecoder; -use kanidm_proto::internal::{ImageType, ImageValue}; - use crate::be::dbvalue::DbValueImage; use crate::prelude::*; use crate::schema::SchemaAttribute; use crate::valueset::{DbValueSetV2, ValueSet}; +use hashbrown::HashSet; +use image::codecs::gif::GifDecoder; +use image::codecs::webp::WebPDecoder; +use image::ImageDecoder; +use kanidm_proto::internal::{ImageType, ImageValue}; #[derive(Debug, Clone)] pub struct ValueSetImage { @@ -388,7 +388,7 @@ impl ValueSetT for ValueSetImage { Box::new(self.set.iter().map(|image| image.hash_imagevalue())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { // TODO: This should be a reference to the image URL, not the image itself! // Does this mean we need to pass in the domain / origin so we can render // these URL's correctly? @@ -398,12 +398,12 @@ impl ValueSetT for ValueSetImage { // // TODO: Scim supports a "type" field here, but do we care? - Some(ScimValueKanidm::from( + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.set .iter() .map(|image| image.hash_imagevalue()) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/iname.rs b/server/lib/src/valueset/iname.rs index 0a273f65d..c723dd1ae 100644 --- a/server/lib/src/valueset/iname.rs +++ b/server/lib/src/valueset/iname.rs @@ -1,10 +1,11 @@ -use std::collections::BTreeSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; use crate::utils::trigraph_iter; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; +use std::collections::BTreeSet; + #[derive(Debug, Clone)] pub struct ValueSetIname { set: BTreeSet, @@ -138,7 +139,7 @@ impl ValueSetT for ValueSetIname { Box::new(self.set.iter().cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().cloned(); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/index.rs b/server/lib/src/valueset/index.rs index c488c35ed..c5be256f9 100644 --- a/server/lib/src/valueset/index.rs +++ b/server/lib/src/valueset/index.rs @@ -1,9 +1,10 @@ -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; +use smolset::SmolSet; + #[derive(Debug, Clone)] pub struct ValueSetIndex { set: SmolSet<[IndexType; 3]>, @@ -105,10 +106,10 @@ impl ValueSetT for ValueSetIndex { Box::new(self.set.iter().map(|b| b.to_string())) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.set.iter().map(|u| u.to_string()).collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/iutf8.rs b/server/lib/src/valueset/iutf8.rs index 2023025fd..fa69a1dc3 100644 --- a/server/lib/src/valueset/iutf8.rs +++ b/server/lib/src/valueset/iutf8.rs @@ -1,11 +1,12 @@ -use std::collections::BTreeSet; - use super::iname::ValueSetIname; use crate::prelude::*; use crate::schema::SchemaAttribute; use crate::utils::trigraph_iter; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; +use std::collections::BTreeSet; + #[derive(Debug, Clone)] pub struct ValueSetIutf8 { set: BTreeSet, @@ -136,7 +137,7 @@ impl ValueSetT for ValueSetIutf8 { Box::new(self.set.iter().cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().cloned(); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/json.rs b/server/lib/src/valueset/json.rs index b4c8762e0..e6d87b154 100644 --- a/server/lib/src/valueset/json.rs +++ b/server/lib/src/valueset/json.rs @@ -1,9 +1,10 @@ -use kanidm_proto::internal::Filter as ProtoFilter; -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; +use kanidm_proto::internal::Filter as ProtoFilter; + +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetJsonFilter { @@ -121,8 +122,8 @@ impl ValueSetT for ValueSetJsonFilter { })) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.set .iter() .filter_map(|s| { @@ -133,7 +134,7 @@ impl ValueSetT for ValueSetJsonFilter { .ok() }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/jws.rs b/server/lib/src/valueset/jws.rs index 5bfa253fc..09aed782e 100644 --- a/server/lib/src/valueset/jws.rs +++ b/server/lib/src/valueset/jws.rs @@ -1,11 +1,11 @@ +use crate::prelude::*; +use crate::schema::SchemaAttribute; +use crate::valueset::ScimResolveStatus; +use crate::valueset::{DbValueSetV2, ValueSet}; use base64urlsafedata::Base64UrlSafeData; use compact_jwt::{crypto::JwsRs256Signer, JwsEs256Signer, JwsSigner}; use hashbrown::HashSet; -use crate::prelude::*; -use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; - #[derive(Debug, Clone)] pub struct ValueSetJwsKeyEs256 { set: HashSet, @@ -129,7 +129,7 @@ impl ValueSetT for ValueSetJwsKeyEs256 { Box::new(self.set.iter().map(|k| k.get_kid().to_string())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { None } @@ -294,7 +294,7 @@ impl ValueSetT for ValueSetJwsKeyRs256 { Box::new(self.set.iter().map(|k| k.get_kid().to_string())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { None } diff --git a/server/lib/src/valueset/key_internal.rs b/server/lib/src/valueset/key_internal.rs index 6209647de..f1d2f324a 100644 --- a/server/lib/src/valueset/key_internal.rs +++ b/server/lib/src/valueset/key_internal.rs @@ -1,4 +1,5 @@ use crate::prelude::*; +use crate::valueset::ScimResolveStatus; use crate::server::keys::KeyId; use crate::value::{KeyStatus, KeyUsage}; @@ -282,8 +283,8 @@ impl ValueSetT for ValueSetKeyInternal { })) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(kid, key_object)| { @@ -298,7 +299,7 @@ impl ValueSetT for ValueSetKeyInternal { } }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/mod.rs b/server/lib/src/valueset/mod.rs index d93c5a1e5..fff7ee9ba 100644 --- a/server/lib/src/valueset/mod.rs +++ b/server/lib/src/valueset/mod.rs @@ -8,6 +8,8 @@ use kanidm_proto::internal::ImageValue; use openssl::ec::EcKey; use openssl::pkey::Private; use openssl::pkey::Public; +use serde::Serialize; +use serde_with::serde_as; use smolset::SmolSet; use sshkey_attest::proto::PublicKey as SshPublicKey; use time::OffsetDateTime; @@ -15,8 +17,6 @@ use webauthn_rs::prelude::AttestationCaList; use webauthn_rs::prelude::AttestedPasskey as AttestedPasskeyV4; use webauthn_rs::prelude::Passkey as PasskeyV4; -use kanidm_proto::internal::{Filter as ProtoFilter, UiHint}; - use crate::be::dbvalue::DbValueSetV2; use crate::credential::{apppwd::ApplicationPassword, totp::Totp, Credential}; use crate::prelude::*; @@ -24,6 +24,7 @@ use crate::repl::cid::Cid; use crate::schema::SchemaAttribute; use crate::server::keys::KeyId; use crate::value::{Address, ApiToken, CredentialType, IntentTokenState, Oauth2Session, Session}; +use kanidm_proto::internal::{Filter as ProtoFilter, UiHint}; pub use self::address::{ValueSetAddress, ValueSetEmailAddress}; use self::apppwd::ValueSetApplicationPassword; @@ -144,7 +145,7 @@ pub trait ValueSetT: std::fmt::Debug + DynClone { fn to_proto_string_clone_iter(&self) -> Box + '_>; - fn to_scim_value(&self) -> Option; + fn to_scim_value(&self) -> Option; fn to_db_valueset_v2(&self) -> DbValueSetV2; @@ -666,6 +667,46 @@ impl PartialEq for ValueSet { } } +#[serde_as] +#[derive(Serialize, Debug, Clone, PartialEq, Eq)] +pub enum ScimValueIntermediate { + Refer(Uuid), + ReferMany(Vec), +} + +pub enum ScimResolveStatus { + Resolved(ScimValueKanidm), + NeedsResolution(ScimValueIntermediate), +} + +impl From for ScimResolveStatus +where + T: Into, +{ + fn from(v: T) -> Self { + Self::Resolved(v.into()) + } +} + +#[cfg(test)] +impl ScimResolveStatus { + pub fn assume_resolved(self) -> ScimValueKanidm { + match self { + ScimResolveStatus::Resolved(v) => v, + ScimResolveStatus::NeedsResolution(_) => { + panic!("assume_resolved called on NeedsResolution") + } + } + } + + pub fn assume_unresolved(self) -> ScimValueIntermediate { + match self { + ScimResolveStatus::Resolved(_) => panic!("assume_unresolved called on Resolved"), + ScimResolveStatus::NeedsResolution(svi) => svi, + } + } +} + pub fn uuid_to_proto_string(u: Uuid) -> String { u.as_hyphenated().to_string() } @@ -877,7 +918,21 @@ pub fn from_db_valueset_v2(dbvs: DbValueSetV2) -> Result, @@ -106,7 +107,7 @@ impl ValueSetT for ValueSetNsUniqueId { Box::new(self.set.iter().cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().cloned(); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/oauth.rs b/server/lib/src/valueset/oauth.rs index 64e2d8991..1f7da6752 100644 --- a/server/lib/src/valueset/oauth.rs +++ b/server/lib/src/valueset/oauth.rs @@ -1,3 +1,4 @@ +use crate::valueset::ScimResolveStatus; use std::collections::btree_map::Entry as BTreeEntry; use std::collections::{BTreeMap, BTreeSet}; @@ -112,8 +113,8 @@ impl ValueSetT for ValueSetOauthScope { Box::new(self.set.iter().cloned()) } - fn to_scim_value(&self) -> Option { - Some(str_join(&self.set).into()) + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(str_join(&self.set).into())) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -289,8 +290,8 @@ impl ValueSetT for ValueSetOauthScopeMap { ) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(uuid, scopes)| { @@ -301,7 +302,7 @@ impl ValueSetT for ValueSetOauthScopeMap { } }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -620,8 +621,8 @@ impl ValueSetT for ValueSetOauthClaimMap { })) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .flat_map(|(claim_name, mappings)| { @@ -636,7 +637,7 @@ impl ValueSetT for ValueSetOauthClaimMap { }) }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/restricted.rs b/server/lib/src/valueset/restricted.rs index ca09df029..29ddb9747 100644 --- a/server/lib/src/valueset/restricted.rs +++ b/server/lib/src/valueset/restricted.rs @@ -1,10 +1,11 @@ -use std::collections::BTreeSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; use crate::utils::trigraph_iter; +use crate::valueset::ScimResolveStatus; use crate::valueset::{DbValueSetV2, ValueSet}; +use std::collections::BTreeSet; + #[derive(Debug, Clone)] pub struct ValueSetRestricted { set: BTreeSet, @@ -138,7 +139,7 @@ impl ValueSetT for ValueSetRestricted { Box::new(self.set.iter().cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().cloned(); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/secret.rs b/server/lib/src/valueset/secret.rs index fb6f8b254..3f5f19366 100644 --- a/server/lib/src/valueset/secret.rs +++ b/server/lib/src/valueset/secret.rs @@ -1,8 +1,8 @@ -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; + +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetSecret { @@ -96,7 +96,7 @@ impl ValueSetT for ValueSetSecret { Box::new(self.set.iter().map(|_| "hidden".to_string())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { None } @@ -145,8 +145,7 @@ impl ValueSetT for ValueSetSecret { #[cfg(test)] mod tests { - use super::ValueSetSecret; - use crate::prelude::ValueSet; + use crate::valueset::{ValueSet, ValueSetSecret}; #[test] fn test_scim_secret() { diff --git a/server/lib/src/valueset/session.rs b/server/lib/src/valueset/session.rs index 8aeb85ec6..f98693968 100644 --- a/server/lib/src/valueset/session.rs +++ b/server/lib/src/valueset/session.rs @@ -13,7 +13,7 @@ use crate::schema::SchemaAttribute; use crate::value::{ ApiToken, ApiTokenScope, AuthType, Oauth2Session, Session, SessionScope, SessionState, }; -use crate::valueset::{uuid_to_proto_string, DbValueSetV2, ValueSet}; +use crate::valueset::{uuid_to_proto_string, DbValueSetV2, ScimResolveStatus, ValueSet}; use kanidm_proto::scim_v1::server::ScimApiToken; use kanidm_proto::scim_v1::server::ScimAuthSession; @@ -354,8 +354,8 @@ impl ValueSetT for ValueSetSession { ) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(session_id, session)| { @@ -381,7 +381,7 @@ impl ValueSetT for ValueSetSession { } }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -855,8 +855,8 @@ impl ValueSetT for ValueSetOauth2Session { ) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(session_id, session)| { @@ -879,7 +879,7 @@ impl ValueSetT for ValueSetOauth2Session { } }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -1196,8 +1196,8 @@ impl ValueSetT for ValueSetApiToken { ) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(token_id, token)| ScimApiToken { @@ -1209,7 +1209,7 @@ impl ValueSetT for ValueSetApiToken { scope: token.scope.to_string(), }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/spn.rs b/server/lib/src/valueset/spn.rs index 67a2f4b90..2bdea78a5 100644 --- a/server/lib/src/valueset/spn.rs +++ b/server/lib/src/valueset/spn.rs @@ -1,8 +1,8 @@ -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; + +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetSpn { @@ -110,7 +110,7 @@ impl ValueSetT for ValueSetSpn { Box::new(self.set.iter().map(|(n, d)| format!("{n}@{d}"))) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().map(|(n, d)| format!("{n}@{d}")); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/ssh.rs b/server/lib/src/valueset/ssh.rs index 8e4afc938..15ab13185 100644 --- a/server/lib/src/valueset/ssh.rs +++ b/server/lib/src/valueset/ssh.rs @@ -5,7 +5,7 @@ use crate::be::dbvalue::DbValueTaggedStringV1; use crate::prelude::*; use crate::schema::SchemaAttribute; use crate::utils::trigraph_iter; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; use sshkey_attest::proto::PublicKey as SshPublicKey; @@ -137,8 +137,8 @@ impl ValueSetT for ValueSetSshKey { Box::new(self.map.iter().map(|(tag, pk)| format!("{}: {}", tag, pk))) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.map .iter() .map(|(label, value)| ScimSshPublicKey { @@ -146,7 +146,7 @@ impl ValueSetT for ValueSetSshKey { value: value.clone(), }) .collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/syntax.rs b/server/lib/src/valueset/syntax.rs index 4e9f93eb9..248b40cf8 100644 --- a/server/lib/src/valueset/syntax.rs +++ b/server/lib/src/valueset/syntax.rs @@ -1,8 +1,8 @@ -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; + +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetSyntax { @@ -105,10 +105,10 @@ impl ValueSetT for ValueSetSyntax { Box::new(self.set.iter().map(|b| b.to_string())) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.set.iter().map(|u| u.to_string()).collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/totp.rs b/server/lib/src/valueset/totp.rs index b1a1ee529..26a2d15a7 100644 --- a/server/lib/src/valueset/totp.rs +++ b/server/lib/src/valueset/totp.rs @@ -1,12 +1,12 @@ -use std::collections::btree_map::Entry as BTreeEntry; -use std::collections::BTreeMap; - use crate::credential::totp::Totp; use crate::prelude::*; +use std::collections::btree_map::Entry as BTreeEntry; +use std::collections::BTreeMap; + use crate::be::dbvalue::DbTotpV1; use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; #[derive(Debug, Clone)] pub struct ValueSetTotpSecret { @@ -117,7 +117,7 @@ impl ValueSetT for ValueSetTotpSecret { Box::new(self.map.keys().cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { None } diff --git a/server/lib/src/valueset/uihint.rs b/server/lib/src/valueset/uihint.rs index 4b63e3459..119df288b 100644 --- a/server/lib/src/valueset/uihint.rs +++ b/server/lib/src/valueset/uihint.rs @@ -2,7 +2,7 @@ use std::collections::BTreeSet; use crate::prelude::*; use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; use kanidm_proto::internal::UiHint; @@ -94,10 +94,10 @@ impl ValueSetT for ValueSetUiHint { Box::new(self.set.iter().map(|u| u.to_string())) } - fn to_scim_value(&self) -> Option { - Some(ScimValueKanidm::from( + fn to_scim_value(&self) -> Option { + Some(ScimResolveStatus::Resolved(ScimValueKanidm::from( self.set.iter().map(|u| u.to_string()).collect::>(), - )) + ))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { diff --git a/server/lib/src/valueset/uint32.rs b/server/lib/src/valueset/uint32.rs index e042d7f73..2a53fd445 100644 --- a/server/lib/src/valueset/uint32.rs +++ b/server/lib/src/valueset/uint32.rs @@ -1,8 +1,8 @@ -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; + +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetUint32 { @@ -108,7 +108,7 @@ impl ValueSetT for ValueSetUint32 { Box::new(self.set.iter().map(|b| b.to_string())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { if self.len() == 1 { // Because self.len == 1 we know this has to yield a value. let b = self.set.iter().copied().next().unwrap_or_default(); diff --git a/server/lib/src/valueset/url.rs b/server/lib/src/valueset/url.rs index b58e32a15..20d8c7f33 100644 --- a/server/lib/src/valueset/url.rs +++ b/server/lib/src/valueset/url.rs @@ -1,8 +1,8 @@ -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; + +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetUrl { @@ -102,7 +102,7 @@ impl ValueSetT for ValueSetUrl { Box::new(self.set.iter().map(|i| i.to_string())) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().map(|url| url.to_string()); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/utf8.rs b/server/lib/src/valueset/utf8.rs index 33ae52df9..e59f6aafd 100644 --- a/server/lib/src/valueset/utf8.rs +++ b/server/lib/src/valueset/utf8.rs @@ -1,9 +1,9 @@ -use std::collections::BTreeSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; use crate::utils::trigraph_iter; -use crate::valueset::{DbValueSetV2, ValueSet}; +use crate::valueset::{DbValueSetV2, ScimResolveStatus, ValueSet}; + +use std::collections::BTreeSet; #[derive(Debug, Clone)] pub struct ValueSetUtf8 { @@ -140,7 +140,7 @@ impl ValueSetT for ValueSetUtf8 { Box::new(self.set.iter().cloned()) } - fn to_scim_value(&self) -> Option { + fn to_scim_value(&self) -> Option { let mut iter = self.set.iter().cloned(); if self.len() == 1 { let v = iter.next().unwrap_or_default(); diff --git a/server/lib/src/valueset/uuid.rs b/server/lib/src/valueset/uuid.rs index b16fb3b11..d277ba734 100644 --- a/server/lib/src/valueset/uuid.rs +++ b/server/lib/src/valueset/uuid.rs @@ -1,10 +1,11 @@ use std::collections::BTreeSet; -use smolset::SmolSet; - use crate::prelude::*; use crate::schema::SchemaAttribute; -use crate::valueset::{uuid_to_proto_string, DbValueSetV2, ValueSet}; +use crate::valueset::{ + uuid_to_proto_string, DbValueSetV2, ScimResolveStatus, ScimValueIntermediate, ValueSet, +}; +use smolset::SmolSet; #[derive(Debug, Clone)] pub struct ValueSetUuid { @@ -113,8 +114,12 @@ impl ValueSetT for ValueSetUuid { Box::new(self.set.iter().copied().map(uuid_to_proto_string)) } - fn to_scim_value(&self) -> Option { - self.set.iter().next().copied().map(ScimValueKanidm::Uuid) + fn to_scim_value(&self) -> Option { + self.set + .iter() + .next() + .copied() + .map(|uuid| ScimResolveStatus::NeedsResolution(ScimValueIntermediate::Refer(uuid))) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -282,8 +287,11 @@ impl ValueSetT for ValueSetRefer { Box::new(self.set.iter().copied().map(uuid_to_proto_string)) } - fn to_scim_value(&self) -> Option { - Some(self.set.iter().copied().collect::>().into()) + fn to_scim_value(&self) -> Option { + let uuids = self.set.iter().copied().collect::>(); + Some(ScimResolveStatus::NeedsResolution( + ScimValueIntermediate::ReferMany(uuids), + )) } fn to_db_valueset_v2(&self) -> DbValueSetV2 { @@ -346,17 +354,17 @@ mod tests { fn test_scim_uuid() { let vs: ValueSet = ValueSetUuid::new(uuid::uuid!("4d21d04a-dc0e-42eb-b850-34dd180b107f")); - let data = r#""4d21d04a-dc0e-42eb-b850-34dd180b107f""#; + let data = r#"{"Refer": "4d21d04a-dc0e-42eb-b850-34dd180b107f"}"#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive_unresolved(vs, data); } #[test] fn test_scim_refer() { let vs: ValueSet = ValueSetRefer::new(uuid::uuid!("4d21d04a-dc0e-42eb-b850-34dd180b107f")); - let data = r#"["4d21d04a-dc0e-42eb-b850-34dd180b107f"]"#; + let data = r#"{"ReferMany": ["4d21d04a-dc0e-42eb-b850-34dd180b107f"]}"#; - crate::valueset::scim_json_reflexive(vs, data); + crate::valueset::scim_json_reflexive_unresolved(vs, data); } }