Scim add EntryReference (#3079)

Allow references to be displayed as a complex object
This commit is contained in:
Merlijn 2024-10-10 02:13:45 +02:00 committed by GitHub
parent 12236a39f5
commit 4e125b5043
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
37 changed files with 386 additions and 186 deletions

View file

@ -180,6 +180,14 @@ pub struct ScimOAuth2ClaimMap {
pub values: BTreeSet<String>,
}
#[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<ScimReference>),
// Other strong outbound types.
ArrayString(Vec<String>),
ArrayDateTime(#[serde_as(as = "Vec<Rfc3339>")] Vec<OffsetDateTime>),

View file

@ -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))
}
}

View file

@ -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<EntryInit, EntryNew>;
pub type EntryInvalidNew = Entry<EntryInvalid, EntryNew>;
@ -2230,15 +2230,32 @@ impl Entry<EntryReduced, EntryCommitted> {
Ok(ProtoEntry { attrs: attrs? })
}
pub fn to_scim_kanidm(&self) -> Result<ScimEntryKanidm, OperationError> {
let attrs = self
pub fn to_scim_kanidm(
&self,
mut read_txn: QueryServerReadTransaction,
) -> Result<ScimEntryKanidm, OperationError> {
let result: Result<BTreeMap<Attribute, ScimValueKanidm>, 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<EntryReduced, EntryCommitted> {
}
}
fn resolve_scim_interim(
scim_value_intermediate: ScimValueIntermediate,
read_txn: &mut QueryServerReadTransaction,
) -> Result<Option<ScimValueKanidm>, 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<STATE> Entry<EntryValid, STATE> {
impl<VALID, STATE> Entry<VALID, STATE> {
/// This internally adds an AVA to the entry. If the entry was newly added, then true is returned.

View file

@ -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);
}
}
}
}

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.set
.iter()
.map(|a| ScimAddress {
@ -150,7 +150,7 @@ impl ValueSetT for ValueSetAddress {
country: a.country.clone(),
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
@ -427,8 +427,8 @@ impl ValueSetT for ValueSetEmailAddress {
}
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.set
.iter()
.map(|mail| {
@ -439,7 +439,7 @@ impl ValueSetT for ValueSetEmailAddress {
}
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.values()
.flatten()
@ -194,7 +195,7 @@ impl ValueSetT for ValueSetApplicationPassword {
label: app_pwd.label.clone(),
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(cid, strdata)| {
@ -121,7 +122,7 @@ impl ValueSetT for ValueSetAuditLogString {
}
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
None
}
@ -277,8 +278,8 @@ impl ValueSetT for ValueSetPublicBinary {
Box::new(self.map.keys().cloned())
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(tag, bin)| ScimBinary {
@ -286,7 +287,7 @@ impl ValueSetT for ValueSetPublicBinary {
value: bin.clone(),
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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();

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let vals: Vec<ScimCertificate> = 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(),

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().map(|cid| cid.to_string());
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
// 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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(token_id, intent_token_state)| {
@ -392,7 +393,7 @@ impl ValueSetT for ValueSetIntentToken {
}
})
.collect::<Vec<_>>(),
))
)))
}
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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
None
}
@ -782,7 +783,7 @@ impl ValueSetT for ValueSetAttestedPasskey {
Box::new(self.map.values().map(|(t, _)| t).cloned())
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.set.iter().map(|ct| ct.to_string()).collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
@ -1089,15 +1090,15 @@ impl ValueSetT for ValueSetWebauthnAttestationCaList {
}
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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::<Vec<_>>(),
))
)))
}
fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().copied();
if self.len() == 1 {
let v = iter.next().unwrap_or(OffsetDateTime::UNIX_EPOCH);

View file

@ -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<Private>,
@ -128,7 +129,7 @@ impl ValueSetT for ValueSetEcKeyPrivate {
Box::new(iter::once(String::from("hidden")))
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
None
}

View file

@ -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<String>,
@ -127,7 +128,7 @@ impl ValueSetT for ValueSetHexString {
Box::new(self.set.iter().cloned())
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().cloned();
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
// 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::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<String>,
@ -138,7 +139,7 @@ impl ValueSetT for ValueSetIname {
Box::new(self.set.iter().cloned())
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().cloned();
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.set.iter().map(|u| u.to_string()).collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<String>,
@ -136,7 +137,7 @@ impl ValueSetT for ValueSetIutf8 {
Box::new(self.set.iter().cloned())
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().cloned();
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.set
.iter()
.filter_map(|s| {
@ -133,7 +134,7 @@ impl ValueSetT for ValueSetJsonFilter {
.ok()
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<JwsEs256Signer>,
@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
None
}

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(kid, key_object)| {
@ -298,7 +299,7 @@ impl ValueSetT for ValueSetKeyInternal {
}
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<dyn Iterator<Item = String> + '_>;
fn to_scim_value(&self) -> Option<ScimValueKanidm>;
fn to_scim_value(&self) -> Option<ScimResolveStatus>;
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<Uuid>),
}
pub enum ScimResolveStatus {
Resolved(ScimValueKanidm),
NeedsResolution(ScimValueIntermediate),
}
impl<T> From<T> for ScimResolveStatus
where
T: Into<ScimValueKanidm>,
{
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<ValueSet, OperationErro
#[cfg(test)]
pub(crate) fn scim_json_reflexive(vs: ValueSet, data: &str) {
let scim_value = vs.to_scim_value().unwrap();
let scim_value = vs.to_scim_value().unwrap().assume_resolved();
let strout = serde_json::to_string_pretty(&scim_value).unwrap();
eprintln!("{}", strout);
let json_value: serde_json::Value = serde_json::to_value(&scim_value).unwrap();
let expect: serde_json::Value = serde_json::from_str(data).unwrap();
assert_eq!(json_value, expect);
}
#[cfg(test)]
pub(crate) fn scim_json_reflexive_unresolved(vs: ValueSet, data: &str) {
let scim_value = vs.to_scim_value().unwrap().assume_unresolved();
let strout = serde_json::to_string_pretty(&scim_value).unwrap();
eprintln!("{}", strout);

View file

@ -1,10 +1,11 @@
use smolset::SmolSet;
use crate::prelude::*;
use crate::schema::SchemaAttribute;
use crate::value::NSUNIQUEID_RE;
use crate::valueset::ScimResolveStatus;
use crate::valueset::{DbValueSetV2, ValueSet};
use smolset::SmolSet;
#[derive(Debug, Clone)]
pub struct ValueSetNsUniqueId {
set: SmolSet<[String; 1]>,
@ -106,7 +107,7 @@ impl ValueSetT for ValueSetNsUniqueId {
Box::new(self.set.iter().cloned())
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().cloned();
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
Some(str_join(&self.set).into())
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(uuid, scopes)| {
@ -301,7 +302,7 @@ impl ValueSetT for ValueSetOauthScopeMap {
}
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
@ -620,8 +621,8 @@ impl ValueSetT for ValueSetOauthClaimMap {
}))
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.flat_map(|(claim_name, mappings)| {
@ -636,7 +637,7 @@ impl ValueSetT for ValueSetOauthClaimMap {
})
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<String>,
@ -138,7 +139,7 @@ impl ValueSetT for ValueSetRestricted {
Box::new(self.set.iter().cloned())
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().cloned();
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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() {

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(session_id, session)| {
@ -381,7 +381,7 @@ impl ValueSetT for ValueSetSession {
}
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
@ -855,8 +855,8 @@ impl ValueSetT for ValueSetOauth2Session {
)
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(session_id, session)| {
@ -879,7 +879,7 @@ impl ValueSetT for ValueSetOauth2Session {
}
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {
@ -1196,8 +1196,8 @@ impl ValueSetT for ValueSetApiToken {
)
}
fn to_scim_value(&self) -> Option<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().map(|(n, d)| format!("{n}@{d}"));
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.map
.iter()
.map(|(label, value)| ScimSshPublicKey {
@ -146,7 +146,7 @@ impl ValueSetT for ValueSetSshKey {
value: value.clone(),
})
.collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.set.iter().map(|u| u.to_string()).collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
None
}

View file

@ -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<ScimValueKanidm> {
Some(ScimValueKanidm::from(
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
self.set.iter().map(|u| u.to_string()).collect::<Vec<_>>(),
))
)))
}
fn to_db_valueset_v2(&self) -> DbValueSetV2 {

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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();

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().map(|url| url.to_string());
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let mut iter = self.set.iter().cloned();
if self.len() == 1 {
let v = iter.next().unwrap_or_default();

View file

@ -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<ScimValueKanidm> {
self.set.iter().next().copied().map(ScimValueKanidm::Uuid)
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
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<ScimValueKanidm> {
Some(self.set.iter().copied().collect::<Vec<_>>().into())
fn to_scim_value(&self) -> Option<ScimResolveStatus> {
let uuids = self.set.iter().copied().collect::<Vec<_>>();
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);
}
}