From d5ed335b529d6eaff52c90a995342a32a7cb3dde Mon Sep 17 00:00:00 2001 From: James Hodgkinson Date: Sat, 16 Sep 2023 12:11:06 +1000 Subject: [PATCH] Cinco de yakko (#2108) * there are always more yaks * see? ldap yaks. * fixing stupid radius container build thing --- Cargo.lock | 1 + proto/src/constants.rs | 5 +- proto/src/scim_v1.rs | 6 +- proto/src/v1.rs | 4 +- rlm_python/Dockerfile | 4 +- server/core/src/actors/v1_read.rs | 15 +- server/core/src/actors/v1_write.rs | 34 +- server/core/src/https/oauth2.rs | 12 +- server/lib/src/be/dbentry.rs | 2 +- server/lib/src/be/idl_arc_sqlite.rs | 2 +- server/lib/src/be/idl_sqlite.rs | 7 +- server/lib/src/be/idxkey.rs | 3 +- server/lib/src/be/mod.rs | 60 +- server/lib/src/constants/acp.rs | 21 +- server/lib/src/constants/entries.rs | 28 +- server/lib/src/entry.rs | 544 +++++++++++------- server/lib/src/event.rs | 2 +- server/lib/src/filter.rs | 57 +- server/lib/src/idm/account.rs | 123 ++-- server/lib/src/idm/applinks.rs | 10 +- server/lib/src/idm/credupdatesession.rs | 18 +- server/lib/src/idm/group.rs | 20 +- server/lib/src/idm/identityverification.rs | 17 +- server/lib/src/idm/ldap.rs | 114 ++-- server/lib/src/idm/oauth2.rs | 56 +- server/lib/src/idm/radius.rs | 27 +- server/lib/src/idm/reauth.rs | 2 +- server/lib/src/idm/scim.rs | 248 ++++---- server/lib/src/idm/server.rs | 40 +- server/lib/src/idm/serviceaccount.rs | 85 +-- server/lib/src/idm/unix.rs | 171 +++--- server/lib/src/macros.rs | 1 + server/lib/src/modify.rs | 60 +- server/lib/src/plugins/attrunique.rs | 34 +- server/lib/src/plugins/base.rs | 22 +- server/lib/src/plugins/cred_import.rs | 21 +- server/lib/src/plugins/domain.rs | 32 +- server/lib/src/plugins/dyngroup.rs | 53 +- server/lib/src/plugins/eckeygen.rs | 31 +- server/lib/src/plugins/gidnumber.rs | 30 +- server/lib/src/plugins/jwskeygen.rs | 32 +- server/lib/src/plugins/memberof.rs | 72 +-- server/lib/src/plugins/namehistory.rs | 34 +- server/lib/src/plugins/protected.rs | 135 +++-- server/lib/src/plugins/refint.rs | 57 +- server/lib/src/plugins/session.rs | 48 +- server/lib/src/plugins/spn.rs | 38 +- server/lib/src/repl/consumer.rs | 14 +- server/lib/src/repl/entry-changelog.rs | 67 ++- server/lib/src/repl/entry.rs | 4 +- server/lib/src/repl/proto.rs | 2 +- server/lib/src/repl/supplier.rs | 4 +- server/lib/src/repl/tests.rs | 165 +++--- server/lib/src/schema.rs | 90 +-- server/lib/src/server/access/create.rs | 4 +- server/lib/src/server/access/delete.rs | 2 +- server/lib/src/server/access/mod.rs | 57 +- server/lib/src/server/access/modify.rs | 12 +- server/lib/src/server/access/profiles.rs | 77 ++- server/lib/src/server/access/search.rs | 23 +- server/lib/src/server/batch_modify.rs | 33 +- server/lib/src/server/create.rs | 27 +- server/lib/src/server/delete.rs | 25 +- server/lib/src/server/identity.rs | 10 +- server/lib/src/server/migrations.rs | 44 +- server/lib/src/server/mod.rs | 35 +- server/lib/src/server/modify.rs | 55 +- server/lib/src/server/recycle.rs | 9 +- server/testkit/Cargo.toml | 5 +- server/testkit/src/lib.rs | 68 ++- server/testkit/tests/default_entries.rs | 306 ++++++---- .../tests/identity_verification_tests.rs | 11 +- server/testkit/tests/oauth2_test.rs | 33 +- server/testkit/tests/proto_v1_test.rs | 92 ++- server/testkit/tests/unix.rs | 8 +- .../web_ui/src/components/admin_accounts.rs | 1 - tools/cli/src/cli/person.rs | 17 +- tools/cli/src/cli/serviceaccount.rs | 13 +- tools/iam_migrations/freeipa/src/main.rs | 57 +- tools/iam_migrations/ldap/src/main.rs | 11 +- unix_integration/tests/cache_layer_test.rs | 12 +- 81 files changed, 2057 insertions(+), 1774 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7ac3535d1..4610e88fb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3108,6 +3108,7 @@ dependencies = [ "kanidm_proto", "kanidmd_core", "kanidmd_lib", + "lazy_static", "oauth2", "openssl", "reqwest", diff --git a/proto/src/constants.rs b/proto/src/constants.rs index 49768f243..da0981f06 100644 --- a/proto/src/constants.rs +++ b/proto/src/constants.rs @@ -64,6 +64,7 @@ pub const ATTR_GROUP: &str = "group"; pub const ATTR_ID_VERIFICATION_ECKEY: &str = "id_verification_eckey"; pub const ATTR_INDEX: &str = "index"; pub const ATTR_IPANTHASH: &str = "ipanthash"; +pub const ATTR_IPASSHPUBKEY: &str = "ipasshpubkey"; pub const ATTR_JWS_ES256_PRIVATE_KEY: &str = "jws_es256_private_key"; pub const ATTR_LAST_MODIFIED_CID: &str = "last_modified_cid"; pub const ATTR_LEGALNAME: &str = "legalname"; @@ -111,8 +112,8 @@ pub const ATTR_SELF: &str = "self"; pub const ATTR_SOURCE_UUID: &str = "source_uuid"; pub const ATTR_SPN: &str = "spn"; pub const ATTR_SUPPLEMENTS: &str = "supplements"; -pub const ATTR_LDAP_SSH_PUBLICKEY: &str = "ssh_publickey"; -pub const ATTR_SSHPUBLICKEY: &str = "sshpublickey"; +pub const ATTR_LDAP_SSHPUBLICKEY: &str = "sshpublickey"; +pub const ATTR_SSH_PUBLICKEY: &str = "ssh_publickey"; pub const ATTR_SYNC_ALLOWED: &str = "sync_allowed"; pub const ATTR_SYNC_CLASS: &str = "sync_class"; pub const ATTR_SYNC_COOKIE: &str = "sync_cookie"; diff --git a/proto/src/scim_v1.rs b/proto/src/scim_v1.rs index e96767fdc..8f25fdbc7 100644 --- a/proto/src/scim_v1.rs +++ b/proto/src/scim_v1.rs @@ -8,8 +8,8 @@ pub use scim_proto::user::MultiValueAttr; use scim_proto::*; use crate::constants::{ - ATTR_DESCRIPTION, ATTR_DISPLAYNAME, ATTR_GIDNUMBER, ATTR_LDAP_SSH_PUBLICKEY, ATTR_LOGINSHELL, - ATTR_MAIL, ATTR_MEMBER, ATTR_NAME, + ATTR_DESCRIPTION, ATTR_DISPLAYNAME, ATTR_GIDNUMBER, ATTR_LOGINSHELL, ATTR_MAIL, ATTR_MEMBER, + ATTR_NAME, ATTR_SSH_PUBLICKEY, }; #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] @@ -183,7 +183,7 @@ impl Into for ScimSyncPerson { set_multi_complex!(attrs, "totp_import", totp_import); set_option_string!(attrs, ATTR_LOGINSHELL, login_shell); set_multi_complex!(attrs, ATTR_MAIL, mail); - set_multi_complex!(attrs, ATTR_LDAP_SSH_PUBLICKEY, ssh_publickey); + set_multi_complex!(attrs, ATTR_SSH_PUBLICKEY, ssh_publickey); // with the underscore ScimEntry { schemas, diff --git a/proto/src/v1.rs b/proto/src/v1.rs index 4cf6afb9f..c48a464f3 100644 --- a/proto/src/v1.rs +++ b/proto/src/v1.rs @@ -15,7 +15,7 @@ use webauthn_rs_proto::{ RequestChallengeResponse, }; -use crate::constants::{ATTR_GROUP, ATTR_LDAP_SSH_PUBLICKEY}; +use crate::constants::{ATTR_GROUP, ATTR_LDAP_SSHPUBLICKEY}; // These proto implementations are here because they have public definitions @@ -613,7 +613,7 @@ impl fmt::Display for UnixUserToken { } self.sshkeys .iter() - .try_for_each(|s| writeln!(f, "{}: {}", ATTR_LDAP_SSH_PUBLICKEY, s))?; + .try_for_each(|s| writeln!(f, "{}: {}", ATTR_LDAP_SSHPUBLICKEY, s))?; self.groups .iter() .try_for_each(|g| writeln!(f, "{}: {}", ATTR_GROUP, g)) diff --git a/rlm_python/Dockerfile b/rlm_python/Dockerfile index ca564a41c..3a620205a 100644 --- a/rlm_python/Dockerfile +++ b/rlm_python/Dockerfile @@ -3,7 +3,7 @@ ARG BASE_IMAGE=opensuse/tumbleweed:latest FROM ${BASE_IMAGE} ADD ../scripts/zypper_fixing.sh /zypper_fixing.sh -RUN --mount=type=cache,id=zypp,target=/var/cache/zypp /zypper_fixing.sh +RUN /zypper_fixing.sh # ====================== # FROM repos @@ -11,7 +11,7 @@ ARG RADIUS_USER=radiusd EXPOSE 1812 1813 ENV KANIDM_CONFIG_FILE="/data/kanidm" -RUN --mount=type=cache,id=zypp,target=/var/cache/zypp zypper install -y \ +RUN zypper install -y \ freeradius-client \ freeradius-server \ freeradius-server-python3 \ diff --git a/server/core/src/actors/v1_read.rs b/server/core/src/actors/v1_read.rs index 6dd574eff..663cd98c2 100644 --- a/server/core/src/actors/v1_read.rs +++ b/server/core/src/actors/v1_read.rs @@ -540,7 +540,7 @@ impl QueryServerReadV1 { // From the entry, turn it into the value .and_then(|entry| { entry - .get_ava_single(Attribute::RadiusSecret.as_ref()) + .get_ava_single(Attribute::RadiusSecret) .and_then(|v| v.get_secret_str().map(str::to_string)) }); Ok(r) @@ -744,7 +744,7 @@ impl QueryServerReadV1 { // get the first entry .and_then(|e| { // From the entry, turn it into the value - e.get_ava_iter_sshpubkeys(Attribute::SshPublicKey.into()) + e.get_ava_iter_sshpubkeys(Attribute::SshPublicKey) .map(|i| i.map(|s| s.to_string()).collect()) }) .unwrap_or_else(|| { @@ -807,11 +807,10 @@ impl QueryServerReadV1 { // get the first entry .map(|e| { // From the entry, turn it into the value - e.get_ava_set(Attribute::SshPublicKey.into()) - .and_then(|vs| { - // Get the one tagged value - vs.get_ssh_tag(&tag).map(str::to_string) - }) + e.get_ava_set(Attribute::SshPublicKey).and_then(|vs| { + // Get the one tagged value + vs.get_ssh_tag(&tag).map(str::to_string) + }) }) .unwrap_or_else(|| { // No matching entry? Return none. @@ -1274,7 +1273,7 @@ impl QueryServerReadV1 { // From the entry, turn it into the value .and_then(|entry| { entry - .get_ava_single(Attribute::OAuth2RsBasicSecret.into()) + .get_ava_single(Attribute::OAuth2RsBasicSecret) .and_then(|v| v.get_secret_str().map(str::to_string)) }); Ok(r) diff --git a/server/core/src/actors/v1_write.rs b/server/core/src/actors/v1_write.rs index 2e1d852b5..2bba12938 100644 --- a/server/core/src/actors/v1_write.rs +++ b/server/core/src/actors/v1_write.rs @@ -882,10 +882,11 @@ impl QueryServerWriteV1 { e })?; + let target_attr = Attribute::try_from(attr)?; let mdf = match ModifyEvent::from_target_uuid_attr_purge( ident, target_uuid, - &attr, + target_attr, filter, &idms_prox_write.qs_write, ) { @@ -1037,7 +1038,7 @@ impl QueryServerWriteV1 { ) -> Result<(), OperationError> { // Because this is from internal, we can generate a real modlist, rather // than relying on the proto ones. - let ml = ModifyList::new_append(ATTR_LDAP_SSH_PUBLICKEY, Value::new_sshkey(tag, key)); + let ml = ModifyList::new_append(Attribute::SshPublicKey, Value::new_sshkey(tag, key)); self.modify_from_internal_parts(uat, &uuid_or_name, &ml, filter) .await @@ -1066,16 +1067,18 @@ impl QueryServerWriteV1 { .chain(iter::once( gidnumber .as_ref() - .map(|_| Modify::Purged("gidnumber".into())), + .map(|_| Modify::Purged(Attribute::GidNumber.into())), )) .chain(iter::once(gidnumber.map(|n| { - Modify::Present("gidnumber".into(), Value::new_uint32(n)) + Modify::Present(Attribute::GidNumber.into(), Value::new_uint32(n)) }))) .chain(iter::once( - shell.as_ref().map(|_| Modify::Purged("loginshell".into())), + shell + .as_ref() + .map(|_| Modify::Purged(Attribute::LoginShell.into())), )) .chain(iter::once(shell.map(|s| { - Modify::Present("loginshell".into(), Value::new_iutf8(s.as_str())) + Modify::Present(Attribute::LoginShell.into(), Value::new_iutf8(s.as_str())) }))) .flatten() .collect(); @@ -1106,8 +1109,11 @@ impl QueryServerWriteV1 { let gidnumber_mods = if let Some(gid) = gx.gidnumber { [ - Some(Modify::Purged("gidnumber".into())), - Some(Modify::Present("gidnumber".into(), Value::new_uint32(gid))), + Some(Modify::Purged(Attribute::GidNumber.into())), + Some(Modify::Present( + Attribute::GidNumber.into(), + Value::new_uint32(gid), + )), ] } else { [None, None] @@ -1209,7 +1215,7 @@ impl QueryServerWriteV1 { })?; let ml = ModifyList::new_append( - "oauth2_rs_scope_map", + Attribute::OAuth2RsScopeMap, Value::new_oauthscopemap(group_uuid, scopes.into_iter().collect()).ok_or_else( || OperationError::InvalidAttribute("Invalid Oauth Scope Map syntax".to_string()), )?, @@ -1266,7 +1272,8 @@ impl QueryServerWriteV1 { e })?; - let ml = ModifyList::new_remove("oauth2_rs_scope_map", PartialValue::Refer(group_uuid)); + let ml = + ModifyList::new_remove(Attribute::OAuth2RsScopeMap, PartialValue::Refer(group_uuid)); let mdf = match ModifyEvent::from_internal_parts( ident, @@ -1323,7 +1330,7 @@ impl QueryServerWriteV1 { })?; let ml = ModifyList::new_append( - "oauth2_rs_sup_scope_map", + Attribute::OAuth2RsSupScopeMap, Value::new_oauthscopemap(group_uuid, scopes.into_iter().collect()).ok_or_else( || OperationError::InvalidAttribute("Invalid Oauth Scope Map syntax".to_string()), )?, @@ -1380,7 +1387,10 @@ impl QueryServerWriteV1 { e })?; - let ml = ModifyList::new_remove("oauth2_rs_sup_scope_map", PartialValue::Refer(group_uuid)); + let ml = ModifyList::new_remove( + Attribute::OAuth2RsSupScopeMap, + PartialValue::Refer(group_uuid), + ); let mdf = match ModifyEvent::from_internal_parts( ident, diff --git a/server/core/src/https/oauth2.rs b/server/core/src/https/oauth2.rs index fc8849a8d..3cead9177 100644 --- a/server/core/src/https/oauth2.rs +++ b/server/core/src/https/oauth2.rs @@ -84,9 +84,9 @@ pub async fn oauth2_basic_post( Json(obj): Json, ) -> impl IntoResponse { let classes = vec![ - "oauth2_resource_server".to_string(), - "oauth2_resource_server_basic".to_string(), - "object".to_string(), + EntryClass::OAuth2ResourceServer.to_string(), + EntryClass::OAuth2ResourceServerBasic.to_string(), + EntryClass::Object.to_string(), ]; json_rest_event_post(state, classes, obj, kopid).await } @@ -97,9 +97,9 @@ pub async fn oauth2_public_post( Json(obj): Json, ) -> impl IntoResponse { let classes = vec![ - "oauth2_resource_server".to_string(), - "oauth2_resource_server_public".to_string(), - "object".to_string(), + EntryClass::OAuth2ResourceServer.to_string(), + EntryClass::OAuth2ResourceServerPublic.to_string(), + EntryClass::Object.to_string(), ]; json_rest_event_post(state, classes, obj, kopid).await } diff --git a/server/lib/src/be/dbentry.rs b/server/lib/src/be/dbentry.rs index 5a2c04e2c..557f03f02 100644 --- a/server/lib/src/be/dbentry.rs +++ b/server/lib/src/be/dbentry.rs @@ -457,7 +457,7 @@ impl std::fmt::Display for DbEntry { write!(f, "{name:?}, ")?; } } - if let Some(names) = dbe_v1.attrs.get("classname") { + if let Some(names) = dbe_v1.attrs.get(Attribute::ClassName.as_ref()) { for name in names { write!(f, "{name:?}, ")?; } diff --git a/server/lib/src/be/idl_arc_sqlite.rs b/server/lib/src/be/idl_arc_sqlite.rs index e3a5c3ddb..94172d35a 100644 --- a/server/lib/src/be/idl_arc_sqlite.rs +++ b/server/lib/src/be/idl_arc_sqlite.rs @@ -1095,7 +1095,7 @@ impl<'a> IdlArcSqliteWriteTransaction<'a> { Ok(()) } - pub fn create_idx(&self, attr: &str, itype: IndexType) -> Result<(), OperationError> { + pub fn create_idx(&self, attr: Attribute, itype: IndexType) -> Result<(), OperationError> { // We don't need to affect this, so pass it down. self.db.create_idx(attr, itype) } diff --git a/server/lib/src/be/idl_sqlite.rs b/server/lib/src/be/idl_sqlite.rs index 431bca116..b12f55a2d 100644 --- a/server/lib/src/be/idl_sqlite.rs +++ b/server/lib/src/be/idl_sqlite.rs @@ -259,10 +259,7 @@ pub trait IdlSqliteTransaction { itype.as_idx_str(), attr ); - let mut stmt = self - .get_conn()? - .prepare(query.as_str()) - .map_err(sqlite_error)?; + let mut stmt = self.get_conn()?.prepare(&query).map_err(sqlite_error)?; let idl_raw: Option> = stmt .query_row(&[(":idx_key", &idx_key)], |row| row.get(0)) // We don't mind if it doesn't exist @@ -1082,7 +1079,7 @@ impl IdlSqliteWriteTransaction { } } - pub fn create_idx(&self, attr: &str, itype: IndexType) -> Result<(), OperationError> { + pub fn create_idx(&self, attr: Attribute, itype: IndexType) -> Result<(), OperationError> { // Is there a better way than formatting this? I can't seem // to template into the str. // diff --git a/server/lib/src/be/idxkey.rs b/server/lib/src/be/idxkey.rs index 3cdb4a9f0..f68835f5f 100644 --- a/server/lib/src/be/idxkey.rs +++ b/server/lib/src/be/idxkey.rs @@ -4,6 +4,7 @@ use std::hash::{Hash, Hasher}; use smartstring::alias::String as AttrString; +use crate::prelude::entries::Attribute; use crate::value::IndexType; pub type IdxSlope = u8; @@ -17,7 +18,7 @@ pub struct IdxKey { } impl IdxKey { - pub fn new(attr: &str, itype: IndexType) -> Self { + pub fn new(attr: Attribute, itype: IndexType) -> Self { IdxKey { attr: attr.into(), itype, diff --git a/server/lib/src/be/mod.rs b/server/lib/src/be/mod.rs index dc2da160d..00500bb99 100644 --- a/server/lib/src/be/mod.rs +++ b/server/lib/src/be/mod.rs @@ -1578,10 +1578,10 @@ impl<'a> BackendWriteTransaction<'a> { trace!("Creating index -> uuid2rdn"); self.idlayer.create_uuid2rdn()?; - self.idxmeta_wr - .idxkeys - .keys() - .try_for_each(|ikey| self.idlayer.create_idx(&ikey.attr, ikey.itype)) + self.idxmeta_wr.idxkeys.keys().try_for_each(|ikey| { + let attr: Attribute = (&ikey.attr).try_into()?; + self.idlayer.create_idx(attr, ikey.itype) + }) } pub fn upgrade_reindex(&mut self, v: i64) -> Result<(), OperationError> { @@ -2249,8 +2249,8 @@ mod tests { // Modify single assert!(be.modify(&CID_ZERO, &[pre1], &[vr1.clone()]).is_ok()); // Assert no other changes - assert!(entry_attr_pres!(be, vr1, Attribute::TestAttr.as_ref())); - assert!(!entry_attr_pres!(be, vr2, Attribute::TestAttr.as_ref())); + assert!(entry_attr_pres!(be, vr1, Attribute::TestAttr)); + assert!(!entry_attr_pres!(be, vr2, Attribute::TestAttr)); // Modify both assert!(be @@ -2261,8 +2261,8 @@ mod tests { ) .is_ok()); - assert!(entry_attr_pres!(be, vr1, Attribute::TestAttr.as_ref())); - assert!(entry_attr_pres!(be, vr2, Attribute::TestAttr.as_ref())); + assert!(entry_attr_pres!(be, vr1, Attribute::TestAttr)); + assert!(entry_attr_pres!(be, vr2, Attribute::TestAttr)); }); } @@ -2884,9 +2884,9 @@ mod tests { // add something. ce1.add_ava(Attribute::TestNumber, Value::from("test")); // remove something. - ce1.purge_ava(Attribute::TestAttr.as_ref()); + ce1.purge_ava(Attribute::TestAttr); // mod something. - ce1.purge_ava(Attribute::Name.as_ref()); + ce1.purge_ava(Attribute::Name); ce1.add_ava(Attribute::Name, Value::new_iname("claire")); let ce1 = ce1.into_sealed_committed(); @@ -2953,8 +2953,8 @@ mod tests { let rset: Vec<_> = rset.into_iter().map(Arc::new).collect(); // Now, alter the new entry. let mut ce1 = rset[0].as_ref().clone().into_invalid(); - ce1.purge_ava(Attribute::Name.as_ref()); - ce1.purge_ava(Attribute::Uuid.as_ref()); + ce1.purge_ava(Attribute::Name); + ce1.purge_ava(Attribute::Uuid); ce1.add_ava(Attribute::Name, Value::new_iname("claire")); ce1.add_ava( Attribute::Uuid, @@ -3357,36 +3357,30 @@ mod tests { assert!(!be.is_idx_slopeyness_generated().unwrap()); let ta_eq_slope = be - .get_idx_slope(&IdxKey::new( - Attribute::TestAttr.as_ref(), - IndexType::Equality, - )) + .get_idx_slope(&IdxKey::new(Attribute::TestAttr, IndexType::Equality)) .unwrap(); assert_eq!(ta_eq_slope, 45); let tb_eq_slope = be - .get_idx_slope(&IdxKey::new( - Attribute::TestNumber.as_ref(), - IndexType::Equality, - )) + .get_idx_slope(&IdxKey::new(Attribute::TestNumber, IndexType::Equality)) .unwrap(); assert_eq!(tb_eq_slope, 45); let name_eq_slope = be - .get_idx_slope(&IdxKey::new(Attribute::Name.as_ref(), IndexType::Equality)) + .get_idx_slope(&IdxKey::new(Attribute::Name, IndexType::Equality)) .unwrap(); assert_eq!(name_eq_slope, 1); let uuid_eq_slope = be - .get_idx_slope(&IdxKey::new(Attribute::Uuid.as_ref(), IndexType::Equality)) + .get_idx_slope(&IdxKey::new(Attribute::Uuid, IndexType::Equality)) .unwrap(); assert_eq!(uuid_eq_slope, 1); let name_pres_slope = be - .get_idx_slope(&IdxKey::new(Attribute::Name.as_ref(), IndexType::Presence)) + .get_idx_slope(&IdxKey::new(Attribute::Name, IndexType::Presence)) .unwrap(); assert_eq!(name_pres_slope, 90); let uuid_pres_slope = be - .get_idx_slope(&IdxKey::new(Attribute::Uuid.as_ref(), IndexType::Presence)) + .get_idx_slope(&IdxKey::new(Attribute::Uuid, IndexType::Presence)) .unwrap(); assert_eq!(uuid_pres_slope, 90); // Check the slopes are what we expect for hardcoded values. @@ -3397,36 +3391,30 @@ mod tests { assert!(be.is_idx_slopeyness_generated().unwrap()); let ta_eq_slope = be - .get_idx_slope(&IdxKey::new( - Attribute::TestAttr.as_ref(), - IndexType::Equality, - )) + .get_idx_slope(&IdxKey::new(Attribute::TestAttr, IndexType::Equality)) .unwrap(); assert_eq!(ta_eq_slope, 200); let tb_eq_slope = be - .get_idx_slope(&IdxKey::new( - Attribute::TestNumber.as_ref(), - IndexType::Equality, - )) + .get_idx_slope(&IdxKey::new(Attribute::TestNumber, IndexType::Equality)) .unwrap(); assert_eq!(tb_eq_slope, 133); let name_eq_slope = be - .get_idx_slope(&IdxKey::new(Attribute::Name.as_ref(), IndexType::Equality)) + .get_idx_slope(&IdxKey::new(Attribute::Name, IndexType::Equality)) .unwrap(); assert_eq!(name_eq_slope, 51); let uuid_eq_slope = be - .get_idx_slope(&IdxKey::new(Attribute::Uuid.as_ref(), IndexType::Equality)) + .get_idx_slope(&IdxKey::new(Attribute::Uuid, IndexType::Equality)) .unwrap(); assert_eq!(uuid_eq_slope, 51); let name_pres_slope = be - .get_idx_slope(&IdxKey::new(Attribute::Name.as_ref(), IndexType::Presence)) + .get_idx_slope(&IdxKey::new(Attribute::Name, IndexType::Presence)) .unwrap(); assert_eq!(name_pres_slope, 200); let uuid_pres_slope = be - .get_idx_slope(&IdxKey::new(Attribute::Uuid.as_ref(), IndexType::Presence)) + .get_idx_slope(&IdxKey::new(Attribute::Uuid, IndexType::Presence)) .unwrap(); assert_eq!(uuid_pres_slope, 200); }) diff --git a/server/lib/src/constants/acp.rs b/server/lib/src/constants/acp.rs index 1196ab88c..fa292e4cc 100644 --- a/server/lib/src/constants/acp.rs +++ b/server/lib/src/constants/acp.rs @@ -40,7 +40,7 @@ lazy_static! { /// Built-in Access Control Profile definitions pub struct BuiltinAcp { classes: Vec, - name: &'static str, + pub name: &'static str, uuid: Uuid, description: &'static str, receiver_group: Uuid, @@ -227,7 +227,7 @@ lazy_static! { ProtoFilter::And( vec![ match_class_filter!(EntryClass::Person), - ProtoFilter::Eq("class".to_string(), "account".to_string()), + ProtoFilter::Eq(EntryClass::Class.to_string(), EntryClass::Account.to_string()), match_class_filter!(EntryClass::Account), ProtoFilter::SelfUuid, ] @@ -268,7 +268,7 @@ lazy_static! { EntryClass::AccessControlModify ], receiver_group: UUID_IDM_ALL_ACCOUNTS, - target_scope: ProtoFilter::And(vec![ProtoFilter::Eq("class".to_string(), "account".to_string()), ProtoFilter::SelfUuid]), + target_scope: ProtoFilter::And(vec![ProtoFilter::Eq(Attribute::Class.to_string(), Attribute::Account.to_string()), ProtoFilter::SelfUuid]), modify_removed_attrs: vec![ Attribute::UserAuthTokenSession ], @@ -1763,10 +1763,19 @@ lazy_static! { description: "Builtin IDM Control for managing IDM synchronisation accounts / connections", receiver_group: UUID_IDM_HP_SYNC_ACCOUNT_MANAGE_PRIV, target_scope: ProtoFilter::And(vec![ - ProtoFilter::Eq("class".to_string(), "sync_account".to_string()), + ProtoFilter::Eq( + Attribute::Class.to_string(), + EntryClass::SyncAccount.to_string() + ), ProtoFilter::AndNot(Box::new(ProtoFilter::Or(vec![ - ProtoFilter::Eq("class".to_string(), "tombstone".to_string()), - ProtoFilter::Eq("class".to_string(), "recycled".to_string()), + ProtoFilter::Eq( + Attribute::Class.to_string(), + EntryClass::Tombstone.to_string() + ), + ProtoFilter::Eq( + Attribute::Class.to_string(), + EntryClass::Tombstone.to_string() + ), ]))), ]), search_attrs: vec![ diff --git a/server/lib/src/constants/entries.rs b/server/lib/src/constants/entries.rs index c76ee222e..a5d8b3016 100644 --- a/server/lib/src/constants/entries.rs +++ b/server/lib/src/constants/entries.rs @@ -34,7 +34,7 @@ fn test_valueattribute_round_trip() { } } -#[derive(Copy, Clone, Debug, PartialEq, Sequence, Hash)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Sequence, Hash)] pub enum Attribute { Account, AccountExpire, @@ -90,6 +90,7 @@ pub enum Attribute { IdVerificationEcKey, Index, IpaNtHash, + IpaSshPubKey, JwsEs256PrivateKey, LastModifiedCid, /// An LDAP Compatible emailAddress @@ -263,11 +264,12 @@ impl TryFrom for Attribute { ATTR_ID_VERIFICATION_ECKEY => Attribute::IdVerificationEcKey, ATTR_INDEX => Attribute::Index, ATTR_IPANTHASH => Attribute::IpaNtHash, + ATTR_IPASSHPUBKEY => Attribute::IpaSshPubKey, ATTR_JWS_ES256_PRIVATE_KEY => Attribute::JwsEs256PrivateKey, ATTR_LAST_MODIFIED_CID => Attribute::LastModifiedCid, ATTR_LDAP_EMAIL_ADDRESS => Attribute::LdapEmailAddress, ATTR_LDAP_KEYS => Attribute::LdapKeys, - ATTR_LDAP_SSH_PUBLICKEY => Attribute::SshPublicKey, + ATTR_SSH_PUBLICKEY => Attribute::SshPublicKey, ATTR_LEGALNAME => Attribute::LegalName, ATTR_LOGINSHELL => Attribute::LoginShell, ATTR_MAIL => Attribute::Mail, @@ -309,7 +311,7 @@ impl TryFrom for Attribute { ATTR_SCOPE => Attribute::Scope, ATTR_SOURCE_UUID => Attribute::SourceUuid, ATTR_SPN => Attribute::Spn, - ATTR_SSHPUBLICKEY => Attribute::LdapSshPublicKey, + ATTR_LDAP_SSHPUBLICKEY => Attribute::LdapSshPublicKey, ATTR_SUPPLEMENTS => Attribute::Supplements, ATTR_SYNC_ALLOWED => Attribute::SyncAllowed, ATTR_SYNC_CLASS => Attribute::SyncClass, @@ -346,7 +348,10 @@ impl TryFrom for Attribute { TEST_ATTR_NUMBER => Attribute::TestNumber, #[cfg(any(debug_assertions, test))] TEST_ATTR_NOTALLOWED => Attribute::TestNotAllowed, - _ => return Err(OperationError::InvalidAttributeName(val)), + _ => { + trace!("Failed to convert {} to Attribute", val); + return Err(OperationError::InvalidAttributeName(val)); + } }; Ok(res) } @@ -409,11 +414,12 @@ impl From for &'static str { Attribute::IdVerificationEcKey => ATTR_ID_VERIFICATION_ECKEY, Attribute::Index => ATTR_INDEX, Attribute::IpaNtHash => ATTR_IPANTHASH, + Attribute::IpaSshPubKey => ATTR_IPASSHPUBKEY, Attribute::JwsEs256PrivateKey => ATTR_JWS_ES256_PRIVATE_KEY, Attribute::LastModifiedCid => ATTR_LAST_MODIFIED_CID, Attribute::LdapEmailAddress => ATTR_LDAP_EMAIL_ADDRESS, Attribute::LdapKeys => ATTR_LDAP_KEYS, - Attribute::LdapSshPublicKey => ATTR_SSHPUBLICKEY, + Attribute::LdapSshPublicKey => ATTR_LDAP_SSHPUBLICKEY, Attribute::LegalName => ATTR_LEGALNAME, Attribute::LoginShell => ATTR_LOGINSHELL, Attribute::Mail => ATTR_MAIL, @@ -455,7 +461,7 @@ impl From for &'static str { Attribute::Scope => ATTR_SCOPE, Attribute::SourceUuid => ATTR_SOURCE_UUID, Attribute::Spn => ATTR_SPN, - Attribute::SshPublicKey => ATTR_LDAP_SSH_PUBLICKEY, + Attribute::SshPublicKey => ATTR_SSH_PUBLICKEY, Attribute::Supplements => ATTR_SUPPLEMENTS, Attribute::SyncAllowed => ATTR_SYNC_ALLOWED, Attribute::SyncClass => ATTR_SYNC_CLASS, @@ -521,6 +527,16 @@ impl Attribute { } } +impl<'a> serde::Deserialize<'a> for Attribute { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'a>, + { + let s = String::deserialize(deserializer)?; + Attribute::try_from(s).map_err(|e| serde::de::Error::custom(format!("{:?}", e))) + } +} + #[derive(Copy, Clone, Debug)] pub enum EntryClass { AccessControlCreate, diff --git a/server/lib/src/entry.rs b/server/lib/src/entry.rs index 57699fd4b..5f46faa05 100644 --- a/server/lib/src/entry.rs +++ b/server/lib/src/entry.rs @@ -159,6 +159,8 @@ pub struct EntryReduced { uuid: Uuid, } +// One day this is going to be Map - @yaleman +// pub type Eattrs = Map; pub type Eattrs = Map; pub(crate) fn compare_attrs(left: &Eattrs, right: &Eattrs) -> bool { @@ -166,9 +168,13 @@ pub(crate) fn compare_attrs(left: &Eattrs, right: &Eattrs) -> bool { // Build the set of all keys between both. let allkeys: Set<&str> = left .keys() - .filter(|k| k != &"last_modified_cid") - .chain(right.keys().filter(|k| k != &"last_modified_cid")) - .map(|s| s.as_str()) + .filter(|k| k != &Attribute::LastModifiedCid.as_ref()) + .chain( + right + .keys() + .filter(|k| k != &Attribute::LastModifiedCid.as_ref()), + ) + .map(|s| s.as_ref()) .collect(); allkeys.into_iter().all(|k| { @@ -629,11 +635,11 @@ impl Entry { where T: IntoIterator, { - self.set_ava_iter_int(attr.as_ref(), iter) + self.set_ava_iter_int(attr, iter); } - pub fn get_ava_mut(&mut self, attr: &str) -> Option<&mut ValueSet> { - self.attrs.get_mut(attr) + pub fn get_ava_mut(&mut self, attr: Attribute) -> Option<&mut ValueSet> { + self.attrs.get_mut(attr.as_ref()) } } @@ -794,7 +800,7 @@ impl Entry { // We need to make a random uuid in the conflict gen process. let new_uuid = Uuid::new_v4(); - cnf_ent.purge_ava(Attribute::Uuid.as_ref()); + cnf_ent.purge_ava(Attribute::Uuid); cnf_ent.add_ava(Attribute::Uuid, Value::Uuid(new_uuid)); cnf_ent.add_ava(Attribute::Class, EntryClass::Recycled.into()); cnf_ent.add_ava(Attribute::Class, EntryClass::Conflict.into()); @@ -991,7 +997,7 @@ impl Entry { // we just send the tombstone ecstate rather than attrs. Our // db stub also lacks these attributes too. let mut attrs_new: Eattrs = Map::new(); - let class_ava = vs_iutf8!["object", "tombstone"]; + let class_ava = vs_iutf8![EntryClass::Object.into(), EntryClass::Tombstone.into()]; let last_mod_ava = vs_cid![left_at.clone()]; attrs_new.insert(Attribute::Uuid.into(), vs_uuid![self.valid.uuid]); @@ -1040,7 +1046,7 @@ impl Entry { }; let mut attrs_new: Eattrs = Map::new(); - let class_ava = vs_iutf8!["object", "tombstone"]; + let class_ava = vs_iutf8![EntryClass::Object.into(), EntryClass::Tombstone.into()]; let last_mod_ava = vs_cid![at.clone()]; attrs_new.insert(Attribute::Uuid.into(), vs_uuid![db_ent.valid.uuid]); @@ -1188,9 +1194,9 @@ impl Entry { /// Extract this entry from the recycle bin into a live state. pub fn to_revived(mut self) -> Self { // This will put the modify ahead of the revive transition. - self.remove_ava(Attribute::Class.as_ref(), &EntryClass::Recycled.into()); - self.remove_ava(Attribute::Class.as_ref(), &EntryClass::Conflict.into()); - self.purge_ava(Attribute::SourceUuid.as_ref()); + self.remove_ava(Attribute::Class, &EntryClass::Recycled.into()); + self.remove_ava(Attribute::Class, &EntryClass::Conflict.into()); + self.purge_ava(Attribute::SourceUuid); // Change state repl doesn't need this flag // self.valid.ecstate.revive(&self.valid.cid); @@ -1407,7 +1413,7 @@ impl Entry { /// entry for sync purposes. These strings are then indexed. fn get_externalid2uuid(&self) -> Option { self.attrs - .get("sync_external_id") + .get(Attribute::SyncExternalId.as_ref()) .and_then(|vs| vs.to_proto_string_single()) } @@ -1599,7 +1605,18 @@ impl Entry { idxmeta .keys() .flat_map(|ikey| { - match pre_e.get_ava_set(ikey.attr.as_str()) { + let attr: Attribute = match Attribute::try_from(ikey.attr.as_str()) { + Ok(val) => val, + Err(err) => { + admin_error!( + "Failed to convert '{}' to attribute: {:?}", + ikey.attr, + err + ); + return Vec::new(); + } + }; + match pre_e.get_ava_set(attr) { None => Vec::new(), Some(vs) => { let changes: Vec> = match ikey.itype { @@ -1626,7 +1643,18 @@ impl Entry { idxmeta .keys() .flat_map(|ikey| { - match post_e.get_ava_set(ikey.attr.as_str()) { + let attr: Attribute = match Attribute::try_from(ikey.attr.as_str()) { + Ok(val) => val, + Err(err) => { + admin_error!( + "Failed to convert '{}' to attribute: {:?}", + ikey.attr, + err + ); + return Vec::new(); + } + }; + match post_e.get_ava_set(attr) { None => Vec::new(), Some(vs) => { let changes: Vec> = match ikey.itype { @@ -1653,10 +1681,18 @@ impl Entry { idxmeta .keys() .flat_map(|ikey| { - match ( - pre_e.get_ava_set(ikey.attr.as_str()), - post_e.get_ava_set(ikey.attr.as_str()), - ) { + let attr: Attribute = match Attribute::try_from(ikey.attr.as_str()) { + Ok(val) => val, + Err(err) => { + admin_error!( + "Failed to convert '{}' to attribute: {:?}", + ikey.attr, + err + ); + return Vec::new(); + } + }; + match (pre_e.get_ava_set(attr), post_e.get_ava_set(attr)) { (None, None) => { // Neither have it, do nothing. Vec::new() @@ -1825,7 +1861,7 @@ impl Entry { * and loaded from disk proper. */ let cid = attrs - .get("last_modified_cid") + .get(Attribute::LastModifiedCid.as_ref()) .and_then(|vs| vs.as_cid_set()) .and_then(|set| set.iter().next().cloned())?; @@ -1894,7 +1930,7 @@ impl Entry { // Duplicate this to a tombstone entry let mut attrs_new: Eattrs = Map::new(); - let class_ava = vs_iutf8!["object", "tombstone"]; + let class_ava = vs_iutf8![EntryClass::Object.into(), EntryClass::Tombstone.into()]; let last_mod_ava = vs_cid![cid.clone()]; attrs_new.insert(Attribute::Uuid.into(), vs_uuid![self.get_uuid()]); @@ -1943,33 +1979,27 @@ impl Entry { trace!(?self.attrs, "Entry::validate -> target"); // First, check we have class on the object .... - if !self.attribute_pres(Attribute::Class.into()) { + if !self.attribute_pres(Attribute::Class) { // lrequest_error!("Missing attribute class"); return Err(SchemaError::NoClassFound); } - if self.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Conflict.into()) { + if self.attribute_equality(Attribute::Class, &EntryClass::Conflict.into()) { // Conflict entries are exempt from schema enforcement. Return true. trace!("Skipping schema validation on conflict entry"); return Ok(()); }; // Are we in the recycle bin? We soften some checks if we are. - let recycled = - self.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Recycled.into()); + let recycled = self.attribute_equality(Attribute::Class, &EntryClass::Recycled.into()); // Do we have extensible? We still validate syntax of attrs but don't // check for valid object structures. - let extensible = self.attribute_equality( - Attribute::Class.into(), - &EntryClass::ExtensibleObject.into(), - ); + let extensible = + self.attribute_equality(Attribute::Class, &EntryClass::ExtensibleObject.into()); - let entry_classes = self.get_ava_set(Attribute::Class.into()).ok_or_else(|| { - admin_debug!( - "Attribute '{}' missing from entry", - Attribute::Class.as_ref() - ); + let entry_classes = self.get_ava_set(Attribute::Class).ok_or_else(|| { + admin_debug!("Attribute '{}' missing from entry", Attribute::Class); SchemaError::NoClassFound })?; let mut invalid_classes = Vec::with_capacity(0); @@ -2073,12 +2103,24 @@ impl Entry { // Check that all must are inplace // for each attr in must, check it's present on our ent let mut missing_must = Vec::with_capacity(0); - must.iter().for_each(|attr| { - let avas = self.get_ava_set(&attr.name); + for attr in must.iter() { + let attribute: Attribute = match Attribute::try_from(attr.name.as_str()) { + Ok(val) => val, + Err(err) => { + admin_error!( + "Failed to convert missing_must '{}' to attribute: {:?}", + attr.name, + err + ); + return Err(SchemaError::InvalidAttribute(attr.name.to_string())); + } + }; + + let avas = self.get_ava_set(attribute); if avas.is_none() { missing_must.push(attr.name.to_string()); } - }); + } if !missing_must.is_empty() { admin_warn!( @@ -2336,7 +2378,7 @@ impl Entry { atype: "entrydn".to_string(), vals: vec![dn.as_bytes().to_vec()], }), - "mail;primary" | "emailprimary" => { + LDAP_ATTR_MAIL_PRIMARY | LDAP_ATTR_EMAIL_PRIMARY => { attr_map.get(kani_a).map(|pvs| LdapPartialAttribute { atype: ldap_a.to_string(), vals: pvs @@ -2345,7 +2387,7 @@ impl Entry { .unwrap_or_default(), }) } - "mail;alternative" | "emailalternative" => { + LDAP_ATTR_MAIL_ALTERNATIVE | LDAP_ATTR_EMAIL_ALTERNATIVE => { attr_map.get(kani_a).map(|pvs| LdapPartialAttribute { atype: ldap_a.to_string(), vals: pvs @@ -2390,7 +2432,7 @@ impl Entry { } /// Overwrite the current set of values for an attribute, with this new set. - fn set_ava_iter_int(&mut self, attr: &str, iter: T) + fn set_ava_iter_int(&mut self, attr: Attribute, iter: T) where T: IntoIterator, { @@ -2399,7 +2441,7 @@ impl Entry { return; }; - if let Some(existing_vs) = self.attrs.get_mut(attr) { + if let Some(existing_vs) = self.attrs.get_mut(attr.as_ref()) { // This is the suboptimal path. This can only exist in rare cases. let _ = existing_vs.merge(&vs); } else { @@ -2453,93 +2495,112 @@ impl Entry { #[inline(always)] /// Return a reference to the current set of values that are associated to this attribute. - pub fn get_ava_set(&self, attr: &str) -> Option<&ValueSet> { - self.attrs.get(attr) + pub fn get_ava_set(&self, attr: Attribute) -> Option<&ValueSet> { + self.attrs.get(attr.as_ref()) } - pub fn get_ava_refer(&self, attr: &str) -> Option<&BTreeSet> { - self.attrs.get(attr).and_then(|vs| vs.as_refer_set()) + pub fn get_ava_refer(&self, attr: Attribute) -> Option<&BTreeSet> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_refer_set()) } #[inline(always)] - pub fn get_ava_as_iutf8_iter(&self, attr: &str) -> Option> { - self.attrs.get(attr).and_then(|vs| vs.as_iutf8_iter()) + pub fn get_ava_as_iutf8_iter(&self, attr: Attribute) -> Option> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_iutf8_iter()) } #[inline(always)] - pub fn get_ava_as_iutf8(&self, attr: &str) -> Option<&BTreeSet> { - self.attrs.get(attr).and_then(|vs| vs.as_iutf8_set()) + pub fn get_ava_as_iutf8(&self, attr: Attribute) -> Option<&BTreeSet> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_iutf8_set()) } #[inline(always)] - pub fn get_ava_as_oauthscopes(&self, attr: &str) -> Option> { - self.attrs.get(attr).and_then(|vs| vs.as_oauthscope_iter()) + pub fn get_ava_as_oauthscopes(&self, attr: Attribute) -> Option> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_oauthscope_iter()) } #[inline(always)] pub fn get_ava_as_oauthscopemaps( &self, - attr: &str, + attr: Attribute, ) -> Option<&std::collections::BTreeMap>> { - self.attrs.get(attr).and_then(|vs| vs.as_oauthscopemap()) + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_oauthscopemap()) } #[inline(always)] pub fn get_ava_as_intenttokens( &self, - attr: &str, + attr: Attribute, ) -> Option<&std::collections::BTreeMap> { - self.attrs.get(attr).and_then(|vs| vs.as_intenttoken_map()) + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_intenttoken_map()) } #[inline(always)] pub fn get_ava_as_session_map( &self, - attr: &str, + attr: Attribute, ) -> Option<&std::collections::BTreeMap> { - self.attrs.get(attr).and_then(|vs| vs.as_session_map()) + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_session_map()) } #[inline(always)] pub fn get_ava_as_apitoken_map( &self, - attr: &str, + attr: Attribute, ) -> Option<&std::collections::BTreeMap> { - self.attrs.get(attr).and_then(|vs| vs.as_apitoken_map()) + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_apitoken_map()) } #[inline(always)] pub fn get_ava_as_oauth2session_map( &self, - attr: &str, + attr: Attribute, ) -> Option<&std::collections::BTreeMap> { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.as_oauth2session_map()) } #[inline(always)] /// If possible, return an iterator over the set of values transformed into a `&str`. - pub fn get_ava_iter_iname(&self, attr: &str) -> Option> { + pub fn get_ava_iter_iname(&self, attr: Attribute) -> Option> { self.get_ava_set(attr).and_then(|vs| vs.as_iname_iter()) } #[inline(always)] /// If possible, return an iterator over the set of values transformed into a `&str`. - pub fn get_ava_iter_iutf8(&self, attr: &str) -> Option> { + pub fn get_ava_iter_iutf8(&self, attr: Attribute) -> Option> { self.get_ava_set(attr).and_then(|vs| vs.as_iutf8_iter()) } #[inline(always)] /// If possible, return an iterator over the set of values transformed into a `Uuid`. - pub fn get_ava_as_refuuid(&self, attr: &str) -> Option + '_>> { + pub fn get_ava_as_refuuid( + &self, + attr: Attribute, + ) -> Option + '_>> { // If any value is NOT a reference, it's filtered out. self.get_ava_set(attr).and_then(|vs| vs.as_ref_uuid_iter()) } #[inline(always)] /// If possible, return an iterator over the set of ssh key values transformed into a `&str`. - pub fn get_ava_iter_sshpubkeys(&self, attr: &str) -> Option> { + pub fn get_ava_iter_sshpubkeys(&self, attr: Attribute) -> Option> { self.get_ava_set(attr) .and_then(|vs| vs.as_sshpubkey_str_iter()) } @@ -2551,10 +2612,10 @@ impl Entry { /// multivalue in schema - IE this will *not* fail if the attribute is /// empty, yielding and empty array instead. /// - /// However, the conversion to IndexType is fallaible, so in case of a failure - /// to convert, an Err is returned. + /// However, the conversion to IndexType is fallible, so in case of a failure + /// to convert, an empty vec is returned #[inline(always)] - pub(crate) fn get_ava_opt_index(&self, attr: &str) -> Option> { + pub(crate) fn get_ava_opt_index(&self, attr: Attribute) -> Option> { if let Some(vs) = self.get_ava_set(attr) { vs.as_indextype_iter().map(|i| i.collect()) } else { @@ -2566,169 +2627,203 @@ impl Entry { /// Return a single value of this attributes name, or `None` if it is NOT present, or /// there are multiple values present (ambiguous). #[inline(always)] - pub fn get_ava_single(&self, attr: &str) -> Option { - self.attrs.get(attr).and_then(|vs| vs.to_value_single()) + pub fn get_ava_single(&self, attr: Attribute) -> Option { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_value_single()) } - pub fn get_ava_single_proto_string(&self, attr: &str) -> Option { + pub fn get_ava_single_proto_string(&self, attr: Attribute) -> Option { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.to_proto_string_single()) } #[inline(always)] /// Return a single bool, if valid to transform this value into a boolean. - pub fn get_ava_single_bool(&self, attr: &str) -> Option { - self.attrs.get(attr).and_then(|vs| vs.to_bool_single()) + pub fn get_ava_single_bool(&self, attr: Attribute) -> Option { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_bool_single()) } #[inline(always)] /// Return a single uint32, if valid to transform this value. - pub fn get_ava_single_uint32(&self, attr: &str) -> Option { - self.attrs.get(attr).and_then(|vs| vs.to_uint32_single()) + pub fn get_ava_single_uint32(&self, attr: Attribute) -> Option { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_uint32_single()) } #[inline(always)] /// Return a single syntax type, if valid to transform this value. - pub fn get_ava_single_syntax(&self, attr: &str) -> Option { + pub fn get_ava_single_syntax(&self, attr: Attribute) -> Option { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.to_syntaxtype_single()) } #[inline(always)] /// Return a single credential, if valid to transform this value. - pub fn get_ava_single_credential(&self, attr: &str) -> Option<&Credential> { + pub fn get_ava_single_credential(&self, attr: Attribute) -> Option<&Credential> { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.to_credential_single()) } #[inline(always)] /// Get the set of passkeys on this account, if any are present. - pub fn get_ava_passkeys(&self, attr: &str) -> Option<&BTreeMap> { - self.attrs.get(attr).and_then(|vs| vs.as_passkey_map()) + pub fn get_ava_passkeys( + &self, + attr: Attribute, + ) -> Option<&BTreeMap> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_passkey_map()) } #[inline(always)] /// Get the set of devicekeys on this account, if any are present. - pub fn get_ava_devicekeys(&self, attr: &str) -> Option<&BTreeMap> { - self.attrs.get(attr).and_then(|vs| vs.as_devicekey_map()) + pub fn get_ava_devicekeys( + &self, + attr: Attribute, + ) -> Option<&BTreeMap> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_devicekey_map()) } #[inline(always)] /// Get the set of uihints on this account, if any are present. - pub fn get_ava_uihint(&self, attr: &str) -> Option<&BTreeSet> { - self.attrs.get(attr).and_then(|vs| vs.as_uihint_set()) + pub fn get_ava_uihint(&self, attr: Attribute) -> Option<&BTreeSet> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.as_uihint_set()) } #[inline(always)] /// Return a single secret value, if valid to transform this value. - pub fn get_ava_single_secret(&self, attr: &str) -> Option<&str> { - self.attrs.get(attr).and_then(|vs| vs.to_secret_single()) + pub fn get_ava_single_secret(&self, attr: Attribute) -> Option<&str> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_secret_single()) } #[inline(always)] /// Return a single datetime, if valid to transform this value. - pub fn get_ava_single_datetime(&self, attr: &str) -> Option { - self.attrs.get(attr).and_then(|vs| vs.to_datetime_single()) + pub fn get_ava_single_datetime(&self, attr: Attribute) -> Option { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_datetime_single()) } #[inline(always)] /// Return a single `&str`, if valid to transform this value. - pub(crate) fn get_ava_single_utf8(&self, attr: &str) -> Option<&str> { - self.attrs.get(attr).and_then(|vs| vs.to_utf8_single()) + pub(crate) fn get_ava_single_utf8(&self, attr: Attribute) -> Option<&str> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_utf8_single()) } #[inline(always)] /// Return a single `&str`, if valid to transform this value. - pub(crate) fn get_ava_single_iutf8(&self, attr: &str) -> Option<&str> { - self.attrs.get(attr).and_then(|vs| vs.to_iutf8_single()) + pub(crate) fn get_ava_single_iutf8(&self, attr: Attribute) -> Option<&str> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_iutf8_single()) } #[inline(always)] /// Return a single `&str`, if valid to transform this value. - pub(crate) fn get_ava_single_iname(&self, attr: &str) -> Option<&str> { - self.attrs.get(attr).and_then(|vs| vs.to_iname_single()) + pub(crate) fn get_ava_single_iname(&self, attr: Attribute) -> Option<&str> { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_iname_single()) } #[inline(always)] /// Return a single `&Url`, if valid to transform this value. - pub fn get_ava_single_url(&self, attr: &str) -> Option<&Url> { - self.attrs.get(attr).and_then(|vs| vs.to_url_single()) - } - - pub fn get_ava_single_uuid(&self, attr: &str) -> Option { - self.attrs.get(attr).and_then(|vs| vs.to_uuid_single()) - } - - pub fn get_ava_single_refer(&self, attr: &str) -> Option { - self.attrs.get(attr).and_then(|vs| vs.to_refer_single()) - } - - pub fn get_ava_mail_primary(&self, attr: &str) -> Option<&str> { + pub fn get_ava_single_url(&self, attr: Attribute) -> Option<&Url> { self.attrs - .get(attr) + .get(attr.as_ref()) + .and_then(|vs| vs.to_url_single()) + } + + pub fn get_ava_single_uuid(&self, attr: Attribute) -> Option { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_uuid_single()) + } + + pub fn get_ava_single_refer(&self, attr: Attribute) -> Option { + self.attrs + .get(attr.as_ref()) + .and_then(|vs| vs.to_refer_single()) + } + + pub fn get_ava_mail_primary(&self, attr: Attribute) -> Option<&str> { + self.attrs + .get(attr.as_ref()) .and_then(|vs| vs.to_email_address_primary_str()) } - pub fn get_ava_iter_mail(&self, attr: &str) -> Option> { + pub fn get_ava_iter_mail(&self, attr: Attribute) -> Option> { self.get_ava_set(attr).and_then(|vs| vs.as_email_str_iter()) } #[inline(always)] /// Return a single protocol filter, if valid to transform this value. - pub fn get_ava_single_protofilter(&self, attr: &str) -> Option<&ProtoFilter> { + pub fn get_ava_single_protofilter(&self, attr: Attribute) -> Option<&ProtoFilter> { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.to_json_filter_single()) } - pub fn get_ava_single_private_binary(&self, attr: &str) -> Option<&[u8]> { + pub fn get_ava_single_private_binary(&self, attr: Attribute) -> Option<&[u8]> { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.to_private_binary_single()) } - pub fn get_ava_single_jws_key_es256(&self, attr: &str) -> Option<&JwsSigner> { + pub fn get_ava_single_jws_key_es256(&self, attr: Attribute) -> Option<&JwsSigner> { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.to_jws_key_es256_single()) } - pub fn get_ava_single_eckey_private(&self, attr: &str) -> Option<&EcKey> { + pub fn get_ava_single_eckey_private(&self, attr: Attribute) -> Option<&EcKey> { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.to_eckey_private_single()) } - pub fn get_ava_single_eckey_public(&self, attr: &str) -> Option<&EcKey> { + pub fn get_ava_single_eckey_public(&self, attr: Attribute) -> Option<&EcKey> { self.attrs - .get(attr) + .get(attr.as_ref()) .and_then(|vs| vs.to_eckey_public_single()) } #[inline(always)] /// Return a single security principle name, if valid to transform this value. pub(crate) fn generate_spn(&self, domain_name: &str) -> Option { - self.get_ava_single_iname(Attribute::Name.as_ref()) + self.get_ava_single_iname(Attribute::Name) .map(|name| Value::new_spn_str(name, domain_name)) } #[inline(always)] /// Assert if an attribute of this name is present on this entry. - pub fn attribute_pres(&self, attr: &str) -> bool { - self.attrs.contains_key(attr) + pub fn attribute_pres(&self, attr: Attribute) -> bool { + self.attrs.contains_key(attr.as_ref()) } #[inline(always)] /// Assert if an attribute of this name is present, and one of its values contains /// an exact match of this partial value. - pub fn attribute_equality(&self, attr: &str, value: &PartialValue) -> bool { + pub fn attribute_equality(&self, attr: Attribute, value: &PartialValue) -> bool { // we assume based on schema normalisation on the way in // that the equality here of the raw values MUST be correct. // We also normalise filters, to ensure that their values are // syntax valid and will correctly match here with our indexes. - match self.attrs.get(attr) { + match self.attrs.get(attr.as_ref()) { Some(v_list) => v_list.contains(value), None => false, } @@ -2737,9 +2832,9 @@ impl Entry { #[inline(always)] /// Assert if an attribute of this name is present, and one of it's values contains /// the following substring, if possible to perform the substring comparison. - pub fn attribute_substring(&self, attr: &str, subvalue: &PartialValue) -> bool { + pub fn attribute_substring(&self, attr: Attribute, subvalue: &PartialValue) -> bool { self.attrs - .get(attr) + .get(attr.as_ref()) .map(|vset| vset.substring(subvalue)) .unwrap_or(false) } @@ -2747,9 +2842,9 @@ impl Entry { #[inline(always)] /// Assert if an attribute of this name is present, and one of it's values is less than /// the following partial value - pub fn attribute_lessthan(&self, attr: &str, subvalue: &PartialValue) -> bool { + pub fn attribute_lessthan(&self, attr: Attribute, subvalue: &PartialValue) -> bool { self.attrs - .get(attr) + .get(attr.as_ref()) .map(|vset| vset.lessthan(subvalue)) .unwrap_or(false) } @@ -2772,17 +2867,34 @@ impl Entry { // Go through the filter components and check them in the entry. // This is recursive!!!! match filter { - FilterResolved::Eq(attr, value, _) => self.attribute_equality(attr.as_str(), value), - FilterResolved::Sub(attr, subvalue, _) => { - self.attribute_substring(attr.as_str(), subvalue) - } - FilterResolved::Pres(attr, _) => { - // Given attr, is is present in the entry? - self.attribute_pres(attr.as_str()) - } - FilterResolved::LessThan(attr, subvalue, _) => { - self.attribute_lessthan(attr.as_str(), subvalue) - } + FilterResolved::Eq(attr, value, _) => match attr.try_into() { + Ok(a) => self.attribute_equality(a, value), + Err(_) => { + admin_error!("Failed to convert {} to attribute!", attr); + false + } + }, + FilterResolved::Sub(attr, subvalue, _) => match attr.try_into() { + Ok(a) => self.attribute_substring(a, subvalue), + Err(_) => { + admin_error!("Failed to convert {} to attribute!", attr); + false + } + }, + FilterResolved::Pres(attr, _) => match attr.try_into() { + Ok(a) => self.attribute_pres(a), + Err(_) => { + admin_error!("Failed to convert {} to attribute!", attr); + false + } + }, + FilterResolved::LessThan(attr, subvalue, _) => match attr.try_into() { + Ok(a) => self.attribute_lessthan(a, subvalue), + Err(_) => { + admin_error!("Failed to convert {} to attribute!", attr); + false + } + }, // Check with ftweedal about or filter zero len correctness. FilterResolved::Or(l, _) => l.iter().any(|f| self.entry_match_no_index_inner(f)), // Check with ftweedal about and filter zero len correctness. @@ -2822,15 +2934,12 @@ impl Entry { } } - Some(filter_all!(f_and( - pairs - .into_iter() - .map(|(attr, pv)| { - // We use FC directly here instead of f_eq to avoid an excess clone. - FC::Eq(attr, pv) - }) - .collect() - ))) + let mut res = Vec::new(); + for (attr, pv) in pairs.into_iter() { + // We use FC directly here instead of f_eq to avoid an excess clone. + res.push(FC::Eq(attr, pv)) // TODO: this is kinda terrible + } + Some(filter_all!(f_and(res))) } /// Given this entry, generate a modification list that would "assert" @@ -2943,7 +3052,7 @@ where fn trigger_last_changed(&mut self) { self.valid .ecstate - .change_ava(&self.valid.cid, Attribute::LastModifiedCid.as_ref()); + .change_ava(&self.valid.cid, Attribute::LastModifiedCid); let cv = vs_cid![self.valid.cid.clone()]; let _ = self.attrs.insert(Attribute::LastModifiedCid.into(), cv); } @@ -2953,28 +3062,19 @@ where // If this already exists, we silently drop the event. This is because // we need this to be *state* based where we assert presence. pub fn add_ava(&mut self, attr: Attribute, value: Value) { - self.valid - .ecstate - .change_ava(&self.valid.cid, attr.as_ref()); + self.valid.ecstate.change_ava(&self.valid.cid, attr); self.add_ava_int(attr, value); } - pub fn add_ava_if_not_exist(&mut self, attr: &str, value: Value) { + pub fn add_ava_if_not_exist(&mut self, attr: Attribute, value: Value) { // This returns true if the value WAS changed! See add_ava_int. - - // TODO: attr can be replaced with Attribute and this can go away - #[allow(clippy::panic)] - let attr = - Attribute::try_from(attr).unwrap_or_else(|_| panic!("Invalid attribute {}", attr)); if self.add_ava_int(attr, value) { // In this case, we ONLY update the changestate if the value was already present! - self.valid - .ecstate - .change_ava(&self.valid.cid, attr.as_ref()); + self.valid.ecstate.change_ava(&self.valid.cid, attr); } } - fn assert_ava(&mut self, attr: &str, value: &PartialValue) -> Result<(), OperationError> { + fn assert_ava(&mut self, attr: Attribute, value: &PartialValue) -> Result<(), OperationError> { self.valid.ecstate.change_ava(&self.valid.cid, attr); if self.attribute_equality(attr, value) { @@ -2986,24 +3086,24 @@ where /// Remove an attribute-value pair from this entry. If the ava doesn't exist, we /// don't do anything else since we are asserting the absence of a value. - pub(crate) fn remove_ava(&mut self, attr: &str, value: &PartialValue) { + pub(crate) fn remove_ava(&mut self, attr: Attribute, value: &PartialValue) { self.valid.ecstate.change_ava(&self.valid.cid, attr); - let rm = if let Some(vs) = self.attrs.get_mut(attr) { + let rm = if let Some(vs) = self.attrs.get_mut(attr.as_ref()) { vs.remove(value, &self.valid.cid); vs.is_empty() } else { false }; if rm { - self.attrs.remove(attr); + self.attrs.remove(attr.as_ref()); }; } - pub(crate) fn remove_avas(&mut self, attr: &str, values: &BTreeSet) { + pub(crate) fn remove_avas(&mut self, attr: Attribute, values: &BTreeSet) { self.valid.ecstate.change_ava(&self.valid.cid, attr); - let rm = if let Some(vs) = self.attrs.get_mut(attr) { + let rm = if let Some(vs) = self.attrs.get_mut(attr.as_ref()) { values.iter().for_each(|k| { vs.remove(k, &self.valid.cid); }); @@ -3012,30 +3112,32 @@ where false }; if rm { - self.attrs.remove(attr); + self.attrs.remove(attr.as_ref()); }; } /// Remove all values of this attribute from the entry. If it doesn't exist, this /// asserts that no content of that attribute exist. - pub(crate) fn purge_ava(&mut self, attr: &str) { + pub(crate) fn purge_ava(&mut self, attr: Attribute) { self.valid.ecstate.change_ava(&self.valid.cid, attr); + // self.valid.eclog.purge_ava(&self.valid.cid, attr); + let can_remove = self .attrs - .get_mut(attr) + .get_mut(attr.as_ref()) .map(|vs| vs.purge(&self.valid.cid)) // Default to false since a missing attr can't be removed! .unwrap_or_default(); if can_remove { - self.attrs.remove(attr); + self.attrs.remove(attr.as_ref()); } } /// Remove this value set from the entry, returning the value set at the time of removal. - pub fn pop_ava(&mut self, attr: &str) -> Option { + pub fn pop_ava(&mut self, attr: Attribute) -> Option { self.valid.ecstate.change_ava(&self.valid.cid, attr); - let mut vs = self.attrs.remove(attr)?; + let mut vs = self.attrs.remove(attr.as_ref())?; if vs.purge(&self.valid.cid) { // Can return as is. Some(vs) @@ -3052,33 +3154,29 @@ where /// useful for things like "session" to test the grace window by removing the revoked /// sessions from the value set that you otherwise, could not. #[cfg(test)] - pub(crate) fn force_trim_ava(&mut self, attr: &str) -> Option { + pub(crate) fn force_trim_ava(&mut self, attr: Attribute) -> Option { self.valid.ecstate.change_ava(&self.valid.cid, attr); - self.attrs.remove(attr) + self.attrs.remove(attr.as_ref()) } /// Replace the content of this attribute with the values from this /// iterator. If the iterator is empty, the attribute is purged. - pub fn set_ava(&mut self, attr: &str, iter: T) + pub fn set_ava(&mut self, attr: Attribute, iter: T) where T: Clone + IntoIterator, { - // self.valid.ecstate.change_ava(&self.valid.cid, attr); - // purge_ava triggers ecstate for us. self.purge_ava(attr); self.set_ava_iter_int(attr, iter) } /// Replace the content of this attribute with a new value set. Effectively this is /// a a "purge and set". - pub fn set_ava_set(&mut self, attr: &str, vs: ValueSet) { - // self.valid.ecstate.change_ava(&self.valid.cid, attr); - // purge_ava triggers ecstate for us. + pub fn set_ava_set(&mut self, attr: Attribute, vs: ValueSet) { self.purge_ava(attr); - if let Some(existing_vs) = self.attrs.get_mut(attr) { + if let Some(existing_vs) = self.attrs.get_mut(attr.as_ref()) { let _ = existing_vs.merge(&vs); } else { - self.attrs.insert(AttrString::from(attr), vs); + self.attrs.insert(attr.into(), vs); } } @@ -3089,18 +3187,18 @@ where ) -> Result<(), OperationError> { for modify in modlist { match modify { - Modify::Present(a, v) => { - self.add_ava(Attribute::try_from(a)?, v.clone()); + Modify::Present(attr, value) => { + self.add_ava(Attribute::try_from(attr)?, value.clone()); } - Modify::Removed(a, v) => { - self.remove_ava(a.as_str(), v); + Modify::Removed(attr, value) => { + self.remove_ava(Attribute::try_from(attr)?, value); } - Modify::Purged(a) => { - self.purge_ava(a.as_str()); + Modify::Purged(attr) => { + self.purge_ava(Attribute::try_from(attr)?); } - Modify::Assert(a, v) => { - self.assert_ava(a.as_str(), v).map_err(|e| { - error!("Modification assertion was not met. {} {:?}", a, v); + Modify::Assert(attr, value) => { + self.assert_ava(attr.to_owned(), value).map_err(|e| { + error!("Modification assertion was not met. {} {:?}", attr, value); e })?; } @@ -3160,7 +3258,11 @@ impl From<&SchemaAttribute> for Entry { attrs.insert(Attribute::Syntax.into(), syntax_v); attrs.insert( Attribute::Class.into(), - vs_iutf8!["object", "system", "attributetype"], + vs_iutf8![ + EntryClass::Object.into(), + EntryClass::System.into(), + EntryClass::AttributeType.into() + ], ); // Insert stuff. @@ -3245,9 +3347,7 @@ mod tests { e.add_ava(Attribute::UserId.into(), Value::from("william")); e.add_ava(Attribute::UserId.into(), Value::from("william")); - let values = e - .get_ava_set(Attribute::UserId.into()) - .expect("Failed to get ava"); + let values = e.get_ava_set(Attribute::UserId).expect("Failed to get ava"); // Should only be one value! assert_eq!(values.len(), 1) } @@ -3257,8 +3357,8 @@ mod tests { let mut e: Entry = Entry::new(); e.add_ava(Attribute::UserId.into(), Value::from("william")); - assert!(e.attribute_pres(Attribute::UserId.into())); - assert!(!e.attribute_pres(Attribute::Name.into())); + assert!(e.attribute_pres(Attribute::UserId)); + assert!(!e.attribute_pres(Attribute::Name)); } #[test] @@ -3271,7 +3371,7 @@ mod tests { Attribute::UserId.into(), &PartialValue::new_utf8s("william") )); - assert!(!e.attribute_equality(Attribute::UserId.into(), &PartialValue::new_utf8s("test"))); + assert!(!e.attribute_equality(Attribute::UserId, &PartialValue::new_utf8s("test"))); assert!(!e.attribute_equality( Attribute::NonExist.into(), &PartialValue::new_utf8s("william") @@ -3293,12 +3393,12 @@ mod tests { Attribute::UserId.into(), &PartialValue::new_utf8s("william") )); - assert!(e.attribute_substring(Attribute::UserId.into(), &PartialValue::new_utf8s("will"))); - assert!(e.attribute_substring(Attribute::UserId.into(), &PartialValue::new_utf8s("liam"))); - assert!(e.attribute_substring(Attribute::UserId.into(), &PartialValue::new_utf8s("lli"))); - assert!(!e.attribute_substring(Attribute::UserId.into(), &PartialValue::new_utf8s("llim"))); - assert!(!e.attribute_substring(Attribute::UserId.into(), &PartialValue::new_utf8s("bob"))); - assert!(!e.attribute_substring(Attribute::UserId.into(), &PartialValue::new_utf8s("wl"))); + assert!(e.attribute_substring(Attribute::UserId, &PartialValue::new_utf8s("will"))); + assert!(e.attribute_substring(Attribute::UserId, &PartialValue::new_utf8s("liam"))); + assert!(e.attribute_substring(Attribute::UserId, &PartialValue::new_utf8s("lli"))); + assert!(!e.attribute_substring(Attribute::UserId, &PartialValue::new_utf8s("llim"))); + assert!(!e.attribute_substring(Attribute::UserId, &PartialValue::new_utf8s("bob"))); + assert!(!e.attribute_substring(Attribute::UserId, &PartialValue::new_utf8s("wl"))); } #[test] @@ -3312,17 +3412,17 @@ mod tests { e1.add_ava(Attribute::TestAttr, Value::new_uint32(10)); - assert!(!e1.attribute_lessthan(Attribute::TestAttr.into(), &pv2)); - assert!(!e1.attribute_lessthan(Attribute::TestAttr.into(), &pv8)); - assert!(!e1.attribute_lessthan(Attribute::TestAttr.into(), &pv10)); - assert!(e1.attribute_lessthan(Attribute::TestAttr.into(), &pv15)); + assert!(!e1.attribute_lessthan(Attribute::TestAttr, &pv2)); + assert!(!e1.attribute_lessthan(Attribute::TestAttr, &pv8)); + assert!(!e1.attribute_lessthan(Attribute::TestAttr, &pv10)); + assert!(e1.attribute_lessthan(Attribute::TestAttr, &pv15)); e1.add_ava(Attribute::TestAttr, Value::new_uint32(8)); - assert!(!e1.attribute_lessthan(Attribute::TestAttr.into(), &pv2)); - assert!(!e1.attribute_lessthan(Attribute::TestAttr.into(), &pv8)); - assert!(e1.attribute_lessthan(Attribute::TestAttr.into(), &pv10)); - assert!(e1.attribute_lessthan(Attribute::TestAttr.into(), &pv15)); + assert!(!e1.attribute_lessthan(Attribute::TestAttr, &pv2)); + assert!(!e1.attribute_lessthan(Attribute::TestAttr, &pv8)); + assert!(e1.attribute_lessthan(Attribute::TestAttr, &pv10)); + assert!(e1.attribute_lessthan(Attribute::TestAttr, &pv15)); } #[test] @@ -3344,7 +3444,7 @@ mod tests { Attribute::UserId.into(), &PartialValue::new_utf8s("william") )); - assert!(e.attribute_equality(Attribute::Attr.into(), &PartialValue::new_iutf8("value"))); + assert!(e.attribute_equality(Attribute::Attr, &PartialValue::new_iutf8("value"))); // Assert present for multivalue let present_multivalue_mods = ModifyList::new_valid_list(vec![ @@ -3354,7 +3454,7 @@ mod tests { assert!(e.apply_modlist(&present_multivalue_mods).is_ok()); - assert!(e.attribute_equality(Attribute::Class.into(), &PartialValue::new_iutf8("test"))); + assert!(e.attribute_equality(Attribute::Class, &PartialValue::new_iutf8("test"))); assert!(e.attribute_equality( Attribute::Class.into(), &PartialValue::new_iutf8("multi_test") @@ -3366,14 +3466,14 @@ mod tests { assert!(e.apply_modlist(&purge_single_mods).is_ok()); - assert!(!e.attribute_pres(Attribute::Attr.into())); + assert!(!e.attribute_pres(Attribute::Attr)); let purge_multi_mods = ModifyList::new_valid_list(vec![Modify::Purged(Attribute::Class.into())]); assert!(e.apply_modlist(&purge_multi_mods).is_ok()); - assert!(!e.attribute_pres(Attribute::Class.into())); + assert!(!e.attribute_pres(Attribute::Class)); let purge_empty_mods = purge_single_mods; @@ -3386,15 +3486,15 @@ mod tests { )]); assert!(e.apply_modlist(&present_single_mods).is_ok()); - assert!(e.attribute_equality(Attribute::Attr.into(), &PartialValue::new_iutf8("value"))); + assert!(e.attribute_equality(Attribute::Attr, &PartialValue::new_iutf8("value"))); assert!(e.apply_modlist(&remove_mods).is_ok()); - assert!(e.attrs.get(Attribute::Attr.into()).is_none()); + assert!(e.attrs.get(Attribute::Attr.as_ref()).is_none()); let remove_empty_mods = remove_mods; assert!(e.apply_modlist(&remove_empty_mods).is_ok()); - assert!(e.attrs.get(Attribute::Attr.into()).is_none()); + assert!(e.attrs.get(Attribute::Attr.as_ref()).is_none()); } #[test] diff --git a/server/lib/src/event.rs b/server/lib/src/event.rs index 1ab835e8a..4f51632b1 100644 --- a/server/lib/src/event.rs +++ b/server/lib/src/event.rs @@ -614,7 +614,7 @@ impl ModifyEvent { pub fn from_target_uuid_attr_purge( ident: Identity, target_uuid: Uuid, - attr: &str, + attr: Attribute, filter: Filter, qs: &QueryServerWriteTransaction, ) -> Result { diff --git a/server/lib/src/filter.rs b/server/lib/src/filter.rs index 7e6dc5272..d1e458239 100644 --- a/server/lib/src/filter.rs +++ b/server/lib/src/filter.rs @@ -41,53 +41,44 @@ pub fn f_eq<'a>(a: Attribute, v: PartialValue) -> FC<'a> { FC::Eq(a.into(), v) } -#[allow(dead_code)] -pub fn f_sub(a: &str, v: PartialValue) -> FC { - FC::Sub(a, v) +pub fn f_sub<'a>(a: Attribute, v: PartialValue) -> FC<'a> { + FC::Sub(a.into(), v) } -#[allow(dead_code)] pub fn f_pres<'a>(a: Attribute) -> FC<'a> { FC::Pres(a.into()) } -#[allow(dead_code)] -pub fn f_lt(a: &str, v: PartialValue) -> FC { - FC::LessThan(a, v) +pub fn f_lt<'a>(a: Attribute, v: PartialValue) -> FC<'a> { + FC::LessThan(a.into(), v) } -#[allow(dead_code)] pub fn f_or(vs: Vec) -> FC { FC::Or(vs) } -#[allow(dead_code)] pub fn f_and(vs: Vec) -> FC { FC::And(vs) } -#[allow(dead_code)] pub fn f_inc(vs: Vec) -> FC { FC::Inclusion(vs) } -#[allow(dead_code)] pub fn f_andnot(fc: FC) -> FC { FC::AndNot(Box::new(fc)) } -#[allow(dead_code)] pub fn f_self<'a>() -> FC<'a> { FC::SelfUuid } -#[allow(dead_code)] -pub fn f_id(id: &str) -> FC<'static> { - let uf = Uuid::parse_str(id) +pub fn f_id(uuid: &str) -> FC<'static> { + let uf = Uuid::parse_str(uuid) .ok() .map(|u| FC::Eq(Attribute::Uuid.as_ref(), PartialValue::Uuid(u))); - let spnf = PartialValue::new_spn_s(id).map(|spn| FC::Eq("spn", spn)); - let nf = FC::Eq(Attribute::Name.as_ref(), PartialValue::new_iname(id)); + let spnf = PartialValue::new_spn_s(uuid).map(|spn| FC::Eq(Attribute::Spn.as_ref(), spn)); + let nf = FC::Eq(Attribute::Name.as_ref(), PartialValue::new_iname(uuid)); let f: Vec<_> = iter::once(uf) .chain(iter::once(spnf)) .flatten() @@ -96,7 +87,6 @@ pub fn f_id(id: &str) -> FC<'static> { FC::Or(f) } -#[allow(dead_code)] pub fn f_spn_name(id: &str) -> FC<'static> { let spnf = PartialValue::new_spn_s(id).map(|spn| FC::Eq(Attribute::Spn.as_ref(), spn)); let nf = FC::Eq(Attribute::Name.as_ref(), PartialValue::new_iname(id)); @@ -1361,7 +1351,7 @@ mod tests { f_eq(Attribute::UserId, PartialValue::new_iutf8("test_a")), f_eq(Attribute::UserId, PartialValue::new_iutf8("test_b")), ]), - f_sub(Attribute::Class.as_ref(), EntryClass::User.into()), + f_sub(Attribute::Class, EntryClass::User.into()), ])); } @@ -1422,14 +1412,14 @@ mod tests { Attribute::Class, EntryClass::TestClass.to_partialvalue() )]), - f_sub(Attribute::Class.as_ref(), PartialValue::new_class("te")), + f_sub(Attribute::Class, PartialValue::new_class("te")), f_pres(Attribute::Class), f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()) ]), f_and(vec![ f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()), f_pres(Attribute::Class), - f_sub(Attribute::Class.as_ref(), PartialValue::new_class("te")), + f_sub(Attribute::Class, PartialValue::new_class("te")), ]) ); @@ -1441,7 +1431,7 @@ mod tests { f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()), f_eq(Attribute::Uid, PartialValue::new_class("bar")), ]), - f_sub(Attribute::Class.as_ref(), PartialValue::new_class("te")), + f_sub(Attribute::Class, PartialValue::new_class("te")), f_pres(Attribute::Class), f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()) ]), @@ -1450,7 +1440,7 @@ mod tests { f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()), f_pres(Attribute::Class), f_eq(Attribute::Uid, PartialValue::new_class("bar")), - f_sub(Attribute::Class.as_ref(), PartialValue::new_class("te")), + f_sub(Attribute::Class, PartialValue::new_class("te")), ]) ); @@ -1458,14 +1448,14 @@ mod tests { f_or(vec![ f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()), f_pres(Attribute::Class), - f_sub(Attribute::Class.as_ref(), PartialValue::new_class("te")), + f_sub(Attribute::Class, PartialValue::new_class("te")), f_or(vec![f_eq( Attribute::Class, EntryClass::TestClass.to_partialvalue() )]), ]), f_or(vec![ - f_sub(Attribute::Class.as_ref(), PartialValue::new_class("te")), + f_sub(Attribute::Class, PartialValue::new_class("te")), f_pres(Attribute::Class), f_eq(Attribute::Class, EntryClass::TestClass.to_partialvalue()) ]) @@ -1538,7 +1528,7 @@ mod tests { assert_eq!(f_t3b.partial_cmp(&f_t1a), Some(Ordering::Less)); // transitivity: a < b and b < c implies a < c. The same must hold for both == and >. - let f_t4b = filter_resolved!(f_sub("userid", PartialValue::new_iutf8(""))); + let f_t4b = filter_resolved!(f_sub(Attribute::UserId, PartialValue::new_iutf8(""))); assert_eq!(f_t1a.partial_cmp(&f_t4b), Some(Ordering::Less)); assert_eq!(f_t3b.partial_cmp(&f_t4b), Some(Ordering::Less)); @@ -1577,22 +1567,13 @@ mod tests { ) .into_sealed_new(); - let f_t1a = filter_resolved!(f_lt( - Attribute::GidNumber.as_ref(), - PartialValue::new_uint32(500) - )); + let f_t1a = filter_resolved!(f_lt(Attribute::GidNumber, PartialValue::new_uint32(500))); assert!(!e.entry_match_no_index(&f_t1a)); - let f_t1b = filter_resolved!(f_lt( - Attribute::GidNumber.as_ref(), - PartialValue::new_uint32(1000) - )); + let f_t1b = filter_resolved!(f_lt(Attribute::GidNumber, PartialValue::new_uint32(1000))); assert!(!e.entry_match_no_index(&f_t1b)); - let f_t1c = filter_resolved!(f_lt( - Attribute::GidNumber.as_ref(), - PartialValue::new_uint32(1001) - )); + let f_t1c = filter_resolved!(f_lt(Attribute::GidNumber, PartialValue::new_uint32(1001))); assert!(e.entry_match_no_index(&f_t1c)); } diff --git a/server/lib/src/idm/account.rs b/server/lib/src/idm/account.rs index 36f4afe6f..481356bfa 100644 --- a/server/lib/src/idm/account.rs +++ b/server/lib/src/idm/account.rs @@ -27,65 +27,65 @@ use kanidm_lib_crypto::CryptoPolicy; macro_rules! try_from_entry { ($value:expr, $groups:expr) => {{ // Check the classes - if !$value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Account.to_partialvalue(), - ) { - return Err(OperationError::InvalidAccountState( - "Missing class: account".to_string(), - )); + if !$value.attribute_equality(Attribute::Class, &EntryClass::Account.to_partialvalue()) { + return Err(OperationError::InvalidAccountState(format!( + "Missing class: {}", + EntryClass::Account + ))); } // Now extract our needed attributes let name = $value - .get_ava_single_iname(Attribute::Name.as_ref()) + .get_ava_single_iname(Attribute::Name) .map(|s| s.to_string()) - .ok_or(OperationError::InvalidAccountState( - "Missing attribute: name".to_string(), - ))?; + .ok_or(OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Name + )))?; let displayname = $value - .get_ava_single_utf8(Attribute::DisplayName.as_ref()) + .get_ava_single_utf8(Attribute::DisplayName) .map(|s| s.to_string()) - .ok_or(OperationError::InvalidAccountState( - "Missing attribute: displayname".to_string(), - ))?; + .ok_or(OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::DisplayName + )))?; - let sync_parent_uuid = $value.get_ava_single_refer("sync_parent_uuid"); + let sync_parent_uuid = $value.get_ava_single_refer(Attribute::SyncParentUuid); let primary = $value - .get_ava_single_credential("primary_credential") + .get_ava_single_credential(Attribute::PrimaryCredential) .map(|v| v.clone()); let passkeys = $value - .get_ava_passkeys("passkeys") + .get_ava_passkeys(Attribute::PassKeys) .cloned() .unwrap_or_default(); let devicekeys = $value - .get_ava_devicekeys("devicekeys") + .get_ava_devicekeys(Attribute::DeviceKeys) .cloned() .unwrap_or_default(); - let spn = $value - .get_ava_single_proto_string(Attribute::Spn.as_ref()) - .ok_or(OperationError::InvalidAccountState( - "Missing attribute: spn".to_string(), - ))?; + let spn = $value.get_ava_single_proto_string(Attribute::Spn).ok_or( + OperationError::InvalidAccountState(format!("Missing attribute: {}", Attribute::Spn)), + )?; - let mail_primary = $value.get_ava_mail_primary("mail").map(str::to_string); + let mail_primary = $value + .get_ava_mail_primary(Attribute::Mail) + .map(str::to_string); let mail = $value - .get_ava_iter_mail("mail") + .get_ava_iter_mail(Attribute::Mail) .map(|i| i.map(str::to_string).collect()) .unwrap_or_else(Vec::new); - let valid_from = $value.get_ava_single_datetime("account_valid_from"); + let valid_from = $value.get_ava_single_datetime(Attribute::AccountValidFrom); - let expire = $value.get_ava_single_datetime("account_expire"); + let expire = $value.get_ava_single_datetime(Attribute::AccountExpire); let radius_secret = $value - .get_ava_single_secret("radius_secret") + .get_ava_single_secret(Attribute::RadiusSecret) .map(str::to_string); // Resolved by the caller @@ -94,7 +94,7 @@ macro_rules! try_from_entry { let uuid = $value.get_uuid().clone(); let credential_update_intent_tokens = $value - .get_ava_as_intenttokens("credential_update_intent_token") + .get_ava_as_intenttokens(Attribute::CredentialUpdateIntentToken) .cloned() .unwrap_or_default(); @@ -107,22 +107,16 @@ macro_rules! try_from_entry { .collect(); // For now disable cred updates on sync accounts too. - if $value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Person.to_partialvalue(), - ) { + if $value.attribute_equality(Attribute::Class, &EntryClass::Person.to_partialvalue()) { ui_hints.insert(UiHint::CredentialUpdate); } - if $value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::SyncObject.to_partialvalue(), - ) { + if $value.attribute_equality(Attribute::Class, &EntryClass::SyncObject.to_partialvalue()) { ui_hints.insert(UiHint::SynchronisedAccount); } if $value.attribute_equality( - Attribute::Class.as_ref(), + Attribute::Class, &EntryClass::PosixAccount.to_partialvalue(), ) { ui_hints.insert(UiHint::PosixAccount); @@ -408,13 +402,19 @@ impl Account { Some(primary) => { let ncred = primary.set_password(crypto_policy, cleartext)?; let vcred = Value::new_credential("primary", ncred); - Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) + Ok(ModifyList::new_purge_and_set( + Attribute::PrimaryCredential, + vcred, + )) } // Make a new credential instead None => { let ncred = Credential::new_password_only(crypto_policy, cleartext)?; let vcred = Value::new_credential("primary", ncred); - Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) + Ok(ModifyList::new_purge_and_set( + Attribute::PrimaryCredential, + vcred, + )) } } } @@ -430,7 +430,7 @@ impl Account { if let Some(ncred) = primary.upgrade_password(crypto_policy, cleartext)? { let vcred = Value::new_credential("primary", ncred); Ok(Some(ModifyList::new_purge_and_set( - "primary_credential", + Attribute::PrimaryCredential, vcred, ))) } else { @@ -456,20 +456,20 @@ impl Account { if let Some(ncred) = opt_ncred { let vcred = Value::new_credential("primary", ncred); - ml.push(Modify::Purged("primary_credential".into())); - ml.push(Modify::Present("primary_credential".into(), vcred)); + ml.push(Modify::Purged(Attribute::PrimaryCredential.into())); + ml.push(Modify::Present(Attribute::PrimaryCredential.into(), vcred)); } // Is it a passkey? self.passkeys.iter_mut().for_each(|(u, (t, k))| { if let Some(true) = k.update_credential(auth_result) { ml.push(Modify::Removed( - "passkeys".into(), + Attribute::PassKeys.into(), PartialValue::Passkey(*u), )); ml.push(Modify::Present( - "passkeys".into(), + Attribute::PassKeys.into(), Value::Passkey(*u, t.clone(), k.clone()), )); } @@ -493,7 +493,10 @@ impl Account { match r_ncred { Ok(ncred) => { let vcred = Value::new_credential("primary", ncred); - Ok(ModifyList::new_purge_and_set("primary_credential", vcred)) + Ok(ModifyList::new_purge_and_set( + Attribute::PrimaryCredential, + vcred, + )) } Err(e) => Err(e), } @@ -510,7 +513,10 @@ impl Account { cleartext: &str, ) -> Result, OperationError> { let vcred = Value::new_secret_str(cleartext); - Ok(ModifyList::new_purge_and_set("radius_secret", vcred)) + Ok(ModifyList::new_purge_and_set( + Attribute::RadiusSecret, + vcred, + )) } pub(crate) fn to_credentialstatus(&self) -> Result { @@ -548,8 +554,12 @@ impl Account { let within_valid_window = Account::check_within_valid_time( ct, - entry.get_ava_single_datetime("account_valid_from").as_ref(), - entry.get_ava_single_datetime("account_expire").as_ref(), + entry + .get_ava_single_datetime(Attribute::AccountValidFrom) + .as_ref(), + entry + .get_ava_single_datetime(Attribute::AccountExpire) + .as_ref(), ); if !within_valid_window { @@ -567,7 +577,7 @@ impl Account { } else { // Get the sessions. let session_present = entry - .get_ava_as_session_map("user_auth_token_session") + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|session_map| session_map.get(&uat.session_id)); // Important - we don't have to check the expiry time against ct here since it was @@ -694,13 +704,16 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // Copy the current classes let prev_classes: BTreeSet<_> = account_entry - .get_ava_as_iutf8_iter(Attribute::Class.as_ref()) + .get_ava_as_iutf8_iter(Attribute::Class) .ok_or_else(|| { admin_error!( "Invalid entry, {} attribute is not present or not iutf8", Attribute::Class.as_ref() ); - OperationError::InvalidAccountState("Missing attribute: class".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Class + )) })? .collect(); @@ -723,7 +736,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // Now construct the modlist which: // removes service_account let mut modlist = ModifyList::new_remove( - Attribute::Class.as_ref(), + Attribute::Class, EntryClass::ServiceAccount.to_partialvalue(), ); // add person @@ -788,7 +801,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> { .and_then(|e| { let account_id = e.get_uuid(); // From the entry, turn it into the value - e.get_ava_as_session_map("user_auth_token_session") + e.get_ava_as_session_map(Attribute::UserAuthTokenSession) .map(|smap| { smap.iter() .map(|(u, s)| { diff --git a/server/lib/src/idm/applinks.rs b/server/lib/src/idm/applinks.rs index 17b88c05b..39e306231 100644 --- a/server/lib/src/idm/applinks.rs +++ b/server/lib/src/idm/applinks.rs @@ -40,16 +40,16 @@ impl<'a> IdmServerProxyReadTransaction<'a> { .iter() .filter_map(|entry| { let display_name = entry - .get_ava_single_utf8(Attribute::DisplayName.as_ref()) + .get_ava_single_utf8(Attribute::DisplayName) .map(str::to_string)?; let redirect_url = entry - .get_ava_single_url("oauth2_rs_origin_landing") - .or_else(|| entry.get_ava_single_url(Attribute::OAuth2RsOrigin.as_ref())) + .get_ava_single_url(Attribute::OAuth2RsOriginLanding) + .or_else(|| entry.get_ava_single_url(Attribute::OAuth2RsOrigin)) .cloned()?; let name = entry - .get_ava_single_iname("oauth2_rs_name") + .get_ava_single_iname(Attribute::OAuth2RsName) .map(str::to_string)?; Some(AppLink::Oauth2 { @@ -160,7 +160,7 @@ mod tests { let mut idms_prox_write = idms.proxy_write(ct).await; let me_inv_m = ModifyEvent::new_internal_invalid( filter!(f_eq(Attribute::Uuid, PartialValue::Refer(grp_uuid))), - ModifyList::new_append("member", Value::Refer(usr_uuid)), + ModifyList::new_append(Attribute::Member, Value::Refer(usr_uuid)), ); assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); assert!(idms_prox_write.commit().is_ok()); diff --git a/server/lib/src/idm/credupdatesession.rs b/server/lib/src/idm/credupdatesession.rs index dbb772fe1..bce29945f 100644 --- a/server/lib/src/idm/credupdatesession.rs +++ b/server/lib/src/idm/credupdatesession.rs @@ -372,19 +372,19 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { let eperm_search_primary_cred = match &eperm.search { Access::Denied => false, Access::Grant => true, - Access::Allow(attrs) => attrs.contains("primary_credential"), + Access::Allow(attrs) => attrs.contains(Attribute::PrimaryCredential.as_ref()), }; let eperm_mod_primary_cred = match &eperm.modify_pres { Access::Denied => false, Access::Grant => true, - Access::Allow(attrs) => attrs.contains("primary_credential"), + Access::Allow(attrs) => attrs.contains(Attribute::PrimaryCredential.as_ref()), }; let eperm_rem_primary_cred = match &eperm.modify_rem { Access::Denied => false, Access::Grant => true, - Access::Allow(attrs) => attrs.contains("primary_credential"), + Access::Allow(attrs) => attrs.contains(Attribute::PrimaryCredential.as_ref()), }; let primary_can_edit = @@ -393,19 +393,19 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { let eperm_search_passkeys = match &eperm.search { Access::Denied => false, Access::Grant => true, - Access::Allow(attrs) => attrs.contains("passkeys"), + Access::Allow(attrs) => attrs.contains(Attribute::PassKeys.as_ref()), }; let eperm_mod_passkeys = match &eperm.modify_pres { Access::Denied => false, Access::Grant => true, - Access::Allow(attrs) => attrs.contains("passkeys"), + Access::Allow(attrs) => attrs.contains(Attribute::PassKeys.as_ref()), }; let eperm_rem_passkeys = match &eperm.modify_rem { Access::Denied => false, Access::Grant => true, - Access::Allow(attrs) => attrs.contains("passkeys"), + Access::Allow(attrs) => attrs.contains(Attribute::PassKeys.as_ref()), }; let passkeys_can_edit = eperm_search_passkeys && eperm_mod_passkeys && eperm_rem_passkeys; @@ -431,7 +431,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { match &eperm.search { Access::Denied => false, Access::Grant => true, - Access::Allow(attrs) => attrs.contains("sync_credential_portal"), + Access::Allow(attrs) => attrs.contains(Attribute::SyncCredentialPortal.as_ref()), } } else { false @@ -487,7 +487,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { (Some(sync_parent_uuid), true) => { let sync_entry = self.qs_write.internal_search_uuid(sync_parent_uuid)?; sync_entry - .get_ava_single_url("sync_credential_portal") + .get_ava_single_url(Attribute::SyncCredentialPortal) .cloned() .map(CUExtPortal::Some) .unwrap_or(CUExtPortal::Hidden) @@ -582,7 +582,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // write anyway, and instead on the intent access path we invalidate IF the collision // occurs. let mut modlist = ModifyList::new_append( - "credential_update_intent_token", + Attribute::CredentialUpdateIntentToken, Value::IntentToken( intent_id.clone(), IntentTokenState::Valid { max_ttl, perms }, diff --git a/server/lib/src/idm/group.rs b/server/lib/src/idm/group.rs index 64bf0962e..ae0165937 100644 --- a/server/lib/src/idm/group.rs +++ b/server/lib/src/idm/group.rs @@ -20,7 +20,7 @@ macro_rules! try_from_account_e { ($value:expr, $qs:expr) => {{ /* let name = $value - .get_ava_single_iname(Attribute::Name.as_ref()) + .get_ava_single_iname(Attribute::Name) .map(str::to_string) .ok_or_else(|| { OperationError::InvalidAccountState("Missing attribute: name".to_string()) @@ -28,11 +28,9 @@ macro_rules! try_from_account_e { */ // Setup the user private group - let spn = $value - .get_ava_single_proto_string(Attribute::Spn.as_ref()) - .ok_or(OperationError::InvalidAccountState( - "Missing attribute: spn".to_string(), - ))?; + let spn = $value.get_ava_single_proto_string(Attribute::Spn).ok_or( + OperationError::InvalidAccountState(format!("Missing attribute: {}", Attribute::Spn)), + )?; let uuid = $value.get_uuid(); @@ -45,7 +43,7 @@ macro_rules! try_from_account_e { ui_hints, }; - let mut groups: Vec = match $value.get_ava_as_refuuid("memberof") { + let mut groups: Vec = match $value.get_ava_as_refuuid(Attribute::MemberOf) { Some(riter) => { // given a list of uuid, make a filter: even if this is empty, the be will // just give and empty result set. @@ -104,7 +102,7 @@ impl Group { pub fn try_from_entry( value: &Entry, ) -> Result { - if !value.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Group.into()) { + if !value.attribute_equality(Attribute::Class, &EntryClass::Group.into()) { return Err(OperationError::InvalidAccountState( "Missing class: group".to_string(), )); @@ -113,14 +111,14 @@ impl Group { // Now extract our needed attributes /* let name = value - .get_ava_single_iname(Attribute::Name.as_ref()) + .get_ava_single_iname(Attribute::Name) .map(|s| s.to_string()) .ok_or_else(|| { OperationError::InvalidAccountState("Missing attribute: name".to_string()) })?; */ let spn = value - .get_ava_single_proto_string(Attribute::Spn.as_ref()) + .get_ava_single_proto_string(Attribute::Spn) .ok_or_else(|| { OperationError::InvalidAccountState("Missing attribute: spn".to_string()) })?; @@ -128,7 +126,7 @@ impl Group { let uuid = value.get_uuid(); let ui_hints = value - .get_ava_uihint("grant_ui_hint") + .get_ava_uihint(Attribute::GrantUiHint) .cloned() .unwrap_or_default(); diff --git a/server/lib/src/idm/identityverification.rs b/server/lib/src/idm/identityverification.rs index fcb9eba44..6bbaac8c6 100644 --- a/server/lib/src/idm/identityverification.rs +++ b/server/lib/src/idm/identityverification.rs @@ -1,6 +1,5 @@ use std::time::SystemTime; -use kanidm_proto::constants::ATTR_ID_VERIFICATION_ECKEY; use kanidm_proto::{internal::IdentifyUserResponse, v1::OperationError}; use openssl::ec::EcKey; use openssl::pkey::{PKey, Private, Public}; @@ -9,7 +8,7 @@ use sketching::admin_error; use uuid::Uuid; use crate::credential::totp::{Totp, TotpAlgo, TotpDigits}; -use crate::prelude::{tagged_event, EventTag}; +use crate::prelude::{tagged_event, Attribute, EventTag}; use crate::server::QueryServerTransaction; use crate::{event::SearchEvent, server::identity::Identity}; @@ -161,7 +160,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> { .search(&search) .and_then(|mut entries| entries.pop().ok_or(OperationError::NoMatchingEntries)) .map( - |entry| match entry.get_ava_single_eckey_private(ATTR_ID_VERIFICATION_ECKEY) { + |entry| match entry.get_ava_single_eckey_private(Attribute::IdVerificationEcKey) { Some(key) => key.check_key().is_ok(), None => false, }, @@ -187,7 +186,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> { .search(&search) .and_then(|mut entries| entries.pop().ok_or(OperationError::NoMatchingEntries))?; - match user_entry.get_ava_single_eckey_public(ATTR_ID_VERIFICATION_ECKEY) { + match user_entry.get_ava_single_eckey_public(Attribute::IdVerificationEcKey) { Some(key) => Ok(key.check_key().is_ok()), None => Ok(false), } @@ -210,7 +209,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> { .search(&search) .and_then(|mut entries| entries.pop().ok_or(OperationError::NoMatchingEntries)) .and_then(|entry| { - match entry.get_ava_single_eckey_private(ATTR_ID_VERIFICATION_ECKEY) { + match entry.get_ava_single_eckey_private(Attribute::IdVerificationEcKey) { Some(key) => Ok(key.clone()), None => Err(OperationError::InvalidAccountState(format!( "{}'s private key is missing!", @@ -240,14 +239,14 @@ impl<'a> IdmServerProxyReadTransaction<'a> { self.qs_read .search(&search) .and_then(|mut entries| entries.pop().ok_or(OperationError::NoMatchingEntries)) - .and_then( - |entry| match entry.get_ava_single_eckey_public(ATTR_ID_VERIFICATION_ECKEY) { + .and_then(|entry| { + match entry.get_ava_single_eckey_public(Attribute::IdVerificationEcKey) { Some(key) => Ok(key.clone()), None => Err(OperationError::InvalidAccountState(format!( "{target}'s public key is missing!", ))), - }, - ) + } + }) } fn compute_totp(&mut self, totp_secret: Vec) -> Result { diff --git a/server/lib/src/idm/ldap.rs b/server/lib/src/idm/ldap.rs index 22cb2389e..76d60fb86 100644 --- a/server/lib/src/idm/ldap.rs +++ b/server/lib/src/idm/ldap.rs @@ -69,11 +69,11 @@ impl LdapServer { .internal_search_uuid(UUID_DOMAIN_INFO)?; let basedn = domain_entry - .get_ava_single_iutf8("domain_ldap_basedn") + .get_ava_single_iutf8(Attribute::DomainLdapBasedn) .map(|s| s.to_string()) .or_else(|| { domain_entry - .get_ava_single_iname(Attribute::DomainName.as_ref()) + .get_ava_single_iname(Attribute::DomainName) .map(ldap_domain_to_dc) }) .ok_or(OperationError::InvalidEntryState)?; @@ -88,7 +88,7 @@ impl LdapServer { dn: "".to_string(), attributes: vec![ LdapPartialAttribute { - atype: "objectclass".to_string(), + atype: ATTR_OBJECTCLASS.to_string(), vals: vec!["top".as_bytes().to_vec()], }, LdapPartialAttribute { @@ -135,7 +135,7 @@ impl LdapServer { // eventid: &Uuid, ) -> Result, OperationError> { admin_info!("Attempt LDAP Search for {}", uat.spn); - // If the request is "", Base, Present("objectclass"), [], then we want the rootdse. + // If the request is "", Base, Present(Attribute::ObjectClass.into()), [], then we want the rootdse. if sr.base.is_empty() && sr.scope == LdapSearchScope::Base { admin_info!("LDAP Search success - RootDSE"); Ok(vec![ @@ -563,16 +563,16 @@ pub(crate) fn ldap_all_vattrs() -> Vec { vec![ ATTR_CN.to_string(), ATTR_EMAIL.to_string(), - "emailaddress".to_string(), - "emailalternative".to_string(), - "emailprimary".to_string(), + ATTR_LDAP_EMAIL_ADDRESS.to_string(), + LDAP_ATTR_EMAIL_ALTERNATIVE.to_string(), + LDAP_ATTR_EMAIL_PRIMARY.to_string(), "entrydn".to_string(), - "entryuuid".to_string(), - "keys".to_string(), - "mail;alternative".to_string(), - "mail;primary".to_string(), - "objectclass".to_string(), - ATTR_LDAP_SSH_PUBLICKEY.to_string(), + LDAP_ATTR_ENTRYUUID.to_string(), + LDAP_ATTR_KEYS.to_string(), + LDAP_ATTR_MAIL_ALTERNATIVE.to_string(), + LDAP_ATTR_MAIL_PRIMARY.to_string(), + ATTR_OBJECTCLASS.to_string(), + ATTR_LDAP_SSHPUBLICKEY.to_string(), ATTR_UIDNUMBER.to_string(), ] } @@ -586,18 +586,18 @@ pub(crate) fn ldap_vattr_map(input: &str) -> Option<&str> { // // LDAP NAME KANI ATTR SOURCE NAME match input { - "cn" => Some(ATTR_NAME), + ATTR_CN => Some(ATTR_NAME), ATTR_EMAIL => Some(ATTR_MAIL), - "emailaddress" => Some(ATTR_MAIL), - "emailalternative" => Some(ATTR_MAIL), - "emailprimary" => Some(ATTR_MAIL), - "entryuuid" => Some(ATTR_UUID), - "keys" => Some(ATTR_LDAP_SSH_PUBLICKEY), - "mail;alternative" => Some(ATTR_MAIL), - "mail;primary" => Some(ATTR_MAIL), - "objectclass" => Some(ATTR_CLASS), - ATTR_LDAP_SSH_PUBLICKEY => Some(ATTR_LDAP_SSH_PUBLICKEY), // no-underscore -> underscore - "uidnumber" => Some(ATTR_GIDNUMBER), // yes this is intentional + ATTR_LDAP_EMAIL_ADDRESS => Some(ATTR_MAIL), + LDAP_ATTR_EMAIL_ALTERNATIVE => Some(ATTR_MAIL), + LDAP_ATTR_EMAIL_PRIMARY => Some(ATTR_MAIL), + LDAP_ATTR_ENTRYUUID => Some(ATTR_UUID), + LDAP_ATTR_KEYS => Some(ATTR_SSH_PUBLICKEY), + LDAP_ATTR_MAIL_ALTERNATIVE => Some(ATTR_MAIL), + LDAP_ATTR_MAIL_PRIMARY => Some(ATTR_MAIL), + ATTR_OBJECTCLASS => Some(ATTR_CLASS), + ATTR_LDAP_SSHPUBLICKEY => Some(ATTR_SSH_PUBLICKEY), // no-underscore -> underscore + ATTR_UIDNUMBER => Some(ATTR_GIDNUMBER), // yes this is intentional _ => None, } } @@ -853,7 +853,7 @@ mod tests { msgid: 1, base: "dc=example,dc=com".to_string(), scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()), attrs: vec!["*".to_string()], }; let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); @@ -885,7 +885,7 @@ mod tests { msgid: 1, base: "dc=example,dc=com".to_string(), scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()), attrs: vec!["+".to_string()], }; let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); @@ -924,12 +924,12 @@ mod tests { msgid: 1, base: "dc=example,dc=com".to_string(), scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()), attrs: vec![ - "name".to_string(), - "entrydn".to_string(), - "keys".to_string(), - "uidnumber".to_string(), + LDAP_ATTR_NAME.to_string(), + Attribute::EntryDn.to_string(), + ATTR_LDAP_KEYS.to_string(), + Attribute::UidNumber.to_string(), ], }; let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); @@ -967,15 +967,18 @@ mod tests { msgid: 1, base: "dc=example,dc=com".to_string(), scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()), attrs: vec![ - "name".to_string(), - "mail".to_string(), - "mail;primary".to_string(), - "mail;alternative".to_string(), - "emailprimary".to_string(), - "emailalternative".to_string(), - ], + LDAP_ATTR_NAME, + LDAP_ATTR_MAIL, + LDAP_ATTR_MAIL_PRIMARY, + LDAP_ATTR_MAIL_ALTERNATIVE, + LDAP_ATTR_EMAIL_PRIMARY, + LDAP_ATTR_EMAIL_ALTERNATIVE, + ] + .into_iter() + .map(|s| s.to_string()) + .collect(), }; let sa_uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"); @@ -1030,7 +1033,7 @@ mod tests { let me = ModifyEvent::new_internal_invalid( filter!(f_eq( Attribute::Name, - PartialValue::new_iname("idm_people_read_priv") + PartialValue::new_iname(IDM_PEOPLE_READ_PRIV_V1.name) )), ModifyList::new_list(vec![Modify::Present( Attribute::Member.into(), @@ -1109,10 +1112,16 @@ mod tests { Attribute::Mail.as_ref(), "testperson1.alternative@example.com" ), - ("mail;primary", "testperson1@example.com"), - ("mail;alternative", "testperson1.alternative@example.com"), - ("emailprimary", "testperson1@example.com"), - ("emailalternative", "testperson1.alternative@example.com") + (LDAP_ATTR_MAIL_PRIMARY, "testperson1@example.com"), + ( + LDAP_ATTR_MAIL_ALTERNATIVE, + "testperson1.alternative@example.com" + ), + (LDAP_ATTR_MAIL_PRIMARY, "testperson1@example.com"), + ( + LDAP_ATTR_MAIL_ALTERNATIVE, + "testperson1.alternative@example.com" + ) ); } _ => assert!(false), @@ -1156,13 +1165,13 @@ mod tests { msgid: 1, base: "dc=example,dc=com".to_string(), scope: LdapSearchScope::Subtree, - filter: LdapFilter::Equality("name".to_string(), "testperson1".to_string()), + filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()), attrs: vec![ "*".to_string(), // Already being returned - "name".to_string(), + LDAP_ATTR_NAME.to_string(), // This is a virtual attribute - "entryuuid".to_string(), + Attribute::EntryUuid.to_string(), ], }; let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); @@ -1177,7 +1186,10 @@ mod tests { (Attribute::Name, "testperson1"), (Attribute::DisplayName, "testperson1"), (Attribute::Uuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930"), - ("entryuuid", "cc8e95b4-c24f-4d68-ba54-8bed76f63930") + ( + Attribute::EntryUuid.as_ref(), + "cc8e95b4-c24f-4d68-ba54-8bed76f63930" + ) ); } _ => assert!(false), @@ -1195,7 +1207,7 @@ mod tests { msgid: 1, base: "".to_string(), scope: LdapSearchScope::Base, - filter: LdapFilter::Present("objectclass".to_string()), + filter: LdapFilter::Present(Attribute::ObjectClass.to_string()), attrs: vec!["*".to_string()], }; let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); @@ -1227,7 +1239,7 @@ mod tests { let me_posix = ModifyEvent::new_internal_invalid( filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))), ModifyList::new_purge_and_set( - "domain_ldap_basedn", + Attribute::DomainLdapBasedn, Value::new_iutf8("o=kanidmproject"), ), ); @@ -1245,7 +1257,7 @@ mod tests { msgid: 1, base: "".to_string(), scope: LdapSearchScope::Base, - filter: LdapFilter::Present("objectclass".to_string()), + filter: LdapFilter::Present(Attribute::ObjectClass.to_string()), attrs: vec!["*".to_string()], }; let r1 = ldaps.do_search(idms, &sr, &anon_t).await.unwrap(); diff --git a/server/lib/src/idm/oauth2.rs b/server/lib/src/idm/oauth2.rs index 71f6f9a1d..70b5cec81 100644 --- a/server/lib/src/idm/oauth2.rs +++ b/server/lib/src/idm/oauth2.rs @@ -317,20 +317,20 @@ impl<'a> Oauth2ResourceServersWriteTransaction<'a> { let uuid = ent.get_uuid(); admin_info!(?uuid, "Checking oauth2 configuration"); // From each entry, attempt to make an oauth2 configuration. - if !ent.attribute_equality(Attribute::Class.as_ref(), &EntryClass::OAuth2ResourceServer.into()) { + if !ent.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) { admin_error!("Missing class oauth2_resource_server"); // Check we have oauth2_resource_server class return Err(OperationError::InvalidEntryState); } - let type_ = if ent.attribute_equality(Attribute::Class.as_ref(), &EntryClass::OAuth2ResourceServerBasic.into()) { + let type_ = if ent.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServerBasic.into()) { let authz_secret = ent - .get_ava_single_secret(ATTR_OAUTH2_RS_BASIC_SECRET) + .get_ava_single_secret(Attribute::OAuth2RsBasicSecret) .map(str::to_string) .ok_or(OperationError::InvalidValueState)?; let enable_pkce = ent - .get_ava_single_bool(Attribute::OAuth2AllowInsecureClientDisablePkce.as_ref()) + .get_ava_single_bool(Attribute::OAuth2AllowInsecureClientDisablePkce) .map(|e| !e) .unwrap_or(true); @@ -338,7 +338,7 @@ impl<'a> Oauth2ResourceServersWriteTransaction<'a> { authz_secret, enable_pkce, } - } else if ent.attribute_equality(Attribute::Class.as_ref(), &EntryClass::OAuth2ResourceServerPublic.into()) { + } else if ent.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServerPublic.into()) { OauthRSType::Public } else { error!("Missing class determining oauth2 rs type"); @@ -347,22 +347,22 @@ impl<'a> Oauth2ResourceServersWriteTransaction<'a> { // Now we know we can load the shared attrs. let name = ent - .get_ava_single_iname("oauth2_rs_name") + .get_ava_single_iname(Attribute::OAuth2RsName) .map(str::to_string) .ok_or(OperationError::InvalidValueState)?; let displayname = ent - .get_ava_single_utf8("displayname") + .get_ava_single_utf8(Attribute::DisplayName) .map(str::to_string) .ok_or(OperationError::InvalidValueState)?; let (origin, origin_https) = ent - .get_ava_single_url(Attribute::OAuth2RsOrigin.as_ref()) + .get_ava_single_url(Attribute::OAuth2RsOrigin) .map(|url| (url.origin(), url.scheme() == "https")) .ok_or(OperationError::InvalidValueState)?; let landing_valid = ent - .get_ava_single_url("oauth2_rs_origin_landing") + .get_ava_single_url(Attribute::OAuth2RsOriginLanding) .map(|url| url.origin() == origin). unwrap_or(true); @@ -371,27 +371,27 @@ impl<'a> Oauth2ResourceServersWriteTransaction<'a> { } let token_fernet = ent - .get_ava_single_secret("oauth2_rs_token_key") + .get_ava_single_secret(Attribute::OAuth2RsTokenKey) .ok_or(OperationError::InvalidValueState) .and_then(|key| { Fernet::new(key).ok_or(OperationError::CryptographyError) })?; let scope_maps = ent - .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap.as_ref()) + .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap) .cloned() .unwrap_or_default(); let sup_scope_maps = ent - .get_ava_as_oauthscopemaps(Attribute::OAuth2RsSupScopeMap.as_ref()) + .get_ava_as_oauthscopemaps(Attribute::OAuth2RsSupScopeMap) .cloned() .unwrap_or_default(); trace!("{}", Attribute::OAuth2JwtLegacyCryptoEnable.as_ref()); - let jws_signer = if ent.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable.as_ref()).unwrap_or(false) { - trace!("rs256_private_key_der"); + let jws_signer = if ent.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable).unwrap_or(false) { + trace!("{}", Attribute::Rs256PrivateKeyDer); ent - .get_ava_single_private_binary("rs256_private_key_der") + .get_ava_single_private_binary(Attribute::Rs256PrivateKeyDer) .ok_or(OperationError::InvalidValueState) .and_then(|key_der| { JwsSigner::from_rs256_der(key_der).map_err(|e| { @@ -400,9 +400,9 @@ impl<'a> Oauth2ResourceServersWriteTransaction<'a> { }) })? } else { - trace!("es256_private_key_der"); + trace!("{}", Attribute::Es256PrivateKeyDer); ent - .get_ava_single_private_binary("es256_private_key_der") + .get_ava_single_private_binary(Attribute::Es256PrivateKeyDer) .ok_or(OperationError::InvalidValueState) .and_then(|key_der| { JwsSigner::from_es256_der(key_der).map_err(|e| { @@ -413,7 +413,7 @@ impl<'a> Oauth2ResourceServersWriteTransaction<'a> { }; let prefer_short_username = ent - .get_ava_single_bool(Attribute::OAuth2PreferShortUsername.as_ref()) + .get_ava_single_bool(Attribute::OAuth2PreferShortUsername) .unwrap_or(false); let mut authorization_endpoint = self.inner.origin.clone(); @@ -914,7 +914,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // Check the not issued before of the session relative to this refresh iat let oauth2_session = entry - .get_ava_as_oauth2session_map("oauth2_session") + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .and_then(|map| map.get(&session_id)) .ok_or_else(|| { security_info!( @@ -1147,7 +1147,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // NOTE: Oauth2_session has special handling that allows update in place without // the remove step needing to be carried out. // Modify::Removed("oauth2_session".into(), PartialValue::Refer(session_id)), - Modify::Present("oauth2_session".into(), session), + Modify::Present(Attribute::OAuth2Session.into(), session), ]); self.qs_write @@ -2119,7 +2119,7 @@ mod tests { .internal_search_uuid(uuid) .expect("Failed to retrieve oauth2 resource entry "); let secret = entry - .get_ava_single_secret("oauth2_rs_basic_secret") + .get_ava_single_secret(Attribute::OAuth2RsBasicSecret) .map(str::to_string) .expect("No oauth2_rs_basic_secret found"); @@ -2164,9 +2164,9 @@ mod tests { // Mod the user let modlist = ModifyList::new_list(vec![ - Modify::Present("user_auth_token_session".into(), session), + Modify::Present(Attribute::UserAuthTokenSession.into(), session), Modify::Present( - "primary_credential".into(), + Attribute::PrimaryCredential.into(), Value::Cred("primary".to_string(), cred), ), ]); @@ -2286,9 +2286,9 @@ mod tests { // Mod the user let modlist = ModifyList::new_list(vec![ - Modify::Present("user_auth_token_session".into(), session), + Modify::Present(Attribute::UserAuthTokenSession.into(), session), Modify::Present( - "primary_credential".into(), + Attribute::PrimaryCredential.into(), Value::Cred("primary".to_string(), cred), ), ]); @@ -3228,7 +3228,7 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("failed"); let valid = entry - .get_ava_as_oauth2session_map("oauth2_session") + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .map(|map| map.get(&session_id).is_some()) .unwrap_or(false); assert!(valid); @@ -3250,7 +3250,7 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("failed"); let revoked = entry - .get_ava_as_oauth2session_map("oauth2_session") + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .and_then(|sessions| sessions.get(&session_id)) .map(|session| matches!(session.state, SessionState::RevokedAt(_))) .unwrap_or(false); @@ -4805,7 +4805,7 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("failed"); let valid = entry - .get_ava_as_oauth2session_map("oauth2_session") + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .and_then(|sessions| sessions.first_key_value()) .map(|(_, session)| !matches!(session.state, SessionState::RevokedAt(_))) // If there is no map, then something is wrong. diff --git a/server/lib/src/idm/radius.rs b/server/lib/src/idm/radius.rs index 1e606b977..b5f1060f0 100644 --- a/server/lib/src/idm/radius.rs +++ b/server/lib/src/idm/radius.rs @@ -24,40 +24,49 @@ impl RadiusAccount { value: &Entry, qs: &mut QueryServerReadTransaction, ) -> Result { - if !value.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Account.into()) { + if !value.attribute_equality(Attribute::Class, &EntryClass::Account.into()) { return Err(OperationError::InvalidAccountState( "Missing class: account".to_string(), )); } let radius_secret = value - .get_ava_single_secret("radius_secret") + .get_ava_single_secret(Attribute::RadiusSecret) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: radius_secret".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::RadiusSecret + )) })? .to_string(); let name = value - .get_ava_single_iname(Attribute::Name.as_ref()) + .get_ava_single_iname(Attribute::Name) .map(|s| s.to_string()) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: name".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Name + )) })?; let uuid = value.get_uuid(); let displayname = value - .get_ava_single_utf8("displayname") + .get_ava_single_utf8(Attribute::DisplayName) .map(|s| s.to_string()) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: displayname".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::DisplayName + )) })?; let groups = Group::try_from_account_entry_red_ro(value, qs)?; - let valid_from = value.get_ava_single_datetime("account_valid_from"); + let valid_from = value.get_ava_single_datetime(Attribute::AccountValidFrom); - let expire = value.get_ava_single_datetime("account_expire"); + let expire = value.get_ava_single_datetime(Attribute::AccountExpire); Ok(RadiusAccount { name, diff --git a/server/lib/src/idm/reauth.rs b/server/lib/src/idm/reauth.rs index 4afc98558..2d1d4d623 100644 --- a/server/lib/src/idm/reauth.rs +++ b/server/lib/src/idm/reauth.rs @@ -44,7 +44,7 @@ impl<'a> IdmServerAuthTransaction<'a> { // Check that the entry/session can be re-authed. let session = entry - .get_ava_as_session_map("user_auth_token_session") + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&ident.session_id)) .ok_or_else(|| { error!("Ident session is not present in entry. Perhaps replication is delayed?"); diff --git a/server/lib/src/idm/scim.rs b/server/lib/src/idm/scim.rs index 71909f7cf..1c3d65beb 100644 --- a/server/lib/src/idm/scim.rs +++ b/server/lib/src/idm/scim.rs @@ -28,28 +28,29 @@ pub(crate) struct SyncAccount { macro_rules! try_from_entry { ($value:expr) => {{ // Check the classes - if !$value.attribute_equality(Attribute::Class.into(), &EntryClass::SyncAccount.into()) { + if !$value.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into()) { return Err(OperationError::InvalidAccountState( "Missing class: sync account".to_string(), )); } let name = $value - .get_ava_single_iname(Attribute::Name.as_ref()) + .get_ava_single_iname(Attribute::Name) .map(|s| s.to_string()) .ok_or(OperationError::InvalidAccountState( "Missing attribute: name".to_string(), ))?; let jws_key = $value - .get_ava_single_jws_key_es256("jws_es256_private_key") + .get_ava_single_jws_key_es256(Attribute::JwsEs256PrivateKey) .cloned() - .ok_or(OperationError::InvalidAccountState( - "Missing attribute: jws_es256_private_key".to_string(), - ))?; + .ok_or(OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::JwsEs256PrivateKey + )))?; let sync_tokens = $value - .get_ava_as_apitoken_map("sync_token_session") + .get_ava_as_apitoken_map(Attribute::SyncTokenSession) .cloned() .unwrap_or_default(); @@ -83,7 +84,7 @@ impl SyncAccount { // Get the sessions. There are no gracewindows on sync, we are much stricter. let session_present = entry - .get_ava_as_apitoken_map("sync_token_session") + .get_ava_as_apitoken_map(Attribute::SyncTokenSession) .map(|session_map| session_map.get(&sst.token_id).is_some()) .unwrap_or(false); @@ -310,13 +311,19 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { if existing_entries { // Now modify these to remove their sync related attributes. let schema = self.qs_write.get_schema(); - let sync_class = schema.get_classes().get("sync_object").ok_or_else(|| { - error!("Failed to access sync_object class, schema corrupt"); - OperationError::InvalidState - })?; + let sync_class = schema + .get_classes() + .get(EntryClass::SyncObject.into()) + .ok_or_else(|| { + error!( + "Failed to access {} class, schema corrupt", + EntryClass::SyncObject + ); + OperationError::InvalidState + })?; let modlist = std::iter::once(Modify::Removed( - "class".into(), + Attribute::Class.into(), EntryClass::SyncObject.into(), )) .chain( @@ -433,13 +440,19 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // Now modify these to remove their sync related attributes. let schema = self.qs_write.get_schema(); - let sync_class = schema.get_classes().get("sync_object").ok_or_else(|| { - error!("Failed to access sync_object class, schema corrupt"); - OperationError::InvalidState - })?; + let sync_class = schema + .get_classes() + .get(EntryClass::SyncObject.into()) + .ok_or_else(|| { + error!( + "Failed to access {} class, schema corrupt", + EntryClass::SyncObject + ); + OperationError::InvalidState + })?; let modlist = std::iter::once(Modify::Removed( - "class".into(), + Attribute::Class.into(), EntryClass::SyncObject.into(), )) .chain( @@ -552,7 +565,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { match ( &changes.from_state, - sync_entry.get_ava_single_private_binary("sync_cookie"), + sync_entry.get_ava_single_private_binary(Attribute::SyncCookie), ) { (ScimSyncState::Refresh, _) => { // valid @@ -581,7 +594,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // Get the sync authority set from the entry. let sync_authority_set = sync_entry - .get_ava_as_iutf8("sync_yield_authority") + .get_ava_as_iutf8(Attribute::SyncYieldAuthority) .cloned() .unwrap_or_default(); @@ -696,11 +709,14 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { *u, ModifyList::new_list(vec![ Modify::Assert( - "sync_parent_uuid".into(), + Attribute::SyncParentUuid, PartialValue::Refer(sync_uuid), ), - Modify::Purged("sync_external_id".into()), - Modify::Present("sync_external_id".into(), Value::new_iutf8(ext_id)), + Modify::Purged(Attribute::SyncExternalId.into()), + Modify::Present( + Attribute::SyncExternalId.into(), + Value::new_iutf8(ext_id), + ), ]), ) }) @@ -1133,17 +1149,17 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { let mut mods = Vec::new(); mods.push(Modify::Assert( - "sync_parent_uuid".into(), + Attribute::SyncParentUuid, PartialValue::Refer(sync_uuid), )); for req_class in requested_classes.keys() { mods.push(Modify::Present( - "sync_class".into(), + Attribute::SyncClass.into(), Value::new_iutf8(req_class), )); mods.push(Modify::Present( - EntryClass::Class.into(), + Attribute::Class.into(), Value::new_iutf8(req_class), )); } @@ -1376,7 +1392,9 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { if ent.mask_recycled_ts().is_none() { debug!("Skipping already deleted entry {}", ent.get_uuid()); None - } else if ent.get_ava_single_refer("sync_parent_uuid") != Some(sync_uuid) { + } else if ent.get_ava_single_refer(Attribute::SyncParentUuid) + != Some(sync_uuid) + { warn!( "Skipping entry that is not within sync control {}", ent.get_uuid() @@ -1429,10 +1447,11 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { // to reflect the new sync state. let modlist = match to_state { - ScimSyncState::Active { cookie } => { - ModifyList::new_purge_and_set("sync_cookie", Value::PrivateBinary(cookie.0.clone())) - } - ScimSyncState::Refresh => ModifyList::new_purge("sync_cookie"), + ScimSyncState::Active { cookie } => ModifyList::new_purge_and_set( + Attribute::SyncCookie, + Value::PrivateBinary(cookie.0.clone()), + ), + ScimSyncState::Refresh => ModifyList::new_purge(Attribute::SyncCookie), }; self.qs_write @@ -1478,7 +1497,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> { let sync_entry = self.qs_read.internal_search_uuid(sync_uuid)?; Ok( - match sync_entry.get_ava_single_private_binary("sync_cookie") { + match sync_entry.get_ava_single_private_binary(Attribute::SyncCookie) { Some(b) => ScimSyncState::Active { cookie: Base64UrlSafeData(b.to_vec()), }, @@ -1646,9 +1665,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("test_scim_sync") )), - ModifyList::new_list(vec![Modify::Purged(AttrString::from( - "jws_es256_private_key", - ))]), + ModifyList::new_list(vec![Modify::Purged(Attribute::JwsEs256PrivateKey.into())]), ); assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok()); assert!(idms_prox_write.commit().is_ok()); @@ -1666,12 +1683,12 @@ mod tests { .expect("Unable to access sync entry"); let jws_key = sync_entry - .get_ava_single_jws_key_es256("jws_es256_private_key") + .get_ava_single_jws_key_es256(Attribute::JwsEs256PrivateKey) .cloned() .expect("Missing attribute: jws_es256_private_key"); let sync_tokens = sync_entry - .get_ava_as_apitoken_map("sync_token_session") + .get_ava_as_apitoken_map(Attribute::SyncTokenSession) .cloned() .unwrap_or_default(); @@ -1804,7 +1821,7 @@ mod tests { .expect("Failed to access sync stub entry"); assert!( - synced_entry.get_ava_single_iutf8("sync_external_id") + synced_entry.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("dn=william,ou=people,dc=test") ); assert!(synced_entry.get_uuid() == user_sync_uuid); @@ -1933,9 +1950,10 @@ mod tests { .internal_search_uuid(user_sync_uuid) .expect("Unable to access entry"); - assert!(ent.get_ava_single_iname(Attribute::Name.as_ref()) == Some("testgroup")); + assert!(ent.get_ava_single_iname(Attribute::Name) == Some("testgroup")); assert!( - ent.get_ava_single_iutf8("sync_external_id") == Some("cn=testgroup,ou=people,dc=test") + ent.get_ava_single_iutf8(Attribute::SyncExternalId) + == Some("cn=testgroup,ou=people,dc=test") ); assert!(idms_prox_write.commit().is_ok()); @@ -2028,7 +2046,7 @@ mod tests { ScimAttr::SingleSimple(ScimSimpleAttr::String("testgroup".to_string())) ), ( - "class".to_string(), + Attribute::Class.to_string(), ScimAttr::SingleSimple(ScimSimpleAttr::String("posixgroup".to_string())) ) ), @@ -2491,7 +2509,7 @@ mod tests { .internal_search_uuid(sync_uuid_a) .expect("Unable to access entry"); - assert!(ent.get_ava_single_iname(Attribute::Name.as_ref()) == Some("testgroup")); + assert!(ent.get_ava_single_iname(Attribute::Name) == Some("testgroup")); assert!(idms_prox_write.commit().is_ok()); } @@ -2588,48 +2606,58 @@ mod tests { let testgroup = get_single_entry("testgroup", &mut idms_prox_write); assert!( - testgroup.get_ava_single_iutf8("sync_external_id") + testgroup.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); + assert!(testgroup + .get_ava_single_uint32(Attribute::GidNumber) + .is_none()); let testposix = get_single_entry("testposix", &mut idms_prox_write); assert!( - testposix.get_ava_single_iutf8("sync_external_id") + testposix.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); + assert!(testposix.get_ava_single_uint32(Attribute::GidNumber) == Some(1234567)); let testexternal = get_single_entry("testexternal", &mut idms_prox_write); assert!( - testexternal.get_ava_single_iutf8("sync_external_id") + testexternal.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); + assert!(testexternal + .get_ava_single_uint32(Attribute::GidNumber) + .is_none()); let testuser = get_single_entry("testuser", &mut idms_prox_write); assert!( - testuser.get_ava_single_iutf8("sync_external_id") + testuser.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); - assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); - assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); + assert!(testuser.get_ava_single_uint32(Attribute::GidNumber) == Some(12345)); + assert!(testuser.get_ava_single_utf8(Attribute::DisplayName) == Some("Test User")); + assert!(testuser.get_ava_single_iutf8(Attribute::LoginShell) == Some("/bin/sh")); let mut ssh_keyiter = testuser - .get_ava_iter_sshpubkeys(Attribute::SshPublicKey.into()) + .get_ava_iter_sshpubkeys(Attribute::SshPublicKey) .expect("Failed to access ssh pubkeys"); assert_eq!(ssh_keyiter.next(), Some("sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBENubZikrb8hu+HeVRdZ0pp/VAk2qv4JDbuJhvD0yNdWDL2e3cBbERiDeNPkWx58Q4rVnxkbV1fa8E2waRtT91wAAAAEc3NoOg== testuser@fidokey")); assert_eq!(ssh_keyiter.next(), None); // Check memberof works. - let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); + let testgroup_mb = testgroup + .get_ava_refer(Attribute::Member) + .expect("No members!"); assert!(testgroup_mb.contains(&testuser.get_uuid())); - let testposix_mb = testposix.get_ava_refer("member").expect("No members!"); + let testposix_mb = testposix + .get_ava_refer(Attribute::Member) + .expect("No members!"); assert!(testposix_mb.contains(&testuser.get_uuid())); - let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); + let testuser_mo = testuser + .get_ava_refer(Attribute::MemberOf) + .expect("No memberof!"); assert!(testuser_mo.contains(&testposix.get_uuid())); assert!(testuser_mo.contains(&testgroup.get_uuid())); @@ -2659,28 +2687,34 @@ mod tests { let testposix = get_single_entry("testposix", &mut idms_prox_write); info!("{:?}", testposix); assert!( - testposix.get_ava_single_iutf8("sync_external_id") + testposix.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); + assert!(testposix.get_ava_single_uint32(Attribute::GidNumber) == Some(1234567)); let testexternal = get_single_entry("testexternal2", &mut idms_prox_write); info!("{:?}", testexternal); assert!( - testexternal.get_ava_single_iutf8("sync_external_id") + testexternal.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testexternal2,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); + assert!(testexternal + .get_ava_single_uint32(Attribute::GidNumber) + .is_none()); let testuser = get_single_entry("testuser", &mut idms_prox_write); // Check memberof works. - let testexternal_mb = testexternal.get_ava_refer("member").expect("No members!"); + let testexternal_mb = testexternal + .get_ava_refer(Attribute::Member) + .expect("No members!"); assert!(testexternal_mb.contains(&testuser.get_uuid())); - assert!(testposix.get_ava_refer("member").is_none()); + assert!(testposix.get_ava_refer(Attribute::Member).is_none()); - let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); + let testuser_mo = testuser + .get_ava_refer(Attribute::MemberOf) + .expect("No memberof!"); assert!(testuser_mo.contains(&testexternal.get_uuid())); assert!(idms_prox_write.commit().is_ok()); @@ -2712,42 +2746,52 @@ mod tests { // Check entries still remain as expected. let testgroup = get_single_entry("testgroup", &mut idms_prox_write); assert!( - testgroup.get_ava_single_iutf8("sync_external_id") + testgroup.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); + assert!(testgroup + .get_ava_single_uint32(Attribute::GidNumber) + .is_none()); let testposix = get_single_entry("testposix", &mut idms_prox_write); assert!( - testposix.get_ava_single_iutf8("sync_external_id") + testposix.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testposix,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testposix.get_ava_single_uint32("gidnumber") == Some(1234567)); + assert!(testposix.get_ava_single_uint32(Attribute::GidNumber) == Some(1234567)); let testexternal = get_single_entry("testexternal", &mut idms_prox_write); assert!( - testexternal.get_ava_single_iutf8("sync_external_id") + testexternal.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testexternal,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testexternal.get_ava_single_uint32("gidnumber").is_none()); + assert!(testexternal + .get_ava_single_uint32(Attribute::GidNumber) + .is_none()); let testuser = get_single_entry("testuser", &mut idms_prox_write); assert!( - testuser.get_ava_single_iutf8("sync_external_id") + testuser.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); - assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); - assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); + assert!(testuser.get_ava_single_uint32(Attribute::GidNumber) == Some(12345)); + assert!(testuser.get_ava_single_utf8(Attribute::DisplayName) == Some("Test User")); + assert!(testuser.get_ava_single_iutf8(Attribute::LoginShell) == Some("/bin/sh")); // Check memberof works. - let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); + let testgroup_mb = testgroup + .get_ava_refer(Attribute::Member) + .expect("No members!"); assert!(testgroup_mb.contains(&testuser.get_uuid())); - let testposix_mb = testposix.get_ava_refer("member").expect("No members!"); + let testposix_mb = testposix + .get_ava_refer(Attribute::Member) + .expect("No members!"); assert!(testposix_mb.contains(&testuser.get_uuid())); - let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); + let testuser_mo = testuser + .get_ava_refer(Attribute::MemberOf) + .expect("No memberof!"); assert!(testuser_mo.contains(&testposix.get_uuid())); assert!(testuser_mo.contains(&testgroup.get_uuid())); @@ -2778,25 +2822,31 @@ mod tests { let testgroup = get_single_entry("testgroup", &mut idms_prox_write); assert!( - testgroup.get_ava_single_iutf8("sync_external_id") + testgroup.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("cn=testgroup,cn=groups,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testgroup.get_ava_single_uint32("gidnumber").is_none()); + assert!(testgroup + .get_ava_single_uint32(Attribute::GidNumber) + .is_none()); let testuser = get_single_entry("testuser", &mut idms_prox_write); assert!( - testuser.get_ava_single_iutf8("sync_external_id") + testuser.get_ava_single_iutf8(Attribute::SyncExternalId) == Some("uid=testuser,cn=users,cn=accounts,dc=dev,dc=blackhats,dc=net,dc=au") ); - assert!(testuser.get_ava_single_uint32("gidnumber") == Some(12345)); - assert!(testuser.get_ava_single_utf8("displayname") == Some("Test User")); - assert!(testuser.get_ava_single_iutf8("loginshell") == Some("/bin/sh")); + assert!(testuser.get_ava_single_uint32(Attribute::GidNumber) == Some(12345)); + assert!(testuser.get_ava_single_utf8(Attribute::DisplayName) == Some("Test User")); + assert!(testuser.get_ava_single_iutf8(Attribute::LoginShell) == Some("/bin/sh")); // Check memberof works. - let testgroup_mb = testgroup.get_ava_refer("member").expect("No members!"); + let testgroup_mb = testgroup + .get_ava_refer(Attribute::Member) + .expect("No members!"); assert!(testgroup_mb.contains(&testuser.get_uuid())); - let testuser_mo = testuser.get_ava_refer("memberof").expect("No memberof!"); + let testuser_mo = testuser + .get_ava_refer(Attribute::MemberOf) + .expect("No memberof!"); assert!(testuser_mo.contains(&testgroup.get_uuid())); assert!(idms_prox_write.commit().is_ok()); @@ -2823,8 +2873,8 @@ mod tests { .internal_modify_uuid( sync_uuid, &ModifyList::new_purge_and_set( - "sync_yield_authority", - Value::new_iutf8("legalname") + Attribute::SyncYieldAuthority, + Value::new_iutf8(Attribute::LegalName.as_ref()) ) ) .is_ok()); @@ -2837,7 +2887,7 @@ mod tests { .internal_modify( &testuser_filter, &ModifyList::new_purge_and_set( - "legalname", + Attribute::LegalName, Value::Utf8("Test Userington the First".to_string()) ) ) @@ -2855,7 +2905,9 @@ mod tests { .map(|mut results| results.pop().expect("Empty result set")) .expect("Failed to access testuser"); - assert!(testuser.get_ava_single_utf8("legalname") == Some("Test Userington the First")); + assert!( + testuser.get_ava_single_utf8(Attribute::LegalName) == Some("Test Userington the First") + ); assert!(idms_prox_write.commit().is_ok()); } @@ -2894,21 +2946,16 @@ mod tests { // Check that the entries still exists but now have no sync_object attached. let testgroup = get_single_entry("testgroup", &mut idms_prox_write); - assert!(!testgroup - .attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncObject.into())); + assert!(!testgroup.attribute_equality(Attribute::Class, &EntryClass::SyncObject.into())); let testposix = get_single_entry("testposix", &mut idms_prox_write); - assert!(!testposix - .attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncObject.into())); + assert!(!testposix.attribute_equality(Attribute::Class, &EntryClass::SyncObject.into())); let testexternal = get_single_entry("testexternal", &mut idms_prox_write); - assert!(!testexternal - .attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncObject.into())); + assert!(!testexternal.attribute_equality(Attribute::Class, &EntryClass::SyncObject.into())); let testuser = get_single_entry("testuser", &mut idms_prox_write); - assert!( - !testuser.attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncObject.into()) - ); + assert!(!testuser.attribute_equality(Attribute::Class, &EntryClass::SyncObject.into())); assert!(idms_prox_write.commit().is_ok()); } @@ -2954,13 +3001,10 @@ mod tests { // Check that the entries still exists but now have no sync_object attached. let testgroup = get_single_entry("testgroup", &mut idms_prox_write); - assert!(!testgroup - .attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncObject.into())); + assert!(!testgroup.attribute_equality(Attribute::Class, &EntryClass::SyncObject.into())); let testuser = get_single_entry("testuser", &mut idms_prox_write); - assert!( - !testuser.attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncObject.into()) - ); + assert!(!testuser.attribute_equality(Attribute::Class, &EntryClass::SyncObject.into())); for iname in ["testposix", "testexternal"] { trace!(%iname); diff --git a/server/lib/src/idm/server.rs b/server/lib/src/idm/server.rs index db468c7b7..4abbccc5d 100644 --- a/server/lib/src/idm/server.rs +++ b/server/lib/src/idm/server.rs @@ -568,7 +568,7 @@ pub trait IdmServerTransaction<'a> { })?; let user_signer = entry - .get_ava_single_jws_key_es256("jws_es256_private_key") + .get_ava_single_jws_key_es256(Attribute::JwsEs256PrivateKey) .ok_or_else(|| { admin_error!( ?kid, @@ -657,10 +657,10 @@ pub trait IdmServerTransaction<'a> { let within_valid_window = Account::check_within_valid_time( ct, entry - .get_ava_single_datetime(Attribute::AccountValidFrom.as_ref()) + .get_ava_single_datetime(Attribute::AccountValidFrom) .as_ref(), entry - .get_ava_single_datetime(Attribute::AccountExpire.as_ref()) + .get_ava_single_datetime(Attribute::AccountExpire) .as_ref(), ); @@ -676,10 +676,10 @@ pub trait IdmServerTransaction<'a> { let grace_valid = ct < (Duration::from_secs(iat as u64) + GRACE_WINDOW); let oauth2_session = entry - .get_ava_as_oauth2session_map(Attribute::OAuth2Session.as_ref()) + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .and_then(|sessions| sessions.get(&session_id)); let uat_session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.as_ref()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&parent_session_id)); if let Some(oauth2_session) = oauth2_session { @@ -848,8 +848,12 @@ pub trait IdmServerTransaction<'a> { if Account::check_within_valid_time( ct, - entry.get_ava_single_datetime("account_valid_from").as_ref(), - entry.get_ava_single_datetime("account_expire").as_ref(), + entry + .get_ava_single_datetime(Attribute::AccountValidFrom) + .as_ref(), + entry + .get_ava_single_datetime(Attribute::AccountExpire) + .as_ref(), ) { // Good to go let limits = Limits::default(); @@ -922,7 +926,7 @@ pub trait IdmServerTransaction<'a> { })?; let user_signer = entry - .get_ava_single_jws_key_es256("jws_es256_private_key") + .get_ava_single_jws_key_es256(Attribute::JwsEs256PrivateKey) .ok_or_else(|| { admin_error!( ?kid, @@ -1334,7 +1338,7 @@ impl<'a> IdmServerAuthTransaction<'a> { } Token::ApiToken(apit, entry) => { let spn = entry - .get_ava_single_proto_string(Attribute::Spn.as_ref()) + .get_ava_single_proto_string(Attribute::Spn) .ok_or_else(|| { OperationError::InvalidAccountState("Missing attribute: spn".to_string()) })?; @@ -1844,9 +1848,9 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { let vcred = Value::new_credential("primary", ncred); // We need to remove other credentials too. let modlist = ModifyList::new_list(vec![ - m_purge("passkeys"), - m_purge("primary_credential"), - Modify::Present("primary_credential".into(), vcred), + m_purge(Attribute::PassKeys), + m_purge(Attribute::PrimaryCredential), + Modify::Present(Attribute::PrimaryCredential.into(), vcred), ]); trace!(?modlist, "processing change"); @@ -2041,7 +2045,7 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { info!(session_id = %asr.session_id, "Persisting auth session"); // modify the account to put the session onto it. - let modlist = ModifyList::new_append("user_auth_token_session", session); + let modlist = ModifyList::new_append(Attribute::UserAuthTokenSession, session); self.qs_write .internal_modify( @@ -2880,7 +2884,7 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("Can't access admin entry."); let cred_before = admin_entry - .get_ava_single_credential("primary_credential") + .get_ava_single_credential(Attribute::PrimaryCredential) .expect("No credential present") .clone(); drop(idms_prox_read); @@ -2911,7 +2915,7 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("Can't access admin entry."); let cred_after = admin_entry - .get_ava_single_credential("primary_credential") + .get_ava_single_credential(Attribute::PrimaryCredential) .expect("No credential present") .clone(); drop(idms_prox_read); @@ -3549,7 +3553,7 @@ mod tests { .qs_read .internal_search_uuid(UUID_ADMIN) .expect("failed"); - let sessions = admin.get_ava_as_session_map("user_auth_token_session"); + let sessions = admin.get_ava_as_session_map(Attribute::UserAuthTokenSession); assert!(sessions.is_none()); drop(idms_prox_read); @@ -3574,7 +3578,7 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("failed"); let sessions = admin - .get_ava_as_session_map("user_auth_token_session") + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .expect("Sessions must be present!"); assert!(sessions.len() == 1); let session_data_a = sessions.get(&session_a).expect("Session A is missing!"); @@ -3604,7 +3608,7 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("failed"); let sessions = admin - .get_ava_as_session_map("user_auth_token_session") + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .expect("Sessions must be present!"); trace!(?sessions); assert!(sessions.len() == 2); diff --git a/server/lib/src/idm/serviceaccount.rs b/server/lib/src/idm/serviceaccount.rs index b3d72a426..783d3e701 100644 --- a/server/lib/src/idm/serviceaccount.rs +++ b/server/lib/src/idm/serviceaccount.rs @@ -32,36 +32,32 @@ use crate::value::ApiToken; macro_rules! try_from_entry { ($value:expr) => {{ // Check the classes - if !$value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::ServiceAccount.into(), - ) { + if !$value.attribute_equality(Attribute::Class, &EntryClass::ServiceAccount.into()) { return Err(OperationError::InvalidAccountState( "Missing class: service account".to_string(), )); } - let spn = $value - .get_ava_single_proto_string(Attribute::Spn.as_ref()) - .ok_or(OperationError::InvalidAccountState( - "Missing attribute: spn".to_string(), - ))?; + let spn = $value.get_ava_single_proto_string(Attribute::Spn).ok_or( + OperationError::InvalidAccountState(format!("Missing attribute: {}", Attribute::Spn)), + )?; let jws_key = $value - .get_ava_single_jws_key_es256("jws_es256_private_key") + .get_ava_single_jws_key_es256(Attribute::JwsEs256PrivateKey) .cloned() - .ok_or(OperationError::InvalidAccountState( - "Missing attribute: jws_es256_private_key".to_string(), - ))?; + .ok_or(OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::JwsEs256PrivateKey + )))?; let api_tokens = $value - .get_ava_as_apitoken_map("api_token_session") + .get_ava_as_apitoken_map(Attribute::ApiTokenSession) .cloned() .unwrap_or_default(); - let valid_from = $value.get_ava_single_datetime("account_valid_from"); + let valid_from = $value.get_ava_single_datetime(Attribute::AccountValidFrom); - let expire = $value.get_ava_single_datetime("account_expire"); + let expire = $value.get_ava_single_datetime(Attribute::AccountExpire); let uuid = $value.get_uuid().clone(); @@ -105,8 +101,12 @@ impl ServiceAccount { ) -> bool { let within_valid_window = Account::check_within_valid_time( ct, - entry.get_ava_single_datetime("account_valid_from").as_ref(), - entry.get_ava_single_datetime("account_expire").as_ref(), + entry + .get_ava_single_datetime(Attribute::AccountValidFrom) + .as_ref(), + entry + .get_ava_single_datetime(Attribute::AccountExpire) + .as_ref(), ); if !within_valid_window { @@ -116,7 +116,7 @@ impl ServiceAccount { // Get the sessions. let session_present = entry - .get_ava_as_apitoken_map("api_token_session") + .get_ava_as_apitoken_map(Attribute::ApiTokenSession) .map(|session_map| session_map.get(&apit.token_id).is_some()) .unwrap_or(false); @@ -335,9 +335,9 @@ impl<'a> IdmServerProxyWriteTransaction<'a> { let vcred = Value::new_credential("primary", ncred); // We need to remove other credentials too. let modlist = ModifyList::new_list(vec![ - m_purge("passkeys"), - m_purge("primary_credential"), - Modify::Present("primary_credential".into(), vcred), + m_purge(Attribute::PassKeys), + m_purge(Attribute::PrimaryCredential), + Modify::Present(Attribute::PrimaryCredential.into(), vcred), ]); trace!(?modlist, "processing change"); @@ -387,26 +387,27 @@ impl<'a> IdmServerProxyReadTransaction<'a> { .and_then(|e| { let account_id = e.get_uuid(); // From the entry, turn it into the value - e.get_ava_as_apitoken_map("api_token_session").map(|smap| { - smap.iter() - .map(|(u, s)| { - s.scope - .try_into() - .map(|purpose| ProtoApiToken { - account_id, - token_id: *u, - label: s.label.clone(), - expiry: s.expiry, - issued_at: s.issued_at, - purpose, - }) - .map_err(|e| { - admin_error!("Invalid api_token {}", u); - e - }) - }) - .collect::, _>>() - }) + e.get_ava_as_apitoken_map(Attribute::ApiTokenSession) + .map(|smap| { + smap.iter() + .map(|(u, s)| { + s.scope + .try_into() + .map(|purpose| ProtoApiToken { + account_id, + token_id: *u, + label: s.label.clone(), + expiry: s.expiry, + issued_at: s.issued_at, + purpose, + }) + .map_err(|e| { + admin_error!("Invalid api_token {}", u); + e + }) + }) + .collect::, _>>() + }) }) .unwrap_or_else(|| { // No matching entry? Return none. diff --git a/server/lib/src/idm/unix.rs b/server/lib/src/idm/unix.rs index 6299bf978..72a93de98 100644 --- a/server/lib/src/idm/unix.rs +++ b/server/lib/src/idm/unix.rs @@ -34,17 +34,14 @@ pub(crate) struct UnixUserAccount { macro_rules! try_from_entry { ($value:expr, $groups:expr) => {{ - if !$value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Account.to_partialvalue(), - ) { + if !$value.attribute_equality(Attribute::Class, &EntryClass::Account.to_partialvalue()) { return Err(OperationError::InvalidAccountState( "Missing class: account".to_string(), )); } if !$value.attribute_equality( - Attribute::Class.as_ref(), + Attribute::Class, &EntryClass::PosixAccount.to_partialvalue(), ) { return Err(OperationError::InvalidAccountState( @@ -53,56 +50,70 @@ macro_rules! try_from_entry { } let name = $value - .get_ava_single_iname(Attribute::Name.as_ref()) + .get_ava_single_iname(Attribute::Name) .map(|s| s.to_string()) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: name".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Name + )) })?; let spn = $value - .get_ava_single_proto_string(Attribute::Spn.as_ref()) + .get_ava_single_proto_string(Attribute::Spn) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: spn".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Spn + )) })?; let uuid = $value.get_uuid(); let displayname = $value - .get_ava_single_utf8("displayname") + .get_ava_single_utf8(Attribute::DisplayName) .map(|s| s.to_string()) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: displayname".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::DisplayName + )) })?; - let gidnumber = $value.get_ava_single_uint32("gidnumber").ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: gidnumber".to_string()) - })?; + let gidnumber = $value + .get_ava_single_uint32(Attribute::GidNumber) + .ok_or_else(|| { + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::GidNumber + )) + })?; let shell = $value - .get_ava_single_iutf8(ATTR_LOGINSHELL) + .get_ava_single_iutf8(Attribute::LoginShell) .map(|s| s.to_string()); let sshkeys = $value - .get_ava_iter_sshpubkeys(ATTR_LDAP_SSH_PUBLICKEY) + .get_ava_iter_sshpubkeys(Attribute::SshPublicKey) .map(|i| i.map(|s| s.to_string()).collect()) .unwrap_or_else(Vec::new); let cred = $value - .get_ava_single_credential("unix_password") + .get_ava_single_credential(Attribute::UnixPassword) .map(|v| v.clone()); let radius_secret = $value - .get_ava_single_secret("radius_secret") + .get_ava_single_secret(Attribute::RadiusSecret) .map(str::to_string); let mail = $value - .get_ava_iter_mail("mail") + .get_ava_iter_mail(Attribute::Mail) .map(|i| i.map(str::to_string).collect()) .unwrap_or_else(Vec::new); - let valid_from = $value.get_ava_single_datetime("account_valid_from"); + let valid_from = $value.get_ava_single_datetime(Attribute::AccountValidFrom); - let expire = $value.get_ava_single_datetime("account_expire"); + let expire = $value.get_ava_single_datetime(Attribute::AccountExpire); Ok(UnixUserAccount { name, @@ -183,7 +194,10 @@ impl UnixUserAccount { ) -> Result, OperationError> { let ncred = Credential::new_password_only(crypto_policy, cleartext)?; let vcred = Value::new_credential("unix", ncred); - Ok(ModifyList::new_purge_and_set("unix_password", vcred)) + Ok(ModifyList::new_purge_and_set( + Attribute::UnixPassword, + vcred, + )) } pub(crate) fn gen_password_upgrade_mod( @@ -196,7 +210,10 @@ impl UnixUserAccount { Some(ucred) => { if let Some(ncred) = ucred.upgrade_password(crypto_policy, cleartext)? { let vcred = Value::new_credential("primary", ncred); - Ok(Some(ModifyList::new_purge_and_set("unix_password", vcred))) + Ok(Some(ModifyList::new_purge_and_set( + Attribute::UnixPassword, + vcred, + ))) } else { // No action, not the same pw Ok(None) @@ -314,42 +331,56 @@ macro_rules! try_from_group_e { ($value:expr) => {{ // We could be looking at a user for their UPG, OR a true group. - if !(($value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Account.to_partialvalue(), - ) && $value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::PosixAccount.to_partialvalue(), - )) || ($value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Group.to_partialvalue(), - ) && $value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::PosixGroup.to_partialvalue(), - ))) { - return Err(OperationError::InvalidAccountState( - "Missing class: account && posixaccount OR group && posixgroup".to_string(), - )); + if !(($value.attribute_equality(Attribute::Class, &EntryClass::Account.to_partialvalue()) + && $value.attribute_equality( + Attribute::Class, + &EntryClass::PosixAccount.to_partialvalue(), + )) + || ($value.attribute_equality(Attribute::Class, &EntryClass::Group.to_partialvalue()) + && $value.attribute_equality( + Attribute::Class, + &EntryClass::PosixGroup.to_partialvalue(), + ))) + { + return Err(OperationError::InvalidAccountState(format!( + "Missing {}: {} && {} OR {} && {}", + Attribute::Class, + Attribute::Account, + EntryClass::PosixAccount, + Attribute::Group, + EntryClass::PosixGroup, + ))); } let name = $value - .get_ava_single_iname(Attribute::Name.as_ref()) + .get_ava_single_iname(Attribute::Name) .map(|s| s.to_string()) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: name".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Name + )) })?; let spn = $value - .get_ava_single_proto_string(Attribute::Spn.as_ref()) + .get_ava_single_proto_string(Attribute::Spn) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: spn".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Spn + )) })?; let uuid = $value.get_uuid(); - let gidnumber = $value.get_ava_single_uint32("gidnumber").ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: gidnumber".to_string()) - })?; + let gidnumber = $value + .get_ava_single_uint32(Attribute::GidNumber) + .ok_or_else(|| { + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::GidNumber + )) + })?; Ok(UnixGroup { name, @@ -365,42 +396,52 @@ macro_rules! try_from_account_group_e { // First synthesise the self-group from the account. // We have already checked these, but paranoia is better than // complacency. - if !$value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Account.to_partialvalue(), - ) { - return Err(OperationError::InvalidAccountState( - "Missing class: account".to_string(), - )); + if !$value.attribute_equality(Attribute::Class, &EntryClass::Account.to_partialvalue()) { + return Err(OperationError::InvalidAccountState(format!( + "Missing class: {}", + EntryClass::Account + ))); } if !$value.attribute_equality( - Attribute::Class.as_ref(), + Attribute::Class, &EntryClass::PosixAccount.to_partialvalue(), ) { - return Err(OperationError::InvalidAccountState( - "Missing class: posixaccount".to_string(), - )); + return Err(OperationError::InvalidAccountState(format!( + "Missing class: {}", + EntryClass::PosixAccount + ))); } let name = $value - .get_ava_single_iname(Attribute::Name.as_ref()) + .get_ava_single_iname(Attribute::Name) .map(|s| s.to_string()) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: name".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Name + )) })?; let spn = $value - .get_ava_single_proto_string(Attribute::Spn.as_ref()) + .get_ava_single_proto_string(Attribute::Spn) .ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: spn".to_string()) + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::Spn + )) })?; let uuid = $value.get_uuid(); - let gidnumber = $value.get_ava_single_uint32("gidnumber").ok_or_else(|| { - OperationError::InvalidAccountState("Missing attribute: gidnumber".to_string()) - })?; + let gidnumber = $value + .get_ava_single_uint32(Attribute::GidNumber) + .ok_or_else(|| { + OperationError::InvalidAccountState(format!( + "Missing attribute: {}", + Attribute::GidNumber + )) + })?; // This is the user private group. let upg = UnixGroup { @@ -410,7 +451,7 @@ macro_rules! try_from_account_group_e { uuid, }; - match $value.get_ava_as_refuuid("memberof") { + match $value.get_ava_as_refuuid(Attribute::MemberOf) { Some(riter) => { let f = filter!(f_and!([ f_eq(Attribute::Class, EntryClass::PosixGroup.into()), diff --git a/server/lib/src/macros.rs b/server/lib/src/macros.rs index 4f6b802fe..1c9cc3c3c 100644 --- a/server/lib/src/macros.rs +++ b/server/lib/src/macros.rs @@ -514,6 +514,7 @@ macro_rules! vs_utf8 { #[allow(unused_macros)] #[macro_export] +/// Takes EntryClass objects and makes a VaueSetIutf8 macro_rules! vs_iutf8 { () => ( compile_error!("ValueSetIutf8 needs at least 1 element") diff --git a/server/lib/src/modify.rs b/server/lib/src/modify.rs index 3fd2c7347..42141ee6a 100644 --- a/server/lib/src/modify.rs +++ b/server/lib/src/modify.rs @@ -33,27 +33,23 @@ pub enum Modify { // This attr *should not* exist. Purged(AttrString), // This attr and value must exist *in this state* for this change to proceed. - Assert(AttrString, PartialValue), + Assert(Attribute, PartialValue), } -#[allow(dead_code)] -pub fn m_pres(a: &str, v: &Value) -> Modify { - Modify::Present(a.into(), v.clone()) +pub fn m_pres(attr: Attribute, v: &Value) -> Modify { + Modify::Present(attr.into(), v.clone()) } -#[allow(dead_code)] -pub fn m_remove(a: &str, v: &PartialValue) -> Modify { - Modify::Removed(a.into(), v.clone()) +pub fn m_remove(attr: Attribute, v: &PartialValue) -> Modify { + Modify::Removed(attr.into(), v.clone()) } -#[allow(dead_code)] -pub fn m_purge(a: &str) -> Modify { - Modify::Purged(AttrString::from(a)) +pub fn m_purge(attr: Attribute) -> Modify { + Modify::Purged(attr.into()) } -#[allow(dead_code)] -pub fn m_assert(a: &str, v: &PartialValue) -> Modify { - Modify::Assert(a.into(), v.clone()) +pub fn m_assert(attr: Attribute, v: &PartialValue) -> Modify { + Modify::Assert(attr, v.clone()) } impl Modify { @@ -102,22 +98,19 @@ impl ModifyList { } } - pub fn new_purge_and_set(attr: &str, v: Value) -> Self { - Self::new_list(vec![ - m_purge(attr), - Modify::Present(AttrString::from(attr), v), - ]) + pub fn new_purge_and_set(attr: Attribute, v: Value) -> Self { + Self::new_list(vec![m_purge(attr), Modify::Present(attr.into(), v)]) } - pub fn new_append(attr: &str, v: Value) -> Self { - Self::new_list(vec![Modify::Present(AttrString::from(attr), v)]) + pub fn new_append(attr: Attribute, v: Value) -> Self { + Self::new_list(vec![Modify::Present(attr.into(), v)]) } - pub fn new_remove(attr: &str, pv: PartialValue) -> Self { - Self::new_list(vec![Modify::Removed(AttrString::from(attr), pv)]) + pub fn new_remove(attr: Attribute, pv: PartialValue) -> Self { + Self::new_list(vec![Modify::Removed(attr.into(), pv)]) } - pub fn new_purge(attr: &str) -> Self { + pub fn new_purge(attr: Attribute) -> Self { Self::new_list(vec![m_purge(attr)]) } @@ -148,12 +141,13 @@ impl ModifyList { pe.attrs.iter().try_for_each(|(attr, vals)| { // Issue a purge to the attr. + let attr: Attribute = (attr.clone()).try_into()?; mods.push(m_purge(attr)); // Now if there are vals, push those too. // For each value we want to now be present. vals.iter().try_for_each(|val| { - qs.clone_value(attr, val).map(|resolved_v| { - mods.push(Modify::Present(attr.as_str().into(), resolved_v)); + qs.clone_value(attr.as_ref(), val).map(|resolved_v| { + mods.push(Modify::Present(attr.into(), resolved_v)); }) }) })?; @@ -196,15 +190,13 @@ impl ModifyList { None => Err(SchemaError::InvalidAttribute(attr_norm.to_string())), } } - Modify::Assert(attr, value) => { - let attr_norm = schema.normalise_attr_name(attr); - match schema_attributes.get(&attr_norm) { - Some(schema_a) => schema_a - .validate_partialvalue(attr_norm.as_str(), value) - .map(|_| Modify::Assert(attr_norm, value.clone())), - None => Err(SchemaError::InvalidAttribute(attr_norm.to_string())), - } - } + Modify::Assert(attr, value) => match schema_attributes.get(attr.as_ref()) { + // TODO: given attr is an enum... you can't get this wrong anymore? + Some(schema_a) => schema_a + .validate_partialvalue(attr.as_ref(), value) + .map(|_| Modify::Assert(attr.to_owned(), value.clone())), + None => Err(SchemaError::InvalidAttribute(attr.to_string())), + }, Modify::Purged(attr) => { let attr_norm = schema.normalise_attr_name(attr); match schema_attributes.get(&attr_norm) { diff --git a/server/lib/src/plugins/attrunique.rs b/server/lib/src/plugins/attrunique.rs index 7134f709c..0dc0851c4 100644 --- a/server/lib/src/plugins/attrunique.rs +++ b/server/lib/src/plugins/attrunique.rs @@ -33,23 +33,23 @@ where .filter_map(|e| e.mask_recycled_ts()) .try_for_each(|e| { let uuid = e - .get_ava_single_uuid("uuid") + .get_ava_single_uuid(Attribute::Uuid) .ok_or_else(|| { error!("An entry is missing its uuid. This should be impossible!"); OperationError::InvalidEntryState })?; // Faster to iterate over the attr vec inside this loop. - for attr in uniqueattrs.iter() { - if let Some(vs) = e.get_ava_set(attr) { + for attrstr in uniqueattrs.iter() { + if let Some(vs) = e.get_ava_set(attrstr.try_into()?) { for pv in vs.to_partialvalue_iter() { - let key = (attr.clone(), pv); + let key = (attrstr.clone(), pv); cand_attr.entry(key) // Must have conflicted, lets append. .and_modify(|v| { warn!( "ava already exists -> {:?} on entry {:?} has conflicts within change set", - attr, + attrstr, e.get_display_id() ); v.push(uuid) @@ -116,17 +116,15 @@ fn enforce_unique( } // Now do an internal search on name and !uuid for each - let cand_filters: Vec<_> = cand_attr - .iter() - .map(|((attr, v), uuid)| { - // and[ attr eq k, andnot [ uuid eq v ]] - // Basically this says where name but also not self. - f_and(vec![ - FC::Eq(attr, v.clone()), - f_andnot(FC::Eq(ATTR_UUID, PartialValue::Uuid(*uuid))), - ]) - }) - .collect(); + let mut cand_filters = Vec::new(); + for ((attr, v), uuid) in cand_attr.iter() { + // and[ attr eq k, andnot [ uuid eq v ]] + // Basically this says where name but also not self. + cand_filters.push(f_and(vec![ + FC::Eq(attr, v.clone()), + f_andnot(FC::Eq(Attribute::Uuid.as_ref(), PartialValue::Uuid(*uuid))), + ])); + } // Or let filt_in = filter!(f_or(cand_filters.clone())); @@ -312,7 +310,7 @@ impl Plugin for AttrUnique { // Basically this says where name but also not self. f_and(vec![ FC::Eq(attr, v.clone()), - f_andnot(FC::Eq(ATTR_UUID, PartialValue::Uuid(*uuid))), + f_andnot(FC::Eq(Attribute::Uuid.as_ref(), PartialValue::Uuid(*uuid))), ]) }) }) @@ -368,7 +366,7 @@ impl Plugin for AttrUnique { .map(|(attr, pv)| { f_and(vec![ FC::Eq(attr, pv.clone()), - f_andnot(FC::Eq(ATTR_UUID, PartialValue::Uuid(uuid))), + f_andnot(FC::Eq(Attribute::Uuid.as_ref(), PartialValue::Uuid(uuid))), ]) }) .collect(); diff --git a/server/lib/src/plugins/base.rs b/server/lib/src/plugins/base.rs index dd0350957..84622db6e 100644 --- a/server/lib/src/plugins/base.rs +++ b/server/lib/src/plugins/base.rs @@ -45,19 +45,23 @@ impl Plugin for Base { entry.add_ava(Attribute::Class, EntryClass::Object.to_value()); // if they don't have uuid, create it. - match entry.get_ava_set("uuid").map(|s| s.len()) { + match entry.get_ava_set(Attribute::Uuid).map(|s| s.len()) { None => { // Generate let ava_uuid = Value::Uuid(Uuid::new_v4()); trace!("Setting temporary UUID {:?} to entry", ava_uuid); - entry.set_ava("uuid", once(ava_uuid)); + entry.set_ava(Attribute::Uuid, once(ava_uuid)); } Some(1) => { // Do nothing } Some(x) => { // If we get some it MUST be 2 + - admin_error!("Entry defines uuid attr, but has multiple ({}) values.", x); + admin_error!( + "Entry defines {} attr, but has multiple ({}) values.", + Attribute::Uuid, + x + ); return Err(OperationError::Plugin(PluginError::Base( "Uuid has multiple values".to_string(), ))); @@ -75,7 +79,7 @@ impl Plugin for Base { // we may not have filled in the uuid field yet. for entry in cand.iter() { let uuid_ref: Uuid = entry - .get_ava_single_uuid("uuid") + .get_ava_single_uuid(Attribute::Uuid) .ok_or_else(|| OperationError::InvalidAttribute(Attribute::Uuid.to_string()))?; if !cand_uuid.insert(uuid_ref) { trace!("uuid duplicate found in create set! {:?}", uuid_ref); @@ -121,7 +125,7 @@ impl Plugin for Base { let filt_in = filter_all!(FC::Or( cand_uuid .into_iter() - .map(|u| FC::Eq("uuid", PartialValue::Uuid(u))) + .map(|u| FC::Eq(Attribute::Uuid.as_ref(), PartialValue::Uuid(u))) .collect(), )); @@ -166,7 +170,7 @@ impl Plugin for Base { Modify::Purged(a) => Some(a), Modify::Assert(_, _) => None, }; - if attr.map(|s| s.as_str()) == Some("uuid") { + if attr == Some(&AttrString::from(Attribute::Uuid)) { debug!(?modify, "Modify in violation"); request_error!("Modifications to UUID's are NOT ALLOWED"); Err(OperationError::SystemProtectedAttribute) @@ -193,7 +197,7 @@ impl Plugin for Base { Modify::Purged(a) => Some(a), Modify::Assert(_, _) => None, }; - if attr.map(|s| s.as_str()) == Some("uuid") { + if attr == Some(&AttrString::from(Attribute::Uuid)) { debug!(?modify, "Modify in violation"); request_error!("Modifications to UUID's are NOT ALLOWED"); Err(OperationError::SystemProtectedAttribute) @@ -348,7 +352,7 @@ mod tests { ))) .expect("Internal search failure"); let ue = cands.first().expect("No cand"); - assert!(ue.attribute_pres("uuid")); + assert!(ue.attribute_pres(Attribute::Uuid)); } ); } @@ -400,7 +404,7 @@ mod tests { }"#, ); - let vs = e.get_ava_mut("uuid").unwrap(); + let vs = e.get_ava_mut(Attribute::Uuid).unwrap(); vs.clear(); let create = vec![e.clone()]; diff --git a/server/lib/src/plugins/cred_import.rs b/server/lib/src/plugins/cred_import.rs index 3accc2d4e..cf601ca18 100644 --- a/server/lib/src/plugins/cred_import.rs +++ b/server/lib/src/plugins/cred_import.rs @@ -59,12 +59,11 @@ impl CredImport { fn modify_inner(cand: &mut [Entry]) -> Result<(), OperationError> { cand.iter_mut().try_for_each(|e| { // PASSWORD IMPORT - if let Some(vs) = e.pop_ava("password_import") { + if let Some(vs) = e.pop_ava(Attribute::PasswordImport) { // if there are multiple, fail. let im_pw = vs.to_utf8_single().ok_or_else(|| { OperationError::Plugin(PluginError::CredImport( - "password_import has incorrect value type - should be a single utf8 string" - .to_string(), + format!("{} has incorrect value type - should be a single utf8 string", Attribute::PasswordImport), )) })?; @@ -86,11 +85,11 @@ impl CredImport { })?; // does the entry have a primary cred? - match e.get_ava_single_credential(Attribute::PrimaryCredential.into()) { + match e.get_ava_single_credential(Attribute::PrimaryCredential) { Some(c) => { let c = c.update_password(pw); e.set_ava( - Attribute::PrimaryCredential.into(), + Attribute::PrimaryCredential, once(Value::new_credential("primary", c)), ); } @@ -98,7 +97,7 @@ impl CredImport { // just set it then! let c = Credential::new_from_password(pw); e.set_ava( - Attribute::PrimaryCredential.into(), + Attribute::PrimaryCredential, once(Value::new_credential("primary", c)), ); } @@ -107,7 +106,7 @@ impl CredImport { // TOTP IMPORT - Must be subsequent to password import to allow primary cred to // be created. - if let Some(vs) = e.pop_ava(Attribute::TotpImport.as_ref()) { + if let Some(vs) = e.pop_ava(Attribute::TotpImport) { // Get the map. let totps = vs.as_totp_map().ok_or_else(|| { OperationError::Plugin(PluginError::CredImport( @@ -115,12 +114,12 @@ impl CredImport { )) })?; - if let Some(c) = e.get_ava_single_credential(Attribute::PrimaryCredential.as_ref()) { + if let Some(c) = e.get_ava_single_credential(Attribute::PrimaryCredential) { let c = totps.iter().fold(c.clone(), |acc, (label, totp)| { acc.append_totp(label.clone(), totp.clone()) }); e.set_ava( - Attribute::PrimaryCredential.as_ref(), + Attribute::PrimaryCredential, once(Value::new_credential("primary", c)), ); } else { @@ -281,7 +280,7 @@ mod tests { .internal_search_uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee")) .expect("failed to get entry"); let c = e - .get_ava_single_credential(Attribute::PrimaryCredential.as_ref()) + .get_ava_single_credential(Attribute::PrimaryCredential) .expect("failed to get primary cred."); match &c.type_ { CredentialType::PasswordMfa(_pw, totp, webauthn, backup_code) => { @@ -342,7 +341,7 @@ mod tests { |qs: &mut QueryServerWriteTransaction| { let e = qs.internal_search_uuid(euuid).expect("failed to get entry"); let c = e - .get_ava_single_credential(Attribute::PrimaryCredential.as_ref()) + .get_ava_single_credential(Attribute::PrimaryCredential) .expect("failed to get primary cred."); match &c.type_ { CredentialType::PasswordMfa(_pw, totp, webauthn, backup_code) => { diff --git a/server/lib/src/plugins/domain.rs b/server/lib/src/plugins/domain.rs index 0903f4f23..a301adc0f 100644 --- a/server/lib/src/plugins/domain.rs +++ b/server/lib/src/plugins/domain.rs @@ -68,54 +68,54 @@ impl Domain { cand: &mut [Entry], ) -> Result<(), OperationError> { cand.iter_mut().try_for_each(|e| { - if e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DomainInfo.into()) - && e.attribute_equality(Attribute::Uuid.as_ref(), &PVUUID_DOMAIN_INFO) + if e.attribute_equality(Attribute::Class, &EntryClass::DomainInfo.into()) + && e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO) { // Validate the domain ldap basedn syntax. if let Some(basedn) = e - .get_ava_single_iutf8("domain_ldap_basedn") { + .get_ava_single_iutf8(Attribute::DomainLdapBasedn) { if !DOMAIN_LDAP_BASEDN_RE.is_match(basedn) { - error!("Invalid domain_ldap_basedn. Must pass regex \"{}\"", *DOMAIN_LDAP_BASEDN_RE); + error!("Invalid {}. Must pass regex \"{}\"", Attribute::DomainLdapBasedn, *DOMAIN_LDAP_BASEDN_RE); return Err(OperationError::InvalidState); } } // We always set this, because the DB uuid is authoritative. let u = Value::Uuid(qs.get_domain_uuid()); - e.set_ava("domain_uuid", once(u)); + e.set_ava(Attribute::DomainUuid, once(u)); trace!("plugin_domain: Applying uuid transform"); // We only apply this if one isn't provided. - if !e.attribute_pres("domain_name") { + if !e.attribute_pres(Attribute::DomainName) { let n = Value::new_iname(qs.get_domain_name()); - e.set_ava("domain_name", once(n)); + e.set_ava(Attribute::DomainName, once(n)); trace!("plugin_domain: Applying domain_name transform"); } // Setup the minimum functional level if one is not set already. - if !e.attribute_pres(Attribute::Version.as_ref()) { + if !e.attribute_pres(Attribute::Version) { let n = Value::Uint32(DOMAIN_MIN_LEVEL); - e.set_ava(Attribute::Version.as_ref(), once(n)); + e.set_ava(Attribute::Version, once(n)); trace!("plugin_domain: Applying domain version transform"); } // create the domain_display_name if it's missing - if !e.attribute_pres(Attribute::DomainDisplayName.as_ref()) { + if !e.attribute_pres(Attribute::DomainDisplayName) { let domain_display_name = Value::new_utf8(format!("Kanidm {}", qs.get_domain_name())); security_info!("plugin_domain: setting default domain_display_name to {:?}", domain_display_name); - e.set_ava(Attribute::DomainDisplayName.into(), once(domain_display_name)); + e.set_ava(Attribute::DomainDisplayName, once(domain_display_name)); } - if !e.attribute_pres(Attribute::FernetPrivateKeyStr.as_ref()) { + if !e.attribute_pres(Attribute::FernetPrivateKeyStr) { security_info!("regenerating domain token encryption key"); let k = fernet::Fernet::generate_key(); let v = Value::new_secret_str(&k); e.add_ava(Attribute::FernetPrivateKeyStr, v); } - if !e.attribute_pres(Attribute::Es256PrivateKeyDer.as_ref()) { + if !e.attribute_pres(Attribute::Es256PrivateKeyDer) { security_info!("regenerating domain es256 private key"); let der = JwsSigner::generate_es256() .and_then(|jws| jws.private_key_to_der()) @@ -127,7 +127,7 @@ impl Domain { e.add_ava(Attribute::Es256PrivateKeyDer, v); } - if !e.attribute_pres(Attribute::PrivateCookieKey.as_ref()) { + if !e.attribute_pres(Attribute::PrivateCookieKey) { security_info!("regenerating domain cookie key"); let mut key = [0; 64]; let mut rng = StdRng::from_entropy(); @@ -159,8 +159,6 @@ mod tests { let u_dom = server_txn.get_domain_uuid(); - assert!( - e_dom.attribute_equality(Attribute::DomainUuid.as_ref(), &PartialValue::Uuid(u_dom)) - ); + assert!(e_dom.attribute_equality(Attribute::DomainUuid, &PartialValue::Uuid(u_dom))); } } diff --git a/server/lib/src/plugins/dyngroup.rs b/server/lib/src/plugins/dyngroup.rs index 4ef16725e..9ea211a56 100644 --- a/server/lib/src/plugins/dyngroup.rs +++ b/server/lib/src/plugins/dyngroup.rs @@ -43,15 +43,15 @@ impl DynGroup { // Go through them all and update the new groups. for (pre, mut nd_group) in work_set.into_iter() { let scope_f: ProtoFilter = nd_group - .get_ava_single_protofilter("dyngroup_filter") + .get_ava_single_protofilter(Attribute::DynGroupFilter) .cloned() .ok_or_else(|| { - admin_error!("Missing dyngroup_filter"); + admin_error!("Missing {}", Attribute::DynGroupFilter); OperationError::InvalidEntryState })?; let scope_i = Filter::from_rw(ident_internal, &scope_f, qs).map_err(|e| { - admin_error!("dyngroup_filter validation failed {:?}", e); + admin_error!("{} validation failed {:?}", Attribute::DynGroupFilter, e); e })?; @@ -72,23 +72,23 @@ impl DynGroup { } // Mark the former members as being affected also. - if let Some(uuid_iter) = pre.get_ava_as_refuuid("dynmember") { + if let Some(uuid_iter) = pre.get_ava_as_refuuid(Attribute::DynMember) { affected_uuids.extend(uuid_iter); } if let Some(members) = members { // Only set something if there is actually something to do! - nd_group.set_ava_set("dynmember", members); + nd_group.set_ava_set(Attribute::DynMember, members); // push the entries to pre/cand } else { - nd_group.purge_ava("dynmember"); + nd_group.purge_ava(Attribute::DynMember); } candidate_tuples.push((pre, nd_group)); // Insert to our new instances if dyn_groups.insts.insert(uuid, scope_i).is_none() == expect { - admin_error!("dyngroup cache uuid conflict {}", uuid); + admin_error!("{} cache uuid conflict {}", Attribute::DynGroup, uuid); return Err(OperationError::InvalidState); } } @@ -109,10 +109,10 @@ impl DynGroup { for nd_group in entries.into_iter() { let scope_f: ProtoFilter = nd_group - .get_ava_single_protofilter("dyngroup_filter") + .get_ava_single_protofilter(Attribute::DynGroupFilter) .cloned() .ok_or_else(|| { - admin_error!("Missing dyngroup_filter"); + admin_error!("Missing {}", Attribute::DynGroupFilter); OperationError::InvalidEntryState })?; @@ -146,7 +146,7 @@ impl DynGroup { let ident_internal = Identity::from_internal(); let (n_dyn_groups, entries): (Vec<&Entry<_, _>>, Vec<_>) = cand.iter().partition(|entry| { - entry.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DynGroup.into()) + entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) }); // DANGER: Why do we have to do this? During the use of qs for internal search @@ -249,12 +249,12 @@ impl DynGroup { // Probably should be filter here instead. let (_, pre_entries): (Vec<&Arc>>, Vec<_>) = pre_cand.iter().partition(|entry| { - entry.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DynGroup.into()) + entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) }); let (n_dyn_groups, post_entries): (Vec<&Entry<_, _>>, Vec<_>) = cand.iter().partition(|entry| { - entry.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DynGroup.into()) + entry.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) }); // DANGER: Why do we have to do this? During the use of qs for internal search @@ -323,8 +323,7 @@ impl DynGroup { if let Some((pre, mut d_group)) = work_set.pop() { matches.iter().copied().for_each(|choice| match choice { Ok(u) => d_group.add_ava(Attribute::DynMember, Value::Refer(u)), - Err(u) => d_group - .remove_ava(Attribute::DynMember.as_ref(), &PartialValue::Refer(u)), + Err(u) => d_group.remove_ava(Attribute::DynMember, &PartialValue::Refer(u)), }); affected_uuids.extend(matches.into_iter().map(|choice| match choice { @@ -406,7 +405,7 @@ mod tests { let d_group = cands.get(0).expect("Unable to access group."); let members = d_group - .get_ava_set("dynmember") + .get_ava_set(Attribute::DynMember) .expect("No members on dyn group"); assert!(members.to_refer_single() == Some(UUID_TEST_GROUP)); @@ -455,7 +454,7 @@ mod tests { let d_group = cands.get(0).expect("Unable to access group."); let members = d_group - .get_ava_set("dynmember") + .get_ava_set(Attribute::DynMember) .expect("No members on dyn group"); assert!(members.to_refer_single() == Some(UUID_TEST_GROUP)); @@ -503,7 +502,7 @@ mod tests { .expect("Internal search failure"); let d_group = cands.get(0).expect("Unable to access group."); - assert!(d_group.get_ava_set("dynmember").is_none()); + assert!(d_group.get_ava_set(Attribute::DynMember).is_none()); } ); } @@ -549,11 +548,11 @@ mod tests { let d_group = cands.get(0).expect("Unable to access group."); let members = d_group - .get_ava_set("dynmember") + .get_ava_set(Attribute::DynMember) .expect("No members on dyn group"); assert!(members.to_refer_single() == Some(UUID_TEST_GROUP)); - assert!(d_group.get_ava_set("member").is_none()); + assert!(d_group.get_ava_set(Attribute::Member).is_none()); } ); } @@ -611,7 +610,7 @@ mod tests { let d_group = cands.get(0).expect("Unable to access group."); let members = d_group - .get_ava_set("dynmember") + .get_ava_set(Attribute::DynMember) .expect("No members on dyn group"); assert!(members.to_refer_single() == Some(UUID_TEST_GROUP)); @@ -671,7 +670,7 @@ mod tests { .expect("Internal search failure"); let d_group = cands.get(0).expect("Unable to access group."); - assert!(d_group.get_ava_set("dynmember").is_none()); + assert!(d_group.get_ava_set(Attribute::DynMember).is_none()); } ); } @@ -723,7 +722,7 @@ mod tests { let d_group = cands.get(0).expect("Unable to access group."); let members = d_group - .get_ava_set("dynmember") + .get_ava_set(Attribute::DynMember) .expect("No members on dyn group"); // We assert to refer single here because we should have "removed" uuid_admin being added // at all. @@ -776,7 +775,7 @@ mod tests { let d_group = cands.get(0).expect("Unable to access group."); let members = d_group - .get_ava_set("dynmember") + .get_ava_set(Attribute::DynMember) .expect("No members on dyn group"); // We assert to refer single here because we should have re-added the members assert!(members.to_refer_single() == Some(UUID_TEST_GROUP)); @@ -831,7 +830,7 @@ mod tests { let d_group = cands.get(0).expect("Unable to access group."); let members = d_group - .get_ava_set("dynmember") + .get_ava_set(Attribute::DynMember) .expect("No members on dyn group"); assert!(members.to_refer_single() == Some(UUID_TEST_GROUP)); @@ -882,7 +881,7 @@ mod tests { .expect("Internal search failure"); let d_group = cands.get(0).expect("Unable to access group."); - assert!(d_group.get_ava_set("dynmember").is_none()); + assert!(d_group.get_ava_set(Attribute::DynMember).is_none()); } ); } @@ -925,7 +924,7 @@ mod tests { .expect("Internal search failure"); let d_group = cands.get(0).expect("Unable to access group."); - assert!(d_group.get_ava_set("dynmember").is_none()); + assert!(d_group.get_ava_set(Attribute::DynMember).is_none()); } ); } @@ -972,7 +971,7 @@ mod tests { .expect("Internal search failure"); let d_group = cands.get(0).expect("Unable to access group."); - assert!(d_group.get_ava_set("memberof").is_none()); + assert!(d_group.get_ava_set(Attribute::MemberOf).is_none()); } ); } diff --git a/server/lib/src/plugins/eckeygen.rs b/server/lib/src/plugins/eckeygen.rs index 7865396a5..67ac15c38 100644 --- a/server/lib/src/plugins/eckeygen.rs +++ b/server/lib/src/plugins/eckeygen.rs @@ -23,19 +23,21 @@ impl EcdhKeyGen { cands: &mut [Entry], ) -> Result<(), OperationError> { for cand in cands.iter_mut() { - if cand.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Person.to_partialvalue(), - ) && !cand.attribute_pres(Attribute::IdVerificationEcKey.into()) + if cand.attribute_equality(Attribute::Class, &EntryClass::Person.to_partialvalue()) + && !cand.attribute_pres(Attribute::IdVerificationEcKey) { - debug!("Generating idv_eckey for {}", cand.get_display_id()); + debug!( + "Generating {} for {}", + Attribute::IdVerificationEcKey, + cand.get_display_id() + ); let new_private_key = EcKey::generate(&DEFAULT_KEY_GROUP).map_err(|e| { error!(err = ?e, "Unable to generate id verification ECDH private key"); OperationError::CryptographyError })?; cand.add_ava_if_not_exist( - Attribute::IdVerificationEcKey.into(), + Attribute::IdVerificationEcKey, crate::value::Value::EcKeyPrivate(new_private_key), ) } @@ -113,7 +115,7 @@ mod tests { let e = qs.internal_search_uuid(uuid).expect("failed to get entry"); let key = e - .get_ava_single_eckey_private(Attribute::IdVerificationEcKey.into()) + .get_ava_single_eckey_private(Attribute::IdVerificationEcKey) .expect("unable to retrieve the ecdh key"); assert!(key.check_key().is_ok()) @@ -178,15 +180,15 @@ mod tests { Ok(()), preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("test_name"))), - modlist!([m_purge(Attribute::IdVerificationEcKey.into())]), + modlist!([m_purge(Attribute::IdVerificationEcKey)]), None, |_| {}, |qs: &mut QueryServerWriteTransaction| { let e = qs.internal_search_uuid(uuid).expect("failed to get entry"); assert!( - !e.attribute_equality(Attribute::IdVerificationEcKey.into(), &key_partialvalue) - && e.attribute_pres(Attribute::IdVerificationEcKey.into()) + !e.attribute_equality(Attribute::IdVerificationEcKey, &key_partialvalue) + && e.attribute_pres(Attribute::IdVerificationEcKey) ) } ); @@ -218,18 +220,15 @@ mod tests { Ok(()), preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("test_name"))), - modlist!([m_remove( - Attribute::IdVerificationEcKey.into(), - &key_partialvalue - )]), + modlist!([m_remove(Attribute::IdVerificationEcKey, &key_partialvalue)]), None, |_| {}, |qs: &mut QueryServerWriteTransaction| { let e = qs.internal_search_uuid(uuid).expect("failed to get entry"); assert!( - !e.attribute_equality(Attribute::IdVerificationEcKey.into(), &key_partialvalue) - && e.attribute_pres(Attribute::IdVerificationEcKey.into()) + !e.attribute_equality(Attribute::IdVerificationEcKey, &key_partialvalue) + && e.attribute_pres(Attribute::IdVerificationEcKey) ) } ); diff --git a/server/lib/src/plugins/gidnumber.rs b/server/lib/src/plugins/gidnumber.rs index 8afe4a7b0..19a426f0b 100644 --- a/server/lib/src/plugins/gidnumber.rs +++ b/server/lib/src/plugins/gidnumber.rs @@ -20,9 +20,9 @@ const GID_SAFETY_NUMBER_MIN: u32 = 1000; pub struct GidNumber {} fn apply_gidnumber(e: &mut Entry) -> Result<(), OperationError> { - if (e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::PosixGroup.into()) - || e.attribute_equality(Attribute::Class.into(), &EntryClass::PosixAccount.into())) - && !e.attribute_pres(Attribute::GidNumber.as_ref()) + if (e.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.into()) + || e.attribute_equality(Attribute::Class, &EntryClass::PosixAccount.into())) + && !e.attribute_pres(Attribute::GidNumber) { let u_ref = e .get_uuid() @@ -36,19 +36,25 @@ fn apply_gidnumber(e: &mut Entry) -> Result<(), Opera // assert the value is greater than the system range. if gid < GID_SYSTEM_NUMBER_MIN { return Err(OperationError::InvalidAttribute(format!( - "gidnumber {gid} may overlap with system range {GID_SYSTEM_NUMBER_MIN}" + "{} {} may overlap with system range {}", + Attribute::GidNumber, + gid, + GID_SYSTEM_NUMBER_MIN ))); } let gid_v = Value::new_uint32(gid); admin_info!("Generated {} for {:?}", gid, u_ref); - e.set_ava(Attribute::GidNumber.as_ref(), once(gid_v)); + e.set_ava(Attribute::GidNumber, once(gid_v)); Ok(()) - } else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber.as_ref()) { + } else if let Some(gid) = e.get_ava_single_uint32(Attribute::GidNumber) { // If they provided us with a gid number, ensure it's in a safe range. if gid <= GID_SAFETY_NUMBER_MIN { Err(OperationError::InvalidAttribute(format!( - "gidnumber {gid} overlaps into system secure range {GID_SAFETY_NUMBER_MIN}" + "{} {} overlaps into system secure range {}", + Attribute::GidNumber, + gid, + GID_SAFETY_NUMBER_MIN ))) } else { Ok(()) @@ -100,7 +106,7 @@ mod tests { fn check_gid(qs_write: &mut QueryServerWriteTransaction, uuid: &str, gid: u32) { let u = Uuid::parse_str(uuid).unwrap(); let e = qs_write.internal_search_uuid(u).unwrap(); - let gidnumber = e.get_ava_single(Attribute::GidNumber.as_ref()).unwrap(); + let gidnumber = e.get_ava_single(Attribute::GidNumber).unwrap(); let ex_gid = Value::new_uint32(gid); assert!(ex_gid == gidnumber); } @@ -188,7 +194,7 @@ mod tests { Ok(()), preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))), - modlist!([m_pres("class", &EntryClass::PosixGroup.into())]), + modlist!([m_pres(Attribute::Class, &EntryClass::PosixGroup.into())]), None, |_| {}, |qs_write: &mut QueryServerWriteTransaction| check_gid( @@ -220,7 +226,7 @@ mod tests { Ok(()), preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))), - modlist!([m_purge(Attribute::GidNumber.as_ref())]), + modlist!([m_purge(Attribute::GidNumber)]), None, |_| {}, |qs_write: &mut QueryServerWriteTransaction| check_gid( @@ -253,8 +259,8 @@ mod tests { preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))), modlist!([ - m_purge(Attribute::GidNumber.as_ref()), - m_pres(Attribute::GidNumber.as_ref(), &Value::new_uint32(2000)) + m_purge(Attribute::GidNumber), + m_pres(Attribute::GidNumber, &Value::new_uint32(2000)) ]), None, |_| {}, diff --git a/server/lib/src/plugins/jwskeygen.rs b/server/lib/src/plugins/jwskeygen.rs index 0aa993878..26cd9e2cc 100644 --- a/server/lib/src/plugins/jwskeygen.rs +++ b/server/lib/src/plugins/jwskeygen.rs @@ -46,21 +46,21 @@ impl Plugin for JwsKeygen { impl JwsKeygen { fn modify_inner(cand: &mut [Entry]) -> Result<(), OperationError> { cand.iter_mut().try_for_each(|e| { - if e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::OAuth2ResourceServerBasic.into()) && - !e.attribute_pres(Attribute::OAuth2RsBasicSecret.into()) { + if e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServerBasic.into()) && + !e.attribute_pres(Attribute::OAuth2RsBasicSecret) { security_info!("regenerating oauth2 basic secret"); let v = Value::SecretValue(password_from_random()); e.add_ava(Attribute::OAuth2RsBasicSecret, v); } - if e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::OAuth2ResourceServer.into()) { - if !e.attribute_pres(Attribute::OAuth2RsTokenKey.as_ref()) { + if e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) { + if !e.attribute_pres(Attribute::OAuth2RsTokenKey) { security_info!("regenerating oauth2 token key"); let k = fernet::Fernet::generate_key(); let v = Value::new_secret_str(&k); e.add_ava(Attribute::OAuth2RsTokenKey, v); } - if !e.attribute_pres(Attribute::Es256PrivateKeyDer.as_ref()) { + if !e.attribute_pres(Attribute::Es256PrivateKeyDer) { security_info!("regenerating oauth2 es256 private key"); let der = JwsSigner::generate_es256() .and_then(|jws| jws.private_key_to_der()) @@ -71,8 +71,8 @@ impl JwsKeygen { let v = Value::new_privatebinary(&der); e.add_ava(Attribute::Es256PrivateKeyDer, v); } - if e.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable.as_ref()).unwrap_or(false) - && !e.attribute_pres(Attribute::Rs256PrivateKeyDer.into()) { + if e.get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable).unwrap_or(false) + && !e.attribute_pres(Attribute::Rs256PrivateKeyDer) { security_info!("regenerating oauth2 legacy rs256 private key"); let der = JwsSigner::generate_legacy_rs256() .and_then(|jws| jws.private_key_to_der()) @@ -85,9 +85,9 @@ impl JwsKeygen { } } - if (e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::ServiceAccount.into()) || - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncAccount.into())) && - !e.attribute_pres(Attribute::JwsEs256PrivateKey.as_ref()) { + if (e.attribute_equality(Attribute::Class, &EntryClass::ServiceAccount.into()) || + e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into())) && + !e.attribute_pres(Attribute::JwsEs256PrivateKey) { security_info!("regenerating jws es256 private key"); let jwssigner = JwsSigner::generate_es256() .map_err(|e| { @@ -156,8 +156,8 @@ mod tests { let e = qs .internal_search_uuid(uuid) .expect("failed to get oauth2 config"); - assert!(e.attribute_pres("oauth2_rs_basic_secret")); - assert!(e.attribute_pres("oauth2_rs_token_key")); + assert!(e.attribute_pres(Attribute::OAuth2RsBasicSecret)); + assert!(e.attribute_pres(Attribute::OAuth2RsTokenKey)); } ); } @@ -220,11 +220,11 @@ mod tests { let e = qs .internal_search_uuid(uuid) .expect("failed to get oauth2 config"); - assert!(e.attribute_pres("oauth2_rs_basic_secret")); - assert!(e.attribute_pres("oauth2_rs_token_key")); + assert!(e.attribute_pres(Attribute::OAuth2RsBasicSecret)); + assert!(e.attribute_pres(Attribute::OAuth2RsTokenKey)); // Check the values are different. - assert!(e.get_ava_single_secret("oauth2_rs_basic_secret") != Some("12345")); - assert!(e.get_ava_single_secret("oauth2_rs_token_key") != Some("12345")); + assert!(e.get_ava_single_secret(Attribute::OAuth2RsBasicSecret) != Some("12345")); + assert!(e.get_ava_single_secret(Attribute::OAuth2RsTokenKey) != Some("12345")); } ); } diff --git a/server/lib/src/plugins/memberof.rs b/server/lib/src/plugins/memberof.rs index 473ddab05..1c11f17a8 100644 --- a/server/lib/src/plugins/memberof.rs +++ b/server/lib/src/plugins/memberof.rs @@ -44,11 +44,11 @@ fn do_memberof( })?; // Ensure we are MO capable. We only add this if it's not already present. - tgte.add_ava_if_not_exist(Attribute::Class.as_ref(), EntryClass::MemberOf.into()); + tgte.add_ava_if_not_exist(Attribute::Class, EntryClass::MemberOf.into()); // Clear the dmo + mos, we will recreate them now. // This is how we handle deletes/etc. - tgte.purge_ava("memberof"); - tgte.purge_ava("directmemberof"); + tgte.purge_ava(Attribute::MemberOf); + tgte.purge_ava(Attribute::DirectMemberOf); // What are our direct and indirect mos? let dmo = ValueSetRefer::from_iter(groups.iter().map(|g| g.get_uuid())); @@ -57,7 +57,7 @@ fn do_memberof( groups .iter() .filter_map(|g| { - g.get_ava_set("memberof") + g.get_ava_set(Attribute::MemberOf) .and_then(|s| s.as_refer_set()) .map(|s| s.iter()) }) @@ -68,7 +68,7 @@ fn do_memberof( // Add all the direct mo's and mos. if let Some(dmo) = dmo { // We need to clone this else type checker gets real sad. - tgte.set_ava_set("directmemberof", dmo.clone()); + tgte.set_ava_set(Attribute::DirectMemberOf, dmo.clone()); if let Some(mo) = &mut mo { let dmo = dmo as ValueSet; @@ -81,18 +81,18 @@ fn do_memberof( }; if let Some(mo) = mo { - tgte.set_ava_set("memberof", mo); + tgte.set_ava_set(Attribute::MemberOf, mo); } trace!( "Updating {:?} to be dir mo {:?}", uuid, - tgte.get_ava_set("directmemberof") + tgte.get_ava_set(Attribute::DirectMemberOf) ); trace!( "Updating {:?} to be mo {:?}", uuid, - tgte.get_ava_set("memberof") + tgte.get_ava_set(Attribute::MemberOf) ); Ok(()) } @@ -133,7 +133,7 @@ fn apply_memberof( for (pre, mut tgte) in work_set.into_iter() { let guuid = pre.get_uuid(); // load the entry from the db. - if !tgte.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Group.into()) { + if !tgte.attribute_equality(Attribute::Class, &EntryClass::Group.into()) { // It's not a group, we'll deal with you later. We should NOT // have seen this UUID before, as either we are on the first // iteration OR the checks belowe should have filtered it out. @@ -147,8 +147,9 @@ fn apply_memberof( do_memberof(qs, guuid, &mut tgte)?; // Did we change? Note we don't check if the class changed, only if mo changed. - if pre.get_ava_set("memberof") != tgte.get_ava_set("memberof") - || pre.get_ava_set("directmemberof") != tgte.get_ava_set("directmemberof") + if pre.get_ava_set(Attribute::MemberOf) != tgte.get_ava_set(Attribute::MemberOf) + || pre.get_ava_set(Attribute::DirectMemberOf) + != tgte.get_ava_set(Attribute::DirectMemberOf) { // Yes we changed - we now must process all our members, as they need to // inherit changes. Some of these members COULD be non groups, but we @@ -157,10 +158,10 @@ fn apply_memberof( "{:?} changed, flagging members as groups to change. ", guuid ); - if let Some(miter) = tgte.get_ava_as_refuuid("member") { + if let Some(miter) = tgte.get_ava_as_refuuid(Attribute::Member) { group_affect.extend(miter.filter(|m| !other_cache.contains_key(m))); }; - if let Some(miter) = tgte.get_ava_as_refuuid("dynmember") { + if let Some(miter) = tgte.get_ava_as_refuuid(Attribute::DynMember) { group_affect.extend(miter.filter(|m| !other_cache.contains_key(m))); }; @@ -188,13 +189,12 @@ fn apply_memberof( .into_iter() .try_for_each(|(auuid, (pre, mut tgte))| { trace!("=> processing affected uuid {:?}", auuid); - debug_assert!( - !tgte.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Group.into()) - ); + debug_assert!(!tgte.attribute_equality(Attribute::Class, &EntryClass::Group.into())); do_memberof(qs, auuid, &mut tgte)?; // Only write if a change occurred. - if pre.get_ava_set("memberof") != tgte.get_ava_set("memberof") - || pre.get_ava_set("directmemberof") != tgte.get_ava_set("directmemberof") + if pre.get_ava_set(Attribute::MemberOf) != tgte.get_ava_set(Attribute::MemberOf) + || pre.get_ava_set(Attribute::DirectMemberOf) + != tgte.get_ava_set(Attribute::DirectMemberOf) { changes.push((pre, tgte)); } @@ -209,7 +209,7 @@ fn apply_memberof( impl Plugin for MemberOf { fn id() -> &'static str { - "memberof" + Attribute::MemberOf.as_ref() } #[instrument(level = "debug", name = "memberof_post_create", skip(qs, cand, ce))] @@ -275,8 +275,8 @@ impl Plugin for MemberOf { .iter() .filter_map(|e| { // Is it a group? - if e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Group.into()) { - e.get_ava_as_refuuid("member") + if e.attribute_equality(Attribute::Class, &EntryClass::Group.into()) { + e.get_ava_as_refuuid(Attribute::Member) } else { None } @@ -286,11 +286,8 @@ impl Plugin for MemberOf { // Or a dyn group? cand.iter() .filter_map(|post| { - if post.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::DynGroup.into(), - ) { - post.get_ava_as_refuuid("dynmember") + if post.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) { + post.get_ava_as_refuuid(Attribute::DynMember) } else { None } @@ -347,7 +344,7 @@ impl Plugin for MemberOf { trace!("DMO search groups {:?} -> {:?}", e.get_uuid(), d_groups_set); - match (e.get_ava_set("directmemberof"), d_groups_set) { + match (e.get_ava_set(Attribute::DirectMemberOf), d_groups_set) { (Some(edmos), Some(b)) => { // Can they both be reference sets? match edmos.as_refer_set() { @@ -414,11 +411,8 @@ impl MemberOf { cand.iter() .filter_map(|e| { // Is it a group? - if e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Group.into(), - ) { - e.get_ava_as_refuuid("member") + if e.attribute_equality(Attribute::Class, &EntryClass::Group.into()) { + e.get_ava_as_refuuid(Attribute::Member) } else { None } @@ -447,11 +441,8 @@ impl MemberOf { pre_cand .iter() .filter_map(|pre| { - if pre.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Group.into(), - ) { - pre.get_ava_as_refuuid("member") + if pre.attribute_equality(Attribute::Class, &EntryClass::Group.into()) { + pre.get_ava_as_refuuid(Attribute::Member) } else { None } @@ -461,11 +452,8 @@ impl MemberOf { .chain( cand.iter() .filter_map(|post| { - if post.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::Group.into(), - ) { - post.get_ava_as_refuuid("member") + if post.attribute_equality(Attribute::Class, &EntryClass::Group.into()) { + post.get_ava_as_refuuid(Attribute::Member) } else { None } diff --git a/server/lib/src/plugins/namehistory.rs b/server/lib/src/plugins/namehistory.rs index a685ad85e..add817ce7 100644 --- a/server/lib/src/plugins/namehistory.rs +++ b/server/lib/src/plugins/namehistory.rs @@ -16,17 +16,23 @@ lazy_static! { // it contains all the partialvalues used to match against an Entry's class, // we just need a partialvalue to match in order to target the entry static ref CLASSES_TO_UPDATE: [PartialValue; 1] = [PartialValue::new_iutf8(EntryClass::Account.into())]; - static ref HISTORY_ATTRIBUTES: [&'static str;1] = [Attribute::Name.as_ref()]; +} + +const HISTORY_ATTRIBUTES: [Attribute; 1] = [Attribute::Name]; + +#[test] +fn test_history_attribute() { + assert_eq!(NameHistory::get_ava_name(Attribute::Name), "name_history"); } impl NameHistory { fn is_entry_to_update(entry: &mut Entry) -> bool { CLASSES_TO_UPDATE .iter() - .any(|pv| entry.attribute_equality(Attribute::Class.as_ref(), pv)) + .any(|pv| entry.attribute_equality(Attribute::Class, pv)) } - fn get_ava_name(history_attr: &str) -> String { + fn get_ava_name(history_attr: Attribute) -> String { format!("{}_history", history_attr) } @@ -38,7 +44,7 @@ impl NameHistory { for (pre, post) in pre_cand.iter().zip(cand) { // here we check if the current entry has at least one of the classes we intend to target if Self::is_entry_to_update(post) { - for history_attr in HISTORY_ATTRIBUTES.iter() { + for history_attr in HISTORY_ATTRIBUTES.into_iter() { let pre_name_option = pre.get_ava_single(history_attr); let post_name_option = post.get_ava_single(history_attr); if let (Some(pre_name), Some(post_name)) = (pre_name_option, post_name_option) { @@ -49,7 +55,7 @@ impl NameHistory { // as of now we're interested just in the name so we use Iname match post_name { Value::Iname(n) => post.add_ava_if_not_exist( - &ava_name, + ava_name.try_into()?, Value::AuditLogString(cid.clone(), n), ), _ => return Err(OperationError::InvalidValueState), @@ -68,12 +74,12 @@ impl NameHistory { ) -> Result<(), OperationError> { for cand in cands.iter_mut() { if Self::is_entry_to_update(cand) { - for history_attr in HISTORY_ATTRIBUTES.iter() { + for history_attr in HISTORY_ATTRIBUTES.into_iter() { if let Some(name) = cand.get_ava_single(history_attr) { let ava_name = Self::get_ava_name(history_attr); match name { Value::Iname(n) => cand.add_ava_if_not_exist( - &ava_name, + ava_name.try_into()?, Value::AuditLogString(cid.clone(), n), ), _ => return Err(OperationError::InvalidValueState), @@ -158,8 +164,8 @@ mod tests { preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("old_name"))), modlist!([ - m_purge(Attribute::Name.as_ref()), - m_pres(Attribute::Name.as_ref(), &Value::new_iname("new_name_1")) + m_purge(Attribute::Name), + m_pres(Attribute::Name, &Value::new_iname("new_name_1")) ]), None, |_| {}, @@ -168,7 +174,7 @@ mod tests { .internal_search_uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee")) .expect("failed to get entry"); let c = e - .get_ava_set(Attribute::NameHistory.as_ref()) + .get_ava_set(Attribute::NameHistory) .expect("failed to get primary cred."); trace!("{:?}", c.clone()); assert!( @@ -206,7 +212,7 @@ mod tests { .expect("failed to get entry"); trace!("{:?}", e.get_ava()); let name_history = e - .get_ava_set(Attribute::NameHistory.as_ref()) + .get_ava_set(Attribute::NameHistory) .expect("failed to get name_history ava"); assert!(name_history.contains(&PartialValue::new_utf8s("old_name"))) @@ -249,8 +255,8 @@ mod tests { preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("old_name8"))), modlist!([ - m_purge(Attribute::Name.as_ref()), - m_pres(Attribute::Name.as_ref(), &Value::new_iname("new_name")) + m_purge(Attribute::Name), + m_pres(Attribute::Name, &Value::new_iname("new_name")) ]), None, |_| {}, @@ -259,7 +265,7 @@ mod tests { .internal_search_uuid(uuid!("d2b496bd-8493-47b7-8142-f568b5cf47ee")) .expect("failed to get entry"); let c = e - .get_ava_set(Attribute::NameHistory.as_ref()) + .get_ava_set(Attribute::NameHistory) .expect("failed to get name_history ava :/"); trace!(?c); assert!( diff --git a/server/lib/src/plugins/protected.rs b/server/lib/src/plugins/protected.rs index d182f0d74..4dab2309d 100644 --- a/server/lib/src/plugins/protected.rs +++ b/server/lib/src/plugins/protected.rs @@ -2,7 +2,6 @@ // may only have certain modifications performed. use hashbrown::HashSet; -use kanidm_proto::constants::{ATTR_MAY, ATTR_MUST}; use std::sync::Arc; use crate::event::{CreateEvent, DeleteEvent, ModifyEvent}; @@ -17,23 +16,23 @@ pub struct Protected {} // schema will have checked this, and we don't allow class changes! lazy_static! { - static ref ALLOWED_ATTRS: HashSet<&'static str> = { + static ref ALLOWED_ATTRS: HashSet = { let mut m = HashSet::with_capacity(16); // Allow modification of some schema class types to allow local extension // of schema types. // - m.insert(ATTR_MUST); - m.insert(ATTR_MAY); + m.insert(Attribute::Must); + m.insert(Attribute::May); // Allow modification of some domain info types for local configuration. - m.insert("domain_ssid"); - m.insert("domain_ldap_basedn"); - m.insert("fernet_private_key_str"); - m.insert("es256_private_key_der"); - m.insert("id_verification_eckey"); - m.insert("badlist_password"); - m.insert("domain_display_name"); - m.insert("authsession_expiry"); - m.insert("privilege_expiry"); + m.insert(Attribute::DomainSsid); + m.insert(Attribute::DomainLdapBasedn); + m.insert(Attribute::FernetPrivateKeyStr); + m.insert(Attribute::Es256PrivateKeyDer); + m.insert(Attribute::IdVerificationEcKey); + m.insert(Attribute::BadlistPassword); + m.insert(Attribute::DomainDisplayName); + m.insert(Attribute::AuthSessionExpiry); + m.insert(Attribute::PrivilegeExpiry); m }; } @@ -56,14 +55,13 @@ impl Plugin for Protected { } cand.iter().try_fold((), |(), cand| { - if cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::System.into()) - || cand.attribute_equality(Attribute::Class.into(), &EntryClass::DomainInfo.into()) - || cand.attribute_equality(Attribute::Class.into(), &EntryClass::SystemInfo.into()) - || cand - .attribute_equality(Attribute::Class.into(), &EntryClass::SystemConfig.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Recycled.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DynGroup.into()) + if cand.attribute_equality(Attribute::Class, &EntryClass::System.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::DomainInfo.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::SystemInfo.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::SystemConfig.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) { Err(OperationError::SystemProtectedObject) } else { @@ -86,7 +84,7 @@ impl Plugin for Protected { // Prevent adding class: system, domain_info, tombstone, or recycled. me.modlist.iter().try_fold((), |(), m| match m { Modify::Present(a, v) => { - if a == "class" + if a == Attribute::Class.as_ref() && (v == &EntryClass::System.to_value() || v == &EntryClass::DomainInfo.to_value() || v == &EntryClass::SystemInfo.into() @@ -107,9 +105,9 @@ impl Plugin for Protected { // HARD block mods on tombstone or recycle. We soft block on the rest as they may // have some allowed attrs. cand.iter().try_fold((), |(), cand| { - if cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Recycled.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DynGroup.into()) + if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) { Err(OperationError::SystemProtectedObject) } else { @@ -121,7 +119,7 @@ impl Plugin for Protected { let system_pres = cand.iter().any(|c| { // We don't need to check for domain info here because domain_info has a class // system also. We just need to block it from being created. - c.attribute_equality(Attribute::Class.as_ref(), &EntryClass::System.into()) + c.attribute_equality(Attribute::Class, &EntryClass::System.into()) }); trace!("class: system -> {}", system_pres); @@ -131,16 +129,17 @@ impl Plugin for Protected { } // Something altered is system, check if it's allowed. - me.modlist.iter().try_fold((), |(), m| { + me.modlist.into_iter().try_fold((), |(), m| { // Already hit an error, move on. let a = match m { Modify::Present(a, _) | Modify::Removed(a, _) | Modify::Purged(a) => Some(a), Modify::Assert(_, _) => None, }; if let Some(a) = a { - match ALLOWED_ATTRS.get(a.as_str()) { - Some(_) => Ok(()), - None => Err(OperationError::SystemProtectedObject), + let attr: Attribute = a.try_into()?; + match ALLOWED_ATTRS.contains(&attr) { + true => Ok(()), + false => Err(OperationError::SystemProtectedObject), } } else { // Was not a mod needing checking @@ -165,10 +164,10 @@ impl Plugin for Protected { .flat_map(|ml| ml.iter()) .try_fold((), |(), m| match m { Modify::Present(a, v) => { - if a == "class" + if a == Attribute::Class.as_ref() && (v == &EntryClass::System.to_value() || v == &EntryClass::DomainInfo.to_value() - || v == &(EntryClass::SystemInfo.to_value()) + || v == &EntryClass::SystemInfo.to_value() || v == &EntryClass::SystemConfig.to_value() || v == &EntryClass::DynGroup.to_value() || v == &EntryClass::SyncObject.to_value() @@ -186,9 +185,9 @@ impl Plugin for Protected { // HARD block mods on tombstone or recycle. We soft block on the rest as they may // have some allowed attrs. cand.iter().try_fold((), |(), cand| { - if cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Recycled.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DynGroup.into()) + if cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) { Err(OperationError::SystemProtectedObject) } else { @@ -200,10 +199,10 @@ impl Plugin for Protected { let system_pres = cand.iter().any(|c| { // We don't need to check for domain info here because domain_info has a class // system also. We just need to block it from being created. - c.attribute_equality(Attribute::Class.as_ref(), &EntryClass::System.into()) + c.attribute_equality(Attribute::Class, &EntryClass::System.into()) }); - trace!("class: system -> {}", system_pres); + trace!("{}: system -> {}", Attribute::Class, system_pres); // No system types being altered, return. if !system_pres { return Ok(()); @@ -220,9 +219,10 @@ impl Plugin for Protected { Modify::Assert(_, _) => None, }; if let Some(a) = a { - match ALLOWED_ATTRS.get(a.as_str()) { - Some(_) => Ok(()), - None => Err(OperationError::SystemProtectedObject), + let attr: Attribute = a.try_into()?; + match ALLOWED_ATTRS.contains(&attr) { + true => Ok(()), + false => Err(OperationError::SystemProtectedObject), } } else { // Was not a mod needing checking @@ -244,14 +244,13 @@ impl Plugin for Protected { } cand.iter().try_fold((), |(), cand| { - if cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::System.into()) - || cand.attribute_equality(Attribute::Class.into(), &EntryClass::DomainInfo.into()) - || cand.attribute_equality(Attribute::Class.into(), &EntryClass::SystemInfo.into()) - || cand - .attribute_equality(Attribute::Class.into(), &EntryClass::SystemConfig.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Recycled.into()) - || cand.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DynGroup.into()) + if cand.attribute_equality(Attribute::Class, &EntryClass::System.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::DomainInfo.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::SystemInfo.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::SystemConfig.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::Recycled.into()) + || cand.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()) { Err(OperationError::SystemProtectedObject) } else { @@ -329,19 +328,19 @@ mod tests { ), ( Attribute::AcpModifyRemovedAttr, - Value::new_iutf8("domain_display_name") + Attribute::DomainDisplayName.to_value() ), ( Attribute::AcpModifyRemovedAttr, - Value::new_iutf8("domain_uuid") + Attribute::DomainUuid.to_value() ), ( Attribute::AcpModifyRemovedAttr, - Value::new_iutf8("domain_ssid") + Attribute::DomainSsid.to_value() ), ( Attribute::AcpModifyRemovedAttr, - Value::new_iutf8("fernet_private_key_str") + Attribute::FernetPrivateKeyStr.to_value() ), ( Attribute::AcpModifyRemovedAttr, @@ -360,23 +359,23 @@ mod tests { (Attribute::AcpModifyPresentAttr, Attribute::Must.to_value()), ( Attribute::AcpModifyPresentAttr, - Value::new_iutf8("domain_name") + Attribute::DomainName.to_value() ), ( Attribute::AcpModifyPresentAttr, - Value::new_iutf8("domain_display_name") + Attribute::DomainDisplayName.to_value() ), ( Attribute::AcpModifyPresentAttr, - Value::new_iutf8("domain_uuid") + Attribute::DomainUuid.to_value() ), ( Attribute::AcpModifyPresentAttr, - Value::new_iutf8("domain_ssid") + Attribute::DomainSsid.to_value() ), ( Attribute::AcpModifyPresentAttr, - Value::new_iutf8("fernet_private_key_str") + Attribute::FernetPrivateKeyStr.to_value() ), ( Attribute::AcpModifyPresentAttr, @@ -397,14 +396,14 @@ mod tests { (Attribute::AcpCreateAttr, Attribute::DomainName.to_value(),), ( Attribute::AcpCreateAttr, - Value::new_iutf8("domain_display_name") + Attribute::DomainDisplayName.to_value() ), - (Attribute::AcpCreateAttr, Value::new_iutf8("domain_uuid")), - (Attribute::AcpCreateAttr, Value::new_iutf8("domain_ssid")), + (Attribute::AcpCreateAttr, Attribute::DomainUuid.to_value()), + (Attribute::AcpCreateAttr, Attribute::DomainSsid.to_value()), (Attribute::AcpCreateAttr, Attribute::Uuid.to_value()), ( Attribute::AcpCreateAttr, - Value::new_iutf8("fernet_private_key_str") + Attribute::FernetPrivateKeyStr.to_value() ), ( Attribute::AcpCreateAttr, @@ -414,7 +413,7 @@ mod tests { Attribute::AcpCreateAttr, Attribute::PrivateCookieKey.to_value() ), - (Attribute::AcpCreateAttr, Value::new_iutf8("version")) + (Attribute::AcpCreateAttr, Attribute::Version.to_value()) ); pub static ref PRELOAD: Vec = vec![TEST_ACCOUNT.clone(), TEST_GROUP.clone(), ALLOW_ALL.clone()]; @@ -470,8 +469,8 @@ mod tests { preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))), modlist!([ - m_purge("displayname"), - m_pres("displayname", &Value::new_utf8s("system test")), + m_purge(Attribute::DisplayName), + m_pres(Attribute::DisplayName, &Value::new_utf8s("system test")), ]), Some(E_TEST_ACCOUNT.clone()), |_| {}, @@ -501,8 +500,8 @@ mod tests { preload, filter!(f_eq(Attribute::ClassName, EntryClass::TestClass.into())), modlist!([ - m_pres(Attribute::May.as_ref(), &Attribute::Name.to_value()), - m_pres(Attribute::Must.as_ref(), &Attribute::Name.to_value()), + m_pres(Attribute::May, &Attribute::Name.to_value()), + m_pres(Attribute::Must, &Attribute::Name.to_value()), ]), Some(E_TEST_ACCOUNT.clone()), |_| {}, @@ -570,8 +569,8 @@ mod tests { PartialValue::new_iname("domain_example.net.au") )), modlist!([ - m_purge("domain_ssid"), - m_pres("domain_ssid", &Value::new_utf8s("NewExampleWifi")), + m_purge(Attribute::DomainSsid), + m_pres(Attribute::DomainSsid, &Value::new_utf8s("NewExampleWifi")), ]), Some(E_TEST_ACCOUNT.clone()), |_| {}, diff --git a/server/lib/src/plugins/refint.rs b/server/lib/src/plugins/refint.rs index ff812e190..870189c06 100644 --- a/server/lib/src/plugins/refint.rs +++ b/server/lib/src/plugins/refint.rs @@ -127,11 +127,12 @@ impl ReferentialIntegrity { let mut work_set = qs.internal_search_writeable(&filt)?; - work_set.iter_mut().for_each(|(_, post)| { - ref_types - .values() - .for_each(|attr| post.remove_avas(attr.name.as_str(), &removed_ids)); - }); + for (_, post) in work_set.iter_mut() { + for schema_attribute in ref_types.values() { + let attribute = (&schema_attribute.name).try_into()?; + post.remove_avas(attribute, &removed_ids); + } + } qs.internal_apply_writable(work_set) } @@ -322,8 +323,19 @@ impl Plugin for ReferentialIntegrity { for c in &all_cand { // For all reference in each cand. for rtype in ref_types.values() { + let attr: Attribute = match (&rtype.name).try_into() { + Ok(val) => val, + Err(err) => { + // we shouldn't be able to get here... + admin_error!("verify referential integrity invalid attribute {} specified - please log this as a bug! {:?}", &rtype.name, err); + res.push(Err(ConsistencyError::InvalidAttributeType( + rtype.name.to_string(), + ))); + continue; + } + }; // If the attribute is present - if let Some(vs) = c.get_ava_set(&rtype.name) { + if let Some(vs) = c.get_ava_set(attr) { // For each value in the set. match vs.as_ref_uuid_iter() { Some(uuid_iter) => { @@ -356,20 +368,27 @@ impl ReferentialIntegrity { // Fast Path let mut vsiter = cand.iter().flat_map(|c| { // If it's dyngroup, skip member since this will be reset in the next step. - let dyn_group = - c.attribute_equality(Attribute::Class.as_ref(), &EntryClass::DynGroup.into()); + let dyn_group = c.attribute_equality(Attribute::Class, &EntryClass::DynGroup.into()); ref_types.values().filter_map(move |rtype| { // Skip dynamic members, these are recalculated by the // memberof plugin. - let skip_mb = dyn_group && rtype.name == "dynmember"; + let skip_mb = dyn_group && rtype.name == Attribute::DynMember.as_ref(); // Skip memberOf, also recalculated. - let skip_mo = rtype.name == "memberof"; + let skip_mo = rtype.name == Attribute::MemberOf.as_ref(); if skip_mb || skip_mo { None } else { trace!(rtype_name = ?rtype.name, "examining"); - c.get_ava_set(&rtype.name) + c.get_ava_set( + (&rtype.name) + .try_into() + .map_err(|e| { + admin_error!(?e, "invalid attribute type {}", &rtype.name); + None:: + }) + .ok()?, + ) } }) }); @@ -978,7 +997,7 @@ mod tests { .expect("Internal search failure"); let ue = cands.first().expect("No entry"); assert!(ue - .get_ava_as_oauthscopemaps("oauth2_rs_scope_map") + .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap) .is_none()) } ); @@ -1104,8 +1123,8 @@ mod tests { // Still there let entry = server_txn.internal_search_uuid(tuuid).expect("failed"); - assert!(entry.attribute_equality(Attribute::UserAuthTokenSession.as_ref(), &pv_parent_id)); - assert!(entry.attribute_equality(Attribute::OAuth2Session.as_ref(), &pv_session_id)); + assert!(entry.attribute_equality(Attribute::UserAuthTokenSession, &pv_parent_id)); + assert!(entry.attribute_equality(Attribute::OAuth2Session, &pv_session_id)); // Delete the oauth2 resource server. assert!(server_txn.internal_delete_uuid(rs_uuid).is_ok()); @@ -1115,14 +1134,14 @@ mod tests { // Note the uat is present still. let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&parent_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::NeverExpires)); // The oauth2 session is revoked. let session = entry - .get_ava_as_oauth2session_map(Attribute::OAuth2Session.into()) + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .and_then(|sessions| sessions.get(&session_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::RevokedAt(_))); @@ -1177,8 +1196,8 @@ mod tests { .expect("Failed to access dyn group"); let dyn_member = dyna - .get_ava_refer("dynmember") - .expect("Failed to get member attribute"); + .get_ava_refer(Attribute::DynMember) + .expect("Failed to get dyn member attribute"); assert!(dyn_member.len() == 1); assert!(dyn_member.contains(&tgroup_uuid)); @@ -1187,7 +1206,7 @@ mod tests { .expect("Failed to access mo group"); let grp_member = group - .get_ava_refer("memberof") + .get_ava_refer(Attribute::MemberOf) .expect("Failed to get memberof attribute"); assert!(grp_member.len() == 1); assert!(grp_member.contains(&dyn_uuid)); diff --git a/server/lib/src/plugins/session.rs b/server/lib/src/plugins/session.rs index 0e1b7fd45..77726f339 100644 --- a/server/lib/src/plugins/session.rs +++ b/server/lib/src/plugins/session.rs @@ -57,18 +57,18 @@ impl SessionConsistency { // * If the session's credential is no longer on the account, we remove the session. let cred_ids: BTreeSet = entry - .get_ava_single_credential(Attribute::PrimaryCredential.into()) + .get_ava_single_credential(Attribute::PrimaryCredential) .iter() .map(|c| c.uuid) .chain( - entry.get_ava_passkeys(Attribute::PassKeys.into()) + entry.get_ava_passkeys(Attribute::PassKeys) .iter() .flat_map(|pks| pks.keys().copied() ) ) .collect(); - let invalidate: Option> = entry.get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + let invalidate: Option> = entry.get_ava_as_session_map(Attribute::UserAuthTokenSession) .map(|sessions| { sessions.iter().filter_map(|(session_id, session)| { if !cred_ids.contains(&session.cred_id) { @@ -82,11 +82,11 @@ impl SessionConsistency { }); if let Some(invalidate) = invalidate.as_ref() { - entry.remove_avas(Attribute::UserAuthTokenSession.into(), invalidate); + entry.remove_avas(Attribute::UserAuthTokenSession, invalidate); } // * If a UAT is past its expiry, remove it. - let expired: Option> = entry.get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + let expired: Option> = entry.get_ava_as_session_map(Attribute::UserAuthTokenSession) .map(|sessions| { sessions.iter().filter_map(|(session_id, session)| { trace!(?session_id, ?session); @@ -102,14 +102,14 @@ impl SessionConsistency { }); if let Some(expired) = expired.as_ref() { - entry.remove_avas(Attribute::UserAuthTokenSession.into(), expired); + entry.remove_avas(Attribute::UserAuthTokenSession, expired); } // * If an oauth2 session is past it's expiry, remove it. // * If an oauth2 session is past the grace window, and no parent session exists, remove it. - let oauth2_remove: Option> = entry.get_ava_as_oauth2session_map("oauth2_session").map(|oauth2_sessions| { + let oauth2_remove: Option> = entry.get_ava_as_oauth2session_map(Attribute::OAuth2Session).map(|oauth2_sessions| { // If we have oauth2 sessions, we need to be able to lookup if sessions exist in the uat. - let sessions = entry.get_ava_as_session_map(Attribute::UserAuthTokenSession.into()); + let sessions = entry.get_ava_as_session_map(Attribute::UserAuthTokenSession); oauth2_sessions.iter().filter_map(|(o2_session_id, session)| { trace!(?o2_session_id, ?session); @@ -157,7 +157,7 @@ impl SessionConsistency { }); if let Some(oauth2_remove) = oauth2_remove.as_ref() { - entry.remove_avas(Attribute::OAuth2Session.as_ref(), oauth2_remove); + entry.remove_avas(Attribute::OAuth2Session, oauth2_remove); } Ok(()) @@ -255,7 +255,7 @@ mod tests { let entry = server_txn.internal_search_uuid(tuuid).expect("failed"); let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&session_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::ExpiresAt(_))); @@ -265,7 +265,7 @@ mod tests { // Mod again - anything will do. let modlist = ModifyList::new_purge_and_set( - Attribute::Description.as_ref(), + Attribute::Description, Value::new_utf8s("test person 1 change"), ); @@ -281,7 +281,7 @@ mod tests { // We get the attribute and have to check it's now in a revoked state. let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&session_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::RevokedAt(_))); @@ -418,13 +418,13 @@ mod tests { let entry = server_txn.internal_search_uuid(tuuid).expect("failed"); let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&parent_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::NeverExpires)); let session = entry - .get_ava_as_oauth2session_map(Attribute::OAuth2Session.into()) + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .and_then(|sessions| sessions.get(&session_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::ExpiresAt(_))); @@ -437,7 +437,7 @@ mod tests { // Mod again - anything will do. let modlist = ModifyList::new_purge_and_set( - Attribute::Description.as_ref(), + Attribute::Description, Value::new_utf8s("test person 1 change"), ); @@ -453,13 +453,13 @@ mod tests { // Note the uat is still present let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&parent_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::NeverExpires)); let session = entry - .get_ava_as_oauth2session_map(Attribute::OAuth2Session.into()) + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .and_then(|sessions| sessions.get(&session_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::RevokedAt(_))); @@ -592,13 +592,13 @@ mod tests { let entry = server_txn.internal_search_uuid(tuuid).expect("failed"); let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&parent_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::NeverExpires)); let session = entry - .get_ava_as_oauth2session_map(Attribute::OAuth2Session.into()) + .get_ava_as_oauth2session_map(Attribute::OAuth2Session) .and_then(|sessions| sessions.get(&session_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::NeverExpires)); @@ -625,7 +625,7 @@ mod tests { // Note the uat is removed let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&parent_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::RevokedAt(_))); @@ -748,7 +748,7 @@ mod tests { // Mod again - anything will do. let modlist = ModifyList::new_purge_and_set( - Attribute::Description.as_ref(), + Attribute::Description, Value::new_utf8s("test person 1 change"), ); @@ -842,7 +842,7 @@ mod tests { let entry = server_txn.internal_search_uuid(tuuid).expect("failed"); let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&session_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::NeverExpires)); @@ -853,7 +853,7 @@ mod tests { let mut server_txn = server.write(curtime).await; // Remove the primary credential - let modlist = ModifyList::new_purge(Attribute::PrimaryCredential.into()); + let modlist = ModifyList::new_purge(Attribute::PrimaryCredential); server_txn .internal_modify( @@ -867,7 +867,7 @@ mod tests { // Note it's a not condition now. let session = entry - .get_ava_as_session_map(Attribute::UserAuthTokenSession.into()) + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&session_id)) .expect("No session map found"); assert!(matches!(session.state, SessionState::RevokedAt(_))); diff --git a/server/lib/src/plugins/spn.rs b/server/lib/src/plugins/spn.rs index 359c18bbf..9184e8ffe 100644 --- a/server/lib/src/plugins/spn.rs +++ b/server/lib/src/plugins/spn.rs @@ -117,7 +117,7 @@ impl Plugin for Spn { r.push(Err(ConsistencyError::InvalidSpn(e.get_id()))); continue; }; - match e.get_ava_single("spn") { + match e.get_ava_single(Attribute::Spn) { Some(r_spn) => { trace!("verify spn: s {:?} == ex {:?} ?", r_spn, g_spn); if r_spn != g_spn { @@ -149,8 +149,8 @@ impl Spn { let domain_name = qs.get_domain_name(); for ent in cand.iter_mut() { - if ent.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Group.into()) - || ent.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Account.into()) + if ent.attribute_equality(Attribute::Class, &EntryClass::Group.into()) + || ent.attribute_equality(Attribute::Class, &EntryClass::Account.into()) { let spn = ent .generate_spn(domain_name) @@ -162,8 +162,13 @@ impl Spn { ); e })?; - trace!("plugin_spn: set spn to {:?}", spn); - ent.set_ava("spn", once(spn)); + trace!( + "plugin_{}: set {} to {:?}", + Attribute::Spn, + Attribute::Spn, + spn + ); + ent.set_ava(Attribute::Spn, once(spn)); } } Ok(()) @@ -177,9 +182,9 @@ impl Spn { // On modify, if changing domain_name on UUID_DOMAIN_INFO trigger the spn regen let domain_name_changed = cand.iter().zip(pre_cand.iter()).find_map(|(post, pre)| { - let domain_name = post.get_ava_single("domain_name"); - if post.attribute_equality(Attribute::Uuid.as_ref(), &PVUUID_DOMAIN_INFO) - && domain_name != pre.get_ava_single("domain_name") + let domain_name = post.get_ava_single(Attribute::DomainName); + if post.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO) + && domain_name != pre.get_ava_single(Attribute::DomainName) { domain_name } else { @@ -209,7 +214,7 @@ impl Spn { f_eq(Attribute::Class, EntryClass::Group.into()), f_eq(Attribute::Class, EntryClass::Account.into()), ])), - &modlist!([m_purge("spn")]), + &modlist!([m_purge(Attribute::Spn)]), ) } } @@ -265,7 +270,7 @@ mod tests { Ok(()), preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))), - modlist!([m_purge("spn")]), + modlist!([m_purge(Attribute::Spn)]), None, |_| {}, |_| {} @@ -320,8 +325,11 @@ mod tests { preload, filter!(f_eq(Attribute::Name, PartialValue::new_iname("testperson"))), modlist!([ - m_purge("spn"), - m_pres("spn", &Value::new_spn_str("invalid", "spn")) + m_purge(Attribute::Spn), + m_pres( + Attribute::Spn, + &Value::new_spn_str("invalid", Attribute::Spn.as_ref()) + ) ]), None, |_| {}, @@ -341,7 +349,7 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("must not fail"); - let e_pre_spn = e_pre.get_ava_single("spn").expect("must not fail"); + let e_pre_spn = e_pre.get_ava_single(Attribute::Spn).expect("must not fail"); assert!(e_pre_spn == ex1); // trigger the domain_name change (this will be a cli option to the server @@ -356,7 +364,9 @@ mod tests { .internal_search_uuid(UUID_ADMIN) .expect("must not fail"); - let e_post_spn = e_post.get_ava_single("spn").expect("must not fail"); + let e_post_spn = e_post + .get_ava_single(Attribute::Spn) + .expect("must not fail"); debug!("{:?}", e_post_spn); debug!("{:?}", ex2); assert!(e_post_spn == ex2); diff --git a/server/lib/src/repl/consumer.rs b/server/lib/src/repl/consumer.rs index e7fba29dd..e421d15b7 100644 --- a/server/lib/src/repl/consumer.rs +++ b/server/lib/src/repl/consumer.rs @@ -221,10 +221,7 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_cand.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality( - Attribute::Class.into(), - &EntryClass::AccessControlProfile.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) }) } if !self.changed_oauth2 { @@ -232,19 +229,14 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_cand.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality( - Attribute::Class.into(), - &EntryClass::OAuth2ResourceServer.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) }); } if !self.changed_sync_agreement { self.changed_sync_agreement = cand .iter() .chain(pre_cand.iter().map(|e| e.as_ref())) - .any(|e| { - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncAccount.into()) - }); + .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into())); } trace!( diff --git a/server/lib/src/repl/entry-changelog.rs b/server/lib/src/repl/entry-changelog.rs index 71f300d10..ac2095c67 100644 --- a/server/lib/src/repl/entry-changelog.rs +++ b/server/lib/src/repl/entry-changelog.rs @@ -251,25 +251,25 @@ impl EntryChangelog { EntryChangelog { anchors, changes } } - pub fn add_ava_iter(&mut self, cid: &Cid, attr: &str, viter: T) - where - T: IntoIterator, - { - if !self.changes.contains_key(cid) { - self.changes.insert(cid.clone(), Change { s: Vec::new() }); - } + // fn add_ava_iter(&mut self, cid: &Cid, attr: Attribute, viter: T) + // where + // T: IntoIterator, + // { + // if !self.changes.contains_key(cid) { + // self.changes.insert(cid.clone(), Change { s: Vec::new() }); + // } - #[allow(clippy::expect_used)] - let change = self - .changes - .get_mut(cid) - .expect("Memory corruption, change must exist"); + // #[allow(clippy::expect_used)] + // let change = self + // .changes + // .get_mut(cid) + // .expect("Memory corruption, change must exist"); - viter - .into_iter() - .map(|v| Transition::ModifyPresent(AttrString::from(attr), Box::new(v))) - .for_each(|t| change.s.push(t)); - } + // viter + // .into_iter() + // .map(|v| Transition::ModifyPresent(attr.into(), Box::new(v))) + // .for_each(|t| change.s.push(t)); + // } pub fn remove_ava_iter(&mut self, cid: &Cid, attr: &str, viter: T) where @@ -291,7 +291,7 @@ impl EntryChangelog { .for_each(|t| change.s.push(t)); } - pub fn assert_ava(&mut self, cid: &Cid, attr: &str, value: PartialValue) { + pub fn assert_ava(&mut self, cid: &Cid, attr: Attribute, value: PartialValue) { if !self.changes.contains_key(cid) { self.changes.insert(cid.clone(), Change { s: Vec::new() }); } @@ -302,25 +302,22 @@ impl EntryChangelog { .get_mut(cid) .expect("Memory corruption, change must exist"); - change.s.push(Transition::ModifyAssert( - AttrString::from(attr), - Box::new(value), - )) - } - - pub fn purge_ava(&mut self, cid: &Cid, attr: &str) { - if !self.changes.contains_key(cid) { - self.changes.insert(cid.clone(), Change { s: Vec::new() }); - } - - #[allow(clippy::expect_used)] - let change = self - .changes - .get_mut(cid) - .expect("Memory corruption, change must exist"); change .s - .push(Transition::ModifyPurge(AttrString::from(attr))); + .push(Transition::ModifyAssert(attr.into(), Box::new(value))) + } + + pub fn purge_ava(&mut self, cid: &Cid, attr: Attribute) { + if !self.changes.contains_key(cid) { + self.changes.insert(cid.clone(), Change { s: Vec::new() }); + } + + #[allow(clippy::expect_used)] + let change = self + .changes + .get_mut(cid) + .expect("Memory corruption, change must exist"); + change.s.push(Transition::ModifyPurge(attr.info())); } pub fn recycled(&mut self, cid: &Cid) { diff --git a/server/lib/src/repl/entry.rs b/server/lib/src/repl/entry.rs index 367ff68fd..f2795a71f 100644 --- a/server/lib/src/repl/entry.rs +++ b/server/lib/src/repl/entry.rs @@ -87,13 +87,13 @@ impl EntryChangeState { EntryChangeState { st } } - pub fn change_ava(&mut self, cid: &Cid, attr: &str) { + pub fn change_ava(&mut self, cid: &Cid, attr: Attribute) { match &mut self.st { State::Live { at: _, ref mut changes, } => { - if let Some(change) = changes.get_mut(attr) { + if let Some(change) = changes.get_mut(attr.as_ref()) { // Update the cid. if change != cid { *change = cid.clone() diff --git a/server/lib/src/repl/proto.rs b/server/lib/src/repl/proto.rs index 6236ecee1..afde4ed70 100644 --- a/server/lib/src/repl/proto.rs +++ b/server/lib/src/repl/proto.rs @@ -528,7 +528,7 @@ impl ReplEntryV1 { let mut eattrs = Eattrs::default(); - let class_ava = vs_iutf8!["object", "tombstone"]; + let class_ava = vs_iutf8![EntryClass::Object.into(), EntryClass::Tombstone.into()]; let last_mod_ava = vs_cid![at.clone()]; eattrs.insert(Attribute::Uuid.into(), vs_uuid![self.uuid]); diff --git a/server/lib/src/repl/supplier.rs b/server/lib/src/repl/supplier.rs index 322c18297..f4ae5784a 100644 --- a/server/lib/src/repl/supplier.rs +++ b/server/lib/src/repl/supplier.rs @@ -78,7 +78,7 @@ impl<'a> QueryServerReadTransaction<'a> { // Separate the entries into schema, meta and remaining. let (schema_entries, rem_entries): (Vec<_>, Vec<_>) = entries.into_iter().partition(|e| { - e.get_ava_set(Attribute::Class.as_ref()) + e.get_ava_set(Attribute::Class) .map(|cls| { cls.contains(&EntryClass::AttributeType.into() as &PartialValue) || cls.contains(&EntryClass::ClassType.into() as &PartialValue) @@ -87,7 +87,7 @@ impl<'a> QueryServerReadTransaction<'a> { }); let (meta_entries, entries): (Vec<_>, Vec<_>) = rem_entries.into_iter().partition(|e| { - e.get_ava_set("uuid") + e.get_ava_set(Attribute::Uuid) .map(|uset| { uset.contains(&PVUUID_DOMAIN_INFO as &PartialValue) || uset.contains(&PVUUID_SYSTEM_INFO as &PartialValue) diff --git a/server/lib/src/repl/tests.rs b/server/lib/src/repl/tests.rs index fab7ada8f..d5d6d4e1c 100644 --- a/server/lib/src/repl/tests.rs +++ b/server/lib/src/repl/tests.rs @@ -446,7 +446,7 @@ async fn test_repl_increment_basic_entry_tombstone(server_a: &QueryServer, serve .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); assert!(e1 == e2); @@ -612,10 +612,7 @@ async fn test_repl_increment_basic_bidirectional_write( // Now perform a write on A assert!(server_a_txn - .internal_modify_uuid( - t_uuid, - &ModifyList::new_purge(Attribute::Description.as_ref()) - ) + .internal_modify_uuid(t_uuid, &ModifyList::new_purge(Attribute::Description)) .is_ok()); server_a_txn.commit().expect("Failed to commit"); @@ -637,7 +634,7 @@ async fn test_repl_increment_basic_bidirectional_write( // They are consistent again. assert!(e1 == e2); - assert!(e1.get_ava_set(Attribute::Description.as_ref()).is_none()); + assert!(e1.get_ava_set(Attribute::Description).is_none()); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -676,10 +673,7 @@ async fn test_repl_increment_basic_deleted_attr(server_a: &QueryServer, server_b // presence let mut server_a_txn = server_a.write(duration_from_epoch_now()).await; assert!(server_a_txn - .internal_modify_uuid( - t_uuid, - &ModifyList::new_purge(Attribute::Description.as_ref()) - ) + .internal_modify_uuid(t_uuid, &ModifyList::new_purge(Attribute::Description)) .is_ok()); server_a_txn.commit().expect("Failed to commit"); @@ -698,7 +692,7 @@ async fn test_repl_increment_basic_deleted_attr(server_a: &QueryServer, server_b .expect("Unable to access entry."); // They are consistent again. - assert!(e1.get_ava_set(Attribute::Description.as_ref()).is_none()); + assert!(e1.get_ava_set(Attribute::Description).is_none()); assert!(e1 == e2); let e1_cs = e1.get_changestate(); @@ -767,10 +761,7 @@ async fn test_repl_increment_simultaneous_bidirectional_write( assert!(server_a_txn .internal_modify_uuid( t_uuid, - &ModifyList::new_purge_and_set( - Attribute::Description.as_ref(), - Value::new_utf8s("repl_test") - ) + &ModifyList::new_purge_and_set(Attribute::Description, Value::new_utf8s("repl_test")) ) .is_ok()); @@ -782,7 +773,7 @@ async fn test_repl_increment_simultaneous_bidirectional_write( assert!(server_b_txn .internal_modify_uuid( t_uuid, - &ModifyList::new_purge_and_set("displayname", Value::new_utf8s("repl_test")) + &ModifyList::new_purge_and_set(Attribute::DisplayName, Value::new_utf8s("repl_test")) ) .is_ok()); @@ -816,8 +807,8 @@ async fn test_repl_increment_simultaneous_bidirectional_write( // They are consistent again. assert!(e1 == e2); - assert!(e1.get_ava_single_utf8(Attribute::Description.as_ref()) == Some("repl_test")); - assert!(e1.get_ava_single_utf8("displayname") == Some("repl_test")); + assert!(e1.get_ava_single_utf8(Attribute::Description) == Some("repl_test")); + assert!(e1.get_ava_single_utf8(Attribute::DisplayName) == Some("repl_test")); } // Create entry on A -> B @@ -900,7 +891,7 @@ async fn test_repl_increment_basic_bidirectional_lifecycle( // They are consistent again. assert!(e1 == e2); - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Recycled.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -934,9 +925,9 @@ async fn test_repl_increment_basic_bidirectional_lifecycle( // They are NOT consistent. assert!(e1 != e2); // E1 from A is NOT a tombstone ... yet. - assert!(!e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(!e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); // E2 from B is a tombstone! - assert!(e2.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e2.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -955,7 +946,7 @@ async fn test_repl_increment_basic_bidirectional_lifecycle( .expect("Unable to access entry."); // Ts on both. - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); assert!(e1 == e2); server_a_txn.commit().expect("Failed to commit"); @@ -1046,7 +1037,7 @@ async fn test_repl_increment_basic_bidirectional_recycle( // They are equal, but their CL states are not. e2 should have been // retained due to being the latest! assert!(e1 == e2); - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Recycled.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Recycled.into())); // Remember entry comparison doesn't compare last_mod_cid. assert!(e1.get_last_changed() < e2.get_last_changed()); @@ -1167,8 +1158,8 @@ async fn test_repl_increment_basic_bidirectional_tombstone( .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); - assert!(e2.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); + assert!(e2.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); trace!("{:?}", e1.get_last_changed()); trace!("{:?}", e2.get_last_changed()); assert!(e1.get_last_changed() < e2.get_last_changed()); @@ -1189,8 +1180,8 @@ async fn test_repl_increment_basic_bidirectional_tombstone( .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); - assert!(e2.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); + assert!(e2.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); assert!(e1.get_last_changed() == e2.get_last_changed()); server_b_txn.commit().expect("Failed to commit"); @@ -1290,7 +1281,7 @@ async fn test_repl_increment_creation_uuid_conflict( // Should be a vec. .pop() .expect("No conflict entries present"); - assert!(cnf_a.get_ava_single_iname(Attribute::Name.as_ref()) == Some("testperson1")); + assert!(cnf_a.get_ava_single_iname(Attribute::Name) == Some("testperson1")); let cnf_b = server_b_txn .internal_search_conflict_uuid(t_uuid) @@ -1395,9 +1386,9 @@ async fn test_repl_increment_create_tombstone_uuid_conflict( .expect("Unable to access entry."); assert!(e1 != e2); // E1 from A is a ts - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); // E2 from B is not a TS - assert!(!e2.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(!e2.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); server_a_txn.commit().expect("Failed to commit"); drop(server_b_txn); @@ -1417,7 +1408,7 @@ async fn test_repl_increment_create_tombstone_uuid_conflict( .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); assert!(e1 == e2); - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -1492,8 +1483,8 @@ async fn test_repl_increment_create_tombstone_conflict( assert!(e1.get_last_changed() > e2.get_last_changed()); // Yet, they are both TS. Curious. - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); - assert!(e2.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); + assert!(e2.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -1513,7 +1504,7 @@ async fn test_repl_increment_create_tombstone_conflict( .expect("Unable to access entry."); assert!(e1 == e2); - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Tombstone.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Tombstone.into())); server_a_txn.commit().expect("Failed to commit"); drop(server_b_txn); @@ -1574,10 +1565,10 @@ async fn test_repl_increment_schema_conflict(server_a: &QueryServer, server_b: & let ct = ct + Duration::from_secs(1); let mut server_b_txn = server_b.write(ct).await; let modlist = ModifyList::new_list(vec![ - Modify::Removed("class".into(), EntryClass::Person.into()), - Modify::Present("class".into(), EntryClass::Group.into()), - Modify::Purged("id_verification_eckey".into()), - Modify::Purged("displayname".into()), + Modify::Removed(Attribute::Class.into(), EntryClass::Person.into()), + Modify::Present(Attribute::Class.into(), EntryClass::Group.into()), + Modify::Purged(Attribute::IdVerificationEcKey.into()), + Modify::Purged(Attribute::DisplayName.into()), ]); assert!(server_b_txn.internal_modify_uuid(t_uuid, &modlist).is_ok()); server_b_txn.commit().expect("Failed to commit"); @@ -1589,7 +1580,7 @@ async fn test_repl_increment_schema_conflict(server_a: &QueryServer, server_b: & .internal_modify_uuid( t_uuid, &ModifyList::new_purge_and_set( - "displayname", + Attribute::DisplayName, Value::Utf8("Updated displayname".to_string()) ) ) @@ -1610,7 +1601,7 @@ async fn test_repl_increment_schema_conflict(server_a: &QueryServer, server_b: & .internal_search_all_uuid(t_uuid) .expect("Unable to access new entry."); - assert!(e1.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Conflict.into())); + assert!(e1.attribute_equality(Attribute::Class, &EntryClass::Conflict.into())); server_a_txn.commit().expect("Failed to commit"); drop(server_b_txn); @@ -1626,7 +1617,7 @@ async fn test_repl_increment_schema_conflict(server_a: &QueryServer, server_b: & .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); - assert!(e2.attribute_equality(Attribute::Class.as_ref(), &EntryClass::Conflict.into())); + assert!(e2.attribute_equality(Attribute::Class, &EntryClass::Conflict.into())); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -1693,7 +1684,7 @@ async fn test_repl_increment_consumer_lagging_attributes( .internal_modify_uuid( t_uuid, &ModifyList::new_purge_and_set( - "displayname", + Attribute::DisplayName, Value::Utf8("Updated displayname".to_string()) ) ) @@ -1956,7 +1947,7 @@ async fn test_repl_increment_domain_rename(server_a: &QueryServer, server_b: &Qu .internal_search_all_uuid(UUID_ADMIN) .expect("Unable to access entry."); - let vx1 = e1.get_ava_single("spn").expect("spn not present"); + let vx1 = e1.get_ava_single(Attribute::Spn).expect("spn not present"); let ex1 = Value::new_spn_str("admin", "new.example.com"); assert!(vx1 == ex1); @@ -1983,7 +1974,7 @@ async fn test_repl_increment_domain_rename(server_a: &QueryServer, server_b: &Qu .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); - let vx2 = e2.get_ava_single("spn").expect("spn not present"); + let vx2 = e2.get_ava_single(Attribute::Spn).expect("spn not present"); let ex2 = Value::new_spn_str("testperson1", "new.example.com"); assert!(vx2 == ex2); @@ -2009,9 +2000,9 @@ async fn test_repl_increment_domain_rename(server_a: &QueryServer, server_b: &Qu .expect("Unable to access new entry."); let e2 = server_b_txn .internal_search_all_uuid(UUID_ADMIN) - .expect("Unable to access entry."); + .expect("get_ava_single(spn)."); - let vx1 = e1.get_ava_single("spn").expect("spn not present"); + let vx1 = e1.get_ava_single(Attribute::Spn).expect("spn not present"); let ex1 = Value::new_spn_str("admin", "new.example.com"); assert!(vx1 == ex1); assert!(e1 == e2); @@ -2028,7 +2019,7 @@ async fn test_repl_increment_domain_rename(server_a: &QueryServer, server_b: &Qu .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); - let vx2 = e2.get_ava_single("spn").expect("spn not present"); + let vx2 = e2.get_ava_single(Attribute::Spn).expect("spn not present"); let ex2 = Value::new_spn_str("testperson1", "new.example.com"); assert!(vx2 == ex2); assert!(e1 == e2); @@ -2182,10 +2173,10 @@ async fn test_repl_increment_memberof_basic(server_a: &QueryServer, server_b: &Q .expect("Unable to access entry."); assert!(e1 == e2); - assert!(e1.attribute_equality(Attribute::MemberOf.as_ref(), &PartialValue::Refer(g_uuid))); + assert!(e1.attribute_equality(Attribute::MemberOf, &PartialValue::Refer(g_uuid))); // We should also check dyngroups too here :) assert!(e1.attribute_equality( - Attribute::MemberOf.as_ref(), + Attribute::MemberOf, &PartialValue::Refer(UUID_IDM_ALL_ACCOUNTS) )); @@ -2261,16 +2252,16 @@ async fn test_repl_increment_memberof_conflict(server_a: &QueryServer, server_b: let e = server_b_txn .internal_search_all_uuid(g_uuid) .expect("Unable to access entry."); - assert!(!e.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(t_uuid))); + assert!(!e.attribute_equality(Attribute::Member, &PartialValue::Refer(t_uuid))); assert!(e.attribute_equality( - Attribute::Name.as_ref(), + Attribute::Name, &PartialValue::new_iname("testgroup_conflict") )); let e = server_b_txn .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); - assert!(!e.attribute_equality(Attribute::MemberOf.as_ref(), &PartialValue::Refer(g_uuid))); + assert!(!e.attribute_equality(Attribute::MemberOf, &PartialValue::Refer(g_uuid))); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -2290,9 +2281,9 @@ async fn test_repl_increment_memberof_conflict(server_a: &QueryServer, server_b: .expect("Unable to access entry."); assert!(e1 == e2); - assert!(!e1.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(t_uuid))); + assert!(!e1.attribute_equality(Attribute::Member, &PartialValue::Refer(t_uuid))); assert!(e1.attribute_equality( - Attribute::Name.as_ref(), + Attribute::Name, &PartialValue::new_iname("testgroup_conflict") )); @@ -2304,7 +2295,7 @@ async fn test_repl_increment_memberof_conflict(server_a: &QueryServer, server_b: .expect("Unable to access entry."); assert!(e1 == e2); - assert!(!e1.attribute_equality(Attribute::MemberOf.as_ref(), &PartialValue::Refer(g_uuid))); + assert!(!e1.attribute_equality(Attribute::MemberOf, &PartialValue::Refer(g_uuid))); server_a_txn.commit().expect("Failed to commit"); drop(server_b_txn); @@ -2375,7 +2366,7 @@ async fn test_repl_increment_refint_tombstone(server_a: &QueryServer, server_b: assert!(server_a_txn .internal_modify_uuid( g_uuid, - &ModifyList::new_purge_and_set(Attribute::Member.as_ref(), Value::Refer(t_uuid)) + &ModifyList::new_purge_and_set(Attribute::Member, Value::Refer(t_uuid)) ) .is_ok()); server_a_txn.commit().expect("Failed to commit"); @@ -2392,7 +2383,7 @@ async fn test_repl_increment_refint_tombstone(server_a: &QueryServer, server_b: let e = server_b_txn .internal_search_all_uuid(g_uuid) .expect("Unable to access entry."); - assert!(!e.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(t_uuid))); + assert!(!e.attribute_equality(Attribute::Member, &PartialValue::Refer(t_uuid))); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -2418,7 +2409,7 @@ async fn test_repl_increment_refint_tombstone(server_a: &QueryServer, server_b: assert!(e1_cs == e2_cs); assert!(e1 == e2); - assert!(!e1.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(t_uuid))); + assert!(!e1.attribute_equality(Attribute::Member, &PartialValue::Refer(t_uuid))); server_a_txn.commit().expect("Failed to commit"); drop(server_b_txn); @@ -2495,7 +2486,7 @@ async fn test_repl_increment_refint_conflict(server_a: &QueryServer, server_b: & let e = server_b_txn .internal_search_all_uuid(g_uuid) .expect("Unable to access entry."); - assert!(!e.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(t_uuid))); + assert!(!e.attribute_equality(Attribute::Member, &PartialValue::Refer(t_uuid))); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -2519,7 +2510,7 @@ async fn test_repl_increment_refint_conflict(server_a: &QueryServer, server_b: & assert!(e1_cs == e2_cs); assert!(e1 == e2); - assert!(!e1.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(t_uuid))); + assert!(!e1.attribute_equality(Attribute::Member, &PartialValue::Refer(t_uuid))); server_a_txn.commit().expect("Failed to commit"); drop(server_b_txn); @@ -2587,7 +2578,7 @@ async fn test_repl_increment_refint_delete_to_member_holder( assert!(server_a_txn .internal_modify_uuid( g_uuid, - &ModifyList::new_purge_and_set(Attribute::Member.as_ref(), Value::Refer(t_uuid)) + &ModifyList::new_purge_and_set(Attribute::Member, Value::Refer(t_uuid)) ) .is_ok()); server_a_txn.commit().expect("Failed to commit"); @@ -2609,7 +2600,7 @@ async fn test_repl_increment_refint_delete_to_member_holder( let e = server_a_txn .internal_search_all_uuid(g_uuid) .expect("Unable to access entry."); - assert!(!e.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(t_uuid))); + assert!(!e.attribute_equality(Attribute::Member, &PartialValue::Refer(t_uuid))); server_a_txn.commit().expect("Failed to commit"); drop(server_b_txn); @@ -2634,7 +2625,7 @@ async fn test_repl_increment_refint_delete_to_member_holder( assert!(e1_cs == e2_cs); assert!(e1 == e2); - assert!(!e1.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(t_uuid))); + assert!(!e1.attribute_equality(Attribute::Member, &PartialValue::Refer(t_uuid))); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -2746,10 +2737,7 @@ async fn test_repl_increment_attrunique_conflict_basic( assert!(server_a_txn .internal_modify_uuid( g_a_uuid, - &ModifyList::new_purge_and_set( - Attribute::Name.as_ref(), - Value::new_iname("name_conflict") - ) + &ModifyList::new_purge_and_set(Attribute::Name, Value::new_iname("name_conflict")) ) .is_ok()); server_a_txn.commit().expect("Failed to commit"); @@ -2758,10 +2746,7 @@ async fn test_repl_increment_attrunique_conflict_basic( assert!(server_b_txn .internal_modify_uuid( g_b_uuid, - &ModifyList::new_purge_and_set( - Attribute::Name.as_ref(), - Value::new_iname("name_conflict") - ) + &ModifyList::new_purge_and_set(Attribute::Name, Value::new_iname("name_conflict")) ) .is_ok()); server_b_txn.commit().expect("Failed to commit"); @@ -2785,28 +2770,28 @@ async fn test_repl_increment_attrunique_conflict_basic( .expect("Unable to search conflict entries.") .pop() .expect("No conflict entries present"); - assert!(cnf_a.get_ava_single_iname("name") == Some("name_conflict")); + assert!(cnf_a.get_ava_single_iname(Attribute::Name) == Some("name_conflict")); let cnf_b = server_a_txn .internal_search_conflict_uuid(g_b_uuid) .expect("Unable to search conflict entries.") .pop() .expect("No conflict entries present"); - assert!(cnf_b.get_ava_single_iname("name") == Some("name_conflict")); + assert!(cnf_b.get_ava_single_iname(Attribute::Name) == Some("name_conflict")); // Check the person has MO A/B removed. let e = server_a_txn .internal_search_all_uuid(t_uuid) .expect("Unable to access entry."); - assert!(!e.attribute_equality(Attribute::MemberOf.as_ref(), &PartialValue::Refer(g_a_uuid))); - assert!(!e.attribute_equality(Attribute::MemberOf.as_ref(), &PartialValue::Refer(g_b_uuid))); + assert!(!e.attribute_equality(Attribute::MemberOf, &PartialValue::Refer(g_a_uuid))); + assert!(!e.attribute_equality(Attribute::MemberOf, &PartialValue::Refer(g_b_uuid))); // Check the group has M A/B removed. let e = server_a_txn .internal_search_all_uuid(g_c_uuid) .expect("Unable to access entry."); - assert!(!e.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(g_a_uuid))); - assert!(!e.attribute_equality(Attribute::Member.as_ref(), &PartialValue::Refer(g_b_uuid))); + assert!(!e.attribute_equality(Attribute::Member, &PartialValue::Refer(g_a_uuid))); + assert!(!e.attribute_equality(Attribute::Member, &PartialValue::Refer(g_b_uuid))); server_a_txn.commit().expect("Failed to commit"); drop(server_b_txn); @@ -2824,14 +2809,14 @@ async fn test_repl_increment_attrunique_conflict_basic( .expect("Unable to search conflict entries.") .pop() .expect("No conflict entries present"); - assert!(cnf_a.get_ava_single_iname("name") == Some("name_conflict")); + assert!(cnf_a.get_ava_single_iname(Attribute::Name) == Some("name_conflict")); let cnf_b = server_b_txn .internal_search_conflict_uuid(g_b_uuid) .expect("Unable to search conflict entries.") .pop() .expect("No conflict entries present"); - assert!(cnf_b.get_ava_single_iname("name") == Some("name_conflict")); + assert!(cnf_b.get_ava_single_iname(Attribute::Name) == Some("name_conflict")); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); @@ -2912,18 +2897,12 @@ async fn test_repl_increment_attrunique_conflict_complex( let e = server_a_txn .internal_search_all_uuid(g_a_uuid) .expect("Unable to access entry."); - assert!(e.attribute_equality( - Attribute::Name.as_ref(), - &PartialValue::new_iname("name_conflict") - )); + assert!(e.attribute_equality(Attribute::Name, &PartialValue::new_iname("name_conflict"))); let e = server_a_txn .internal_search_all_uuid(g_b_uuid) .expect("Unable to access entry."); - assert!(e.attribute_equality( - Attribute::Name.as_ref(), - &PartialValue::new_iname("uuid_conflict") - )); + assert!(e.attribute_equality(Attribute::Name, &PartialValue::new_iname("uuid_conflict"))); // The other entry will not be conflicted here, since A is not the origin node. @@ -2943,18 +2922,12 @@ async fn test_repl_increment_attrunique_conflict_complex( let e = server_b_txn .internal_search_all_uuid(g_a_uuid) .expect("Unable to access entry."); - assert!(e.attribute_equality( - Attribute::Name.as_ref(), - &PartialValue::new_iname("name_conflict") - )); + assert!(e.attribute_equality(Attribute::Name, &PartialValue::new_iname("name_conflict"))); let e = server_b_txn .internal_search_all_uuid(g_b_uuid) .expect("Unable to access entry."); - assert!(e.attribute_equality( - Attribute::Name.as_ref(), - &PartialValue::new_iname("uuid_conflict") - )); + assert!(e.attribute_equality(Attribute::Name, &PartialValue::new_iname("uuid_conflict"))); // Check the conflict was also now created. let cnf_a = server_b_txn @@ -2962,7 +2935,7 @@ async fn test_repl_increment_attrunique_conflict_complex( .expect("Unable to search conflict entries.") .pop() .expect("No conflict entries present"); - assert!(cnf_a.get_ava_single_iname("name") == Some("name_conflict")); + assert!(cnf_a.get_ava_single_iname(Attribute::Name) == Some("name_conflict")); server_b_txn.commit().expect("Failed to commit"); drop(server_a_txn); diff --git a/server/lib/src/schema.rs b/server/lib/src/schema.rs index fe47fcbb5..0dfff0894 100644 --- a/server/lib/src/schema.rs +++ b/server/lib/src/schema.rs @@ -101,16 +101,21 @@ impl SchemaAttribute { let uuid = value.get_uuid(); // class - if !value.attribute_equality(Attribute::Class.as_ref(), &EntryClass::AttributeType.into()) { - admin_error!("class attribute type not present - {:?}", uuid); - return Err(OperationError::InvalidSchemaState( - "missing attributetype".to_string(), - )); + if !value.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) { + admin_error!( + "class {} not present - {:?}", + EntryClass::AttributeType, + uuid + ); + return Err(OperationError::InvalidSchemaState(format!( + "missing {}", + EntryClass::AttributeType + ))); } // name let name = value - .get_ava_single_iutf8(Attribute::AttributeName.into()) + .get_ava_single_iutf8(Attribute::AttributeName) .map(|s| s.into()) .ok_or_else(|| { admin_error!("missing {} - {:?}", Attribute::AttributeName.as_ref(), uuid); @@ -118,7 +123,7 @@ impl SchemaAttribute { })?; // description let description = value - .get_ava_single_utf8(Attribute::Description.as_ref()) + .get_ava_single_utf8(Attribute::Description) .map(|s| s.to_string()) .ok_or_else(|| { admin_error!("missing {} - {}", Attribute::Description, name); @@ -127,44 +132,46 @@ impl SchemaAttribute { // multivalue let multivalue = value - .get_ava_single_bool(Attribute::MultiValue.as_ref()) + .get_ava_single_bool(Attribute::MultiValue) .ok_or_else(|| { admin_error!("missing {} - {}", Attribute::MultiValue, name); OperationError::InvalidSchemaState("missing multivalue".to_string()) })?; let unique = value - .get_ava_single_bool(Attribute::Unique.as_ref()) + .get_ava_single_bool(Attribute::Unique) .ok_or_else(|| { admin_error!("missing {} - {}", Attribute::Unique, name); OperationError::InvalidSchemaState("missing unique".to_string()) })?; let phantom = value - .get_ava_single_bool(Attribute::Phantom.as_ref()) + .get_ava_single_bool(Attribute::Phantom) .unwrap_or(false); let sync_allowed = value - .get_ava_single_bool(Attribute::SyncAllowed.as_ref()) + .get_ava_single_bool(Attribute::SyncAllowed) .unwrap_or(false); // Default, all attributes are replicated unless you opt in for them to NOT be. // Generally this is internal to the server only, so we don't advertise it. let replicated = value - .get_ava_single_bool(Attribute::Replicated.as_ref()) + .get_ava_single_bool(Attribute::Replicated) .unwrap_or(true); // index vec // even if empty, it SHOULD be present ... (is that valid to put an empty set?) // The get_ava_opt_index handles the optional case for us :) - let index = value.get_ava_opt_index(ATTR_INDEX).ok_or_else(|| { + let index = value.get_ava_opt_index(Attribute::Index).ok_or_else(|| { admin_error!("invalid {} - {}", Attribute::Index, name); - OperationError::InvalidSchemaState(format!("invalid {}", ATTR_INDEX)) + OperationError::InvalidSchemaState(format!("invalid {}", Attribute::Index)) })?; // syntax type - let syntax = value.get_ava_single_syntax(ATTR_SYNTAX).ok_or_else(|| { - admin_error!("missing {} - {}", Attribute::Syntax, name); - OperationError::InvalidSchemaState(format!("missing {}", ATTR_SYNTAX)) - })?; + let syntax = value + .get_ava_single_syntax(Attribute::Syntax) + .ok_or_else(|| { + admin_error!("missing {} - {}", Attribute::Syntax, name); + OperationError::InvalidSchemaState(format!("missing {}", Attribute::Syntax)) + })?; Ok(SchemaAttribute { name, @@ -403,7 +410,7 @@ impl SchemaClass { // uuid let uuid = value.get_uuid(); // Convert entry to a schema class. - if !value.attribute_equality(Attribute::Class.as_ref(), &EntryClass::ClassType.into()) { + if !value.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) { admin_error!("class classtype not present - {:?}", uuid); return Err(OperationError::InvalidSchemaState( "missing classtype".to_string(), @@ -412,57 +419,57 @@ impl SchemaClass { // name let name = value - .get_ava_single_iutf8(Attribute::ClassName.as_ref()) + .get_ava_single_iutf8(Attribute::ClassName) .map(AttrString::from) .ok_or_else(|| { - admin_error!("missing classname - {:?}", uuid); - OperationError::InvalidSchemaState("missing classname".to_string()) + admin_error!("missing {} - {:?}", Attribute::ClassName, uuid); + OperationError::InvalidSchemaState(format!("missing {}", Attribute::ClassName)) })?; // description let description = value - .get_ava_single_utf8(Attribute::Description.as_ref()) + .get_ava_single_utf8(Attribute::Description) .map(String::from) .ok_or_else(|| { - admin_error!("missing description - {}", name); - OperationError::InvalidSchemaState("missing description".to_string()) + admin_error!("missing {} - {}", Attribute::Description, name); + OperationError::InvalidSchemaState(format!("missing {}", Attribute::Description)) })?; let sync_allowed = value - .get_ava_single_bool(Attribute::SyncAllowed.as_ref()) + .get_ava_single_bool(Attribute::SyncAllowed) .unwrap_or(false); // These are all "optional" lists of strings. let systemmay = value - .get_ava_iter_iutf8(Attribute::SystemMay.as_ref()) + .get_ava_iter_iutf8(Attribute::SystemMay) .map(|i| i.map(|v| v.into()).collect()) .unwrap_or_else(Vec::new); let systemmust = value - .get_ava_iter_iutf8(Attribute::SystemMust.as_ref()) + .get_ava_iter_iutf8(Attribute::SystemMust) .map(|i| i.map(|v| v.into()).collect()) .unwrap_or_else(Vec::new); let may = value - .get_ava_iter_iutf8(Attribute::May.as_ref()) + .get_ava_iter_iutf8(Attribute::May) .map(|i| i.map(|v| v.into()).collect()) .unwrap_or_else(Vec::new); let must = value - .get_ava_iter_iutf8(Attribute::Must.as_ref()) + .get_ava_iter_iutf8(Attribute::Must) .map(|i| i.map(|v| v.into()).collect()) .unwrap_or_else(Vec::new); let systemsupplements = value - .get_ava_iter_iutf8(Attribute::SystemSupplements.as_ref()) + .get_ava_iter_iutf8(Attribute::SystemSupplements) .map(|i| i.map(|v| v.into()).collect()) .unwrap_or_else(Vec::new); let supplements = value - .get_ava_iter_iutf8(Attribute::Supplements.as_ref()) + .get_ava_iter_iutf8(Attribute::Supplements) .map(|i| i.map(|v| v.into()).collect()) .unwrap_or_else(Vec::new); let systemexcludes = value - .get_ava_iter_iutf8(Attribute::SystemExcludes.as_ref()) + .get_ava_iter_iutf8(Attribute::SystemExcludes) .map(|i| i.map(|v| v.into()).collect()) .unwrap_or_else(Vec::new); let excludes = value - .get_ava_iter_iutf8(Attribute::Excludes.as_ref()) + .get_ava_iter_iutf8(Attribute::Excludes) .map(|i| i.map(|v| v.into()).collect()) .unwrap_or_else(Vec::new); @@ -2355,7 +2362,6 @@ mod tests { // Test single value string let single_value_string = SchemaAttribute { - // class: vec![String::from("attributetype")], name: AttrString::from("single_value"), uuid: Uuid::new_v4(), description: String::from(""), @@ -2379,7 +2385,6 @@ mod tests { // test multivalue string, boolean let multi_value_string = SchemaAttribute { - // class: vec![String::from("attributetype")], name: AttrString::from("mv_string"), uuid: Uuid::new_v4(), description: String::from(""), @@ -2394,7 +2399,6 @@ mod tests { assert_eq!(r5, Ok(())); let multi_value_boolean = SchemaAttribute { - // class: vec![String::from("attributetype")], name: AttrString::from("mv_bool"), uuid: Uuid::new_v4(), description: String::from(""), @@ -2426,7 +2430,6 @@ mod tests { // syntax_id and index_type values let single_value_syntax = SchemaAttribute { - // class: vec![String::from("attributetype")], name: AttrString::from("sv_syntax"), uuid: Uuid::new_v4(), description: String::from(""), @@ -2447,7 +2450,6 @@ mod tests { ); let single_value_index = SchemaAttribute { - // class: vec![String::from("attributetype")], name: AttrString::from("sv_index"), uuid: Uuid::new_v4(), description: String::from(""), @@ -2690,7 +2692,9 @@ mod tests { assert_eq!( e_extensible_phantom.validate(&schema), - Err(SchemaError::PhantomAttribute("password_import".to_string())) + Err(SchemaError::PhantomAttribute( + Attribute::PasswordImport.to_string() + )) ); let e_extensible: Entry = Entry::unsafe_from_entry_str( @@ -2756,14 +2760,14 @@ mod tests { // Test mixed case attr name - this is a pass, due to normalisation let f_or_ok = filter_all!(f_andnot(f_and!([ f_eq(Attribute::Class, EntryClass::AttributeType.into()), - f_sub(Attribute::Class.as_ref(), EntryClass::ClassType.into()), + f_sub(Attribute::Class, EntryClass::ClassType.into()), f_pres(Attribute::Class) ]))); assert_eq!( f_or_ok.validate(&schema), Ok(filter_valid!(f_andnot(f_and!([ f_eq(Attribute::Class, EntryClass::AttributeType.into()), - f_sub(Attribute::Class.as_ref(), EntryClass::ClassType.into()), + f_sub(Attribute::Class, EntryClass::ClassType.into()), f_pres(Attribute::Class) ])))) ); @@ -2868,7 +2872,7 @@ mod tests { assert_eq!( e_service.validate(&schema), - Err(SchemaError::RequiresNotSatisfied(vec!["account".to_string()])) + Err(SchemaError::RequiresNotSatisfied(vec![Attribute::Account.to_string()])) ); */ diff --git a/server/lib/src/server/access/create.rs b/server/lib/src/server/access/create.rs index b7e1879c8..1a4c4a0e5 100644 --- a/server/lib/src/server/access/create.rs +++ b/server/lib/src/server/access/create.rs @@ -91,7 +91,7 @@ fn create_filter_entry<'a>( // I still think if this is None, we should just fail here ... // because it shouldn't be possible to match. - let create_classes: BTreeSet<&str> = match entry.get_ava_iter_iutf8(Attribute::Class.as_ref()) { + let create_classes: BTreeSet<&str> = match entry.get_ava_iter_iutf8(Attribute::Class) { Some(s) => s.collect(), None => { admin_error!("Class set failed to build - corrupted entry?"); @@ -161,7 +161,7 @@ fn protected_filter_entry(ident: &Identity, entry: &Entry) // Now check things ... // For now we just block create on sync object - if let Some(classes) = entry.get_ava_set(Attribute::Class.as_ref()) { + if let Some(classes) = entry.get_ava_set(Attribute::Class) { if classes.contains(&EntryClass::SyncObject.into()) { // Block the mod security_access!("attempt to create with protected class type"); diff --git a/server/lib/src/server/access/delete.rs b/server/lib/src/server/access/delete.rs index 45f0adc93..2aadd83af 100644 --- a/server/lib/src/server/access/delete.rs +++ b/server/lib/src/server/access/delete.rs @@ -115,7 +115,7 @@ fn protected_filter_entry(ident: &Identity, entry: &Arc) - // Now check things ... // For now we just block create on sync object - if let Some(classes) = entry.get_ava_set(Attribute::Class.as_ref()) { + if let Some(classes) = entry.get_ava_set(Attribute::Class) { if classes.contains(&EntryClass::SyncObject.into()) { // Block the mod security_access!("attempt to delete with protected class type"); diff --git a/server/lib/src/server/access/mod.rs b/server/lib/src/server/access/mod.rs index 2cd2c0df6..77803031b 100644 --- a/server/lib/src/server/access/mod.rs +++ b/server/lib/src/server/access/mod.rs @@ -378,10 +378,10 @@ pub trait AccessControlsTransaction<'a> { let disallow = me .modlist .iter() - .any(|m| matches!(m, Modify::Purged(a) if a == "class")); + .any(|m| matches!(m, Modify::Purged(a) if a == Attribute::Class.as_ref())); if disallow { - security_access!("Disallowing purge class in modification"); + security_access!("Disallowing purge {} in modification", Attribute::Class); return Ok(false); } @@ -417,7 +417,7 @@ pub trait AccessControlsTransaction<'a> { .iter() .filter_map(|m| match m { Modify::Present(a, v) => { - if a.as_str() == "class" { + if a == Attribute::Class.as_ref() { // Here we have an option<&str> which could mean there is a risk of // a malicious entity attempting to trick us by masking class mods // in non-iutf8 types. However, the server first won't respect their @@ -430,7 +430,7 @@ pub trait AccessControlsTransaction<'a> { } } Modify::Removed(a, v) => { - if a.as_str() == "class" { + if a == Attribute::Class.as_ref() { v.to_str() } else { None @@ -519,10 +519,10 @@ pub trait AccessControlsTransaction<'a> { let disallow = modlist .iter() - .any(|m| matches!(m, Modify::Purged(a) if a == "class")); + .any(|m| matches!(m, Modify::Purged(a) if a == Attribute::Class.as_ref())); if disallow { - security_access!("Disallowing purge class in modification"); + security_access!("Disallowing purge in modification"); return false; } @@ -551,7 +551,7 @@ pub trait AccessControlsTransaction<'a> { .iter() .filter_map(|m| match m { Modify::Present(a, v) => { - if a.as_str() == "class" { + if a == Attribute::Class.as_ref() { // Here we have an option<&str> which could mean there is a risk of // a malicious entity attempting to trick us by masking class mods // in non-iutf8 types. However, the server first won't respect their @@ -564,7 +564,7 @@ pub trait AccessControlsTransaction<'a> { } } Modify::Removed(a, v) => { - if a.as_str() == "class" { + if a == Attribute::Class.as_ref() { v.to_str() } else { None @@ -1882,7 +1882,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_pres(Attribute::Name.as_ref(), &Value::new_iname("value"))]), + modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]), ); // Name rem let me_rem = ModifyEvent::new_impersonate_entry( @@ -1891,10 +1891,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_remove( - Attribute::Name.as_ref(), - &PartialValue::new_iname("value") - )]), + modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]), ); // Name purge let me_purge = ModifyEvent::new_impersonate_entry( @@ -1903,7 +1900,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_purge("name")]), + modlist!([m_purge(Attribute::Name)]), ); // Class account pres @@ -1913,7 +1910,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_pres("class", &EntryClass::Account.to_value())]), + modlist!([m_pres(Attribute::Class, &EntryClass::Account.to_value())]), ); // Class account rem let me_rem_class = ModifyEvent::new_impersonate_entry( @@ -1922,7 +1919,10 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_remove("class", &EntryClass::Account.to_partialvalue())]), + modlist!([m_remove( + Attribute::Class, + &EntryClass::Account.to_partialvalue() + )]), ); // Class purge let me_purge_class = ModifyEvent::new_impersonate_entry( @@ -1931,7 +1931,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_purge("class")]), + modlist!([m_purge(Attribute::Class)]), ); // Allow name and class, class is account @@ -2032,7 +2032,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_pres(Attribute::Name.as_ref(), &Value::new_iname("value"))]), + modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]), ); // Name present @@ -2042,7 +2042,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_pres(Attribute::Name.as_ref(), &Value::new_iname("value"))]), + modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]), ); let acp_allow = AccessControlModify::from_raw( @@ -2564,9 +2564,9 @@ mod tests { PartialValue::new_iname("testperson1") )), // Allow pres user_auth_token_session - "user_auth_token_session name", + &format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name), // Allow user_auth_token_session - "user_auth_token_session name", + &format!("{} {}", Attribute::UserAuthTokenSession, Attribute::Name), // And the class allowed is account, we don't use it though. EntryClass::Account.into(), ); @@ -2581,7 +2581,7 @@ mod tests { PartialValue::new_iname("testperson1") )), modlist!([m_pres( - "user_auth_token_session", + Attribute::UserAuthTokenSession, &Value::new_iname("value") )]), ); @@ -2593,7 +2593,7 @@ mod tests { PartialValue::new_iname("testperson1") )), modlist!([m_remove( - "user_auth_token_session", + Attribute::UserAuthTokenSession, &PartialValue::new_iname("value") )]), ); @@ -2604,7 +2604,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_purge("user_auth_token_session")]), + modlist!([m_purge(Attribute::UserAuthTokenSession)]), ); // Test allowed pres @@ -2628,7 +2628,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_pres(Attribute::Name.as_ref(), &Value::new_iname("value"))]), + modlist!([m_pres(Attribute::Name, &Value::new_iname("value"))]), ); // Name rem let me_rem = ModifyEvent::new_impersonate_entry( @@ -2637,10 +2637,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_remove( - Attribute::Name.as_ref(), - &PartialValue::new_iname("value") - )]), + modlist!([m_remove(Attribute::Name, &PartialValue::new_iname("value"))]), ); // Name purge let me_purge = ModifyEvent::new_impersonate_entry( @@ -2649,7 +2646,7 @@ mod tests { Attribute::Name, PartialValue::new_iname("testperson1") )), - modlist!([m_purge("name")]), + modlist!([m_purge(Attribute::Name)]), ); // Test reject pres diff --git a/server/lib/src/server/access/modify.rs b/server/lib/src/server/access/modify.rs index 2c234919f..6eeb8eed4 100644 --- a/server/lib/src/server/access/modify.rs +++ b/server/lib/src/server/access/modify.rs @@ -203,7 +203,7 @@ fn modify_sync_constrain<'a>( // * We are a sync object // * We have a sync_parent_uuid let is_sync = entry - .get_ava_set(Attribute::Class.as_ref()) + .get_ava_set(Attribute::Class) .map(|classes| classes.contains(&EntryClass::SyncObject.into())) .unwrap_or(false); @@ -211,12 +211,12 @@ fn modify_sync_constrain<'a>( return AccessResult::Ignore; } - if let Some(sync_uuid) = entry.get_ava_single_refer("sync_parent_uuid") { + if let Some(sync_uuid) = entry.get_ava_single_refer(Attribute::SyncParentUuid) { let mut set = btreeset![ - "user_auth_token_session", - "oauth2_session", - "oauth2_consent_scope_map", - "credential_update_intent_token" + Attribute::UserAuthTokenSession.as_ref(), + Attribute::OAuth2Session.as_ref(), + Attribute::OAuth2ConsentScopeMap.as_ref(), + Attribute::CredentialUpdateIntentToken.as_ref() ]; if let Some(sync_yield_authority) = sync_agreements.get(&sync_uuid) { diff --git a/server/lib/src/server/access/profiles.rs b/server/lib/src/server/access/profiles.rs index 05f134db2..826e5fd5c 100644 --- a/server/lib/src/server/access/profiles.rs +++ b/server/lib/src/server/access/profiles.rs @@ -20,21 +20,19 @@ impl AccessControlSearch { qs: &mut QueryServerWriteTransaction, value: &Entry, ) -> Result { - if !value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlSearch.into(), - ) { - admin_error!("class access_control_search not present."); - return Err(OperationError::InvalidAcpState( - "Missing access_control_search".to_string(), - )); + if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlSearch.into()) { + admin_error!("class {} not present.", EntryClass::AccessControlSearch); + return Err(OperationError::InvalidAcpState(format!( + "Missing {}", + EntryClass::AccessControlSearch + ))); } let attrs = value - .get_ava_iter_iutf8("acp_search_attr") + .get_ava_iter_iutf8(Attribute::AcpSearchAttr) .ok_or_else(|| { - admin_error!("Missing acp_search_attr"); - OperationError::InvalidAcpState("Missing acp_search_attr".to_string()) + admin_error!("Missing {}", Attribute::AcpSearchAttr); + OperationError::InvalidAcpState(format!("Missing {}", Attribute::AcpSearchAttr)) })? .map(AttrString::from) .collect(); @@ -76,10 +74,7 @@ impl AccessControlDelete { qs: &mut QueryServerWriteTransaction, value: &Entry, ) -> Result { - if !value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlDelete.into(), - ) { + if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlDelete.into()) { admin_error!("class access_control_delete not present."); return Err(OperationError::InvalidAcpState( "Missing access_control_delete".to_string(), @@ -123,23 +118,21 @@ impl AccessControlCreate { qs: &mut QueryServerWriteTransaction, value: &Entry, ) -> Result { - if !value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlCreate.into(), - ) { - admin_error!("class access_control_create not present."); - return Err(OperationError::InvalidAcpState( - "Missing access_control_create".to_string(), - )); + if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlCreate.into()) { + admin_error!("class {} not present.", EntryClass::AccessControlCreate); + return Err(OperationError::InvalidAcpState(format!( + "Missing {}", + EntryClass::AccessControlCreate + ))); } let attrs = value - .get_ava_iter_iutf8("acp_create_attr") + .get_ava_iter_iutf8(Attribute::AcpCreateAttr) .map(|i| i.map(AttrString::from).collect()) .unwrap_or_else(Vec::new); let classes = value - .get_ava_iter_iutf8("acp_create_class") + .get_ava_iter_iutf8(Attribute::AcpCreateClass) .map(|i| i.map(AttrString::from).collect()) .unwrap_or_else(Vec::new); @@ -187,10 +180,7 @@ impl AccessControlModify { qs: &mut QueryServerWriteTransaction, value: &Entry, ) -> Result { - if !value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlModify.into(), - ) { + if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlModify.into()) { admin_error!("class access_control_modify not present."); return Err(OperationError::InvalidAcpState( "Missing access_control_modify".to_string(), @@ -198,17 +188,17 @@ impl AccessControlModify { } let presattrs = value - .get_ava_iter_iutf8("acp_modify_presentattr") + .get_ava_iter_iutf8(Attribute::AcpModifyPresentAttr) .map(|i| i.map(AttrString::from).collect()) .unwrap_or_else(Vec::new); let remattrs = value - .get_ava_iter_iutf8("acp_modify_removedattr") + .get_ava_iter_iutf8(Attribute::AcpModifyRemovedAttr) .map(|i| i.map(AttrString::from).collect()) .unwrap_or_else(Vec::new); let classes = value - .get_ava_iter_iutf8("acp_modify_class") + .get_ava_iter_iutf8(Attribute::AcpModifyClass) .map(|i| i.map(AttrString::from).collect()) .unwrap_or_else(Vec::new); @@ -278,10 +268,7 @@ impl AccessControlProfile { value: &Entry, ) -> Result { // Assert we have class access_control_profile - if !value.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlProfile.into(), - ) { + if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) { admin_error!("class access_control_profile not present."); return Err(OperationError::InvalidAcpState( "Missing access_control_profile".to_string(), @@ -290,10 +277,10 @@ impl AccessControlProfile { // copy name let name = value - .get_ava_single_iname("name") + .get_ava_single_iname(Attribute::Name) .ok_or_else(|| { - admin_error!("Missing name"); - OperationError::InvalidAcpState("Missing name".to_string()) + admin_error!("Missing {}", Attribute::Name); + OperationError::InvalidAcpState(format!("Missing {}", Attribute::Name)) })? .to_string(); // copy uuid @@ -302,7 +289,7 @@ impl AccessControlProfile { // === ⚠️ WARNING!!! ⚠️ === // See struct ACP for details. - let receiver = value.get_ava_single_refer(ATTR_ACP_RECEIVER_GROUP); + let receiver = value.get_ava_single_refer(Attribute::AcpReceiverGroup); /* .ok_or_else(|| { admin_error!("Missing acp_receiver_group"); @@ -312,23 +299,23 @@ impl AccessControlProfile { // targetscope, and turn to real filter let targetscope_f: ProtoFilter = value - .get_ava_single_protofilter("acp_targetscope") + .get_ava_single_protofilter(Attribute::AcpTargetScope) // .map(|pf| pf.clone()) .cloned() .ok_or_else(|| { - admin_error!("Missing acp_targetscope"); - OperationError::InvalidAcpState("Missing acp_targetscope".to_string()) + admin_error!("Missing {}", Attribute::AcpTargetScope); + OperationError::InvalidAcpState(format!("Missing {}", Attribute::AcpTargetScope)) })?; let ident = Identity::from_internal(); let targetscope_i = Filter::from_rw(&ident, &targetscope_f, qs).map_err(|e| { - admin_error!("Targetscope validation failed {:?}", e); + admin_error!("{} validation failed {:?}", Attribute::AcpTargetScope, e); e })?; let targetscope = targetscope_i.validate(qs.get_schema()).map_err(|e| { - admin_error!("acp_targetscope Schema Violation {:?}", e); + admin_error!("{} Schema Violation {:?}", Attribute::AcpTargetScope, e); OperationError::SchemaViolation(e) })?; diff --git a/server/lib/src/server/access/search.rs b/server/lib/src/server/access/search.rs index 298088efc..8a3bca9db 100644 --- a/server/lib/src/server/access/search.rs +++ b/server/lib/src/server/access/search.rs @@ -127,15 +127,15 @@ fn search_oauth2_filter_entry<'a>( IdentType::Internal | IdentType::Synch(_) => AccessResult::Ignore, IdentType::User(iuser) => { let contains_o2_rs = entry - .get_ava_as_iutf8(Attribute::Class.as_ref()) + .get_ava_as_iutf8(Attribute::Class) .map(|set| { trace!(?set); - set.contains("oauth2_resource_server") + set.contains(&EntryClass::OAuth2ResourceServer.to_string()) }) .unwrap_or(false); let contains_o2_scope_member = entry - .get_ava_as_oauthscopemaps("oauth2_rs_scope_map") + .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap) .and_then(|maps| ident.get_memberof().map(|mo| (maps, mo))) .map(|(maps, mo)| maps.keys().any(|k| mo.contains(k))) .unwrap_or(false); @@ -167,19 +167,20 @@ fn search_sync_account_filter_entry<'a>( // Is the user a synced object? let is_user_sync_account = iuser .entry - .get_ava_as_iutf8(Attribute::Class.as_ref()) + .get_ava_as_iutf8(Attribute::Class) .map(|set| { trace!(?set); - set.contains("sync_object") && set.contains("account") + set.contains(&EntryClass::SyncObject.to_string()) + && set.contains(EntryClass::Account.into()) }) .unwrap_or(false); if is_user_sync_account { let is_target_sync_account = entry - .get_ava_as_iutf8(Attribute::Class.as_ref()) + .get_ava_as_iutf8(Attribute::Class) .map(|set| { trace!(?set); - set.contains("sync_account") + set.contains(&EntryClass::SyncAccount.to_string()) }) .unwrap_or(false); @@ -188,7 +189,7 @@ fn search_sync_account_filter_entry<'a>( let sync_uuid = entry.get_uuid(); let sync_source_match = iuser .entry - .get_ava_single_refer("sync_parent_uuid") + .get_ava_single_refer(Attribute::SyncParentUuid) .map(|sync_parent_uuid| sync_parent_uuid == sync_uuid) .unwrap_or(false); @@ -197,9 +198,9 @@ fn search_sync_account_filter_entry<'a>( security_access!(entry = ?entry.get_uuid(), ident = ?iuser.entry.get_uuid2rdn(), "ident is a synchronsied account from this sync account"); return AccessResult::Allow(btreeset!( - "class", - "uuid", - "sync_credential_portal" + Attribute::Class.as_ref(), + Attribute::Uuid.as_ref(), + Attribute::SyncCredentialPortal.as_ref() )); } } diff --git a/server/lib/src/server/batch_modify.rs b/server/lib/src/server/batch_modify.rs index 774087924..275468006 100644 --- a/server/lib/src/server/batch_modify.rs +++ b/server/lib/src/server/batch_modify.rs @@ -189,11 +189,8 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::ClassType.into()) - || e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AttributeType.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) + || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) }); } if !self.changed_acp { @@ -201,10 +198,7 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlProfile.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) }); } if !self.changed_oauth2 { @@ -212,17 +206,14 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::OAuth2ResourceServer.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) }); } if !self.changed_domain { self.changed_domain = norm_cand .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) - .any(|e| e.attribute_equality(Attribute::Uuid.as_ref(), &PVUUID_DOMAIN_INFO)); + .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO)); } self.changed_uuid.extend( @@ -296,17 +287,11 @@ mod tests { [ ( uuid_a, - ModifyList::new_append( - Attribute::Description.as_ref(), - Value::Utf8("a".into()) - ) + ModifyList::new_append(Attribute::Description, Value::Utf8("a".into())) ), ( uuid_b, - ModifyList::new_append( - Attribute::Description.as_ref(), - Value::Utf8("b".into()) - ) + ModifyList::new_append(Attribute::Description, Value::Utf8("b".into())) ), ] .into_iter() @@ -321,7 +306,7 @@ mod tests { .internal_search_uuid(uuid_b) .expect("Failed to get entry."); - assert!(ent_a.get_ava_single_utf8(Attribute::Description.as_ref()) == Some("a")); - assert!(ent_b.get_ava_single_utf8(Attribute::Description.as_ref()) == Some("b")); + assert!(ent_a.get_ava_single_utf8(Attribute::Description) == Some("a")); + assert!(ent_b.get_ava_single_utf8(Attribute::Description) == Some("b")); } } diff --git a/server/lib/src/server/create.rs b/server/lib/src/server/create.rs index 3534e046a..2320654e3 100644 --- a/server/lib/src/server/create.rs +++ b/server/lib/src/server/create.rs @@ -107,38 +107,29 @@ impl<'a> QueryServerWriteTransaction<'a> { // schema or acp requires reload. if !self.changed_schema { self.changed_schema = commit_cand.iter().any(|e| { - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::ClassType.into()) - || e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AttributeType.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) + || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) }); } if !self.changed_acp { self.changed_acp = commit_cand.iter().any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlProfile.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) }); } if !self.changed_oauth2 { self.changed_oauth2 = commit_cand.iter().any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::OAuth2ResourceServer.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) }); } if !self.changed_domain { self.changed_domain = commit_cand .iter() - .any(|e| e.attribute_equality(Attribute::Uuid.as_ref(), &PVUUID_DOMAIN_INFO)); + .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO)); } if !self.changed_sync_agreement { - self.changed_sync_agreement = commit_cand.iter().any(|e| { - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncAccount.into()) - }); + self.changed_sync_agreement = commit_cand + .iter() + .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into())); } self.changed_uuid @@ -233,7 +224,7 @@ mod tests { let key = r2 .first() .unwrap() - .get_ava_single_eckey_private(Attribute::IdVerificationEcKey.as_ref()) + .get_ava_single_eckey_private(Attribute::IdVerificationEcKey) .unwrap(); e.add_ava( diff --git a/server/lib/src/server/delete.rs b/server/lib/src/server/delete.rs index b392014bd..10dcefa95 100644 --- a/server/lib/src/server/delete.rs +++ b/server/lib/src/server/delete.rs @@ -102,38 +102,29 @@ impl<'a> QueryServerWriteTransaction<'a> { // schema or acp requires reload. if !self.changed_schema { self.changed_schema = del_cand.iter().any(|e| { - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::ClassType.into()) - || e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AttributeType.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) + || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) }); } if !self.changed_acp { self.changed_acp = del_cand.iter().any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlProfile.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) }); } if !self.changed_oauth2 { self.changed_oauth2 = del_cand.iter().any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::OAuth2ResourceServer.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) }); } if !self.changed_domain { self.changed_domain = del_cand .iter() - .any(|e| e.attribute_equality(Attribute::Uuid.as_ref(), &PVUUID_DOMAIN_INFO)); + .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO)); } if !self.changed_sync_agreement { - self.changed_sync_agreement = del_cand.iter().any(|e| { - e.attribute_equality(Attribute::Uuid.as_ref(), &EntryClass::SyncAccount.into()) - }); + self.changed_sync_agreement = del_cand + .iter() + .any(|e| e.attribute_equality(Attribute::Uuid, &EntryClass::SyncAccount.into())); } self.changed_uuid diff --git a/server/lib/src/server/identity.rs b/server/lib/src/server/identity.rs index bdc558818..a923c4ac2 100644 --- a/server/lib/src/server/identity.rs +++ b/server/lib/src/server/identity.rs @@ -181,7 +181,7 @@ impl Identity { IdentType::Internal | IdentType::Synch(_) => None, IdentType::User(u) => u .entry - .get_ava_as_session_map("user_auth_token_session") + .get_ava_as_session_map(Attribute::UserAuthTokenSession) .and_then(|sessions| sessions.get(&self.session_id)), } } @@ -223,7 +223,7 @@ impl Identity { IdentType::Internal | IdentType::Synch(_) => false, IdentType::User(u) => u .entry - .attribute_equality(Attribute::Claim.as_ref(), &PartialValue::new_iutf8(claim)), + .attribute_equality(Attribute::Claim, &PartialValue::new_iutf8(claim)), } } @@ -232,14 +232,14 @@ impl Identity { IdentType::Internal | IdentType::Synch(_) => false, IdentType::User(u) => u .entry - .attribute_equality(Attribute::MemberOf.as_ref(), &PartialValue::Refer(group)), + .attribute_equality(Attribute::MemberOf, &PartialValue::Refer(group)), } } pub fn get_memberof(&self) -> Option<&BTreeSet> { match &self.origin { IdentType::Internal | IdentType::Synch(_) => None, - IdentType::User(u) => u.entry.get_ava_refer("memberof"), + IdentType::User(u) => u.entry.get_ava_refer(Attribute::MemberOf), } } @@ -248,7 +248,7 @@ impl Identity { IdentType::Internal | IdentType::Synch(_) => None, IdentType::User(u) => u .entry - .get_ava_as_oauthscopemaps("oauth2_consent_scope_map") + .get_ava_as_oauthscopemaps(Attribute::OAuth2ConsentScopeMap) .and_then(|scope_map| scope_map.get(&oauth2_rs)), } } diff --git a/server/lib/src/server/migrations.rs b/server/lib/src/server/migrations.rs index 7d3a9dfab..1c7b84140 100644 --- a/server/lib/src/server/migrations.rs +++ b/server/lib/src/server/migrations.rs @@ -71,7 +71,7 @@ impl QueryServer { // effect as ... there is nothing to migrate! It allows reset of the version to 0 to force // db migrations to take place. let system_info_version = match write_txn.internal_search_uuid(UUID_SYSTEM_INFO) { - Ok(e) => Ok(e.get_ava_single_uint32("version").unwrap_or(0)), + Ok(e) => Ok(e.get_ava_single_uint32(Attribute::Version).unwrap_or(0)), Err(OperationError::NoMatchingEntries) => Ok(0), Err(r) => Err(r), }?; @@ -233,17 +233,17 @@ impl<'a> QueryServerWriteTransaction<'a> { candidates.iter_mut().try_for_each(|er| { // Migrate basic secrets if they exist. let nvs = er - .get_ava_set("oauth2_rs_basic_secret") + .get_ava_set(Attribute::OAuth2RsBasicSecret) .and_then(|vs| vs.as_utf8_iter()) .and_then(|vs_iter| { ValueSetSecret::from_iter(vs_iter.map(|s: &str| s.to_string())) }); if let Some(nvs) = nvs { - er.set_ava_set("oauth2_rs_basic_secret", nvs) + er.set_ava_set(Attribute::OAuth2RsBasicSecret, nvs) } // Migrate implicit scopes if they exist. - let nv = if let Some(vs) = er.get_ava_set(Attribute::OAuth2RsImplicitScopes.as_ref()) { + let nv = if let Some(vs) = er.get_ava_set(Attribute::OAuth2RsImplicitScopes) { vs.as_oauthscope_set() .map(|v| Value::OauthScopeMap(UUID_IDM_ALL_PERSONS, v.clone())) } else { @@ -253,7 +253,7 @@ impl<'a> QueryServerWriteTransaction<'a> { if let Some(nv) = nv { er.add_ava(Attribute::OAuth2RsScopeMap, nv) } - er.purge_ava(Attribute::OAuth2RsImplicitScopes.as_ref()); + er.purge_ava(Attribute::OAuth2RsImplicitScopes); Ok(()) })?; @@ -298,8 +298,7 @@ impl<'a> QueryServerWriteTransaction<'a> { ])); // This "does nothing" since everything has object anyway, but it forces the entry to be // loaded and rewritten. - let modlist = - ModifyList::new_append(Attribute::Class.as_ref(), EntryClass::Object.to_value()); + let modlist = ModifyList::new_append(Attribute::Class, EntryClass::Object.to_value()); self.internal_modify(&filter, &modlist) // Complete } @@ -325,7 +324,7 @@ impl<'a> QueryServerWriteTransaction<'a> { let modset: Vec<_> = pre_candidates .into_iter() .filter_map(|ent| { - ent.get_ava_single_credential("primary_credential") + ent.get_ava_single_credential(Attribute::PrimaryCredential) .and_then(|cred| cred.passkey_ref().ok()) .map(|pk_map| { let modlist = pk_map @@ -336,7 +335,7 @@ impl<'a> QueryServerWriteTransaction<'a> { Value::Passkey(Uuid::new_v4(), t.clone(), k.clone()), ) }) - .chain(std::iter::once(m_purge("primary_credential"))) + .chain(std::iter::once(m_purge(Attribute::PrimaryCredential))) .collect(); (ent.get_uuid(), ModifyList::new_list(modlist)) }) @@ -381,19 +380,22 @@ impl<'a> QueryServerWriteTransaction<'a> { // webauthn type. for (_, ent) in mod_candidates.iter_mut() { - if let Some(api_token_session) = ent.pop_ava("api_token_session") { + if let Some(api_token_session) = ent.pop_ava(Attribute::ApiTokenSession) { let api_token_session = api_token_session .migrate_session_to_apitoken() .map_err(|e| { - error!("Failed to convert api_token_session from session -> apitoken"); + error!( + "Failed to convert {} from session -> apitoken", + Attribute::ApiTokenSession + ); e })?; - ent.set_ava_set("api_token_session", api_token_session); + ent.set_ava_set(Attribute::ApiTokenSession, api_token_session); } - if let Some(sync_token_session) = ent.pop_ava("sync_token_session") { + if let Some(sync_token_session) = ent.pop_ava(Attribute::SyncTokenSession) { let sync_token_session = sync_token_session .migrate_session_to_apitoken() @@ -402,7 +404,7 @@ impl<'a> QueryServerWriteTransaction<'a> { e })?; - ent.set_ava_set("sync_token_session", sync_token_session); + ent.set_ava_set(Attribute::SyncTokenSession, sync_token_session); } } @@ -418,7 +420,7 @@ impl<'a> QueryServerWriteTransaction<'a> { f_eq(Attribute::Uuid, PVUUID_DOMAIN_INFO.clone()), ])); // Delete the existing cookie key to trigger a regeneration. - let modlist = ModifyList::new_purge(ATTR_PRIVATE_COOKIE_KEY); + let modlist = ModifyList::new_purge(Attribute::PrivateCookieKey); self.internal_modify(&filter, &modlist) // Complete } @@ -431,7 +433,7 @@ impl<'a> QueryServerWriteTransaction<'a> { EntryClass::DynGroup.to_partialvalue() )); // Delete the incorrectly added "member" attr. - let modlist = ModifyList::new_purge("member"); + let modlist = ModifyList::new_purge(Attribute::Member); self.internal_modify(&filter, &modlist) // Complete } @@ -442,7 +444,7 @@ impl<'a> QueryServerWriteTransaction<'a> { let filter = filter!(f_eq(Attribute::Class, EntryClass::Person.into())); // Delete the non-existing attr for idv private key which triggers // it to regen. - let modlist = ModifyList::new_purge("id_verification_eckey"); + let modlist = ModifyList::new_purge(Attribute::IdVerificationEcKey); self.internal_modify(&filter, &modlist) // Complete } @@ -821,7 +823,7 @@ mod tests { ModifyEvent::new_internal_invalid( filter!(f_or!([ f_eq(Attribute::AttributeName, Attribute::Name.to_partialvalue()), - f_eq(Attribute::AttributeName, PartialValue::new_iutf8("domain_name")), + f_eq(Attribute::AttributeName, Attribute::DomainName.into()), ])), ModifyList::new_purge_and_set( "syntax", @@ -864,7 +866,7 @@ mod tests { ModifyEvent::new_internal_invalid( filter!(f_or!([ f_eq(Attribute::AttributeName, Attribute::Name.to_partialvalue()), - f_eq(Attribute::AttributeName, PartialValue::new_iutf8("domain_name")), + f_eq(Attribute::AttributeName, Attribute::DomainName.into()), ])), ModifyList::new_purge_and_set( "syntax", @@ -891,12 +893,12 @@ mod tests { .expect("failed"); // ++ assert all names are iname assert!( - domain.get_ava_set(Attribute::Name.as_ref()).expect("no name?").syntax() == SyntaxType::Utf8StringIname + domain.get_ava_set(Attribute::Name).expect("no name?").syntax() == SyntaxType::Utf8StringIname ); // ++ assert all domain/domain_name are iname assert!( domain - .get_ava_set("domain_name") + .get_ava_set(Attribute::DomainName.as_ref()) .expect("no domain_name?") .syntax() == SyntaxType::Utf8StringIname diff --git a/server/lib/src/server/mod.rs b/server/lib/src/server/mod.rs index d8217bdb0..2b75e7eca 100644 --- a/server/lib/src/server/mod.rs +++ b/server/lib/src/server/mod.rs @@ -757,7 +757,7 @@ pub trait QueryServerTransaction<'a> { self.internal_search_uuid(UUID_DOMAIN_INFO) .and_then(|e| { trace!(?e); - e.get_ava_single_iname("domain_name") + e.get_ava_single_iname(Attribute::DomainName) .map(str::to_string) .ok_or(OperationError::InvalidEntryState) }) @@ -770,7 +770,7 @@ pub trait QueryServerTransaction<'a> { fn get_domain_fernet_private_key(&mut self) -> Result { self.internal_search_uuid(UUID_DOMAIN_INFO) .and_then(|e| { - e.get_ava_single_secret("fernet_private_key_str") + e.get_ava_single_secret(Attribute::FernetPrivateKeyStr) .map(str::to_string) .ok_or(OperationError::InvalidEntryState) }) @@ -783,7 +783,7 @@ pub trait QueryServerTransaction<'a> { fn get_domain_es256_private_key(&mut self) -> Result, OperationError> { self.internal_search_uuid(UUID_DOMAIN_INFO) .and_then(|e| { - e.get_ava_single_private_binary("es256_private_key_der") + e.get_ava_single_private_binary(Attribute::Es256PrivateKeyDer) .map(|s| s.to_vec()) .ok_or(OperationError::InvalidEntryState) }) @@ -796,7 +796,7 @@ pub trait QueryServerTransaction<'a> { fn get_domain_cookie_key(&mut self) -> Result<[u8; 64], OperationError> { self.internal_search_uuid(UUID_DOMAIN_INFO) .and_then(|e| { - e.get_ava_single_private_binary(ATTR_PRIVATE_COOKIE_KEY) + e.get_ava_single_private_binary(Attribute::PrivateCookieKey) .and_then(|s| { let mut x = [0; 64]; if s.len() == x.len() { @@ -817,7 +817,7 @@ pub trait QueryServerTransaction<'a> { // This is a helper to get password badlist. fn get_password_badlist(&mut self) -> Result, OperationError> { self.internal_search_uuid(UUID_SYSTEM_CONFIG) - .map(|e| match e.get_ava_iter_iutf8("badlist_password") { + .map(|e| match e.get_ava_iter_iutf8(Attribute::BadlistPassword) { Some(vs_str_iter) => vs_str_iter.map(str::to_string).collect::>(), None => HashSet::default(), }) @@ -830,7 +830,7 @@ pub trait QueryServerTransaction<'a> { fn get_authsession_expiry(&mut self) -> Result { self.internal_search_uuid(UUID_SYSTEM_CONFIG) .and_then(|e| { - if let Some(expiry_time) = e.get_ava_single_uint32("authsession_expiry") { + if let Some(expiry_time) = e.get_ava_single_uint32(Attribute::AuthSessionExpiry) { Ok(expiry_time) } else { Err(OperationError::NoMatchingAttributes) @@ -848,7 +848,7 @@ pub trait QueryServerTransaction<'a> { fn get_privilege_expiry(&mut self) -> Result { self.internal_search_uuid(UUID_SYSTEM_CONFIG) .and_then(|e| { - if let Some(expiry_time) = e.get_ava_single_uint32("privilege_expiry") { + if let Some(expiry_time) = e.get_ava_single_uint32(Attribute::PrivilegeExpiry) { Ok(expiry_time) } else { Err(OperationError::NoMatchingAttributes) @@ -1358,7 +1358,7 @@ impl<'a> QueryServerWriteTransaction<'a> { let sync_agreement_map: HashMap> = res .iter() .filter_map(|e| { - e.get_ava_as_iutf8("sync_yield_authority") + e.get_ava_as_iutf8(Attribute::SyncYieldAuthority) .cloned() .map(|set| (e.get_uuid(), set)) }) @@ -1485,7 +1485,7 @@ impl<'a> QueryServerWriteTransaction<'a> { self.internal_search_uuid(UUID_DOMAIN_INFO) .and_then(|e| { trace!(?e); - e.get_ava_single_utf8("domain_display_name") + e.get_ava_single_utf8(Attribute::DomainDisplayName) .map(str::to_string) .ok_or(OperationError::InvalidEntryState) }) @@ -1530,7 +1530,7 @@ impl<'a> QueryServerWriteTransaction<'a> { /// activities (yet) pub fn set_domain_display_name(&mut self, new_domain_name: &str) -> Result<(), OperationError> { let modl = ModifyList::new_purge_and_set( - "domain_display_name", + Attribute::DomainDisplayName, Value::new_utf8(new_domain_name.to_string()), ); let udi = PVUUID_DOMAIN_INFO.clone(); @@ -1551,7 +1551,8 @@ impl<'a> QueryServerWriteTransaction<'a> { /// approached, especially if we have a domain re-name replicated to us. It could /// be that we end up needing to have this as a cow cell or similar? pub fn danger_domain_rename(&mut self, new_domain_name: &str) -> Result<(), OperationError> { - let modl = ModifyList::new_purge_and_set("domain_name", Value::new_iname(new_domain_name)); + let modl = + ModifyList::new_purge_and_set(Attribute::DomainName, Value::new_iname(new_domain_name)); let udi = PVUUID_DOMAIN_INFO.clone(); let filt = filter_all!(f_eq(Attribute::Uuid, udi)); self.internal_modify(&filt, &modl) @@ -1908,7 +1909,7 @@ mod tests { let testobj1 = server_txn .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")) .expect("failed"); - assert!(testobj1.attribute_equality("class", &EntryClass::TestClass.into())); + assert!(testobj1.attribute_equality(Attribute::Class, &EntryClass::TestClass.into())); // Should still be good server_txn.commit().expect("should not fail"); @@ -1936,10 +1937,7 @@ mod tests { Attribute::Uuid, Value::Uuid(uuid!("cfcae205-31c3-484b-8ced-667d1709c5e3")) ), - ( - Attribute::AttributeName, - Value::new_iutf8(Attribute::TestAttr.as_ref()) - ), + (Attribute::AttributeName, Attribute::TestAttr.to_value()), (Attribute::Description, Value::new_utf8s("Test Attribute")), (Attribute::MultiValue, Value::new_bool(false)), (Attribute::Unique, Value::new_bool(false)), @@ -1994,10 +1992,7 @@ mod tests { let testobj1 = server_txn .internal_search_uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930")) .expect("failed"); - assert!(testobj1.attribute_equality( - Attribute::TestAttr.as_ref(), - &PartialValue::new_utf8s("test") - )); + assert!(testobj1.attribute_equality(Attribute::TestAttr, &PartialValue::new_utf8s("test"))); server_txn.commit().expect("should not fail"); // Commit. diff --git a/server/lib/src/server/modify.rs b/server/lib/src/server/modify.rs index f7e545e9c..dd37f3018 100644 --- a/server/lib/src/server/modify.rs +++ b/server/lib/src/server/modify.rs @@ -194,11 +194,8 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::ClassType.into()) - || e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AttributeType.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) + || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) }); } if !self.changed_acp { @@ -206,10 +203,7 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlProfile.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) }) } if !self.changed_oauth2 { @@ -217,25 +211,20 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::OAuth2ResourceServer.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) }); } if !self.changed_domain { self.changed_domain = norm_cand .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) - .any(|e| e.attribute_equality(Attribute::Uuid.as_ref(), &PVUUID_DOMAIN_INFO)); + .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO)); } if !self.changed_sync_agreement { self.changed_sync_agreement = norm_cand .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) - .any(|e| { - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::SyncAccount.into()) - }); + .any(|e| e.attribute_equality(Attribute::Class, &EntryClass::SyncAccount.into())); } self.changed_uuid.extend( @@ -364,11 +353,8 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality(Attribute::Class.as_ref(), &EntryClass::ClassType.into()) - || e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AttributeType.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::ClassType.into()) + || e.attribute_equality(Attribute::Class, &EntryClass::AttributeType.into()) }); } if !self.changed_acp { @@ -376,24 +362,18 @@ impl<'a> QueryServerWriteTransaction<'a> { .iter() .chain(pre_candidates.iter().map(|e| e.as_ref())) .any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::AccessControlProfile.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) }); } if !self.changed_oauth2 { self.changed_oauth2 = norm_cand.iter().any(|e| { - e.attribute_equality( - Attribute::Class.as_ref(), - &EntryClass::OAuth2ResourceServer.into(), - ) + e.attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into()) }); } if !self.changed_domain { self.changed_domain = norm_cand .iter() - .any(|e| e.attribute_equality(Attribute::Uuid.into(), &PVUUID_DOMAIN_INFO)); + .any(|e| e.attribute_equality(Attribute::Uuid, &PVUUID_DOMAIN_INFO)); } self.changed_uuid.extend( norm_cand @@ -553,7 +533,6 @@ mod tests { assert!(server_txn.modify(&me_emp) == Err(OperationError::EmptyRequest)); // Mod changes no objects - // TODO: @yaleman fix this because we don't have a way to do this anymore let me_nochg = ModifyEvent::new_impersonate_entry_ser( BUILTIN_ACCOUNT_IDM_ADMIN.clone(), filter!(f_eq( @@ -652,8 +631,8 @@ mod tests { server_txn.internal_modify_uuid( t_uuid, &ModifyList::new_list(vec![ - m_assert(Attribute::Uuid.as_ref(), &PartialValue::Uuid(r_uuid)), - m_pres(Attribute::Description.into(), &Value::Utf8("test".into())) + m_assert(Attribute::Uuid, &PartialValue::Uuid(r_uuid)), + m_pres(Attribute::Description, &Value::Utf8("test".into())) ]) ), Err(OperationError::ModifyAssertionFailed) @@ -664,8 +643,8 @@ mod tests { .internal_modify_uuid( t_uuid, &ModifyList::new_list(vec![ - m_assert("uuid", &PartialValue::Uuid(t_uuid)), - m_pres(Attribute::Description.into(), &Value::Utf8("test".into())) + m_assert(Attribute::Uuid, &PartialValue::Uuid(t_uuid)), + m_pres(Attribute::Description, &Value::Utf8("test".into())) ]) ) .is_ok()); @@ -728,7 +707,7 @@ mod tests { )), ModifyList::new_list(vec![ Modify::Present(Attribute::Class.into(), EntryClass::SystemInfo.to_value()), - // Modify::Present("domain".to_string(), Value::new_iutf8("domain.name")), + // Modify::Present(Attribute::Domain.into(), Value::new_iutf8("domain.name")), Modify::Present(Attribute::Version.into(), Value::new_uint32(1)), ]), ); @@ -795,7 +774,7 @@ mod tests { .expect("failed"); // get the primary ava let cred_ref = test_ent - .get_ava_single_credential("primary_credential") + .get_ava_single_credential(Attribute::PrimaryCredential) .expect("Failed"); // do a pw check. assert!(cred_ref.verify_password("test_password").unwrap()); diff --git a/server/lib/src/server/recycle.rs b/server/lib/src/server/recycle.rs index d3962e7f7..e8d54e8e3 100644 --- a/server/lib/src/server/recycle.rs +++ b/server/lib/src/server/recycle.rs @@ -32,7 +32,7 @@ impl<'a> QueryServerWriteTransaction<'a> { })?; let rc = self.internal_search(filter_all!(f_and!([ f_eq(Attribute::Class, EntryClass::Recycled.into()), - f_lt("last_modified_cid", PartialValue::new_cid(cid)), + f_lt(Attribute::LastModifiedCid, PartialValue::new_cid(cid)), ])))?; if rc.is_empty() { @@ -142,7 +142,7 @@ impl<'a> QueryServerWriteTransaction<'a> { // Get this entries uuid. let u: Uuid = e.get_uuid(); - if let Some(riter) = e.get_ava_as_refuuid("directmemberof") { + if let Some(riter) = e.get_ava_as_refuuid(Attribute::DirectMemberOf) { for g_uuid in riter { dm_mods .entry(g_uuid) @@ -642,10 +642,7 @@ mod tests { .pop() .unwrap(); - e.attribute_equality( - Attribute::MemberOf.as_ref(), - &PartialValue::new_refer_s(mo).unwrap(), - ) + e.attribute_equality(Attribute::MemberOf, &PartialValue::new_refer_s(mo).unwrap()) } #[qs_test] diff --git a/server/testkit/Cargo.toml b/server/testkit/Cargo.toml index a5feec709..f1b4a20d8 100644 --- a/server/testkit/Cargo.toml +++ b/server/testkit/Cargo.toml @@ -30,15 +30,14 @@ hyper-tls = { workspace = true } # used for webdriver testing fantoccini = { version = "0.19.3", optional = true } serde = { workspace = true } - - url = { workspace = true, features = ["serde"] } reqwest = { workspace = true, default-features = false, features = ["cookies"] } sketching = { workspace = true } testkit-macros = { workspace = true } tracing = { workspace = true, features = ["attributes"] } tokio = { workspace = true, features = ["net", "sync", "io-util", "macros"] } -openssl.workspace = true +openssl = { workspace = true } +lazy_static = { workspace = true } [build-dependencies] diff --git a/server/testkit/src/lib.rs b/server/testkit/src/lib.rs index f54ea9112..c56824109 100644 --- a/server/testkit/src/lib.rs +++ b/server/testkit/src/lib.rs @@ -14,11 +14,14 @@ use std::net::TcpStream; use std::sync::atomic::{AtomicU16, Ordering}; use kanidm_client::{KanidmClient, KanidmClientBuilder}; -use kanidm_proto::constants::{ATTR_DESCRIPTION, ATTR_LDAP_SSH_PUBLICKEY, ATTR_MAIL, ATTR_NAME}; + use kanidm_proto::v1::{Filter, Modify, ModifyList}; use kanidmd_core::config::{Configuration, IntegrationTestConfig}; use kanidmd_core::{create_server_core, CoreHandle}; -use kanidmd_lib::prelude::Attribute; +use kanidmd_lib::prelude::{ + Attribute, BUILTIN_GROUP_IDM_ADMINS_V1, IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1, + IDM_PEOPLE_EXTEND_PRIV_V1, +}; use tokio::task; pub const ADMIN_TEST_USER: &str = "admin"; @@ -151,18 +154,18 @@ pub async fn add_all_attrs( .await .expect("Failed to extend user group"); - for attr in [ATTR_LDAP_SSH_PUBLICKEY, ATTR_MAIL].iter() { + for attr in [Attribute::SshPublicKey, Attribute::Mail].into_iter() { println!("Checking writable for {}", attr); #[allow(clippy::expect_used)] let res = is_attr_writable(rsclient, id, attr) .await - .expect("Failed to get wriable status for attribute"); + .expect("Failed to get writable status for attribute"); assert!(res); } if let Some(legalname) = legalname { #[allow(clippy::expect_used)] - let res = is_attr_writable(rsclient, legalname, "legalname") + let res = is_attr_writable(rsclient, legalname, Attribute::LegalName) .await .expect("Failed to get writable status for legalname field"); assert!(res); @@ -185,22 +188,22 @@ pub async fn add_all_attrs( } } -pub async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> Option { +pub async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: Attribute) -> Option { println!("writing to attribute: {}", attr); match attr { - "radius_secret" => Some( + Attribute::RadiusSecret => Some( rsclient .idm_account_radius_credential_regenerate(id) .await .is_ok(), ), - "primary_credential" => Some( + Attribute::PrimaryCredential => Some( rsclient .idm_person_account_primary_credential_set_password(id, "dsadjasiodqwjk12asdl") .await .is_ok(), ), - kanidm_proto::constants::ATTR_LDAP_SSH_PUBLICKEY => Some( + Attribute::SshPublicKey => Some( rsclient .idm_person_account_post_ssh_pubkey( id, @@ -211,28 +214,36 @@ pub async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: &str) -> .await .is_ok(), ), - "unix_password" => Some( + Attribute::UnixPassword => Some( rsclient .idm_person_account_unix_cred_put(id, "dsadjasiodqwjk12asdl") .await .is_ok(), ), - "legalname" => Some( + Attribute::LegalName => Some( rsclient - .idm_person_account_set_attr(id, "legalname", &["test legal name"]) + .idm_person_account_set_attr( + id, + Attribute::LegalName.as_ref(), + &["test legal name"], + ) .await .is_ok(), ), - "mail" => Some( + Attribute::Mail => Some( rsclient - .idm_person_account_set_attr(id, "mail", &[&format!("{}@example.com", id)]) + .idm_person_account_set_attr( + id, + Attribute::Mail.as_ref(), + &[&format!("{}@example.com", id)], + ) .await .is_ok(), ), entry => { let new_value = match entry { - "acp_receiver_group" => "00000000-0000-0000-0000-000000000011".to_string(), - "acp_targetscope" => "{\"and\": [{\"eq\": [\"class\",\"access_control_profile\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}".to_string(), + Attribute::AcpReceiverGroup => "00000000-0000-0000-0000-000000000011".to_string(), + Attribute::AcpTargetScope => "{\"and\": [{\"eq\": [\"class\",\"access_control_profile\"]}, {\"andnot\": {\"or\": [{\"eq\": [\"class\", \"tombstone\"]}, {\"eq\": [\"class\", \"recycled\"]}]}}]}".to_string(), _ => id.to_string(), }; let m = ModifyList::new_list(vec![ @@ -249,7 +260,7 @@ pub async fn login_account(rsclient: &KanidmClient, id: &str) { #[allow(clippy::expect_used)] rsclient .idm_group_add_members( - "idm_people_account_password_import_priv", + IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1.name, &[ADMIN_TEST_USER], ) .await @@ -257,7 +268,7 @@ pub async fn login_account(rsclient: &KanidmClient, id: &str) { #[allow(clippy::expect_used)] rsclient - .idm_group_add_members("idm_people_extend_priv", &[ADMIN_TEST_USER]) + .idm_group_add_members(IDM_PEOPLE_EXTEND_PRIV_V1.name, &[ADMIN_TEST_USER]) .await .expect("Failed to add user to idm_people_extend_priv"); @@ -297,7 +308,12 @@ pub async fn login_account_via_admin(rsclient: &KanidmClient, id: &str) { login_account(rsclient, id).await } -pub async fn test_read_attrs(rsclient: &KanidmClient, id: &str, attrs: &[&str], is_readable: bool) { +pub async fn test_read_attrs( + rsclient: &KanidmClient, + id: &str, + attrs: &[Attribute], + is_readable: bool, +) { println!("Test read to {}, is readable: {}", id, is_readable); #[allow(clippy::expect_used)] let rset = rsclient @@ -312,14 +328,14 @@ pub async fn test_read_attrs(rsclient: &KanidmClient, id: &str, attrs: &[&str], println!("Reading {}", attr); #[allow(clippy::unwrap_used)] let is_ok = match *attr { - "radius_secret" => rsclient + Attribute::RadiusSecret => rsclient .idm_account_radius_credential_get(id) .await .unwrap() .is_some(), - _ => e.attrs.get(*attr).is_some(), + _ => e.attrs.get(attr.as_ref()).is_some(), }; - + dbg!(is_ok, is_readable); assert!(is_ok == is_readable) } } @@ -327,14 +343,14 @@ pub async fn test_read_attrs(rsclient: &KanidmClient, id: &str, attrs: &[&str], pub async fn test_write_attrs( rsclient: &KanidmClient, id: &str, - attrs: &[&str], + attrs: &[Attribute], is_writeable: bool, ) { println!("Test write to {}, is writeable: {}", id, is_writeable); for attr in attrs.iter() { println!("Writing to {} - ex {}", attr, is_writeable); #[allow(clippy::unwrap_used)] - let is_ok = is_attr_writable(rsclient, id, attr).await.unwrap(); + let is_ok = is_attr_writable(rsclient, id, *attr).await.unwrap(); assert!(is_ok == is_writeable) } } @@ -347,7 +363,7 @@ pub async fn test_modify_group( // need user test created to be added as test part for group in group_names.iter() { println!("Testing group: {}", group); - for attr in [ATTR_DESCRIPTION, ATTR_NAME].iter() { + for attr in [Attribute::Description, Attribute::Name].into_iter() { #[allow(clippy::unwrap_used)] let is_writable = is_attr_writable(rsclient, group, attr).await.unwrap(); dbg!(group, attr, is_writable, can_be_modified); @@ -373,7 +389,7 @@ pub async fn login_put_admin_idm_admins(rsclient: &KanidmClient) { #[allow(clippy::expect_used)] rsclient - .idm_group_add_members("idm_admins", &[ADMIN_TEST_USER]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &[ADMIN_TEST_USER]) .await .expect("Failed to add admin user to idm_admins") } diff --git a/server/testkit/tests/default_entries.rs b/server/testkit/tests/default_entries.rs index ef73d92eb..829f976bd 100644 --- a/server/testkit/tests/default_entries.rs +++ b/server/testkit/tests/default_entries.rs @@ -1,66 +1,67 @@ #![deny(warnings)] +use lazy_static::lazy_static; use std::collections::HashSet; use kanidm_client::KanidmClient; -use kanidm_proto::constants::{ - APPLICATION_JSON, ATTR_ACP_RECEIVER_GROUP, ATTR_ACP_TARGET_SCOPE, ATTR_DESCRIPTION, - ATTR_LDAP_SSH_PUBLICKEY, ATTR_NAME, -}; -use kanidmd_lib::prelude::{Attribute, EntryClass}; +use kanidm_proto::constants::APPLICATION_JSON; +use kanidmd_lib::prelude::*; use kanidmd_testkit::*; use reqwest::header::CONTENT_TYPE; -// TODO: feed this off attrs -static USER_READABLE_ATTRS: [&str; 9] = [ - "name", - "spn", - "displayname", - "class", - "memberof", - "uuid", - "gidnumber", - "loginshell", - ATTR_LDAP_SSH_PUBLICKEY, +static USER_READABLE_ATTRS: [Attribute; 9] = [ + Attribute::Name, + Attribute::Spn, + Attribute::DisplayName, + Attribute::Class, + Attribute::MemberOf, + Attribute::Uuid, + Attribute::GidNumber, + Attribute::LoginShell, + Attribute::SshPublicKey, ]; -// TODO: feed this off attrs -static SELF_WRITEABLE_ATTRS: [&str; 7] = [ - "name", - "displayname", - "legalname", - "radius_secret", - ATTR_LDAP_SSH_PUBLICKEY, - "unix_password", - // Must be last - changing credential invalidates auth sessions! - "primary_credential", +static SELF_WRITEABLE_ATTRS: [Attribute; 7] = [ + Attribute::Name, + Attribute::DisplayName, + Attribute::LegalName, + Attribute::RadiusSecret, + Attribute::SshPublicKey, + Attribute::UnixPassword, + // needs to be last + Attribute::PrimaryCredential, ]; -static DEFAULT_HP_GROUP_NAMES: [&str; 24] = [ - "idm_admins", - "system_admins", - "idm_people_manage_priv", - "idm_people_account_password_import_priv", - "idm_people_extend_priv", - "idm_people_write_priv", - "idm_people_read_priv", - "idm_group_manage_priv", - "idm_group_write_priv", - "idm_account_manage_priv", - "idm_account_write_priv", - "idm_account_read_priv", - "idm_radius_servers", - "idm_hp_account_manage_priv", - "idm_hp_account_write_priv", - "idm_hp_account_read_priv", - "idm_hp_account_unix_extend_priv", - "idm_schema_manage_priv", - "idm_hp_group_manage_priv", - "idm_hp_group_write_priv", - "idm_hp_group_unix_extend_priv", - "idm_acp_manage_priv", - "domain_admins", - "idm_high_privilege", -]; -static DEFAULT_NOT_HP_GROUP_NAMES: [&str; 2] = - ["idm_account_unix_extend_priv", "idm_group_unix_extend_priv"]; + +lazy_static! { + static ref DEFAULT_HP_GROUP_NAMES: [&'static str; 24] = [ + BUILTIN_GROUP_IDM_ADMINS_V1.name, + BUILTIN_GROUP_SYSTEM_ADMINS_V1.name, + IDM_PEOPLE_MANAGE_PRIV_V1.name, + IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1.name, + IDM_PEOPLE_EXTEND_PRIV_V1.name, + IDM_PEOPLE_WRITE_PRIV_V1.name, + IDM_PEOPLE_READ_PRIV_V1.name, + IDM_GROUP_MANAGE_PRIV_V1.name, + IDM_GROUP_WRITE_PRIV_V1.name, + IDM_ACCOUNT_MANAGE_PRIV_V1.name, + IDM_ACCOUNT_WRITE_PRIV_V1.name, + IDM_ACCOUNT_READ_PRIV_V1.name, + IDM_RADIUS_SERVERS_V1.name, + IDM_HP_ACCOUNT_MANAGE_PRIV_V1.name, + IDM_HP_ACCOUNT_WRITE_PRIV_V1.name, + IDM_HP_ACCOUNT_READ_PRIV_V1.name, + IDM_HP_ACCOUNT_UNIX_EXTEND_PRIV_V1.name, + IDM_SCHEMA_MANAGE_PRIV_V1.name, + IDM_HP_GROUP_MANAGE_PRIV_V1.name, + IDM_HP_GROUP_WRITE_PRIV_V1.name, + IDM_HP_GROUP_UNIX_EXTEND_PRIV_V1.name, + IDM_ACP_MANAGE_PRIV_V1.name, + DOMAIN_ADMINS.name, + IDM_HIGH_PRIVILEGE_V1.name, + ]; + static ref DEFAULT_NOT_HP_GROUP_NAMES: [&'static str; 2] = [ + IDM_ACCOUNT_UNIX_EXTEND_PRIV_V1.name, + IDM_GROUP_UNIX_EXTEND_PRIV_V1.name, + ]; +} // Users // - Read to all self attributes (within security constraints). @@ -78,21 +79,33 @@ async fn test_default_entries_rbac_users(rsclient: KanidmClient) { test_read_attrs(&rsclient, "self_account", &USER_READABLE_ATTRS, true).await; test_read_attrs(&rsclient, "other_account", &USER_READABLE_ATTRS, true).await; - static GROUP_READABLE_ATTRS: [&str; 5] = ["class", "name", "spn", "uuid", "member"]; + static GROUP_READABLE_ATTRS: [Attribute; 5] = [ + Attribute::Class, + Attribute::Name, + Attribute::Spn, + Attribute::Uuid, + Attribute::Member, + ]; test_read_attrs(&rsclient, "self_group", &GROUP_READABLE_ATTRS, true).await; test_read_attrs(&rsclient, "other_group", &GROUP_READABLE_ATTRS, true).await; - static USER_SENSITIVE_ATTRS: [&str; 2] = ["legalname", "mail"]; + static USER_SENSITIVE_ATTRS: [Attribute; 2] = [Attribute::LegalName, Attribute::Mail]; test_read_attrs(&rsclient, "other_account", &USER_SENSITIVE_ATTRS, false).await; - static SELF_READABLE_ATTRS: [&str; 1] = ["radius_secret"]; + static SELF_READABLE_ATTRS: [Attribute; 1] = [Attribute::RadiusSecret]; test_read_attrs(&rsclient, "self_account", &SELF_READABLE_ATTRS, true).await; test_read_attrs(&rsclient, "other_account", &SELF_READABLE_ATTRS, false).await; test_write_attrs(&rsclient, "self_account", &SELF_WRITEABLE_ATTRS, true).await; test_write_attrs(&rsclient, "other_account", &SELF_WRITEABLE_ATTRS, false).await; - static NON_SELF_WRITEABLE_ATTRS: [&str; 5] = ["spn", "class", "memberof", "gidnumber", "uuid"]; + static NON_SELF_WRITEABLE_ATTRS: [Attribute; 5] = [ + Attribute::Spn, + Attribute::Class, + Attribute::MemberOf, + Attribute::GidNumber, + Attribute::Uuid, + ]; test_write_attrs(&rsclient, "self_account", &NON_SELF_WRITEABLE_ATTRS, false).await; } @@ -103,7 +116,12 @@ async fn test_default_entries_rbac_users(rsclient: KanidmClient) { async fn test_default_entries_rbac_account_managers(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; - create_user(&rsclient, "account_manager", "idm_account_manage_priv").await; + create_user( + &rsclient, + "account_manager", + IDM_ACCOUNT_MANAGE_PRIV_V1.name, + ) + .await; create_user_with_all_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, Some("test_group")).await; login_account(&rsclient, "account_manager").await; @@ -115,13 +133,12 @@ async fn test_default_entries_rbac_account_managers(rsclient: KanidmClient) { true, ) .await; - static ACCOUNT_MANAGER_ATTRS: [&str; 5] = [ - "name", - "displayname", - ATTR_LDAP_SSH_PUBLICKEY, - "mail", - // Must be last, writing to this invalidates sessions. - "primary_credential", + static ACCOUNT_MANAGER_ATTRS: [Attribute; 5] = [ + Attribute::Name, + Attribute::DisplayName, + Attribute::PrimaryCredential, + Attribute::SshPublicKey, + Attribute::Mail, ]; test_write_attrs( &rsclient, @@ -131,7 +148,7 @@ async fn test_default_entries_rbac_account_managers(rsclient: KanidmClient) { ) .await; - static PRIVATE_DATA_ATTRS: [&str; 1] = ["legalname"]; + static PRIVATE_DATA_ATTRS: [Attribute; 1] = [Attribute::LegalName]; test_read_attrs( &rsclient, NOT_ADMIN_TEST_USERNAME, @@ -156,9 +173,14 @@ async fn test_default_entries_rbac_account_managers(rsclient: KanidmClient) { async fn test_default_entries_rbac_group_managers(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; - create_user(&rsclient, "group_manager", "idm_group_manage_priv").await; + create_user(&rsclient, "group_manager", IDM_GROUP_MANAGE_PRIV_V1.name).await; // create test user without creating new groups - create_user(&rsclient, NOT_ADMIN_TEST_USERNAME, "idm_admins").await; + create_user( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + BUILTIN_GROUP_IDM_ADMINS_V1.name, + ) + .await; login_account(&rsclient, "group_manager").await; @@ -172,22 +194,31 @@ async fn test_default_entries_rbac_group_managers(rsclient: KanidmClient) { let groups = rsclient.idm_group_list().await.unwrap(); let group_names: HashSet = groups .iter() - .map(|entry| entry.attrs.get("name").unwrap().first().unwrap()) + .map(|entry| { + entry + .attrs + .get(Attribute::Name.as_ref()) + .unwrap() + .first() + .unwrap() + }) .cloned() .collect(); assert!(default_group_names.is_subset(&group_names)); - test_modify_group(&rsclient, &DEFAULT_HP_GROUP_NAMES, false).await; - test_modify_group(&rsclient, &DEFAULT_NOT_HP_GROUP_NAMES, true).await; + test_modify_group(&rsclient, &(*DEFAULT_HP_GROUP_NAMES), false).await; + test_modify_group(&rsclient, &(*DEFAULT_NOT_HP_GROUP_NAMES), true).await; rsclient.idm_group_create("test_group").await.unwrap(); rsclient .idm_group_add_members("test_group", &[NOT_ADMIN_TEST_USERNAME]) .await .unwrap(); - assert!(is_attr_writable(&rsclient, "test_group", ATTR_DESCRIPTION) - .await - .unwrap()); + assert!( + is_attr_writable(&rsclient, "test_group", Attribute::Description) + .await + .unwrap() + ); } // Admins @@ -196,46 +227,46 @@ async fn test_default_entries_rbac_group_managers(rsclient: KanidmClient) { async fn test_default_entries_rbac_admins_access_control_entries(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; - static ACP_COMMON_ATTRS: [&str; 4] = [ - ATTR_NAME, - ATTR_DESCRIPTION, - ATTR_ACP_RECEIVER_GROUP, - ATTR_ACP_TARGET_SCOPE, + static ACP_COMMON_ATTRS: [Attribute; 4] = [ + Attribute::Name, + Attribute::Description, + Attribute::AcpReceiverGroup, + Attribute::AcpTargetScope, ]; - static ACP_ENTRIES: [&str; 28] = [ - "idm_admins_acp_recycle_search", - "idm_admins_acp_revive", - "idm_self_acp_read", - "idm_self_acp_write", - "idm_all_acp_read", - "idm_acp_people_read_priv", - "idm_acp_people_write_priv", - "idm_acp_people_manage", - "idm_acp_people_account_password_import_priv", - "idm_acp_people_extend_priv", - "idm_acp_group_write_priv", - "idm_acp_account_read_priv", - "idm_acp_account_write_priv", - "idm_acp_account_manage", - "idm_acp_radius_servers", - "idm_acp_hp_account_read_priv", - "idm_acp_hp_account_write_priv", - "idm_acp_hp_group_write_priv", - "idm_acp_schema_write_attrs_priv", - "idm_acp_acp_manage_priv", - "idm_acp_schema_write_classes_priv", - "idm_acp_group_manage", - "idm_acp_hp_account_manage", - "idm_acp_hp_group_manage", - "idm_acp_domain_admin_priv", - "idm_acp_system_config_priv", - "idm_acp_account_unix_extend_priv", - "idm_acp_group_unix_extend_priv", + let acp_entries = vec![ + IDM_ADMINS_ACP_RECYCLE_SEARCH_V1.clone(), + IDM_ADMINS_ACP_REVIVE_V1.clone(), + IDM_SELF_ACP_READ_V1.clone(), + IDM_SELF_ACP_WRITE_V1.clone(), + IDM_ALL_ACP_READ_V1.clone(), + IDM_ACP_PEOPLE_READ_PRIV_V1.clone(), + IDM_ACP_PEOPLE_WRITE_PRIV_V1.clone(), + IDM_ACP_PEOPLE_MANAGE_PRIV_V1.clone(), + IDM_ACP_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1.clone(), + IDM_ACP_PEOPLE_EXTEND_PRIV_V1.clone(), + IDM_ACP_GROUP_WRITE_PRIV_V1.clone(), + IDM_ACP_ACCOUNT_READ_PRIV_V1.clone(), + IDM_ACP_ACCOUNT_WRITE_PRIV_V1.clone(), + IDM_ACP_ACCOUNT_MANAGE_PRIV_V1.clone(), + IDM_ACP_RADIUS_SERVERS_V1.clone(), + IDM_ACP_HP_ACCOUNT_READ_PRIV_V1.clone(), + IDM_ACP_HP_ACCOUNT_WRITE_PRIV_V1.clone(), + IDM_ACP_HP_GROUP_WRITE_PRIV_V1.clone(), + IDM_ACP_SCHEMA_WRITE_ATTRS_PRIV_V1.clone(), + IDM_ACP_ACP_MANAGE_PRIV_V1.clone(), + IDM_ACP_SCHEMA_WRITE_CLASSES_PRIV_V1.clone(), + IDM_ACP_GROUP_MANAGE_PRIV_V1.clone(), + IDM_ACP_HP_ACCOUNT_MANAGE_PRIV_V1.clone(), + IDM_ACP_HP_GROUP_MANAGE_PRIV_V1.clone(), + IDM_ACP_DOMAIN_ADMIN_PRIV_V1.clone(), + IDM_ACP_SYSTEM_CONFIG_PRIV_V1.clone(), + IDM_ACP_ACCOUNT_UNIX_EXTEND_PRIV_V1.clone(), + IDM_ACP_GROUP_UNIX_EXTEND_PRIV_V1.clone(), ]; - for entry in ACP_ENTRIES.iter() { - test_read_attrs(&rsclient, entry, &ACP_COMMON_ATTRS, true).await; - test_write_attrs(&rsclient, entry, &ACP_COMMON_ATTRS, true).await; + for entry in acp_entries.iter() { + test_read_attrs(&rsclient, entry.name, &ACP_COMMON_ATTRS, true).await; + test_write_attrs(&rsclient, entry.name, &ACP_COMMON_ATTRS, true).await; } } @@ -327,7 +358,7 @@ async fn test_default_entries_rbac_admins_schema_entries(rsclient: KanidmClient) Attribute::DisplayName, Attribute::LegalName, Attribute::Mail, - Attribute::LdapSshPublicKey, + Attribute::SshPublicKey, Attribute::PrimaryCredential, Attribute::RadiusSecret, Attribute::DomainName, @@ -383,8 +414,12 @@ async fn test_default_entries_rbac_admins_group_entries(rsclient: KanidmClient) async fn test_default_entries_rbac_admins_ha_accounts(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; - static MAIN_ATTRS: [&str; 3] = ["name", "displayname", "primary_credential"]; - test_write_attrs(&rsclient, "idm_admin", &MAIN_ATTRS, true).await; + static MAIN_ATTRS: [Attribute; 3] = [ + Attribute::Name, + Attribute::DisplayName, + Attribute::PrimaryCredential, + ]; + test_write_attrs(&rsclient, BUILTIN_ACCOUNT_IDM_ADMIN.name, &MAIN_ATTRS, true).await; } // recover from the recycle bin @@ -417,12 +452,21 @@ async fn test_default_entries_rbac_admins_recycle_accounts(rsclient: KanidmClien async fn test_default_entries_rbac_people_managers(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; - create_user(&rsclient, "read_people_manager", "idm_people_read_priv").await; + create_user( + &rsclient, + "read_people_manager", + IDM_PEOPLE_READ_PRIV_V1.name, + ) + .await; create_user_with_all_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, Some("test_group")).await; - static PEOPLE_MANAGER_ATTRS: [&str; 2] = ["legalname", "mail"]; + static PEOPLE_MANAGER_ATTRS: [Attribute; 2] = [Attribute::LegalName, Attribute::Mail]; - static TECHNICAL_ATTRS: [&str; 3] = ["radius_secret", "unix_password", "primary_credential"]; + static TECHNICAL_ATTRS: [Attribute; 3] = [ + Attribute::PrimaryCredential, + Attribute::RadiusSecret, + Attribute::UnixPassword, + ]; test_read_attrs( &rsclient, NOT_ADMIN_TEST_USERNAME, @@ -455,7 +499,12 @@ async fn test_default_entries_rbac_people_managers(rsclient: KanidmClient) { .auth_simple_password(ADMIN_TEST_USER, ADMIN_TEST_PASSWORD) .await .unwrap(); - create_user(&rsclient, "write_people_manager", "idm_people_write_priv").await; + create_user( + &rsclient, + "write_people_manager", + IDM_PEOPLE_WRITE_PRIV_V1.name, + ) + .await; login_account(&rsclient, "write_people_manager").await; test_read_attrs( @@ -517,11 +566,16 @@ async fn test_default_entries_rbac_anonymous_entry(rsclient: KanidmClient) { async fn test_default_entries_rbac_radius_servers(rsclient: KanidmClient) { login_put_admin_idm_admins(&rsclient).await; - create_user(&rsclient, "radius_server", "idm_radius_servers").await; + create_user(&rsclient, "radius_server", IDM_RADIUS_SERVERS_V1.name).await; create_user_with_all_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, Some("test_group")).await; login_account(&rsclient, "radius_server").await; - static RADIUS_NECESSARY_ATTRS: [&str; 4] = ["name", "spn", "uuid", "radius_secret"]; + static RADIUS_NECESSARY_ATTRS: [Attribute; 4] = [ + Attribute::Name, + Attribute::Spn, + Attribute::Uuid, + Attribute::RadiusSecret, + ]; test_read_attrs( &rsclient, @@ -555,7 +609,7 @@ async fn test_self_write_mail_priv_people(rsclient: KanidmClient) { create_user_with_all_attrs(&rsclient, "other", None).await; rsclient .idm_group_add_members( - "idm_people_self_write_mail_priv", + IDM_PEOPLE_SELF_WRITE_MAIL_PRIV_V1.name, &["other", NOT_ADMIN_TEST_USERNAME], ) .await @@ -565,15 +619,15 @@ async fn test_self_write_mail_priv_people(rsclient: KanidmClient) { login_account(&rsclient, NOT_ADMIN_TEST_USERNAME).await; // can write to own mail - test_write_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, &["mail"], true).await; + test_write_attrs(&rsclient, NOT_ADMIN_TEST_USERNAME, &[Attribute::Mail], true).await; // not someone elses - test_write_attrs(&rsclient, "other", &["mail"], false).await; + test_write_attrs(&rsclient, "other", &[Attribute::Mail], false).await; // but they can write to theirs login_account_via_admin(&rsclient, "other").await; - test_write_attrs(&rsclient, "other", &["mail"], true).await; + test_write_attrs(&rsclient, "other", &[Attribute::Mail], true).await; login_account_via_admin(&rsclient, "nonperson").await; - test_write_attrs(&rsclient, "nonperson", &["mail"], false).await; + test_write_attrs(&rsclient, "nonperson", &[Attribute::Mail], false).await; } #[kanidmd_testkit::test] diff --git a/server/testkit/tests/identity_verification_tests.rs b/server/testkit/tests/identity_verification_tests.rs index 8100f5fe3..efcee525f 100644 --- a/server/testkit/tests/identity_verification_tests.rs +++ b/server/testkit/tests/identity_verification_tests.rs @@ -5,7 +5,10 @@ use kanidm_proto::{ v1::Entry, }; -use kanidmd_lib::prelude::Attribute; +use kanidmd_lib::prelude::{ + Attribute, BUILTIN_GROUP_IDM_ADMINS_V1, IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1, + IDM_PEOPLE_MANAGE_PRIV_V1, +}; use kanidmd_testkit::ADMIN_TEST_PASSWORD; use reqwest::StatusCode; @@ -273,17 +276,17 @@ async fn setup_server(rsclient: &KanidmClient) { // To enable the admin to actually make some of these changes, we have // to make them a people admin. NOT recommended in production! rsclient - .idm_group_add_members("idm_people_account_password_import_priv", &["admin"]) + .idm_group_add_members(IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1.name, &["admin"]) .await .unwrap(); rsclient - .idm_group_add_members("idm_people_manage_priv", &["admin"]) + .idm_group_add_members(IDM_PEOPLE_MANAGE_PRIV_V1.name, &["admin"]) .await .unwrap(); rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); } diff --git a/server/testkit/tests/oauth2_test.rs b/server/testkit/tests/oauth2_test.rs index 6daa40b79..0d2325375 100644 --- a/server/testkit/tests/oauth2_test.rs +++ b/server/testkit/tests/oauth2_test.rs @@ -9,6 +9,7 @@ use kanidm_proto::oauth2::{ AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest, AccessTokenResponse, AuthorisationResponse, GrantTypeReq, OidcDiscoveryResponse, }; +use kanidmd_lib::prelude::{Attribute, BUILTIN_GROUP_IDM_ADMINS_V1, IDM_ALL_ACCOUNTS}; use oauth2_ext::PkceCodeChallenge; use reqwest::header::{HeaderValue, CONTENT_TYPE}; use reqwest::StatusCode; @@ -65,7 +66,7 @@ async fn test_oauth2_openid_basic_flow(rsclient: KanidmClient) { // Extend the admin account with extended details for openid claims. rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -75,7 +76,11 @@ async fn test_oauth2_openid_basic_flow(rsclient: KanidmClient) { .expect("Failed to create account details"); rsclient - .idm_person_account_set_attr("oauth_test", "mail", &["oauth_test@localhost"]) + .idm_person_account_set_attr( + "oauth_test", + Attribute::Mail.as_ref(), + &["oauth_test@localhost"], + ) .await .expect("Failed to create account mail"); @@ -92,14 +97,18 @@ async fn test_oauth2_openid_basic_flow(rsclient: KanidmClient) { rsclient .idm_oauth2_rs_update_scope_map( "test_integration", - "idm_all_accounts", + IDM_ALL_ACCOUNTS.name, vec![OAUTH2_SCOPE_READ, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID], ) .await .expect("Failed to update oauth2 scopes"); rsclient - .idm_oauth2_rs_update_sup_scope_map("test_integration", "idm_all_accounts", vec!["admin"]) + .idm_oauth2_rs_update_sup_scope_map( + "test_integration", + IDM_ALL_ACCOUNTS.name, + vec!["admin"], + ) .await .expect("Failed to update oauth2 scopes"); @@ -417,7 +426,7 @@ async fn test_oauth2_openid_public_flow(rsclient: KanidmClient) { // Extend the admin account with extended details for openid claims. rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -427,7 +436,11 @@ async fn test_oauth2_openid_public_flow(rsclient: KanidmClient) { .expect("Failed to create account details"); rsclient - .idm_person_account_set_attr("oauth_test", "mail", &["oauth_test@localhost"]) + .idm_person_account_set_attr( + "oauth_test", + Attribute::Mail.as_ref(), + &["oauth_test@localhost"], + ) .await .expect("Failed to create account mail"); @@ -444,14 +457,18 @@ async fn test_oauth2_openid_public_flow(rsclient: KanidmClient) { rsclient .idm_oauth2_rs_update_scope_map( "test_integration", - "idm_all_accounts", + IDM_ALL_ACCOUNTS.name, vec![OAUTH2_SCOPE_READ, OAUTH2_SCOPE_EMAIL, OAUTH2_SCOPE_OPENID], ) .await .expect("Failed to update oauth2 scopes"); rsclient - .idm_oauth2_rs_update_sup_scope_map("test_integration", "idm_all_accounts", vec!["admin"]) + .idm_oauth2_rs_update_sup_scope_map( + "test_integration", + IDM_ALL_ACCOUNTS.name, + vec!["admin"], + ) .await .expect("Failed to update oauth2 scopes"); diff --git a/server/testkit/tests/proto_v1_test.rs b/server/testkit/tests/proto_v1_test.rs index 2b7fedc32..446084630 100644 --- a/server/testkit/tests/proto_v1_test.rs +++ b/server/testkit/tests/proto_v1_test.rs @@ -7,7 +7,10 @@ use kanidm_proto::v1::{ UserAuthToken, }; use kanidmd_lib::credential::totp::Totp; -use kanidmd_lib::prelude::Attribute; +use kanidmd_lib::prelude::{ + Attribute, BUILTIN_GROUP_IDM_ADMINS_V1, BUILTIN_GROUP_SYSTEM_ADMINS_V1, + IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1, +}; use tracing::debug; use std::str::FromStr; @@ -52,8 +55,8 @@ async fn test_server_modify(rsclient: KanidmClient) { // Build a self mod. let f = Filter::SelfUuid; let m = ModifyList::new_list(vec![ - Modify::Purged("displayname".to_string()), - Modify::Present("displayname".to_string(), "test".to_string()), + Modify::Purged(Attribute::DisplayName.to_string()), + Modify::Present(Attribute::DisplayName.to_string(), "test".to_string()), ]); // Not logged in - should fail! @@ -155,7 +158,10 @@ async fn test_server_rest_group_read(rsclient: KanidmClient) { let g_list = rsclient.idm_group_list().await.unwrap(); assert!(!g_list.is_empty()); - let g = rsclient.idm_group_get("idm_admins").await.unwrap(); + let g = rsclient + .idm_group_get(BUILTIN_GROUP_IDM_ADMINS_V1.name) + .await + .unwrap(); assert!(g.is_some()); println!("{:?}", g); } @@ -224,12 +230,18 @@ async fn test_server_rest_group_lifecycle(rsclient: KanidmClient) { assert!(g_list_3.len() == g_list.len()); // Check we can get an exact group - let g = rsclient.idm_group_get("idm_admins").await.unwrap(); + let g = rsclient + .idm_group_get(BUILTIN_GROUP_IDM_ADMINS_V1.name) + .await + .unwrap(); assert!(g.is_some()); println!("{:?}", g); // They should have members - let members = rsclient.idm_group_get_members("idm_admins").await.unwrap(); + let members = rsclient + .idm_group_get_members(BUILTIN_GROUP_IDM_ADMINS_V1.name) + .await + .unwrap(); println!("{:?}", members); assert!(members == Some(vec!["idm_admin@localhost".to_string()])); } @@ -268,11 +280,17 @@ async fn test_server_rest_schema_read(rsclient: KanidmClient) { assert!(!c_list.is_empty()); // Get an attr/class - let a = rsclient.idm_schema_attributetype_get("name").await.unwrap(); + let a = rsclient + .idm_schema_attributetype_get(Attribute::Name.as_ref()) + .await + .unwrap(); assert!(a.is_some()); println!("{:?}", a); - let c = rsclient.idm_schema_classtype_get("account").await.unwrap(); + let c = rsclient + .idm_schema_classtype_get(Attribute::Account.as_ref()) + .await + .unwrap(); assert!(c.is_some()); println!("{:?}", c); } @@ -287,7 +305,7 @@ async fn test_server_radius_credential_lifecycle(rsclient: KanidmClient) { // All admin to create persons. rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -358,7 +376,7 @@ async fn test_server_rest_person_account_lifecycle(rsclient: KanidmClient) { // To enable the admin to actually make some of these changes, we have // to make them a people admin. NOT recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -382,12 +400,16 @@ async fn test_server_rest_person_account_lifecycle(rsclient: KanidmClient) { // Test adding some mail addrs rsclient - .idm_person_account_add_attr("demo_account", "mail", &["demo@idm.example.com"]) + .idm_person_account_add_attr( + "demo_account", + Attribute::Mail.as_ref(), + &["demo@idm.example.com"], + ) .await .unwrap(); let r = rsclient - .idm_person_account_get_attr("demo_account", "mail") + .idm_person_account_get_attr("demo_account", Attribute::Mail.as_ref()) .await .unwrap(); @@ -494,7 +516,7 @@ async fn test_server_rest_domain_lifecycle(rsclient: KanidmClient) { assert!( dlocal .attrs - .get("domain_display_name") + .get(Attribute::DomainDisplayName.as_ref()) .and_then(|v| v.get(0)) == Some(&"Super Cool Crabz".to_string()) ); @@ -508,7 +530,7 @@ async fn test_server_rest_posix_lifecycle(rsclient: KanidmClient) { assert!(res.is_ok()); // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -617,7 +639,7 @@ async fn test_server_rest_posix_auth_lifecycle(rsclient: KanidmClient) { // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -714,7 +736,7 @@ async fn test_server_rest_recycle_lifecycle(rsclient: KanidmClient) { // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -768,11 +790,11 @@ async fn test_server_rest_account_import_password(rsclient: KanidmClient) { // To enable the admin to actually make some of these changes, we have // to make them a password import admin. NOT recommended in production! rsclient - .idm_group_add_members("idm_people_account_password_import_priv", &["admin"]) + .idm_group_add_members(IDM_PEOPLE_ACCOUNT_PASSWORD_IMPORT_PRIV_V1.name, &["admin"]) .await .unwrap(); rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -865,9 +887,13 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) { eprintln!("{:?}", oauth2_config); // What can we see? - assert!(oauth2_config.attrs.contains_key("oauth2_rs_basic_secret")); + assert!(oauth2_config + .attrs + .contains_key(Attribute::OAuth2RsBasicSecret.as_ref())); // This is present, but redacted. - assert!(oauth2_config.attrs.contains_key("oauth2_rs_token_key")); + assert!(oauth2_config + .attrs + .contains_key(Attribute::OAuth2RsTokenKey.as_ref())); // Mod delete the secret/key and check them again. // Check we can patch the oauth2_rs_name / oauth2_rs_origin @@ -896,7 +922,11 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) { // Check that we can add scope maps and delete them. rsclient - .idm_oauth2_rs_update_scope_map("test_integration", "system_admins", vec!["a", "b"]) + .idm_oauth2_rs_update_scope_map( + "test_integration", + BUILTIN_GROUP_SYSTEM_ADMINS_V1.name, + vec!["a", "b"], + ) .await .expect("Failed to create scope map"); @@ -911,7 +941,11 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) { // Check we can update a scope map rsclient - .idm_oauth2_rs_update_scope_map("test_integration", "system_admins", vec!["a", "b", "c"]) + .idm_oauth2_rs_update_scope_map( + "test_integration", + BUILTIN_GROUP_SYSTEM_ADMINS_V1.name, + vec!["a", "b", "c"], + ) .await .expect("Failed to create scope map"); @@ -927,7 +961,7 @@ async fn test_server_rest_oauth2_basic_lifecycle(rsclient: KanidmClient) { // Check we can delete a scope map. rsclient - .idm_oauth2_rs_delete_scope_map("test_integration", "system_admins") + .idm_oauth2_rs_delete_scope_map("test_integration", BUILTIN_GROUP_SYSTEM_ADMINS_V1.name) .await .expect("Failed to delete scope map"); @@ -967,7 +1001,7 @@ async fn test_server_credential_update_session_pw(rsclient: KanidmClient) { // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -1025,7 +1059,7 @@ async fn test_server_credential_update_session_totp_pw(rsclient: KanidmClient) { // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -1154,7 +1188,7 @@ async fn setup_demo_account_passkey(rsclient: &KanidmClient) -> WebauthnAuthenti // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -1238,7 +1272,7 @@ async fn setup_demo_account_password( // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .expect("Failed to add admin to idm_admins"); @@ -1312,7 +1346,7 @@ async fn test_server_api_token_lifecycle(rsclient: KanidmClient) { // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); @@ -1370,7 +1404,7 @@ async fn test_server_user_auth_token_lifecycle(rsclient: KanidmClient) { // Not recommended in production! rsclient - .idm_group_add_members("idm_admins", &["admin"]) + .idm_group_add_members(BUILTIN_GROUP_IDM_ADMINS_V1.name, &["admin"]) .await .unwrap(); diff --git a/server/testkit/tests/unix.rs b/server/testkit/tests/unix.rs index 09713b9ee..8381b4e69 100644 --- a/server/testkit/tests/unix.rs +++ b/server/testkit/tests/unix.rs @@ -1,4 +1,5 @@ use kanidm_client::KanidmClient; +use kanidmd_lib::prelude::BUILTIN_GROUP_IDM_ADMINS_V1; use kanidmd_testkit::*; #[kanidmd_testkit::test] @@ -7,7 +8,12 @@ async fn account_id_unix_token(rsclient: KanidmClient) { create_user(&rsclient, "group_manager", "idm_group_manage_priv").await; // create test user without creating new groups - create_user(&rsclient, NOT_ADMIN_TEST_USERNAME, "idm_admins").await; + create_user( + &rsclient, + NOT_ADMIN_TEST_USERNAME, + BUILTIN_GROUP_IDM_ADMINS_V1.name, + ) + .await; login_account(&rsclient, "group_manager").await; let response = rsclient diff --git a/server/web_ui/src/components/admin_accounts.rs b/server/web_ui/src/components/admin_accounts.rs index 77fe9c2d4..209dd0a1d 100644 --- a/server/web_ui/src/components/admin_accounts.rs +++ b/server/web_ui/src/components/admin_accounts.rs @@ -368,7 +368,6 @@ impl Component for AdminViewPerson { Some(value) => value.to_string(), None => String::from("Display Name Unset"), }; - // let user_groups = userinfo.youare.attrs.get("memberof"); // let mail_primary = match userinfo.uat.mail_primary.as_ref() { // Some(email_address) => { diff --git a/tools/cli/src/cli/person.rs b/tools/cli/src/cli/person.rs index df5e9a63d..74131a92d 100644 --- a/tools/cli/src/cli/person.rs +++ b/tools/cli/src/cli/person.rs @@ -6,6 +6,7 @@ use dialoguer::theme::ColorfulTheme; use dialoguer::{Confirm, Input, Password, Select}; use kanidm_client::ClientError::Http as ClientErrorHttp; use kanidm_client::KanidmClient; +use kanidm_proto::constants::{ATTR_ACCOUNT_EXPIRE, ATTR_ACCOUNT_VALID_FROM}; use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus}; use kanidm_proto::v1::OperationError::PasswordQuality; use kanidm_proto::v1::{ @@ -355,7 +356,7 @@ impl PersonOpt { let ex = match client .idm_person_account_get_attr( ano.aopts.account_id.as_str(), - "account_expire", + ATTR_ACCOUNT_EXPIRE, ) .await { @@ -366,7 +367,7 @@ impl PersonOpt { let vf = match client .idm_person_account_get_attr( ano.aopts.account_id.as_str(), - "account_valid_from", + ATTR_ACCOUNT_VALID_FROM, ) .await { @@ -415,7 +416,7 @@ impl PersonOpt { match client .idm_person_account_purge_attr( ano.aopts.account_id.as_str(), - "account_expire", + ATTR_ACCOUNT_EXPIRE, ) .await { @@ -435,7 +436,7 @@ impl PersonOpt { match client .idm_person_account_set_attr( ano.aopts.account_id.as_str(), - "account_expire", + ATTR_ACCOUNT_EXPIRE, &[&now], ) .await @@ -456,7 +457,7 @@ impl PersonOpt { match client .idm_person_account_set_attr( ano.aopts.account_id.as_str(), - "account_expire", + ATTR_ACCOUNT_EXPIRE, &[&epoch_str], ) .await @@ -473,7 +474,7 @@ impl PersonOpt { match client .idm_person_account_set_attr( ano.aopts.account_id.as_str(), - "account_expire", + ATTR_ACCOUNT_EXPIRE, &[ano.datetime.as_str()], ) .await @@ -490,7 +491,7 @@ impl PersonOpt { match client .idm_person_account_purge_attr( ano.aopts.account_id.as_str(), - "account_valid_from", + ATTR_ACCOUNT_VALID_FROM, ) .await { @@ -511,7 +512,7 @@ impl PersonOpt { match client .idm_person_account_set_attr( ano.aopts.account_id.as_str(), - "account_valid_from", + ATTR_ACCOUNT_VALID_FROM, &[ano.datetime.as_str()], ) .await diff --git a/tools/cli/src/cli/serviceaccount.rs b/tools/cli/src/cli/serviceaccount.rs index b810115cd..0aa3f3bda 100644 --- a/tools/cli/src/cli/serviceaccount.rs +++ b/tools/cli/src/cli/serviceaccount.rs @@ -1,4 +1,5 @@ use crate::common::OpType; +use kanidm_proto::constants::{ATTR_ACCOUNT_EXPIRE, ATTR_ACCOUNT_VALID_FROM}; use kanidm_proto::messages::{AccountChangeMessage, ConsoleOutputMode, MessageStatus}; use time::OffsetDateTime; @@ -368,7 +369,7 @@ impl ServiceAccountOpt { let ex = match client .idm_service_account_get_attr( ano.aopts.account_id.as_str(), - "account_expire", + ATTR_ACCOUNT_EXPIRE, ) .await { @@ -382,7 +383,7 @@ impl ServiceAccountOpt { let vf = match client .idm_service_account_get_attr( ano.aopts.account_id.as_str(), - "account_valid_from", + ATTR_ACCOUNT_VALID_FROM, ) .await { @@ -434,7 +435,7 @@ impl ServiceAccountOpt { match client .idm_service_account_purge_attr( ano.aopts.account_id.as_str(), - "account_expire", + ATTR_ACCOUNT_EXPIRE, ) .await { @@ -450,7 +451,7 @@ impl ServiceAccountOpt { match client .idm_service_account_set_attr( ano.aopts.account_id.as_str(), - "account_expire", + ATTR_ACCOUNT_EXPIRE, &[ano.datetime.as_str()], ) .await @@ -467,7 +468,7 @@ impl ServiceAccountOpt { match client .idm_service_account_purge_attr( ano.aopts.account_id.as_str(), - "account_valid_from", + ATTR_ACCOUNT_VALID_FROM, ) .await { @@ -484,7 +485,7 @@ impl ServiceAccountOpt { match client .idm_service_account_set_attr( ano.aopts.account_id.as_str(), - "account_valid_from", + ATTR_ACCOUNT_VALID_FROM, &[ano.datetime.as_str()], ) .await diff --git a/tools/iam_migrations/freeipa/src/main.rs b/tools/iam_migrations/freeipa/src/main.rs index 2e2e5257f..e5f5d5fd7 100644 --- a/tools/iam_migrations/freeipa/src/main.rs +++ b/tools/iam_migrations/freeipa/src/main.rs @@ -23,6 +23,7 @@ use base64urlsafedata::Base64UrlSafeData; use chrono::Utc; use clap::Parser; use cron::Schedule; +use kanidm_proto::constants::{LDAP_ATTR_CN, LDAP_ATTR_OBJECTCLASS}; use kanidmd_lib::prelude::Attribute; use std::collections::BTreeMap; use std::fs::metadata; @@ -354,35 +355,35 @@ async fn run_sync( let is_initialise = cookie.is_none(); let filter = LdapFilter::Or(vec![ - // LdapFilter::Equality("objectclass".to_string(), "domain".to_string()), + // LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "domain".to_string()), LdapFilter::And(vec![ - LdapFilter::Equality("objectclass".to_string(), "person".to_string()), - LdapFilter::Equality("objectclass".to_string(), "ipantuserattrs".to_string()), - LdapFilter::Equality("objectclass".to_string(), "posixaccount".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "person".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "ipantuserattrs".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "posixaccount".to_string()), ]), LdapFilter::And(vec![ - LdapFilter::Equality("objectclass".to_string(), "groupofnames".to_string()), - LdapFilter::Equality("objectclass".to_string(), "ipausergroup".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "groupofnames".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "ipausergroup".to_string()), // Ignore user private groups, kani generates these internally. LdapFilter::Not(Box::new(LdapFilter::Equality( - "objectclass".to_string(), + LDAP_ATTR_OBJECTCLASS.into(), "mepmanagedentry".to_string(), ))), // Need to exclude the admins group as it gid conflicts to admin. LdapFilter::Not(Box::new(LdapFilter::Equality( - "cn".to_string(), + LDAP_ATTR_CN.into(), "admins".to_string(), ))), // Kani internally has an all persons group. LdapFilter::Not(Box::new(LdapFilter::Equality( - "cn".to_string(), + LDAP_ATTR_CN.into(), "ipausers".to_string(), ))), ]), // Fetch TOTP's so we know when/if they change. LdapFilter::And(vec![ - LdapFilter::Equality("objectclass".to_string(), "ipatoken".to_string()), - LdapFilter::Equality("objectclass".to_string(), "ipatokentotp".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "ipatoken".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "ipatokentotp".to_string()), ]), ]); @@ -549,7 +550,7 @@ async fn process_ipa_sync_result( if lentry .entry .attrs - .get("objectclass") + .get(LDAP_ATTR_OBJECTCLASS) .map(|oc| oc.contains("ipatokentotp")) .unwrap_or_default() { @@ -580,7 +581,7 @@ async fn process_ipa_sync_result( if lentry .entry .attrs - .get("objectclass") + .get(LDAP_ATTR_OBJECTCLASS) .map(|oc| oc.contains("person")) .unwrap_or_default() { @@ -624,8 +625,8 @@ async fn process_ipa_sync_result( if !totp_conditions.is_empty() { or_filter.push(LdapFilter::And(vec![ - LdapFilter::Equality("objectclass".to_string(), "ipatoken".to_string()), - LdapFilter::Equality("objectclass".to_string(), "ipatokentotp".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "ipatoken".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "ipatokentotp".to_string()), LdapFilter::Or(totp_conditions), ])); } @@ -642,9 +643,9 @@ async fn process_ipa_sync_result( if !user_conditions.is_empty() { or_filter.push(LdapFilter::And(vec![ - LdapFilter::Equality("objectclass".to_string(), "person".to_string()), - LdapFilter::Equality("objectclass".to_string(), "ipantuserattrs".to_string()), - LdapFilter::Equality("objectclass".to_string(), "posixaccount".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "person".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "ipantuserattrs".to_string()), + LdapFilter::Equality(LDAP_ATTR_OBJECTCLASS.into(), "posixaccount".to_string()), LdapFilter::Or(user_conditions), ])); } @@ -681,7 +682,7 @@ async fn process_ipa_sync_result( if lentry .entry .attrs - .get("objectclass") + .get(LDAP_ATTR_OBJECTCLASS) .map(|oc| oc.contains("ipatokentotp")) .unwrap_or_default() { @@ -776,9 +777,13 @@ fn ipa_to_scim_entry( return Ok(None); } - let oc = sync_entry.entry.attrs.get("objectclass").ok_or_else(|| { - error!("Invalid entry - no object class {}", dn); - })?; + let oc = sync_entry + .entry + .attrs + .get(LDAP_ATTR_OBJECTCLASS) + .ok_or_else(|| { + error!("Invalid entry - no object class {}", dn); + })?; if oc.contains("person") { let LdapSyncReplEntry { @@ -837,7 +842,7 @@ fn ipa_to_scim_entry( .or_else(|| entry.remove_ava_single(Attribute::UserPassword.as_ref())); let mail: Vec<_> = entry - .remove_ava("mail") + .remove_ava(Attribute::Mail.as_ref()) .map(|set| { set.into_iter() .map(|addr| MultiValueAttr { @@ -867,12 +872,12 @@ fn ipa_to_scim_entry( }; let ssh_publickey = entry - .remove_ava("ipasshpubkey") + .remove_ava(Attribute::IpaSshPubKey.as_ref()) .map(|set| { set.into_iter() .enumerate() .map(|(i, value)| ScimSshPubKey { - label: format!("ipasshpubkey-{}", i), + label: format!("{}-{}", Attribute::IpaSshPubKey, i), value, }) .collect() @@ -930,7 +935,7 @@ fn ipa_to_scim_entry( .transpose()?; let members: Vec<_> = entry - .remove_ava("member") + .remove_ava(Attribute::Member.as_ref()) .map(|set| { set.into_iter() .map(|external_id| ScimExternalMember { external_id }) diff --git a/tools/iam_migrations/ldap/src/main.rs b/tools/iam_migrations/ldap/src/main.rs index def1dcc22..1f0b90e5a 100644 --- a/tools/iam_migrations/ldap/src/main.rs +++ b/tools/iam_migrations/ldap/src/main.rs @@ -19,6 +19,7 @@ use crate::error::SyncError; use chrono::Utc; use clap::Parser; use cron::Schedule; +use kanidm_proto::constants::ATTR_OBJECTCLASS; use std::fs::metadata; use std::fs::File; use std::io::Read; @@ -472,9 +473,13 @@ fn ldap_to_scim_entry( return Ok(None); } - let oc = sync_entry.entry.attrs.get("objectclass").ok_or_else(|| { - error!("Invalid entry - no object class {}", dn); - })?; + let oc = sync_entry + .entry + .attrs + .get(ATTR_OBJECTCLASS) + .ok_or_else(|| { + error!("Invalid entry - no object class {}", dn); + })?; if oc.contains(&sync_config.person_objectclass) { let LdapSyncReplEntry { diff --git a/unix_integration/tests/cache_layer_test.rs b/unix_integration/tests/cache_layer_test.rs index 6dfe3d57a..ca8ae47d0 100644 --- a/unix_integration/tests/cache_layer_test.rs +++ b/unix_integration/tests/cache_layer_test.rs @@ -6,6 +6,7 @@ use std::sync::atomic::{AtomicU16, Ordering}; use std::time::Duration; use kanidm_client::{KanidmClient, KanidmClientBuilder}; +use kanidm_proto::constants::ATTR_ACCOUNT_EXPIRE; use kanidm_unix_common::constants::{ DEFAULT_GID_ATTR_MAP, DEFAULT_HOME_ALIAS, DEFAULT_HOME_ATTR, DEFAULT_HOME_PREFIX, DEFAULT_SHELL, DEFAULT_UID_ATTR_MAP, @@ -132,6 +133,15 @@ async fn setup_test(fix_fn: Fixture) -> (Resolver, KanidmClient) // let the tables hit the floor } +/// This is the test fixture. It sets up the following: +/// - adds admin to idm_admins +/// - creates a test account (testaccount1) +/// - extends the test account with posix attrs +/// - adds a ssh public key to the test account +/// - sets a posix password for the test account +/// - creates a test group (testgroup1) and adds the test account to the test group +/// - extends testgroup1 with posix attrs +/// - creates two more groups with unix perms (allowed_group, masked_group) async fn test_fixture(rsclient: KanidmClient) { let res = rsclient .auth_simple_password("admin", ADMIN_TEST_PASSWORD) @@ -585,7 +595,7 @@ async fn test_cache_account_expiry() { .await .expect("failed to auth as admin"); adminclient - .idm_person_account_set_attr("testaccount1", "account_expire", &[ACCOUNT_EXPIRE]) + .idm_person_account_set_attr("testaccount1", ATTR_ACCOUNT_EXPIRE, &[ACCOUNT_EXPIRE]) .await .unwrap(); // auth will fail