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