1use crate::prelude::*;
2
3use crate::credential::softlock::CredSoftLock;
4use crate::idm::account::Account;
5use crate::idm::authentication::{AuthState, ReauthRequest};
6use crate::idm::authsession::{AuthSession, AuthSessionData};
7use crate::idm::event::AuthResult;
8use crate::idm::server::IdmServerAuthTransaction;
9use crate::utils::uuid_from_duration;
10
11use std::sync::Arc;
14use tokio::sync::Mutex;
15
16use kanidm_proto::v1::AuthIssueSession;
17
18use super::server::CredSoftLockMutex;
19
20impl IdmServerAuthTransaction<'_> {
21 pub async fn reauth_init(
22 &mut self,
23 ident: Identity,
24 issue: AuthIssueSession,
25 ct: Duration,
26 client_auth_info: ClientAuthInfo,
27 reauth_req: ReauthRequest,
28 ) -> Result<AuthResult, OperationError> {
29 let Some(entry) = ident.get_user_entry() else {
32 error!("Ident is not a user and has no entry associated. Unable to proceed.");
33 return Err(OperationError::InvalidState);
34 };
35
36 let (account, account_policy) =
38 Account::try_from_entry_with_policy(entry.as_ref(), &mut self.qs_read)?;
39
40 security_info!(
41 spn = %account.spn(),
42 issue = ?issue,
43 uuid = %account.uuid,
44 "Initiating Re-Authentication Session",
45 );
46
47 let session = entry
49 .get_ava_as_session_map(Attribute::UserAuthTokenSession)
50 .and_then(|sessions| sessions.get(&ident.session_id))
51 .ok_or_else(|| {
52 error!("Ident session is not present in entry. Perhaps replication is delayed?");
53 OperationError::InvalidState
54 })?;
55
56 match session.scope {
57 SessionScope::PrivilegeCapable => {
58 }
60 SessionScope::ReadOnly | SessionScope::ReadWrite | SessionScope::Synchronise => {
61 error!("Session scope is not PrivilegeCapable and can not be used in re-auth.");
63 return Err(OperationError::SessionMayNotReauth);
64 }
65 };
66
67 let session_cred_id = session.cred_id;
69
70 let sessionid = uuid_from_duration(ct, self.sid);
75
76 let _session_ticket = self.session_ticket.acquire().await;
78
79 let maybe_slock = account
81 .primary_cred_uuid_and_policy()
82 .and_then(|(cred_uuid, policy)| {
83 if cred_uuid == session_cred_id {
92 let mut softlock_write = self.softlocks.write();
93 let slock_ref: CredSoftLockMutex =
94 if let Some(slock_ref) = softlock_write.get(&cred_uuid) {
95 slock_ref.clone()
96 } else {
97 let slock = Arc::new(Mutex::new(CredSoftLock::new(policy)));
99 softlock_write.insert(cred_uuid, slock.clone());
100 slock
101 };
102 softlock_write.commit();
103 Some(slock_ref)
104 } else {
105 None
106 }
107 });
108
109 let is_valid = if let Some(slock_ref) = maybe_slock {
114 let mut slock = slock_ref.lock().await;
115 slock.apply_time_step(ct, None);
116 slock.is_valid()
117 } else {
118 true
119 };
120
121 if !is_valid {
122 warn!(
123 "Credential {:?} is currently softlocked, unable to proceed",
124 session_cred_id
125 );
126 return Ok(AuthResult {
127 sessionid: ident.get_session_id(),
128 state: AuthState::Denied("Credential is temporarily locked".to_string()),
129 });
130 }
131
132 let asd: AuthSessionData = AuthSessionData {
134 account,
135 account_policy,
136 issue,
137 webauthn: self.webauthn,
138 ct,
139 client_auth_info,
140 oauth2_client_provider: None,
141 };
142
143 let domain_keys = self.qs_read.get_domain_key_object_handle()?;
144
145 let (auth_session, state) = AuthSession::new_reauth(
146 asd,
147 ident.session_id,
148 session,
149 session_cred_id,
150 domain_keys,
151 &reauth_req,
152 );
153
154 match auth_session {
156 Some(auth_session) => {
157 let mut session_write = self.sessions.write();
158 if session_write.contains_key(&sessionid) {
159 Err(OperationError::InvalidSessionState)
162 } else {
163 session_write.insert(sessionid, Arc::new(Mutex::new(auth_session)));
164 debug_assert!(session_write.get(&sessionid).is_some());
166 Ok(())
167 }?;
168 session_write.commit();
169 }
170 None => {
171 security_info!("Authentication Session Unable to begin");
172 }
173 };
174
175 Ok(AuthResult { sessionid, state })
176 }
177}
178
179#[cfg(test)]
180mod tests {
181 use crate::credential::totp::Totp;
182 use crate::idm::audit::AuditEvent;
183 use crate::idm::authentication::{AuthState, ReauthRequest};
184 use crate::idm::credupdatesession::{InitCredentialUpdateEvent, MfaRegStateStatus};
185 use crate::idm::delayed::DelayedAction;
186 use crate::idm::event::{AuthEvent, AuthResult};
187 use crate::idm::server::IdmServerTransaction;
188 use crate::prelude::*;
189 use compact_jwt::JwsCompact;
190 use kanidm_proto::v1::{AuthAllowed, AuthIssueSession, AuthMech};
191 use uuid::uuid;
192 use webauthn_authenticator_rs::softpasskey::SoftPasskey;
193 use webauthn_authenticator_rs::WebauthnAuthenticator;
194
195 const TESTPERSON_UUID: Uuid = uuid!("cf231fea-1a8f-4410-a520-fd9b1a379c86");
196
197 async fn setup_testaccount(idms: &IdmServer, ct: Duration) {
198 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
199
200 let e2 = entry_init!(
201 (Attribute::Class, EntryClass::Object.to_value()),
202 (Attribute::Class, EntryClass::Account.to_value()),
203 (Attribute::Class, EntryClass::Person.to_value()),
204 (Attribute::Name, Value::new_iname("testperson")),
205 (Attribute::Uuid, Value::Uuid(TESTPERSON_UUID)),
206 (Attribute::Description, Value::new_utf8s("testperson")),
207 (Attribute::DisplayName, Value::new_utf8s("testperson"))
208 );
209
210 let cr = idms_prox_write.qs_write.internal_create(vec![e2]);
211 assert!(cr.is_ok());
212 assert!(idms_prox_write.commit().is_ok());
213 }
214
215 async fn setup_testaccount_passkey(idms: &IdmServer, ct: Duration) -> SoftPasskey {
216 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
217 let testperson = idms_prox_write
218 .qs_write
219 .internal_search_uuid(TESTPERSON_UUID)
220 .expect("failed");
221 let (cust, _c_status) = idms_prox_write
222 .init_credential_update(
223 &InitCredentialUpdateEvent::new_impersonate_entry(testperson),
224 ct,
225 )
226 .expect("Failed to begin credential update.");
227 idms_prox_write.commit().expect("Failed to commit txn");
228
229 let cutxn = idms.cred_update_transaction().await.unwrap();
232 let origin = cutxn.get_origin().clone();
233
234 let mut wa = SoftPasskey::new(true);
235
236 let c_status = cutxn
237 .credential_passkey_init(&cust, ct)
238 .expect("Failed to initiate passkey registration");
239
240 let passkey_chal = match c_status.mfaregstate() {
241 MfaRegStateStatus::Passkey(c) => Some(c),
242 _ => None,
243 }
244 .expect("Unable to access passkey challenge, invalid state");
245
246 let passkey_resp = wa
247 .do_registration(origin.clone(), passkey_chal.clone())
248 .expect("Failed to create soft passkey");
249
250 let label = "softtoken".to_string();
252 let c_status = cutxn
253 .credential_passkey_finish(&cust, ct, label, &passkey_resp)
254 .expect("Failed to initiate passkey registration");
255
256 assert!(c_status.can_commit());
257
258 drop(cutxn);
259 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
260
261 idms_prox_write
262 .commit_credential_update(&cust, ct)
263 .expect("Failed to commit credential update.");
264
265 idms_prox_write.commit().expect("Failed to commit txn");
266
267 wa
268 }
269
270 async fn setup_testaccount_password_totp(idms: &IdmServer, ct: Duration) -> (String, Totp) {
271 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
272 let testperson = idms_prox_write
273 .qs_write
274 .internal_search_uuid(TESTPERSON_UUID)
275 .expect("failed");
276 let (cust, _c_status) = idms_prox_write
277 .init_credential_update(
278 &InitCredentialUpdateEvent::new_impersonate_entry(testperson),
279 ct,
280 )
281 .expect("Failed to begin credential update.");
282 idms_prox_write.commit().expect("Failed to commit txn");
283
284 let cutxn = idms.cred_update_transaction().await.unwrap();
285
286 let pw = crate::utils::password_from_random();
287
288 let _c_status = cutxn
289 .credential_primary_set_password(&cust, ct, &pw)
290 .expect("Failed to update the primary cred password");
291
292 let c_status = cutxn
293 .credential_primary_init_totp(&cust, ct)
294 .expect("Failed to update the primary cred password");
295
296 let totp_token: Totp = match c_status.mfaregstate() {
298 MfaRegStateStatus::TotpCheck(secret) => Some(secret.clone().try_into().unwrap()),
299
300 _ => None,
301 }
302 .expect("Unable to retrieve totp token, invalid state.");
303
304 trace!(?totp_token);
305 let chal = totp_token
306 .do_totp_duration_from_epoch(&ct)
307 .expect("Failed to perform totp step");
308
309 let c_status = cutxn
310 .credential_primary_check_totp(&cust, ct, chal, "totp")
311 .expect("Failed to update the primary cred password");
312
313 assert!(matches!(c_status.mfaregstate(), MfaRegStateStatus::None));
314 assert!(c_status.can_commit());
315
316 drop(cutxn);
317 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
318
319 idms_prox_write
320 .commit_credential_update(&cust, ct)
321 .expect("Failed to commit credential update.");
322
323 idms_prox_write.commit().expect("Failed to commit txn");
324
325 (pw, totp_token)
326 }
327
328 async fn auth_passkey(
329 idms: &IdmServer,
330 ct: Duration,
331 wa: &mut SoftPasskey,
332 idms_delayed: &mut IdmServerDelayed,
333 ) -> Option<JwsCompact> {
334 let mut idms_auth = idms.auth().await.unwrap();
335 let origin = idms_auth.get_origin().clone();
336
337 let auth_init = AuthEvent::named_init("testperson");
338
339 let r1 = idms_auth
340 .auth(&auth_init, ct, Source::Internal.into())
341 .await;
342 let ar = r1.unwrap();
343 let AuthResult { sessionid, state } = ar;
344
345 if !matches!(state, AuthState::Choose(_)) {
346 debug!("Can't proceed - {:?}", state);
347 return None;
348 };
349
350 let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::Passkey);
351
352 let r2 = idms_auth
353 .auth(&auth_begin, ct, Source::Internal.into())
354 .await;
355 let ar = r2.unwrap();
356 let AuthResult { sessionid, state } = ar;
357
358 trace!(?state);
359
360 let rcr = match state {
361 AuthState::Continue(mut allowed) => match allowed.pop() {
362 Some(AuthAllowed::Passkey(rcr)) => rcr,
363 _ => unreachable!(),
364 },
365 _ => unreachable!(),
366 };
367
368 trace!(?rcr);
369
370 let resp = wa
371 .do_authentication(origin, rcr)
372 .expect("failed to use softtoken to authenticate");
373
374 let passkey_step = AuthEvent::cred_step_passkey(sessionid, resp);
375
376 let r3 = idms_auth
377 .auth(&passkey_step, ct, Source::Internal.into())
378 .await;
379 debug!("r3 ==> {:?}", r3);
380 idms_auth.commit().expect("Must not fail");
381
382 match r3 {
383 Ok(AuthResult {
384 sessionid: _,
385 state: AuthState::Success(token, AuthIssueSession::Token),
386 }) => {
387 let da = idms_delayed.try_recv().expect("invalid");
389 assert!(matches!(da, DelayedAction::WebauthnCounterIncrement(_)));
390 let r = idms.delayed_action(ct, da).await;
391 assert!(r.is_ok());
392
393 let da = idms_delayed.try_recv().expect("invalid");
395 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
396 let r = idms.delayed_action(ct, da).await;
399 assert!(r.is_ok());
400
401 Some(*token)
402 }
403 _ => None,
404 }
405 }
406
407 async fn auth_password_totp(
408 idms: &IdmServer,
409 ct: Duration,
410 pw: &str,
411 token: &Totp,
412 idms_delayed: &mut IdmServerDelayed,
413 ) -> Option<JwsCompact> {
414 let mut idms_auth = idms.auth().await.unwrap();
415
416 let auth_init = AuthEvent::named_init("testperson");
417
418 let r1 = idms_auth
419 .auth(&auth_init, ct, Source::Internal.into())
420 .await;
421 let ar = r1.unwrap();
422 let AuthResult { sessionid, state } = ar;
423
424 if !matches!(state, AuthState::Choose(_)) {
425 debug!("Can't proceed - {:?}", state);
426 return None;
427 };
428
429 let auth_begin = AuthEvent::begin_mech(sessionid, AuthMech::PasswordTotp);
430
431 let r2 = idms_auth
432 .auth(&auth_begin, ct, Source::Internal.into())
433 .await;
434 let ar = r2.unwrap();
435 let AuthResult { sessionid, state } = ar;
436
437 assert!(matches!(state, AuthState::Continue(_)));
438
439 let totp = token
440 .do_totp_duration_from_epoch(&ct)
441 .expect("Failed to perform totp step");
442
443 let totp_step = AuthEvent::cred_step_totp(sessionid, totp);
444 let r2 = idms_auth
445 .auth(&totp_step, ct, Source::Internal.into())
446 .await;
447 let ar = r2.unwrap();
448 let AuthResult { sessionid, state } = ar;
449
450 assert!(matches!(state, AuthState::Continue(_)));
451
452 let pw_step = AuthEvent::cred_step_password(sessionid, pw);
453
454 let r3 = idms_auth.auth(&pw_step, ct, Source::Internal.into()).await;
456 debug!("r3 ==> {:?}", r3);
457 idms_auth.commit().expect("Must not fail");
458
459 match r3 {
460 Ok(AuthResult {
461 sessionid: _,
462 state: AuthState::Success(token, AuthIssueSession::Token),
463 }) => {
464 let da = idms_delayed.try_recv().expect("invalid");
466 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
467 let r = idms.delayed_action(ct, da).await;
470 assert!(r.is_ok());
471
472 Some(*token)
473 }
474 _ => None,
475 }
476 }
477
478 async fn token_to_ident(
479 idms: &IdmServer,
480 ct: Duration,
481 client_auth_info: ClientAuthInfo,
482 ) -> Identity {
483 let mut idms_prox_read = idms.proxy_read().await.unwrap();
484
485 idms_prox_read
486 .validate_client_auth_info_to_ident(client_auth_info, ct)
487 .expect("Invalid UAT")
488 }
489
490 async fn reauth_passkey(
491 idms: &IdmServer,
492 ct: Duration,
493 ident: &Identity,
494 wa: &mut SoftPasskey,
495 idms_delayed: &mut IdmServerDelayed,
496 reauth_req: ReauthRequest,
497 ) -> Option<JwsCompact> {
498 let mut idms_auth = idms.auth().await.unwrap();
499 let origin = idms_auth.get_origin().clone();
500
501 let auth_allowed = idms_auth
502 .reauth_init(
503 ident.clone(),
504 AuthIssueSession::Token,
505 ct,
506 Source::Internal.into(),
507 reauth_req,
508 )
509 .await
510 .expect("Failed to start reauth.");
511
512 let AuthResult { sessionid, state } = auth_allowed;
513
514 trace!(?state);
515
516 let rcr = match state {
517 AuthState::Continue(mut allowed) => match allowed.pop() {
518 Some(AuthAllowed::Passkey(rcr)) => rcr,
519 _ => return None,
520 },
521 _ => unreachable!(),
522 };
523
524 trace!(?rcr);
525
526 let resp = wa
527 .do_authentication(origin, rcr)
528 .expect("failed to use softtoken to authenticate");
529
530 let passkey_step = AuthEvent::cred_step_passkey(sessionid, resp);
531
532 let r3 = idms_auth
533 .auth(&passkey_step, ct, Source::Internal.into())
534 .await;
535 debug!("r3 ==> {:?}", r3);
536 idms_auth.commit().expect("Must not fail");
537
538 match r3 {
539 Ok(AuthResult {
540 sessionid: _,
541 state: AuthState::Success(token, AuthIssueSession::Token),
542 }) => {
543 let da = idms_delayed.try_recv().expect("invalid");
545 assert!(matches!(da, DelayedAction::WebauthnCounterIncrement(_)));
546 let r = idms.delayed_action(ct, da).await;
547 assert!(r.is_ok());
548
549 Some(*token)
553 }
554 _ => unreachable!(),
555 }
556 }
557
558 async fn reauth_password_totp(
559 idms: &IdmServer,
560 ct: Duration,
561 ident: &Identity,
562 pw: &str,
563 token: &Totp,
564 idms_delayed: &mut IdmServerDelayed,
565 ) -> Option<JwsCompact> {
566 let mut idms_auth = idms.auth().await.unwrap();
567
568 let auth_allowed = idms_auth
569 .reauth_init(
570 ident.clone(),
571 AuthIssueSession::Token,
572 ct,
573 Source::Internal.into(),
574 ReauthRequest::GrantReadWrite,
575 )
576 .await
577 .expect("Failed to start reauth.");
578
579 let AuthResult { sessionid, state } = auth_allowed;
580
581 trace!(?state);
582
583 match state {
584 AuthState::Denied(reason) => {
585 trace!("{}", reason);
586 return None;
587 }
588 AuthState::Continue(mut allowed) => match allowed.pop() {
589 Some(AuthAllowed::Totp) => {}
590 _ => return None,
591 },
592 _ => unreachable!(),
593 };
594
595 let totp = token
596 .do_totp_duration_from_epoch(&ct)
597 .expect("Failed to perform totp step");
598
599 let totp_step = AuthEvent::cred_step_totp(sessionid, totp);
600 let r2 = idms_auth
601 .auth(&totp_step, ct, Source::Internal.into())
602 .await;
603 let ar = r2.unwrap();
604 let AuthResult { sessionid, state } = ar;
605
606 assert!(matches!(state, AuthState::Continue(_)));
607
608 let pw_step = AuthEvent::cred_step_password(sessionid, pw);
609
610 let r3 = idms_auth.auth(&pw_step, ct, Source::Internal.into()).await;
612 debug!("r3 ==> {:?}", r3);
613 idms_auth.commit().expect("Must not fail");
614
615 match r3 {
616 Ok(AuthResult {
617 sessionid: _,
618 state: AuthState::Success(token, AuthIssueSession::Token),
619 }) => {
620 let da = idms_delayed.try_recv().expect("invalid");
622 assert!(matches!(da, DelayedAction::AuthSessionRecord(_)));
623 Some(*token)
624 }
625 _ => None,
626 }
627 }
628
629 #[idm_test]
630 async fn test_idm_reauth_passkey(idms: &IdmServer, idms_delayed: &mut IdmServerDelayed) {
631 let ct = duration_from_epoch_now();
632
633 setup_testaccount(idms, ct).await;
635 let mut passkey = setup_testaccount_passkey(idms, ct).await;
636
637 let token = auth_passkey(idms, ct, &mut passkey, idms_delayed)
639 .await
640 .expect("failed to authenticate with passkey");
641
642 let ident = token_to_ident(idms, ct, token.clone().into()).await;
644
645 debug!(?ident);
647 assert!(matches!(ident.access_scope(), AccessScope::ReadOnly));
648
649 let session = ident.get_session().expect("Unable to access sessions");
653
654 assert!(matches!(session.scope, SessionScope::PrivilegeCapable));
655
656 let token = reauth_passkey(
658 idms,
659 ct,
660 &ident,
661 &mut passkey,
662 idms_delayed,
663 ReauthRequest::GrantReadWrite,
664 )
665 .await
666 .expect("Failed to get new session token");
667
668 let ident = token_to_ident(idms, ct, token.clone().into()).await;
670
671 debug!(?ident);
673 assert!(matches!(ident.access_scope(), AccessScope::ReadWrite));
674
675 let token = reauth_passkey(
677 idms,
678 ct,
679 &ident,
680 &mut passkey,
681 idms_delayed,
682 ReauthRequest::VerifyCredentials,
683 )
684 .await
685 .expect("Failed to get new session token");
686
687 let ident = token_to_ident(idms, ct, token.clone().into()).await;
689
690 debug!(?ident);
692 assert!(matches!(ident.access_scope(), AccessScope::ReadOnly));
693 }
694
695 #[idm_test(audit = 1)]
696 async fn test_idm_reauth_softlocked_pw(
697 idms: &IdmServer,
698 idms_delayed: &mut IdmServerDelayed,
699 idms_audit: &mut IdmServerAudit,
700 ) {
701 let ct = duration_from_epoch_now();
704
705 setup_testaccount(idms, ct).await;
707 let (pw, totp) = setup_testaccount_password_totp(idms, ct).await;
708
709 let token = auth_password_totp(idms, ct, &pw, &totp, idms_delayed)
711 .await
712 .expect("failed to authenticate with passkey");
713
714 let ident = token_to_ident(idms, ct, token.into()).await;
716
717 debug!(?ident);
719 assert!(matches!(ident.access_scope(), AccessScope::ReadOnly));
720
721 let session = ident.get_session().expect("Unable to access sessions");
724
725 assert!(matches!(session.scope, SessionScope::PrivilegeCapable));
726
727 assert!(reauth_password_totp(
729 idms,
730 ct,
731 &ident,
732 "absolutely-wrong-password",
733 &totp,
734 idms_delayed
735 )
736 .await
737 .is_none());
738
739 match idms_audit.audit_rx().try_recv() {
741 Ok(AuditEvent::AuthenticationDenied { .. }) => {}
742 _ => panic!("Oh no"),
743 }
744
745 assert!(
747 reauth_password_totp(idms, ct, &ident, &pw, &totp, idms_delayed)
748 .await
749 .is_none()
750 );
751 }
752}