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