kanidmd_lib/idm/
group.rs

1use std::collections::BTreeSet;
2
3use kanidm_proto::internal::{Group as ProtoGroup, UiHint};
4use kanidm_proto::v1::UnixGroupToken;
5use uuid::Uuid;
6
7use crate::entry::{Committed, Entry, EntryCommitted, EntrySealed, GetUuid};
8use crate::prelude::*;
9use crate::value::PartialValue;
10
11use super::accountpolicy::{AccountPolicy, ResolvedAccountPolicy};
12
13// I hate that rust is forcing this to be public
14pub trait GroupType {}
15
16#[derive(Debug, Clone)]
17pub(crate) struct Unix {
18    name: String,
19    gidnumber: u32,
20}
21
22impl GroupType for Unix {}
23
24impl GroupType for () {}
25
26#[derive(Debug, Clone)]
27pub struct Group<T>
28where
29    T: GroupType,
30{
31    inner: T,
32    spn: String,
33    uuid: Uuid,
34    // We'll probably add policy and claims later to this
35    ui_hints: BTreeSet<UiHint>,
36}
37
38macro_rules! try_from_entry {
39    ($value:expr, $inner:expr) => {{
40        let spn = $value
41            .get_ava_single_proto_string(Attribute::Spn)
42            .ok_or_else(|| OperationError::MissingAttribute(Attribute::Spn))?;
43
44        let uuid = $value.get_uuid();
45
46        let ui_hints = $value
47            .get_ava_uihint(Attribute::GrantUiHint)
48            .cloned()
49            .unwrap_or_default();
50
51        Ok(Self {
52            inner: $inner,
53            spn,
54            uuid,
55            ui_hints,
56        })
57    }};
58}
59
60impl<T: GroupType> Group<T> {
61    pub fn spn(&self) -> &String {
62        &self.spn
63    }
64    pub fn uuid(&self) -> &Uuid {
65        &self.uuid
66    }
67    pub fn ui_hints(&self) -> &BTreeSet<UiHint> {
68        &self.ui_hints
69    }
70    pub fn to_proto(&self) -> ProtoGroup {
71        ProtoGroup {
72            spn: self.spn.clone(),
73            uuid: self.uuid.as_hyphenated().to_string(),
74        }
75    }
76}
77
78macro_rules! try_from_account {
79    ($value:expr, $qs:expr) => {{
80        let Some(iter) = $value.get_ava_as_refuuid(Attribute::MemberOf) else {
81            return Ok(vec![]);
82        };
83
84        // given a list of uuid, make a filter: even if this is empty, the be will
85        // just give and empty result set.
86        let f = filter!(f_or(
87            iter.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
88                .collect()
89        ));
90
91        let entries = $qs.internal_search(f).map_err(|e| {
92            admin_error!(?e, "internal search failed");
93            e
94        })?;
95
96        Ok(entries
97            .iter()
98            .map(|entry| Self::try_from_entry(&entry))
99            .filter_map(|v| v.ok())
100            .collect())
101    }};
102}
103
104impl Group<()> {
105    pub fn try_from_account<'a, TXN>(
106        value: &Entry<EntrySealed, EntryCommitted>,
107        qs: &mut TXN,
108    ) -> Result<Vec<Group<()>>, OperationError>
109    where
110        TXN: QueryServerTransaction<'a>,
111    {
112        if !value.attribute_equality(Attribute::Class, &EntryClass::Account.into()) {
113            return Err(OperationError::MissingClass(ENTRYCLASS_ACCOUNT.into()));
114        }
115
116        let user_group = try_from_entry!(value, ())?;
117        Ok(std::iter::once(user_group)
118            .chain(Self::try_from_account_reduced(value, qs)?)
119            .collect())
120    }
121
122    pub fn try_from_account_reduced<'a, E, TXN>(
123        value: &Entry<E, EntryCommitted>,
124        qs: &mut TXN,
125    ) -> Result<Vec<Group<()>>, OperationError>
126    where
127        E: Committed,
128        TXN: QueryServerTransaction<'a>,
129    {
130        try_from_account!(value, qs)
131    }
132
133    pub fn try_from_entry<E>(value: &Entry<E, EntryCommitted>) -> Result<Self, OperationError>
134    where
135        E: Committed,
136        Entry<E, EntryCommitted>: GetUuid,
137    {
138        if !value.attribute_equality(Attribute::Class, &EntryClass::Group.into()) {
139            return Err(OperationError::MissingAttribute(Attribute::Group));
140        }
141
142        try_from_entry!(value, ())
143    }
144}
145
146impl Group<Unix> {
147    pub fn try_from_account<'a, TXN>(
148        value: &Entry<EntrySealed, EntryCommitted>,
149        qs: &mut TXN,
150    ) -> Result<Vec<Group<Unix>>, OperationError>
151    where
152        TXN: QueryServerTransaction<'a>,
153    {
154        if !value.attribute_equality(Attribute::Class, &EntryClass::Account.into()) {
155            return Err(OperationError::MissingClass(ENTRYCLASS_ACCOUNT.into()));
156        }
157
158        if !value.attribute_equality(Attribute::Class, &EntryClass::PosixAccount.into()) {
159            return Err(OperationError::MissingClass(
160                ENTRYCLASS_POSIX_ACCOUNT.into(),
161            ));
162        }
163
164        let name = value
165            .get_ava_single_iname(Attribute::Name)
166            .map(|s| s.to_string())
167            .ok_or_else(|| OperationError::MissingAttribute(Attribute::Name))?;
168
169        let gidnumber = value
170            .get_ava_single_uint32(Attribute::GidNumber)
171            .ok_or_else(|| OperationError::MissingAttribute(Attribute::GidNumber))?;
172
173        let user_group = try_from_entry!(value, Unix { name, gidnumber })?;
174
175        Ok(std::iter::once(user_group)
176            .chain(Self::try_from_account_reduced(value, qs)?)
177            .collect())
178    }
179
180    pub fn try_from_account_reduced<'a, E, TXN>(
181        value: &Entry<E, EntryCommitted>,
182        qs: &mut TXN,
183    ) -> Result<Vec<Group<Unix>>, OperationError>
184    where
185        E: Committed,
186        TXN: QueryServerTransaction<'a>,
187    {
188        try_from_account!(value, qs)
189    }
190
191    fn check_entry_classes<E>(value: &Entry<E, EntryCommitted>) -> Result<(), OperationError>
192    where
193        E: Committed,
194        Entry<E, EntryCommitted>: GetUuid,
195    {
196        // If its an account, it must be a posix account
197        if value.attribute_equality(Attribute::Class, &EntryClass::Account.into()) {
198            if !value.attribute_equality(Attribute::Class, &EntryClass::PosixAccount.into()) {
199                return Err(OperationError::MissingClass(
200                    ENTRYCLASS_POSIX_ACCOUNT.into(),
201                ));
202            }
203        } else {
204            // Otherwise it must be both a group and a posix group
205            if !value.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.into()) {
206                return Err(OperationError::MissingClass(ENTRYCLASS_POSIX_GROUP.into()));
207            }
208
209            if !value.attribute_equality(Attribute::Class, &EntryClass::Group.into()) {
210                return Err(OperationError::MissingAttribute(Attribute::Group));
211            }
212        }
213        Ok(())
214    }
215
216    pub fn try_from_entry<E>(value: &Entry<E, EntryCommitted>) -> Result<Self, OperationError>
217    where
218        E: Committed,
219        Entry<E, EntryCommitted>: GetUuid,
220    {
221        Self::check_entry_classes(value)?;
222
223        let name = value
224            .get_ava_single_iname(Attribute::Name)
225            .map(|s| s.to_string())
226            .ok_or_else(|| OperationError::MissingAttribute(Attribute::Name))?;
227
228        let gidnumber = value
229            .get_ava_single_uint32(Attribute::GidNumber)
230            .ok_or_else(|| OperationError::MissingAttribute(Attribute::GidNumber))?;
231
232        try_from_entry!(value, Unix { name, gidnumber })
233    }
234
235    pub(crate) fn to_unixgrouptoken(&self) -> UnixGroupToken {
236        UnixGroupToken {
237            name: self.inner.name.clone(),
238            spn: self.spn.clone(),
239            uuid: self.uuid,
240            gidnumber: self.inner.gidnumber,
241        }
242    }
243}
244
245pub(crate) fn load_account_policy<'a, T>(
246    value: &Entry<EntrySealed, EntryCommitted>,
247    qs: &mut T,
248) -> Result<ResolvedAccountPolicy, OperationError>
249where
250    T: QueryServerTransaction<'a>,
251{
252    let iter = match value.get_ava_as_refuuid(Attribute::MemberOf) {
253        Some(v) => v,
254        None => Box::new(Vec::<Uuid>::new().into_iter()),
255    };
256
257    // given a list of uuid, make a filter: even if this is empty, the be will
258    // just give and empty result set.
259    let f = filter!(f_or(
260        iter.map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
261            .collect()
262    ));
263
264    let entries = qs.internal_search(f).map_err(|e| {
265        admin_error!(?e, "internal search failed");
266        e
267    })?;
268
269    Ok(ResolvedAccountPolicy::fold_from(entries.iter().filter_map(
270        |entry| {
271            let acc_pol: Option<AccountPolicy> = entry.as_ref().into();
272            acc_pol
273        },
274    )))
275}
276
277pub(crate) fn load_all_groups_from_account<'a, E, TXN>(
278    value: &Entry<E, EntryCommitted>,
279    qs: &mut TXN,
280) -> Result<(Vec<Group<()>>, Vec<Group<Unix>>), OperationError>
281where
282    E: Committed,
283    Entry<E, EntryCommitted>: GetUuid,
284    TXN: QueryServerTransaction<'a>,
285{
286    let Some(iter) = value.get_ava_as_refuuid(Attribute::MemberOf) else {
287        return Ok((vec![], vec![]));
288    };
289
290    let conditions: Vec<_> = iter
291        .map(|u| f_eq(Attribute::Uuid, PartialValue::Uuid(u)))
292        .collect();
293
294    let f = filter!(f_or(conditions));
295
296    let entries = qs.internal_search(f).map_err(|e| {
297        admin_error!(?e, "internal search failed");
298        e
299    })?;
300
301    let mut groups = vec![];
302    let mut unix_groups = Group::<Unix>::try_from_entry(value)
303        .ok()
304        .into_iter()
305        .collect::<Vec<_>>();
306
307    for entry in entries.iter() {
308        let entry = entry.as_ref();
309        if entry.attribute_equality(Attribute::Class, &EntryClass::PosixGroup.into()) {
310            unix_groups.push(Group::<Unix>::try_from_entry::<EntrySealed>(entry)?);
311        }
312
313        // No idea why we need to explicitly specify the type here
314        groups.push(Group::<()>::try_from_entry::<EntrySealed>(entry)?);
315    }
316
317    Ok((groups, unix_groups))
318}