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