20241012 attr name SCIM fix (#3102)

* Fix handling of attribute to ensure that it is consistently Attribute in scim sync
This commit is contained in:
Firstyear 2024-10-14 08:00:03 +10:00 committed by GitHub
parent 4e125b5043
commit 1cccebd382
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -540,7 +540,15 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
&mut self, &mut self,
sse: &'b ScimSyncUpdateEvent, sse: &'b ScimSyncUpdateEvent,
changes: &'b ScimSyncRequest, changes: &'b ScimSyncRequest,
) -> Result<(Uuid, BTreeSet<String>, BTreeMap<Uuid, &'b ScimEntry>, bool), OperationError> { ) -> Result<
(
Uuid,
BTreeSet<Attribute>,
BTreeMap<Uuid, &'b ScimEntry>,
bool,
),
OperationError,
> {
// Assert the token is valid. // Assert the token is valid.
let sync_uuid = match &sse.ident.origin { let sync_uuid = match &sse.ident.origin {
IdentType::User(_) | IdentType::Internal => { IdentType::User(_) | IdentType::Internal => {
@ -607,9 +615,9 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
let sync_refresh = matches!(&changes.from_state, ScimSyncState::Refresh); let sync_refresh = matches!(&changes.from_state, ScimSyncState::Refresh);
// Get the sync authority set from the entry. // Get the sync authority set from the entry.
let sync_authority_set = sync_entry let sync_authority_set: BTreeSet<Attribute> = sync_entry
.get_ava_as_iutf8(Attribute::SyncYieldAuthority) .get_ava_as_iutf8(Attribute::SyncYieldAuthority)
.cloned() .map(|set| set.iter().map(|s| Attribute::from(s.as_str())).collect())
.unwrap_or_default(); .unwrap_or_default();
// Transform the changes into something that supports lookups. // Transform the changes into something that supports lookups.
@ -1114,8 +1122,8 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
scim_ent: &ScimEntry, scim_ent: &ScimEntry,
sync_uuid: Uuid, sync_uuid: Uuid,
sync_allow_class_set: &BTreeMap<String, SchemaClass>, sync_allow_class_set: &BTreeMap<String, SchemaClass>,
sync_allow_attr_set: &BTreeSet<String>, sync_allow_attr_set: &BTreeSet<Attribute>,
phantom_attr_set: &BTreeSet<String>, phantom_attr_set: &BTreeSet<Attribute>,
) -> Result<ModifyList<ModifyInvalid>, OperationError> { ) -> Result<ModifyList<ModifyInvalid>, OperationError> {
// What classes did they request for this entry to sync? // What classes did they request for this entry to sync?
let requested_classes = scim_ent.schemas.iter() let requested_classes = scim_ent.schemas.iter()
@ -1179,7 +1187,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
// //
// - either we nominate phantom attrs on the classes they can import with // - either we nominate phantom attrs on the classes they can import with
// or we need to always allow them? // or we need to always allow them?
let sync_owned_attrs: BTreeSet<_> = requested_classes let sync_owned_attrs: BTreeSet<Attribute> = requested_classes
.values() .values()
.flat_map(|cls| { .flat_map(|cls| {
cls.systemmay cls.systemmay
@ -1188,26 +1196,28 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
.chain(cls.systemmust.iter()) .chain(cls.systemmust.iter())
.chain(cls.must.iter()) .chain(cls.must.iter())
}) })
.map(|s| s.as_str())
// Finally, establish if the attribute is syncable. Technically this could probe some attrs // Finally, establish if the attribute is syncable. Technically this could probe some attrs
// multiple times due to how the loop is established, but in reality there are few attr overlaps. // multiple times due to how the loop is established, but in reality there are few attr overlaps.
.filter(|a| sync_allow_attr_set.contains(*a)) .filter(|a| sync_allow_attr_set.contains(*a))
// Add in the set of phantom syncable attrs. // Add in the set of phantom syncable attrs.
.chain(phantom_attr_set.iter().map(|s| s.as_str())) .chain(phantom_attr_set.iter())
.cloned()
.collect(); .collect();
debug!(?sync_owned_attrs); debug!(?sync_owned_attrs);
for attr in sync_owned_attrs.iter().copied() { for attr in sync_owned_attrs.iter() {
if !phantom_attr_set.contains(attr) { if !phantom_attr_set.contains(attr) {
// These are the attrs that are "real" and need to be cleaned out first. // These are the attrs that are "real" and need to be cleaned out first.
mods.push(Modify::Purged(attr.into())); mods.push(Modify::Purged(attr.clone()));
} }
} }
// For each attr in the scim entry, see if it's in the sync_owned set. If so, proceed. // For each attr in the scim entry, see if it's in the sync_owned set. If so, proceed.
for (scim_attr_name, scim_attr) in scim_ent.attrs.iter() { for (scim_attr_name, scim_attr) in scim_ent.attrs.iter() {
if !sync_owned_attrs.contains(scim_attr_name.as_str()) { let scim_attr_name = Attribute::from(scim_attr_name.as_str());
if !sync_owned_attrs.contains(&scim_attr_name) {
error!( error!(
"Rejecting attribute {} for entry {} which is not sync owned", "Rejecting attribute {} for entry {} which is not sync owned",
scim_attr_name, scim_ent.id scim_attr_name, scim_ent.id
@ -1215,9 +1225,6 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
return Err(OperationError::InvalidEntryState); return Err(OperationError::InvalidEntryState);
} }
// Make it a native attribute name.
let scim_attr_name = Attribute::from(scim_attr_name.as_str());
// Convert each scim_attr to a set of values. // Convert each scim_attr to a set of values.
let values = self let values = self
.scim_attr_to_values(&scim_attr_name, scim_attr) .scim_attr_to_values(&scim_attr_name, scim_attr)
@ -1245,7 +1252,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
&mut self, &mut self,
change_entries: &BTreeMap<Uuid, &ScimEntry>, change_entries: &BTreeMap<Uuid, &ScimEntry>,
sync_uuid: Uuid, sync_uuid: Uuid,
sync_authority_set: &BTreeSet<String>, sync_authority_set: &BTreeSet<Attribute>,
) -> Result<(), OperationError> { ) -> Result<(), OperationError> {
if change_entries.is_empty() { if change_entries.is_empty() {
info!("No change_entries requested"); info!("No change_entries requested");
@ -1279,23 +1286,23 @@ impl<'a> IdmServerProxyWriteTransaction<'a> {
}) })
.collect(); .collect();
let sync_allow_attr_set: BTreeSet<String> = attr_snapshot let sync_allow_attr_set: BTreeSet<Attribute> = attr_snapshot
.values() .values()
// Only add attrs to this if they are both sync allowed AND authority granted. // Only add attrs to this if they are both sync allowed AND authority granted.
.filter_map(|attr| { .filter_map(|attr| {
if attr.sync_allowed && !sync_authority_set.contains(attr.name.as_str()) { if attr.sync_allowed && !sync_authority_set.contains(&attr.name) {
Some(attr.name.to_string()) Some(attr.name.clone())
} else { } else {
None None
} }
}) })
.collect(); .collect();
let phantom_attr_set: BTreeSet<String> = attr_snapshot let phantom_attr_set: BTreeSet<Attribute> = attr_snapshot
.values() .values()
.filter_map(|attr| { .filter_map(|attr| {
if attr.phantom && attr.sync_allowed { if attr.phantom && attr.sync_allowed {
Some(attr.name.to_string()) Some(attr.name.clone())
} else { } else {
None None
} }