mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 12:37:00 +01:00
Chore: Refactor Groups to be more generic (#3136)
This commit is contained in:
parent
d2ae2ca206
commit
dc56a3217d
|
@ -166,6 +166,15 @@ pub struct EntryReduced {
|
||||||
// Today is that day - @firstyear
|
// Today is that day - @firstyear
|
||||||
pub type Eattrs = Map<Attribute, ValueSet>;
|
pub type Eattrs = Map<Attribute, ValueSet>;
|
||||||
|
|
||||||
|
pub trait GetUuid {
|
||||||
|
fn get_uuid(&self) -> Uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait Committed {}
|
||||||
|
|
||||||
|
impl Committed for EntrySealed {}
|
||||||
|
impl Committed for EntryReduced {}
|
||||||
|
|
||||||
pub(crate) fn compare_attrs(left: &Eattrs, right: &Eattrs) -> bool {
|
pub(crate) fn compare_attrs(left: &Eattrs, right: &Eattrs) -> bool {
|
||||||
// We can't shortcut based on len because cid mod may not be present.
|
// We can't shortcut based on len because cid mod may not be present.
|
||||||
// Build the set of all keys between both.
|
// Build the set of all keys between both.
|
||||||
|
@ -260,7 +269,7 @@ where
|
||||||
STATE: Clone,
|
STATE: Clone,
|
||||||
{
|
{
|
||||||
/// Get the uuid of this entry.
|
/// Get the uuid of this entry.
|
||||||
pub(crate) fn get_uuid(&self) -> Option<Uuid> {
|
pub fn get_uuid(&self) -> Option<Uuid> {
|
||||||
self.attrs
|
self.attrs
|
||||||
.get(&Attribute::Uuid)
|
.get(&Attribute::Uuid)
|
||||||
.and_then(|vs| vs.to_uuid_single())
|
.and_then(|vs| vs.to_uuid_single())
|
||||||
|
@ -2127,6 +2136,15 @@ impl<STATE> Entry<EntryValid, STATE> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<STATE> GetUuid for Entry<EntrySealed, STATE>
|
||||||
|
where
|
||||||
|
STATE: Clone,
|
||||||
|
{
|
||||||
|
fn get_uuid(&self) -> Uuid {
|
||||||
|
self.valid.uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<STATE> Entry<EntrySealed, STATE>
|
impl<STATE> Entry<EntrySealed, STATE>
|
||||||
where
|
where
|
||||||
STATE: Clone,
|
STATE: Clone,
|
||||||
|
@ -2213,6 +2231,12 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl GetUuid for Entry<EntryReduced, EntryCommitted> {
|
||||||
|
fn get_uuid(&self) -> Uuid {
|
||||||
|
self.valid.uuid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Entry<EntryReduced, EntryCommitted> {
|
impl Entry<EntryReduced, EntryCommitted> {
|
||||||
pub fn get_uuid(&self) -> Uuid {
|
pub fn get_uuid(&self) -> Uuid {
|
||||||
self.valid.uuid
|
self.valid.uuid
|
||||||
|
|
|
@ -12,10 +12,7 @@ use webauthn_rs::prelude::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::accountpolicy::ResolvedAccountPolicy;
|
use super::accountpolicy::ResolvedAccountPolicy;
|
||||||
use super::group::{
|
use super::group::{load_account_policy, load_all_groups_from_account, Group, Unix};
|
||||||
load_all_groups_from_account_entry, load_all_groups_from_account_entry_reduced,
|
|
||||||
load_all_groups_from_account_entry_with_policy, Group, UnixGroup,
|
|
||||||
};
|
|
||||||
use crate::constants::UUID_ANONYMOUS;
|
use crate::constants::UUID_ANONYMOUS;
|
||||||
use crate::credential::softlock::CredSoftLockPolicy;
|
use crate::credential::softlock::CredSoftLockPolicy;
|
||||||
use crate::credential::{apppwd::ApplicationPassword, Credential};
|
use crate::credential::{apppwd::ApplicationPassword, Credential};
|
||||||
|
@ -36,7 +33,7 @@ pub struct UnixExtensions {
|
||||||
ucred: Option<Credential>,
|
ucred: Option<Credential>,
|
||||||
shell: Option<String>,
|
shell: Option<String>,
|
||||||
gidnumber: u32,
|
gidnumber: u32,
|
||||||
groups: Vec<UnixGroup>,
|
groups: Vec<Group<Unix>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UnixExtensions {
|
impl UnixExtensions {
|
||||||
|
@ -54,7 +51,7 @@ pub struct Account {
|
||||||
pub displayname: String,
|
pub displayname: String,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub sync_parent_uuid: Option<Uuid>,
|
pub sync_parent_uuid: Option<Uuid>,
|
||||||
pub groups: Vec<Group>,
|
pub groups: Vec<Group<()>>,
|
||||||
pub primary: Option<Credential>,
|
pub primary: Option<Credential>,
|
||||||
pub passkeys: BTreeMap<Uuid, (String, PasskeyV4)>,
|
pub passkeys: BTreeMap<Uuid, (String, PasskeyV4)>,
|
||||||
pub attested_passkeys: BTreeMap<Uuid, (String, AttestedPasskeyV4)>,
|
pub attested_passkeys: BTreeMap<Uuid, (String, AttestedPasskeyV4)>,
|
||||||
|
@ -138,7 +135,7 @@ macro_rules! try_from_entry {
|
||||||
// Provide hints from groups.
|
// Provide hints from groups.
|
||||||
let mut ui_hints: BTreeSet<_> = groups
|
let mut ui_hints: BTreeSet<_> = groups
|
||||||
.iter()
|
.iter()
|
||||||
.map(|group: &Group| group.ui_hints.iter())
|
.map(|group: &Group<()>| group.ui_hints().iter())
|
||||||
.flatten()
|
.flatten()
|
||||||
.copied()
|
.copied()
|
||||||
.collect();
|
.collect();
|
||||||
|
@ -235,7 +232,7 @@ impl Account {
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
qs: &mut QueryServerReadTransaction,
|
qs: &mut QueryServerReadTransaction,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
let (groups, unix_groups) = load_all_groups_from_account_entry(value, qs)?;
|
let (groups, unix_groups) = load_all_groups_from_account(value, qs)?;
|
||||||
|
|
||||||
try_from_entry!(value, groups, unix_groups)
|
try_from_entry!(value, groups, unix_groups)
|
||||||
}
|
}
|
||||||
|
@ -248,8 +245,8 @@ impl Account {
|
||||||
where
|
where
|
||||||
TXN: QueryServerTransaction<'a>,
|
TXN: QueryServerTransaction<'a>,
|
||||||
{
|
{
|
||||||
let ((groups, unix_groups), rap) =
|
let (groups, unix_groups) = load_all_groups_from_account(value, qs)?;
|
||||||
load_all_groups_from_account_entry_with_policy(value, qs)?;
|
let rap = load_account_policy(value, qs)?;
|
||||||
|
|
||||||
try_from_entry!(value, groups, unix_groups).map(|acct| (acct, rap))
|
try_from_entry!(value, groups, unix_groups).map(|acct| (acct, rap))
|
||||||
}
|
}
|
||||||
|
@ -259,7 +256,7 @@ impl Account {
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
qs: &mut QueryServerWriteTransaction,
|
qs: &mut QueryServerWriteTransaction,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
let (groups, unix_groups) = load_all_groups_from_account_entry(value, qs)?;
|
let (groups, unix_groups) = load_all_groups_from_account(value, qs)?;
|
||||||
|
|
||||||
try_from_entry!(value, groups, unix_groups)
|
try_from_entry!(value, groups, unix_groups)
|
||||||
}
|
}
|
||||||
|
@ -269,7 +266,7 @@ impl Account {
|
||||||
value: &Entry<EntryReduced, EntryCommitted>,
|
value: &Entry<EntryReduced, EntryCommitted>,
|
||||||
qs: &mut QueryServerReadTransaction,
|
qs: &mut QueryServerReadTransaction,
|
||||||
) -> Result<Self, OperationError> {
|
) -> Result<Self, OperationError> {
|
||||||
let (groups, unix_groups) = load_all_groups_from_account_entry_reduced(value, qs)?;
|
let (groups, unix_groups) = load_all_groups_from_account(value, qs)?;
|
||||||
try_from_entry!(value, groups, unix_groups)
|
try_from_entry!(value, groups, unix_groups)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,239 +1,72 @@
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::iter;
|
|
||||||
|
|
||||||
use kanidm_proto::internal::{Group as ProtoGroup, UiHint};
|
use kanidm_proto::internal::{Group as ProtoGroup, UiHint};
|
||||||
use kanidm_proto::v1::UnixGroupToken;
|
use kanidm_proto::v1::UnixGroupToken;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use super::accountpolicy::{AccountPolicy, ResolvedAccountPolicy};
|
use crate::entry::{Committed, Entry, EntryCommitted, EntrySealed, GetUuid};
|
||||||
use crate::entry::{Entry, EntryCommitted, EntryReduced, EntrySealed};
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::value::PartialValue;
|
use crate::value::PartialValue;
|
||||||
|
|
||||||
macro_rules! entry_groups {
|
use super::accountpolicy::{AccountPolicy, ResolvedAccountPolicy};
|
||||||
($value:expr, $qs:expr) => {{
|
|
||||||
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.
|
|
||||||
let f = filter!(f_or(
|
|
||||||
riter
|
|
||||||
.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
|
|
||||||
.collect()
|
|
||||||
));
|
|
||||||
$qs.internal_search(f).map_err(|e| {
|
|
||||||
admin_error!(?e, "internal search failed");
|
|
||||||
e
|
|
||||||
})?
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// No memberof, no groups!
|
|
||||||
vec![]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! load_all_groups_from_iter {
|
// I hate that rust is forcing this to be public
|
||||||
($value:expr, $group_iter:expr) => {{
|
pub trait GroupType {}
|
||||||
let mut groups: Vec<Group> = vec![];
|
|
||||||
let mut unix_groups: Vec<UnixGroup> = vec![];
|
|
||||||
|
|
||||||
let is_unix_account = $value.attribute_equality(
|
|
||||||
Attribute::Class,
|
|
||||||
&EntryClass::PosixAccount.to_partialvalue(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Setup the user private group
|
|
||||||
let spn = $value
|
|
||||||
.get_ava_single_proto_string(Attribute::Spn)
|
|
||||||
.ok_or(OperationError::MissingAttribute(Attribute::Spn))?;
|
|
||||||
|
|
||||||
let uuid = $value.get_uuid();
|
|
||||||
|
|
||||||
// We could allow ui hints on the user direct in the future?
|
|
||||||
let ui_hints = BTreeSet::default();
|
|
||||||
|
|
||||||
groups.push(Group {
|
|
||||||
spn: spn.clone(),
|
|
||||||
uuid: uuid.clone(),
|
|
||||||
ui_hints,
|
|
||||||
});
|
|
||||||
|
|
||||||
if is_unix_account {
|
|
||||||
let name = $value
|
|
||||||
.get_ava_single_proto_string(Attribute::Name)
|
|
||||||
.ok_or(OperationError::MissingAttribute(Attribute::Name))?;
|
|
||||||
|
|
||||||
let gidnumber = $value
|
|
||||||
.get_ava_single_uint32(Attribute::GidNumber)
|
|
||||||
.ok_or(OperationError::MissingAttribute(Attribute::GidNumber))?;
|
|
||||||
|
|
||||||
unix_groups.push(UnixGroup {
|
|
||||||
name,
|
|
||||||
spn,
|
|
||||||
gidnumber,
|
|
||||||
uuid,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for group_entry in $group_iter {
|
|
||||||
let group = Group::try_from_entry(group_entry.as_ref())?;
|
|
||||||
groups.push(group);
|
|
||||||
|
|
||||||
if is_unix_account
|
|
||||||
&& group_entry
|
|
||||||
.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.to_partialvalue())
|
|
||||||
{
|
|
||||||
let unix_group = UnixGroup::try_from_entry(group_entry.as_ref())?;
|
|
||||||
unix_groups.push(unix_group);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(groups, unix_groups)
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn load_all_groups_from_account_entry<'a, T>(
|
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
|
||||||
qs: &mut T,
|
|
||||||
) -> Result<(Vec<Group>, Vec<UnixGroup>), OperationError>
|
|
||||||
where
|
|
||||||
T: QueryServerTransaction<'a>,
|
|
||||||
{
|
|
||||||
let group_iter = entry_groups!(value, qs);
|
|
||||||
Ok(load_all_groups_from_iter!(value, group_iter))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn load_all_groups_from_account_entry_with_policy<'a, T>(
|
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
|
||||||
qs: &mut T,
|
|
||||||
) -> Result<((Vec<Group>, Vec<UnixGroup>), ResolvedAccountPolicy), OperationError>
|
|
||||||
where
|
|
||||||
T: QueryServerTransaction<'a>,
|
|
||||||
{
|
|
||||||
let group_iter = entry_groups!(value, qs);
|
|
||||||
|
|
||||||
let rap = ResolvedAccountPolicy::fold_from(group_iter.iter().filter_map(|entry| {
|
|
||||||
let acc_pol: Option<AccountPolicy> = entry.as_ref().into();
|
|
||||||
acc_pol
|
|
||||||
}));
|
|
||||||
|
|
||||||
Ok((load_all_groups_from_iter!(value, group_iter), rap))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn load_all_groups_from_account_entry_reduced<'a, T>(
|
|
||||||
value: &Entry<EntryReduced, EntryCommitted>,
|
|
||||||
qs: &mut T,
|
|
||||||
) -> Result<(Vec<Group>, Vec<UnixGroup>), OperationError>
|
|
||||||
where
|
|
||||||
T: QueryServerTransaction<'a>,
|
|
||||||
{
|
|
||||||
let group_iter = entry_groups!(value, qs);
|
|
||||||
Ok(load_all_groups_from_iter!(value, group_iter))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Group {
|
pub(crate) struct Unix {
|
||||||
|
name: String,
|
||||||
|
gidnumber: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl GroupType for Unix {}
|
||||||
|
|
||||||
|
impl GroupType for () {}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Group<T>
|
||||||
|
where
|
||||||
|
T: GroupType,
|
||||||
|
{
|
||||||
|
inner: T,
|
||||||
spn: String,
|
spn: String,
|
||||||
uuid: Uuid,
|
uuid: Uuid,
|
||||||
// We'll probably add policy and claims later to this
|
// We'll probably add policy and claims later to this
|
||||||
pub ui_hints: BTreeSet<UiHint>,
|
ui_hints: BTreeSet<UiHint>,
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! upg_from_account_e {
|
macro_rules! try_from_entry {
|
||||||
($value:expr, $groups:expr) => {{
|
($value:expr, $inner:expr) => {{
|
||||||
// Setup the user private group
|
|
||||||
let spn = $value
|
let spn = $value
|
||||||
.get_ava_single_proto_string(Attribute::Spn)
|
|
||||||
.ok_or(OperationError::MissingAttribute(Attribute::Spn))?;
|
|
||||||
|
|
||||||
let uuid = $value.get_uuid();
|
|
||||||
|
|
||||||
// We could allow ui hints on the user direct in the future?
|
|
||||||
let ui_hints = BTreeSet::default();
|
|
||||||
|
|
||||||
let upg = Group {
|
|
||||||
spn,
|
|
||||||
uuid,
|
|
||||||
ui_hints,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Now convert the group entries to groups.
|
|
||||||
let groups: Result<Vec<_>, _> = $groups
|
|
||||||
.iter()
|
|
||||||
.map(|e| Group::try_from_entry(e.as_ref()))
|
|
||||||
.chain(std::iter::once(Ok(upg)))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
groups.map_err(|e| {
|
|
||||||
error!(?e, "failed to transform group entries to groups");
|
|
||||||
e
|
|
||||||
})
|
|
||||||
}};
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Group {
|
|
||||||
pub(crate) fn uuid(&self) -> Uuid {
|
|
||||||
self.uuid
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_from_account_entry_reduced<'a, TXN>(
|
|
||||||
value: &Entry<EntryReduced, EntryCommitted>,
|
|
||||||
qs: &mut TXN,
|
|
||||||
) -> Result<Vec<Self>, OperationError>
|
|
||||||
where
|
|
||||||
TXN: QueryServerTransaction<'a>,
|
|
||||||
{
|
|
||||||
let groups = entry_groups!(value, qs);
|
|
||||||
upg_from_account_e!(value, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_from_account_entry<'a, TXN>(
|
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
|
||||||
qs: &mut TXN,
|
|
||||||
) -> Result<Vec<Self>, OperationError>
|
|
||||||
where
|
|
||||||
TXN: QueryServerTransaction<'a>,
|
|
||||||
{
|
|
||||||
let groups = entry_groups!(value, qs);
|
|
||||||
upg_from_account_e!(value, groups)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn try_from_entry(
|
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
|
||||||
) -> Result<Self, OperationError> {
|
|
||||||
if !value.attribute_equality(Attribute::Class, &EntryClass::Group.into()) {
|
|
||||||
return Err(OperationError::MissingAttribute(Attribute::Group));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Now extract our needed attributes
|
|
||||||
/*
|
|
||||||
let name = value
|
|
||||||
.get_ava_single_iname(Attribute::Name)
|
|
||||||
.map(|s| s.to_string())
|
|
||||||
.ok_or_else(|| {
|
|
||||||
OperationError::MissingAttribute(Attribute::Name)
|
|
||||||
})?;
|
|
||||||
*/
|
|
||||||
let spn = value
|
|
||||||
.get_ava_single_proto_string(Attribute::Spn)
|
.get_ava_single_proto_string(Attribute::Spn)
|
||||||
.ok_or_else(|| OperationError::MissingAttribute(Attribute::Spn))?;
|
.ok_or_else(|| OperationError::MissingAttribute(Attribute::Spn))?;
|
||||||
|
|
||||||
let uuid = value.get_uuid();
|
let uuid = $value.get_uuid();
|
||||||
|
|
||||||
let ui_hints = value
|
let ui_hints = $value
|
||||||
.get_ava_uihint(Attribute::GrantUiHint)
|
.get_ava_uihint(Attribute::GrantUiHint)
|
||||||
.cloned()
|
.cloned()
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
Ok(Group {
|
Ok(Self {
|
||||||
|
inner: $inner,
|
||||||
spn,
|
spn,
|
||||||
uuid,
|
uuid,
|
||||||
ui_hints,
|
ui_hints,
|
||||||
})
|
})
|
||||||
}
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: GroupType> Group<T> {
|
||||||
|
pub fn spn(&self) -> &String {
|
||||||
|
&self.spn
|
||||||
|
}
|
||||||
|
pub fn uuid(&self) -> &Uuid {
|
||||||
|
&self.uuid
|
||||||
|
}
|
||||||
|
pub fn ui_hints(&self) -> &BTreeSet<UiHint> {
|
||||||
|
&self.ui_hints
|
||||||
|
}
|
||||||
pub fn to_proto(&self) -> ProtoGroup {
|
pub fn to_proto(&self) -> ProtoGroup {
|
||||||
ProtoGroup {
|
ProtoGroup {
|
||||||
spn: self.spn.clone(),
|
spn: self.spn.clone(),
|
||||||
|
@ -242,160 +75,248 @@ impl Group {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
macro_rules! try_from_account {
|
||||||
pub(crate) struct UnixGroup {
|
($value:expr, $qs:expr) => {{
|
||||||
pub name: String,
|
let Some(iter) = $value.get_ava_as_refuuid(Attribute::MemberOf) else {
|
||||||
pub spn: String,
|
return Ok(vec![]);
|
||||||
pub gidnumber: u32,
|
};
|
||||||
pub uuid: Uuid,
|
|
||||||
}
|
|
||||||
|
|
||||||
macro_rules! try_from_group_e {
|
// given a list of uuid, make a filter: even if this is empty, the be will
|
||||||
($value:expr) => {{
|
// just give and empty result set.
|
||||||
// We could be looking at a user for their UPG, OR a true group.
|
let f = filter!(f_or(
|
||||||
|
iter.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
|
||||||
|
.collect()
|
||||||
|
));
|
||||||
|
|
||||||
if !(($value.attribute_equality(Attribute::Class, &EntryClass::Account.to_partialvalue())
|
let entries = $qs.internal_search(f).map_err(|e| {
|
||||||
&& $value.attribute_equality(
|
admin_error!(?e, "internal search failed");
|
||||||
Attribute::Class,
|
e
|
||||||
&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
|
Ok(entries
|
||||||
.get_ava_single_iname(Attribute::Name)
|
.iter()
|
||||||
.map(|s| s.to_string())
|
.map(|entry| Self::try_from_entry(&entry))
|
||||||
.ok_or_else(|| OperationError::MissingAttribute(Attribute::Name))?;
|
.filter_map(|v| v.ok())
|
||||||
|
.collect())
|
||||||
let spn = $value
|
|
||||||
.get_ava_single_proto_string(Attribute::Spn)
|
|
||||||
.ok_or_else(|| OperationError::MissingAttribute(Attribute::Spn))?;
|
|
||||||
|
|
||||||
let uuid = $value.get_uuid();
|
|
||||||
|
|
||||||
let gidnumber = $value
|
|
||||||
.get_ava_single_uint32(Attribute::GidNumber)
|
|
||||||
.ok_or_else(|| OperationError::MissingAttribute(Attribute::GidNumber))?;
|
|
||||||
|
|
||||||
Ok(UnixGroup {
|
|
||||||
name,
|
|
||||||
spn,
|
|
||||||
gidnumber,
|
|
||||||
uuid,
|
|
||||||
})
|
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! try_from_account_group_e {
|
impl Group<()> {
|
||||||
($value:expr, $qs:expr) => {{
|
pub fn try_from_account<'a, TXN>(
|
||||||
// First synthesise the self-group from the account.
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
// We have already checked these, but paranoia is better than
|
qs: &mut TXN,
|
||||||
// complacency.
|
) -> Result<Vec<Group<()>>, OperationError>
|
||||||
if !$value.attribute_equality(Attribute::Class, &EntryClass::Account.to_partialvalue()) {
|
where
|
||||||
|
TXN: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
if !value.attribute_equality(Attribute::Class, &EntryClass::Account.into()) {
|
||||||
return Err(OperationError::MissingClass(ENTRYCLASS_ACCOUNT.into()));
|
return Err(OperationError::MissingClass(ENTRYCLASS_ACCOUNT.into()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !$value.attribute_equality(
|
let user_group = try_from_entry!(value, ())?;
|
||||||
Attribute::Class,
|
Ok(std::iter::once(user_group)
|
||||||
&EntryClass::PosixAccount.to_partialvalue(),
|
.chain(Self::try_from_account_reduced(value, qs)?)
|
||||||
) {
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_from_account_reduced<'a, E, TXN>(
|
||||||
|
value: &Entry<E, EntryCommitted>,
|
||||||
|
qs: &mut TXN,
|
||||||
|
) -> Result<Vec<Group<()>>, OperationError>
|
||||||
|
where
|
||||||
|
E: Committed,
|
||||||
|
TXN: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
try_from_account!(value, qs)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_from_entry<E>(value: &Entry<E, EntryCommitted>) -> Result<Self, OperationError>
|
||||||
|
where
|
||||||
|
E: Committed,
|
||||||
|
Entry<E, EntryCommitted>: GetUuid,
|
||||||
|
{
|
||||||
|
if !value.attribute_equality(Attribute::Class, &EntryClass::Group.into()) {
|
||||||
|
return Err(OperationError::MissingAttribute(Attribute::Group));
|
||||||
|
}
|
||||||
|
|
||||||
|
try_from_entry!(value, ())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Group<Unix> {
|
||||||
|
pub fn try_from_account<'a, TXN>(
|
||||||
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
|
qs: &mut TXN,
|
||||||
|
) -> Result<Vec<Group<Unix>>, OperationError>
|
||||||
|
where
|
||||||
|
TXN: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
if !value.attribute_equality(Attribute::Class, &EntryClass::Account.into()) {
|
||||||
|
return Err(OperationError::MissingClass(ENTRYCLASS_ACCOUNT.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !value.attribute_equality(Attribute::Class, &EntryClass::PosixAccount.into()) {
|
||||||
return Err(OperationError::MissingClass(
|
return Err(OperationError::MissingClass(
|
||||||
ENTRYCLASS_POSIX_ACCOUNT.into(),
|
ENTRYCLASS_POSIX_ACCOUNT.into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let name = $value
|
let name = value
|
||||||
.get_ava_single_iname(Attribute::Name)
|
.get_ava_single_iname(Attribute::Name)
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.ok_or_else(|| OperationError::MissingAttribute(Attribute::Name))?;
|
.ok_or_else(|| OperationError::MissingAttribute(Attribute::Name))?;
|
||||||
|
|
||||||
let spn = $value
|
let gidnumber = value
|
||||||
.get_ava_single_proto_string(Attribute::Spn)
|
|
||||||
.ok_or_else(|| OperationError::MissingAttribute(Attribute::Spn))?;
|
|
||||||
|
|
||||||
let uuid = $value.get_uuid();
|
|
||||||
|
|
||||||
let gidnumber = $value
|
|
||||||
.get_ava_single_uint32(Attribute::GidNumber)
|
.get_ava_single_uint32(Attribute::GidNumber)
|
||||||
.ok_or_else(|| OperationError::MissingAttribute(Attribute::GidNumber))?;
|
.ok_or_else(|| OperationError::MissingAttribute(Attribute::GidNumber))?;
|
||||||
|
|
||||||
// This is the user private group.
|
let user_group = try_from_entry!(value, Unix { name, gidnumber })?;
|
||||||
let upg = UnixGroup {
|
|
||||||
name,
|
|
||||||
spn,
|
|
||||||
gidnumber,
|
|
||||||
uuid,
|
|
||||||
};
|
|
||||||
|
|
||||||
match $value.get_ava_as_refuuid(Attribute::MemberOf) {
|
Ok(std::iter::once(user_group)
|
||||||
Some(riter) => {
|
.chain(Self::try_from_account_reduced(value, qs)?)
|
||||||
let f = filter!(f_and!([
|
.collect())
|
||||||
f_eq(Attribute::Class, EntryClass::PosixGroup.into()),
|
}
|
||||||
f_eq(Attribute::Class, EntryClass::Group.into()),
|
|
||||||
f_or(
|
pub fn try_from_account_reduced<'a, E, TXN>(
|
||||||
riter
|
value: &Entry<E, EntryCommitted>,
|
||||||
.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
|
qs: &mut TXN,
|
||||||
.collect()
|
) -> Result<Vec<Group<Unix>>, OperationError>
|
||||||
)
|
where
|
||||||
]));
|
E: Committed,
|
||||||
let group_entries: Vec<_> = $qs.internal_search(f)?;
|
TXN: QueryServerTransaction<'a>,
|
||||||
let groups: Result<Vec<_>, _> = iter::once(Ok(upg))
|
{
|
||||||
.chain(
|
try_from_account!(value, qs)
|
||||||
group_entries
|
}
|
||||||
.iter()
|
|
||||||
.map(|e| UnixGroup::try_from_entry(e.as_ref())),
|
fn check_entry_classes<E>(value: &Entry<E, EntryCommitted>) -> Result<(), OperationError>
|
||||||
)
|
where
|
||||||
.collect();
|
E: Committed,
|
||||||
groups
|
Entry<E, EntryCommitted>: GetUuid,
|
||||||
|
{
|
||||||
|
// If its an account, it must be a posix account
|
||||||
|
if value.attribute_equality(Attribute::Class, &EntryClass::Account.into()) {
|
||||||
|
if !value.attribute_equality(Attribute::Class, &EntryClass::PosixAccount.into()) {
|
||||||
|
return Err(OperationError::MissingClass(
|
||||||
|
ENTRYCLASS_POSIX_ACCOUNT.into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
None => {
|
} else {
|
||||||
// No memberof, no groups!
|
// Otherwise it must be both a group and a posix group
|
||||||
Ok(vec![upg])
|
if !value.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.into()) {
|
||||||
|
return Err(OperationError::MissingClass(ENTRYCLASS_POSIX_GROUP.into()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !value.attribute_equality(Attribute::Class, &EntryClass::Group.into()) {
|
||||||
|
return Err(OperationError::MissingAttribute(Attribute::Group));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}};
|
Ok(())
|
||||||
}
|
|
||||||
|
|
||||||
impl UnixGroup {
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub(crate) fn try_from_account_entry_rw(
|
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
|
||||||
qs: &mut QueryServerWriteTransaction,
|
|
||||||
) -> Result<Vec<Self>, OperationError> {
|
|
||||||
try_from_account_group_e!(value, qs)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn try_from_entry_reduced(
|
pub fn try_from_entry<E>(value: &Entry<E, EntryCommitted>) -> Result<Self, OperationError>
|
||||||
value: &Entry<EntryReduced, EntryCommitted>,
|
where
|
||||||
) -> Result<Self, OperationError> {
|
E: Committed,
|
||||||
try_from_group_e!(value)
|
Entry<E, EntryCommitted>: GetUuid,
|
||||||
}
|
{
|
||||||
|
Self::check_entry_classes(value)?;
|
||||||
|
|
||||||
pub(crate) fn try_from_entry(
|
let name = value
|
||||||
value: &Entry<EntrySealed, EntryCommitted>,
|
.get_ava_single_iname(Attribute::Name)
|
||||||
) -> Result<Self, OperationError> {
|
.map(|s| s.to_string())
|
||||||
try_from_group_e!(value)
|
.ok_or_else(|| OperationError::MissingAttribute(Attribute::Name))?;
|
||||||
|
|
||||||
|
let gidnumber = value
|
||||||
|
.get_ava_single_uint32(Attribute::GidNumber)
|
||||||
|
.ok_or_else(|| OperationError::MissingAttribute(Attribute::GidNumber))?;
|
||||||
|
|
||||||
|
try_from_entry!(value, Unix { name, gidnumber })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn to_unixgrouptoken(&self) -> UnixGroupToken {
|
pub(crate) fn to_unixgrouptoken(&self) -> UnixGroupToken {
|
||||||
UnixGroupToken {
|
UnixGroupToken {
|
||||||
name: self.name.clone(),
|
name: self.inner.name.clone(),
|
||||||
spn: self.spn.clone(),
|
spn: self.spn.clone(),
|
||||||
uuid: self.uuid,
|
uuid: self.uuid,
|
||||||
gidnumber: self.gidnumber,
|
gidnumber: self.inner.gidnumber,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_account_policy<'a, T>(
|
||||||
|
value: &Entry<EntrySealed, EntryCommitted>,
|
||||||
|
qs: &mut T,
|
||||||
|
) -> Result<ResolvedAccountPolicy, OperationError>
|
||||||
|
where
|
||||||
|
T: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
let iter = match value.get_ava_as_refuuid(Attribute::MemberOf) {
|
||||||
|
Some(v) => v,
|
||||||
|
None => Box::new(Vec::<Uuid>::new().into_iter()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// given a list of uuid, make a filter: even if this is empty, the be will
|
||||||
|
// just give and empty result set.
|
||||||
|
let f = filter!(f_or(
|
||||||
|
iter.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
|
||||||
|
.collect()
|
||||||
|
));
|
||||||
|
|
||||||
|
let entries = qs.internal_search(f).map_err(|e| {
|
||||||
|
admin_error!(?e, "internal search failed");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(ResolvedAccountPolicy::fold_from(entries.iter().filter_map(
|
||||||
|
|entry| {
|
||||||
|
let acc_pol: Option<AccountPolicy> = entry.as_ref().into();
|
||||||
|
acc_pol
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_all_groups_from_account<'a, E, TXN>(
|
||||||
|
value: &Entry<E, EntryCommitted>,
|
||||||
|
qs: &mut TXN,
|
||||||
|
) -> Result<(Vec<Group<()>>, Vec<Group<Unix>>), OperationError>
|
||||||
|
where
|
||||||
|
E: Committed,
|
||||||
|
Entry<E, EntryCommitted>: GetUuid,
|
||||||
|
TXN: QueryServerTransaction<'a>,
|
||||||
|
{
|
||||||
|
let Some(iter) = value.get_ava_as_refuuid(Attribute::MemberOf) else {
|
||||||
|
return Ok((vec![], vec![]));
|
||||||
|
};
|
||||||
|
|
||||||
|
let conditions: Vec<_> = iter
|
||||||
|
.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
println!("{:?}", conditions);
|
||||||
|
|
||||||
|
let f = filter!(f_or(conditions));
|
||||||
|
|
||||||
|
let entries = qs.internal_search(f).map_err(|e| {
|
||||||
|
admin_error!(?e, "internal search failed");
|
||||||
|
e
|
||||||
|
})?;
|
||||||
|
|
||||||
|
println!("{}", entries.len());
|
||||||
|
|
||||||
|
let mut groups = vec![];
|
||||||
|
let mut unix_groups = Group::<Unix>::try_from_entry(value)
|
||||||
|
.ok()
|
||||||
|
.into_iter()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for entry in entries.iter() {
|
||||||
|
let entry = entry.as_ref();
|
||||||
|
if entry.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.into()) {
|
||||||
|
unix_groups.push(Group::<Unix>::try_from_entry::<EntrySealed>(entry)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No idea why we need to explicitly specify the type here
|
||||||
|
groups.push(Group::<()>::try_from_entry::<EntrySealed>(entry)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((groups, unix_groups))
|
||||||
|
}
|
||||||
|
|
|
@ -2635,7 +2635,7 @@ fn extra_claims_for_account(
|
||||||
// for each group
|
// for each group
|
||||||
for group_uuid in account.groups.iter().map(|g| g.uuid()) {
|
for group_uuid in account.groups.iter().map(|g| g.uuid()) {
|
||||||
// Does this group have any custom claims?
|
// Does this group have any custom claims?
|
||||||
if let Some(claim) = claim_map.get(&group_uuid) {
|
if let Some(claim) = claim_map.get(group_uuid) {
|
||||||
// If so, iterate over the set of claims and values.
|
// If so, iterate over the set of claims and values.
|
||||||
for (claim_name, claim_value) in claim.iter() {
|
for (claim_name, claim_value) in claim.iter() {
|
||||||
// Does this claim name already exist in our in-progress map?
|
// Does this claim name already exist in our in-progress map?
|
||||||
|
|
|
@ -13,7 +13,7 @@ pub(crate) struct RadiusAccount {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub displayname: String,
|
pub displayname: String,
|
||||||
pub uuid: Uuid,
|
pub uuid: Uuid,
|
||||||
pub groups: Vec<Group>,
|
pub groups: Vec<Group<()>>,
|
||||||
pub radius_secret: String,
|
pub radius_secret: String,
|
||||||
pub valid_from: Option<OffsetDateTime>,
|
pub valid_from: Option<OffsetDateTime>,
|
||||||
pub expire: Option<OffsetDateTime>,
|
pub expire: Option<OffsetDateTime>,
|
||||||
|
@ -45,7 +45,7 @@ impl RadiusAccount {
|
||||||
.map(|s| s.to_string())
|
.map(|s| s.to_string())
|
||||||
.ok_or_else(|| OperationError::MissingAttribute(Attribute::DisplayName))?;
|
.ok_or_else(|| OperationError::MissingAttribute(Attribute::DisplayName))?;
|
||||||
|
|
||||||
let groups = Group::try_from_account_entry_reduced(value, qs)?;
|
let groups = Group::<()>::try_from_account_reduced(value, qs)?;
|
||||||
|
|
||||||
let valid_from = value.get_ava_single_datetime(Attribute::AccountValidFrom);
|
let valid_from = value.get_ava_single_datetime(Attribute::AccountValidFrom);
|
||||||
|
|
||||||
|
|
|
@ -46,7 +46,7 @@ use crate::idm::event::{
|
||||||
RegenerateRadiusSecretEvent, UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent,
|
RegenerateRadiusSecretEvent, UnixGroupTokenEvent, UnixPasswordChangeEvent, UnixUserAuthEvent,
|
||||||
UnixUserTokenEvent,
|
UnixUserTokenEvent,
|
||||||
};
|
};
|
||||||
use crate::idm::group::UnixGroup;
|
use crate::idm::group::{Group, Unix};
|
||||||
use crate::idm::oauth2::{
|
use crate::idm::oauth2::{
|
||||||
Oauth2ResourceServers, Oauth2ResourceServersReadTransaction,
|
Oauth2ResourceServers, Oauth2ResourceServersReadTransaction,
|
||||||
Oauth2ResourceServersWriteTransaction,
|
Oauth2ResourceServersWriteTransaction,
|
||||||
|
@ -1575,7 +1575,7 @@ impl<'a> IdmServerProxyReadTransaction<'a> {
|
||||||
let group = self
|
let group = self
|
||||||
.qs_read
|
.qs_read
|
||||||
.impersonate_search_ext_uuid(uute.target, &uute.ident)
|
.impersonate_search_ext_uuid(uute.target, &uute.ident)
|
||||||
.and_then(|e| UnixGroup::try_from_entry_reduced(&e))
|
.and_then(|e| Group::<Unix>::try_from_entry(&e))
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
admin_error!("Failed to start unix group token {:?}", e);
|
admin_error!("Failed to start unix group token {:?}", e);
|
||||||
e
|
e
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use kanidm_client::{ClientError, KanidmClient};
|
use kanidm_client::{ClientError, KanidmClient};
|
||||||
use kanidm_proto::constants::ATTR_DESCRIPTION;
|
use kanidm_proto::constants::ATTR_DESCRIPTION;
|
||||||
|
use kanidmd_lib::idm::group::Group;
|
||||||
use kanidmd_testkit::{create_user, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
use kanidmd_testkit::{create_user, ADMIN_TEST_PASSWORD, ADMIN_TEST_USER};
|
||||||
use serde_json::Value;
|
use serde_json::Value;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue