1use crate::be::dbvalue::{
2 DbCidV1, DbValueAccessScopeV1, DbValueApiToken, DbValueApiTokenScopeV1, DbValueAuthTypeV1,
3 DbValueIdentityId, DbValueOauth2Session, DbValueSession, DbValueSessionStateV1,
4};
5use crate::prelude::*;
6use crate::repl::cid::Cid;
7use crate::schema::SchemaAttribute;
8use crate::value::{
9 ApiToken, ApiTokenScope, AuthType, Oauth2Session, Session, SessionScope, SessionState,
10};
11use crate::valueset::{uuid_to_proto_string, DbValueSetV2, ScimResolveStatus, ValueSet};
12use kanidm_proto::scim_v1::server::ScimApiToken;
13use kanidm_proto::scim_v1::server::ScimAuthSession;
14use kanidm_proto::scim_v1::server::ScimOAuth2Session;
15use std::collections::btree_map::Entry as BTreeEntry;
16use std::collections::BTreeMap;
17use time::OffsetDateTime;
18
19#[derive(Debug, Clone)]
20pub struct ValueSetSession {
21 map: BTreeMap<Uuid, Session>,
22}
23
24impl ValueSetSession {
25 pub fn new(u: Uuid, m: Session) -> Box<Self> {
26 let mut map = BTreeMap::new();
27 map.insert(u, m);
28 Box::new(ValueSetSession { map })
29 }
30
31 pub fn push(&mut self, u: Uuid, m: Session) -> bool {
32 self.map.insert(u, m).is_none()
33 }
34
35 fn to_vec_dbvs(&self) -> Vec<DbValueSession> {
36 self.map
37 .iter()
38 .map(|(u, m)| DbValueSession::V4 {
39 refer: *u,
40 label: m.label.clone(),
41
42 state: match &m.state {
43 SessionState::ExpiresAt(odt) => {
44 debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
45 #[allow(clippy::expect_used)]
46 odt.format(&Rfc3339)
47 .map(DbValueSessionStateV1::ExpiresAt)
48 .expect("Failed to format timestamp into RFC3339!")
49 }
50 SessionState::NeverExpires => DbValueSessionStateV1::Never,
51 SessionState::RevokedAt(c) => DbValueSessionStateV1::RevokedAt(DbCidV1 {
52 server_id: c.s_uuid,
53 timestamp: c.ts,
54 }),
55 },
56
57 issued_at: {
58 debug_assert_eq!(m.issued_at.offset(), time::UtcOffset::UTC);
59 #[allow(clippy::expect_used)]
60 m.issued_at
61 .format(&Rfc3339)
62 .expect("Failed to format timestamp into RFC3339!")
63 },
64 issued_by: match m.issued_by {
65 IdentityId::Internal => DbValueIdentityId::V1Internal,
66 IdentityId::User(u) => DbValueIdentityId::V1Uuid(u),
67 IdentityId::Synch(u) => DbValueIdentityId::V1Sync(u),
68 },
69 cred_id: m.cred_id,
70 scope: match m.scope {
71 SessionScope::ReadOnly => DbValueAccessScopeV1::ReadOnly,
72 SessionScope::ReadWrite => DbValueAccessScopeV1::ReadWrite,
73 SessionScope::PrivilegeCapable => DbValueAccessScopeV1::PrivilegeCapable,
74 SessionScope::Synchronise => DbValueAccessScopeV1::Synchronise,
75 },
76 type_: match m.type_ {
77 AuthType::Anonymous => DbValueAuthTypeV1::Anonymous,
78 AuthType::Password => DbValueAuthTypeV1::Password,
79 AuthType::GeneratedPassword => DbValueAuthTypeV1::GeneratedPassword,
80 AuthType::PasswordTotp => DbValueAuthTypeV1::PasswordTotp,
81 AuthType::PasswordBackupCode => DbValueAuthTypeV1::PasswordBackupCode,
82 AuthType::PasswordSecurityKey => DbValueAuthTypeV1::PasswordSecurityKey,
83 AuthType::Passkey => DbValueAuthTypeV1::Passkey,
84 AuthType::AttestedPasskey => DbValueAuthTypeV1::AttestedPasskey,
85 },
86 })
87 .collect()
88 }
89
90 fn from_dbv_iter<'a>(
91 iter: impl Iterator<Item = &'a DbValueSession>,
92 ) -> Result<ValueSet, OperationError> {
93 let map = iter
94 .filter_map(|dbv| {
95 match dbv {
96 DbValueSession::V1 { .. }
99 | DbValueSession::V2 { .. }
100 | DbValueSession::V3 { .. } => None,
101 DbValueSession::V4 {
102 refer,
103 label,
104 state,
105 issued_at,
106 issued_by,
107 cred_id,
108 scope,
109 type_,
110 } => {
111 let issued_at = OffsetDateTime::parse(issued_at, &Rfc3339)
113 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
114 .map_err(|e| {
115 admin_error!(
116 ?e,
117 "Invalidating session {} due to invalid issued_at timestamp",
118 refer
119 )
120 })
121 .ok()?;
122
123 let state = match state {
124 DbValueSessionStateV1::ExpiresAt(e_inner) => {
125 OffsetDateTime::parse(e_inner, &Rfc3339)
126 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
127 .map(SessionState::ExpiresAt)
128 .map_err(|e| {
129 admin_error!(
130 ?e,
131 "Invalidating session {} due to invalid expiry timestamp",
132 refer
133 )
134 })
135 .ok()?
136 }
137 DbValueSessionStateV1::Never => SessionState::NeverExpires,
138 DbValueSessionStateV1::RevokedAt(dc) => SessionState::RevokedAt(Cid {
139 s_uuid: dc.server_id,
140 ts: dc.timestamp,
141 }),
142 };
143
144 let issued_by = match issued_by {
145 DbValueIdentityId::V1Internal => IdentityId::Internal,
146 DbValueIdentityId::V1Uuid(u) => IdentityId::User(*u),
147 DbValueIdentityId::V1Sync(u) => IdentityId::Synch(*u),
148 };
149
150 let scope = match scope {
151 DbValueAccessScopeV1::IdentityOnly | DbValueAccessScopeV1::ReadOnly => {
152 SessionScope::ReadOnly
153 }
154 DbValueAccessScopeV1::ReadWrite => SessionScope::ReadWrite,
155 DbValueAccessScopeV1::PrivilegeCapable => {
156 SessionScope::PrivilegeCapable
157 }
158 DbValueAccessScopeV1::Synchronise => SessionScope::Synchronise,
159 };
160
161 let type_ = match type_ {
162 DbValueAuthTypeV1::Anonymous => AuthType::Anonymous,
163 DbValueAuthTypeV1::Password => AuthType::Password,
164 DbValueAuthTypeV1::GeneratedPassword => AuthType::GeneratedPassword,
165 DbValueAuthTypeV1::PasswordTotp => AuthType::PasswordTotp,
166 DbValueAuthTypeV1::PasswordBackupCode => AuthType::PasswordBackupCode,
167 DbValueAuthTypeV1::PasswordSecurityKey => AuthType::PasswordSecurityKey,
168 DbValueAuthTypeV1::Passkey => AuthType::Passkey,
169 DbValueAuthTypeV1::AttestedPasskey => AuthType::AttestedPasskey,
170 };
171
172 Some((
173 *refer,
174 Session {
175 label: label.clone(),
176 state,
177 issued_at,
178 issued_by,
179 cred_id: *cred_id,
180 scope,
181 type_,
182 },
183 ))
184 }
185 }
186 })
187 .collect();
188 Ok(Box::new(ValueSetSession { map }))
189 }
190
191 pub fn from_dbvs2(data: &[DbValueSession]) -> Result<ValueSet, OperationError> {
192 Self::from_dbv_iter(data.iter())
193 }
194
195 #[allow(clippy::should_implement_trait)]
198 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
199 where
200 T: IntoIterator<Item = (Uuid, Session)>,
201 {
202 let map = iter.into_iter().collect();
203 Some(Box::new(ValueSetSession { map }))
204 }
205}
206
207impl ValueSetT for ValueSetSession {
208 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
209 match value {
210 Value::Session(u, m) => {
211 if let BTreeEntry::Vacant(e) = self.map.entry(u) {
212 e.insert(m);
213 Ok(true)
214 } else {
215 Ok(false)
216 }
217 }
218 _ => Err(OperationError::InvalidValueState),
219 }
220 }
221
222 fn clear(&mut self) {
223 self.map.clear();
224 }
225
226 fn remove(&mut self, pv: &PartialValue, cid: &Cid) -> bool {
227 match pv {
228 PartialValue::Refer(u) => {
229 if let Some(session) = self.map.get_mut(u) {
230 if !matches!(session.state, SessionState::RevokedAt(_)) {
231 session.state = SessionState::RevokedAt(cid.clone());
232 true
233 } else {
234 false
235 }
236 } else {
237 false
238 }
239 }
240 _ => false,
241 }
242 }
243
244 fn purge(&mut self, cid: &Cid) -> bool {
245 for (_uuid, session) in self.map.iter_mut() {
246 if !matches!(session.state, SessionState::RevokedAt(_)) {
248 session.state = SessionState::RevokedAt(cid.clone())
249 }
250 }
251 false
253 }
254
255 fn trim(&mut self, trim_cid: &Cid) {
256 self.map.retain(|_, session| {
261 match &session.state {
262 SessionState::RevokedAt(cid) if cid < trim_cid => {
263 false
266 }
267 _ => true,
269 }
270 });
271
272 if self.map.len() > SESSION_MAXIMUM {
274 warn!(
284 "entry has exceeded session_maximum limit ({:?}), force trimming will occur",
285 SESSION_MAXIMUM
286 );
287
288 let time_idx: BTreeMap<OffsetDateTime, Uuid> = self
289 .map
290 .iter()
291 .map(|(session_id, session)| (session.issued_at, *session_id))
292 .collect();
293
294 let to_take = self.map.len() - SESSION_MAXIMUM;
295
296 time_idx.values().take(to_take).for_each(|session_id| {
297 warn!(?session_id, "force trimmed");
298 self.map.remove(session_id);
299 });
300 }
301 }
303
304 fn contains(&self, pv: &PartialValue) -> bool {
305 match pv {
306 PartialValue::Refer(u) => self.map.contains_key(u),
307 _ => false,
308 }
309 }
310
311 fn substring(&self, _pv: &PartialValue) -> bool {
312 false
313 }
314
315 fn startswith(&self, _pv: &PartialValue) -> bool {
316 false
317 }
318
319 fn endswith(&self, _pv: &PartialValue) -> bool {
320 false
321 }
322
323 fn lessthan(&self, _pv: &PartialValue) -> bool {
324 false
325 }
326
327 fn len(&self) -> usize {
328 self.map.len()
329 }
330
331 fn generate_idx_eq_keys(&self) -> Vec<String> {
332 self.map
333 .keys()
334 .map(|u| u.as_hyphenated().to_string())
335 .collect()
336 }
337
338 fn syntax(&self) -> SyntaxType {
339 SyntaxType::Session
340 }
341
342 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
343 true
344 }
345
346 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
347 Box::new(
348 self.map
349 .iter()
350 .map(|(u, m)| format!("{}: {:?}", uuid_to_proto_string(*u), m)),
351 )
352 }
353
354 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
355 Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
356 self.map
357 .iter()
358 .map(|(session_id, session)| {
359 let (expires, revoked) = match &session.state {
360 SessionState::ExpiresAt(odt) => (Some(*odt), None),
361 SessionState::NeverExpires => (None, None),
362 SessionState::RevokedAt(cid) => {
363 let odt: OffsetDateTime = cid.into();
364 (None, Some(odt))
365 }
366 };
367
368 ScimAuthSession {
369 id: *session_id,
370 expires,
371 revoked,
372
373 issued_at: session.issued_at,
374 issued_by: Uuid::from(&session.issued_by),
375 credential_id: session.cred_id,
376 auth_type: session.type_.to_string(),
377 session_scope: session.scope.to_string(),
378 }
379 })
380 .collect::<Vec<_>>(),
381 )))
382 }
383
384 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
385 DbValueSetV2::Session(self.to_vec_dbvs())
386 }
387
388 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
389 Box::new(self.map.keys().cloned().map(PartialValue::Refer))
390 }
391
392 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
393 Box::new(self.map.iter().map(|(u, m)| Value::Session(*u, m.clone())))
394 }
395
396 fn equal(&self, other: &ValueSet) -> bool {
397 if let Some(other) = other.as_session_map() {
398 &self.map == other
399 } else {
400 debug_assert!(false);
401 false
402 }
403 }
404
405 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
406 if let Some(b) = other.as_session_map() {
407 for (k_other, v_other) in b.iter() {
410 if let Some(v_self) = self.map.get_mut(k_other) {
411 if v_other.state > v_self.state {
415 *v_self = v_other.clone();
416 }
417 } else {
418 self.map.insert(*k_other, v_other.clone());
420 }
421 }
422 Ok(())
423 } else {
424 debug_assert!(false);
425 Err(OperationError::InvalidValueState)
426 }
427 }
428
429 fn as_session_map(&self) -> Option<&BTreeMap<Uuid, Session>> {
430 Some(&self.map)
431 }
432
433 fn as_ref_uuid_iter(&self) -> Option<Box<dyn Iterator<Item = Uuid> + '_>> {
434 Some(Box::new(self.map.keys().copied()))
436 }
437
438 fn repl_merge_valueset(&self, older: &ValueSet, trim_cid: &Cid) -> Option<ValueSet> {
439 let b = older.as_session_map()?;
442 let mut map = self.map.clone();
445 for (k_other, v_other) in b.iter() {
446 if let Some(v_self) = map.get_mut(k_other) {
447 if v_other.state > v_self.state {
451 *v_self = v_other.clone();
452 }
453 } else {
454 map.insert(*k_other, v_other.clone());
456 }
457 }
458
459 let mut vs = Box::new(ValueSetSession { map });
460
461 vs.trim(trim_cid);
462
463 Some(vs)
464 }
465}
466
467#[derive(Debug, Clone)]
470pub struct ValueSetOauth2Session {
471 map: BTreeMap<Uuid, Oauth2Session>,
472 rs_filter: u128,
478}
479
480impl ValueSetOauth2Session {
481 pub fn new(u: Uuid, m: Oauth2Session) -> Box<Self> {
482 let mut map = BTreeMap::new();
483 let rs_filter = m.rs_uuid.as_u128();
484 map.insert(u, m);
485 Box::new(ValueSetOauth2Session { map, rs_filter })
486 }
487
488 pub fn push(&mut self, u: Uuid, m: Oauth2Session) -> bool {
489 self.rs_filter |= m.rs_uuid.as_u128();
490 self.map.insert(u, m).is_none()
491 }
492
493 pub fn from_dbvs2(data: Vec<DbValueOauth2Session>) -> Result<ValueSet, OperationError> {
494 let mut rs_filter = u128::MIN;
495 let map = data
496 .into_iter()
497 .filter_map(|dbv| {
498 match dbv {
499 DbValueOauth2Session::V1 {
500 refer,
501 parent,
502 expiry,
503 issued_at,
504 rs_uuid,
505 } => {
506 let issued_at = OffsetDateTime::parse(&issued_at, &Rfc3339)
508 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
509 .map_err(|e| {
510 admin_error!(
511 ?e,
512 "Invalidating session {} due to invalid issued_at timestamp",
513 refer
514 )
515 })
516 .ok()?;
517
518 let expiry = expiry
523 .map(|e_inner| {
524 OffsetDateTime::parse(&e_inner, &Rfc3339)
525 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
526 })
529 .transpose()
530 .map_err(|e| {
532 admin_error!(
533 ?e,
534 "Invalidating session {} due to invalid expiry timestamp",
535 refer
536 )
537 })
538 .ok()?;
540
541 let state = expiry
542 .map(SessionState::ExpiresAt)
543 .unwrap_or(SessionState::NeverExpires);
544
545 let parent = Some(parent);
546
547 rs_filter |= rs_uuid.as_u128();
549 Some((
550 refer,
551 Oauth2Session {
552 parent,
553 state,
554 issued_at,
555 rs_uuid,
556 },
557 ))
558 }
559 DbValueOauth2Session::V2 {
560 refer,
561 parent,
562 state,
563 issued_at,
564 rs_uuid,
565 } => {
566 let issued_at = OffsetDateTime::parse(&issued_at, &Rfc3339)
568 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
569 .map_err(|e| {
570 admin_error!(
571 ?e,
572 "Invalidating session {} due to invalid issued_at timestamp",
573 refer
574 )
575 })
576 .ok()?;
577
578 let state = match state {
579 DbValueSessionStateV1::ExpiresAt(e_inner) => {
580 OffsetDateTime::parse(&e_inner, &Rfc3339)
581 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
582 .map(SessionState::ExpiresAt)
583 .map_err(|e| {
584 admin_error!(
585 ?e,
586 "Invalidating session {} due to invalid expiry timestamp",
587 refer
588 )
589 })
590 .ok()?
591 }
592 DbValueSessionStateV1::Never => SessionState::NeverExpires,
593 DbValueSessionStateV1::RevokedAt(dc) => SessionState::RevokedAt(Cid {
594 s_uuid: dc.server_id,
595 ts: dc.timestamp,
596 }),
597 };
598
599 rs_filter |= rs_uuid.as_u128();
600
601 let parent = Some(parent);
602
603 Some((
604 refer,
605 Oauth2Session {
606 parent,
607 state,
608 issued_at,
609 rs_uuid,
610 },
611 ))
612 } DbValueOauth2Session::V3 {
614 refer,
615 parent,
616 state,
617 issued_at,
618 rs_uuid,
619 } => {
620 let issued_at = OffsetDateTime::parse(&issued_at, &Rfc3339)
622 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
623 .map_err(|e| {
624 admin_error!(
625 ?e,
626 "Invalidating session {} due to invalid issued_at timestamp",
627 refer
628 )
629 })
630 .ok()?;
631
632 let state = match state {
633 DbValueSessionStateV1::ExpiresAt(e_inner) => {
634 OffsetDateTime::parse(&e_inner, &Rfc3339)
635 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
636 .map(SessionState::ExpiresAt)
637 .map_err(|e| {
638 admin_error!(
639 ?e,
640 "Invalidating session {} due to invalid expiry timestamp",
641 refer
642 )
643 })
644 .ok()?
645 }
646 DbValueSessionStateV1::Never => SessionState::NeverExpires,
647 DbValueSessionStateV1::RevokedAt(dc) => SessionState::RevokedAt(Cid {
648 s_uuid: dc.server_id,
649 ts: dc.timestamp,
650 }),
651 };
652
653 rs_filter |= rs_uuid.as_u128();
654
655 Some((
656 refer,
657 Oauth2Session {
658 parent,
659 state,
660 issued_at,
661 rs_uuid,
662 },
663 ))
664 } }
666 })
667 .collect();
668 Ok(Box::new(ValueSetOauth2Session { map, rs_filter }))
669 }
670
671 #[allow(clippy::should_implement_trait)]
674 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
675 where
676 T: IntoIterator<Item = (Uuid, Oauth2Session)>,
677 {
678 let mut rs_filter = u128::MIN;
679 let map = iter
680 .into_iter()
681 .map(|(u, m)| {
682 rs_filter |= m.rs_uuid.as_u128();
683 (u, m)
684 })
685 .collect();
686 Some(Box::new(ValueSetOauth2Session { map, rs_filter }))
687 }
688}
689
690impl ValueSetT for ValueSetOauth2Session {
691 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
692 match value {
693 Value::Oauth2Session(u, m) => {
694 match self.map.entry(u) {
697 BTreeEntry::Vacant(e) => {
698 self.rs_filter |= m.rs_uuid.as_u128();
699 e.insert(m);
700 Ok(true)
701 }
702 BTreeEntry::Occupied(mut e) => {
703 let e_v = e.get_mut();
704 if m.state > e_v.state {
705 *e_v = m;
707 Ok(true)
708 } else {
709 Ok(false)
711 }
712 }
713 }
714 }
715 _ => Err(OperationError::InvalidValueState),
716 }
717 }
718
719 fn clear(&mut self) {
720 self.rs_filter = u128::MIN;
721 self.map.clear();
722 }
723
724 fn remove(&mut self, pv: &PartialValue, cid: &Cid) -> bool {
725 match pv {
726 PartialValue::Refer(u) => {
727 if let Some(session) = self.map.get_mut(u) {
728 if !matches!(session.state, SessionState::RevokedAt(_)) {
729 session.state = SessionState::RevokedAt(cid.clone());
730 true
731 } else {
732 false
733 }
734 } else {
735 let u_int = u.as_u128();
737 if self.rs_filter & u_int == u_int {
738 let mut removed = false;
741 self.map.values_mut().for_each(|session| {
742 if session.rs_uuid == *u {
743 session.state = SessionState::RevokedAt(cid.clone());
744 removed = true;
745 }
746 });
747 removed
748 } else {
749 false
751 }
752 }
753 }
754 _ => false,
755 }
756 }
757
758 fn purge(&mut self, cid: &Cid) -> bool {
759 for (_uuid, session) in self.map.iter_mut() {
760 if !matches!(session.state, SessionState::RevokedAt(_)) {
762 session.state = SessionState::RevokedAt(cid.clone())
763 }
764 }
765 false
767 }
768
769 fn trim(&mut self, trim_cid: &Cid) {
770 self.map.retain(|_, session| {
775 match &session.state {
776 SessionState::RevokedAt(cid) if cid < trim_cid => {
777 false
780 }
781 _ => true,
783 }
784 })
785 }
786
787 fn contains(&self, pv: &PartialValue) -> bool {
788 match pv {
789 PartialValue::Refer(u) => {
790 self.map.contains_key(u) || {
791 let u_int = u.as_u128();
792 if self.rs_filter & u_int == u_int {
793 self.map.values().any(|session| {
794 session.rs_uuid == *u
795 && !matches!(session.state, SessionState::RevokedAt(_))
796 })
797 } else {
798 false
799 }
800 }
801 }
802 _ => false,
803 }
804 }
805
806 fn substring(&self, _pv: &PartialValue) -> bool {
807 false
808 }
809
810 fn startswith(&self, _pv: &PartialValue) -> bool {
811 false
812 }
813
814 fn endswith(&self, _pv: &PartialValue) -> bool {
815 false
816 }
817
818 fn lessthan(&self, _pv: &PartialValue) -> bool {
819 false
820 }
821
822 fn len(&self) -> usize {
823 self.map.len()
824 }
825
826 fn generate_idx_eq_keys(&self) -> Vec<String> {
827 let mut idx_keys = Vec::with_capacity(self.map.len() * 2);
830 for (k, v) in self.map.iter() {
831 idx_keys.push(k.as_hyphenated().to_string());
832 idx_keys.push(v.rs_uuid.as_hyphenated().to_string());
833 }
834 idx_keys.sort_unstable();
835 idx_keys.dedup();
836 idx_keys
837 }
838
839 fn syntax(&self) -> SyntaxType {
840 SyntaxType::Oauth2Session
841 }
842
843 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
844 true
845 }
846
847 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
848 Box::new(
849 self.map
850 .iter()
851 .map(|(u, m)| format!("{}: {:?}", uuid_to_proto_string(*u), m)),
852 )
853 }
854
855 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
856 Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
857 self.map
858 .iter()
859 .map(|(session_id, session)| {
860 let (expires, revoked) = match &session.state {
861 SessionState::ExpiresAt(odt) => (Some(*odt), None),
862 SessionState::NeverExpires => (None, None),
863 SessionState::RevokedAt(cid) => {
864 let odt: OffsetDateTime = cid.into();
865 (None, Some(odt))
866 }
867 };
868
869 ScimOAuth2Session {
870 id: *session_id,
871 parent_id: session.parent,
872 client_id: session.rs_uuid,
873 issued_at: session.issued_at,
874 expires,
875 revoked,
876 }
877 })
878 .collect::<Vec<_>>(),
879 )))
880 }
881
882 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
883 DbValueSetV2::Oauth2Session(
884 self.map
885 .iter()
886 .map(|(u, m)| DbValueOauth2Session::V3 {
887 refer: *u,
888 parent: m.parent,
889 state: match &m.state {
890 SessionState::ExpiresAt(odt) => {
891 debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
892 #[allow(clippy::expect_used)]
893 odt.format(&Rfc3339)
894 .map(DbValueSessionStateV1::ExpiresAt)
895 .expect("Failed to format timestamp into RFC3339!")
896 }
897 SessionState::NeverExpires => DbValueSessionStateV1::Never,
898 SessionState::RevokedAt(c) => DbValueSessionStateV1::RevokedAt(DbCidV1 {
899 server_id: c.s_uuid,
900 timestamp: c.ts,
901 }),
902 },
903 issued_at: {
904 debug_assert_eq!(m.issued_at.offset(), time::UtcOffset::UTC);
905 #[allow(clippy::expect_used)]
906 m.issued_at
907 .format(&Rfc3339)
908 .expect("Failed to format timestamp as RFC3339")
909 },
910 rs_uuid: m.rs_uuid,
911 })
912 .collect(),
913 )
914 }
915
916 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
917 Box::new(self.map.keys().cloned().map(PartialValue::Refer))
918 }
919
920 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
921 Box::new(
922 self.map
923 .iter()
924 .map(|(u, m)| Value::Oauth2Session(*u, m.clone())),
925 )
926 }
927
928 fn equal(&self, other: &ValueSet) -> bool {
929 if let Some(other) = other.as_oauth2session_map() {
930 &self.map == other
931 } else {
932 debug_assert!(false);
933 false
934 }
935 }
936
937 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
938 if let Some(b) = other.as_oauth2session_map() {
939 for (k_other, v_other) in b.iter() {
943 if let Some(v_self) = self.map.get_mut(k_other) {
944 if v_other.state > v_self.state {
948 *v_self = v_other.clone();
949 }
950 } else {
951 self.rs_filter |= v_other.rs_uuid.as_u128();
953 self.map.insert(*k_other, v_other.clone());
955 }
956 }
957 Ok(())
958 } else {
959 debug_assert!(false);
960 Err(OperationError::InvalidValueState)
961 }
962 }
963
964 fn as_oauth2session_map(&self) -> Option<&BTreeMap<Uuid, Oauth2Session>> {
965 Some(&self.map)
966 }
967
968 fn as_ref_uuid_iter(&self) -> Option<Box<dyn Iterator<Item = Uuid> + '_>> {
969 Some(Box::new(self.map.values().map(|m| &m.rs_uuid).copied()))
972 }
973
974 fn repl_merge_valueset(&self, older: &ValueSet, trim_cid: &Cid) -> Option<ValueSet> {
975 if let Some(b) = older.as_oauth2session_map() {
976 let mut map = self.map.clone();
979 let mut rs_filter = self.rs_filter;
980 for (k_other, v_other) in b.iter() {
981 if let Some(v_self) = map.get_mut(k_other) {
982 if v_other.state > v_self.state {
986 *v_self = v_other.clone();
987 }
988 } else {
989 rs_filter |= v_other.rs_uuid.as_u128();
991 map.insert(*k_other, v_other.clone());
992 }
993 }
994
995 let mut vs = Box::new(ValueSetOauth2Session { map, rs_filter });
996
997 vs.trim(trim_cid);
998
999 Some(vs)
1000 } else {
1001 None
1004 }
1005 }
1006}
1007
1008#[derive(Debug, Clone)]
1009pub struct ValueSetApiToken {
1010 map: BTreeMap<Uuid, ApiToken>,
1011}
1012
1013impl ValueSetApiToken {
1014 pub fn new(u: Uuid, m: ApiToken) -> Box<Self> {
1015 let mut map = BTreeMap::new();
1016 map.insert(u, m);
1017 Box::new(ValueSetApiToken { map })
1018 }
1019
1020 pub fn push(&mut self, u: Uuid, m: ApiToken) -> bool {
1021 self.map.insert(u, m).is_none()
1022 }
1023
1024 pub fn from_dbvs2(data: Vec<DbValueApiToken>) -> Result<ValueSet, OperationError> {
1025 let map = data
1026 .into_iter()
1027 .filter_map(|dbv| {
1028 match dbv {
1029 DbValueApiToken::V1 {
1030 refer,
1031 label,
1032 expiry,
1033 issued_at,
1034 issued_by,
1035 scope,
1036 } => {
1037 let issued_at = OffsetDateTime::parse(&issued_at, &Rfc3339)
1039 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
1040 .map_err(|e| {
1041 admin_error!(
1042 ?e,
1043 "Invalidating api token {} due to invalid issued_at timestamp",
1044 refer
1045 )
1046 })
1047 .ok()?;
1048
1049 let expiry = expiry
1054 .map(|e_inner| {
1055 OffsetDateTime::parse(&e_inner, &Rfc3339)
1056 .map(|odt| odt.to_offset(time::UtcOffset::UTC))
1057 })
1060 .transpose()
1061 .map_err(|e| {
1063 admin_error!(
1064 ?e,
1065 "Invalidating api token {} due to invalid expiry timestamp",
1066 refer
1067 )
1068 })
1069 .ok()?;
1071
1072 let issued_by = match issued_by {
1073 DbValueIdentityId::V1Internal => IdentityId::Internal,
1074 DbValueIdentityId::V1Uuid(u) => IdentityId::User(u),
1075 DbValueIdentityId::V1Sync(u) => IdentityId::Synch(u),
1076 };
1077
1078 let scope = match scope {
1079 DbValueApiTokenScopeV1::ReadOnly => ApiTokenScope::ReadOnly,
1080 DbValueApiTokenScopeV1::ReadWrite => ApiTokenScope::ReadWrite,
1081 DbValueApiTokenScopeV1::Synchronise => ApiTokenScope::Synchronise,
1082 };
1083
1084 Some((
1085 refer,
1086 ApiToken {
1087 label,
1088 expiry,
1089 issued_at,
1090 issued_by,
1091 scope,
1092 },
1093 ))
1094 }
1095 }
1096 })
1097 .collect();
1098 Ok(Box::new(ValueSetApiToken { map }))
1099 }
1100
1101 #[allow(clippy::should_implement_trait)]
1104 pub fn from_iter<T>(iter: T) -> Option<Box<Self>>
1105 where
1106 T: IntoIterator<Item = (Uuid, ApiToken)>,
1107 {
1108 let map = iter.into_iter().collect();
1109 Some(Box::new(ValueSetApiToken { map }))
1110 }
1111}
1112
1113impl ValueSetT for ValueSetApiToken {
1114 fn insert_checked(&mut self, value: Value) -> Result<bool, OperationError> {
1115 match value {
1116 Value::ApiToken(u, m) => {
1117 if let BTreeEntry::Vacant(e) = self.map.entry(u) {
1118 e.insert(m);
1119 Ok(true)
1120 } else {
1121 Ok(false)
1122 }
1123 }
1124 _ => Err(OperationError::InvalidValueState),
1125 }
1126 }
1127
1128 fn clear(&mut self) {
1129 self.map.clear();
1130 }
1131
1132 fn remove(&mut self, pv: &PartialValue, _cid: &Cid) -> bool {
1133 match pv {
1134 PartialValue::Refer(u) => self.map.remove(u).is_some(),
1135 _ => false,
1136 }
1137 }
1138
1139 fn purge(&mut self, _cid: &Cid) -> bool {
1140 true
1142 }
1143
1144 fn contains(&self, pv: &PartialValue) -> bool {
1145 match pv {
1146 PartialValue::Refer(u) => self.map.contains_key(u),
1147 _ => false,
1148 }
1149 }
1150
1151 fn substring(&self, _pv: &PartialValue) -> bool {
1152 false
1153 }
1154
1155 fn startswith(&self, _pv: &PartialValue) -> bool {
1156 false
1157 }
1158
1159 fn endswith(&self, _pv: &PartialValue) -> bool {
1160 false
1161 }
1162
1163 fn lessthan(&self, _pv: &PartialValue) -> bool {
1164 false
1165 }
1166
1167 fn len(&self) -> usize {
1168 self.map.len()
1169 }
1170
1171 fn generate_idx_eq_keys(&self) -> Vec<String> {
1172 self.map
1173 .keys()
1174 .map(|u| u.as_hyphenated().to_string())
1175 .collect()
1176 }
1177
1178 fn syntax(&self) -> SyntaxType {
1179 SyntaxType::ApiToken
1180 }
1181
1182 fn validate(&self, _schema_attr: &SchemaAttribute) -> bool {
1183 self.map.iter().all(|(_, at)| {
1184 Value::validate_str_escapes(&at.label) && Value::validate_singleline(&at.label)
1185 })
1186 }
1187
1188 fn to_proto_string_clone_iter(&self) -> Box<dyn Iterator<Item = String> + '_> {
1189 Box::new(
1190 self.map
1191 .iter()
1192 .map(|(u, m)| format!("{}: {:?}", uuid_to_proto_string(*u), m)),
1193 )
1194 }
1195
1196 fn to_scim_value(&self) -> Option<ScimResolveStatus> {
1197 Some(ScimResolveStatus::Resolved(ScimValueKanidm::from(
1198 self.map
1199 .iter()
1200 .map(|(token_id, token)| ScimApiToken {
1201 id: *token_id,
1202 label: token.label.clone(),
1203 issued_by: Uuid::from(&token.issued_by),
1204 issued_at: token.issued_at,
1205 expires: token.expiry,
1206 scope: token.scope.to_string(),
1207 })
1208 .collect::<Vec<_>>(),
1209 )))
1210 }
1211
1212 fn to_db_valueset_v2(&self) -> DbValueSetV2 {
1213 DbValueSetV2::ApiToken(
1214 self.map
1215 .iter()
1216 .map(|(u, m)| DbValueApiToken::V1 {
1217 refer: *u,
1218 label: m.label.clone(),
1219 expiry: m.expiry.map(|odt| {
1220 debug_assert_eq!(odt.offset(), time::UtcOffset::UTC);
1221 #[allow(clippy::expect_used)]
1222 odt.format(&Rfc3339)
1223 .expect("Failed to format timestamp into RFC3339")
1224 }),
1225 issued_at: {
1226 debug_assert_eq!(m.issued_at.offset(), time::UtcOffset::UTC);
1227 #[allow(clippy::expect_used)]
1228 m.issued_at
1229 .format(&Rfc3339)
1230 .expect("Failed to format timestamp into RFC3339")
1231 },
1232 issued_by: match m.issued_by {
1233 IdentityId::Internal => DbValueIdentityId::V1Internal,
1234 IdentityId::User(u) => DbValueIdentityId::V1Uuid(u),
1235 IdentityId::Synch(u) => DbValueIdentityId::V1Sync(u),
1236 },
1237 scope: match m.scope {
1238 ApiTokenScope::ReadOnly => DbValueApiTokenScopeV1::ReadOnly,
1239 ApiTokenScope::ReadWrite => DbValueApiTokenScopeV1::ReadWrite,
1240 ApiTokenScope::Synchronise => DbValueApiTokenScopeV1::Synchronise,
1241 },
1242 })
1243 .collect(),
1244 )
1245 }
1246
1247 fn to_partialvalue_iter(&self) -> Box<dyn Iterator<Item = PartialValue> + '_> {
1248 Box::new(self.map.keys().cloned().map(PartialValue::Refer))
1249 }
1250
1251 fn to_value_iter(&self) -> Box<dyn Iterator<Item = Value> + '_> {
1252 Box::new(self.map.iter().map(|(u, m)| Value::ApiToken(*u, m.clone())))
1253 }
1254
1255 fn equal(&self, other: &ValueSet) -> bool {
1256 if let Some(other) = other.as_apitoken_map() {
1257 &self.map == other
1258 } else {
1259 debug_assert!(false);
1260 false
1261 }
1262 }
1263
1264 fn merge(&mut self, other: &ValueSet) -> Result<(), OperationError> {
1265 if let Some(b) = other.as_apitoken_map() {
1266 mergemaps!(self.map, b)
1267 } else {
1268 debug_assert!(false);
1269 Err(OperationError::InvalidValueState)
1270 }
1271 }
1272
1273 fn as_apitoken_map(&self) -> Option<&BTreeMap<Uuid, ApiToken>> {
1274 Some(&self.map)
1275 }
1276
1277 fn as_ref_uuid_iter(&self) -> Option<Box<dyn Iterator<Item = Uuid> + '_>> {
1278 Some(Box::new(self.map.keys().copied()))
1280 }
1281}
1282
1283#[cfg(test)]
1284mod tests {
1285 use super::{ValueSetOauth2Session, ValueSetSession, SESSION_MAXIMUM};
1286 use crate::prelude::ValueSet;
1287 use crate::prelude::{IdentityId, SessionScope, Uuid};
1288 use crate::repl::cid::Cid;
1289 use crate::value::{AuthType, Oauth2Session, Session, SessionState};
1290 use time::OffsetDateTime;
1291
1292 #[test]
1293 fn test_valueset_session_purge() {
1294 let s_uuid = Uuid::new_v4();
1295
1296 let mut vs: ValueSet = ValueSetSession::new(
1297 s_uuid,
1298 Session {
1299 label: "hacks".to_string(),
1300 state: SessionState::NeverExpires,
1301 issued_at: OffsetDateTime::now_utc(),
1302 issued_by: IdentityId::Internal,
1303 cred_id: Uuid::new_v4(),
1304 scope: SessionScope::ReadOnly,
1305 type_: AuthType::Passkey,
1306 },
1307 );
1308
1309 let zero_cid = Cid::new_zero();
1310
1311 vs.purge(&zero_cid);
1313
1314 assert_eq!(vs.len(), 1);
1315
1316 let session = vs
1317 .as_session_map()
1318 .and_then(|map| map.get(&s_uuid))
1319 .expect("Unable to locate session");
1320
1321 assert_eq!(session.state, SessionState::RevokedAt(zero_cid));
1322 }
1323
1324 #[test]
1325 fn test_valueset_session_merge_left() {
1326 let s_uuid = Uuid::new_v4();
1327 let zero_cid = Cid::new_zero();
1328
1329 let mut vs_a: ValueSet = ValueSetSession::new(
1330 s_uuid,
1331 Session {
1332 label: "hacks".to_string(),
1333 state: SessionState::NeverExpires,
1334 issued_at: OffsetDateTime::now_utc(),
1335 issued_by: IdentityId::Internal,
1336 cred_id: Uuid::new_v4(),
1337 scope: SessionScope::ReadOnly,
1338 type_: AuthType::Passkey,
1339 },
1340 );
1341
1342 let vs_b: ValueSet = ValueSetSession::new(
1343 s_uuid,
1344 Session {
1345 label: "hacks".to_string(),
1346 state: SessionState::RevokedAt(zero_cid.clone()),
1347 issued_at: OffsetDateTime::now_utc(),
1348 issued_by: IdentityId::Internal,
1349 cred_id: Uuid::new_v4(),
1350 scope: SessionScope::ReadOnly,
1351 type_: AuthType::Passkey,
1352 },
1353 );
1354
1355 vs_a.merge(&vs_b).expect("failed to merge");
1356
1357 let session = vs_a
1358 .as_session_map()
1359 .and_then(|map| map.get(&s_uuid))
1360 .expect("Unable to locate session");
1361
1362 assert_eq!(session.state, SessionState::RevokedAt(zero_cid));
1363 }
1364
1365 #[test]
1366 fn test_valueset_session_merge_right() {
1367 let s_uuid = Uuid::new_v4();
1368 let zero_cid = Cid::new_zero();
1369
1370 let vs_a: ValueSet = ValueSetSession::new(
1371 s_uuid,
1372 Session {
1373 label: "hacks".to_string(),
1374 state: SessionState::NeverExpires,
1375 issued_at: OffsetDateTime::now_utc(),
1376 issued_by: IdentityId::Internal,
1377 cred_id: Uuid::new_v4(),
1378 scope: SessionScope::ReadOnly,
1379 type_: AuthType::Passkey,
1380 },
1381 );
1382
1383 let mut vs_b: ValueSet = ValueSetSession::new(
1384 s_uuid,
1385 Session {
1386 label: "hacks".to_string(),
1387 state: SessionState::RevokedAt(zero_cid.clone()),
1388 issued_at: OffsetDateTime::now_utc(),
1389 issued_by: IdentityId::Internal,
1390 cred_id: Uuid::new_v4(),
1391 scope: SessionScope::ReadOnly,
1392 type_: AuthType::Passkey,
1393 },
1394 );
1395
1396 vs_b.merge(&vs_a).expect("failed to merge");
1398
1399 let session = vs_b
1400 .as_session_map()
1401 .and_then(|map| map.get(&s_uuid))
1402 .expect("Unable to locate session");
1403
1404 assert_eq!(session.state, SessionState::RevokedAt(zero_cid));
1405 }
1406
1407 #[test]
1408 fn test_valueset_session_repl_merge_left() {
1409 let s_uuid = Uuid::new_v4();
1410 let r_uuid = Uuid::new_v4();
1411 let zero_cid = Cid::new_zero();
1412 let one_cid = Cid::new_count(1);
1413
1414 let vs_a: ValueSet = ValueSetSession::new(
1415 s_uuid,
1416 Session {
1417 label: "hacks".to_string(),
1418 state: SessionState::NeverExpires,
1419 issued_at: OffsetDateTime::now_utc(),
1420 issued_by: IdentityId::Internal,
1421 cred_id: Uuid::new_v4(),
1422 scope: SessionScope::ReadOnly,
1423 type_: AuthType::Passkey,
1424 },
1425 );
1426
1427 let vs_b: ValueSet = ValueSetSession::from_iter([
1428 (
1429 s_uuid,
1430 Session {
1431 label: "hacks".to_string(),
1432 state: SessionState::RevokedAt(one_cid.clone()),
1433 issued_at: OffsetDateTime::now_utc(),
1434 issued_by: IdentityId::Internal,
1435 cred_id: Uuid::new_v4(),
1436 scope: SessionScope::ReadOnly,
1437 type_: AuthType::Passkey,
1438 },
1439 ),
1440 (
1441 r_uuid,
1442 Session {
1443 label: "hacks".to_string(),
1444 state: SessionState::RevokedAt(zero_cid.clone()),
1445 issued_at: OffsetDateTime::now_utc(),
1446 issued_by: IdentityId::Internal,
1447 cred_id: Uuid::new_v4(),
1448 scope: SessionScope::ReadOnly,
1449 type_: AuthType::Passkey,
1450 },
1451 ),
1452 ])
1453 .expect("Unable to build valueset session");
1454
1455 let r_vs = vs_a
1456 .repl_merge_valueset(&vs_b, &one_cid)
1457 .expect("failed to merge");
1458
1459 let sessions = r_vs.as_session_map().expect("Unable to locate sessions");
1460
1461 let session = sessions.get(&s_uuid).expect("Unable to locate session");
1462
1463 assert_eq!(session.state, SessionState::RevokedAt(one_cid));
1464
1465 assert!(!sessions.contains_key(&r_uuid));
1466 }
1467
1468 #[test]
1469 fn test_valueset_session_repl_merge_right() {
1470 let s_uuid = Uuid::new_v4();
1471 let r_uuid = Uuid::new_v4();
1472 let zero_cid = Cid::new_zero();
1473 let one_cid = Cid::new_count(1);
1474
1475 let vs_a: ValueSet = ValueSetSession::new(
1476 s_uuid,
1477 Session {
1478 label: "hacks".to_string(),
1479 state: SessionState::NeverExpires,
1480 issued_at: OffsetDateTime::now_utc(),
1481 issued_by: IdentityId::Internal,
1482 cred_id: Uuid::new_v4(),
1483 scope: SessionScope::ReadOnly,
1484 type_: AuthType::Passkey,
1485 },
1486 );
1487
1488 let vs_b: ValueSet = ValueSetSession::from_iter([
1489 (
1490 s_uuid,
1491 Session {
1492 label: "hacks".to_string(),
1493 state: SessionState::RevokedAt(one_cid.clone()),
1494 issued_at: OffsetDateTime::now_utc(),
1495 issued_by: IdentityId::Internal,
1496 cred_id: Uuid::new_v4(),
1497 scope: SessionScope::ReadOnly,
1498 type_: AuthType::Passkey,
1499 },
1500 ),
1501 (
1502 r_uuid,
1503 Session {
1504 label: "hacks".to_string(),
1505 state: SessionState::RevokedAt(zero_cid.clone()),
1506 issued_at: OffsetDateTime::now_utc(),
1507 issued_by: IdentityId::Internal,
1508 cred_id: Uuid::new_v4(),
1509 scope: SessionScope::ReadOnly,
1510 type_: AuthType::Passkey,
1511 },
1512 ),
1513 ])
1514 .expect("Unable to build valueset session");
1515
1516 let r_vs = vs_b
1518 .repl_merge_valueset(&vs_a, &one_cid)
1519 .expect("failed to merge");
1520
1521 let sessions = r_vs.as_session_map().expect("Unable to locate sessions");
1522
1523 let session = sessions.get(&s_uuid).expect("Unable to locate session");
1524
1525 assert_eq!(session.state, SessionState::RevokedAt(one_cid));
1526
1527 assert!(!sessions.contains_key(&r_uuid));
1528 }
1529
1530 #[test]
1531 fn test_valueset_session_repl_trim() {
1532 let zero_uuid = Uuid::new_v4();
1533 let zero_cid = Cid::new_zero();
1534 let one_uuid = Uuid::new_v4();
1535 let one_cid = Cid::new_count(1);
1536 let two_uuid = Uuid::new_v4();
1537 let two_cid = Cid::new_count(2);
1538
1539 let mut vs_a: ValueSet = ValueSetSession::from_iter([
1540 (
1541 zero_uuid,
1542 Session {
1543 state: SessionState::RevokedAt(zero_cid),
1544 label: "hacks".to_string(),
1545 issued_at: OffsetDateTime::now_utc(),
1546 issued_by: IdentityId::Internal,
1547 cred_id: Uuid::new_v4(),
1548 scope: SessionScope::ReadOnly,
1549 type_: AuthType::Passkey,
1550 },
1551 ),
1552 (
1553 one_uuid,
1554 Session {
1555 state: SessionState::RevokedAt(one_cid),
1556 label: "hacks".to_string(),
1557 issued_at: OffsetDateTime::now_utc(),
1558 issued_by: IdentityId::Internal,
1559 cred_id: Uuid::new_v4(),
1560 scope: SessionScope::ReadOnly,
1561 type_: AuthType::Passkey,
1562 },
1563 ),
1564 (
1565 two_uuid,
1566 Session {
1567 state: SessionState::RevokedAt(two_cid.clone()),
1568 label: "hacks".to_string(),
1569 issued_at: OffsetDateTime::now_utc(),
1570 issued_by: IdentityId::Internal,
1571 cred_id: Uuid::new_v4(),
1572 scope: SessionScope::ReadOnly,
1573 type_: AuthType::Passkey,
1574 },
1575 ),
1576 ])
1577 .unwrap();
1578
1579 vs_a.trim(&two_cid);
1580
1581 let sessions = vs_a.as_session_map().expect("Unable to locate session");
1582
1583 assert!(!sessions.contains_key(&zero_uuid));
1584 assert!(!sessions.contains_key(&one_uuid));
1585 assert!(sessions.contains_key(&two_uuid));
1586 }
1587
1588 #[test]
1589 fn test_valueset_session_limit_trim() {
1590 let zero_uuid = Uuid::new_v4();
1592 let zero_cid = Cid::new_zero();
1593 let issued_at = OffsetDateTime::UNIX_EPOCH;
1594
1595 let session_iter = std::iter::once((
1596 zero_uuid,
1597 Session {
1598 state: SessionState::NeverExpires,
1599 label: "hacks".to_string(),
1600 issued_at,
1601 issued_by: IdentityId::Internal,
1602 cred_id: Uuid::new_v4(),
1603 scope: SessionScope::ReadOnly,
1604 type_: AuthType::Passkey,
1605 },
1606 ))
1607 .chain((0..SESSION_MAXIMUM).map(|_| {
1608 (
1609 Uuid::new_v4(),
1610 Session {
1611 state: SessionState::NeverExpires,
1612 label: "hacks".to_string(),
1613 issued_at: OffsetDateTime::now_utc(),
1614 issued_by: IdentityId::Internal,
1615 cred_id: Uuid::new_v4(),
1616 scope: SessionScope::ReadOnly,
1617 type_: AuthType::Passkey,
1618 },
1619 )
1620 }));
1621
1622 let mut vs_a: ValueSet = ValueSetSession::from_iter(session_iter).unwrap();
1623
1624 assert!(vs_a.len() > SESSION_MAXIMUM);
1625
1626 vs_a.trim(&zero_cid);
1627
1628 assert_eq!(vs_a.len(), SESSION_MAXIMUM);
1629
1630 let sessions = vs_a.as_session_map().expect("Unable to access sessions");
1631
1632 assert!(!sessions.contains_key(&zero_uuid));
1633 }
1634
1635 #[test]
1636 fn test_valueset_oauth2_session_purge() {
1637 let s_uuid = Uuid::new_v4();
1638 let mut vs: ValueSet = ValueSetOauth2Session::new(
1639 s_uuid,
1640 Oauth2Session {
1641 state: SessionState::NeverExpires,
1642 issued_at: OffsetDateTime::now_utc(),
1643 parent: Some(Uuid::new_v4()),
1644 rs_uuid: Uuid::new_v4(),
1645 },
1646 );
1647
1648 let zero_cid = Cid::new_zero();
1649
1650 vs.purge(&zero_cid);
1652
1653 assert_eq!(vs.len(), 1);
1654
1655 let session = vs
1656 .as_oauth2session_map()
1657 .and_then(|map| map.get(&s_uuid))
1658 .expect("Unable to locate session");
1659
1660 assert_eq!(session.state, SessionState::RevokedAt(zero_cid));
1661 }
1662
1663 #[test]
1664 fn test_valueset_oauth2_session_merge_left() {
1665 let s_uuid = Uuid::new_v4();
1666 let zero_cid = Cid::new_zero();
1667
1668 let mut vs_a: ValueSet = ValueSetOauth2Session::new(
1669 s_uuid,
1670 Oauth2Session {
1671 state: SessionState::NeverExpires,
1672 issued_at: OffsetDateTime::now_utc(),
1673 parent: Some(Uuid::new_v4()),
1674 rs_uuid: Uuid::new_v4(),
1675 },
1676 );
1677
1678 let vs_b: ValueSet = ValueSetOauth2Session::new(
1679 s_uuid,
1680 Oauth2Session {
1681 state: SessionState::RevokedAt(zero_cid.clone()),
1682 issued_at: OffsetDateTime::now_utc(),
1683 parent: Some(Uuid::new_v4()),
1684 rs_uuid: Uuid::new_v4(),
1685 },
1686 );
1687
1688 vs_a.merge(&vs_b).expect("failed to merge");
1689
1690 let session = vs_a
1691 .as_oauth2session_map()
1692 .and_then(|map| map.get(&s_uuid))
1693 .expect("Unable to locate session");
1694
1695 assert_eq!(session.state, SessionState::RevokedAt(zero_cid));
1696 }
1697
1698 #[test]
1699 fn test_valueset_oauth2_session_merge_right() {
1700 let s_uuid = Uuid::new_v4();
1701 let zero_cid = Cid::new_zero();
1702
1703 let vs_a: ValueSet = ValueSetOauth2Session::new(
1704 s_uuid,
1705 Oauth2Session {
1706 state: SessionState::NeverExpires,
1707 issued_at: OffsetDateTime::now_utc(),
1708 parent: Some(Uuid::new_v4()),
1709 rs_uuid: Uuid::new_v4(),
1710 },
1711 );
1712
1713 let mut vs_b: ValueSet = ValueSetOauth2Session::new(
1714 s_uuid,
1715 Oauth2Session {
1716 state: SessionState::RevokedAt(zero_cid.clone()),
1717 issued_at: OffsetDateTime::now_utc(),
1718 parent: Some(Uuid::new_v4()),
1719 rs_uuid: Uuid::new_v4(),
1720 },
1721 );
1722
1723 vs_b.merge(&vs_a).expect("failed to merge");
1725
1726 let session = vs_b
1727 .as_oauth2session_map()
1728 .and_then(|map| map.get(&s_uuid))
1729 .expect("Unable to locate session");
1730
1731 assert_eq!(session.state, SessionState::RevokedAt(zero_cid));
1732 }
1733
1734 #[test]
1735 fn test_valueset_oauth2_session_repl_merge_left() {
1736 let s_uuid = Uuid::new_v4();
1737 let r_uuid = Uuid::new_v4();
1738 let zero_cid = Cid::new_zero();
1739 let one_cid = Cid::new_count(1);
1740
1741 let vs_a: ValueSet = ValueSetOauth2Session::new(
1742 s_uuid,
1743 Oauth2Session {
1744 state: SessionState::NeverExpires,
1745 issued_at: OffsetDateTime::now_utc(),
1746 parent: Some(Uuid::new_v4()),
1747 rs_uuid: Uuid::new_v4(),
1748 },
1749 );
1750
1751 let vs_b: ValueSet = ValueSetOauth2Session::from_iter([
1752 (
1753 s_uuid,
1754 Oauth2Session {
1755 state: SessionState::RevokedAt(one_cid.clone()),
1756 issued_at: OffsetDateTime::now_utc(),
1757 parent: Some(Uuid::new_v4()),
1758 rs_uuid: Uuid::new_v4(),
1759 },
1760 ),
1761 (
1762 r_uuid,
1763 Oauth2Session {
1764 state: SessionState::RevokedAt(zero_cid.clone()),
1765 issued_at: OffsetDateTime::now_utc(),
1766 parent: Some(Uuid::new_v4()),
1767 rs_uuid: Uuid::new_v4(),
1768 },
1769 ),
1770 ])
1771 .expect("Unable to build valueset oauth2 session");
1772
1773 let r_vs = vs_a
1774 .repl_merge_valueset(&vs_b, &one_cid)
1775 .expect("failed to merge");
1776
1777 let sessions = r_vs
1778 .as_oauth2session_map()
1779 .expect("Unable to locate sessions");
1780
1781 let session = sessions.get(&s_uuid).expect("Unable to locate session");
1782
1783 assert_eq!(session.state, SessionState::RevokedAt(one_cid));
1784
1785 assert!(!sessions.contains_key(&r_uuid));
1786 }
1787
1788 #[test]
1789 fn test_valueset_oauth2_session_repl_merge_right() {
1790 let s_uuid = Uuid::new_v4();
1791 let r_uuid = Uuid::new_v4();
1792 let zero_cid = Cid::new_zero();
1793 let one_cid = Cid::new_count(1);
1794
1795 let vs_a: ValueSet = ValueSetOauth2Session::new(
1796 s_uuid,
1797 Oauth2Session {
1798 state: SessionState::NeverExpires,
1799 issued_at: OffsetDateTime::now_utc(),
1800 parent: Some(Uuid::new_v4()),
1801 rs_uuid: Uuid::new_v4(),
1802 },
1803 );
1804
1805 let vs_b: ValueSet = ValueSetOauth2Session::from_iter([
1806 (
1807 s_uuid,
1808 Oauth2Session {
1809 state: SessionState::RevokedAt(one_cid.clone()),
1810 issued_at: OffsetDateTime::now_utc(),
1811 parent: Some(Uuid::new_v4()),
1812 rs_uuid: Uuid::new_v4(),
1813 },
1814 ),
1815 (
1816 r_uuid,
1817 Oauth2Session {
1818 state: SessionState::RevokedAt(zero_cid.clone()),
1819 issued_at: OffsetDateTime::now_utc(),
1820 parent: Some(Uuid::new_v4()),
1821 rs_uuid: Uuid::new_v4(),
1822 },
1823 ),
1824 ])
1825 .expect("Unable to build valueset oauth2 session");
1826
1827 let r_vs = vs_b
1829 .repl_merge_valueset(&vs_a, &one_cid)
1830 .expect("failed to merge");
1831
1832 let sessions = r_vs
1833 .as_oauth2session_map()
1834 .expect("Unable to locate sessions");
1835
1836 let session = sessions.get(&s_uuid).expect("Unable to locate session");
1837
1838 assert_eq!(session.state, SessionState::RevokedAt(one_cid));
1839
1840 assert!(!sessions.contains_key(&r_uuid));
1841 }
1842
1843 #[test]
1844 fn test_valueset_oauth2_session_repl_trim() {
1845 let zero_uuid = Uuid::new_v4();
1846 let zero_cid = Cid::new_zero();
1847 let one_uuid = Uuid::new_v4();
1848 let one_cid = Cid::new_count(1);
1849 let two_uuid = Uuid::new_v4();
1850 let two_cid = Cid::new_count(2);
1851
1852 let mut vs_a: ValueSet = ValueSetOauth2Session::from_iter([
1853 (
1854 zero_uuid,
1855 Oauth2Session {
1856 state: SessionState::RevokedAt(zero_cid),
1857 issued_at: OffsetDateTime::now_utc(),
1858 parent: Some(Uuid::new_v4()),
1859 rs_uuid: Uuid::new_v4(),
1860 },
1861 ),
1862 (
1863 one_uuid,
1864 Oauth2Session {
1865 state: SessionState::RevokedAt(one_cid),
1866 issued_at: OffsetDateTime::now_utc(),
1867 parent: Some(Uuid::new_v4()),
1868 rs_uuid: Uuid::new_v4(),
1869 },
1870 ),
1871 (
1872 two_uuid,
1873 Oauth2Session {
1874 state: SessionState::RevokedAt(two_cid.clone()),
1875 issued_at: OffsetDateTime::now_utc(),
1876 parent: Some(Uuid::new_v4()),
1877 rs_uuid: Uuid::new_v4(),
1878 },
1879 ),
1880 ])
1881 .unwrap();
1882
1883 vs_a.trim(&two_cid);
1884
1885 let sessions = vs_a
1886 .as_oauth2session_map()
1887 .expect("Unable to locate session");
1888
1889 assert!(!sessions.contains_key(&zero_uuid));
1890 assert!(!sessions.contains_key(&one_uuid));
1891 assert!(sessions.contains_key(&two_uuid));
1892 }
1893
1894 #[test]
1895 fn test_scim_session() {
1896 let s_uuid = uuid::uuid!("3a163ca0-4762-4620-a188-06b750c84c86");
1897
1898 let vs: ValueSet = ValueSetSession::new(
1899 s_uuid,
1900 Session {
1901 label: "hacks".to_string(),
1902 state: SessionState::NeverExpires,
1903 issued_at: OffsetDateTime::UNIX_EPOCH,
1904 issued_by: IdentityId::Internal,
1905 cred_id: s_uuid,
1906 scope: SessionScope::ReadOnly,
1907 type_: AuthType::Passkey,
1908 },
1909 );
1910
1911 let data = r#"
1912[
1913 {
1914 "authType": "passkey",
1915 "credentialId": "3a163ca0-4762-4620-a188-06b750c84c86",
1916 "issuedAt": "1970-01-01T00:00:00Z",
1917 "issuedBy": "00000000-0000-0000-0000-ffffff000000",
1918 "id": "3a163ca0-4762-4620-a188-06b750c84c86",
1919 "sessionScope": "read_only"
1920 }
1921]
1922 "#;
1923 crate::valueset::scim_json_reflexive(&vs, data);
1924 }
1925
1926 #[test]
1927 fn test_scim_oauth2_session() {
1928 let s_uuid = uuid::uuid!("3a163ca0-4762-4620-a188-06b750c84c86");
1929
1930 let vs: ValueSet = ValueSetOauth2Session::new(
1931 s_uuid,
1932 Oauth2Session {
1933 state: SessionState::NeverExpires,
1934 issued_at: OffsetDateTime::UNIX_EPOCH,
1935 parent: Some(s_uuid),
1936 rs_uuid: s_uuid,
1937 },
1938 );
1939
1940 let data = r#"
1941[
1942 {
1943 "clientId": "3a163ca0-4762-4620-a188-06b750c84c86",
1944 "issuedAt": "1970-01-01T00:00:00Z",
1945 "parentId": "3a163ca0-4762-4620-a188-06b750c84c86",
1946 "id": "3a163ca0-4762-4620-a188-06b750c84c86"
1947 }
1948]
1949 "#;
1950
1951 crate::valueset::scim_json_reflexive(&vs, data);
1952 }
1953}