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
13pub 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 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 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 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 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 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 groups.push(Group::<()>::try_from_entry::<EntrySealed>(entry)?);
315 }
316
317 Ok((groups, unix_groups))
318}