Cinco de yakko (#2108)

* there are always more yaks
* see? ldap yaks.
* fixing stupid radius container build thing
This commit is contained in:
James Hodgkinson 2023-09-16 12:11:06 +10:00 committed by GitHub
parent 77da40d528
commit d5ed335b52
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
81 changed files with 2057 additions and 1774 deletions

1
Cargo.lock generated
View file

@ -3108,6 +3108,7 @@ dependencies = [
"kanidm_proto",
"kanidmd_core",
"kanidmd_lib",
"lazy_static",
"oauth2",
"openssl",
"reqwest",

View file

@ -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";

View file

@ -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<ScimEntry> 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,

View file

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

View file

@ -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 \

View file

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

View file

@ -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,

View file

@ -84,9 +84,9 @@ pub async fn oauth2_basic_post(
Json(obj): Json<ProtoEntry>,
) -> 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<ProtoEntry>,
) -> 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
}

View file

@ -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:?}, ")?;
}

View file

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

View file

@ -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<Vec<u8>> = 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.
//

View file

@ -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,

View file

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

View file

@ -40,7 +40,7 @@ lazy_static! {
/// Built-in Access Control Profile definitions
pub struct BuiltinAcp {
classes: Vec<EntryClass>,
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![

View file

@ -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<String> 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<String> 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<String> 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<Attribute> 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<Attribute> 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<D>(deserializer: D) -> Result<Self, D::Error>
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,

File diff suppressed because it is too large Load diff

View file

@ -614,7 +614,7 @@ impl ModifyEvent {
pub fn from_target_uuid_attr_purge(
ident: Identity,
target_uuid: Uuid,
attr: &str,
attr: Attribute,
filter: Filter<FilterInvalid>,
qs: &QueryServerWriteTransaction,
) -> Result<Self, OperationError> {

View file

@ -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 {
FC::Or(vs)
}
#[allow(dead_code)]
pub fn f_and(vs: Vec<FC>) -> FC {
FC::And(vs)
}
#[allow(dead_code)]
pub fn f_inc(vs: Vec<FC>) -> 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));
}

View file

@ -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<ModifyList<ModifyInvalid>, 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<CredentialStatus, OperationError> {
@ -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)| {

View file

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

View file

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

View file

@ -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<Group> = match $value.get_ava_as_refuuid("memberof") {
let mut groups: Vec<Group> = 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<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
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();

View file

@ -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<u8>) -> Result<u32, OperationError> {

View file

@ -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<Vec<LdapMsg>, 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<String> {
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();

View file

@ -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.

View file

@ -24,40 +24,49 @@ impl RadiusAccount {
value: &Entry<EntryReduced, EntryCommitted>,
qs: &mut QueryServerReadTransaction,
) -> Result<Self, OperationError> {
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,

View file

@ -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?");

View file

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

View file

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

View file

@ -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::<Result<Vec<_>, _>>()
})
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::<Result<Vec<_>, _>>()
})
})
.unwrap_or_else(|| {
// No matching entry? Return none.

View file

@ -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<ModifyList<ModifyInvalid>, 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()),

View file

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

View file

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

View file

@ -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<VALID, STATE>(
}
// 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();

View file

@ -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()];

View file

@ -59,12 +59,11 @@ impl CredImport {
fn modify_inner<T: Clone>(cand: &mut [Entry<EntryInvalid, T>]) -> 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) => {

View file

@ -68,54 +68,54 @@ impl Domain {
cand: &mut [Entry<EntryInvalid, T>],
) -> 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)));
}
}

View file

@ -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<Entry<_, _>>>, 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());
}
);
}

View file

@ -23,19 +23,21 @@ impl EcdhKeyGen {
cands: &mut [Entry<EntryInvalid, STATE>],
) -> 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)
)
}
);

View file

@ -20,9 +20,9 @@ const GID_SAFETY_NUMBER_MIN: u32 = 1000;
pub struct GidNumber {}
fn apply_gidnumber<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> 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<T: Clone>(e: &mut Entry<EntryInvalid, T>) -> 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,
|_| {},

View file

@ -46,21 +46,21 @@ impl Plugin for JwsKeygen {
impl JwsKeygen {
fn modify_inner<T: Clone>(cand: &mut [Entry<EntryInvalid, T>]) -> 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"));
}
);
}

View file

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

View file

@ -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<VALUE, STATE>(entry: &mut Entry<VALUE, STATE>) -> 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!(

View file

@ -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<Attribute> = {
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<EntryInitNew> =
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()),
|_| {},

View file

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

View file

@ -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<Uuid> =
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<BTreeSet<_>> = entry.get_ava_as_session_map(Attribute::UserAuthTokenSession.into())
let invalidate: Option<BTreeSet<_>> = 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<BTreeSet<_>> = entry.get_ava_as_session_map(Attribute::UserAuthTokenSession.into())
let expired: Option<BTreeSet<_>> = 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<BTreeSet<_>> = entry.get_ava_as_oauth2session_map("oauth2_session").map(|oauth2_sessions| {
let oauth2_remove: Option<BTreeSet<_>> = 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(_)));

View file

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

View file

@ -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!(

View file

@ -251,25 +251,25 @@ impl EntryChangelog {
EntryChangelog { anchors, changes }
}
pub fn add_ava_iter<T>(&mut self, cid: &Cid, attr: &str, viter: T)
where
T: IntoIterator<Item = Value>,
{
if !self.changes.contains_key(cid) {
self.changes.insert(cid.clone(), Change { s: Vec::new() });
}
// fn add_ava_iter<T>(&mut self, cid: &Cid, attr: Attribute, viter: T)
// where
// T: IntoIterator<Item = Value>,
// {
// 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<T>(&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) {

View file

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

View file

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

View file

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

View file

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

View file

@ -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<EntryInvalid, EntryNew> = 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()]))
);
*/

View file

@ -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<EntryInit, EntryNew>)
// 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");

View file

@ -115,7 +115,7 @@ fn protected_filter_entry(ident: &Identity, entry: &Arc<EntrySealedCommitted>) -
// 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");

View file

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

View file

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

View file

@ -20,21 +20,19 @@ impl AccessControlSearch {
qs: &mut QueryServerWriteTransaction,
value: &Entry<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
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<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
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<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
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<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
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<EntrySealed, EntryCommitted>,
) -> Result<Self, OperationError> {
// 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)
})?;

View file

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

View file

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

View file

@ -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(

View file

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

View file

@ -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<Uuid>> {
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)),
}
}

View file

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

View file

@ -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<String, OperationError> {
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<Vec<u8>, 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<HashSet<String>, 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::<HashSet<_>>(),
None => HashSet::default(),
})
@ -830,7 +830,7 @@ pub trait QueryServerTransaction<'a> {
fn get_authsession_expiry(&mut self) -> Result<u32, OperationError> {
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<u32, OperationError> {
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<Uuid, BTreeSet<String>> = 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.

View file

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

View file

@ -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]

View file

@ -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]

View file

@ -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<bool> {
pub async fn is_attr_writable(rsclient: &KanidmClient, id: &str, attr: Attribute) -> Option<bool> {
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")
}

View file

@ -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<String> = 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]

View file

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

View file

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

View file

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

View file

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

View file

@ -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) => {

View file

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

View file

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

View file

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

View file

@ -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 {

View file

@ -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<KanidmProvider>, 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