1use acls_rs::algebra::{BoundedJoinSemilattice, BoundedMeetSemilattice};
4use acls_rs::permission::AtomicPermission;
5use acls_rs::prelude::*;
6
7use std::cmp::Ordering;
8use std::fmt;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
18#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
19#[non_exhaustive]
20pub enum OperationType {
21 Bind,
23 Search,
25 Compare,
27 Read,
29 Add,
31 Delete,
33 Modify,
35 ModifyDn,
37 All,
39 SelfWrite,
41}
42
43impl OperationType {
44 pub fn as_str(&self) -> &'static str {
45 match self {
46 Self::Bind => "bind",
47 Self::Search => "search",
48 Self::Compare => "compare",
49 Self::Read => "read",
50 Self::Add => "add",
51 Self::Delete => "delete",
52 Self::Modify => "modify",
53 Self::ModifyDn => "modifydn",
54 Self::All => "all",
55 Self::SelfWrite => "selfwrite",
56 }
57 }
58
59 pub fn grants(&self, other: &OperationType) -> bool {
62 *self == OperationType::All
63 || *self == *other
64 || (*self == OperationType::SelfWrite && *other == OperationType::Modify)
65 }
66
67 pub fn is_write(&self) -> bool {
70 matches!(
71 self,
72 Self::Modify | Self::Add | Self::Delete | Self::ModifyDn | Self::All | Self::SelfWrite
73 )
74 }
75
76 pub fn is_read(&self) -> bool {
79 matches!(self, Self::Read | Self::Search | Self::Compare | Self::All)
80 }
81
82 fn bit(self) -> u16 {
83 match self {
84 Self::Bind => 1 << 0,
85 Self::Search => 1 << 1,
86 Self::Compare => 1 << 2,
87 Self::Read => 1 << 3,
88 Self::Add => 1 << 4,
89 Self::Delete => 1 << 5,
90 Self::Modify => 1 << 6,
91 Self::ModifyDn => 1 << 7,
92 Self::SelfWrite => 1 << 8,
93 Self::All => OperationSet::ALL_CONCRETE,
94 }
95 }
96
97 pub fn bit_index(self) -> Option<usize> {
100 match self {
101 Self::Bind => Some(0),
102 Self::Search => Some(1),
103 Self::Compare => Some(2),
104 Self::Read => Some(3),
105 Self::Add => Some(4),
106 Self::Delete => Some(5),
107 Self::Modify => Some(6),
108 Self::ModifyDn => Some(7),
109 Self::SelfWrite => Some(8),
110 Self::All => None,
111 }
112 }
113}
114
115impl PartialOrd for OperationType {
116 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
117 if self == other {
118 return Some(Ordering::Equal);
119 }
120 if self.grants(other) {
121 return Some(Ordering::Greater);
122 }
123 if other.grants(self) {
124 return Some(Ordering::Less);
125 }
126 None
127 }
128}
129
130pub trait PermissionSlice {
132 fn grants(&self, op: &OperationType) -> bool;
134 fn grants_write(&self) -> bool;
136 fn grants_read(&self) -> bool;
138}
139
140impl PermissionSlice for [OperationType] {
141 fn grants(&self, op: &OperationType) -> bool {
142 self.iter().any(|p| p.grants(op))
143 }
144
145 fn grants_write(&self) -> bool {
146 self.iter().any(|p| p.is_write())
147 }
148
149 fn grants_read(&self) -> bool {
150 self.iter().any(|p| p.is_read())
151 }
152}
153
154#[derive(Debug, Clone, PartialEq, Eq, Hash)]
156pub struct LdapPermission {
157 operation: OperationType,
158 target: String,
159}
160
161impl LdapPermission {
162 pub fn new(operation: OperationType, target: impl Into<String>) -> Self {
163 Self {
164 operation,
165 target: target.into(),
166 }
167 }
168
169 pub fn read(dn: impl Into<String>) -> Self {
171 Self::new(OperationType::Read, dn)
172 }
173
174 pub fn write(dn: impl Into<String>) -> Self {
176 Self::new(OperationType::Modify, dn)
177 }
178
179 pub fn search(base_dn: impl Into<String>) -> Self {
181 Self::new(OperationType::Search, base_dn)
182 }
183
184 pub fn add(parent_dn: impl Into<String>) -> Self {
186 Self::new(OperationType::Add, parent_dn)
187 }
188
189 pub fn delete(dn: impl Into<String>) -> Self {
191 Self::new(OperationType::Delete, dn)
192 }
193
194 pub fn to_atomic(&self) -> AtomicPermission {
196 AtomicPermission::new(&self.target, self.operation.as_str())
197 }
198}
199
200#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct LdapOperation {
203 pub operation_type: OperationType,
204 pub target_dn: String,
205 pub attributes: Vec<String>,
206}
207
208impl LdapOperation {
209 pub fn new(op: OperationType, dn: impl Into<String>) -> Self {
210 Self {
211 operation_type: op,
212 target_dn: dn.into(),
213 attributes: Vec::new(),
214 }
215 }
216
217 pub fn with_attributes(mut self, attrs: Vec<String>) -> Self {
218 self.attributes = attrs.into_iter().map(|a| a.to_lowercase()).collect();
219 self
220 }
221
222 pub fn with_target(&self, target_dn: impl Into<String>) -> Self {
224 Self {
225 operation_type: self.operation_type,
226 target_dn: target_dn.into(),
227 attributes: self.attributes.clone(),
228 }
229 }
230
231 pub fn to_permission(&self) -> LdapPermission {
233 LdapPermission::new(self.operation_type, &self.target_dn)
234 }
235}
236
237#[derive(Clone, Copy, PartialEq, Eq, Hash)]
246#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
247pub struct OperationSet(u16);
248
249pub const NUM_CONCRETE_OPS: usize = 9;
250
251impl OperationSet {
252 const ALL_CONCRETE: u16 = (1 << NUM_CONCRETE_OPS) - 1;
253
254 pub(crate) fn raw_bits(self) -> u16 {
255 self.0
256 }
257
258 pub fn empty() -> Self {
259 Self(0)
260 }
261
262 pub fn all() -> Self {
263 Self(Self::ALL_CONCRETE)
264 }
265
266 pub fn contains(self, op: OperationType) -> bool {
267 if self.0 & op.bit() == op.bit() {
268 return true;
269 }
270 op == OperationType::Modify && self.0 & OperationType::SelfWrite.bit() != 0
272 }
273
274 pub fn is_empty(self) -> bool {
275 self.0 == 0
276 }
277
278 pub fn len(self) -> usize {
279 self.0.count_ones() as usize
280 }
281
282 pub fn insert(&mut self, op: OperationType) {
283 self.0 |= op.bit();
284 }
285
286 pub fn union(self, other: Self) -> Self {
287 Self(self.0 | other.0)
288 }
289
290 pub fn intersection(self, other: Self) -> Self {
291 Self(self.0 & other.0)
292 }
293
294 pub fn difference(self, other: Self) -> Self {
295 Self(self.0 & !other.0)
296 }
297
298 pub fn is_subset_of(self, other: Self) -> bool {
299 self.0 & other.0 == self.0
300 }
301
302 pub fn is_superset_of(self, other: Self) -> bool {
303 other.is_subset_of(self)
304 }
305
306 pub fn grants_write(self) -> bool {
307 let write_bits = OperationType::Modify.bit()
308 | OperationType::Add.bit()
309 | OperationType::Delete.bit()
310 | OperationType::ModifyDn.bit()
311 | OperationType::SelfWrite.bit();
312 self.0 & write_bits != 0
313 }
314
315 pub fn grants_read(self) -> bool {
316 let read_bits =
317 OperationType::Read.bit() | OperationType::Search.bit() | OperationType::Compare.bit();
318 self.0 & read_bits != 0
319 }
320
321 pub fn iter(self) -> OperationSetIter {
322 OperationSetIter {
323 bits: self.0,
324 pos: 0,
325 }
326 }
327
328 pub fn to_vec(self) -> Vec<OperationType> {
329 self.iter().collect()
330 }
331}
332
333impl From<OperationType> for OperationSet {
334 fn from(op: OperationType) -> Self {
335 Self(op.bit())
336 }
337}
338
339impl From<&[OperationType]> for OperationSet {
340 fn from(ops: &[OperationType]) -> Self {
341 let mut bits = 0u16;
342 for op in ops {
343 bits |= op.bit();
344 }
345 Self(bits)
346 }
347}
348
349impl FromIterator<OperationType> for OperationSet {
350 fn from_iter<I: IntoIterator<Item = OperationType>>(iter: I) -> Self {
351 let mut bits = 0u16;
352 for op in iter {
353 bits |= op.bit();
354 }
355 Self(bits)
356 }
357}
358
359impl fmt::Debug for OperationSet {
360 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
361 f.debug_set().entries(self.iter()).finish()
362 }
363}
364
365impl fmt::Display for OperationSet {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 let ops: Vec<&str> = self.iter().map(|op| op.as_str()).collect();
368 write!(f, "{}", ops.join(","))
369 }
370}
371
372impl PartialOrd for OperationSet {
373 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
374 let a_sub_b = self.is_subset_of(*other);
375 let b_sub_a = other.is_subset_of(*self);
376 match (a_sub_b, b_sub_a) {
377 (true, true) => Some(Ordering::Equal),
378 (true, false) => Some(Ordering::Less),
379 (false, true) => Some(Ordering::Greater),
380 (false, false) => None,
381 }
382 }
383}
384
385impl Semigroup for OperationSet {
386 fn combine(self, other: Self) -> Self {
387 self.union(other)
388 }
389}
390
391impl Monoid for OperationSet {
392 fn identity() -> Self {
393 Self::empty()
394 }
395}
396
397impl MeetSemilattice for OperationSet {
398 fn meet(self, other: Self) -> Self {
399 self.intersection(other)
400 }
401}
402
403impl JoinSemilattice for OperationSet {
404 fn join(self, other: Self) -> Self {
405 self.union(other)
406 }
407}
408
409impl BoundedMeetSemilattice for OperationSet {
410 fn top() -> Self {
411 Self::all()
412 }
413}
414
415impl BoundedJoinSemilattice for OperationSet {
416 fn bottom() -> Self {
417 Self::empty()
418 }
419}
420
421pub struct OperationSetIter {
423 bits: u16,
424 pos: u8,
425}
426
427const OPERATION_TABLE: [OperationType; 9] = [
428 OperationType::Bind,
429 OperationType::Search,
430 OperationType::Compare,
431 OperationType::Read,
432 OperationType::Add,
433 OperationType::Delete,
434 OperationType::Modify,
435 OperationType::ModifyDn,
436 OperationType::SelfWrite,
437];
438
439impl Iterator for OperationSetIter {
440 type Item = OperationType;
441
442 fn next(&mut self) -> Option<Self::Item> {
443 while (self.pos as usize) < OPERATION_TABLE.len() {
444 let bit = 1u16 << self.pos;
445 self.pos += 1;
446 if self.bits & bit != 0 {
447 return Some(OPERATION_TABLE[(self.pos - 1) as usize]);
448 }
449 }
450 None
451 }
452}
453
454#[cfg(test)]
455mod tests {
456 use super::*;
457
458 mod operation_type_ordering {
459 use super::*;
460
461 #[test]
462 fn all_subsumes_everything() {
463 for op in OPERATION_TABLE {
464 assert!(OperationType::All >= op, "All should subsume {:?}", op);
465 }
466 }
467
468 #[test]
469 fn selfwrite_subsumes_modify() {
470 assert!(OperationType::SelfWrite > OperationType::Modify);
471 }
472
473 #[test]
474 fn incomparable_leaves() {
475 assert_eq!(
476 OperationType::Read.partial_cmp(&OperationType::Search),
477 None
478 );
479 assert_eq!(OperationType::Add.partial_cmp(&OperationType::Delete), None);
480 assert_eq!(
481 OperationType::Bind.partial_cmp(&OperationType::Modify),
482 None
483 );
484 }
485
486 #[test]
487 fn reflexive() {
488 for op in OPERATION_TABLE {
489 assert_eq!(op.partial_cmp(&op), Some(Ordering::Equal));
490 }
491 assert_eq!(
492 OperationType::All.partial_cmp(&OperationType::All),
493 Some(Ordering::Equal)
494 );
495 }
496 }
497
498 mod operation_set {
499 use super::*;
500
501 #[test]
502 fn from_slice() {
503 let ops = [OperationType::Read, OperationType::Search];
504 let set = OperationSet::from(ops.as_slice());
505 assert!(set.contains(OperationType::Read));
506 assert!(set.contains(OperationType::Search));
507 assert!(!set.contains(OperationType::Modify));
508 assert_eq!(set.len(), 2);
509 }
510
511 #[test]
512 fn all_expands() {
513 let set = OperationSet::from(OperationType::All);
514 assert_eq!(set.len(), 9);
515 for op in OPERATION_TABLE {
516 assert!(set.contains(op), "All should contain {:?}", op);
517 }
518 }
519
520 #[test]
521 fn selfwrite_subsumes_modify_in_set() {
522 let set = OperationSet::from(OperationType::SelfWrite);
523 assert!(set.contains(OperationType::SelfWrite));
524 assert!(set.contains(OperationType::Modify));
525 assert_eq!(set.len(), 1);
526 }
527
528 #[test]
529 fn subset_ordering() {
530 let small = OperationSet::from([OperationType::Read].as_slice());
531 let big = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
532 assert!(small < big);
533 assert!(small <= big);
534 assert!(big > small);
535 }
536
537 #[test]
538 fn incomparable_sets() {
539 let a = OperationSet::from([OperationType::Read].as_slice());
540 let b = OperationSet::from([OperationType::Modify].as_slice());
541 assert_eq!(a.partial_cmp(&b), None);
542 }
543
544 #[test]
545 fn lattice_meet() {
546 let a = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
547 let b = OperationSet::from([OperationType::Read, OperationType::Modify].as_slice());
548 let meet = a.meet(b);
549 assert_eq!(meet.len(), 1);
550 assert!(meet.contains(OperationType::Read));
551 }
552
553 #[test]
554 fn lattice_join() {
555 let a = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
556 let b = OperationSet::from([OperationType::Read, OperationType::Modify].as_slice());
557 let join = a.join(b);
558 assert_eq!(join.len(), 3);
559 assert!(join.contains(OperationType::Read));
560 assert!(join.contains(OperationType::Search));
561 assert!(join.contains(OperationType::Modify));
562 }
563
564 #[test]
565 fn monoid_identity() {
566 let set = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
567 assert_eq!(set.combine(OperationSet::identity()), set);
568 assert_eq!(OperationSet::identity().combine(set), set);
569 }
570
571 #[test]
572 fn bounded_top_bottom() {
573 let set = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
574 assert_eq!(set.meet(OperationSet::top()), set);
575 assert_eq!(set.join(OperationSet::bottom()), set);
576 }
577
578 #[test]
579 fn idempotence() {
580 let set = OperationSet::from([OperationType::Read, OperationType::Modify].as_slice());
581 assert_eq!(set.meet(set), set);
582 assert_eq!(set.join(set), set);
583 }
584
585 #[test]
586 fn iter_round_trip() {
587 let ops = [
588 OperationType::Read,
589 OperationType::Search,
590 OperationType::Add,
591 ];
592 let set = OperationSet::from(ops.as_slice());
593 let vec = set.to_vec();
594 assert_eq!(vec.len(), 3);
595 assert!(vec.contains(&OperationType::Read));
596 assert!(vec.contains(&OperationType::Search));
597 assert!(vec.contains(&OperationType::Add));
598 }
599 }
600}