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 name: Option<String>,
34 uuid: Uuid,
35 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 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 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 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 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 groups.push(Group::<()>::try_from_entry::<EntrySealed>(entry)?);
328 }
329
330 Ok((groups, unix_groups))
331}