kanidmd_lib/server/access/
profiles.rs

1use crate::prelude::*;
2use std::collections::BTreeSet;
3
4use crate::filter::{Filter, FilterValid, FilterValidResolved};
5
6use kanidm_proto::internal::Filter as ProtoFilter;
7
8// =========================================================================
9// PARSE ENTRY TO ACP, AND ACP MANAGEMENT
10// =========================================================================
11
12#[derive(Debug, Clone)]
13pub struct AccessControlSearchResolved<'a> {
14    pub acp: &'a AccessControlSearch,
15    pub receiver_condition: AccessControlReceiverCondition,
16    pub target_condition: AccessControlTargetCondition,
17}
18
19#[derive(Debug, Clone)]
20pub struct AccessControlSearch {
21    pub acp: AccessControlProfile,
22    pub attrs: BTreeSet<Attribute>,
23}
24
25impl AccessControlSearch {
26    pub fn try_from(
27        qs: &mut QueryServerWriteTransaction,
28        value: &Entry<EntrySealed, EntryCommitted>,
29    ) -> Result<Self, OperationError> {
30        if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlSearch.into()) {
31            admin_error!("class {} not present.", EntryClass::AccessControlSearch);
32            return Err(OperationError::InvalidAcpState(format!(
33                "Missing {}",
34                EntryClass::AccessControlSearch
35            )));
36        }
37
38        let mut attrs: BTreeSet<_> = value
39            .get_ava_iter_iutf8(Attribute::AcpSearchAttr)
40            .ok_or_else(|| {
41                admin_error!("Missing {}", Attribute::AcpSearchAttr);
42                OperationError::InvalidAcpState(format!("Missing {}", Attribute::AcpSearchAttr))
43            })?
44            .map(Attribute::from)
45            .collect();
46
47        // Ability to search memberof, implies the ability to read directmemberof
48        if attrs.contains(&Attribute::MemberOf) {
49            attrs.insert(Attribute::DirectMemberOf);
50        }
51
52        let acp = AccessControlProfile::try_from(qs, value)?;
53
54        Ok(AccessControlSearch { acp, attrs })
55    }
56
57    /// ⚠️  - Manually create a search access profile from values.
58    /// This is a TEST ONLY method and will never be exposed in production.
59    #[cfg(test)]
60    pub(super) fn from_raw(
61        name: &str,
62        uuid: Uuid,
63        receiver: Uuid,
64        targetscope: Filter<FilterValid>,
65        attrs: &str,
66    ) -> Self {
67        let mut attrs: BTreeSet<_> = attrs.split_whitespace().map(Attribute::from).collect();
68
69        // Ability to search memberof, implies the ability to read directmemberof
70        if attrs.contains(&Attribute::MemberOf) {
71            attrs.insert(Attribute::DirectMemberOf);
72        }
73
74        AccessControlSearch {
75            acp: AccessControlProfile {
76                name: name.to_string(),
77                uuid,
78                receiver: AccessControlReceiver::Group(btreeset!(receiver)),
79                target: AccessControlTarget::Scope(targetscope),
80            },
81            attrs,
82        }
83    }
84
85    /// ⚠️  - Manually create a search access profile from values.
86    /// This is a TEST ONLY method and will never be exposed in production.
87    #[cfg(test)]
88    pub(super) fn from_managed_by(
89        name: &str,
90        uuid: Uuid,
91        target: AccessControlTarget,
92        attrs: &str,
93    ) -> Self {
94        AccessControlSearch {
95            acp: AccessControlProfile {
96                name: name.to_string(),
97                uuid,
98                receiver: AccessControlReceiver::EntryManager,
99                target,
100            },
101            attrs: attrs.split_whitespace().map(Attribute::from).collect(),
102        }
103    }
104}
105
106#[derive(Debug, Clone)]
107pub struct AccessControlDeleteResolved<'a> {
108    pub acp: &'a AccessControlDelete,
109    pub receiver_condition: AccessControlReceiverCondition,
110    pub target_condition: AccessControlTargetCondition,
111}
112
113#[derive(Debug, Clone)]
114pub struct AccessControlDelete {
115    pub acp: AccessControlProfile,
116}
117
118impl AccessControlDelete {
119    pub fn try_from(
120        qs: &mut QueryServerWriteTransaction,
121        value: &Entry<EntrySealed, EntryCommitted>,
122    ) -> Result<Self, OperationError> {
123        if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlDelete.into()) {
124            admin_error!("class access_control_delete not present.");
125            return Err(OperationError::InvalidAcpState(
126                "Missing access_control_delete".to_string(),
127            ));
128        }
129
130        Ok(AccessControlDelete {
131            acp: AccessControlProfile::try_from(qs, value)?,
132        })
133    }
134
135    /// ⚠️  - Manually create a delete access profile from values.
136    /// This is a TEST ONLY method and will never be exposed in production.
137    #[cfg(test)]
138    pub(super) fn from_raw(
139        name: &str,
140        uuid: Uuid,
141        receiver: Uuid,
142        targetscope: Filter<FilterValid>,
143    ) -> Self {
144        AccessControlDelete {
145            acp: AccessControlProfile {
146                name: name.to_string(),
147                uuid,
148                receiver: AccessControlReceiver::Group(btreeset!(receiver)),
149                target: AccessControlTarget::Scope(targetscope),
150            },
151        }
152    }
153
154    /// ⚠️  - Manually create a delete access profile from values.
155    /// This is a TEST ONLY method and will never be exposed in production.
156    #[cfg(test)]
157    pub(super) fn from_managed_by(name: &str, uuid: Uuid, target: AccessControlTarget) -> Self {
158        AccessControlDelete {
159            acp: AccessControlProfile {
160                name: name.to_string(),
161                uuid,
162                receiver: AccessControlReceiver::EntryManager,
163                target,
164            },
165        }
166    }
167}
168
169#[derive(Debug, Clone)]
170pub struct AccessControlCreateResolved<'a> {
171    pub acp: &'a AccessControlCreate,
172    pub receiver_condition: AccessControlReceiverCondition,
173    pub target_condition: AccessControlTargetCondition,
174}
175
176#[derive(Debug, Clone)]
177pub struct AccessControlCreate {
178    pub acp: AccessControlProfile,
179    pub classes: Vec<AttrString>,
180    pub attrs: Vec<Attribute>,
181}
182
183impl AccessControlCreate {
184    pub fn try_from(
185        qs: &mut QueryServerWriteTransaction,
186        value: &Entry<EntrySealed, EntryCommitted>,
187    ) -> Result<Self, OperationError> {
188        if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlCreate.into()) {
189            admin_error!("class {} not present.", EntryClass::AccessControlCreate);
190            return Err(OperationError::InvalidAcpState(format!(
191                "Missing {}",
192                EntryClass::AccessControlCreate
193            )));
194        }
195
196        let attrs = value
197            .get_ava_iter_iutf8(Attribute::AcpCreateAttr)
198            .map(|i| i.map(Attribute::from).collect())
199            .unwrap_or_default();
200
201        let classes = value
202            .get_ava_iter_iutf8(Attribute::AcpCreateClass)
203            .map(|i| i.map(AttrString::from).collect())
204            .unwrap_or_default();
205
206        Ok(AccessControlCreate {
207            acp: AccessControlProfile::try_from(qs, value)?,
208            classes,
209            attrs,
210        })
211    }
212
213    /// ⚠️  - Manually create a create access profile from values.
214    /// This is a TEST ONLY method and will never be exposed in production.
215    #[cfg(test)]
216    pub(super) fn from_raw(
217        name: &str,
218        uuid: Uuid,
219        receiver: Uuid,
220        targetscope: Filter<FilterValid>,
221        classes: &str,
222        attrs: &str,
223    ) -> Self {
224        AccessControlCreate {
225            acp: AccessControlProfile {
226                name: name.to_string(),
227                uuid,
228                receiver: AccessControlReceiver::Group(btreeset!(receiver)),
229                target: AccessControlTarget::Scope(targetscope),
230            },
231            classes: classes.split_whitespace().map(AttrString::from).collect(),
232            attrs: attrs.split_whitespace().map(Attribute::from).collect(),
233        }
234    }
235
236    /// ⚠️  - Manually create a create access profile from values.
237    /// This is a TEST ONLY method and will never be exposed in production.
238    #[cfg(test)]
239    pub(super) fn from_managed_by(
240        name: &str,
241        uuid: Uuid,
242        target: AccessControlTarget,
243        classes: &str,
244        attrs: &str,
245    ) -> Self {
246        AccessControlCreate {
247            acp: AccessControlProfile {
248                name: name.to_string(),
249                uuid,
250                receiver: AccessControlReceiver::EntryManager,
251                target,
252            },
253            classes: classes.split_whitespace().map(AttrString::from).collect(),
254            attrs: attrs.split_whitespace().map(Attribute::from).collect(),
255        }
256    }
257}
258
259#[derive(Debug, Clone)]
260pub struct AccessControlModifyResolved<'a> {
261    pub acp: &'a AccessControlModify,
262    pub receiver_condition: AccessControlReceiverCondition,
263    pub target_condition: AccessControlTargetCondition,
264}
265
266#[derive(Debug, Clone)]
267pub struct AccessControlModify {
268    pub acp: AccessControlProfile,
269    pub presattrs: Vec<Attribute>,
270    pub remattrs: Vec<Attribute>,
271    pub pres_classes: Vec<AttrString>,
272    pub rem_classes: Vec<AttrString>,
273}
274
275impl AccessControlModify {
276    pub fn try_from(
277        qs: &mut QueryServerWriteTransaction,
278        value: &Entry<EntrySealed, EntryCommitted>,
279    ) -> Result<Self, OperationError> {
280        if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlModify.into()) {
281            admin_error!("class access_control_modify not present.");
282            return Err(OperationError::InvalidAcpState(
283                "Missing access_control_modify".to_string(),
284            ));
285        }
286
287        let presattrs = value
288            .get_ava_iter_iutf8(Attribute::AcpModifyPresentAttr)
289            .map(|i| i.map(Attribute::from).collect())
290            .unwrap_or_default();
291
292        let remattrs = value
293            .get_ava_iter_iutf8(Attribute::AcpModifyRemovedAttr)
294            .map(|i| i.map(Attribute::from).collect())
295            .unwrap_or_default();
296
297        let classes: Vec<AttrString> = value
298            .get_ava_iter_iutf8(Attribute::AcpModifyClass)
299            .map(|i| i.map(AttrString::from).collect())
300            .unwrap_or_default();
301
302        let pres_classes = value
303            .get_ava_iter_iutf8(Attribute::AcpModifyPresentClass)
304            .map(|i| i.map(AttrString::from).collect())
305            .unwrap_or_else(|| classes.clone());
306
307        let rem_classes = value
308            .get_ava_iter_iutf8(Attribute::AcpModifyRemoveClass)
309            .map(|i| i.map(AttrString::from).collect())
310            .unwrap_or_else(|| classes);
311
312        Ok(AccessControlModify {
313            acp: AccessControlProfile::try_from(qs, value)?,
314            pres_classes,
315            rem_classes,
316            presattrs,
317            remattrs,
318        })
319    }
320
321    /// ⚠️  - Manually create a modify access profile from values.
322    /// This is a TEST ONLY method and will never be exposed in production.
323    #[cfg(test)]
324    pub(super) fn from_raw(
325        name: &str,
326        uuid: Uuid,
327        receiver: Uuid,
328        targetscope: Filter<FilterValid>,
329        presattrs: &str,
330        remattrs: &str,
331        pres_classes: &str,
332        rem_classes: &str,
333    ) -> Self {
334        AccessControlModify {
335            acp: AccessControlProfile {
336                name: name.to_string(),
337                uuid,
338                receiver: AccessControlReceiver::Group(btreeset!(receiver)),
339                target: AccessControlTarget::Scope(targetscope),
340            },
341            pres_classes: pres_classes
342                .split_whitespace()
343                .map(AttrString::from)
344                .collect(),
345            rem_classes: rem_classes
346                .split_whitespace()
347                .map(AttrString::from)
348                .collect(),
349            presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
350            remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
351        }
352    }
353
354    /// ⚠️  - Manually create a modify access profile from values.
355    /// This is a TEST ONLY method and will never be exposed in production.
356    #[cfg(test)]
357    pub(super) fn from_managed_by(
358        name: &str,
359        uuid: Uuid,
360        target: AccessControlTarget,
361        presattrs: &str,
362        remattrs: &str,
363        pres_classes: &str,
364        rem_classes: &str,
365    ) -> Self {
366        AccessControlModify {
367            acp: AccessControlProfile {
368                name: name.to_string(),
369                uuid,
370                receiver: AccessControlReceiver::EntryManager,
371                target,
372            },
373            pres_classes: pres_classes
374                .split_whitespace()
375                .map(AttrString::from)
376                .collect(),
377            rem_classes: rem_classes
378                .split_whitespace()
379                .map(AttrString::from)
380                .collect(),
381            presattrs: presattrs.split_whitespace().map(Attribute::from).collect(),
382            remattrs: remattrs.split_whitespace().map(Attribute::from).collect(),
383        }
384    }
385}
386
387#[derive(Debug, Clone)]
388pub enum AccessControlReceiver {
389    None,
390    Group(BTreeSet<Uuid>),
391    EntryManager,
392}
393
394#[derive(Debug, Clone)]
395pub enum AccessControlReceiverCondition {
396    // None,
397    GroupChecked,
398    EntryManager,
399}
400
401/*
402impl AccessControlReceiverCondition {
403    pub(crate) fn is_none(&self) {
404        matches!(self, AccessControlReceiverCondition::None)
405    }
406}
407*/
408
409#[derive(Debug, Clone)]
410pub enum AccessControlTarget {
411    None,
412    Scope(Filter<FilterValid>),
413}
414
415#[derive(Debug, Clone)]
416pub enum AccessControlTargetCondition {
417    // None,
418    Scope(Filter<FilterValidResolved>),
419}
420
421/*
422impl AccessControlTargetCondition {
423    pub(crate) fn is_none(&self) {
424        matches!(&self, AccessControlTargetCondition::None)
425    }
426}
427*/
428
429#[derive(Debug, Clone)]
430pub struct AccessControlProfile {
431    pub name: String,
432    // Currently we retrieve this but don't use it. We could depending on how we change
433    // the acp update routine.
434    #[allow(dead_code)]
435    uuid: Uuid,
436    pub receiver: AccessControlReceiver,
437    pub target: AccessControlTarget,
438}
439
440impl AccessControlProfile {
441    pub(super) fn try_from(
442        qs: &mut QueryServerWriteTransaction,
443        value: &Entry<EntrySealed, EntryCommitted>,
444    ) -> Result<Self, OperationError> {
445        // Assert we have class access_control_profile
446        if !value.attribute_equality(Attribute::Class, &EntryClass::AccessControlProfile.into()) {
447            error!("class access_control_profile not present.");
448            return Err(OperationError::InvalidAcpState(
449                "Missing access_control_profile".to_string(),
450            ));
451        }
452
453        // copy name
454        let name = value
455            .get_ava_single_iname(Attribute::Name)
456            .ok_or_else(|| {
457                error!("Missing {}", Attribute::Name);
458                OperationError::InvalidAcpState(format!("Missing {}", Attribute::Name))
459            })?
460            .to_string();
461        // copy uuid
462        let uuid = value.get_uuid();
463
464        let receiver = if value.attribute_equality(
465            Attribute::Class,
466            &EntryClass::AccessControlReceiverGroup.into(),
467        ) {
468            value
469                .get_ava_refer(Attribute::AcpReceiverGroup)
470                .cloned()
471                .map(AccessControlReceiver::Group)
472                .ok_or_else(|| {
473                    admin_error!("Missing {}", Attribute::AcpReceiverGroup);
474                    OperationError::InvalidAcpState(format!(
475                        "Missing {}",
476                        Attribute::AcpReceiverGroup
477                    ))
478                })?
479        } else if value.attribute_equality(
480            Attribute::Class,
481            &EntryClass::AccessControlReceiverEntryManager.into(),
482        ) {
483            AccessControlReceiver::EntryManager
484        } else {
485            warn!(
486                ?name,
487                "access control has no defined receivers - this will do nothing!"
488            );
489            AccessControlReceiver::None
490        };
491
492        let target = if value.attribute_equality(
493            Attribute::Class,
494            &EntryClass::AccessControlTargetScope.into(),
495        ) {
496            // targetscope, and turn to real filter
497            let targetscope_f: ProtoFilter = value
498                .get_ava_single_protofilter(Attribute::AcpTargetScope)
499                .cloned()
500                .ok_or_else(|| {
501                    admin_error!("Missing {}", Attribute::AcpTargetScope);
502                    OperationError::InvalidAcpState(format!(
503                        "Missing {}",
504                        Attribute::AcpTargetScope
505                    ))
506                })?;
507
508            let ident = Identity::from_internal();
509
510            let targetscope_i = Filter::from_rw(&ident, &targetscope_f, qs).map_err(|e| {
511                admin_error!("{} validation failed {:?}", Attribute::AcpTargetScope, e);
512                e
513            })?;
514
515            targetscope_i
516                .validate(qs.get_schema())
517                .map_err(|e| {
518                    admin_error!("{} Schema Violation {:?}", Attribute::AcpTargetScope, e);
519                    OperationError::SchemaViolation(e)
520                })
521                .map(AccessControlTarget::Scope)?
522        } else {
523            warn!(
524                ?name,
525                "access control has no defined targets - this will do nothing!"
526            );
527            AccessControlTarget::None
528        };
529
530        Ok(AccessControlProfile {
531            name,
532            uuid,
533            receiver,
534            target,
535        })
536    }
537}