1use crate::idm::account::Account;
8use crate::idm::server::{
9 IdmServerProxyReadTransaction, IdmServerProxyWriteTransaction, IdmServerTransaction, Token,
10};
11use crate::prelude::*;
12use crate::server::keys::{
13 KeyId, KeyObject, KeyProvidersTransaction, KeyProvidersWriteTransaction,
14};
15use crate::utils;
16use crate::value::{Oauth2Session, OauthClaimMapJoin, SessionState, OAUTHSCOPE_RE};
17use base64::{engine::general_purpose, Engine as _};
18pub use compact_jwt::{compact::JwkKeySet, OidcToken};
19use compact_jwt::{
20 crypto::{JweA128GCMEncipher, JweA128KWEncipher},
21 jwe::JweBuilder,
22 jws::JwsBuilder,
23 JweCompact, JwsCompact, OidcClaims, OidcSubject,
24};
25use concread::cowcell::*;
26use crypto_glue::{
27 s256::{Sha256, Sha256Output},
28 traits::Digest,
29};
30use hashbrown::HashMap;
31use hashbrown::HashSet;
32use kanidm_proto::constants::*;
33pub use kanidm_proto::oauth2::{
34 AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
35 AccessTokenResponse, AccessTokenType, AuthorisationRequest, ClaimType, ClientAuth,
36 ClientPostAuth, CodeChallengeMethod, DeviceAuthorizationResponse, DisplayValue,
37 EndpointAuthMethod, ErrorResponse, GrantType, GrantTypeReq, IdTokenSignAlg, OAuth2RFC9068Token,
38 OAuth2RFC9068TokenExtensions, Oauth2Rfc8414MetadataResponse, OidcDiscoveryResponse,
39 OidcWebfingerRel, OidcWebfingerResponse, PkceAlg, PkceRequest, ResponseMode, ResponseType,
40 SubjectType, TokenRevokeRequest, OAUTH2_TOKEN_TYPE_ACCESS_TOKEN,
41};
42use kanidm_proto::oauth2::{IssuedTokenType, Prompt};
43use serde::{Deserialize, Serialize};
44use serde_with::{formats, serde_as};
45use std::collections::btree_map::Entry as BTreeEntry;
46use std::collections::{BTreeMap, BTreeSet};
47use std::fmt;
48use std::str::FromStr;
49use std::sync::Arc;
50use std::time::Duration;
51use subtle::ConstantTimeEq;
52use time::OffsetDateTime;
53use tracing::trace;
54use uri::{OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
55use url::{Host, Origin, Url};
56use utoipa::ToSchema;
57
58const TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS: &str = OAUTH2_TOKEN_TYPE_ACCESS_TOKEN;
59
60#[derive(Serialize, Deserialize, Debug, PartialEq, ToSchema)]
61#[serde(rename_all = "snake_case")]
62pub enum Oauth2Error {
63 AuthenticationRequired,
65 InvalidClientId,
66 InvalidOrigin,
67 InvalidRequest,
69 InvalidGrant,
70 UnauthorizedClient,
71 LoginRequired,
72 InteractionRequired,
73 AccessDenied,
74 UnsupportedResponseType,
75 InvalidScope,
76 InvalidTarget,
77 ServerError(OperationError),
78 TemporarilyUnavailable,
79 InvalidToken,
81 InsufficientScope,
82 UnsupportedTokenType,
84 SlowDown,
88 AuthorizationPending,
98 ExpiredToken,
103}
104
105impl std::fmt::Display for Oauth2Error {
106 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
107 f.write_str(match self {
108 Oauth2Error::AuthenticationRequired => "authentication_required",
109 Oauth2Error::InvalidClientId => "invalid_client_id",
110 Oauth2Error::InvalidOrigin => "invalid_origin",
111 Oauth2Error::InvalidGrant => "invalid_grant",
112 Oauth2Error::InvalidRequest => "invalid_request",
113 Oauth2Error::UnauthorizedClient => "unauthorized_client",
114 Oauth2Error::LoginRequired => "login_required",
115 Oauth2Error::InteractionRequired => "interaction_required",
116 Oauth2Error::AccessDenied => "access_denied",
117 Oauth2Error::UnsupportedResponseType => "unsupported_response_type",
118 Oauth2Error::InvalidScope => "invalid_scope",
119 Oauth2Error::InvalidTarget => "invalid_target",
120 Oauth2Error::ServerError(_) => "server_error",
121 Oauth2Error::TemporarilyUnavailable => "temporarily_unavailable",
122 Oauth2Error::InvalidToken => "invalid_token",
123 Oauth2Error::InsufficientScope => "insufficient_scope",
124 Oauth2Error::UnsupportedTokenType => "unsupported_token_type",
125 Oauth2Error::SlowDown => "slow_down",
126 Oauth2Error::AuthorizationPending => "authorization_pending",
127 Oauth2Error::ExpiredToken => "expired_token",
128 })
129 }
130}
131
132#[derive(Clone)]
133pub struct PkceS256Secret {
134 secret: String,
135}
136
137impl Default for PkceS256Secret {
138 fn default() -> Self {
139 Self {
140 secret: utils::password_from_random(),
141 }
142 }
143}
144
145impl From<String> for PkceS256Secret {
146 fn from(secret: String) -> Self {
147 Self { secret }
148 }
149}
150
151impl PkceS256Secret {
152 pub fn to_challenge(&self) -> Sha256Output {
153 let mut hasher = Sha256::new();
154 hasher.update(self.secret.as_bytes());
155 hasher.finalize()
156 }
157
158 pub fn to_request(&self) -> PkceRequest {
159 let code_challenge = self.to_challenge();
160
161 PkceRequest {
162 code_challenge: code_challenge.to_vec(),
163 code_challenge_method: CodeChallengeMethod::S256,
164 }
165 }
166
167 pub(crate) fn verifier(&self) -> &str {
168 &self.secret
169 }
170
171 pub fn to_verifier(self) -> String {
172 self.secret
173 }
174
175 pub fn verify<V: AsRef<[u8]>>(&self, challenge: V) -> bool {
176 let code_challenge = self.to_challenge();
177 challenge.as_ref() == code_challenge.as_slice()
178 }
179}
180
181#[derive(Debug, Default)]
182pub struct AuthorisationRequestContext {
183 resumed: bool,
184}
185
186impl AuthorisationRequestContext {
187 pub fn resumed_session() -> Self {
188 Self { resumed: true }
189 }
190}
191
192struct OAuth2SessionContext {
193 pub(crate) auth_time: Option<OffsetDateTime>,
194 pub(crate) nonce: Option<String>,
195 pub(crate) account_uuid: Uuid,
196}
197
198#[derive(Serialize, Deserialize, Debug, PartialEq)]
200enum SupportedResponseMode {
201 Query,
202 Fragment,
203}
204
205#[serde_as]
206#[derive(Serialize, Deserialize, Debug, PartialEq)]
207struct ConsentToken {
208 pub client_id: String,
209 pub session_id: Uuid,
211 pub expiry: u64,
212
213 pub ident_id: IdentityId,
215 pub state: Option<String>,
217 #[serde_as(
219 as = "Option<serde_with::base64::Base64<serde_with::base64::UrlSafe, formats::Unpadded>>"
220 )]
221 pub code_challenge: Option<Vec<u8>>,
222 pub redirect_uri: Url,
224 pub scopes: BTreeSet<String>,
226 pub nonce: Option<String>,
228 pub response_mode: SupportedResponseMode,
230}
231
232#[serde_as]
233#[derive(Serialize, Deserialize, Debug)]
234struct TokenExchangeCode {
235 pub account_uuid: Uuid,
238 pub session_id: Uuid,
239
240 pub expiry: u64,
241
242 #[serde_as(
244 as = "Option<serde_with::base64::Base64<serde_with::base64::UrlSafe, formats::Unpadded>>"
245 )]
246 pub code_challenge: Option<Vec<u8>>,
247 pub redirect_uri: Url,
249 pub scopes: BTreeSet<String>,
251 pub nonce: Option<String>,
253 pub auth_time: Option<OffsetDateTime>,
254}
255
256#[derive(Serialize, Deserialize, Debug)]
257pub(crate) enum Oauth2TokenType {
258 Refresh {
259 scopes: BTreeSet<String>,
260 parent_session_id: Option<Uuid>,
261 session_id: Uuid,
262 exp: i64,
263 uuid: Uuid,
264 iat: i64,
266 nbf: i64,
267 nonce: Option<String>,
269 auth_time: Option<OffsetDateTime>,
270 },
271 ClientAccess {
272 scopes: BTreeSet<String>,
273 session_id: Uuid,
274 uuid: Uuid,
275 exp: i64,
276 iat: i64,
277 nbf: i64,
278 },
279}
280
281impl fmt::Display for Oauth2TokenType {
282 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
283 match self {
284 Oauth2TokenType::Refresh { session_id, .. } => {
285 write!(f, "refresh_token ({session_id}) ")
286 }
287 Oauth2TokenType::ClientAccess { session_id, .. } => {
288 write!(f, "client_access_token ({session_id})")
289 }
290 }
291 }
292}
293
294#[derive(Debug)]
295pub enum AuthoriseResponse {
296 AuthenticationRequired {
297 client_name: String,
299 login_hint: Option<String>,
301 },
302 ReauthenticationRequired {
303 client_name: String,
305 },
306 ConsentRequested {
307 client_name: String,
309 scopes: BTreeSet<String>,
311 pii_scopes: BTreeSet<String>,
313 consent_token: String,
317 },
318 Permitted(AuthorisePermitSuccess),
319}
320
321#[derive(Debug)]
322pub struct AuthorisePermitSuccess {
323 pub redirect_uri: Url,
325 pub state: Option<String>,
327 pub code: String,
329 response_mode: SupportedResponseMode,
331}
332
333impl AuthorisePermitSuccess {
334 pub fn build_redirect_uri(&self) -> Url {
337 let mut redirect_uri = self.redirect_uri.clone();
338
339 redirect_uri.set_fragment(None);
341
342 match self.response_mode {
343 SupportedResponseMode::Query => {
344 redirect_uri
345 .query_pairs_mut()
346 .append_pair("code", &self.code);
347
348 if let Some(state) = self.state.as_ref() {
349 redirect_uri.query_pairs_mut().append_pair("state", state);
350 };
351 }
352 SupportedResponseMode::Fragment => {
353 redirect_uri.set_query(None);
354
355 let mut uri_builder = url::form_urlencoded::Serializer::new(String::new());
357 uri_builder.append_pair("code", &self.code);
358 if let Some(state) = self.state.as_ref() {
359 uri_builder.append_pair("state", state);
360 };
361 let encoded = uri_builder.finish();
362
363 redirect_uri.set_fragment(Some(&encoded))
364 }
365 }
366
367 redirect_uri
368 }
369}
370
371#[derive(Debug)]
372pub struct AuthoriseReject {
373 pub redirect_uri: Url,
375 response_mode: SupportedResponseMode,
377}
378
379impl AuthoriseReject {
380 pub fn build_redirect_uri(&self) -> Url {
383 let mut redirect_uri = self.redirect_uri.clone();
384
385 redirect_uri.set_query(None);
387 redirect_uri.set_fragment(None);
388
389 let encoded = url::form_urlencoded::Serializer::new(String::new())
391 .append_pair("error", "access_denied")
392 .append_pair("error_description", "authorisation rejected")
393 .finish();
394
395 match self.response_mode {
396 SupportedResponseMode::Query => redirect_uri.set_query(Some(&encoded)),
397 SupportedResponseMode::Fragment => redirect_uri.set_fragment(Some(&encoded)),
398 }
399
400 redirect_uri
401 }
402}
403
404#[derive(Clone)]
405struct CtSecret {
406 inner: String,
407}
408
409impl CtSecret {
410 fn ct_eq(&self, rhs: &str) -> bool {
411 self.inner.as_bytes().ct_eq(rhs.as_bytes()).unwrap_u8() == 1
412 }
413}
414
415impl From<String> for CtSecret {
416 fn from(inner: String) -> Self {
417 CtSecret { inner }
418 }
419}
420
421#[derive(Clone)]
422enum OauthRSType {
423 Basic {
424 authz_secret: CtSecret,
425 enable_pkce: bool,
426 enable_consent_prompt: bool,
427 },
428 Public {
430 allow_localhost_redirect: bool,
431 },
432}
433
434impl OauthRSType {
435 fn allow_localhost_redirect(&self) -> bool {
437 match self {
438 OauthRSType::Basic { .. } => false,
439 OauthRSType::Public {
440 allow_localhost_redirect,
441 } => *allow_localhost_redirect,
442 }
443 }
444
445 fn allow_localhost_redirect_could_be_possible(&self) -> bool {
448 match self {
449 OauthRSType::Basic { .. } => false,
450 OauthRSType::Public { .. } => true,
451 }
452 }
453}
454
455impl std::fmt::Debug for OauthRSType {
456 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
457 let mut ds = f.debug_struct("OauthRSType");
458 match self {
459 OauthRSType::Basic {
460 enable_pkce,
461 enable_consent_prompt,
462 ..
463 } => ds
464 .field("type", &"basic")
465 .field("pkce", enable_pkce)
466 .field("consent_prompt", enable_consent_prompt),
467 OauthRSType::Public {
468 allow_localhost_redirect,
469 } => ds
470 .field("type", &"public")
471 .field("allow_localhost_redirect", allow_localhost_redirect),
472 };
473 ds.finish()
474 }
475}
476
477#[derive(Clone, Debug)]
478struct ClaimValue {
479 join: OauthClaimMapJoin,
480 values: BTreeSet<String>,
481}
482
483impl ClaimValue {
484 fn merge(&mut self, other: &Self) {
485 self.values.extend(other.values.iter().cloned())
486 }
487
488 fn to_json_value(&self) -> serde_json::Value {
489 let join_str = match self.join {
490 OauthClaimMapJoin::JsonArray => {
491 let arr: Vec<_> = self
492 .values
493 .iter()
494 .cloned()
495 .map(serde_json::Value::String)
496 .collect();
497
498 return serde_json::Value::Array(arr);
500 }
501 joiner => joiner.to_str(),
502 };
503
504 let joined = str_concat!(&self.values, join_str);
505
506 serde_json::Value::String(joined)
507 }
508}
509
510#[derive(Clone, Copy, Debug)]
511enum SignatureAlgo {
512 Es256,
513 Rs256,
514}
515
516#[derive(Clone)]
517pub struct Oauth2RS {
518 name: String,
519 displayname: String,
520 uuid: Uuid,
521
522 origins: HashSet<Origin>,
523 opaque_origins: HashSet<Url>,
524 redirect_uris: HashSet<Url>,
525 origin_secure_required: bool,
526
527 claim_map: BTreeMap<Uuid, Vec<(String, ClaimValue)>>,
528 scope_maps: BTreeMap<Uuid, BTreeSet<String>>,
529 sup_scope_maps: BTreeMap<Uuid, BTreeSet<String>>,
530 client_scopes: BTreeSet<String>,
531 client_sup_scopes: BTreeSet<String>,
532 sign_alg: SignatureAlgo,
534 key_object: Arc<KeyObject>,
535
536 refresh_token_expiry: u32,
537
538 iss: Url,
540 authorization_endpoint: Url,
542 token_endpoint: Url,
543 revocation_endpoint: Url,
544 introspection_endpoint: Url,
545 userinfo_endpoint: Url,
546 jwks_uri: Url,
547 scopes_supported: BTreeSet<String>,
548 prefer_short_username: bool,
549 type_: OauthRSType,
550 has_custom_image: bool,
552
553 device_authorization_endpoint: Option<Url>,
554}
555
556impl Oauth2RS {
557 pub fn is_basic(&self) -> bool {
558 match self.type_ {
559 OauthRSType::Basic { .. } => true,
560 OauthRSType::Public { .. } => false,
561 }
562 }
563
564 pub fn is_pkce(&self) -> bool {
565 match self.type_ {
566 OauthRSType::Basic { .. } => false,
567 OauthRSType::Public { .. } => true,
568 }
569 }
570
571 pub fn require_pkce(&self) -> bool {
573 match &self.type_ {
574 OauthRSType::Basic { enable_pkce, .. } => *enable_pkce,
575 OauthRSType::Public { .. } => true,
576 }
577 }
578
579 pub fn device_flow_enabled(&self) -> bool {
581 self.device_authorization_endpoint.is_some()
582 }
583
584 pub fn enable_consent_prompt(&self) -> bool {
587 match &self.type_ {
588 OauthRSType::Basic {
589 enable_consent_prompt,
590 ..
591 } => *enable_consent_prompt,
592 OauthRSType::Public { .. } => true,
593 }
594 }
595}
596
597impl std::fmt::Debug for Oauth2RS {
598 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
599 f.debug_struct("Oauth2RS")
600 .field("name", &self.name)
601 .field("displayname", &self.displayname)
602 .field("uuid", &self.uuid)
603 .field("type", &self.type_)
604 .field("origins", &self.origins)
605 .field("opaque_origins", &self.opaque_origins)
606 .field("scope_maps", &self.scope_maps)
607 .field("sup_scope_maps", &self.sup_scope_maps)
608 .field("claim_map", &self.claim_map)
609 .field("has_custom_image", &self.has_custom_image)
610 .finish()
611 }
612}
613
614#[derive(Clone)]
615struct Oauth2RSInner {
616 origin: Url,
617 consent_key: JweA128KWEncipher,
618 private_rs_set: HashMap<String, Oauth2RS>,
619 private_key_map: HashMap<KeyId, String>,
620}
621
622impl Oauth2RSInner {
623 fn rs_set_get(&self, client_id: &str) -> Option<&Oauth2RS> {
624 self.private_rs_set.get(client_id.to_lowercase().as_str())
625 }
626
627 fn rs_from_kid(&self, kid: &str) -> Option<&Oauth2RS> {
628 self.private_key_map
629 .get(kid)
630 .and_then(|client_id| self.private_rs_set.get(client_id))
631 }
632}
633
634pub struct Oauth2ResourceServers {
635 inner: CowCell<Oauth2RSInner>,
636}
637
638pub struct Oauth2ResourceServersReadTransaction {
639 inner: CowCellReadTxn<Oauth2RSInner>,
640}
641
642pub struct Oauth2ResourceServersWriteTransaction<'a> {
643 inner: CowCellWriteTxn<'a, Oauth2RSInner>,
644}
645
646impl Oauth2ResourceServers {
647 pub fn new(origin: Url) -> Result<Self, OperationError> {
648 let consent_key = JweA128KWEncipher::generate_ephemeral()
649 .map_err(|_| OperationError::CryptographyError)?;
650
651 Ok(Oauth2ResourceServers {
652 inner: CowCell::new(Oauth2RSInner {
653 origin,
654 consent_key,
655 private_rs_set: HashMap::new(),
656 private_key_map: HashMap::new(),
657 }),
658 })
659 }
660
661 pub fn read(&self) -> Oauth2ResourceServersReadTransaction {
662 Oauth2ResourceServersReadTransaction {
663 inner: self.inner.read(),
664 }
665 }
666
667 pub fn write(&self) -> Oauth2ResourceServersWriteTransaction<'_> {
668 Oauth2ResourceServersWriteTransaction {
669 inner: self.inner.write(),
670 }
671 }
672}
673
674fn get_client_auth(
676 client_auth_info: &ClientAuthInfo,
677 client_post_auth: &ClientPostAuth,
678) -> Result<ClientAuth, Oauth2Error> {
679 if let Some(client_authz) = client_auth_info.basic_authz.as_ref() {
680 parse_basic_authz(client_authz.as_str())
681 } else if let Some(client_id) = &client_post_auth.client_id {
682 Ok(ClientAuth {
683 client_id: client_id.clone(),
684 client_secret: client_post_auth.client_secret.clone(),
685 })
686 } else {
687 Err(Oauth2Error::AuthenticationRequired)
688 }
689}
690
691impl Oauth2ResourceServersWriteTransaction<'_> {
692 #[instrument(level = "debug", name = "oauth2::reload", skip_all)]
693 pub fn reload(
694 &mut self,
695 value: Vec<Arc<EntrySealedCommitted>>,
696 key_providers: &KeyProvidersWriteTransaction,
697 _domain_level: DomainVersion,
698 ) -> Result<(), OperationError> {
699 let mut kid_map: HashMap<KeyId, String> = Default::default();
700
701 let rs_set: Result<HashMap<_, _>, _> = value
702 .into_iter()
703 .map(|ent| {
704 let uuid = ent.get_uuid();
705
706 trace!(?uuid, "Checking OAuth2 configuration");
707
708 if !ent
710 .attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
711 {
712 error!("Missing class oauth2_resource_server");
713 return Err(OperationError::InvalidEntryState);
715 }
716
717 let Some(key_object) = key_providers.get_key_object_handle(uuid) else {
718 error!("OAuth2 RS is missing its key object!");
719 return Err(OperationError::InvalidEntryState);
720 };
721
722 let type_ = if ent.attribute_equality(
723 Attribute::Class,
724 &EntryClass::OAuth2ResourceServerBasic.into(),
725 ) {
726 let authz_secret = ent
727 .get_ava_single_secret(Attribute::OAuth2RsBasicSecret)
728 .map(str::to_string)
729 .map(CtSecret::from)
730 .ok_or(OperationError::InvalidValueState)?;
731
732 let enable_pkce = ent
733 .get_ava_single_bool(Attribute::OAuth2AllowInsecureClientDisablePkce)
734 .map(|e| !e)
735 .unwrap_or(true);
736
737 let enable_consent_prompt = ent
738 .get_ava_single_bool(Attribute::OAuth2ConsentPromptEnable)
739 .unwrap_or(true);
740
741 OauthRSType::Basic {
742 authz_secret,
743 enable_pkce,
744 enable_consent_prompt,
745 }
746 } else if ent.attribute_equality(
747 Attribute::Class,
748 &EntryClass::OAuth2ResourceServerPublic.into(),
749 ) {
750 let allow_localhost_redirect = ent
751 .get_ava_single_bool(Attribute::OAuth2AllowLocalhostRedirect)
752 .unwrap_or(false);
753
754 OauthRSType::Public {
755 allow_localhost_redirect,
756 }
757 } else {
758 error!("Missing class determining OAuth2 rs type");
759 return Err(OperationError::InvalidEntryState);
760 };
761
762 let client_id = ent
764 .get_ava_single_iname(Attribute::Name)
765 .map(str::to_string)
766 .ok_or(OperationError::InvalidValueState)?;
767
768 let displayname = ent
769 .get_ava_single_utf8(Attribute::DisplayName)
770 .map(str::to_string)
771 .ok_or(OperationError::InvalidValueState)?;
772
773 let landing_url = ent
776 .get_ava_single_url(Attribute::OAuth2RsOriginLanding)
777 .cloned()
778 .ok_or(OperationError::InvalidValueState)?;
779
780 let maybe_extra_urls = ent
781 .get_ava_set(Attribute::OAuth2RsOrigin)
782 .and_then(|s| s.as_url_set());
783
784 let len_uris = maybe_extra_urls.map(|s| s.len() + 1).unwrap_or(1);
785
786 let mut redirect_uris_v = Vec::with_capacity(len_uris);
789
790 redirect_uris_v.push(landing_url);
791 if let Some(extra_origins) = maybe_extra_urls {
792 for x_origin in extra_origins {
793 redirect_uris_v.push(x_origin.clone());
794 }
795 }
796
797 let mut origins = HashSet::with_capacity(len_uris);
801 let mut redirect_uris = HashSet::with_capacity(len_uris);
802 let mut opaque_origins = HashSet::with_capacity(len_uris);
803 let mut origin_secure_required = false;
804
805 for mut uri in redirect_uris_v.into_iter() {
806 uri.set_fragment(None);
809 if uri.scheme() == "https" {
811 origin_secure_required = true;
812 origins.insert(uri.origin());
813 redirect_uris.insert(uri);
814 } else if uri.scheme() == "http" {
815 origins.insert(uri.origin());
816 redirect_uris.insert(uri);
817 } else {
818 opaque_origins.insert(uri);
819 }
820 }
821
822 let scope_maps = ent
823 .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap)
824 .cloned()
825 .unwrap_or_default();
826
827 let sup_scope_maps = ent
828 .get_ava_as_oauthscopemaps(Attribute::OAuth2RsSupScopeMap)
829 .cloned()
830 .unwrap_or_default();
831
832 let (client_scopes, client_sup_scopes) =
835 if let Some(client_member_of) = ent.get_ava_refer(Attribute::MemberOf) {
836 let client_scopes = scope_maps
837 .iter()
838 .filter_map(|(u, m)| {
839 if client_member_of.contains(u) {
840 Some(m.iter())
841 } else {
842 None
843 }
844 })
845 .flatten()
846 .cloned()
847 .collect::<BTreeSet<_>>();
848
849 let client_sup_scopes = sup_scope_maps
850 .iter()
851 .filter_map(|(u, m)| {
852 if client_member_of.contains(u) {
853 Some(m.iter())
854 } else {
855 None
856 }
857 })
858 .flatten()
859 .cloned()
860 .collect::<BTreeSet<_>>();
861
862 (client_scopes, client_sup_scopes)
863 } else {
864 (BTreeSet::default(), BTreeSet::default())
865 };
866
867 let e_claim_maps = ent
868 .get_ava_set(Attribute::OAuth2RsClaimMap)
869 .and_then(|vs| vs.as_oauthclaim_map());
870
871 let claim_map = if let Some(e_claim_maps) = e_claim_maps {
876 let mut claim_map = BTreeMap::default();
877
878 for (claim_name, claim_mapping) in e_claim_maps.iter() {
879 for (group_uuid, claim_values) in claim_mapping.values().iter() {
880 match claim_map.entry(*group_uuid) {
883 BTreeEntry::Vacant(e) => {
884 e.insert(vec![(
885 claim_name.clone(),
886 ClaimValue {
887 join: claim_mapping.join(),
888 values: claim_values.clone(),
889 },
890 )]);
891 }
892 BTreeEntry::Occupied(mut e) => {
893 e.get_mut().push((
894 claim_name.clone(),
895 ClaimValue {
896 join: claim_mapping.join(),
897 values: claim_values.clone(),
898 },
899 ));
900 }
901 }
902 }
903 }
904
905 claim_map
906 } else {
907 BTreeMap::default()
908 };
909
910 let sign_alg = if ent
911 .get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable)
912 .unwrap_or(false)
913 {
914 kid_map.extend(
916 key_object
917 .jws_rs256_kid()
918 .into_iter()
919 .map(|kid| (kid.clone(), client_id.clone())),
920 );
921
922 SignatureAlgo::Rs256
923 } else {
924 kid_map.extend(
926 key_object
927 .jws_es256_kid()
928 .into_iter()
929 .map(|kid| (kid.clone(), client_id.clone())),
930 );
931
932 SignatureAlgo::Es256
933 };
934
935 kid_map.extend(
937 key_object
938 .jwe_a128gcm_kid()
939 .into_iter()
940 .map(|kid| (kid.clone(), client_id.clone())),
941 );
942
943 let prefer_short_username = ent
944 .get_ava_single_bool(Attribute::OAuth2PreferShortUsername)
945 .unwrap_or(false);
946
947 let has_custom_image = ent.get_ava_single_image(Attribute::Image).is_some();
948
949 let refresh_token_expiry = ent
950 .get_ava_single_uint32(Attribute::OAuth2RefreshTokenExpiry)
951 .unwrap_or(OAUTH_REFRESH_TOKEN_EXPIRY);
952
953 let mut authorization_endpoint = self.inner.origin.clone();
954 authorization_endpoint.set_path("/ui/oauth2");
955
956 let mut token_endpoint = self.inner.origin.clone();
957 token_endpoint.set_path(uri::OAUTH2_TOKEN_ENDPOINT);
958
959 let mut revocation_endpoint = self.inner.origin.clone();
960 revocation_endpoint.set_path(OAUTH2_TOKEN_REVOKE_ENDPOINT);
961
962 let mut introspection_endpoint = self.inner.origin.clone();
963 introspection_endpoint.set_path(OAUTH2_TOKEN_INTROSPECT_ENDPOINT);
964
965 let mut userinfo_endpoint = self.inner.origin.clone();
966 userinfo_endpoint.set_path(&format!("/oauth2/openid/{client_id}/userinfo"));
967
968 let mut jwks_uri = self.inner.origin.clone();
969 jwks_uri.set_path(&format!("/oauth2/openid/{client_id}/public_key.jwk"));
970
971 let mut iss = self.inner.origin.clone();
972 iss.set_path(&format!("/oauth2/openid/{client_id}"));
973
974 let scopes_supported: BTreeSet<String> = scope_maps
975 .values()
976 .flat_map(|bts| bts.iter())
977 .chain(sup_scope_maps.values().flat_map(|bts| bts.iter()))
978 .cloned()
979 .collect();
980
981 let device_authorization_endpoint: Option<Url> =
982 match cfg!(feature = "dev-oauth2-device-flow") {
983 true => {
984 match ent
985 .get_ava_single_bool(Attribute::OAuth2DeviceFlowEnable)
986 .unwrap_or(false)
987 {
988 true => {
989 let mut device_authorization_endpoint =
990 self.inner.origin.clone();
991 device_authorization_endpoint
992 .set_path(uri::OAUTH2_AUTHORISE_DEVICE);
993 Some(device_authorization_endpoint)
994 }
995 false => None,
996 }
997 }
998 false => None,
999 };
1000
1001 let rscfg = Oauth2RS {
1002 name: client_id.clone(),
1003 displayname,
1004 uuid,
1005 origins,
1006 opaque_origins,
1007 redirect_uris,
1008 origin_secure_required,
1009 scope_maps,
1010 sup_scope_maps,
1011 client_scopes,
1012 client_sup_scopes,
1013 claim_map,
1014 sign_alg,
1015 key_object,
1016 refresh_token_expiry,
1017 iss,
1018 authorization_endpoint,
1019 token_endpoint,
1020 revocation_endpoint,
1021 introspection_endpoint,
1022 userinfo_endpoint,
1023 jwks_uri,
1024 scopes_supported,
1025 prefer_short_username,
1026 type_,
1027 has_custom_image,
1028 device_authorization_endpoint,
1029 };
1030
1031 Ok((client_id, rscfg))
1032 })
1033 .collect();
1034
1035 rs_set.map(|mut rs_set| {
1036 let inner_ref = self.inner.get_mut();
1038 std::mem::swap(&mut inner_ref.private_rs_set, &mut rs_set);
1040 std::mem::swap(&mut inner_ref.private_key_map, &mut kid_map);
1041 })
1042 }
1043
1044 pub fn commit(self) {
1045 self.inner.commit();
1046 }
1047}
1048
1049impl IdmServerProxyWriteTransaction<'_> {
1050 #[instrument(level = "debug", skip_all)]
1051 pub fn oauth2_token_revoke(
1052 &mut self,
1053 revoke_req: &TokenRevokeRequest,
1054 ct: Duration,
1055 ) -> Result<(), Oauth2Error> {
1056 let (session_id, expiry, uuid) = if let Ok(jwsc) = JwsCompact::from_str(&revoke_req.token) {
1064 let unverified_kid = jwsc.header().kid.as_ref().ok_or_else(|| {
1065 error!("Token does not contain signature key id");
1066 Oauth2Error::AuthenticationRequired
1067 })?;
1068
1069 let o2rs = self
1071 .oauth2rs
1072 .inner
1073 .rs_from_kid(unverified_kid)
1074 .ok_or_else(|| {
1075 warn!("Invalid OAuth2 key id");
1076 Oauth2Error::AuthenticationRequired
1077 })?;
1078
1079 let access_token = o2rs
1080 .key_object
1081 .jws_verify(&jwsc)
1082 .map_err(|err| {
1083 admin_error!(?err, "Unable to verify access token");
1084 Oauth2Error::AuthenticationRequired
1085 })
1086 .and_then(|jws| {
1087 jws.from_json().map_err(|err| {
1088 admin_error!(?err, "Unable to deserialise access token");
1089 Oauth2Error::InvalidRequest
1090 })
1091 })?;
1092
1093 let OAuth2RFC9068Token::<_> {
1094 sub: uuid,
1095 exp,
1096 extensions: OAuth2RFC9068TokenExtensions { session_id, .. },
1097 ..
1098 } = access_token;
1099
1100 (session_id, exp, uuid)
1101 } else if let Ok(jwec) = JweCompact::from_str(&revoke_req.token) {
1102 let unverified_kid = jwec.header().kid.as_ref().ok_or_else(|| {
1104 error!("Token does not contain encryption key id");
1105 Oauth2Error::AuthenticationRequired
1106 })?;
1107
1108 let o2rs = self
1110 .oauth2rs
1111 .inner
1112 .rs_from_kid(unverified_kid)
1113 .ok_or_else(|| {
1114 warn!("Invalid OAuth2 key id");
1115 Oauth2Error::AuthenticationRequired
1116 })?;
1117
1118 let token: Oauth2TokenType = o2rs
1119 .key_object
1120 .jwe_decrypt(&jwec)
1121 .map_err(|_| {
1122 error!("Failed to decrypt token revoke request");
1123 Oauth2Error::AuthenticationRequired
1124 })
1125 .and_then(|jwe| {
1126 jwe.from_json().map_err(|err| {
1127 error!(?err, "Failed to deserialise token");
1128 Oauth2Error::InvalidRequest
1129 })
1130 })?;
1131
1132 match token {
1133 Oauth2TokenType::ClientAccess {
1134 session_id,
1135 exp,
1136 uuid,
1137 ..
1138 }
1139 | Oauth2TokenType::Refresh {
1140 session_id,
1141 exp,
1142 uuid,
1143 ..
1144 } => (session_id, exp, uuid),
1145 }
1146 } else {
1147 error!("Failed to deserialise a valid JWE or JWS");
1148 return Err(Oauth2Error::AuthenticationRequired);
1149 };
1150
1151 if expiry <= ct.as_secs() as i64 {
1153 security_info!(?uuid, "token has expired, returning inactive");
1154 return Ok(());
1155 }
1156
1157 let modlist: ModifyList<ModifyInvalid> = ModifyList::new_list(vec![Modify::Removed(
1166 Attribute::OAuth2Session,
1167 PartialValue::Refer(session_id),
1168 )]);
1169
1170 self.qs_write
1171 .internal_modify(
1172 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid))),
1173 &modlist,
1174 )
1175 .map_err(|e| {
1176 admin_error!("Failed to modify - revoke OAuth2 session {:?}", e);
1177 Oauth2Error::ServerError(e)
1178 })
1179 }
1180
1181 #[instrument(level = "debug", skip_all)]
1182 pub fn check_oauth2_token_exchange(
1183 &mut self,
1184 client_auth_info: &ClientAuthInfo,
1185 token_req: &AccessTokenRequest,
1186 ct: Duration,
1187 ) -> Result<AccessTokenResponse, Oauth2Error> {
1188 let client_auth = get_client_auth(client_auth_info, &token_req.client_post_auth)
1190 .inspect_err(|_| {
1191 warn!("OAuth2 Client Authentcation Required");
1192 })?;
1193
1194 let o2rs = self
1195 .oauth2rs
1196 .inner
1197 .rs_set_get(&client_auth.client_id)
1198 .ok_or_else(|| {
1199 debug!("Invalid OAuth2 client_id {}", &client_auth.client_id);
1200 Oauth2Error::AuthenticationRequired
1201 })?
1202 .clone();
1203
1204 let is_token_exchange = matches!(token_req.grant_type, GrantTypeReq::TokenExchange { .. });
1205
1206 let client_authentication_valid = match (&o2rs.type_, is_token_exchange) {
1208 (OauthRSType::Basic { .. }, true) => {
1209 if client_auth.client_secret.is_some() {
1210 security_info!(
1211 "Client secret is not accepted when exchanging a service account token"
1212 );
1213 return Err(Oauth2Error::InvalidRequest);
1214 }
1215 true
1216 }
1217 (OauthRSType::Basic { authz_secret, .. }, false) => {
1218 match client_auth.client_secret {
1219 Some(secret) => {
1220 if authz_secret.ct_eq(&secret) {
1221 true
1222 } else {
1223 info!("Invalid OAuth2 client_id secret");
1224 return Err(Oauth2Error::AuthenticationRequired);
1225 }
1226 }
1227 None => {
1228 info!(
1230 "Invalid OAuth2 authentication - no secret in access token request - this can happen if you're expecting a public client and configured a basic one."
1231 );
1232 return Err(Oauth2Error::AuthenticationRequired);
1233 }
1234 }
1235 }
1236 (OauthRSType::Public { .. }, _) => false,
1238 };
1239
1240 match &token_req.grant_type {
1242 GrantTypeReq::AuthorizationCode {
1243 code,
1244 redirect_uri,
1245 code_verifier,
1246 } => self.check_oauth2_token_exchange_authorization_code(
1247 &o2rs,
1248 code,
1249 redirect_uri,
1250 code_verifier.as_deref(),
1251 ct,
1252 ),
1253 GrantTypeReq::ClientCredentials { scope } => {
1254 if client_authentication_valid {
1255 self.check_oauth2_token_client_credentials(&o2rs, scope.as_ref(), ct)
1256 } else {
1257 security_info!(
1258 "Unable to proceed with client credentials grant unless client authentication is provided and valid"
1259 );
1260 Err(Oauth2Error::AuthenticationRequired)
1261 }
1262 }
1263 GrantTypeReq::RefreshToken {
1264 refresh_token,
1265 scope,
1266 } => self.check_oauth2_token_refresh(&o2rs, refresh_token, scope.as_ref(), ct),
1267 GrantTypeReq::TokenExchange {
1268 subject_token,
1269 subject_token_type,
1270 requested_token_type,
1271 audience,
1272 resource,
1273 actor_token,
1274 actor_token_type,
1275 scope,
1276 } => {
1277 if actor_token.is_some() || actor_token_type.is_some() {
1278 warn!("actor_token is not supported for token exchange");
1279 return Err(Oauth2Error::InvalidRequest);
1280 }
1281
1282 self.check_oauth2_token_exchange_service_account(
1283 &o2rs,
1284 subject_token,
1285 subject_token_type,
1286 requested_token_type.as_deref(),
1287 audience.as_deref(),
1288 resource.as_deref(),
1289 scope.as_ref(),
1290 ct,
1291 )
1292 }
1293 GrantTypeReq::DeviceCode { device_code, scope } => {
1294 self.check_oauth2_device_code_status(device_code, scope)
1295 }
1296 }
1297 }
1298
1299 #[instrument(level = "info", skip(self))]
1300 pub fn handle_oauth2_start_device_flow(
1301 &mut self,
1302 _client_auth_info: ClientAuthInfo,
1303 _client_id: &str,
1304 _scope: &Option<BTreeSet<String>>,
1305 _eventid: Uuid,
1306 ) -> Result<DeviceAuthorizationResponse, Oauth2Error> {
1307 Err(Oauth2Error::InvalidGrant)
1337
1338 }
1345
1346 #[instrument(level = "info", skip(self))]
1347 fn check_oauth2_device_code_status(
1348 &mut self,
1349 device_code: &str,
1350 scope: &Option<BTreeSet<String>>,
1351 ) -> Result<AccessTokenResponse, Oauth2Error> {
1352 error!(
1355 "haven't done the device grant yet! Got device_code={} scope={:?}",
1356 device_code, scope
1357 );
1358 Err(Oauth2Error::AuthorizationPending)
1359
1360 }
1363
1364 #[instrument(level = "debug", skip_all)]
1365 pub fn check_oauth2_authorise_permit(
1366 &mut self,
1367 ident: &Identity,
1368 consent_token: &str,
1369 ct: Duration,
1370 ) -> Result<AuthorisePermitSuccess, OperationError> {
1371 let account_uuid = ident.get_uuid();
1372
1373 let consent_token_jwe = JweCompact::from_str(consent_token).map_err(|err| {
1374 error!(?err, "Consent token is not a valid jwe compact");
1375 OperationError::InvalidSessionState
1376 })?;
1377
1378 let consent_req: ConsentToken = self
1379 .oauth2rs
1380 .inner
1381 .consent_key
1382 .decipher(&consent_token_jwe)
1383 .map_err(|err| {
1384 error!(?err, "Failed to decrypt consent request");
1385 OperationError::CryptographyError
1386 })
1387 .and_then(|jwe| {
1388 jwe.from_json().map_err(|err| {
1389 error!(?err, "Failed to deserialise consent request");
1390 OperationError::SerdeJsonError
1391 })
1392 })?;
1393
1394 if consent_req.ident_id != ident.get_event_origin_id() {
1396 security_info!("consent request ident id does not match the identity of our UAT.");
1397 return Err(OperationError::InvalidSessionState);
1398 }
1399
1400 if consent_req.session_id != ident.get_session_id() {
1402 security_info!("consent request session id does not match the session id of our UAT.");
1403 return Err(OperationError::InvalidSessionState);
1404 }
1405
1406 if consent_req.expiry <= ct.as_secs() {
1407 error!("Failed to decrypt consent request");
1409 return Err(OperationError::CryptographyError);
1410 }
1411
1412 let expiry = ct.as_secs() + 60;
1414
1415 let o2rs = self
1417 .oauth2rs
1418 .inner
1419 .rs_set_get(&consent_req.client_id)
1420 .ok_or_else(|| {
1421 admin_error!("Invalid consent request OAuth2 client_id");
1422 OperationError::InvalidRequestState
1423 })?;
1424
1425 let xchg_code = TokenExchangeCode {
1427 account_uuid,
1428 session_id: ident.get_session_id(),
1429 expiry,
1430 code_challenge: consent_req.code_challenge,
1431 redirect_uri: consent_req.redirect_uri.clone(),
1432 scopes: consent_req.scopes.clone(),
1433 nonce: consent_req.nonce,
1434 auth_time: ident.last_verified_at(),
1435 };
1436
1437 let code_data_jwe = JweBuilder::into_json(&xchg_code)
1439 .map(|builder| builder.build())
1440 .map_err(|err| {
1441 error!(?err, "Unable to encode xchg_code data");
1442 OperationError::SerdeJsonError
1443 })?;
1444
1445 let code = o2rs
1446 .key_object
1447 .jwe_a128gcm_encrypt(&code_data_jwe, ct)
1448 .map(|code| code.to_string())
1449 .map_err(|err| {
1450 error!(?err, "Unable to encrypt xchg_code");
1451 OperationError::CryptographyError
1452 })?;
1453
1454 let modlist = ModifyList::new_list(vec![
1459 Modify::Removed(
1460 Attribute::OAuth2ConsentScopeMap,
1461 PartialValue::Refer(o2rs.uuid),
1462 ),
1463 Modify::Present(
1464 Attribute::OAuth2ConsentScopeMap,
1465 Value::OauthScopeMap(o2rs.uuid, consent_req.scopes.iter().cloned().collect()),
1466 ),
1467 ]);
1468
1469 self.qs_write.internal_modify(
1470 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(account_uuid))),
1471 &modlist,
1472 )?;
1473
1474 Ok(AuthorisePermitSuccess {
1475 redirect_uri: consent_req.redirect_uri,
1476 state: consent_req.state,
1477 code,
1478 response_mode: consent_req.response_mode,
1479 })
1480 }
1481
1482 #[instrument(level = "debug", skip_all)]
1483 fn check_oauth2_token_exchange_authorization_code(
1484 &mut self,
1485 o2rs: &Oauth2RS,
1486 token_req_code: &str,
1487 token_req_redirect_uri: &Url,
1488 token_req_code_verifier: Option<&str>,
1489 ct: Duration,
1490 ) -> Result<AccessTokenResponse, Oauth2Error> {
1491 let jwe_compact = JweCompact::from_str(token_req_code).map_err(|_| {
1494 error!("Failed to deserialise a valid JWE");
1495 Oauth2Error::InvalidRequest
1496 })?;
1497
1498 let code_xchg: TokenExchangeCode = o2rs
1499 .key_object
1500 .jwe_decrypt(&jwe_compact)
1501 .map_err(|_| {
1502 admin_error!("Failed to decrypt token exchange request");
1503 Oauth2Error::InvalidRequest
1504 })
1505 .and_then(|jwe| {
1506 debug!(?jwe);
1507 jwe.from_json::<TokenExchangeCode>().map_err(|err| {
1508 error!(?err, "Failed to deserialise token exchange code");
1509 Oauth2Error::InvalidRequest
1510 })
1511 })?;
1512
1513 if code_xchg.expiry <= ct.as_secs() {
1514 error!("Expired token exchange request");
1515 return Err(Oauth2Error::InvalidRequest);
1516 }
1517
1518 if let Some(code_challenge) = code_xchg.code_challenge {
1522 let code_verifier = token_req_code_verifier
1524 .ok_or_else(|| {
1525 security_info!("PKCE code verification failed - code challenge is present, but no verifier was provided");
1526 Oauth2Error::InvalidRequest
1527 })?;
1528
1529 let verifier_secret = PkceS256Secret::from(code_verifier.to_string());
1530
1531 if !verifier_secret.verify(code_challenge) {
1532 security_info!(
1533 "PKCE code verification failed - this may indicate malicious activity"
1534 );
1535 return Err(Oauth2Error::InvalidRequest);
1536 }
1537 } else if o2rs.require_pkce() {
1538 security_info!(
1539 "PKCE code verification failed - no code challenge present in PKCE enforced mode"
1540 );
1541 return Err(Oauth2Error::InvalidRequest);
1542 } else if token_req_code_verifier.is_some() {
1543 security_info!(
1544 "PKCE code verification failed - a code verifier is present, but no code challenge in exchange"
1545 );
1546 return Err(Oauth2Error::InvalidRequest);
1547 }
1548
1549 if token_req_redirect_uri != &code_xchg.redirect_uri {
1551 security_info!("Invalid OAuth2 redirect_uri (differs from original request uri)");
1552 return Err(Oauth2Error::InvalidOrigin);
1553 }
1554
1555 let parent_session_id = Some(code_xchg.session_id);
1574 let session_id = Uuid::new_v4();
1575
1576 let scopes = code_xchg.scopes;
1577 let session_ctx = OAuth2SessionContext {
1578 auth_time: code_xchg.auth_time,
1579 nonce: code_xchg.nonce,
1580 account_uuid: code_xchg.account_uuid,
1581 };
1582
1583 self.generate_access_token_response(
1584 o2rs,
1585 ct,
1586 scopes,
1587 parent_session_id,
1588 session_id,
1589 session_ctx,
1590 )
1591 }
1592
1593 #[instrument(level = "debug", skip_all)]
1594 fn check_oauth2_token_refresh(
1595 &mut self,
1596 o2rs: &Oauth2RS,
1597 refresh_token: &str,
1598 req_scopes: Option<&BTreeSet<String>>,
1599 ct: Duration,
1600 ) -> Result<AccessTokenResponse, Oauth2Error> {
1601 let jwe_compact = JweCompact::from_str(refresh_token).map_err(|_| {
1602 error!("Failed to deserialise a valid JWE");
1603 Oauth2Error::InvalidRequest
1604 })?;
1605
1606 let token: Oauth2TokenType = o2rs
1608 .key_object
1609 .jwe_decrypt(&jwe_compact)
1610 .map_err(|_| {
1611 admin_error!("Failed to decrypt refresh token request");
1612 Oauth2Error::InvalidRequest
1613 })
1614 .and_then(|jwe| {
1615 jwe.from_json().map_err(|err| {
1616 error!(?err, "Failed to deserialise token");
1617 Oauth2Error::InvalidRequest
1618 })
1619 })?;
1620
1621 match token {
1622 Oauth2TokenType::ClientAccess { .. } => {
1624 admin_error!("attempt to refresh with an access token");
1625 Err(Oauth2Error::InvalidRequest)
1626 }
1627 Oauth2TokenType::Refresh {
1628 scopes,
1629 parent_session_id,
1630 session_id,
1631 exp,
1632 uuid,
1633 iat,
1634 nbf: _,
1635 nonce,
1636 auth_time,
1637 } => {
1638 if exp <= ct.as_secs() as i64 {
1639 security_info!(?uuid, "refresh token has expired, ");
1640 return Err(Oauth2Error::InvalidGrant);
1641 }
1642
1643 let valid = self
1646 .check_oauth2_account_uuid_valid(uuid, session_id, parent_session_id, iat, ct)
1647 .map_err(|_| admin_error!("Account is not valid"));
1648
1649 let Ok(Some(entry)) = valid else {
1650 security_info!(
1651 ?uuid,
1652 "access token has no account not valid, returning inactive"
1653 );
1654 return Err(Oauth2Error::InvalidGrant);
1655 };
1656
1657 let oauth2_session = entry
1659 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
1660 .and_then(|map| map.get(&session_id))
1661 .ok_or_else(|| {
1662 security_info!(
1663 ?session_id,
1664 "No OAuth2 session found, unable to proceed with refresh"
1665 );
1666 Oauth2Error::InvalidGrant
1667 })?;
1668
1669 if iat < oauth2_session.issued_at.unix_timestamp() {
1674 security_info!(
1675 ?session_id,
1676 "Attempt to reuse a refresh token detected, destroying session"
1677 );
1678
1679 let modlist = ModifyList::new_list(vec![Modify::Removed(
1681 Attribute::OAuth2Session,
1682 PartialValue::Refer(session_id),
1683 )]);
1684
1685 self.qs_write
1686 .internal_modify(
1687 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid))),
1688 &modlist,
1689 )
1690 .map_err(|e| {
1691 admin_error!("Failed to modify - revoke OAuth2 session {:?}", e);
1692 Oauth2Error::ServerError(e)
1693 })?;
1694
1695 return Err(Oauth2Error::InvalidGrant);
1696 }
1697
1698 let update_scopes = if let Some(req_scopes) = req_scopes {
1700 if req_scopes.is_subset(&scopes) {
1701 debug!("oauth2 scopes requested, checked as valid.");
1702 req_scopes.clone()
1705 } else {
1706 warn!("oauth2 scopes requested, invalid.");
1707 return Err(Oauth2Error::InvalidScope);
1708 }
1709 } else {
1710 debug!("No OAuth2 scopes requested, this is valid.");
1711 scopes
1713 };
1714
1715 let account_uuid = uuid;
1718 let session_ctx = OAuth2SessionContext {
1719 auth_time,
1720 nonce,
1721 account_uuid,
1722 };
1723
1724 self.generate_access_token_response(
1725 o2rs,
1726 ct,
1727 update_scopes,
1728 parent_session_id,
1729 session_id,
1730 session_ctx,
1731 )
1732 }
1733 }
1734 }
1735
1736 #[allow(clippy::too_many_arguments)]
1737 #[instrument(level = "debug", skip_all)]
1738 fn check_oauth2_token_exchange_service_account(
1739 &mut self,
1740 o2rs: &Oauth2RS,
1741 subject_token: &str,
1742 subject_token_type: &str,
1743 requested_token_type: Option<&str>,
1744 audience: Option<&str>,
1745 resource: Option<&str>,
1746 req_scopes: Option<&BTreeSet<String>>,
1747 ct: Duration,
1748 ) -> Result<AccessTokenResponse, Oauth2Error> {
1749 if let Some(rtt) = requested_token_type {
1750 if rtt != OAUTH2_TOKEN_TYPE_ACCESS_TOKEN {
1751 warn!(
1752 requested_token_type = rtt,
1753 "Unsupported requested_token_type in token exchange"
1754 );
1755 return Err(Oauth2Error::InvalidRequest);
1756 }
1757 }
1758
1759 if let Some(aud) = audience {
1760 if aud != o2rs.name {
1761 warn!(expected = %o2rs.name, requested = aud, "Token exchange audience mismatch");
1762 return Err(Oauth2Error::InvalidTarget);
1763 }
1764 }
1765
1766 if let Some(res) = resource {
1767 let parsed_resource = Url::parse(res).map_err(|_| {
1768 warn!(
1769 requested = res,
1770 "Invalid resource parameter in token exchange"
1771 );
1772 Oauth2Error::InvalidRequest
1773 })?;
1774
1775 if parsed_resource.fragment().is_some() {
1776 warn!(
1777 requested = res,
1778 "Resource parameter must not contain a fragment"
1779 );
1780 return Err(Oauth2Error::InvalidRequest);
1781 }
1782
1783 let origin = parsed_resource.origin();
1784 let target_allowed =
1785 o2rs.origins.contains(&origin) || o2rs.opaque_origins.contains(&parsed_resource);
1786 if !target_allowed {
1787 admin_warn!(requested = res, "Token exchange resource target mismatch");
1788 return Err(Oauth2Error::InvalidTarget);
1789 }
1790 }
1791
1792 if subject_token_type != TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS {
1793 security_info!(
1794 ?subject_token_type,
1795 "Unsupported subject_token_type in token exchange"
1796 );
1797 return Err(Oauth2Error::InvalidRequest);
1798 }
1799
1800 let jwsc = JwsCompact::from_str(subject_token).map_err(|_| {
1801 error!("Failed to deserialise subject token for token exchange");
1802 Oauth2Error::InvalidRequest
1803 })?;
1804
1805 let token = self
1806 .validate_and_parse_token_to_identity_token(&jwsc, ct)
1807 .map_err(|err| {
1808 security_info!(?err, "Unable to validate subject token for token exchange");
1809 Oauth2Error::InvalidRequest
1810 })?;
1811
1812 let (apit, entry) = match token {
1813 Token::ApiToken(apit, entry) => (apit, entry),
1814 Token::UserAuthToken(_) => {
1815 security_info!("Token exchange subject_token must be a service account api token");
1816 return Err(Oauth2Error::InvalidRequest);
1817 }
1818 };
1819
1820 let ident = self
1821 .process_apit_to_identity(&apit, Source::Internal, entry, ct)
1822 .map_err(|err| match err {
1823 OperationError::SessionExpired | OperationError::NotAuthenticated => {
1824 security_info!(
1825 ?err,
1826 "Service account api token rejected during token exchange"
1827 );
1828 Oauth2Error::InvalidRequest
1829 }
1830 err => Oauth2Error::ServerError(err),
1831 })?;
1832
1833 let (_req_scopes, granted_scopes) =
1834 process_requested_scopes_for_identity(o2rs, &ident, req_scopes)?;
1835
1836 let session_id = Uuid::new_v4();
1837 let parent_session_id = None;
1838 let session_ctx = OAuth2SessionContext {
1839 auth_time: None,
1841 account_uuid: apit.account_id,
1842 nonce: None,
1843 };
1844
1845 self.generate_access_token_response(
1846 o2rs,
1847 ct,
1848 granted_scopes,
1849 parent_session_id,
1850 session_id,
1851 session_ctx,
1852 )
1853 }
1854
1855 fn check_oauth2_token_client_credentials(
1856 &mut self,
1857 o2rs: &Oauth2RS,
1858 req_scopes: Option<&BTreeSet<String>>,
1859 ct: Duration,
1860 ) -> Result<AccessTokenResponse, Oauth2Error> {
1861 let req_scopes = req_scopes.cloned().unwrap_or_default();
1862
1863 validate_scopes(&req_scopes)?;
1865
1866 let avail_scopes: Vec<String> = req_scopes
1868 .intersection(&o2rs.client_scopes)
1869 .map(|s| s.to_string())
1870 .collect();
1871
1872 if avail_scopes.len() != req_scopes.len() {
1873 admin_warn!(
1874 ident = %o2rs.name,
1875 requested_scopes = ?req_scopes,
1876 available_scopes = ?o2rs.client_scopes,
1877 "Client does not have access to the requested scopes"
1878 );
1879 return Err(Oauth2Error::AccessDenied);
1880 }
1881
1882 let granted_scopes = avail_scopes
1885 .into_iter()
1886 .chain(o2rs.client_sup_scopes.iter().cloned())
1887 .collect::<BTreeSet<_>>();
1888
1889 let odt_ct = OffsetDateTime::UNIX_EPOCH + ct;
1890 let iat = ct.as_secs() as i64;
1891 let exp = iat + OAUTH2_ACCESS_TOKEN_EXPIRY as i64;
1892 let odt_exp = odt_ct + Duration::from_secs(OAUTH2_ACCESS_TOKEN_EXPIRY as u64);
1893 let expires_in = OAUTH2_ACCESS_TOKEN_EXPIRY;
1894
1895 let session_id = Uuid::new_v4();
1896
1897 let scope = granted_scopes.clone();
1898
1899 let uuid = o2rs.uuid;
1900
1901 let access_token_raw = Oauth2TokenType::ClientAccess {
1902 scopes: granted_scopes,
1903 session_id,
1904 uuid,
1905 exp,
1906 iat,
1907 nbf: iat,
1908 };
1909
1910 let access_token_data = JweBuilder::into_json(&access_token_raw)
1911 .map(|builder| builder.build())
1912 .map_err(|err| {
1913 error!(?err, "Unable to encode token data");
1914 Oauth2Error::ServerError(OperationError::SerdeJsonError)
1915 })?;
1916
1917 let access_token = o2rs
1918 .key_object
1919 .jwe_a128gcm_encrypt(&access_token_data, ct)
1920 .map(|jwe| jwe.to_string())
1921 .map_err(|err| {
1922 error!(?err, "Unable to encode token data");
1923 Oauth2Error::ServerError(OperationError::CryptographyError)
1924 })?;
1925
1926 let session = Value::Oauth2Session(
1928 session_id,
1929 Oauth2Session {
1930 parent: None,
1931 state: SessionState::ExpiresAt(odt_exp),
1932 issued_at: odt_ct,
1933 rs_uuid: o2rs.uuid,
1934 },
1935 );
1936
1937 let modlist =
1939 ModifyList::new_list(vec![Modify::Present(Attribute::OAuth2Session, session)]);
1940
1941 self.qs_write
1942 .internal_modify(
1943 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid))),
1944 &modlist,
1945 )
1946 .map_err(|e| {
1947 admin_error!("Failed to persist OAuth2 session record {:?}", e);
1948 Oauth2Error::ServerError(e)
1949 })?;
1950
1951 Ok(AccessTokenResponse {
1952 access_token,
1953 token_type: AccessTokenType::Bearer,
1954 issued_token_type: Some(IssuedTokenType::AccessToken),
1955 expires_in,
1956 refresh_token: None,
1957 scope,
1958 id_token: None,
1959 })
1960 }
1961
1962 fn generate_access_token_response(
1963 &mut self,
1964 o2rs: &Oauth2RS,
1965 ct: Duration,
1966 scopes: BTreeSet<String>,
1967 parent_session_id: Option<Uuid>,
1968 session_id: Uuid,
1969 session_ctx: OAuth2SessionContext,
1970 ) -> Result<AccessTokenResponse, Oauth2Error> {
1971 let odt_ct = OffsetDateTime::UNIX_EPOCH + ct;
1972 let iat = ct.as_secs() as i64;
1973
1974 let expiry = odt_ct + Duration::from_secs(OAUTH2_ACCESS_TOKEN_EXPIRY as u64);
1985 let expires_in = OAUTH2_ACCESS_TOKEN_EXPIRY;
1986 let refresh_expiry = iat + o2rs.refresh_token_expiry as i64;
1987 let odt_refresh_expiry = odt_ct + Duration::from_secs(o2rs.refresh_token_expiry as u64);
1988
1989 let scope = scopes.clone();
1990
1991 let iss = o2rs.iss.clone();
1992
1993 let exp = expiry.unix_timestamp();
1995
1996 let aud = o2rs.name.clone();
1997
1998 let client_id = o2rs.name.clone();
1999
2000 let entry = match self.qs_write.internal_search_uuid(session_ctx.account_uuid) {
2001 Ok(entry) => entry,
2002 Err(err) => return Err(Oauth2Error::ServerError(err)),
2003 };
2004
2005 let auth_time = session_ctx.auth_time;
2006
2007 let id_token = if scopes.contains(OAUTH2_SCOPE_OPENID) {
2008 let amr = None;
2024
2025 let account = match Account::try_from_entry_rw(&entry, &mut self.qs_write) {
2026 Ok(account) => account,
2027 Err(err) => return Err(Oauth2Error::ServerError(err)),
2028 };
2029
2030 let s_claims = s_claims_for_account(o2rs, &account, &scopes);
2031 let extra_claims = extra_claims_for_account(&account, &o2rs.claim_map, &scopes);
2032
2033 let oidc = OidcToken {
2034 iss: iss.clone(),
2035 sub: OidcSubject::U(session_ctx.account_uuid),
2036 aud: aud.clone(),
2037 iat,
2038 nbf: Some(iat),
2039 exp,
2040 auth_time: auth_time.map(|at| at.unix_timestamp()),
2041 nonce: session_ctx.nonce.clone(),
2042 at_hash: None,
2043 acr: None,
2044 amr,
2045 azp: Some(o2rs.name.clone()),
2046 jti: Some(session_id.to_string()),
2047 s_claims,
2048 claims: extra_claims,
2049 };
2050
2051 trace!(?oidc);
2052 let oidc = JwsBuilder::into_json(&oidc)
2053 .map(|builder| builder.build())
2054 .map_err(|err| {
2055 admin_error!(?err, "Unable to encode access token data");
2056 Oauth2Error::ServerError(OperationError::InvalidState)
2057 })?;
2058
2059 let jwt_signed = match o2rs.sign_alg {
2060 SignatureAlgo::Es256 => o2rs.key_object.jws_es256_sign(&oidc, ct),
2061 SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_sign(&oidc, ct),
2062 }
2063 .map_err(|err| {
2064 error!(?err, "Unable to encode oidc token data");
2065 Oauth2Error::ServerError(OperationError::InvalidState)
2066 })?;
2067
2068 Some(jwt_signed.to_string())
2069 } else {
2070 None
2072 };
2073
2074 let access_token_data = OAuth2RFC9068Token {
2076 iss: iss.to_string(),
2077 sub: session_ctx.account_uuid,
2078 aud,
2079 exp,
2080 nbf: iat,
2081 iat,
2082 jti: session_id,
2083 client_id,
2084 extensions: OAuth2RFC9068TokenExtensions {
2085 auth_time: auth_time.map(|at| at.unix_timestamp()),
2086 acr: None,
2087 amr: None,
2088 scope: scopes.clone(),
2089 nonce: session_ctx.nonce.clone(),
2090 session_id,
2091 parent_session_id,
2092 },
2093 };
2094
2095 let access_token_data = JwsBuilder::into_json(&access_token_data)
2096 .map(|builder| builder.set_typ(Some("at+jwt")).build())
2097 .map_err(|err| {
2098 error!(?err, "Unable to encode access token data");
2099 Oauth2Error::ServerError(OperationError::InvalidState)
2100 })?;
2101
2102 let access_token = match o2rs.sign_alg {
2103 SignatureAlgo::Es256 => o2rs.key_object.jws_es256_sign(&access_token_data, ct),
2104 SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_sign(&access_token_data, ct),
2105 }
2106 .map_err(|e| {
2107 admin_error!(err = ?e, "Unable to sign access token data");
2108 Oauth2Error::ServerError(OperationError::InvalidState)
2109 })?;
2110
2111 let refresh_token_raw = Oauth2TokenType::Refresh {
2112 scopes,
2113 parent_session_id,
2114 session_id,
2115 exp: refresh_expiry,
2116 uuid: session_ctx.account_uuid,
2117 iat,
2118 nbf: iat,
2119 nonce: session_ctx.nonce,
2120 auth_time,
2121 };
2122
2123 let refresh_token_data = JweBuilder::into_json(&refresh_token_raw)
2124 .map(|builder| builder.build())
2125 .map_err(|err| {
2126 error!(?err, "Unable to encode token data");
2127 Oauth2Error::ServerError(OperationError::SerdeJsonError)
2128 })?;
2129
2130 let refresh_token = o2rs
2131 .key_object
2132 .jwe_a128gcm_encrypt(&refresh_token_data, ct)
2133 .map(|jwe| jwe.to_string())
2134 .map_err(|err| {
2135 error!(?err, "Unable to encrypt token data");
2136 Oauth2Error::ServerError(OperationError::CryptographyError)
2137 })?;
2138
2139 let session = Value::Oauth2Session(
2142 session_id,
2143 Oauth2Session {
2144 parent: parent_session_id,
2145 state: SessionState::ExpiresAt(odt_refresh_expiry),
2146 issued_at: odt_ct,
2147 rs_uuid: o2rs.uuid,
2148 },
2149 );
2150
2151 let modlist = ModifyList::new_list(vec![
2153 Modify::Present(Attribute::OAuth2Session, session),
2157 ]);
2158
2159 self.qs_write
2160 .internal_modify(
2161 &filter!(f_eq(
2162 Attribute::Uuid,
2163 PartialValue::Uuid(session_ctx.account_uuid)
2164 )),
2165 &modlist,
2166 )
2167 .map_err(|e| {
2168 admin_error!("Failed to persist OAuth2 session record {:?}", e);
2169 Oauth2Error::ServerError(e)
2170 })?;
2171
2172 Ok(AccessTokenResponse {
2173 access_token: access_token.to_string(),
2174 token_type: AccessTokenType::Bearer,
2175 issued_token_type: Some(IssuedTokenType::AccessToken),
2176 expires_in,
2177 refresh_token: Some(refresh_token),
2178 scope,
2179 id_token,
2180 })
2181 }
2182
2183 #[cfg(test)]
2184 fn reflect_oauth2_token(&mut self, token: &str) -> Result<Oauth2TokenType, OperationError> {
2185 let jwec = JweCompact::from_str(token).map_err(|err| {
2186 error!(?err, "Failed to deserialise a valid JWE");
2187 OperationError::InvalidSessionState
2188 })?;
2189
2190 let unverified_kid = jwec.header().kid.as_ref().ok_or_else(|| {
2191 error!("Token does not contain encryption key id");
2192 OperationError::InvalidSessionState
2193 })?;
2194
2195 let o2rs = self
2196 .oauth2rs
2197 .inner
2198 .rs_from_kid(unverified_kid)
2199 .ok_or_else(|| {
2200 debug!("Invalid OAuth2 key id");
2201 OperationError::InvalidSessionState
2202 })?;
2203
2204 o2rs.key_object
2205 .jwe_decrypt(&jwec)
2206 .map_err(|_| {
2207 admin_error!("Failed to decrypt token introspection request");
2208 OperationError::CryptographyError
2209 })
2210 .and_then(|jwe| {
2211 jwe.from_json().map_err(|err| {
2212 error!(?err, "Failed to deserialise token");
2213 OperationError::InvalidSessionState
2214 })
2215 })
2216 }
2217}
2218
2219impl IdmServerProxyReadTransaction<'_> {
2220 #[instrument(level = "debug", skip_all)]
2221 pub fn check_oauth2_authorisation(
2222 &self,
2223 maybe_ident: Option<&Identity>,
2224 auth_req: &AuthorisationRequest,
2225 auth_req_ctx: &AuthorisationRequestContext,
2226 ct: Duration,
2227 ) -> Result<AuthoriseResponse, Oauth2Error> {
2228 trace!(?auth_req, ?auth_req_ctx);
2232
2233 if auth_req.response_type != ResponseType::Code {
2234 admin_warn!("Unsupported OAuth2 response_type (should be 'code')");
2235 return Err(Oauth2Error::UnsupportedResponseType);
2236 }
2237
2238 let Some(response_mode) = auth_req.get_response_mode() else {
2239 warn!(
2240 "Invalid response_mode {:?} for response_type {:?}",
2241 auth_req.response_mode, auth_req.response_type
2242 );
2243 return Err(Oauth2Error::InvalidRequest);
2244 };
2245
2246 let response_mode = match response_mode {
2247 ResponseMode::Query => SupportedResponseMode::Query,
2248 ResponseMode::Fragment => SupportedResponseMode::Fragment,
2249 ResponseMode::FormPost => {
2250 warn!(
2251 "Invalid response mode form_post requested - many clients request this incorrectly but proceed with response_mode=query. Remapping to query."
2252 );
2253 warn!("This behaviour WILL BE REMOVED in a future release.");
2254 SupportedResponseMode::Query
2255 }
2256 ResponseMode::Invalid => {
2257 warn!("Invalid response mode requested, unable to proceed");
2258 return Err(Oauth2Error::InvalidRequest);
2259 }
2260 };
2261
2262 if auth_req.prompt.len() > 4 {
2263 warn!(
2264 "Request contained too many prompt values. Max: 4, Provided: {}",
2265 auth_req.prompt.len()
2266 );
2267 return Err(Oauth2Error::InvalidRequest);
2268 }
2269
2270 let invalid_prompts: Vec<&str> = auth_req
2271 .prompt
2272 .iter()
2273 .filter_map(|v| match v {
2274 Prompt::Invalid(s) => Some(s.as_str()),
2275 _ => None,
2276 })
2277 .collect();
2278
2279 if !invalid_prompts.is_empty() {
2280 warn!("Invalid prompt value(s): {:?}", invalid_prompts);
2281 return Err(Oauth2Error::InvalidRequest);
2282 }
2283
2284 if auth_req.prompt.contains(&Prompt::None) && auth_req.prompt.len() > 1 {
2285 warn!("Prompt cannot be none and contain other values at the same time");
2286 return Err(Oauth2Error::InvalidRequest);
2287 }
2288
2289 let o2rs = self
2301 .oauth2rs
2302 .inner
2303 .rs_set_get(&auth_req.client_id)
2304 .ok_or_else(|| {
2305 warn!(
2306 "Invalid OAuth2 client_id ({}) Have you configured the OAuth2 resource server?",
2307 &auth_req.client_id
2308 );
2309 Oauth2Error::InvalidClientId
2310 })?;
2311
2312 let auth_req_uri_is_loopback = check_is_loopback(&auth_req.redirect_uri);
2318 let type_allows_localhost_redirect = o2rs.type_.allow_localhost_redirect();
2319
2320 let loopback_uri_matched = auth_req_uri_is_loopback && type_allows_localhost_redirect;
2322
2323 let strict_redirect_uri_matched = o2rs.redirect_uris.contains(&auth_req.redirect_uri);
2325
2326 let opaque_origin_matched = o2rs.opaque_origins.contains(&auth_req.redirect_uri);
2328
2329 let redirect_origin_is_secure = opaque_origin_matched
2331 || auth_req_uri_is_loopback
2332 || auth_req.redirect_uri.scheme() == "https";
2333
2334 let valid_match_condition_asserted =
2336 loopback_uri_matched || strict_redirect_uri_matched || opaque_origin_matched;
2337
2338 if valid_match_condition_asserted {
2339 debug!(
2340 ?loopback_uri_matched,
2341 ?strict_redirect_uri_matched,
2342 ?opaque_origin_matched,
2343 "valid redirect uri match condition met."
2344 );
2345 } else {
2346 let could_allow_localhost_redirect =
2352 o2rs.type_.allow_localhost_redirect_could_be_possible();
2353
2354 if auth_req_uri_is_loopback
2355 && could_allow_localhost_redirect
2356 && !type_allows_localhost_redirect
2357 {
2358 warn!(redirect_uri = %auth_req.redirect_uri, "OAuth2 redirect_uri returns to localhost, but localhost redirection is not allowed. See 'kanidm system oauth2 enable-localhost-redirects'");
2359 } else {
2360 warn!(
2362 "Invalid OAuth2 redirect_uri (must be an exact match to a redirect-url) - got {} from client but configured uris do not match (check oauth2_rs_origin entries)",
2363 auth_req.redirect_uri.as_str()
2364 );
2365 }
2366
2367 return Err(Oauth2Error::InvalidOrigin);
2369 }
2370
2371 if o2rs.origin_secure_required && !redirect_origin_is_secure {
2373 warn!(
2374 "Invalid OAuth2 redirect_uri scheme (must be a secure origin) - got {} instead. Secure origins are required when *at least* one redirect_uri is https, then all uri's must also be secure origins.",
2375 auth_req.redirect_uri
2376 );
2377 return Err(Oauth2Error::InvalidOrigin);
2378 }
2379
2380 let code_challenge = if let Some(pkce_request) = &auth_req.pkce_request {
2383 if !o2rs.require_pkce() {
2384 security_info!(?o2rs.name, "Insecure OAuth2 client configuration - PKCE is not enforced, but client is requesting it!");
2385 }
2386 if pkce_request.code_challenge_method != CodeChallengeMethod::S256 {
2388 admin_warn!("Invalid OAuth2 code_challenge_method (must be 'S256')");
2389 return Err(Oauth2Error::InvalidRequest);
2390 }
2391 Some(pkce_request.code_challenge.clone())
2392 } else if o2rs.require_pkce() {
2393 security_error!(?o2rs.name, "No PKCE code challenge was provided with client in enforced PKCE mode");
2394 return Err(Oauth2Error::InvalidRequest);
2395 } else {
2396 security_info!(?o2rs.name, "Insecure client configuration - PKCE is not enforced");
2397 None
2398 };
2399
2400 let Some(ident) = maybe_ident else {
2422 debug!("No identity available, assume authentication required");
2423
2424 if auth_req.prompt.contains(&Prompt::None) {
2427 debug!("prompt=none was requested, but no identity is available, returning error");
2428 return Err(Oauth2Error::LoginRequired);
2429 } else {
2430 return Ok(AuthoriseResponse::AuthenticationRequired {
2431 client_name: o2rs.displayname.clone(),
2432 login_hint: auth_req.oidc_ext.login_hint.clone(),
2433 });
2434 }
2435 };
2436
2437 let max_age = if auth_req.prompt.contains(&Prompt::Login) {
2441 Some(0)
2442 } else {
2443 auth_req
2444 .max_age
2445 .map(|m| m.clamp(0, OAUTH2_OIDC_MAX_AGE_CLAMP))
2446 };
2447
2448 let auth_time = ident.last_verified_at();
2449
2450 if let Some(max_age) = max_age {
2451 let session_recently_validated = if max_age <= 0 {
2452 false
2454 } else {
2455 let odt_prompt_deadline =
2456 (OffsetDateTime::UNIX_EPOCH + ct) - Duration::from_secs(max_age as u64);
2457
2458 auth_time
2460 .map(|at| {
2461 let at = at.truncate_to_second();
2463 let odt_prompt_deadline = odt_prompt_deadline.truncate_to_second();
2464 at > odt_prompt_deadline
2465 })
2466 .unwrap_or_default()
2467 };
2468
2469 if auth_req_ctx.resumed {
2470 debug!("auth_request_context indicates that the session was just authenticated - proceeding.");
2473 } else if session_recently_validated {
2474 debug!("prompt=login was requested, session is fresh enough to proceed");
2475 } else {
2476 debug!("prompt=login was requested, forcing re-authentication");
2477 return Ok(AuthoriseResponse::ReauthenticationRequired {
2478 client_name: o2rs.displayname.clone(),
2479 });
2480 }
2481 }
2482
2483 let account_uuid = ident.get_uuid();
2484
2485 if account_uuid == UUID_ANONYMOUS {
2487 admin_error!(
2488 "Invalid OAuth2 request - refusing to allow user that authenticated with anonymous"
2489 );
2490 return Err(Oauth2Error::AccessDenied);
2491 }
2492
2493 let (req_scopes, granted_scopes) =
2495 process_requested_scopes_for_identity(o2rs, ident, Some(&auth_req.scope))?;
2496
2497 let openid_requested = req_scopes.contains(OAUTH2_SCOPE_OPENID);
2500
2501 let consent_previously_granted =
2502 if let Some(consent_scopes) = ident.get_oauth2_consent_scopes(o2rs.uuid) {
2503 trace!(?granted_scopes);
2504 trace!(?consent_scopes);
2505 granted_scopes.eq(consent_scopes)
2506 } else {
2507 false
2508 };
2509
2510 let session_id = ident.get_session_id();
2511
2512 let consent_required = (
2513 !consent_previously_granted
2515 ||
2517 (!o2rs.is_basic() && loopback_uri_matched)
2521 )
2523 &&
2525 o2rs.enable_consent_prompt();
2527
2528 if !consent_required {
2529 if event_enabled!(tracing::Level::DEBUG) {
2530 let pretty_scopes: Vec<String> =
2531 granted_scopes.iter().map(|s| s.to_owned()).collect();
2532 debug!(
2533 pretty_scopes = pretty_scopes.join(","),
2534 prompt_enabled = o2rs.enable_consent_prompt(),
2535 previously_granted = consent_previously_granted,
2536 "Consent flow passed"
2537 );
2538 }
2539
2540 let expiry = ct.as_secs() + 60;
2542
2543 let xchg_code = TokenExchangeCode {
2545 account_uuid,
2546 session_id,
2547 expiry,
2548 code_challenge,
2549 redirect_uri: auth_req.redirect_uri.clone(),
2550 scopes: granted_scopes.into_iter().collect(),
2551 nonce: auth_req.nonce.clone(),
2552 auth_time,
2553 };
2554
2555 let code_data_jwe = JweBuilder::into_json(&xchg_code)
2557 .map(|builder| builder.build())
2558 .map_err(|err| {
2559 error!(?err, "Unable to encode xchg_code data");
2560 Oauth2Error::ServerError(OperationError::SerdeJsonError)
2561 })?;
2562
2563 let code = o2rs
2564 .key_object
2565 .jwe_a128gcm_encrypt(&code_data_jwe, ct)
2566 .map(|jwe| jwe.to_string())
2567 .map_err(|err| {
2568 error!(?err, "Unable to encrypt xchg_code data");
2569 Oauth2Error::ServerError(OperationError::CryptographyError)
2570 })?;
2571
2572 Ok(AuthoriseResponse::Permitted(AuthorisePermitSuccess {
2573 redirect_uri: auth_req.redirect_uri.clone(),
2574 state: auth_req.state.clone(),
2575 code,
2576 response_mode,
2577 }))
2578 } else {
2579 if auth_req.prompt.contains(&Prompt::None) {
2583 debug!("prompt=none was requested, but consent is required, returning error");
2584 return Err(Oauth2Error::InteractionRequired);
2585 }
2586
2587 let mut pii_scopes = BTreeSet::default();
2601 if openid_requested {
2602 if granted_scopes.contains(OAUTH2_SCOPE_EMAIL) {
2604 pii_scopes.insert(OAUTH2_SCOPE_EMAIL.to_string());
2605 pii_scopes.insert("email_verified".to_string());
2606 }
2607 };
2608
2609 if granted_scopes.contains(OAUTH2_SCOPE_SSH_PUBLICKEYS) {
2610 pii_scopes.insert(OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string());
2611 }
2612
2613 let expiry = ct.as_secs() + 300;
2615
2616 let consent_req = ConsentToken {
2622 client_id: auth_req.client_id.clone(),
2623 ident_id: ident.get_event_origin_id(),
2624 expiry,
2625 session_id,
2626 state: auth_req.state.clone(),
2627 code_challenge,
2628 redirect_uri: auth_req.redirect_uri.clone(),
2629 scopes: granted_scopes.iter().cloned().collect(),
2630 nonce: auth_req.nonce.clone(),
2631 response_mode,
2632 };
2633
2634 let consent_jwe = JweBuilder::into_json(&consent_req)
2635 .map(|builder| builder.build())
2636 .map_err(|err| {
2637 error!(?err, "Unable to encode consent data");
2638 Oauth2Error::ServerError(OperationError::SerdeJsonError)
2639 })?;
2640
2641 let consent_token = self
2642 .oauth2rs
2643 .inner
2644 .consent_key
2645 .encipher::<JweA128GCMEncipher>(&consent_jwe)
2646 .map(|jwe_compact| jwe_compact.to_string())
2647 .map_err(|err| {
2648 error!(?err, "Unable to encrypt jwe");
2649 Oauth2Error::ServerError(OperationError::CryptographyError)
2650 })?;
2651
2652 Ok(AuthoriseResponse::ConsentRequested {
2653 client_name: o2rs.displayname.clone(),
2654 scopes: granted_scopes.into_iter().collect(),
2655 pii_scopes,
2656 consent_token,
2657 })
2658 }
2659 }
2660
2661 #[instrument(level = "debug", skip_all)]
2662 pub fn check_oauth2_authorise_reject(
2663 &self,
2664 ident: &Identity,
2665 consent_token: &str,
2666 ct: Duration,
2667 ) -> Result<AuthoriseReject, OperationError> {
2668 let jwe_compact = JweCompact::from_str(consent_token).map_err(|_| {
2669 error!("Failed to deserialise a valid JWE");
2670 OperationError::CryptographyError
2671 })?;
2672
2673 let consent_req: ConsentToken = self
2675 .oauth2rs
2676 .inner
2677 .consent_key
2678 .decipher(&jwe_compact)
2679 .map_err(|_| {
2680 admin_error!("Failed to decrypt consent request");
2681 OperationError::CryptographyError
2682 })
2683 .and_then(|jwe| {
2684 jwe.from_json().map_err(|err| {
2685 error!(?err, "Failed to deserialise consent request");
2686 OperationError::SerdeJsonError
2687 })
2688 })?;
2689
2690 if consent_req.ident_id != ident.get_event_origin_id() {
2692 security_info!("consent request ident id does not match the identity of our UAT.");
2693 return Err(OperationError::InvalidSessionState);
2694 }
2695
2696 if consent_req.session_id != ident.get_session_id() {
2698 security_info!("consent request sessien id does not match the session id of our UAT.");
2699 return Err(OperationError::InvalidSessionState);
2700 }
2701
2702 if consent_req.expiry <= ct.as_secs() {
2703 error!("Failed to decrypt consent request");
2705 return Err(OperationError::CryptographyError);
2706 }
2707
2708 let _o2rs = self
2710 .oauth2rs
2711 .inner
2712 .rs_set_get(&consent_req.client_id)
2713 .ok_or_else(|| {
2714 admin_error!("Invalid consent request OAuth2 client_id");
2715 OperationError::InvalidRequestState
2716 })?;
2717
2718 Ok(AuthoriseReject {
2720 redirect_uri: consent_req.redirect_uri,
2721 response_mode: consent_req.response_mode,
2722 })
2723 }
2724
2725 #[instrument(level = "debug", skip_all)]
2726 pub fn check_oauth2_token_introspect(
2727 &mut self,
2728 intr_req: &AccessTokenIntrospectRequest,
2729 ct: Duration,
2730 ) -> Result<AccessTokenIntrospectResponse, Oauth2Error> {
2731 if let Ok(jwsc) = JwsCompact::from_str(&intr_req.token) {
2736 self.oauth2_token_introspect_jwt(&jwsc, ct)
2737 } else if let Ok(jwec) = JweCompact::from_str(&intr_req.token) {
2738 self.oauth2_token_introspect_jwe(&jwec, ct)
2739 } else {
2740 error!("Failed to deserialise a valid JWE");
2741 Err(Oauth2Error::AuthenticationRequired)
2742 }
2743 }
2744
2745 #[instrument(level = "trace", skip_all)]
2746 pub fn oauth2_token_introspect_jwt(
2747 &mut self,
2748 jwsc: &JwsCompact,
2749 ct: Duration,
2750 ) -> Result<AccessTokenIntrospectResponse, Oauth2Error> {
2751 let unverified_kid = jwsc.header().kid.as_ref().ok_or_else(|| {
2752 error!("Token does not contain signature key id");
2753 Oauth2Error::AuthenticationRequired
2754 })?;
2755
2756 let o2rs = self
2757 .oauth2rs
2758 .inner
2759 .rs_from_kid(unverified_kid)
2760 .ok_or_else(|| {
2761 warn!("Invalid OAuth2 key id");
2762 Oauth2Error::AuthenticationRequired
2763 })?;
2764
2765 let access_token = o2rs
2766 .key_object
2767 .jws_verify(jwsc)
2768 .map_err(|err| {
2769 error!(?err, "Unable to verify access token");
2770 Oauth2Error::AuthenticationRequired
2771 })
2772 .and_then(|jws| {
2773 jws.from_json().map_err(|err| {
2774 error!(?err, "Unable to deserialise access token");
2775 Oauth2Error::InvalidRequest
2776 })
2777 })?;
2778
2779 let OAuth2RFC9068Token::<_> {
2780 iss: _,
2781 sub,
2782 aud: _,
2783 exp,
2784 nbf,
2785 iat,
2786 jti,
2787 client_id: _,
2788 extensions:
2789 OAuth2RFC9068TokenExtensions {
2790 auth_time: _,
2791 acr: _,
2792 amr: _,
2793 scope: scopes,
2794 nonce: _,
2795 session_id,
2796 parent_session_id,
2797 },
2798 } = access_token;
2799
2800 if exp <= ct.as_secs() as i64 {
2802 security_info!(?sub, "access token has expired, returning inactive");
2803 return Ok(AccessTokenIntrospectResponse::inactive(jti));
2804 }
2805
2806 let prefer_short_username = o2rs.prefer_short_username;
2807 let client_id = o2rs.name.clone();
2808 let iss = o2rs.iss.to_string();
2809
2810 let valid = self
2812 .check_oauth2_account_uuid_valid(sub, session_id, parent_session_id, iat, ct)
2813 .map_err(|_| admin_error!("Account is not valid"));
2814
2815 let Ok(Some(entry)) = valid else {
2816 security_info!(
2817 ?sub,
2818 "access token account is not valid, returning inactive"
2819 );
2820 return Ok(AccessTokenIntrospectResponse::inactive(jti));
2821 };
2822
2823 let account = match Account::try_from_entry_ro(&entry, &mut self.qs_read) {
2824 Ok(account) => account,
2825 Err(err) => return Err(Oauth2Error::ServerError(err)),
2826 };
2827
2828 let scope = scopes.clone();
2831
2832 let preferred_username = if prefer_short_username {
2833 Some(account.name().into())
2834 } else {
2835 Some(account.spn().into())
2836 };
2837
2838 let token_type = Some(AccessTokenType::Bearer);
2839 Ok(AccessTokenIntrospectResponse {
2840 active: true,
2841 scope,
2842 client_id: Some(client_id.clone()),
2843 username: preferred_username,
2844 token_type,
2845 iat: Some(iat),
2846 exp: Some(exp),
2847 nbf: Some(nbf),
2848 sub: Some(sub.to_string()),
2849 aud: Some(client_id),
2850 iss: Some(iss),
2851 jti,
2852 })
2853 }
2854
2855 #[instrument(level = "trace", skip_all)]
2856 pub fn oauth2_token_introspect_jwe(
2857 &mut self,
2858 jwec: &JweCompact,
2859 ct: Duration,
2860 ) -> Result<AccessTokenIntrospectResponse, Oauth2Error> {
2861 let unverified_kid = jwec.header().kid.as_ref().ok_or_else(|| {
2862 error!("Token does not contain signature key id");
2863 Oauth2Error::AuthenticationRequired
2864 })?;
2865
2866 let o2rs = self
2867 .oauth2rs
2868 .inner
2869 .rs_from_kid(unverified_kid)
2870 .ok_or_else(|| {
2871 warn!("Invalid OAuth2 key id");
2872 Oauth2Error::AuthenticationRequired
2873 })?;
2874
2875 let token: Oauth2TokenType = o2rs
2876 .key_object
2877 .jwe_decrypt(jwec)
2878 .map_err(|_| {
2879 admin_error!("Failed to decrypt token introspection request");
2880 Oauth2Error::AuthenticationRequired
2881 })
2882 .and_then(|jwe| {
2883 jwe.from_json().map_err(|err| {
2884 error!(?err, "Failed to deserialise token");
2885 Oauth2Error::InvalidRequest
2886 })
2887 })?;
2888
2889 match token {
2890 Oauth2TokenType::ClientAccess {
2891 scopes,
2892 session_id,
2893 uuid,
2894 exp,
2895 iat,
2896 nbf,
2897 } => {
2898 if exp <= ct.as_secs() as i64 {
2900 security_info!(?uuid, "access token has expired, returning inactive");
2901 return Ok(AccessTokenIntrospectResponse::inactive(session_id));
2902 }
2903
2904 let prefer_short_username = o2rs.prefer_short_username;
2905 let client_id = o2rs.name.clone();
2906 let iss = o2rs.iss.to_string();
2907
2908 let valid = self
2910 .check_oauth2_account_uuid_valid(uuid, session_id, None, iat, ct)
2911 .map_err(|_| admin_error!("Account is not valid"));
2912
2913 let Ok(Some(entry)) = valid else {
2914 security_info!(
2915 ?uuid,
2916 "access token account is not valid, returning inactive"
2917 );
2918 return Ok(AccessTokenIntrospectResponse::inactive(session_id));
2919 };
2920
2921 let scope = scopes.clone();
2922
2923 let token_type = Some(AccessTokenType::Bearer);
2924
2925 let username = if prefer_short_username {
2926 entry
2927 .get_ava_single_iname(Attribute::Name)
2928 .map(|s| s.to_string())
2929 } else {
2930 entry.get_ava_single_proto_string(Attribute::Spn)
2931 };
2932
2933 Ok(AccessTokenIntrospectResponse {
2934 active: true,
2935 scope,
2936 client_id: Some(client_id.clone()),
2937 username,
2938 token_type,
2939 iat: Some(iat),
2940 exp: Some(exp),
2941 nbf: Some(nbf),
2942 sub: Some(uuid.to_string()),
2943 aud: Some(client_id),
2944 iss: Some(iss),
2945 jti: session_id,
2946 })
2947 }
2948 Oauth2TokenType::Refresh { session_id, .. } => {
2949 Ok(AccessTokenIntrospectResponse::inactive(session_id))
2950 }
2951 }
2952 }
2953
2954 #[instrument(level = "debug", skip_all)]
2955 pub fn oauth2_openid_userinfo(
2956 &mut self,
2957 client_id: &str,
2958 token: &JwsCompact,
2959 ct: Duration,
2960 ) -> Result<OidcToken, Oauth2Error> {
2961 let o2rs: &Oauth2RS = unsafe {
2968 let s = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
2969 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
2970 Oauth2Error::InvalidClientId
2971 })?;
2972 &*(s as *const _)
2973 };
2974
2975 let access_token = o2rs
2976 .key_object
2977 .jws_verify(token)
2978 .map_err(|err| {
2979 error!(?err, "Unable to verify access token");
2980 Oauth2Error::InvalidRequest
2981 })
2982 .and_then(|jws| {
2983 jws.from_json().map_err(|err| {
2984 error!(?err, "Unable to deserialise access token");
2985 Oauth2Error::InvalidRequest
2986 })
2987 })?;
2988
2989 let OAuth2RFC9068Token::<_> {
2990 iss: _,
2991 sub,
2992 aud: _,
2993 exp,
2994 nbf,
2995 iat,
2996 jti: _,
2997 client_id: _,
2998 extensions:
2999 OAuth2RFC9068TokenExtensions {
3000 auth_time,
3001 acr: _,
3002 amr: _,
3003 scope: scopes,
3004 nonce,
3005 session_id,
3006 parent_session_id,
3007 },
3008 } = access_token;
3009 if exp <= ct.as_secs() as i64 {
3011 security_info!(?sub, "access token has expired, returning inactive");
3012 return Err(Oauth2Error::InvalidToken);
3013 }
3014
3015 let valid = self
3017 .check_oauth2_account_uuid_valid(sub, session_id, parent_session_id, iat, ct)
3018 .map_err(|_| admin_error!("Account is not valid"));
3019
3020 let Ok(Some(entry)) = valid else {
3021 security_info!(
3022 ?sub,
3023 "access token has account not valid, returning inactive"
3024 );
3025 return Err(Oauth2Error::InvalidToken);
3026 };
3027
3028 let account = match Account::try_from_entry_ro(&entry, &mut self.qs_read) {
3029 Ok(account) => account,
3030 Err(err) => return Err(Oauth2Error::ServerError(err)),
3031 };
3032
3033 let amr = None;
3034
3035 let iss = o2rs.iss.clone();
3036
3037 let s_claims = s_claims_for_account(o2rs, &account, &scopes);
3038 let extra_claims = extra_claims_for_account(&account, &o2rs.claim_map, &scopes);
3039
3040 Ok(OidcToken {
3043 iss,
3044 sub: OidcSubject::U(sub),
3045 aud: client_id.to_string(),
3046 iat,
3047 nbf: Some(nbf),
3048 exp,
3049 auth_time,
3050 nonce,
3051 at_hash: None,
3052 acr: None,
3053 amr,
3054 azp: Some(client_id.to_string()),
3055 jti: Some(session_id.to_string()),
3056 s_claims,
3057 claims: extra_claims,
3058 })
3059 }
3060
3061 #[instrument(level = "debug", skip_all)]
3062 pub fn oauth2_rfc8414_metadata(
3063 &self,
3064 client_id: &str,
3065 ) -> Result<Oauth2Rfc8414MetadataResponse, OperationError> {
3066 let o2rs = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
3067 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
3068 OperationError::NoMatchingEntries
3069 })?;
3070
3071 let issuer = o2rs.iss.clone();
3072 let authorization_endpoint = o2rs.authorization_endpoint.clone();
3073 let token_endpoint = o2rs.token_endpoint.clone();
3074 let revocation_endpoint = Some(o2rs.revocation_endpoint.clone());
3075 let introspection_endpoint = Some(o2rs.introspection_endpoint.clone());
3076 let jwks_uri = Some(o2rs.jwks_uri.clone());
3077 let scopes_supported = Some(o2rs.scopes_supported.iter().cloned().collect());
3078 let response_types_supported = vec![ResponseType::Code];
3079 let response_modes_supported = vec![ResponseMode::Query, ResponseMode::Fragment];
3080 let grant_types_supported = vec![
3081 GrantType::AuthorisationCode,
3082 GrantType::TokenExchange,
3083 GrantType::RefreshToken,
3084 ];
3085
3086 let token_endpoint_auth_methods_supported = if o2rs.is_basic() {
3087 vec![
3088 EndpointAuthMethod::ClientSecretBasic,
3089 EndpointAuthMethod::ClientSecretPost,
3090 ]
3091 } else {
3092 vec![EndpointAuthMethod::None]
3093 };
3094
3095 let revocation_endpoint_auth_methods_supported = vec![EndpointAuthMethod::None];
3096
3097 let introspection_endpoint_auth_methods_supported = vec![EndpointAuthMethod::None];
3098
3099 let service_documentation = Some(URL_SERVICE_DOCUMENTATION.clone());
3100
3101 let code_challenge_methods_supported = if o2rs.require_pkce() {
3102 vec![PkceAlg::S256]
3103 } else {
3104 Vec::with_capacity(0)
3105 };
3106
3107 Ok(Oauth2Rfc8414MetadataResponse {
3108 issuer,
3109 authorization_endpoint,
3110 token_endpoint,
3111 jwks_uri,
3112 registration_endpoint: None,
3113 scopes_supported,
3114 response_types_supported,
3115 response_modes_supported,
3116 grant_types_supported,
3117 token_endpoint_auth_methods_supported,
3118 token_endpoint_auth_signing_alg_values_supported: None,
3119 service_documentation,
3120 ui_locales_supported: None,
3121 op_policy_uri: None,
3122 op_tos_uri: None,
3123 revocation_endpoint,
3124 revocation_endpoint_auth_methods_supported,
3125 introspection_endpoint,
3126 introspection_endpoint_auth_methods_supported,
3127 introspection_endpoint_auth_signing_alg_values_supported: None,
3128 code_challenge_methods_supported,
3129 })
3130 }
3131
3132 #[instrument(level = "debug", skip_all)]
3133 pub fn oauth2_openid_discovery(
3134 &self,
3135 client_id: &str,
3136 ) -> Result<OidcDiscoveryResponse, OperationError> {
3137 let o2rs = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
3138 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
3139 OperationError::NoMatchingEntries
3140 })?;
3141
3142 let issuer = o2rs.iss.clone();
3143
3144 let authorization_endpoint = o2rs.authorization_endpoint.clone();
3145 let token_endpoint = o2rs.token_endpoint.clone();
3146 let userinfo_endpoint = Some(o2rs.userinfo_endpoint.clone());
3147 let jwks_uri = o2rs.jwks_uri.clone();
3148 let scopes_supported = Some(o2rs.scopes_supported.iter().cloned().collect());
3149 let response_types_supported = vec![ResponseType::Code];
3150 let response_modes_supported = vec![ResponseMode::Query, ResponseMode::Fragment];
3151
3152 let grant_types_supported = vec![
3155 GrantType::AuthorisationCode,
3156 GrantType::TokenExchange,
3157 GrantType::RefreshToken,
3158 ];
3159
3160 let subject_types_supported = vec![SubjectType::Public];
3161
3162 let id_token_signing_alg_values_supported = match &o2rs.sign_alg {
3163 SignatureAlgo::Es256 => vec![IdTokenSignAlg::ES256],
3164 SignatureAlgo::Rs256 => vec![IdTokenSignAlg::RS256],
3165 };
3166
3167 let userinfo_signing_alg_values_supported = None;
3168
3169 let token_endpoint_auth_methods_supported = if o2rs.is_basic() {
3170 vec![
3171 EndpointAuthMethod::ClientSecretBasic,
3172 EndpointAuthMethod::ClientSecretPost,
3173 ]
3174 } else {
3175 vec![EndpointAuthMethod::None]
3176 };
3177
3178 let display_values_supported = Some(vec![DisplayValue::Page]);
3179 let claim_types_supported = vec![ClaimType::Normal];
3180 let claims_supported = None;
3182 let service_documentation = Some(URL_SERVICE_DOCUMENTATION.clone());
3183
3184 let code_challenge_methods_supported = if o2rs.require_pkce() {
3185 vec![PkceAlg::S256]
3186 } else {
3187 Vec::with_capacity(0)
3188 };
3189
3190 let revocation_endpoint = Some(o2rs.revocation_endpoint.clone());
3193 let revocation_endpoint_auth_methods_supported = vec![EndpointAuthMethod::None];
3194
3195 let introspection_endpoint = Some(o2rs.introspection_endpoint.clone());
3196 let introspection_endpoint_auth_methods_supported = vec![EndpointAuthMethod::None];
3197
3198 Ok(OidcDiscoveryResponse {
3199 issuer,
3200 authorization_endpoint,
3201 token_endpoint,
3202 userinfo_endpoint,
3203 jwks_uri,
3204 registration_endpoint: None,
3205 scopes_supported,
3206 response_types_supported,
3207 response_modes_supported,
3208 grant_types_supported,
3209 acr_values_supported: None,
3210 subject_types_supported,
3211 id_token_signing_alg_values_supported,
3212 id_token_encryption_alg_values_supported: None,
3213 id_token_encryption_enc_values_supported: None,
3214 userinfo_signing_alg_values_supported,
3215 userinfo_encryption_alg_values_supported: None,
3216 userinfo_encryption_enc_values_supported: None,
3217 request_object_signing_alg_values_supported: None,
3218 request_object_encryption_alg_values_supported: None,
3219 request_object_encryption_enc_values_supported: None,
3220 token_endpoint_auth_methods_supported,
3221 token_endpoint_auth_signing_alg_values_supported: None,
3222 display_values_supported,
3223 claim_types_supported,
3224 claims_supported,
3225 service_documentation,
3226 claims_locales_supported: None,
3227 ui_locales_supported: None,
3228 claims_parameter_supported: false,
3229 request_parameter_supported: false,
3231 request_uri_parameter_supported: false,
3233 require_request_uri_registration: false,
3235 op_policy_uri: None,
3236 op_tos_uri: None,
3237 code_challenge_methods_supported,
3238 revocation_endpoint,
3240 revocation_endpoint_auth_methods_supported,
3241 introspection_endpoint,
3242 introspection_endpoint_auth_methods_supported,
3243 introspection_endpoint_auth_signing_alg_values_supported: None,
3244 device_authorization_endpoint: o2rs.device_authorization_endpoint.clone(),
3245 })
3246 }
3247
3248 #[instrument(level = "debug", skip_all)]
3249 pub fn oauth2_openid_webfinger(
3250 &mut self,
3251 client_id: &str,
3252 resource_id: &str,
3253 ) -> Result<OidcWebfingerResponse, OperationError> {
3254 let o2rs = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
3255 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
3256 OperationError::NoMatchingEntries
3257 })?;
3258
3259 let Some(spn) = PartialValue::new_spn_s(resource_id) else {
3260 return Err(OperationError::NoMatchingEntries);
3261 };
3262
3263 if !self
3265 .qs_read
3266 .internal_exists(&Filter::new(f_eq(Attribute::Spn, spn)))?
3267 {
3268 return Err(OperationError::NoMatchingEntries);
3269 }
3270
3271 let issuer = o2rs.iss.clone();
3272
3273 Ok(OidcWebfingerResponse {
3274 subject: resource_id.to_string(),
3277 links: vec![OidcWebfingerRel {
3278 rel: "http://openid.net/specs/connect/1.0/issuer".into(),
3279 href: issuer.into(),
3280 }],
3281 })
3282 }
3283
3284 #[instrument(level = "debug", skip_all)]
3285 pub fn oauth2_openid_publickey(&self, client_id: &str) -> Result<JwkKeySet, OperationError> {
3286 let o2rs = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
3287 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
3288 OperationError::NoMatchingEntries
3289 })?;
3290
3291 trace!(sign_alg = ?o2rs.sign_alg);
3292
3293 match o2rs.sign_alg {
3294 SignatureAlgo::Es256 => o2rs.key_object.jws_es256_jwks(),
3295 SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_jwks(),
3296 }
3297 .ok_or_else(|| {
3298 error!(o2_client = ?o2rs.name, "Unable to retrieve public keys");
3299 OperationError::InvalidState
3300 })
3301 }
3302}
3303
3304fn parse_basic_authz(client_authz: &str) -> Result<ClientAuth, Oauth2Error> {
3305 let authz = general_purpose::STANDARD
3307 .decode(client_authz)
3308 .map_err(|_| {
3309 admin_error!("Basic authz invalid base64");
3310 Oauth2Error::AuthenticationRequired
3311 })
3312 .and_then(|data| {
3313 String::from_utf8(data).map_err(|_| {
3314 admin_error!("Basic authz invalid utf8");
3315 Oauth2Error::AuthenticationRequired
3316 })
3317 })?;
3318
3319 let mut split_iter = authz.split(':');
3322
3323 let client_id = split_iter.next().ok_or_else(|| {
3324 admin_error!("Basic authz invalid format (corrupt input?)");
3325 Oauth2Error::AuthenticationRequired
3326 })?;
3327 let secret = split_iter.next().ok_or_else(|| {
3328 admin_error!("Basic authz invalid format (missing ':' separator?)");
3329 Oauth2Error::AuthenticationRequired
3330 })?;
3331
3332 let client_id = client_id.replace("%2D", "-").replace("%5F", "_");
3346
3347 Ok((client_id.as_str(), Some(secret)).into())
3348}
3349
3350fn s_claims_for_account(
3351 o2rs: &Oauth2RS,
3352 account: &Account,
3353 scopes: &BTreeSet<String>,
3354) -> OidcClaims {
3355 let preferred_username = if o2rs.prefer_short_username {
3356 Some(account.name().into())
3357 } else {
3358 Some(account.spn().into())
3359 };
3360
3361 let (email, email_verified) = if scopes.contains(OAUTH2_SCOPE_EMAIL) {
3362 if let Some(mp) = &account.mail_primary {
3363 (Some(mp.clone()), Some(true))
3364 } else {
3365 (None, None)
3366 }
3367 } else {
3368 (None, None)
3369 };
3370
3371 let updated_at: Option<OffsetDateTime> = if scopes.contains(OAUTH2_SCOPE_PROFILE) {
3372 account
3373 .updated_at
3374 .as_ref()
3375 .map(OffsetDateTime::from)
3376 .and_then(|odt| odt.replace_nanosecond(0).ok())
3377 } else {
3378 None
3379 };
3380 OidcClaims {
3381 name: Some(account.displayname.clone()),
3383 scopes: scopes.iter().cloned().collect(),
3384 preferred_username,
3385 email,
3386 email_verified,
3387 updated_at,
3388 ..Default::default()
3389 }
3390}
3391
3392fn extra_claims_for_account(
3393 account: &Account,
3394
3395 claim_map: &BTreeMap<Uuid, Vec<(String, ClaimValue)>>,
3396
3397 scopes: &BTreeSet<String>,
3398) -> BTreeMap<String, serde_json::Value> {
3399 let mut extra_claims = BTreeMap::new();
3400
3401 let mut account_claims: BTreeMap<&str, ClaimValue> = BTreeMap::new();
3402
3403 for group_uuid in account.groups.iter().map(|g| g.uuid()) {
3405 if let Some(claim) = claim_map.get(group_uuid) {
3407 for (claim_name, claim_value) in claim.iter() {
3409 match account_claims.entry(claim_name.as_str()) {
3411 BTreeEntry::Vacant(e) => {
3412 e.insert(claim_value.clone());
3413 }
3414 BTreeEntry::Occupied(mut e) => {
3415 let mut_claim_value = e.get_mut();
3416 mut_claim_value.merge(claim_value);
3418 }
3419 }
3420 }
3421 }
3422 }
3423
3424 for (claim_name, claim_value) in account_claims {
3426 extra_claims.insert(claim_name.to_string(), claim_value.to_json_value());
3427 }
3428
3429 if scopes.contains(OAUTH2_SCOPE_SSH_PUBLICKEYS) {
3433 extra_claims.insert(
3434 OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string(),
3435 account
3436 .sshkeys()
3437 .values()
3438 .map(|pub_key| serde_json::Value::String(pub_key.to_string()))
3439 .collect(),
3440 );
3441 }
3442
3443 let wants_groups = scopes.contains(OAUTH2_SCOPE_GROUPS);
3444 let wants_groups_uuid = wants_groups || scopes.contains(OAUTH2_SCOPE_GROUPS_UUID);
3446 let wants_groups_spn = wants_groups || scopes.contains(OAUTH2_SCOPE_GROUPS_SPN);
3447 let wants_groups_name = scopes.contains(OAUTH2_SCOPE_GROUPS_NAME);
3448
3449 if wants_groups_uuid || wants_groups_name || wants_groups_spn {
3450 extra_claims.insert(
3451 OAUTH2_SCOPE_GROUPS.to_string(),
3452 account
3453 .groups
3454 .iter()
3455 .flat_map(|group| {
3456 let mut attrs = Vec::with_capacity(3);
3457
3458 if wants_groups_uuid {
3459 attrs.push(group.uuid().as_hyphenated().to_string())
3460 }
3461
3462 if wants_groups_spn {
3463 attrs.push(group.spn().clone())
3464 }
3465
3466 if wants_groups_name {
3467 if let Some(name) = group.name() {
3468 attrs.push(name.into())
3469 }
3470 }
3471
3472 attrs
3473 })
3474 .collect(),
3475 );
3476 }
3477
3478 trace!(?extra_claims);
3479
3480 extra_claims
3481}
3482
3483fn process_requested_scopes_for_identity(
3484 o2rs: &Oauth2RS,
3485 ident: &Identity,
3486 req_scopes: Option<&BTreeSet<String>>,
3487) -> Result<(BTreeSet<String>, BTreeSet<String>), Oauth2Error> {
3488 let req_scopes = req_scopes.cloned().unwrap_or_default();
3489
3490 if req_scopes.is_empty() {
3491 admin_error!("Invalid OAuth2 request - must contain at least one requested scope");
3492 return Err(Oauth2Error::InvalidRequest);
3493 }
3494
3495 validate_scopes(&req_scopes)?;
3496
3497 let available_scopes: BTreeSet<String> = o2rs
3498 .scope_maps
3499 .iter()
3500 .filter_map(|(u, m)| ident.is_memberof(*u).then_some(m.iter()))
3501 .flatten()
3502 .cloned()
3503 .collect();
3504
3505 if !req_scopes.is_subset(&available_scopes) {
3506 admin_warn!(
3507 %ident,
3508 requested_scopes = ?req_scopes,
3509 available_scopes = ?available_scopes,
3510 "Identity does not have access to the requested scopes"
3511 );
3512 return Err(Oauth2Error::AccessDenied);
3513 }
3514
3515 let granted_scopes: BTreeSet<String> = o2rs
3516 .sup_scope_maps
3517 .iter()
3518 .filter_map(|(u, m)| ident.is_memberof(*u).then_some(m.iter()))
3519 .flatten()
3520 .cloned()
3521 .chain(req_scopes.iter().cloned())
3522 .collect();
3523
3524 Ok((req_scopes, granted_scopes))
3525}
3526
3527fn validate_scopes(req_scopes: &BTreeSet<String>) -> Result<(), Oauth2Error> {
3528 let failed_scopes = req_scopes
3529 .iter()
3530 .filter(|&s| !OAUTHSCOPE_RE.is_match(s))
3531 .cloned()
3532 .collect::<Vec<String>>();
3533
3534 if !failed_scopes.is_empty() {
3535 let requested_scopes_string = req_scopes
3536 .iter()
3537 .cloned()
3538 .collect::<Vec<String>>()
3539 .join(",");
3540 admin_error!(
3541 "Invalid OAuth2 request - requested scopes ({}) but ({}) failed to pass validation rules - all must match the regex {}",
3542 requested_scopes_string,
3543 failed_scopes.join(","),
3544 OAUTHSCOPE_RE.as_str()
3545 );
3546 return Err(Oauth2Error::InvalidScope);
3547 }
3548 Ok(())
3549}
3550
3551#[inline]
3553#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3554#[allow(dead_code)]
3555fn gen_device_code() -> Result<[u8; 16], Oauth2Error> {
3556 use rand::TryRng;
3557
3558 let mut rng = rand::rng();
3559 let mut result = [0u8; 16];
3560 if let Err(err) = rng.try_fill_bytes(&mut result) {
3562 error!("Failed to generate device code! {:?}", err);
3563 return Err(Oauth2Error::ServerError(OperationError::Backend));
3564 }
3565 Ok(result)
3566}
3567
3568#[inline]
3569#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3570#[allow(dead_code)]
3571fn gen_user_code() -> (String, u32) {
3573 use rand::RngExt;
3574 let mut rng = rand::rng();
3575 let num: u32 = rng.random_range(0..=999999999);
3576 let result = format!("{num:09}");
3577 (
3578 format!("{}-{}-{}", &result[0..3], &result[3..6], &result[6..9]),
3579 num,
3580 )
3581}
3582
3583#[allow(dead_code)]
3585fn parse_user_code(val: &str) -> Result<u32, Oauth2Error> {
3586 let mut val = val.to_string();
3587 val.retain(|c| c.is_ascii_digit());
3588 val.parse().map_err(|err| {
3589 debug!("Failed to parse value={} as u32: {:?}", val, err);
3590 Oauth2Error::InvalidRequest
3591 })
3592}
3593
3594fn host_is_local(host: &Host<&str>) -> bool {
3596 match host {
3597 Host::Ipv4(ip) => ip.is_loopback(),
3598 Host::Ipv6(ip) => ip.is_loopback(),
3599 Host::Domain(domain) => *domain == "localhost",
3600 }
3601}
3602
3603fn check_is_loopback(redirect_uri: &Url) -> bool {
3605 redirect_uri.host().is_some_and(|host| {
3606 host_is_local(&host)
3608 })
3609}
3610
3611#[cfg(test)]
3612mod tests {
3613 use super::{
3614 AuthorisationRequestContext, CtSecret, Oauth2TokenType, PkceS256Secret,
3615 TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS,
3616 };
3617 use crate::credential::Credential;
3618 use crate::idm::accountpolicy::ResolvedAccountPolicy;
3619 use crate::idm::oauth2::{
3620 host_is_local, parse_basic_authz, AuthoriseResponse, Oauth2Error, OauthRSType,
3621 };
3622 use crate::idm::server::{IdmServer, IdmServerTransaction};
3623 use crate::idm::serviceaccount::GenerateApiTokenEvent;
3624 use crate::prelude::*;
3625 use crate::value::{AuthType, OauthClaimMapJoin, SessionState};
3626 use crate::valueset::{ValueSetOauthScopeMap, ValueSetSshKey, ValueSetUint32};
3627 use base64::{engine::general_purpose, Engine as _};
3628 use compact_jwt::{
3629 compact::JwkUse, crypto::JwsRs256Verifier, dangernoverify::JwsDangerReleaseWithoutVerify,
3630 JwaAlg, Jwk, JwsCompact, JwsEs256Verifier, JwsVerifier, OidcSubject, OidcToken,
3631 OidcUnverified,
3632 };
3633 use kanidm_lib_crypto::CryptoPolicy;
3634 use kanidm_proto::constants::*;
3635 use kanidm_proto::internal::{SshPublicKey, UserAuthToken};
3636 use kanidm_proto::oauth2::*;
3637 use std::collections::{BTreeMap, BTreeSet};
3638 use std::convert::TryFrom;
3639 use std::str::FromStr;
3640 use std::time::Duration;
3641 use time::OffsetDateTime;
3642 use uri::{OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
3643
3644 const TEST_CURRENT_TIME: u64 = 6000;
3645 const UAT_EXPIRE: u64 = 5;
3646 const TOKEN_EXPIRE: u64 = 900;
3647
3648 const UUID_TESTGROUP: Uuid = uuid!("a3028223-bf20-47d5-8b65-967b5d2bb3eb");
3649
3650 macro_rules! good_authorisation_request {
3651 (
3652 $idms_prox_read:expr,
3653 $ident:expr,
3654 $ct:expr,
3655 $pkce_request:expr,
3656 $scope:expr
3657 ) => {{
3658 #[allow(clippy::unnecessary_to_owned)]
3659 let scope: BTreeSet<String> = $scope.split(" ").map(|s| s.to_string()).collect();
3660
3661 let auth_req = AuthorisationRequest {
3662 response_type: ResponseType::Code,
3663 response_mode: None,
3664 client_id: "test_resource_server".to_string(),
3665 state: Some("123".to_string()),
3666 pkce_request: Some($pkce_request),
3667 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3668 scope,
3669 nonce: Some("abcdef".to_string()),
3670 oidc_ext: Default::default(),
3671 max_age: None,
3672 prompt: Default::default(),
3673 ui_locales: Default::default(),
3674 unknown_keys: Default::default(),
3675 };
3676
3677 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
3678
3679 $idms_prox_read
3680 .check_oauth2_authorisation(Some($ident), &auth_req, &auth_req_ctx, $ct)
3681 .expect("OAuth2 authorisation failed")
3682 }};
3683 }
3684
3685 async fn setup_oauth2_resource_server_basic(
3687 idms: &IdmServer,
3688 ct: Duration,
3689 enable_pkce: bool,
3690 enable_legacy_crypto: bool,
3691 prefer_short_username: bool,
3692 ) -> (String, UserAuthToken, Identity, Uuid) {
3693 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3694
3695 let rs_uuid = Uuid::new_v4();
3696
3697 let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3698 (Attribute::Class, EntryClass::Group.to_value()),
3699 (Attribute::Name, Value::new_iname("testgroup")),
3700 (Attribute::Description, Value::new_utf8s("testgroup")),
3701 (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3702 (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3703 );
3704
3705 let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3706 (Attribute::Class, EntryClass::Object.to_value()),
3707 (Attribute::Class, EntryClass::Account.to_value()),
3708 (
3709 Attribute::Class,
3710 EntryClass::OAuth2ResourceServer.to_value()
3711 ),
3712 (
3713 Attribute::Class,
3714 EntryClass::OAuth2ResourceServerBasic.to_value()
3715 ),
3716 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3717 (Attribute::Name, Value::new_iname("test_resource_server")),
3718 (
3719 Attribute::DisplayName,
3720 Value::new_utf8s("test_resource_server")
3721 ),
3722 (
3723 Attribute::OAuth2RsOriginLanding,
3724 Value::new_url_s("https://demo.example.com").unwrap()
3725 ),
3726 (
3728 Attribute::OAuth2RsOrigin,
3729 Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3730 ),
3731 (
3732 Attribute::OAuth2RsOrigin,
3733 Value::new_url_s("https://portal.example.com/?custom=foo").unwrap()
3734 ),
3735 (
3736 Attribute::OAuth2RsOrigin,
3737 Value::new_url_s("app://cheese").unwrap()
3738 ),
3739 (
3741 Attribute::OAuth2RsScopeMap,
3742 Value::new_oauthscopemap(
3743 UUID_TESTGROUP,
3744 btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3745 )
3746 .expect("invalid oauthscope")
3747 ),
3748 (
3749 Attribute::OAuth2RsScopeMap,
3750 Value::new_oauthscopemap(
3751 UUID_IDM_ALL_ACCOUNTS,
3752 btreeset![
3753 OAUTH2_SCOPE_OPENID.to_string(),
3754 OAUTH2_SCOPE_PROFILE.to_string()
3755 ]
3756 )
3757 .expect("invalid oauthscope")
3758 ),
3759 (
3760 Attribute::OAuth2RsSupScopeMap,
3761 Value::new_oauthscopemap(
3762 UUID_IDM_ALL_ACCOUNTS,
3763 btreeset!["supplement".to_string()]
3764 )
3765 .expect("invalid oauthscope")
3766 ),
3767 (
3768 Attribute::OAuth2AllowInsecureClientDisablePkce,
3769 Value::new_bool(!enable_pkce)
3770 ),
3771 (
3772 Attribute::OAuth2JwtLegacyCryptoEnable,
3773 Value::new_bool(enable_legacy_crypto)
3774 ),
3775 (
3776 Attribute::OAuth2PreferShortUsername,
3777 Value::new_bool(prefer_short_username)
3778 )
3779 );
3780
3781 let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3782 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3783
3784 let entry = idms_prox_write
3785 .qs_write
3786 .internal_search_uuid(rs_uuid)
3787 .expect("Failed to retrieve OAuth2 resource entry ");
3788 let secret = entry
3789 .get_ava_single_secret(Attribute::OAuth2RsBasicSecret)
3790 .map(str::to_string)
3791 .expect("No oauth2_rs_basic_secret found");
3792
3793 let session_id = uuid::Uuid::new_v4();
3796
3797 let account = idms_prox_write
3798 .target_to_account(UUID_TESTPERSON_1)
3799 .expect("account must exist");
3800
3801 let uat = account
3802 .to_userauthtoken(
3803 session_id,
3804 SessionScope::ReadWrite,
3805 ct,
3806 &ResolvedAccountPolicy::test_policy(),
3807 )
3808 .expect("Unable to create uat");
3809
3810 let state = uat
3812 .expiry
3813 .map(SessionState::ExpiresAt)
3814 .unwrap_or(SessionState::NeverExpires);
3815
3816 let p = CryptoPolicy::minimum();
3817 let cred =
3818 Credential::new_password_only(&p, "test_password", OffsetDateTime::UNIX_EPOCH).unwrap();
3819 let cred_id = cred.uuid;
3820
3821 let session = Value::Session(
3822 session_id,
3823 crate::value::Session {
3824 label: "label".to_string(),
3825 state,
3826 issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3827 issued_by: IdentityId::Internal(UUID_SYSTEM),
3828 cred_id,
3829 scope: SessionScope::ReadWrite,
3830 type_: AuthType::Passkey,
3831 ext_metadata: Default::default(),
3832 },
3833 );
3834
3835 let modlist = ModifyList::new_list(vec![
3837 Modify::Present(Attribute::UserAuthTokenSession, session),
3838 Modify::Present(
3839 Attribute::PrimaryCredential,
3840 Value::Cred("primary".to_string(), cred),
3841 ),
3842 ]);
3843
3844 idms_prox_write
3845 .qs_write
3846 .internal_modify(
3847 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3848 &modlist,
3849 )
3850 .expect("Failed to modify user");
3851
3852 let ident = idms_prox_write
3853 .process_uat_to_identity(&uat, ct, Source::Internal)
3854 .expect("Unable to process uat");
3855
3856 idms_prox_write.commit().expect("failed to commit");
3857
3858 (secret, uat, ident, rs_uuid)
3859 }
3860
3861 async fn setup_oauth2_resource_server_public(
3862 idms: &IdmServer,
3863 ct: Duration,
3864 ) -> (UserAuthToken, Identity, Uuid) {
3865 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3866
3867 let rs_uuid = Uuid::new_v4();
3868
3869 let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3870 (Attribute::Class, EntryClass::Group.to_value()),
3871 (Attribute::Name, Value::new_iname("testgroup")),
3872 (Attribute::Description, Value::new_utf8s("testgroup")),
3873 (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3874 (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3875 );
3876
3877 let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3878 (Attribute::Class, EntryClass::Object.to_value()),
3879 (Attribute::Class, EntryClass::Account.to_value()),
3880 (
3881 Attribute::Class,
3882 EntryClass::OAuth2ResourceServer.to_value()
3883 ),
3884 (
3885 Attribute::Class,
3886 EntryClass::OAuth2ResourceServerPublic.to_value()
3887 ),
3888 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3889 (Attribute::Name, Value::new_iname("test_resource_server")),
3890 (
3891 Attribute::DisplayName,
3892 Value::new_utf8s("test_resource_server")
3893 ),
3894 (
3895 Attribute::OAuth2RsOriginLanding,
3896 Value::new_url_s("https://demo.example.com").unwrap()
3897 ),
3898 (
3899 Attribute::OAuth2RsOrigin,
3900 Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3901 ),
3902 (
3904 Attribute::OAuth2RsScopeMap,
3905 Value::new_oauthscopemap(
3906 UUID_TESTGROUP,
3907 btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3908 )
3909 .expect("invalid oauthscope")
3910 ),
3911 (
3912 Attribute::OAuth2RsScopeMap,
3913 Value::new_oauthscopemap(
3914 UUID_IDM_ALL_ACCOUNTS,
3915 btreeset![OAUTH2_SCOPE_OPENID.to_string()]
3916 )
3917 .expect("invalid oauthscope")
3918 ),
3919 (
3920 Attribute::OAuth2RsSupScopeMap,
3921 Value::new_oauthscopemap(
3922 UUID_IDM_ALL_ACCOUNTS,
3923 btreeset!["supplement".to_string()]
3924 )
3925 .expect("invalid oauthscope")
3926 )
3927 );
3928 let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3929 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3930
3931 let session_id = uuid::Uuid::new_v4();
3935
3936 let account = idms_prox_write
3937 .target_to_account(UUID_TESTPERSON_1)
3938 .expect("account must exist");
3939 let uat = account
3940 .to_userauthtoken(
3941 session_id,
3942 SessionScope::ReadWrite,
3943 ct,
3944 &ResolvedAccountPolicy::test_policy(),
3945 )
3946 .expect("Unable to create uat");
3947
3948 let state = uat
3950 .expiry
3951 .map(SessionState::ExpiresAt)
3952 .unwrap_or(SessionState::NeverExpires);
3953
3954 let p = CryptoPolicy::minimum();
3955 let cred =
3956 Credential::new_password_only(&p, "test_password", OffsetDateTime::UNIX_EPOCH).unwrap();
3957 let cred_id = cred.uuid;
3958
3959 let session = Value::Session(
3960 session_id,
3961 crate::value::Session {
3962 label: "label".to_string(),
3963 state,
3964 issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3965 issued_by: IdentityId::Internal(UUID_SYSTEM),
3966 cred_id,
3967 scope: SessionScope::ReadWrite,
3968 type_: AuthType::Passkey,
3969 ext_metadata: Default::default(),
3970 },
3971 );
3972
3973 let modlist = ModifyList::new_list(vec![
3975 Modify::Present(Attribute::UserAuthTokenSession, session),
3976 Modify::Present(
3977 Attribute::PrimaryCredential,
3978 Value::Cred("primary".to_string(), cred),
3979 ),
3980 ]);
3981
3982 idms_prox_write
3983 .qs_write
3984 .internal_modify(
3985 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3986 &modlist,
3987 )
3988 .expect("Failed to modify user");
3989
3990 let ident = idms_prox_write
3991 .process_uat_to_identity(&uat, ct, Source::Internal)
3992 .expect("Unable to process uat");
3993
3994 idms_prox_write.commit().expect("failed to commit");
3995
3996 (uat, ident, rs_uuid)
3997 }
3998
3999 async fn perform_oauth2_exchange(
4001 idms: &IdmServer,
4002 ident: &Identity,
4003 ct: Duration,
4004 client_authz: ClientAuthInfo,
4005 scopes: String,
4006 ) -> AccessTokenResponse {
4007 let idms_prox_read = idms.proxy_read().await.unwrap();
4008
4009 let pkce_secret = PkceS256Secret::default();
4010
4011 let consent_request = good_authorisation_request!(
4012 idms_prox_read,
4013 ident,
4014 ct,
4015 pkce_secret.to_request(),
4016 scopes
4017 );
4018
4019 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4020 unreachable!();
4021 };
4022
4023 drop(idms_prox_read);
4025 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4026
4027 let permit_success = idms_prox_write
4028 .check_oauth2_authorise_permit(ident, &consent_token, ct)
4029 .expect("Failed to perform OAuth2 permit");
4030
4031 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4033 code: permit_success.code,
4034 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4035 code_verifier: Some(pkce_secret.to_verifier()),
4036 }
4037 .into();
4038
4039 let token_response = idms_prox_write
4040 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4041 .expect("Failed to perform OAuth2 token exchange");
4042
4043 assert!(idms_prox_write.commit().is_ok());
4044
4045 token_response
4046 }
4047
4048 async fn validate_id_token(idms: &IdmServer, ct: Duration, id_token: &str) -> OidcToken {
4049 let idms_prox_read = idms.proxy_read().await.unwrap();
4050
4051 let mut jwkset = idms_prox_read
4052 .oauth2_openid_publickey("test_resource_server")
4053 .expect("Failed to get public key");
4054 let public_jwk = jwkset.keys.pop().expect("no such jwk");
4055
4056 let jws_validator =
4057 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
4058
4059 let oidc_unverified = OidcUnverified::from_str(id_token).expect("Failed to parse id_token");
4060
4061 let iat = ct.as_secs() as i64;
4062
4063 jws_validator
4064 .verify(&oidc_unverified)
4065 .unwrap()
4066 .verify_exp(iat)
4067 .expect("Failed to verify oidc")
4068 }
4069
4070 async fn setup_idm_admin(idms: &IdmServer, ct: Duration) -> (UserAuthToken, Identity) {
4071 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4072 let account = idms_prox_write
4073 .target_to_account(UUID_IDM_ADMIN)
4074 .expect("account must exist");
4075 let session_id = uuid::Uuid::new_v4();
4076 let uat = account
4077 .to_userauthtoken(
4078 session_id,
4079 SessionScope::ReadWrite,
4080 ct,
4081 &ResolvedAccountPolicy::test_policy(),
4082 )
4083 .expect("Unable to create uat");
4084 let ident = idms_prox_write
4085 .process_uat_to_identity(&uat, ct, Source::Internal)
4086 .expect("Unable to process uat");
4087
4088 idms_prox_write.commit().expect("failed to commit");
4089
4090 (uat, ident)
4091 }
4092
4093 #[test]
4094 fn oauth2_parse_basic_authz() {
4095 let r1 = parse_basic_authz("czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3").unwrap();
4096 assert_eq!(r1.client_id, "s6BhdRkqt3");
4097 assert_eq!(r1.client_secret.as_deref(), Some("7Fjfp0ZBr1KtDRbnfVdmIw"));
4098
4099 let r2 = parse_basic_authz("bXklMkRpZDpEZWk3dGhhaTFhaG5lNGE=").unwrap();
4101 assert_eq!(r2.client_id, "my-id");
4102 assert_eq!(r2.client_secret.as_deref(), Some("Dei7thai1ahne4a"));
4103 }
4104
4105 #[idm_test]
4106 async fn test_idm_oauth2_basic_function(
4107 idms: &IdmServer,
4108 _idms_delayed: &mut IdmServerDelayed,
4109 ) {
4110 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4111 let (secret, _uat, ident, _) =
4112 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4113
4114 let idms_prox_read = idms.proxy_read().await.unwrap();
4115
4116 let pkce_secret = PkceS256Secret::default();
4118
4119 let consent_request = good_authorisation_request!(
4120 idms_prox_read,
4121 &ident,
4122 ct,
4123 pkce_secret.to_request(),
4124 OAUTH2_SCOPE_OPENID.to_string()
4125 );
4126
4127 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4129 unreachable!();
4130 };
4131
4132 drop(idms_prox_read);
4134 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4135
4136 let permit_success = idms_prox_write
4137 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4138 .expect("Failed to perform OAuth2 permit");
4139
4140 assert_eq!(permit_success.state.as_deref(), Some("123"));
4142
4143 let token_req = AccessTokenRequest {
4146 grant_type: GrantTypeReq::AuthorizationCode {
4147 code: permit_success.code,
4148 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4149 code_verifier: Some(pkce_secret.to_verifier()),
4150 },
4151 client_post_auth: ClientPostAuth {
4152 client_id: Some("test_resource_server".to_string()),
4153 client_secret: Some(secret),
4154 },
4155 };
4156
4157 let token_response = idms_prox_write
4158 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4159 .expect("Failed to perform OAuth2 token exchange");
4160
4161 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4163
4164 assert!(idms_prox_write.commit().is_ok());
4165 }
4166
4167 #[idm_test]
4168 async fn test_idm_oauth2_public_function(
4169 idms: &IdmServer,
4170 _idms_delayed: &mut IdmServerDelayed,
4171 ) {
4172 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4173 let (_uat, ident, _) = setup_oauth2_resource_server_public(idms, ct).await;
4174
4175 let idms_prox_read = idms.proxy_read().await.unwrap();
4176
4177 let pkce_secret = PkceS256Secret::default();
4181
4182 let consent_request = good_authorisation_request!(
4183 idms_prox_read,
4184 &ident,
4185 ct,
4186 pkce_secret.to_request(),
4187 OAUTH2_SCOPE_OPENID.to_string()
4188 );
4189
4190 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4192 unreachable!();
4193 };
4194
4195 drop(idms_prox_read);
4197 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4198
4199 let permit_success = idms_prox_write
4200 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4201 .expect("Failed to perform OAuth2 permit");
4202
4203 assert_eq!(permit_success.state.as_deref(), Some("123"));
4205
4206 let token_req = AccessTokenRequest {
4209 grant_type: GrantTypeReq::AuthorizationCode {
4210 code: permit_success.code,
4211 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4212 code_verifier: Some(pkce_secret.to_verifier()),
4214 },
4215
4216 client_post_auth: ClientPostAuth {
4217 client_id: Some("Test_Resource_Server".to_string()),
4218 client_secret: None,
4219 },
4220 };
4221
4222 let token_response = idms_prox_write
4223 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4224 .expect("Failed to perform OAuth2 token exchange");
4225
4226 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4228
4229 assert!(idms_prox_write.commit().is_ok());
4230 }
4231
4232 #[idm_test]
4233 async fn test_idm_oauth2_invalid_authorisation_requests(
4234 idms: &IdmServer,
4235 _idms_delayed: &mut IdmServerDelayed,
4236 ) {
4237 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4239 let (_secret, _uat, ident, _) =
4240 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4241
4242 let (_anon_uat, anon_ident) = setup_idm_admin(idms, ct).await;
4243 let (_idm_admin_uat, idm_admin_ident) = setup_idm_admin(idms, ct).await;
4244
4245 let idms_prox_read = idms.proxy_read().await.unwrap();
4247
4248 let pkce_secret = PkceS256Secret::default();
4249
4250 let pkce_request = pkce_secret.to_request();
4251
4252 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
4253
4254 let auth_req = AuthorisationRequest {
4256 response_type: ResponseType::Token,
4258 response_mode: None,
4259 client_id: "test_resource_server".to_string(),
4260 state: Some("123".to_string()),
4261 pkce_request: Some(pkce_request.clone()),
4262 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4263 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4264 nonce: None,
4265 oidc_ext: Default::default(),
4266 max_age: None,
4267 ui_locales: Default::default(),
4268 prompt: Default::default(),
4269 unknown_keys: Default::default(),
4270 };
4271
4272 assert!(
4273 idms_prox_read
4274 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4275 .unwrap_err()
4276 == Oauth2Error::UnsupportedResponseType
4277 );
4278
4279 let auth_req = AuthorisationRequest {
4281 response_type: ResponseType::Code,
4282 response_mode: None,
4283 client_id: "test_resource_server".to_string(),
4284 state: Some("123".to_string()),
4285 pkce_request: None,
4286 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4287 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4288 nonce: None,
4289 oidc_ext: Default::default(),
4290 max_age: None,
4291 ui_locales: Default::default(),
4292 prompt: Default::default(),
4293 unknown_keys: Default::default(),
4294 };
4295
4296 assert!(
4297 idms_prox_read
4298 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4299 .unwrap_err()
4300 == Oauth2Error::InvalidRequest
4301 );
4302
4303 let auth_req = AuthorisationRequest {
4305 response_type: ResponseType::Code,
4306 response_mode: None,
4307 client_id: "NOT A REAL RESOURCE SERVER".to_string(),
4308 state: Some("123".to_string()),
4309 pkce_request: Some(pkce_request.clone()),
4310 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4311 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4312 nonce: None,
4313 oidc_ext: Default::default(),
4314 max_age: None,
4315 ui_locales: Default::default(),
4316 prompt: Default::default(),
4317 unknown_keys: Default::default(),
4318 };
4319
4320 assert!(
4321 idms_prox_read
4322 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4323 .unwrap_err()
4324 == Oauth2Error::InvalidClientId
4325 );
4326
4327 let auth_req = AuthorisationRequest {
4329 response_type: ResponseType::Code,
4330 response_mode: None,
4331 client_id: "test_resource_server".to_string(),
4332 state: Some("123".to_string()),
4333 pkce_request: Some(pkce_request.clone()),
4334 redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4335 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4336 nonce: None,
4337 oidc_ext: Default::default(),
4338 max_age: None,
4339 prompt: Default::default(),
4340 ui_locales: Default::default(),
4341 unknown_keys: Default::default(),
4342 };
4343
4344 assert!(
4345 idms_prox_read
4346 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4347 .unwrap_err()
4348 == Oauth2Error::InvalidOrigin
4349 );
4350
4351 let auth_req = AuthorisationRequest {
4353 response_type: ResponseType::Code,
4354 response_mode: None,
4355 client_id: "test_resource_server".to_string(),
4356 state: Some("123".to_string()),
4357 pkce_request: Some(pkce_request.clone()),
4358 redirect_uri: Url::parse("https://demo.example.com/oauth2/wrong_place").unwrap(),
4359 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4360 nonce: None,
4361 oidc_ext: Default::default(),
4362 max_age: None,
4363 ui_locales: Default::default(),
4364 prompt: Default::default(),
4365 unknown_keys: Default::default(),
4366 };
4367
4368 assert!(
4369 idms_prox_read
4370 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4371 .unwrap_err()
4372 == Oauth2Error::InvalidOrigin
4373 );
4374
4375 let auth_req = AuthorisationRequest {
4377 response_type: ResponseType::Code,
4378 response_mode: None,
4379 client_id: "test_resource_server".to_string(),
4380 state: Some("123".to_string()),
4381 pkce_request: Some(pkce_request.clone()),
4382 redirect_uri: Url::parse("https://portal.example.com/?custom=foo&too=many").unwrap(),
4383 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4384 nonce: None,
4385 oidc_ext: Default::default(),
4386 max_age: None,
4387 ui_locales: Default::default(),
4388 prompt: Default::default(),
4389 unknown_keys: Default::default(),
4390 };
4391
4392 assert!(
4393 idms_prox_read
4394 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4395 .unwrap_err()
4396 == Oauth2Error::InvalidOrigin
4397 );
4398
4399 let auth_req = AuthorisationRequest {
4400 response_type: ResponseType::Code,
4401 response_mode: None,
4402 client_id: "test_resource_server".to_string(),
4403 state: Some("123".to_string()),
4404 pkce_request: Some(pkce_request.clone()),
4405 redirect_uri: Url::parse("https://portal.example.com").unwrap(),
4406 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4407 nonce: None,
4408 oidc_ext: Default::default(),
4409 max_age: None,
4410 ui_locales: Default::default(),
4411 prompt: Default::default(),
4412 unknown_keys: Default::default(),
4413 };
4414
4415 assert!(
4416 idms_prox_read
4417 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4418 .unwrap_err()
4419 == Oauth2Error::InvalidOrigin
4420 );
4421
4422 let auth_req = AuthorisationRequest {
4423 response_type: ResponseType::Code,
4424 response_mode: None,
4425 client_id: "test_resource_server".to_string(),
4426 state: Some("123".to_string()),
4427 pkce_request: Some(pkce_request.clone()),
4428 redirect_uri: Url::parse("https://portal.example.com/?wrong=queryparam").unwrap(),
4429 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4430 nonce: None,
4431 oidc_ext: Default::default(),
4432 max_age: None,
4433 ui_locales: Default::default(),
4434 prompt: Default::default(),
4435 unknown_keys: Default::default(),
4436 };
4437
4438 assert!(
4439 idms_prox_read
4440 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4441 .unwrap_err()
4442 == Oauth2Error::InvalidOrigin
4443 );
4444
4445 let auth_req = AuthorisationRequest {
4447 response_type: ResponseType::Code,
4448 response_mode: None,
4449 client_id: "test_resource_server".to_string(),
4450 state: Some("123".to_string()),
4451 pkce_request: Some(pkce_request.clone()),
4452 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4453 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4454 nonce: None,
4455 oidc_ext: Default::default(),
4456 max_age: None,
4457 ui_locales: Default::default(),
4458 prompt: Default::default(),
4459 unknown_keys: Default::default(),
4460 };
4461
4462 let req = idms_prox_read
4463 .check_oauth2_authorisation(None, &auth_req, &auth_req_ctx, ct)
4464 .unwrap();
4465
4466 assert!(matches!(
4467 req,
4468 AuthoriseResponse::AuthenticationRequired { .. }
4469 ));
4470
4471 let auth_req = AuthorisationRequest {
4473 response_type: ResponseType::Code,
4474 response_mode: None,
4475 client_id: "test_resource_server".to_string(),
4476 state: Some("123".to_string()),
4477 pkce_request: Some(pkce_request.clone()),
4478 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4479 scope: btreeset!["invalid_scope".to_string(), "read".to_string()],
4480 nonce: None,
4481 oidc_ext: Default::default(),
4482 max_age: None,
4483 ui_locales: Default::default(),
4484 prompt: Default::default(),
4485 unknown_keys: Default::default(),
4486 };
4487
4488 assert!(
4489 idms_prox_read
4490 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4491 .unwrap_err()
4492 == Oauth2Error::AccessDenied
4493 );
4494
4495 let auth_req = AuthorisationRequest {
4497 response_type: ResponseType::Code,
4498 response_mode: None,
4499 client_id: "test_resource_server".to_string(),
4500 state: Some("123".to_string()),
4501 pkce_request: Some(pkce_request.clone()),
4502 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4503 scope: btreeset!["openid".to_string(), "read".to_string()],
4504 nonce: None,
4505 oidc_ext: Default::default(),
4506 max_age: None,
4507 ui_locales: Default::default(),
4508 prompt: Default::default(),
4509 unknown_keys: Default::default(),
4510 };
4511
4512 assert!(
4513 idms_prox_read
4514 .check_oauth2_authorisation(Some(&idm_admin_ident), &auth_req, &auth_req_ctx, ct)
4515 .unwrap_err()
4516 == Oauth2Error::AccessDenied
4517 );
4518
4519 let auth_req = AuthorisationRequest {
4521 response_type: ResponseType::Code,
4522 response_mode: None,
4523 client_id: "test_resource_server".to_string(),
4524 state: Some("123".to_string()),
4525 pkce_request: Some(pkce_request),
4526 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4527 scope: btreeset!["openid".to_string(), "read".to_string()],
4528 nonce: None,
4529 oidc_ext: Default::default(),
4530 max_age: None,
4531 ui_locales: Default::default(),
4532 prompt: Default::default(),
4533 unknown_keys: Default::default(),
4534 };
4535
4536 assert!(
4537 idms_prox_read
4538 .check_oauth2_authorisation(Some(&anon_ident), &auth_req, &auth_req_ctx, ct)
4539 .unwrap_err()
4540 == Oauth2Error::AccessDenied
4541 );
4542 }
4543
4544 #[idm_test]
4545 async fn test_idm_oauth2_invalid_authorisation_permit_requests(
4546 idms: &IdmServer,
4547 _idms_delayed: &mut IdmServerDelayed,
4548 ) {
4549 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4551 let (_secret, uat, ident, _) =
4552 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4553
4554 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4555
4556 let mut uat_wrong_session_id = uat.clone();
4557 uat_wrong_session_id.session_id = uuid::Uuid::new_v4();
4558 let ident_wrong_session_id = idms_prox_write
4559 .process_uat_to_identity(&uat_wrong_session_id, ct, Source::Internal)
4560 .expect("Unable to process uat");
4561
4562 let account = idms_prox_write
4563 .target_to_account(UUID_IDM_ADMIN)
4564 .expect("account must exist");
4565 let session_id = uuid::Uuid::new_v4();
4566 let uat2 = account
4567 .to_userauthtoken(
4568 session_id,
4569 SessionScope::ReadWrite,
4570 ct,
4571 &ResolvedAccountPolicy::test_policy(),
4572 )
4573 .expect("Unable to create uat");
4574 let ident2 = idms_prox_write
4575 .process_uat_to_identity(&uat2, ct, Source::Internal)
4576 .expect("Unable to process uat");
4577
4578 assert!(idms_prox_write.commit().is_ok());
4579
4580 let idms_prox_read = idms.proxy_read().await.unwrap();
4583
4584 let pkce_secret = PkceS256Secret::default();
4585
4586 let consent_request = good_authorisation_request!(
4587 idms_prox_read,
4588 &ident,
4589 ct,
4590 pkce_secret.to_request(),
4591 OAUTH2_SCOPE_OPENID.to_string()
4592 );
4593
4594 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4595 unreachable!();
4596 };
4597
4598 drop(idms_prox_read);
4599 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4600
4601 assert!(
4604 idms_prox_write
4605 .check_oauth2_authorise_permit(
4606 &ident,
4607 &consent_token,
4608 ct + Duration::from_secs(TOKEN_EXPIRE),
4609 )
4610 .unwrap_err()
4611 == OperationError::CryptographyError
4612 );
4613
4614 assert!(
4619 idms_prox_write
4620 .check_oauth2_authorise_permit(&ident2, &consent_token, ct,)
4621 .unwrap_err()
4622 == OperationError::InvalidSessionState
4623 );
4624
4625 assert!(
4627 idms_prox_write
4628 .check_oauth2_authorise_permit(&ident_wrong_session_id, &consent_token, ct,)
4629 .unwrap_err()
4630 == OperationError::InvalidSessionState
4631 );
4632
4633 assert!(idms_prox_write.commit().is_ok());
4634 }
4635
4636 #[idm_test]
4637 async fn test_idm_oauth2_invalid_token_exchange_requests(
4638 idms: &IdmServer,
4639 _idms_delayed: &mut IdmServerDelayed,
4640 ) {
4641 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4642 let (secret, mut uat, ident, _) =
4643 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4644
4645 uat.expiry = Some(
4655 time::OffsetDateTime::UNIX_EPOCH
4656 + Duration::from_secs(TEST_CURRENT_TIME + UAT_EXPIRE - 1),
4657 );
4658
4659 let idms_prox_read = idms.proxy_read().await.unwrap();
4660
4661 let pkce_secret = PkceS256Secret::default();
4663
4664 let consent_request = good_authorisation_request!(
4665 idms_prox_read,
4666 &ident,
4667 ct,
4668 pkce_secret.to_request(),
4669 OAUTH2_SCOPE_OPENID.to_string()
4670 );
4671
4672 let code_verifier = Some(pkce_secret.to_verifier());
4673
4674 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4675 unreachable!();
4676 };
4677
4678 drop(idms_prox_read);
4679 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4680
4681 let permit_success = idms_prox_write
4683 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4684 .expect("Failed to perform OAuth2 permit");
4685
4686 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4691 code: permit_success.code.clone(),
4692 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4693 code_verifier: code_verifier.clone(),
4694 }
4695 .into();
4696
4697 let client_authz = ClientAuthInfo::from("not base64");
4698
4699 assert!(
4700 idms_prox_write
4701 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4702 .unwrap_err()
4703 == Oauth2Error::AuthenticationRequired
4704 );
4705
4706 let client_authz =
4708 general_purpose::STANDARD.encode(format!("test_resource_server {secret}"));
4709 let client_authz = ClientAuthInfo::from(client_authz.as_str());
4710
4711 assert!(
4712 idms_prox_write
4713 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4714 .unwrap_err()
4715 == Oauth2Error::AuthenticationRequired
4716 );
4717
4718 let client_authz = ClientAuthInfo::encode_basic("NOT A REAL SERVER", secret.as_str());
4720
4721 assert!(
4722 idms_prox_write
4723 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4724 .unwrap_err()
4725 == Oauth2Error::AuthenticationRequired
4726 );
4727
4728 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
4730
4731 assert!(
4732 idms_prox_write
4733 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4734 .unwrap_err()
4735 == Oauth2Error::AuthenticationRequired
4736 );
4737
4738 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4740
4741 assert!(
4743 idms_prox_write
4744 .check_oauth2_token_exchange(
4745 &client_authz,
4746 &token_req,
4747 ct + Duration::from_secs(TOKEN_EXPIRE)
4748 )
4749 .unwrap_err()
4750 == Oauth2Error::InvalidRequest
4751 );
4752
4753 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4774 code: permit_success.code.clone(),
4775 redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4776 code_verifier: code_verifier.clone(),
4777 }
4778 .into();
4779 assert!(
4780 idms_prox_write
4781 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4782 .unwrap_err()
4783 == Oauth2Error::InvalidOrigin
4784 );
4785
4786 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4788 code: permit_success.code,
4789 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4790 code_verifier: Some("12345".to_string()),
4791 }
4792 .into();
4793 assert!(
4794 idms_prox_write
4795 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4796 .unwrap_err()
4797 == Oauth2Error::InvalidRequest
4798 );
4799
4800 assert!(idms_prox_write.commit().is_ok());
4801 }
4802
4803 #[idm_test]
4804 async fn test_idm_oauth2_supplemental_origin_redirect(
4805 idms: &IdmServer,
4806 _idms_delayed: &mut IdmServerDelayed,
4807 ) {
4808 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4809 let (secret, uat, ident, _) =
4810 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4811
4812 let idms_prox_read = idms.proxy_read().await.unwrap();
4813
4814 let pkce_secret = PkceS256Secret::default();
4816
4817 let redirect_uri = Url::parse("https://portal.example.com/?custom=foo").unwrap();
4818
4819 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
4820
4821 let auth_req = AuthorisationRequest {
4822 response_type: ResponseType::Code,
4823 response_mode: None,
4824 client_id: "test_resource_server".to_string(),
4825 state: None,
4826 pkce_request: Some(pkce_secret.to_request()),
4827 redirect_uri: redirect_uri.clone(),
4828 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4829 nonce: Some("abcdef".to_string()),
4830 oidc_ext: Default::default(),
4831 max_age: None,
4832 ui_locales: Default::default(),
4833 prompt: Default::default(),
4834 unknown_keys: Default::default(),
4835 };
4836
4837 let consent_request = idms_prox_read
4838 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4839 .expect("OAuth2 authorisation failed");
4840
4841 trace!(?consent_request);
4842
4843 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4845 unreachable!();
4846 };
4847
4848 drop(idms_prox_read);
4850 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4851
4852 let permit_success = idms_prox_write
4853 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4854 .expect("Failed to perform OAuth2 permit");
4855
4856 assert_eq!(permit_success.state.as_deref(), None);
4858
4859 let permit_redirect_uri = permit_success.build_redirect_uri();
4862
4863 assert_eq!(permit_redirect_uri.origin(), redirect_uri.origin());
4864 assert_eq!(permit_redirect_uri.path(), redirect_uri.path());
4865 let query = BTreeMap::from_iter(permit_redirect_uri.query_pairs().into_owned());
4866 assert_eq!(query.get("custom").map(|s| s.as_str()), Some("foo"));
4868
4869 let token_req = AccessTokenRequest {
4872 grant_type: GrantTypeReq::AuthorizationCode {
4873 code: permit_success.code,
4874 redirect_uri,
4875 code_verifier: Some(pkce_secret.to_verifier()),
4876 },
4877
4878 client_post_auth: ClientPostAuth {
4879 client_id: Some("test_resource_server".to_string()),
4880 client_secret: Some(secret.clone()),
4881 },
4882 };
4883
4884 let token_response = idms_prox_write
4885 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4886 .expect("Failed to perform OAuth2 token exchange");
4887
4888 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4890
4891 assert!(idms_prox_write.commit().is_ok());
4892
4893 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4897
4898 let ident = idms_prox_read
4900 .process_uat_to_identity(&uat, ct, Source::Internal)
4901 .expect("Unable to process uat");
4902
4903 let pkce_secret = PkceS256Secret::default();
4904
4905 let auth_req = AuthorisationRequest {
4906 response_type: ResponseType::Code,
4907 response_mode: None,
4908 client_id: "test_resource_server".to_string(),
4909 state: Some("123".to_string()),
4910 pkce_request: Some(pkce_secret.to_request()),
4911 redirect_uri: Url::parse("app://cheese").unwrap(),
4912 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4913 nonce: Some("abcdef".to_string()),
4914 oidc_ext: Default::default(),
4915 max_age: None,
4916 ui_locales: Default::default(),
4917 prompt: Default::default(),
4918 unknown_keys: Default::default(),
4919 };
4920
4921 let consent_request = idms_prox_read
4922 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
4923 .expect("OAuth2 authorisation failed");
4924
4925 trace!(?consent_request);
4926
4927 let AuthoriseResponse::Permitted(permit_success) = consent_request else {
4928 unreachable!();
4929 };
4930
4931 assert_eq!(permit_success.state.as_deref(), Some("123"));
4934
4935 let token_req = AccessTokenRequest {
4938 grant_type: GrantTypeReq::AuthorizationCode {
4939 code: permit_success.code,
4940 redirect_uri: Url::parse("app://cheese").unwrap(),
4941 code_verifier: Some(pkce_secret.to_verifier()),
4942 },
4943
4944 client_post_auth: ClientPostAuth {
4945 client_id: Some("test_resource_server".to_string()),
4946 client_secret: Some(secret),
4947 },
4948 };
4949
4950 drop(idms_prox_read);
4951 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4952
4953 let token_response = idms_prox_write
4954 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4955 .expect("Failed to perform OAuth2 token exchange");
4956
4957 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4959 }
4960
4961 #[idm_test]
4962 async fn test_idm_oauth2_token_introspect(
4963 idms: &IdmServer,
4964 _idms_delayed: &mut IdmServerDelayed,
4965 ) {
4966 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4967 let (secret, _uat, ident, _) =
4968 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4969 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4970
4971 let idms_prox_read = idms.proxy_read().await.unwrap();
4972
4973 let pkce_secret = PkceS256Secret::default();
4975 let consent_request = good_authorisation_request!(
4976 idms_prox_read,
4977 &ident,
4978 ct,
4979 pkce_secret.to_request(),
4980 OAUTH2_SCOPE_OPENID.to_string()
4981 );
4982
4983 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4984 unreachable!();
4985 };
4986
4987 drop(idms_prox_read);
4989 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4990
4991 let permit_success = idms_prox_write
4992 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4993 .expect("Failed to perform OAuth2 permit");
4994
4995 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4996 code: permit_success.code,
4997 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4998 code_verifier: Some(pkce_secret.to_verifier()),
4999 }
5000 .into();
5001 let oauth2_token = idms_prox_write
5002 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5003 .expect("Unable to exchange for OAuth2 token");
5004
5005 assert!(idms_prox_write.commit().is_ok());
5006
5007 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5009
5010 let intr_request = AccessTokenIntrospectRequest {
5011 token: oauth2_token.access_token,
5012 token_type_hint: None,
5013 client_post_auth: ClientPostAuth::default(),
5014 };
5015 let intr_response = idms_prox_read
5016 .check_oauth2_token_introspect(&intr_request, ct)
5017 .expect("Failed to inspect token");
5018
5019 eprintln!("👉 {intr_response:?}");
5020 assert!(intr_response.active);
5021 assert_eq!(
5022 intr_response.scope,
5023 btreeset!["openid".to_string(), "supplement".to_string()]
5024 );
5025 assert_eq!(
5026 intr_response.client_id.as_deref(),
5027 Some("test_resource_server")
5028 );
5029 assert_eq!(
5030 intr_response.username.as_deref(),
5031 Some("testperson1@example.com")
5032 );
5033 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
5034 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
5035 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
5036
5037 drop(idms_prox_read);
5038 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5041 let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_CURRENT_TIME - 1));
5043 let me_inv_m = ModifyEvent::new_internal_invalid(
5044 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
5045 ModifyList::new_list(vec![Modify::Present(Attribute::AccountExpire, v_expire)]),
5046 );
5047 assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok());
5049 assert!(idms_prox_write.commit().is_ok());
5050
5051 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5054 let intr_response = idms_prox_read
5055 .check_oauth2_token_introspect(&intr_request, ct)
5056 .expect("Failed to inspect token");
5057
5058 assert!(!intr_response.active);
5059 }
5060
5061 #[idm_test]
5062 async fn test_idm_oauth2_token_revoke(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
5063 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5065 let (secret, _uat, ident, _) =
5066 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5067 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5068
5069 let idms_prox_read = idms.proxy_read().await.unwrap();
5070
5071 let pkce_secret = PkceS256Secret::default();
5073
5074 let consent_request = good_authorisation_request!(
5075 idms_prox_read,
5076 &ident,
5077 ct,
5078 pkce_secret.to_request(),
5079 OAUTH2_SCOPE_OPENID.to_string()
5080 );
5081
5082 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5083 unreachable!();
5084 };
5085
5086 drop(idms_prox_read);
5088 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5089
5090 let permit_success = idms_prox_write
5091 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5092 .expect("Failed to perform OAuth2 permit");
5093
5094 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5096 code: permit_success.code,
5097 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5098 code_verifier: Some(pkce_secret.to_verifier()),
5099 }
5100 .into();
5101 let oauth2_token = idms_prox_write
5102 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5103 .expect("Unable to exchange for OAuth2 token");
5104
5105 assert!(idms_prox_write.commit().is_ok());
5106
5107 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5111 let intr_request = AccessTokenIntrospectRequest {
5112 token: oauth2_token.access_token.clone(),
5113 token_type_hint: None,
5114 client_post_auth: ClientPostAuth::default(),
5115 };
5116 let intr_response = idms_prox_read
5117 .check_oauth2_token_introspect(&intr_request, ct)
5118 .expect("Failed to inspect token");
5119 eprintln!("👉 {intr_response:?}");
5120 assert!(intr_response.active);
5121 drop(idms_prox_read);
5122
5123 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5126 let revoke_request = TokenRevokeRequest {
5127 token: "this is an invalid token, nothing will happen!".to_string(),
5128 token_type_hint: None,
5129 client_post_auth: ClientPostAuth::default(),
5130 };
5131 let e = idms_prox_write
5132 .oauth2_token_revoke(&revoke_request, ct)
5133 .unwrap_err();
5134 assert!(matches!(e, Oauth2Error::AuthenticationRequired));
5135 assert!(idms_prox_write.commit().is_ok());
5136
5137 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5139 let intr_response = idms_prox_read
5140 .check_oauth2_token_introspect(&intr_request, ct)
5141 .expect("Failed to inspect token");
5142 assert!(intr_response.active);
5143 drop(idms_prox_read);
5144
5145 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5147 let revoke_request = TokenRevokeRequest {
5148 token: oauth2_token.access_token.clone(),
5149 token_type_hint: None,
5150 client_post_auth: ClientPostAuth::default(),
5151 };
5152 assert!(idms_prox_write
5153 .oauth2_token_revoke(&revoke_request, ct,)
5154 .is_ok());
5155 assert!(idms_prox_write.commit().is_ok());
5156
5157 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5159 let intr_response = idms_prox_read
5160 .check_oauth2_token_introspect(&intr_request, ct)
5161 .expect("Failed to inspect token");
5162
5163 assert!(!intr_response.active);
5164 drop(idms_prox_read);
5165
5166 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5168 let filt = filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(ident.get_uuid())));
5169 let mut work_set = idms_prox_write
5170 .qs_write
5171 .internal_search_writeable(&filt)
5172 .expect("Failed to perform internal search writeable");
5173 for (_, entry) in work_set.iter_mut() {
5174 let _ = entry.force_trim_ava(Attribute::OAuth2Session);
5175 }
5176 assert!(idms_prox_write
5177 .qs_write
5178 .internal_apply_writable(work_set)
5179 .is_ok());
5180
5181 assert!(idms_prox_write.commit().is_ok());
5182
5183 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5184 let intr_response = idms_prox_read
5186 .check_oauth2_token_introspect(&intr_request, ct)
5187 .expect("Failed to inspect token");
5188 assert!(intr_response.active);
5189
5190 let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
5192 let intr_response = idms_prox_read
5193 .check_oauth2_token_introspect(&intr_request, ct)
5194 .expect("Failed to inspect token");
5195 assert!(!intr_response.active);
5196
5197 drop(idms_prox_read);
5198
5199 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5201 let revoke_request = TokenRevokeRequest {
5202 token: oauth2_token.access_token,
5203 token_type_hint: None,
5204 client_post_auth: ClientPostAuth::default(),
5205 };
5206 assert!(idms_prox_write
5207 .oauth2_token_revoke(&revoke_request, ct,)
5208 .is_ok());
5209 assert!(idms_prox_write.commit().is_ok());
5210 }
5211
5212 #[idm_test]
5213 async fn test_idm_oauth2_session_cleanup_post_rs_delete(
5214 idms: &IdmServer,
5215 _idms_delayed: &mut IdmServerDelayed,
5216 ) {
5217 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5219 let (secret, _uat, ident, _) =
5220 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5221 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5222
5223 let idms_prox_read = idms.proxy_read().await.unwrap();
5224
5225 let pkce_secret = PkceS256Secret::default();
5227
5228 let consent_request = good_authorisation_request!(
5229 idms_prox_read,
5230 &ident,
5231 ct,
5232 pkce_secret.to_request(),
5233 OAUTH2_SCOPE_OPENID.to_string()
5234 );
5235
5236 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5237 unreachable!();
5238 };
5239
5240 drop(idms_prox_read);
5242 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5243
5244 let permit_success = idms_prox_write
5245 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5246 .expect("Failed to perform OAuth2 permit");
5247
5248 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5249 code: permit_success.code,
5250 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5251 code_verifier: Some(pkce_secret.to_verifier()),
5252 }
5253 .into();
5254
5255 let oauth2_token = idms_prox_write
5256 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5257 .expect("Unable to exchange for OAuth2 token");
5258
5259 let access_token =
5260 JwsCompact::from_str(&oauth2_token.access_token).expect("Invalid Access Token");
5261
5262 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
5263
5264 let reflected_token = jws_verifier
5265 .verify(&access_token)
5266 .unwrap()
5267 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
5268 .expect("Failed to access internals of the refresh token");
5269
5270 let session_id = reflected_token.extensions.session_id;
5271
5272 assert!(idms_prox_write.commit().is_ok());
5273
5274 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5276
5277 let entry = idms_prox_write
5279 .qs_write
5280 .internal_search_uuid(UUID_TESTPERSON_1)
5281 .expect("failed");
5282 let valid = entry
5283 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
5284 .map(|map| map.get(&session_id).is_some())
5285 .unwrap_or(false);
5286 assert!(valid);
5287
5288 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
5291 Attribute::Name,
5292 PartialValue::new_iname("test_resource_server")
5293 )));
5294
5295 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
5296
5297 let entry = idms_prox_write
5301 .qs_write
5302 .internal_search_uuid(UUID_TESTPERSON_1)
5303 .expect("failed");
5304 let revoked = entry
5305 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
5306 .and_then(|sessions| sessions.get(&session_id))
5307 .map(|session| matches!(session.state, SessionState::RevokedAt(_)))
5308 .unwrap_or(false);
5309 assert!(revoked);
5310
5311 assert!(idms_prox_write.commit().is_ok());
5312 }
5313
5314 #[idm_test]
5315 async fn test_idm_oauth2_authorisation_reject(
5316 idms: &IdmServer,
5317 _idms_delayed: &mut IdmServerDelayed,
5318 ) {
5319 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5320 let (_secret, _uat, ident, _) =
5321 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5322
5323 let ident2 = {
5324 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5325 let account = idms_prox_write
5326 .target_to_account(UUID_IDM_ADMIN)
5327 .expect("account must exist");
5328 let session_id = uuid::Uuid::new_v4();
5329 let uat2 = account
5330 .to_userauthtoken(
5331 session_id,
5332 SessionScope::ReadWrite,
5333 ct,
5334 &ResolvedAccountPolicy::test_policy(),
5335 )
5336 .expect("Unable to create uat");
5337
5338 idms_prox_write
5339 .process_uat_to_identity(&uat2, ct, Source::Internal)
5340 .expect("Unable to process uat")
5341 };
5342
5343 let idms_prox_read = idms.proxy_read().await.unwrap();
5344 let redirect_uri = Url::parse("https://demo.example.com/oauth2/result").unwrap();
5345
5346 let pkce_secret = PkceS256Secret::default();
5347
5348 let consent_request = good_authorisation_request!(
5350 idms_prox_read,
5351 &ident,
5352 ct,
5353 pkce_secret.to_request(),
5354 OAUTH2_SCOPE_OPENID.to_string()
5355 );
5356
5357 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5358 unreachable!();
5359 };
5360
5361 let reject_success = idms_prox_read
5362 .check_oauth2_authorise_reject(&ident, &consent_token, ct)
5363 .expect("Failed to perform OAuth2 reject");
5364
5365 assert_eq!(reject_success.redirect_uri, redirect_uri);
5366
5367 let past_ct = Duration::from_secs(TEST_CURRENT_TIME + 301);
5369 assert!(
5370 idms_prox_read
5371 .check_oauth2_authorise_reject(&ident, &consent_token, past_ct)
5372 .unwrap_err()
5373 == OperationError::CryptographyError
5374 );
5375
5376 assert_eq!(
5378 idms_prox_read
5379 .check_oauth2_authorise_reject(&ident, "not a token", ct)
5380 .unwrap_err(),
5381 OperationError::CryptographyError
5382 );
5383
5384 assert!(
5386 idms_prox_read
5387 .check_oauth2_authorise_reject(&ident2, &consent_token, ct)
5388 .unwrap_err()
5389 == OperationError::InvalidSessionState
5390 );
5391 }
5392
5393 #[idm_test]
5394 async fn test_idm_oauth2_rfc8414_metadata(
5395 idms: &IdmServer,
5396 _idms_delayed: &mut IdmServerDelayed,
5397 ) {
5398 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5399 let (_secret, _uat, _ident, _) =
5400 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5401
5402 let idms_prox_read = idms.proxy_read().await.unwrap();
5403
5404 assert!(
5406 idms_prox_read
5407 .oauth2_rfc8414_metadata("nosuchclient")
5408 .unwrap_err()
5409 == OperationError::NoMatchingEntries
5410 );
5411
5412 let discovery = idms_prox_read
5413 .oauth2_rfc8414_metadata("test_resource_server")
5414 .expect("Failed to get discovery");
5415
5416 assert!(
5417 discovery.issuer
5418 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5419 .unwrap()
5420 );
5421
5422 assert!(
5423 discovery.authorization_endpoint
5424 == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
5425 );
5426
5427 assert!(
5428 discovery.token_endpoint
5429 == Url::parse(&format!(
5430 "https://idm.example.com{}",
5431 uri::OAUTH2_TOKEN_ENDPOINT
5432 ))
5433 .unwrap()
5434 );
5435
5436 assert!(
5437 discovery.jwks_uri
5438 == Some(
5439 Url::parse(
5440 "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
5441 )
5442 .unwrap()
5443 )
5444 );
5445
5446 assert!(discovery.registration_endpoint.is_none());
5447
5448 assert!(
5449 discovery.scopes_supported
5450 == Some(vec![
5451 OAUTH2_SCOPE_GROUPS.to_string(),
5452 OAUTH2_SCOPE_OPENID.to_string(),
5453 OAUTH2_SCOPE_PROFILE.to_string(),
5454 "supplement".to_string(),
5455 ])
5456 );
5457
5458 assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5459 assert_eq!(
5460 discovery.response_modes_supported,
5461 vec![ResponseMode::Query, ResponseMode::Fragment]
5462 );
5463 assert_eq!(
5464 discovery.grant_types_supported,
5465 vec![
5466 GrantType::AuthorisationCode,
5467 GrantType::TokenExchange,
5468 GrantType::RefreshToken
5469 ]
5470 );
5471 assert!(
5472 discovery.token_endpoint_auth_methods_supported
5473 == vec![
5474 EndpointAuthMethod::ClientSecretBasic,
5475 EndpointAuthMethod::ClientSecretPost
5476 ]
5477 );
5478 assert!(discovery.service_documentation.is_some());
5479
5480 assert!(discovery.ui_locales_supported.is_none());
5481 assert!(discovery.op_policy_uri.is_none());
5482 assert!(discovery.op_tos_uri.is_none());
5483
5484 assert!(
5485 discovery.revocation_endpoint
5486 == Some(
5487 Url::parse(&format!(
5488 "https://idm.example.com{OAUTH2_TOKEN_REVOKE_ENDPOINT}"
5489 ))
5490 .unwrap()
5491 )
5492 );
5493 assert!(
5494 discovery.revocation_endpoint_auth_methods_supported == vec![EndpointAuthMethod::None,]
5495 );
5496
5497 assert!(
5498 discovery.introspection_endpoint
5499 == Some(
5500 Url::parse(&format!(
5501 "https://idm.example.com{}",
5502 kanidm_proto::constants::uri::OAUTH2_TOKEN_INTROSPECT_ENDPOINT
5503 ))
5504 .unwrap()
5505 )
5506 );
5507 assert!(
5508 discovery.introspection_endpoint_auth_methods_supported
5509 == vec![EndpointAuthMethod::None,]
5510 );
5511 assert!(discovery
5512 .introspection_endpoint_auth_signing_alg_values_supported
5513 .is_none());
5514
5515 assert_eq!(
5516 discovery.code_challenge_methods_supported,
5517 vec![PkceAlg::S256]
5518 )
5519 }
5520
5521 #[idm_test]
5522 async fn test_idm_oauth2_openid_discovery(
5523 idms: &IdmServer,
5524 _idms_delayed: &mut IdmServerDelayed,
5525 ) {
5526 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5527 let (_secret, _uat, _ident, _) =
5528 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5529
5530 let idms_prox_read = idms.proxy_read().await.unwrap();
5531
5532 assert!(
5534 idms_prox_read
5535 .oauth2_openid_discovery("nosuchclient")
5536 .unwrap_err()
5537 == OperationError::NoMatchingEntries
5538 );
5539
5540 assert!(
5541 idms_prox_read
5542 .oauth2_openid_publickey("nosuchclient")
5543 .unwrap_err()
5544 == OperationError::NoMatchingEntries
5545 );
5546
5547 let discovery = idms_prox_read
5548 .oauth2_openid_discovery("test_resource_server")
5549 .expect("Failed to get discovery");
5550
5551 let mut jwkset = idms_prox_read
5552 .oauth2_openid_publickey("test_resource_server")
5553 .expect("Failed to get public key");
5554
5555 let jwk = jwkset.keys.pop().expect("no such jwk");
5556
5557 match jwk {
5558 Jwk::EC { alg, use_, kid, .. } => {
5559 match (
5560 alg.unwrap(),
5561 &discovery.id_token_signing_alg_values_supported[0],
5562 ) {
5563 (JwaAlg::ES256, IdTokenSignAlg::ES256) => {}
5564 _ => panic!(),
5565 };
5566 assert_eq!(use_.unwrap(), JwkUse::Sig);
5567 assert!(kid.is_some())
5568 }
5569 _ => panic!(),
5570 };
5571
5572 assert!(
5573 discovery.issuer
5574 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5575 .unwrap()
5576 );
5577
5578 assert!(
5579 discovery.authorization_endpoint
5580 == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
5581 );
5582
5583 assert!(
5584 discovery.token_endpoint == Url::parse("https://idm.example.com/oauth2/token").unwrap()
5585 );
5586
5587 assert!(
5588 discovery.userinfo_endpoint
5589 == Some(
5590 Url::parse(
5591 "https://idm.example.com/oauth2/openid/test_resource_server/userinfo"
5592 )
5593 .unwrap()
5594 )
5595 );
5596
5597 assert!(
5598 discovery.jwks_uri
5599 == Url::parse(
5600 "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
5601 )
5602 .unwrap()
5603 );
5604
5605 assert!(
5606 discovery.scopes_supported
5607 == Some(vec![
5608 OAUTH2_SCOPE_GROUPS.to_string(),
5609 OAUTH2_SCOPE_OPENID.to_string(),
5610 OAUTH2_SCOPE_PROFILE.to_string(),
5611 "supplement".to_string(),
5612 ])
5613 );
5614
5615 assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5616 assert_eq!(
5617 discovery.response_modes_supported,
5618 vec![ResponseMode::Query, ResponseMode::Fragment]
5619 );
5620 assert_eq!(
5621 discovery.grant_types_supported,
5622 vec![
5623 GrantType::AuthorisationCode,
5624 GrantType::TokenExchange,
5625 GrantType::RefreshToken
5626 ]
5627 );
5628 assert_eq!(discovery.subject_types_supported, vec![SubjectType::Public]);
5629 assert_eq!(
5630 discovery.id_token_signing_alg_values_supported,
5631 vec![IdTokenSignAlg::ES256]
5632 );
5633 assert!(discovery.userinfo_signing_alg_values_supported.is_none());
5634 assert!(
5635 discovery.token_endpoint_auth_methods_supported
5636 == vec![
5637 EndpointAuthMethod::ClientSecretBasic,
5638 EndpointAuthMethod::ClientSecretPost
5639 ]
5640 );
5641 assert_eq!(
5642 discovery.display_values_supported,
5643 Some(vec![DisplayValue::Page])
5644 );
5645 assert_eq!(discovery.claim_types_supported, vec![ClaimType::Normal]);
5646 assert!(discovery.claims_supported.is_none());
5647 assert!(discovery.service_documentation.is_some());
5648
5649 assert!(discovery.registration_endpoint.is_none());
5650 assert!(discovery.acr_values_supported.is_none());
5651 assert!(discovery.id_token_encryption_alg_values_supported.is_none());
5652 assert!(discovery.id_token_encryption_enc_values_supported.is_none());
5653 assert!(discovery.userinfo_encryption_alg_values_supported.is_none());
5654 assert!(discovery.userinfo_encryption_enc_values_supported.is_none());
5655 assert!(discovery
5656 .request_object_signing_alg_values_supported
5657 .is_none());
5658 assert!(discovery
5659 .request_object_encryption_alg_values_supported
5660 .is_none());
5661 assert!(discovery
5662 .request_object_encryption_enc_values_supported
5663 .is_none());
5664 assert!(discovery
5665 .token_endpoint_auth_signing_alg_values_supported
5666 .is_none());
5667 assert!(discovery.claims_locales_supported.is_none());
5668 assert!(discovery.ui_locales_supported.is_none());
5669 assert!(discovery.op_policy_uri.is_none());
5670 assert!(discovery.op_tos_uri.is_none());
5671 assert!(!discovery.claims_parameter_supported);
5672 assert!(!discovery.request_uri_parameter_supported);
5673 assert!(!discovery.require_request_uri_registration);
5674 assert!(!discovery.request_parameter_supported);
5675 assert_eq!(
5676 discovery.code_challenge_methods_supported,
5677 vec![PkceAlg::S256]
5678 );
5679
5680 assert!(
5682 discovery.revocation_endpoint
5683 == Some(
5684 Url::parse(&format!(
5685 "https://idm.example.com{OAUTH2_TOKEN_REVOKE_ENDPOINT}"
5686 ))
5687 .unwrap()
5688 )
5689 );
5690 assert!(
5691 discovery.revocation_endpoint_auth_methods_supported == vec![EndpointAuthMethod::None,]
5692 );
5693
5694 assert!(
5695 discovery.introspection_endpoint
5696 == Some(
5697 Url::parse(&format!(
5698 "https://idm.example.com{OAUTH2_TOKEN_INTROSPECT_ENDPOINT}"
5699 ))
5700 .unwrap()
5701 )
5702 );
5703 assert!(
5704 discovery.introspection_endpoint_auth_methods_supported
5705 == vec![EndpointAuthMethod::None,]
5706 );
5707 assert!(discovery
5708 .introspection_endpoint_auth_signing_alg_values_supported
5709 .is_none());
5710 }
5711
5712 #[idm_test]
5713 async fn test_idm_oauth2_openid_extensions(
5714 idms: &IdmServer,
5715 _idms_delayed: &mut IdmServerDelayed,
5716 ) {
5717 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5718 let (secret, _uat, ident, _) =
5719 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5720 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5721
5722 let idms_prox_read = idms.proxy_read().await.unwrap();
5723
5724 let pkce_secret = PkceS256Secret::default();
5725
5726 let consent_request = good_authorisation_request!(
5727 idms_prox_read,
5728 &ident,
5729 ct,
5730 pkce_secret.to_request(),
5731 format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_PROFILE}")
5732 );
5733
5734 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5735 unreachable!();
5736 };
5737
5738 drop(idms_prox_read);
5740 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5741
5742 let permit_success = idms_prox_write
5743 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5744 .expect("Failed to perform OAuth2 permit");
5745
5746 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5748 code: permit_success.code,
5749 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5750 code_verifier: Some(pkce_secret.to_verifier()),
5751 }
5752 .into();
5753
5754 let token_response = idms_prox_write
5755 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5756 .expect("Failed to perform OAuth2 token exchange");
5757
5758 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
5760
5761 let id_token = token_response.id_token.expect("No id_token in response!");
5762
5763 let access_token =
5764 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5765
5766 let refresh_token = token_response
5767 .refresh_token
5768 .as_ref()
5769 .expect("no refresh token was issued")
5770 .clone();
5771
5772 assert!(idms_prox_write.commit().is_ok());
5774
5775 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5776
5777 let mut jwkset = idms_prox_read
5778 .oauth2_openid_publickey("test_resource_server")
5779 .expect("Failed to get public key");
5780
5781 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5782
5783 let jws_validator =
5784 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5785
5786 let oidc_unverified =
5787 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5788
5789 let iat = ct.as_secs() as i64;
5790
5791 let oidc = jws_validator
5792 .verify(&oidc_unverified)
5793 .unwrap()
5794 .verify_exp(iat)
5795 .expect("Failed to verify oidc");
5796
5797 assert!(
5799 oidc.iss
5800 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5801 .unwrap()
5802 );
5803 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
5804 assert_eq!(oidc.aud, "test_resource_server");
5805 assert_eq!(oidc.iat, iat);
5806 assert_eq!(oidc.nbf, Some(iat));
5807 assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
5809 assert!(oidc.auth_time.is_some());
5810 assert_eq!(oidc.nonce, Some("abcdef".to_string()));
5812 assert!(oidc.at_hash.is_none());
5813 assert!(oidc.acr.is_none());
5814 assert!(oidc.amr.is_none());
5815 assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
5816 assert!(oidc.jti.is_some());
5817 if let Some(jti) = &oidc.jti {
5818 assert!(Uuid::from_str(jti).is_ok());
5819 }
5820 assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
5821 assert_eq!(
5822 oidc.s_claims.preferred_username,
5823 Some("testperson1@example.com".to_string())
5824 );
5825 assert!(
5826 oidc.s_claims.scopes
5827 == vec![
5828 OAUTH2_SCOPE_OPENID.to_string(),
5829 OAUTH2_SCOPE_PROFILE.to_string(),
5830 "supplement".to_string()
5831 ]
5832 );
5833 assert!(oidc.claims.is_empty());
5834 let userinfo = idms_prox_read
5837 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5838 .expect("failed to get userinfo");
5839
5840 assert_eq!(oidc.iss, userinfo.iss);
5841 assert_eq!(oidc.sub, userinfo.sub);
5842 assert_eq!(oidc.aud, userinfo.aud);
5843 assert_eq!(oidc.iat, userinfo.iat);
5844 assert_eq!(oidc.nbf, userinfo.nbf);
5845 assert_eq!(oidc.exp, userinfo.exp);
5846 assert_eq!(oidc.auth_time, userinfo.auth_time);
5847 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5848 assert!(userinfo.at_hash.is_none());
5849 assert!(userinfo.acr.is_none());
5850 assert_eq!(oidc.amr, userinfo.amr);
5851 assert_eq!(oidc.azp, userinfo.azp);
5852 assert!(userinfo.jti.is_some());
5853 if let Some(jti) = &userinfo.jti {
5854 assert!(Uuid::from_str(jti).is_ok());
5855 }
5856 assert_eq!(oidc.s_claims, userinfo.s_claims);
5857 assert!(userinfo.claims.is_empty());
5858
5859 drop(idms_prox_read);
5860
5861 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5865
5866 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
5867 refresh_token,
5868 scope: None,
5869 }
5870 .into();
5871
5872 let token_response = idms_prox_write
5873 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5874 .expect("Unable to exchange for OAuth2 token");
5875
5876 let access_token =
5877 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5878
5879 assert!(idms_prox_write.commit().is_ok());
5880
5881 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5883
5884 let userinfo = idms_prox_read
5885 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5886 .expect("failed to get userinfo");
5887
5888 assert_eq!(oidc.iss, userinfo.iss);
5889 assert_eq!(oidc.sub, userinfo.sub);
5890 assert_eq!(oidc.aud, userinfo.aud);
5891 assert_eq!(oidc.iat, userinfo.iat);
5892 assert_eq!(oidc.nbf, userinfo.nbf);
5893 assert_eq!(oidc.exp, userinfo.exp);
5894 assert_eq!(oidc.auth_time, userinfo.auth_time);
5895 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5896 assert!(userinfo.at_hash.is_none());
5897 assert!(userinfo.acr.is_none());
5898 assert_eq!(oidc.amr, userinfo.amr);
5899 assert_eq!(oidc.azp, userinfo.azp);
5900 assert!(userinfo.jti.is_some());
5901 if let Some(jti) = &userinfo.jti {
5902 assert!(Uuid::from_str(jti).is_ok());
5903 }
5904 assert_eq!(oidc.s_claims, userinfo.s_claims);
5905 assert!(userinfo.claims.is_empty());
5906 }
5907
5908 #[idm_test]
5909 async fn test_idm_oauth2_openid_short_username(
5910 idms: &IdmServer,
5911 _idms_delayed: &mut IdmServerDelayed,
5912 ) {
5913 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5916 let (secret, _uat, ident, _) =
5917 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5918 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5919
5920 let idms_prox_read = idms.proxy_read().await.unwrap();
5921
5922 let pkce_secret = PkceS256Secret::default();
5923
5924 let consent_request = good_authorisation_request!(
5925 idms_prox_read,
5926 &ident,
5927 ct,
5928 pkce_secret.to_request(),
5929 OAUTH2_SCOPE_OPENID.to_string()
5930 );
5931
5932 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5933 unreachable!();
5934 };
5935
5936 drop(idms_prox_read);
5938 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5939
5940 let permit_success = idms_prox_write
5941 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5942 .expect("Failed to perform OAuth2 permit");
5943
5944 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5946 code: permit_success.code,
5947 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5948 code_verifier: Some(pkce_secret.to_verifier()),
5949 }
5950 .into();
5951
5952 let token_response = idms_prox_write
5953 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5954 .expect("Failed to perform OAuth2 token exchange");
5955
5956 let id_token = token_response.id_token.expect("No id_token in response!");
5957 let access_token =
5958 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5959
5960 assert!(idms_prox_write.commit().is_ok());
5961 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5962
5963 let mut jwkset = idms_prox_read
5964 .oauth2_openid_publickey("test_resource_server")
5965 .expect("Failed to get public key");
5966 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5967
5968 let jws_validator =
5969 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5970
5971 let oidc_unverified =
5972 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5973
5974 let iat = ct.as_secs() as i64;
5975
5976 let oidc = jws_validator
5977 .verify(&oidc_unverified)
5978 .unwrap()
5979 .verify_exp(iat)
5980 .expect("Failed to verify oidc");
5981
5982 assert_eq!(
5984 oidc.s_claims.preferred_username,
5985 Some("testperson1".to_string())
5986 );
5987 let userinfo = idms_prox_read
5989 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5990 .expect("failed to get userinfo");
5991
5992 assert_eq!(oidc.s_claims, userinfo.s_claims);
5993 }
5994
5995 #[idm_test]
5996 async fn test_idm_oauth2_openid_group_claims(
5997 idms: &IdmServer,
5998 _idms_delayed: &mut IdmServerDelayed,
5999 ) {
6000 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6003 let (secret, _uat, ident, _) =
6004 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
6005
6006 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6007
6008 let token_response = perform_oauth2_exchange(
6009 idms,
6010 &ident,
6011 ct,
6012 client_authz,
6013 format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_GROUPS}"),
6014 )
6015 .await;
6016
6017 let id_token = token_response.id_token.expect("No id_token in response!");
6018 let access_token =
6019 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
6020
6021 let oidc = validate_id_token(idms, ct, &id_token).await;
6022
6023 assert!(oidc.claims.contains_key("groups"));
6025
6026 assert!(oidc
6027 .claims
6028 .get("groups")
6029 .expect("unable to find key")
6030 .as_array()
6031 .unwrap()
6032 .contains(&serde_json::json!(STR_UUID_IDM_ALL_ACCOUNTS)));
6033
6034 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6035
6036 let userinfo = idms_prox_read
6038 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
6039 .expect("failed to get userinfo");
6040
6041 assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
6043 }
6044
6045 #[idm_test]
6046 async fn test_idm_oauth2_openid_group_extended_claims(
6047 idms: &IdmServer,
6048 _idms_delayed: &mut IdmServerDelayed,
6049 ) {
6050 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6053 let (secret, _uat, ident, oauth2_client_uuid) =
6054 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
6055
6056 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6058
6059 let modlist = ModifyList::new_list(vec![
6060 Modify::Removed(
6061 Attribute::OAuth2RsScopeMap,
6062 PartialValue::Refer(UUID_TESTGROUP),
6063 ),
6064 Modify::Present(
6065 Attribute::OAuth2RsScopeMap,
6066 Value::new_oauthscopemap(
6067 UUID_TESTGROUP,
6068 btreeset![OAUTH2_SCOPE_GROUPS_NAME.to_string()],
6069 )
6070 .expect("invalid oauthscope"),
6071 ),
6072 ]);
6073
6074 idms_prox_write
6075 .qs_write
6076 .internal_modify_uuid(oauth2_client_uuid, &modlist)
6077 .expect("Failed to modify scopes");
6078
6079 idms_prox_write.commit().expect("failed to commit");
6080
6081 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6083
6084 let token_response = perform_oauth2_exchange(
6085 idms,
6086 &ident,
6087 ct,
6088 client_authz,
6089 format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_GROUPS_NAME}"),
6090 )
6091 .await;
6092
6093 let id_token = token_response.id_token.expect("No id_token in response!");
6094 let access_token =
6095 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
6096
6097 let oidc = validate_id_token(idms, ct, &id_token).await;
6098
6099 assert!(oidc.claims.contains_key("groups"));
6101
6102 assert!(oidc
6103 .claims
6104 .get("groups")
6105 .expect("unable to find key")
6106 .as_array()
6107 .unwrap()
6108 .contains(&serde_json::json!("testgroup")));
6109
6110 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6111
6112 let userinfo = idms_prox_read
6114 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
6115 .expect("failed to get userinfo");
6116
6117 assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
6119 }
6120
6121 #[idm_test]
6122 async fn test_idm_oauth2_openid_ssh_publickey_claim(
6123 idms: &IdmServer,
6124 _idms_delayed: &mut IdmServerDelayed,
6125 ) {
6126 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6127 let (secret, _uat, ident, client_uuid) =
6128 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
6129
6130 const ECDSA_SSH_PUBLIC_KEY: &str = "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3BtOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81lzPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@amethyst";
6133 let ssh_pubkey = SshPublicKey::from_string(ECDSA_SSH_PUBLIC_KEY).unwrap();
6134
6135 let scope_set = BTreeSet::from([OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string()]);
6136
6137 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6138
6139 idms_prox_write
6140 .qs_write
6141 .internal_batch_modify(
6142 [
6143 (
6144 UUID_TESTPERSON_1,
6145 ModifyList::new_set(
6146 Attribute::SshPublicKey,
6147 ValueSetSshKey::new("label".to_string(), ssh_pubkey),
6148 ),
6149 ),
6150 (
6151 client_uuid,
6152 ModifyList::new_set(
6153 Attribute::OAuth2RsSupScopeMap,
6154 ValueSetOauthScopeMap::new(UUID_IDM_ALL_ACCOUNTS, scope_set),
6155 ),
6156 ),
6157 ]
6158 .into_iter(),
6159 )
6160 .expect("Failed to modify test entries");
6161
6162 assert!(idms_prox_write.commit().is_ok());
6163
6164 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6165
6166 let idms_prox_read = idms.proxy_read().await.unwrap();
6167
6168 let pkce_secret = PkceS256Secret::default();
6169
6170 let consent_request = good_authorisation_request!(
6171 idms_prox_read,
6172 &ident,
6173 ct,
6174 pkce_secret.to_request(),
6175 "openid groups".to_string()
6176 );
6177
6178 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6179 unreachable!();
6180 };
6181
6182 drop(idms_prox_read);
6184 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6185
6186 let permit_success = idms_prox_write
6187 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6188 .expect("Failed to perform OAuth2 permit");
6189
6190 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
6192 code: permit_success.code,
6193 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6194 code_verifier: Some(pkce_secret.to_verifier()),
6195 }
6196 .into();
6197
6198 let token_response = idms_prox_write
6199 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6200 .expect("Failed to perform OAuth2 token exchange");
6201
6202 let id_token = token_response.id_token.expect("No id_token in response!");
6203 let access_token =
6204 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
6205
6206 assert!(idms_prox_write.commit().is_ok());
6207 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6208
6209 let mut jwkset = idms_prox_read
6210 .oauth2_openid_publickey("test_resource_server")
6211 .expect("Failed to get public key");
6212 let public_jwk = jwkset.keys.pop().expect("no such jwk");
6213
6214 let jws_validator =
6215 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
6216
6217 let oidc_unverified =
6218 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
6219
6220 let iat = ct.as_secs() as i64;
6221
6222 let oidc = jws_validator
6223 .verify(&oidc_unverified)
6224 .unwrap()
6225 .verify_exp(iat)
6226 .expect("Failed to verify oidc");
6227
6228 assert!(oidc.claims.contains_key(OAUTH2_SCOPE_SSH_PUBLICKEYS));
6230
6231 assert!(oidc
6232 .claims
6233 .get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
6234 .expect("unable to find key")
6235 .as_array()
6236 .unwrap()
6237 .contains(&serde_json::json!(ECDSA_SSH_PUBLIC_KEY)));
6238
6239 let userinfo = idms_prox_read
6241 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
6242 .expect("failed to get userinfo");
6243
6244 assert_eq!(
6246 oidc.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS),
6247 userinfo.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
6248 );
6249 }
6250
6251 #[idm_test]
6253 async fn test_idm_oauth2_insecure_pkce(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
6254 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6255 let (_secret, _uat, ident, _) =
6256 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6257
6258 let idms_prox_read = idms.proxy_read().await.unwrap();
6259
6260 let pkce_secret = PkceS256Secret::default();
6262
6263 let _consent_request = good_authorisation_request!(
6265 idms_prox_read,
6266 &ident,
6267 ct,
6268 pkce_secret.to_request(),
6269 OAUTH2_SCOPE_OPENID.to_string()
6270 );
6271
6272 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
6273
6274 let auth_req = AuthorisationRequest {
6276 response_type: ResponseType::Code,
6277 response_mode: None,
6278 client_id: "test_resource_server".to_string(),
6279 state: Some("123".to_string()),
6280 pkce_request: None,
6281 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6282 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
6283 nonce: Some("abcdef".to_string()),
6284 oidc_ext: Default::default(),
6285 max_age: None,
6286 ui_locales: Default::default(),
6287 prompt: Default::default(),
6288 unknown_keys: Default::default(),
6289 };
6290
6291 idms_prox_read
6292 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
6293 .expect("Oauth2 authorisation failed");
6294 }
6295
6296 #[idm_test]
6297 async fn test_idm_oauth2_webfinger(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
6298 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6299 let (_secret, _uat, _ident, _) =
6300 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
6301 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6302
6303 let user = "testperson1@example.com";
6304
6305 let webfinger = idms_prox_read
6306 .oauth2_openid_webfinger("test_resource_server", user)
6307 .expect("Failed to get webfinger");
6308
6309 assert_eq!(webfinger.subject, user);
6310 assert_eq!(webfinger.links.len(), 1);
6311
6312 let link = &webfinger.links[0];
6313 assert_eq!(link.rel, "http://openid.net/specs/connect/1.0/issuer");
6314 assert_eq!(
6315 link.href,
6316 "https://idm.example.com/oauth2/openid/test_resource_server"
6317 );
6318
6319 let failed_webfinger = idms_prox_read
6320 .oauth2_openid_webfinger("test_resource_server", "someone@another.domain");
6321 assert!(failed_webfinger.is_err());
6322 }
6323
6324 #[idm_test]
6325 async fn test_idm_oauth2_openid_legacy_crypto(
6326 idms: &IdmServer,
6327 _idms_delayed: &mut IdmServerDelayed,
6328 ) {
6329 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6330 let (secret, _uat, ident, _) =
6331 setup_oauth2_resource_server_basic(idms, ct, false, true, false).await;
6332 let idms_prox_read = idms.proxy_read().await.unwrap();
6333 let discovery = idms_prox_read
6336 .oauth2_openid_discovery("test_resource_server")
6337 .expect("Failed to get discovery");
6338
6339 let mut jwkset = idms_prox_read
6340 .oauth2_openid_publickey("test_resource_server")
6341 .expect("Failed to get public key");
6342
6343 let jwk = jwkset.keys.pop().expect("no such jwk");
6344 let public_jwk = jwk.clone();
6345
6346 match jwk {
6347 Jwk::RSA { alg, use_, kid, .. } => {
6348 match (
6349 alg.unwrap(),
6350 &discovery.id_token_signing_alg_values_supported[0],
6351 ) {
6352 (JwaAlg::RS256, IdTokenSignAlg::RS256) => {}
6353 _ => panic!(),
6354 };
6355 assert_eq!(use_.unwrap(), JwkUse::Sig);
6356 assert!(kid.is_some());
6357 }
6358 _ => panic!(),
6359 };
6360
6361 let pkce_secret = PkceS256Secret::default();
6363
6364 let consent_request = good_authorisation_request!(
6365 idms_prox_read,
6366 &ident,
6367 ct,
6368 pkce_secret.to_request(),
6369 OAUTH2_SCOPE_OPENID.to_string()
6370 );
6371
6372 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6373 unreachable!();
6374 };
6375
6376 drop(idms_prox_read);
6378 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6379
6380 let permit_success = idms_prox_write
6381 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6382 .expect("Failed to perform OAuth2 permit");
6383
6384 let token_req = AccessTokenRequest {
6386 grant_type: GrantTypeReq::AuthorizationCode {
6387 code: permit_success.code,
6388 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6389 code_verifier: Some(pkce_secret.to_verifier()),
6390 },
6391
6392 client_post_auth: ClientPostAuth {
6393 client_id: Some("test_resource_server".to_string()),
6394 client_secret: Some(secret),
6395 },
6396 };
6397
6398 let token_response = idms_prox_write
6399 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
6400 .expect("Failed to perform OAuth2 token exchange");
6401
6402 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
6404 let id_token = token_response.id_token.expect("No id_token in response!");
6405
6406 let jws_validator =
6407 JwsRs256Verifier::try_from(&public_jwk).expect("failed to build validator");
6408
6409 let oidc_unverified =
6410 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
6411
6412 let iat = ct.as_secs() as i64;
6413
6414 let oidc = jws_validator
6415 .verify(&oidc_unverified)
6416 .unwrap()
6417 .verify_exp(iat)
6418 .expect("Failed to verify oidc");
6419
6420 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
6421
6422 assert!(idms_prox_write.commit().is_ok());
6423 }
6424
6425 #[idm_test]
6426 async fn test_idm_oauth2_consent_granted_and_changed_workflow(
6427 idms: &IdmServer,
6428 _idms_delayed: &mut IdmServerDelayed,
6429 ) {
6430 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6431 let (_secret, uat, ident, _) =
6432 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6433
6434 let idms_prox_read = idms.proxy_read().await.unwrap();
6435
6436 let pkce_secret = PkceS256Secret::default();
6437
6438 let consent_request = good_authorisation_request!(
6439 idms_prox_read,
6440 &ident,
6441 ct,
6442 pkce_secret.to_request(),
6443 OAUTH2_SCOPE_OPENID.to_string()
6444 );
6445
6446 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6448 unreachable!();
6449 };
6450
6451 drop(idms_prox_read);
6453 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6454
6455 let _permit_success = idms_prox_write
6456 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6457 .expect("Failed to perform OAuth2 permit");
6458
6459 assert!(idms_prox_write.commit().is_ok());
6460
6461 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6463
6464 let ident = idms_prox_read
6466 .process_uat_to_identity(&uat, ct, Source::Internal)
6467 .expect("Unable to process uat");
6468
6469 let pkce_secret = PkceS256Secret::default();
6470
6471 let consent_request = good_authorisation_request!(
6472 idms_prox_read,
6473 &ident,
6474 ct,
6475 pkce_secret.to_request(),
6476 OAUTH2_SCOPE_OPENID.to_string()
6477 );
6478
6479 let AuthoriseResponse::Permitted(_permit_success) = consent_request else {
6481 unreachable!();
6482 };
6483
6484 drop(idms_prox_read);
6485
6486 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6488
6489 let me_extend_scopes = ModifyEvent::new_internal_invalid(
6490 filter!(f_eq(
6491 Attribute::Name,
6492 PartialValue::new_iname("test_resource_server")
6493 )),
6494 ModifyList::new_list(vec![Modify::Present(
6495 Attribute::OAuth2RsScopeMap,
6496 Value::new_oauthscopemap(
6497 UUID_IDM_ALL_ACCOUNTS,
6498 btreeset![
6499 OAUTH2_SCOPE_EMAIL.to_string(),
6500 OAUTH2_SCOPE_PROFILE.to_string(),
6501 OAUTH2_SCOPE_OPENID.to_string()
6502 ],
6503 )
6504 .expect("invalid oauthscope"),
6505 )]),
6506 );
6507
6508 assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
6509 assert!(idms_prox_write.commit().is_ok());
6510
6511 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6514
6515 let ident = idms_prox_read
6517 .process_uat_to_identity(&uat, ct, Source::Internal)
6518 .expect("Unable to process uat");
6519
6520 let pkce_secret = PkceS256Secret::default();
6521
6522 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
6523
6524 let auth_req = AuthorisationRequest {
6525 response_type: ResponseType::Code,
6526 response_mode: None,
6527 client_id: "test_resource_server".to_string(),
6528 state: Some("123".to_string()),
6529 pkce_request: Some(pkce_secret.to_request()),
6530 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6531 scope: btreeset![
6532 "openid".to_string(),
6533 "email".to_string(),
6534 "profile".to_string()
6535 ],
6536 nonce: Some("abcdef".to_string()),
6537 oidc_ext: Default::default(),
6538 max_age: None,
6539 ui_locales: Default::default(),
6540 prompt: Default::default(),
6541 unknown_keys: Default::default(),
6542 };
6543
6544 let consent_request = idms_prox_read
6545 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
6546 .expect("Oauth2 authorisation failed");
6547
6548 let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
6550 unreachable!();
6551 };
6552
6553 drop(idms_prox_read);
6554
6555 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6559
6560 let me_extend_scopes = ModifyEvent::new_internal_invalid(
6561 filter!(f_eq(
6562 Attribute::Name,
6563 PartialValue::new_iname("test_resource_server")
6564 )),
6565 ModifyList::new_list(vec![Modify::Present(
6566 Attribute::OAuth2RsSupScopeMap,
6567 Value::new_oauthscopemap(UUID_IDM_ALL_ACCOUNTS, btreeset!["newscope".to_string()])
6568 .expect("invalid oauthscope"),
6569 )]),
6570 );
6571
6572 assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
6573 assert!(idms_prox_write.commit().is_ok());
6574
6575 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6578
6579 let ident = idms_prox_read
6581 .process_uat_to_identity(&uat, ct, Source::Internal)
6582 .expect("Unable to process uat");
6583
6584 let pkce_secret = PkceS256Secret::default();
6585
6586 let auth_req = AuthorisationRequest {
6587 response_type: ResponseType::Code,
6588 response_mode: None,
6589 client_id: "test_resource_server".to_string(),
6590 state: Some("123".to_string()),
6591 pkce_request: Some(pkce_secret.to_request()),
6592 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6593 scope: btreeset![
6595 "openid".to_string(),
6596 "email".to_string(),
6597 "profile".to_string()
6598 ],
6599 nonce: Some("abcdef".to_string()),
6600 oidc_ext: Default::default(),
6601 max_age: None,
6602 ui_locales: Default::default(),
6603 prompt: Default::default(),
6604 unknown_keys: Default::default(),
6605 };
6606
6607 let consent_request = idms_prox_read
6608 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
6609 .expect("Oauth2 authorisation failed");
6610
6611 let _consent_token = if let AuthoriseResponse::ConsentRequested {
6613 consent_token,
6614 scopes,
6615 ..
6616 } = consent_request
6617 {
6618 assert!(scopes.contains("newscope"));
6619 consent_token
6620 } else {
6621 unreachable!();
6622 };
6623 }
6624
6625 #[idm_test]
6626 async fn test_idm_oauth2_consent_granted_refint_cleanup_on_delete(
6627 idms: &IdmServer,
6628 _idms_delayed: &mut IdmServerDelayed,
6629 ) {
6630 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6631 let (_secret, uat, ident, o2rs_uuid) =
6632 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6633
6634 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
6636
6637 let idms_prox_read = idms.proxy_read().await.unwrap();
6638
6639 let pkce_secret = PkceS256Secret::default();
6640 let consent_request = good_authorisation_request!(
6641 idms_prox_read,
6642 &ident,
6643 ct,
6644 pkce_secret.to_request(),
6645 OAUTH2_SCOPE_OPENID.to_string()
6646 );
6647
6648 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6650 unreachable!();
6651 };
6652
6653 drop(idms_prox_read);
6655 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6656
6657 let _permit_success = idms_prox_write
6658 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6659 .expect("Failed to perform OAuth2 permit");
6660
6661 let ident = idms_prox_write
6662 .process_uat_to_identity(&uat, ct, Source::Internal)
6663 .expect("Unable to process uat");
6664
6665 assert!(
6667 ident.get_oauth2_consent_scopes(o2rs_uuid)
6668 == Some(&btreeset![
6669 OAUTH2_SCOPE_OPENID.to_string(),
6670 "supplement".to_string()
6671 ])
6672 );
6673
6674 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
6676 Attribute::Name,
6677 PartialValue::new_iname("test_resource_server")
6678 )));
6679
6680 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
6681 let ident = idms_prox_write
6683 .process_uat_to_identity(&uat, ct, Source::Internal)
6684 .expect("Unable to process uat");
6685 dbg!(&o2rs_uuid);
6686 dbg!(&ident);
6687 let consent_scopes = ident.get_oauth2_consent_scopes(o2rs_uuid);
6688 dbg!(consent_scopes);
6689 assert!(consent_scopes.is_none());
6690
6691 assert!(idms_prox_write.commit().is_ok());
6692 }
6693
6694 #[idm_test]
6714 async fn test_idm_oauth2_1076_pkce_downgrade(
6715 idms: &IdmServer,
6716 _idms_delayed: &mut IdmServerDelayed,
6717 ) {
6718 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6719 let (secret, _uat, ident, _) =
6721 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6722
6723 let idms_prox_read = idms.proxy_read().await.unwrap();
6724
6725 let pkce_secret = PkceS256Secret::default();
6730
6731 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
6732
6733 let auth_req = AuthorisationRequest {
6735 response_type: ResponseType::Code,
6736 response_mode: None,
6737 client_id: "test_resource_server".to_string(),
6738 state: Some("123".to_string()),
6739 pkce_request: None,
6740 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6741 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6742 nonce: None,
6743 oidc_ext: Default::default(),
6744 max_age: None,
6745 ui_locales: Default::default(),
6746 prompt: Default::default(),
6747 unknown_keys: Default::default(),
6748 };
6749
6750 let consent_request = idms_prox_read
6751 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
6752 .expect("Failed to perform OAuth2 authorisation request.");
6753
6754 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6756 unreachable!();
6757 };
6758
6759 drop(idms_prox_read);
6761 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6762
6763 let permit_success = idms_prox_write
6764 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6765 .expect("Failed to perform OAuth2 permit");
6766
6767 let token_req = AccessTokenRequest {
6771 grant_type: GrantTypeReq::AuthorizationCode {
6772 code: permit_success.code,
6773 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6774 code_verifier: Some(pkce_secret.to_verifier()),
6775 },
6776 client_post_auth: ClientPostAuth {
6777 client_id: Some("test_resource_server".to_string()),
6778 client_secret: Some(secret),
6779 },
6780 };
6781
6782 assert!(matches!(
6784 idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6785 Err(Oauth2Error::InvalidRequest)
6786 ));
6787
6788 assert!(idms_prox_write.commit().is_ok());
6789 }
6790
6791 #[idm_test]
6792 async fn test_idm_oauth2_redir_http_downgrade(
6796 idms: &IdmServer,
6797 _idms_delayed: &mut IdmServerDelayed,
6798 ) {
6799 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6800 let (secret, _uat, ident, _) =
6802 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6803
6804 let idms_prox_read = idms.proxy_read().await.unwrap();
6805
6806 let pkce_secret = PkceS256Secret::default();
6811
6812 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
6813
6814 let auth_req = AuthorisationRequest {
6816 response_type: ResponseType::Code,
6817 response_mode: None,
6818 client_id: "test_resource_server".to_string(),
6819 state: Some("123".to_string()),
6820 pkce_request: Some(pkce_secret.to_request()),
6821 redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6822 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6823 nonce: None,
6824 oidc_ext: Default::default(),
6825 max_age: None,
6826 ui_locales: Default::default(),
6827 prompt: Default::default(),
6828 unknown_keys: Default::default(),
6829 };
6830
6831 assert!(
6832 idms_prox_read
6833 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
6834 .unwrap_err()
6835 == Oauth2Error::InvalidOrigin
6836 );
6837
6838 let consent_request = good_authorisation_request!(
6840 idms_prox_read,
6841 &ident,
6842 ct,
6843 pkce_secret.to_request(),
6844 OAUTH2_SCOPE_OPENID.to_string()
6845 );
6846
6847 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6849 unreachable!();
6850 };
6851
6852 drop(idms_prox_read);
6854 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6855
6856 let permit_success = idms_prox_write
6857 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6858 .expect("Failed to perform OAuth2 permit");
6859
6860 let token_req = AccessTokenRequest {
6863 grant_type: GrantTypeReq::AuthorizationCode {
6864 code: permit_success.code,
6865 redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6866 code_verifier: Some(pkce_secret.to_verifier()),
6867 },
6868
6869 client_post_auth: ClientPostAuth {
6870 client_id: Some("test_resource_server".to_string()),
6871 client_secret: Some(secret),
6872 },
6873 };
6874
6875 assert!(matches!(
6877 idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6878 Err(Oauth2Error::InvalidOrigin)
6879 ));
6880
6881 assert!(idms_prox_write.commit().is_ok());
6882 }
6883
6884 async fn setup_refresh_token(
6885 idms: &IdmServer,
6886 _idms_delayed: &mut IdmServerDelayed,
6887 ct: Duration,
6888 ) -> (AccessTokenResponse, ClientAuthInfo, Uuid) {
6889 let (secret, _uat, ident, oauth2_rs_uuid) =
6891 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6892 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6893
6894 let idms_prox_read = idms.proxy_read().await.unwrap();
6895
6896 let pkce_secret = PkceS256Secret::default();
6898
6899 let consent_request = good_authorisation_request!(
6900 idms_prox_read,
6901 &ident,
6902 ct,
6903 pkce_secret.to_request(),
6904 OAUTH2_SCOPE_OPENID.to_string()
6905 );
6906
6907 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6908 unreachable!();
6909 };
6910
6911 drop(idms_prox_read);
6913 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6914
6915 let permit_success = idms_prox_write
6916 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6917 .expect("Failed to perform OAuth2 permit");
6918
6919 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
6920 code: permit_success.code,
6921 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6922 code_verifier: Some(pkce_secret.to_verifier()),
6923 }
6924 .into();
6925 let access_token_response_1 = idms_prox_write
6926 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6927 .expect("Unable to exchange for OAuth2 token");
6928
6929 assert!(idms_prox_write.commit().is_ok());
6930
6931 trace!(?access_token_response_1);
6932
6933 (access_token_response_1, client_authz, oauth2_rs_uuid)
6934 }
6935
6936 #[idm_test]
6937 async fn test_idm_oauth2_refresh_token_basic(
6938 idms: &IdmServer,
6939 idms_delayed: &mut IdmServerDelayed,
6940 ) {
6941 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6943
6944 let (access_token_response_1, client_authz, oauth2_rs_uuid) =
6945 setup_refresh_token(idms, idms_delayed, ct).await;
6946
6947 let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
6951 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6952
6953 let refresh_token = access_token_response_1
6954 .refresh_token
6955 .as_ref()
6956 .expect("no refresh token was issued")
6957 .clone();
6958
6959 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6960 refresh_token,
6961 scope: None,
6962 }
6963 .into();
6964
6965 let access_token_response_2 = idms_prox_write
6966 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6967 .expect("Unable to exchange for OAuth2 token");
6968
6969 assert!(idms_prox_write.commit().is_ok());
6970
6971 trace!(?access_token_response_2);
6972
6973 assert!(access_token_response_1.access_token != access_token_response_2.access_token);
6974 assert!(access_token_response_1.refresh_token != access_token_response_2.refresh_token);
6975 assert!(access_token_response_1.id_token != access_token_response_2.id_token);
6976
6977 let ct =
6980 Duration::from_secs(TEST_CURRENT_TIME + 20 + access_token_response_2.expires_in as u64);
6981
6982 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6983
6984 let refresh_token = access_token_response_2
6985 .refresh_token
6986 .as_ref()
6987 .expect("no refresh token was issued")
6988 .clone();
6989
6990 let reflected_token = idms_prox_write
6992 .reflect_oauth2_token(&refresh_token)
6993 .expect("Failed to access internals of the refresh token");
6994
6995 let refresh_exp = match reflected_token {
6996 Oauth2TokenType::Refresh { exp, .. } => exp,
6997 Oauth2TokenType::ClientAccess { .. } => unreachable!(),
6999 };
7000
7001 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7002 refresh_token,
7003 scope: None,
7004 }
7005 .into();
7006
7007 let access_token_response_3 = idms_prox_write
7008 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7009 .expect("Unable to exchange for OAuth2 token");
7010
7011 let entry = idms_prox_write
7014 .qs_write
7015 .internal_search_uuid(UUID_TESTPERSON_1)
7016 .expect("failed");
7017 let session = entry
7018 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
7019 .and_then(|sessions| sessions.first_key_value())
7020 .unwrap();
7022
7023 trace!(?session);
7024 assert_eq!(
7026 SessionState::ExpiresAt(
7027 time::OffsetDateTime::UNIX_EPOCH
7028 + ct
7029 + Duration::from_secs(OAUTH_REFRESH_TOKEN_EXPIRY as u64)
7030 ),
7031 session.1.state
7032 );
7033
7034 assert!(idms_prox_write.commit().is_ok());
7035
7036 trace!(?access_token_response_3);
7037
7038 assert!(access_token_response_3.access_token != access_token_response_2.access_token);
7039 assert!(access_token_response_3.refresh_token != access_token_response_2.refresh_token);
7040 assert!(access_token_response_3.id_token != access_token_response_2.id_token);
7041
7042 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7046
7047 let custom_exp = OAUTH_REFRESH_TOKEN_EXPIRY + 1;
7048
7049 let modlist = ModifyList::new_list(vec![
7050 Modify::Set(
7052 Attribute::OAuth2RefreshTokenExpiry,
7053 ValueSetUint32::new(custom_exp),
7054 ),
7055 ]);
7056
7057 assert!(idms_prox_write
7058 .qs_write
7059 .internal_modify(
7060 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7061 &modlist,
7062 )
7063 .is_ok());
7064
7065 assert!(idms_prox_write.commit().is_ok());
7066
7067 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7069
7070 let refresh_token = access_token_response_3
7071 .refresh_token
7072 .as_ref()
7073 .expect("no refresh token was issued")
7074 .clone();
7075
7076 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7077 refresh_token,
7078 scope: None,
7079 }
7080 .into();
7081 let access_token_response_4 = idms_prox_write
7082 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7083 .unwrap();
7084
7085 let entry = idms_prox_write
7086 .qs_write
7087 .internal_search_uuid(UUID_TESTPERSON_1)
7088 .expect("failed");
7089 let session = entry
7090 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
7091 .and_then(|sessions| sessions.first_key_value())
7092 .unwrap();
7094
7095 trace!(?session);
7096 assert_eq!(
7099 SessionState::ExpiresAt(
7100 time::OffsetDateTime::UNIX_EPOCH + ct + Duration::from_secs(custom_exp as u64)
7101 ),
7102 session.1.state
7103 );
7104
7105 assert!(idms_prox_write.commit().is_ok());
7106
7107 let ct = Duration::from_secs(
7113 TEST_CURRENT_TIME + refresh_exp as u64 + access_token_response_4.expires_in as u64,
7114 );
7115
7116 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7117
7118 let refresh_token = access_token_response_4
7119 .refresh_token
7120 .as_ref()
7121 .expect("no refresh token was issued")
7122 .clone();
7123
7124 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7125 refresh_token,
7126 scope: None,
7127 }
7128 .into();
7129 let access_token_response_5 = idms_prox_write
7130 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7131 .unwrap_err();
7132
7133 assert_eq!(access_token_response_5, Oauth2Error::InvalidGrant);
7134
7135 assert!(idms_prox_write.commit().is_ok());
7136 }
7137
7138 #[idm_test]
7140 async fn test_idm_oauth2_refresh_token_oauth2_session_expired(
7141 idms: &IdmServer,
7142 idms_delayed: &mut IdmServerDelayed,
7143 ) {
7144 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7146
7147 let (access_token_response_1, client_authz, _oauth2_rs_uuid) =
7148 setup_refresh_token(idms, idms_delayed, ct).await;
7149
7150 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7154 let revoke_request = TokenRevokeRequest {
7155 token: access_token_response_1.access_token.clone(),
7156 token_type_hint: None,
7157 client_post_auth: ClientPostAuth::default(),
7158 };
7159 assert!(idms_prox_write
7160 .oauth2_token_revoke(&revoke_request, ct,)
7161 .is_ok());
7162 assert!(idms_prox_write.commit().is_ok());
7163
7164 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7167
7168 let refresh_token = access_token_response_1
7169 .refresh_token
7170 .as_ref()
7171 .expect("no refresh token was issued")
7172 .clone();
7173
7174 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7175 refresh_token,
7176 scope: None,
7177 }
7178 .into();
7179 let access_token_response_2 = idms_prox_write
7180 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7181 .unwrap_err();
7183
7184 assert_eq!(access_token_response_2, Oauth2Error::InvalidGrant);
7185
7186 assert!(idms_prox_write.commit().is_ok());
7187 }
7188
7189 #[idm_test]
7191 async fn test_idm_oauth2_refresh_token_invalid_client_authz(
7192 idms: &IdmServer,
7193 idms_delayed: &mut IdmServerDelayed,
7194 ) {
7195 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7197
7198 let (access_token_response_1, _client_authz, _oauth2_rs_uuid) =
7199 setup_refresh_token(idms, idms_delayed, ct).await;
7200
7201 let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
7202
7203 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7207
7208 let refresh_token = access_token_response_1
7209 .refresh_token
7210 .as_ref()
7211 .expect("no refresh token was issued")
7212 .clone();
7213
7214 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7215 refresh_token,
7216 scope: None,
7217 }
7218 .into();
7219 let access_token_response_2 = idms_prox_write
7220 .check_oauth2_token_exchange(&bad_client_authz, &token_req, ct)
7221 .unwrap_err();
7222
7223 assert_eq!(access_token_response_2, Oauth2Error::AuthenticationRequired);
7224
7225 assert!(idms_prox_write.commit().is_ok());
7226 }
7227
7228 #[idm_test]
7230 async fn test_idm_oauth2_refresh_token_inconsistent_scopes(
7231 idms: &IdmServer,
7232 idms_delayed: &mut IdmServerDelayed,
7233 ) {
7234 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7236
7237 let (access_token_response_1, client_authz, _oauth2_rs_uuid) =
7238 setup_refresh_token(idms, idms_delayed, ct).await;
7239
7240 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7244
7245 let refresh_token = access_token_response_1
7246 .refresh_token
7247 .as_ref()
7248 .expect("no refresh token was issued")
7249 .clone();
7250
7251 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7252 refresh_token,
7253 scope: Some(btreeset!["invalid_scope".to_string()]),
7254 }
7255 .into();
7256 let access_token_response_2 = idms_prox_write
7257 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7258 .unwrap_err();
7259
7260 assert_eq!(access_token_response_2, Oauth2Error::InvalidScope);
7261
7262 assert!(idms_prox_write.commit().is_ok());
7263 }
7264
7265 #[idm_test]
7269 async fn test_idm_oauth2_refresh_token_reuse_invalidates_session(
7270 idms: &IdmServer,
7271 idms_delayed: &mut IdmServerDelayed,
7272 ) {
7273 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7275
7276 let (access_token_response_1, client_authz, _oauth2_rs_uuid) =
7277 setup_refresh_token(idms, idms_delayed, ct).await;
7278
7279 let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
7282 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7283
7284 let refresh_token = access_token_response_1
7285 .refresh_token
7286 .as_ref()
7287 .expect("no refresh token was issued")
7288 .clone();
7289
7290 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7291 refresh_token,
7292 scope: None,
7293 }
7294 .into();
7295
7296 let _access_token_response_2 = idms_prox_write
7297 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7298 .expect("Unable to exchange for OAuth2 token");
7299
7300 assert!(idms_prox_write.commit().is_ok());
7301
7302 let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
7304 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7305
7306 let refresh_token = access_token_response_1
7307 .refresh_token
7308 .as_ref()
7309 .expect("no refresh token was issued")
7310 .clone();
7311
7312 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7313 refresh_token,
7314 scope: None,
7315 }
7316 .into();
7317
7318 let access_token_response_3 = idms_prox_write
7319 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7320 .unwrap_err();
7321
7322 assert_eq!(access_token_response_3, Oauth2Error::InvalidGrant);
7323
7324 let entry = idms_prox_write
7325 .qs_write
7326 .internal_search_uuid(UUID_TESTPERSON_1)
7327 .expect("failed");
7328 let valid = entry
7329 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
7330 .and_then(|sessions| sessions.first_key_value())
7331 .map(|(_, session)| !matches!(session.state, SessionState::RevokedAt(_)))
7332 .unwrap();
7334 assert!(!valid);
7336
7337 assert!(idms_prox_write.commit().is_ok());
7338 }
7339
7340 #[idm_test]
7347 async fn test_idm_oauth2_refresh_token_divergence(
7348 idms: &IdmServer,
7349 idms_delayed: &mut IdmServerDelayed,
7350 ) {
7351 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7353
7354 let (access_token_response_1, client_authz, _oauth2_rs_uuid) =
7355 setup_refresh_token(idms, idms_delayed, ct).await;
7356
7357 let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
7360 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7361
7362 let refresh_token = access_token_response_1
7363 .refresh_token
7364 .as_ref()
7365 .expect("no refresh token was issued")
7366 .clone();
7367
7368 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7369 refresh_token,
7370 scope: None,
7371 }
7372 .into();
7373
7374 let access_token_response_2 = idms_prox_write
7375 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7376 .expect("Unable to exchange for OAuth2 token");
7377
7378 drop(idms_prox_write);
7381
7382 let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
7384 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7385
7386 let refresh_token = access_token_response_2
7387 .refresh_token
7388 .as_ref()
7389 .expect("no refresh token was issued")
7390 .clone();
7391
7392 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7393 refresh_token,
7394 scope: None,
7395 }
7396 .into();
7397
7398 let _access_token_response_3 = idms_prox_write
7399 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7400 .expect("Unable to exchange for OAuth2 token");
7401
7402 assert!(idms_prox_write.commit().is_ok());
7403
7404 }
7406
7407 #[idm_test]
7408 async fn test_idm_oauth2_refresh_token_scope_constraints(
7409 idms: &IdmServer,
7410 idms_delayed: &mut IdmServerDelayed,
7411 ) {
7412 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7414
7415 let (access_token_response_1, client_authz, _oauth2_rs_uuid) =
7416 setup_refresh_token(idms, idms_delayed, ct).await;
7417
7418 let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
7427 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7428
7429 let refresh_token = access_token_response_1
7430 .refresh_token
7431 .as_ref()
7432 .expect("no refresh token was issued")
7433 .clone();
7434
7435 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
7437
7438 let access_token_unverified = JwsCompact::from_str(&access_token_response_1.access_token)
7439 .expect("Invalid Access Token");
7440
7441 let reflected_token = jws_verifier
7442 .verify(&access_token_unverified)
7443 .unwrap()
7444 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7445 .expect("Failed to access internals of the refresh token");
7446
7447 trace!(?reflected_token);
7448 let initial_scopes = reflected_token.extensions.scope;
7449 trace!(?initial_scopes);
7450
7451 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7453 refresh_token,
7454 scope: None,
7455 }
7456 .into();
7457
7458 let access_token_response_2 = idms_prox_write
7459 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7460 .expect("Unable to exchange for OAuth2 token");
7461
7462 let access_token_unverified = JwsCompact::from_str(&access_token_response_2.access_token)
7463 .expect("Invalid Access Token");
7464
7465 let reflected_token = jws_verifier
7466 .verify(&access_token_unverified)
7467 .unwrap()
7468 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7469 .expect("Failed to access internals of the refresh token");
7470
7471 assert_eq!(initial_scopes, reflected_token.extensions.scope);
7472
7473 let refresh_token = access_token_response_2
7474 .refresh_token
7475 .as_ref()
7476 .expect("no refresh token was issued")
7477 .clone();
7478
7479 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7481 refresh_token,
7482 scope: Some(["openid".to_string()].into()),
7483 }
7484 .into();
7485
7486 let access_token_response_3 = idms_prox_write
7487 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7488 .expect("Unable to exchange for OAuth2 token");
7489
7490 let access_token_unverified = JwsCompact::from_str(&access_token_response_3.access_token)
7491 .expect("Invalid Access Token");
7492
7493 let reflected_token = jws_verifier
7494 .verify(&access_token_unverified)
7495 .unwrap()
7496 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7497 .expect("Failed to access internals of the refresh token");
7498
7499 assert_ne!(initial_scopes, reflected_token.extensions.scope);
7500
7501 let constrained_scopes = reflected_token.extensions.scope;
7503
7504 let refresh_token = access_token_response_3
7505 .refresh_token
7506 .as_ref()
7507 .expect("no refresh token was issued")
7508 .clone();
7509
7510 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7512 refresh_token,
7513 scope: None,
7514 }
7515 .into();
7516
7517 let access_token_response_4 = idms_prox_write
7518 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7519 .expect("Unable to exchange for OAuth2 token");
7520
7521 let access_token_unverified = JwsCompact::from_str(&access_token_response_4.access_token)
7522 .expect("Invalid Access Token");
7523
7524 let reflected_token = jws_verifier
7525 .verify(&access_token_unverified)
7526 .unwrap()
7527 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7528 .expect("Failed to access internals of the refresh token");
7529
7530 assert_ne!(initial_scopes, reflected_token.extensions.scope);
7531 assert_eq!(constrained_scopes, reflected_token.extensions.scope);
7532
7533 let refresh_token = access_token_response_4
7534 .refresh_token
7535 .as_ref()
7536 .expect("no refresh token was issued")
7537 .clone();
7538
7539 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7541 refresh_token,
7542 scope: Some(initial_scopes),
7543 }
7544 .into();
7545
7546 let access_token_response_5_err = idms_prox_write
7547 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7548 .unwrap_err();
7549
7550 assert_eq!(access_token_response_5_err, Oauth2Error::InvalidScope);
7551
7552 assert!(idms_prox_write.commit().is_ok());
7553 }
7554
7555 #[test]
7556 fn compliant_serialization_test() {
7559 let token_req: Result<AccessTokenRequest, serde_json::Error> = serde_json::from_str(
7560 r#"
7561 {
7562 "grant_type": "refresh_token",
7563 "refresh_token": "some_dumb_refresh_token",
7564 "scope": "invalid_scope vasd asd"
7565 }
7566 "#,
7567 );
7568 assert!(token_req.is_ok());
7569 }
7570
7571 #[idm_test]
7572 async fn test_idm_oauth2_custom_claims(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
7573 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7574 let (secret, _uat, ident, oauth2_rs_uuid) =
7575 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7576
7577 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7579
7580 let modlist = ModifyList::new_list(vec![
7581 Modify::Present(
7583 Attribute::OAuth2RsClaimMap,
7584 Value::OauthClaimMap(
7585 "custom_a".to_string(),
7586 OauthClaimMapJoin::CommaSeparatedValue,
7587 ),
7588 ),
7589 Modify::Present(
7590 Attribute::OAuth2RsClaimMap,
7591 Value::OauthClaimValue(
7592 "custom_a".to_string(),
7593 UUID_TESTGROUP,
7594 btreeset!["value_a".to_string()],
7595 ),
7596 ),
7597 Modify::Present(
7599 Attribute::OAuth2RsClaimMap,
7600 Value::OauthClaimValue(
7601 "custom_a".to_string(),
7602 UUID_IDM_ALL_ACCOUNTS,
7603 btreeset!["value_b".to_string()],
7604 ),
7605 ),
7606 Modify::Present(
7608 Attribute::OAuth2RsClaimMap,
7609 Value::OauthClaimMap(
7610 "custom_b".to_string(),
7611 OauthClaimMapJoin::SpaceSeparatedValue,
7612 ),
7613 ),
7614 Modify::Present(
7615 Attribute::OAuth2RsClaimMap,
7616 Value::OauthClaimValue(
7617 "custom_b".to_string(),
7618 UUID_TESTGROUP,
7619 btreeset!["value_a".to_string()],
7620 ),
7621 ),
7622 Modify::Present(
7623 Attribute::OAuth2RsClaimMap,
7624 Value::OauthClaimValue(
7625 "custom_b".to_string(),
7626 UUID_IDM_ALL_ACCOUNTS,
7627 btreeset!["value_b".to_string()],
7628 ),
7629 ),
7630 Modify::Present(
7632 Attribute::OAuth2RsClaimMap,
7633 Value::OauthClaimValue(
7634 "custom_b".to_string(),
7635 UUID_IDM_ADMINS,
7636 btreeset!["value_c".to_string()],
7637 ),
7638 ),
7639 Modify::Present(
7641 Attribute::OAuth2RsClaimMap,
7642 Value::OauthClaimMap(
7643 "custom:claim-name".to_string(),
7644 OauthClaimMapJoin::CommaSeparatedValue,
7645 ),
7646 ),
7647 Modify::Present(
7648 Attribute::OAuth2RsClaimMap,
7649 Value::OauthClaimValue(
7650 "custom:claim-name".to_string(),
7651 UUID_TESTGROUP,
7652 btreeset!["value:a-a".to_string()],
7653 ),
7654 ),
7655 ]);
7656
7657 assert!(idms_prox_write
7658 .qs_write
7659 .internal_modify(
7660 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7661 &modlist,
7662 )
7663 .is_ok());
7664
7665 assert!(idms_prox_write.commit().is_ok());
7666
7667 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7669
7670 let idms_prox_read = idms.proxy_read().await.unwrap();
7671
7672 let pkce_secret = PkceS256Secret::default();
7673
7674 let consent_request = good_authorisation_request!(
7675 idms_prox_read,
7676 &ident,
7677 ct,
7678 pkce_secret.to_request(),
7679 OAUTH2_SCOPE_OPENID.to_string()
7680 );
7681
7682 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7683 unreachable!();
7684 };
7685
7686 drop(idms_prox_read);
7688 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7689
7690 let permit_success = idms_prox_write
7691 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7692 .expect("Failed to perform OAuth2 permit");
7693
7694 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
7696 code: permit_success.code,
7697 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
7698 code_verifier: Some(pkce_secret.to_verifier()),
7699 }
7700 .into();
7701
7702 let token_response = idms_prox_write
7703 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7704 .expect("Failed to perform OAuth2 token exchange");
7705
7706 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7708
7709 let id_token = token_response.id_token.expect("No id_token in response!");
7710 let access_token =
7711 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
7712
7713 assert!(idms_prox_write.commit().is_ok());
7715
7716 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7717
7718 let mut jwkset = idms_prox_read
7719 .oauth2_openid_publickey("test_resource_server")
7720 .expect("Failed to get public key");
7721
7722 let public_jwk = jwkset.keys.pop().expect("no such jwk");
7723
7724 let jws_validator =
7725 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
7726
7727 let oidc_unverified =
7728 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
7729
7730 let iat = ct.as_secs() as i64;
7731
7732 let oidc = jws_validator
7733 .verify(&oidc_unverified)
7734 .unwrap()
7735 .verify_exp(iat)
7736 .expect("Failed to verify oidc");
7737
7738 assert!(
7740 oidc.iss
7741 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
7742 .unwrap()
7743 );
7744 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
7745 assert_eq!(oidc.aud, "test_resource_server");
7746 assert_eq!(oidc.iat, iat);
7747 assert_eq!(oidc.nbf, Some(iat));
7748 assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
7750 assert!(oidc.auth_time.is_some());
7751 assert_eq!(oidc.nonce, Some("abcdef".to_string()));
7753 assert!(oidc.at_hash.is_none());
7754 assert!(oidc.acr.is_none());
7755 assert!(oidc.amr.is_none());
7756 assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
7757 assert!(oidc.jti.is_some());
7758 if let Some(jti) = &oidc.jti {
7759 assert!(Uuid::from_str(jti).is_ok());
7760 }
7761 assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
7762 assert_eq!(
7763 oidc.s_claims.preferred_username,
7764 Some("testperson1@example.com".to_string())
7765 );
7766 assert!(
7767 oidc.s_claims.scopes == vec![OAUTH2_SCOPE_OPENID.to_string(), "supplement".to_string()]
7768 );
7769
7770 assert_eq!(
7771 oidc.claims.get("custom_a").and_then(|v| v.as_str()),
7772 Some("value_a,value_b")
7773 );
7774 assert_eq!(
7775 oidc.claims.get("custom_b").and_then(|v| v.as_str()),
7776 Some("value_a value_b")
7777 );
7778
7779 assert_eq!(
7780 oidc.claims
7781 .get("custom:claim-name")
7782 .and_then(|v| v.as_str()),
7783 Some("value:a-a")
7784 );
7785
7786 let userinfo = idms_prox_read
7789 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
7790 .expect("failed to get userinfo");
7791
7792 assert_eq!(oidc.iss, userinfo.iss);
7793 assert_eq!(oidc.sub, userinfo.sub);
7794 assert_eq!(oidc.aud, userinfo.aud);
7795 assert_eq!(oidc.iat, userinfo.iat);
7796 assert_eq!(oidc.nbf, userinfo.nbf);
7797 assert_eq!(oidc.exp, userinfo.exp);
7798 assert_eq!(oidc.auth_time, userinfo.auth_time);
7799 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
7800 assert!(userinfo.at_hash.is_none());
7801 assert!(userinfo.jti.is_some());
7802 if let Some(jti) = &userinfo.jti {
7803 assert!(Uuid::from_str(jti).is_ok());
7804 }
7805 assert_eq!(oidc.amr, userinfo.amr);
7806 assert_eq!(oidc.azp, userinfo.azp);
7807 assert!(userinfo.jti.is_some());
7808 if let Some(jti) = &userinfo.jti {
7809 assert!(Uuid::from_str(jti).is_ok());
7810 }
7811 assert_eq!(oidc.s_claims, userinfo.s_claims);
7812 assert_eq!(oidc.claims, userinfo.claims);
7813
7814 let intr_request = AccessTokenIntrospectRequest {
7816 token: token_response.access_token.clone(),
7817 token_type_hint: None,
7818 client_post_auth: ClientPostAuth::default(),
7819 };
7820 let intr_response = idms_prox_read
7821 .check_oauth2_token_introspect(&intr_request, ct)
7822 .expect("Failed to inspect token");
7823
7824 eprintln!("👉 {intr_response:?}");
7825 assert!(intr_response.active);
7826 assert_eq!(
7827 intr_response.scope,
7828 btreeset!["openid".to_string(), "supplement".to_string()]
7829 );
7830 assert_eq!(
7831 intr_response.client_id.as_deref(),
7832 Some("test_resource_server")
7833 );
7834 assert_eq!(
7835 intr_response.username.as_deref(),
7836 Some("testperson1@example.com")
7837 );
7838 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7839 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7840 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7841 drop(idms_prox_read);
7844 }
7845
7846 #[idm_test]
7847 async fn test_idm_oauth2_public_allow_localhost_redirect(
7848 idms: &IdmServer,
7849 _idms_delayed: &mut IdmServerDelayed,
7850 ) {
7851 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7852 let (_uat, ident, oauth2_rs_uuid) = setup_oauth2_resource_server_public(idms, ct).await;
7853
7854 let mut idms_prox_write: crate::idm::server::IdmServerProxyWriteTransaction<'_> =
7855 idms.proxy_write(ct).await.unwrap();
7856
7857 let redirect_uri = Url::parse("http://localhost:8765/oauth2/result")
7858 .expect("Failed to parse redirect URL");
7859
7860 let modlist = ModifyList::new_list(vec![
7861 Modify::Present(Attribute::OAuth2AllowLocalhostRedirect, Value::Bool(true)),
7862 Modify::Present(Attribute::OAuth2RsOrigin, Value::Url(redirect_uri.clone())),
7863 ]);
7864
7865 assert!(idms_prox_write
7866 .qs_write
7867 .internal_modify(
7868 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7869 &modlist,
7870 )
7871 .is_ok());
7872
7873 assert!(idms_prox_write.commit().is_ok());
7874
7875 let idms_prox_read = idms.proxy_read().await.unwrap();
7876
7877 let pkce_secret = PkceS256Secret::default();
7879
7880 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
7881
7882 let auth_req = AuthorisationRequest {
7883 response_type: ResponseType::Code,
7884 response_mode: None,
7885 client_id: "test_resource_server".to_string(),
7886 state: Some("123".to_string()),
7887 pkce_request: Some(pkce_secret.to_request()),
7888 redirect_uri: Url::parse("http://localhost:8765/oauth2/result").unwrap(),
7889 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
7890 nonce: Some("abcdef".to_string()),
7891 oidc_ext: Default::default(),
7892 max_age: None,
7893 ui_locales: Default::default(),
7894 prompt: Default::default(),
7895 unknown_keys: Default::default(),
7896 };
7897
7898 let consent_request = idms_prox_read
7899 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
7900 .expect("OAuth2 authorisation failed");
7901
7902 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7904 unreachable!();
7905 };
7906
7907 drop(idms_prox_read);
7909 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7910
7911 let permit_success = idms_prox_write
7912 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7913 .expect("Failed to perform OAuth2 permit");
7914
7915 assert_eq!(permit_success.state.as_deref(), Some("123"));
7917
7918 let token_req = AccessTokenRequest {
7920 grant_type: GrantTypeReq::AuthorizationCode {
7921 code: permit_success.code,
7922 redirect_uri,
7923 code_verifier: Some(pkce_secret.to_verifier()),
7924 },
7925 client_post_auth: ClientPostAuth {
7926 client_id: Some("test_resource_server".to_string()),
7927 client_secret: None,
7928 },
7929 };
7930
7931 let token_response = idms_prox_write
7932 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7933 .expect("Failed to perform OAuth2 token exchange");
7934
7935 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7937
7938 assert!(idms_prox_write.commit().is_ok());
7939 }
7940
7941 #[idm_test]
7942 async fn test_idm_oauth2_service_account_token_exchange(
7943 idms: &IdmServer,
7944 _idms_delayed: &mut IdmServerDelayed,
7945 ) {
7946 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7947 let (secret, _uat, _ident, _) =
7948 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7949
7950 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7951
7952 let service_account_uuid = Uuid::new_v4();
7953 let sa_entry: Entry<EntryInit, EntryNew> = entry_init!(
7954 (Attribute::Class, EntryClass::Object.to_value()),
7955 (Attribute::Class, EntryClass::Account.to_value()),
7956 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
7957 (Attribute::Name, Value::new_iname("test_sa_oauth2")),
7958 (Attribute::Uuid, Value::Uuid(service_account_uuid)),
7959 (Attribute::DisplayName, Value::new_utf8s("test_sa_oauth2")),
7960 (Attribute::Description, Value::new_utf8s("test_sa_oauth2"))
7961 );
7962
7963 idms_prox_write
7964 .qs_write
7965 .internal_create(vec![sa_entry])
7966 .expect("Failed to create service account");
7967
7968 idms_prox_write
7969 .qs_write
7970 .internal_modify(
7971 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTGROUP))),
7972 &ModifyList::new_list(vec![Modify::Present(
7973 Attribute::Member,
7974 Value::Refer(service_account_uuid),
7975 )]),
7976 )
7977 .expect("Failed to add service account to scope group");
7978
7979 let gte = GenerateApiTokenEvent::new_internal(service_account_uuid, "sa-token", None);
7980
7981 let api_token = idms_prox_write
7982 .service_account_generate_api_token(>e, ct)
7983 .expect("failed to generate api token");
7984
7985 assert!(idms_prox_write.commit().is_ok());
7986
7987 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7988
7989 let scopes: BTreeSet<String> =
7990 btreeset![OAUTH2_SCOPE_OPENID.into(), OAUTH2_SCOPE_GROUPS.into()];
7991
7992 let build_exchange_request =
7993 |requested_scopes: BTreeSet<String>, client_secret: Option<String>| {
7994 AccessTokenRequest {
7995 grant_type: GrantTypeReq::TokenExchange {
7996 subject_token: api_token.to_string(),
7997 subject_token_type: TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS.into(),
7998 requested_token_type: None,
7999 audience: Some("test_resource_server".into()),
8000 resource: None,
8001 actor_token: None,
8002 actor_token_type: None,
8003 scope: Some(requested_scopes),
8004 },
8005 client_post_auth: ClientPostAuth {
8006 client_id: Some("test_resource_server".into()),
8007 client_secret,
8008 },
8009 }
8010 };
8011
8012 let token_req = build_exchange_request(scopes.clone(), None);
8013 let forbidden_secret_req = build_exchange_request(scopes.clone(), Some(secret.clone()));
8014 let empty_scope_req = build_exchange_request(BTreeSet::new(), None);
8015
8016 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
8017
8018 assert_eq!(
8019 idms_prox_write
8020 .check_oauth2_token_exchange(&client_authz, &forbidden_secret_req, ct)
8021 .unwrap_err(),
8022 Oauth2Error::InvalidRequest
8023 );
8024
8025 assert_eq!(
8026 idms_prox_write
8027 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &empty_scope_req, ct)
8028 .unwrap_err(),
8029 Oauth2Error::InvalidRequest
8030 );
8031
8032 let token_response = idms_prox_write
8033 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
8034 .expect("Failed to perform OAuth2 token exchange for service account");
8035
8036 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
8037 assert_eq!(
8038 token_response.issued_token_type,
8039 Some(IssuedTokenType::AccessToken)
8040 );
8041 assert!(token_response.refresh_token.is_some());
8042 assert!(token_response.id_token.is_some());
8043 let response_scopes = token_response.scope.clone();
8044 assert!(response_scopes.contains(OAUTH2_SCOPE_OPENID));
8045 assert!(response_scopes.contains(OAUTH2_SCOPE_GROUPS));
8046
8047 assert!(idms_prox_write.commit().is_ok());
8048
8049 let mut idms_prox_read = idms.proxy_read().await.unwrap();
8050 let intr_request = AccessTokenIntrospectRequest {
8051 token: token_response.access_token.clone(),
8052 token_type_hint: None,
8053 client_post_auth: ClientPostAuth::default(),
8054 };
8055 let intr_response = idms_prox_read
8056 .check_oauth2_token_introspect(&intr_request, ct)
8057 .expect("Failed to introspect service account token");
8058
8059 assert!(intr_response.active);
8060 assert_eq!(
8061 intr_response.client_id.as_deref(),
8062 Some("test_resource_server")
8063 );
8064 assert_eq!(
8065 intr_response.sub.as_deref(),
8066 Some(service_account_uuid.to_string().as_str())
8067 );
8068 }
8069
8070 #[idm_test]
8071 async fn test_idm_oauth2_basic_client_credentials_grant_valid(
8072 idms: &IdmServer,
8073 _idms_delayed: &mut IdmServerDelayed,
8074 ) {
8075 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8076 let (secret, _uat, _ident, _) =
8077 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8078
8079 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
8081
8082 let token_req = AccessTokenRequest {
8083 grant_type: GrantTypeReq::ClientCredentials { scope: None },
8084
8085 client_post_auth: ClientPostAuth {
8086 client_id: Some("test_resource_server".to_string()),
8087 client_secret: Some(secret),
8088 },
8089 };
8090
8091 let oauth2_token = idms_prox_write
8092 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
8093 .expect("Failed to perform OAuth2 token exchange");
8094
8095 assert!(idms_prox_write.commit().is_ok());
8096
8097 assert_eq!(oauth2_token.token_type, AccessTokenType::Bearer);
8099
8100 let mut idms_prox_read = idms.proxy_read().await.unwrap();
8102
8103 let intr_request = AccessTokenIntrospectRequest {
8104 token: oauth2_token.access_token.clone(),
8105 token_type_hint: None,
8106 client_post_auth: ClientPostAuth::default(),
8107 };
8108 let intr_response = idms_prox_read
8109 .check_oauth2_token_introspect(&intr_request, ct)
8110 .expect("Failed to inspect token");
8111
8112 eprintln!("👉 {intr_response:?}");
8113 assert!(intr_response.active);
8114 assert_eq!(intr_response.scope, btreeset!["supplement".to_string()]);
8115 assert_eq!(
8116 intr_response.client_id.as_deref(),
8117 Some("test_resource_server")
8118 );
8119 assert_eq!(
8120 intr_response.username.as_deref(),
8121 Some("test_resource_server@example.com")
8122 );
8123 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
8124 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
8125 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
8126
8127 drop(idms_prox_read);
8128
8129 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
8131 let revoke_request = TokenRevokeRequest {
8132 token: oauth2_token.access_token.clone(),
8133 token_type_hint: None,
8134 client_post_auth: ClientPostAuth::default(),
8135 };
8136 assert!(idms_prox_write
8137 .oauth2_token_revoke(&revoke_request, ct,)
8138 .is_ok());
8139 assert!(idms_prox_write.commit().is_ok());
8140
8141 let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
8143 let mut idms_prox_read = idms.proxy_read().await.unwrap();
8144
8145 let intr_request = AccessTokenIntrospectRequest {
8146 token: oauth2_token.access_token.clone(),
8147 token_type_hint: None,
8148 client_post_auth: ClientPostAuth::default(),
8149 };
8150
8151 let intr_response = idms_prox_read
8152 .check_oauth2_token_introspect(&intr_request, ct)
8153 .expect("Failed to inspect token");
8154 assert!(!intr_response.active);
8155
8156 drop(idms_prox_read);
8157 }
8158
8159 #[idm_test]
8160 async fn test_idm_oauth2_basic_client_credentials_grant_invalid(
8161 idms: &IdmServer,
8162 _idms_delayed: &mut IdmServerDelayed,
8163 ) {
8164 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8165 let (secret, _uat, _ident, _) =
8166 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8167
8168 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
8169
8170 let token_req = AccessTokenRequest {
8172 grant_type: GrantTypeReq::ClientCredentials { scope: None },
8173 client_post_auth: ClientPostAuth {
8174 client_id: Some("test_resource_server".to_string()),
8175 client_secret: None,
8176 },
8177 };
8178
8179 assert_eq!(
8180 idms_prox_write
8181 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
8182 .unwrap_err(),
8183 Oauth2Error::AuthenticationRequired
8184 );
8185
8186 let token_req = AccessTokenRequest {
8188 grant_type: GrantTypeReq::ClientCredentials { scope: None },
8189
8190 client_post_auth: ClientPostAuth {
8191 client_id: Some("test_resource_server".to_string()),
8192 client_secret: Some("wrong password".to_string()),
8193 },
8194 };
8195
8196 assert_eq!(
8197 idms_prox_write
8198 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
8199 .unwrap_err(),
8200 Oauth2Error::AuthenticationRequired
8201 );
8202
8203 let scope = Some(btreeset!["💅".to_string()]);
8205 let token_req = AccessTokenRequest {
8206 grant_type: GrantTypeReq::ClientCredentials { scope },
8207
8208 client_post_auth: ClientPostAuth {
8209 client_id: Some("test_resource_server".to_string()),
8210 client_secret: Some(secret.clone()),
8211 },
8212 };
8213
8214 assert_eq!(
8215 idms_prox_write
8216 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
8217 .unwrap_err(),
8218 Oauth2Error::InvalidScope
8219 );
8220
8221 let scope = Some(btreeset!["invalid_scope".to_string()]);
8223 let token_req = AccessTokenRequest {
8224 grant_type: GrantTypeReq::ClientCredentials { scope },
8225
8226 client_post_auth: ClientPostAuth {
8227 client_id: Some("test_resource_server".to_string()),
8228 client_secret: Some(secret.clone()),
8229 },
8230 };
8231
8232 assert_eq!(
8233 idms_prox_write
8234 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
8235 .unwrap_err(),
8236 Oauth2Error::AccessDenied
8237 );
8238
8239 assert!(idms_prox_write.commit().is_ok());
8240 }
8241
8242 #[test]
8243 fn test_get_code() {
8244 use super::{gen_device_code, gen_user_code, parse_user_code};
8245
8246 assert!(gen_device_code().is_ok());
8247
8248 let (res_string, res_value) = gen_user_code();
8249
8250 assert!(res_string.split('-').count() == 3);
8251
8252 let res_string_clean = res_string.replace("-", "");
8253 let res_string_as_num = res_string_clean
8254 .parse::<u32>()
8255 .expect("Failed to parse as number");
8256 assert_eq!(res_string_as_num, res_value);
8257
8258 assert_eq!(
8259 parse_user_code(&res_string).expect("Failed to parse code"),
8260 res_value
8261 );
8262 }
8263
8264 #[idm_test]
8265 async fn handle_oauth2_start_device_flow(
8266 idms: &IdmServer,
8267 _idms_delayed: &mut IdmServerDelayed,
8268 ) {
8269 let ct = duration_from_epoch_now();
8270
8271 let client_auth_info = ClientAuthInfo::from(Source::Https(
8272 "127.0.0.1"
8273 .parse()
8274 .expect("Failed to parse 127.0.0.1 as an IP!"),
8275 ));
8276 let eventid = Uuid::new_v4();
8277
8278 let res = idms
8279 .proxy_write(ct)
8280 .await
8281 .expect("Failed to get idmspwt")
8282 .handle_oauth2_start_device_flow(client_auth_info, "test_rs_id", &None, eventid);
8283 dbg!(&res);
8284 assert!(res.is_err());
8285 }
8286
8287 #[test]
8288 fn test_url_localhost_domain() {
8289 let example_is_not_local = "https://example.com/sdfsdf";
8293 println!("Ensuring that {example_is_not_local} is not local");
8294 assert!(!host_is_local(
8295 &Url::parse(example_is_not_local)
8296 .expect("Failed to parse example.com as a host?")
8297 .host()
8298 .unwrap_or_else(|| panic!("Couldn't get a host from {example_is_not_local}"))
8299 ));
8300
8301 let test_urls = [
8302 ("http://localhost:8080/oauth2/callback", "/oauth2/callback"),
8303 ("https://localhost/foo/bar", "/foo/bar"),
8304 ("http://127.0.0.1:12345/foo", "/foo"),
8305 ("http://[::1]:12345/foo", "/foo"),
8306 ];
8307
8308 for (url, path) in test_urls.into_iter() {
8309 println!("Testing URL: {url}");
8310 let url = Url::parse(url).expect("One of the test values failed!");
8311 assert!(host_is_local(
8312 &url.host().expect("Didn't parse a host out?")
8313 ));
8314
8315 assert_eq!(url.path(), path);
8316 }
8317 }
8318
8319 #[test]
8320 fn test_oauth2_rs_type_allow_localhost_redirect() {
8321 let test_cases = [
8322 (
8323 OauthRSType::Public {
8324 allow_localhost_redirect: true,
8325 },
8326 true,
8327 ),
8328 (
8329 OauthRSType::Public {
8330 allow_localhost_redirect: false,
8331 },
8332 false,
8333 ),
8334 (
8335 OauthRSType::Basic {
8336 authz_secret: CtSecret::from("supersecret".to_string()),
8337 enable_pkce: false,
8338 enable_consent_prompt: true,
8339 },
8340 false,
8341 ),
8342 ];
8343
8344 assert!(test_cases.iter().all(|(rs_type, expected)| {
8345 let actual = rs_type.allow_localhost_redirect();
8346 println!("Testing {rs_type:?} -> {expected}");
8347 actual == *expected
8348 }));
8349 }
8350
8351 #[idm_test]
8352 async fn test_oauth2_auth_with_no_state(
8353 idms: &IdmServer,
8354 _idms_delayed: &mut IdmServerDelayed,
8355 ) {
8356 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8357 let (_secret, _uat, ident, _) =
8358 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8359
8360 let idms_prox_read = idms.proxy_read().await.unwrap();
8361
8362 let pkce_secret = PkceS256Secret::default();
8364
8365 let scope: BTreeSet<String> = OAUTH2_SCOPE_OPENID
8366 .split(" ")
8367 .map(|s| s.to_string())
8368 .collect();
8369
8370 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
8371
8372 let auth_req = AuthorisationRequest {
8373 response_type: ResponseType::Code,
8374 response_mode: None,
8375 client_id: "test_resource_server".to_string(),
8376 state: None,
8377 pkce_request: Some(pkce_secret.to_request()),
8378 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
8379 scope,
8380 nonce: Some("abcdef".to_string()),
8381 oidc_ext: Default::default(),
8382 max_age: None,
8383 ui_locales: Default::default(),
8384 prompt: Default::default(),
8385 unknown_keys: Default::default(),
8386 };
8387 println!("{auth_req:?}");
8388
8389 let consent_request = idms_prox_read
8390 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
8391 .expect("OAuth2 authorisation failed");
8392
8393 let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
8395 unreachable!("Expected a ConsentRequested response, got: {consent_request:?}");
8396 };
8397 }
8398
8399 #[idm_test]
8400 async fn test_idm_oauth2_consent_prompt_disabled(
8401 idms: &IdmServer,
8402 _idms_delayed: &mut IdmServerDelayed,
8403 ) {
8404 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8405 let (_secret, _uat, ident, o2rs_uuid) =
8406 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8407
8408 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
8409 idms_prox_write
8410 .qs_write
8411 .internal_modify_uuid(
8412 o2rs_uuid,
8413 &ModifyList::new_purge_and_set(
8414 Attribute::OAuth2ConsentPromptEnable,
8415 Value::new_bool(false),
8416 ),
8417 )
8418 .expect("Unable to disable consent prompt");
8419 assert!(idms_prox_write.commit().is_ok());
8420
8421 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
8423
8424 let idms_prox_read = idms.proxy_read().await.unwrap();
8425
8426 let pkce_secret = PkceS256Secret::default();
8427 let consent_request = good_authorisation_request!(
8428 idms_prox_read,
8429 &ident,
8430 ct,
8431 pkce_secret.to_request(),
8432 OAUTH2_SCOPE_OPENID.to_string()
8433 );
8434
8435 let AuthoriseResponse::Permitted(_permitted) = consent_request else {
8437 unreachable!();
8438 };
8439
8440 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
8442 }
8443
8444 fn auth_req_with_prompt(
8445 pkce_request: PkceRequest,
8446 prompt: Vec<Prompt>,
8447 ) -> AuthorisationRequest {
8448 AuthorisationRequest {
8449 response_type: ResponseType::Code,
8450 response_mode: None,
8451 client_id: "test_resource_server".to_string(),
8452 state: Some("123".to_string()),
8453 pkce_request: Some(pkce_request),
8454 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
8455 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
8456 nonce: Some("abcdef".to_string()),
8457 oidc_ext: Default::default(),
8458 max_age: None,
8459 ui_locales: Default::default(),
8460 prompt,
8461 unknown_keys: Default::default(),
8462 }
8463 }
8464
8465 async fn grant_consent_for_identity(
8466 idms: &IdmServer,
8467 ident: &Identity,
8468 uat: &UserAuthToken,
8469 ct: Duration,
8470 ) -> Identity {
8471 let idms_prox_read = idms.proxy_read().await.unwrap();
8472
8473 let pkce_secret = PkceS256Secret::default();
8474 let consent_request = good_authorisation_request!(
8475 idms_prox_read,
8476 ident,
8477 ct,
8478 pkce_secret.to_request(),
8479 OAUTH2_SCOPE_OPENID.to_string()
8480 );
8481
8482 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
8483 unreachable!("Expected ConsentRequested, got: {consent_request:?}");
8484 };
8485
8486 drop(idms_prox_read);
8487 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
8488
8489 let _permit_success = idms_prox_write
8490 .check_oauth2_authorise_permit(ident, &consent_token, ct)
8491 .expect("Failed to perform OAuth2 permit");
8492
8493 let new_ident = idms_prox_write
8494 .process_uat_to_identity(uat, ct, Source::Internal)
8495 .expect("Unable to process uat");
8496
8497 assert!(idms_prox_write.commit().is_ok());
8498 new_ident
8499 }
8500
8501 #[idm_test]
8504 async fn test_idm_oauth2_prompt_fails_for_too_many_values(
8505 idms: &IdmServer,
8506 _idms_delayed: &mut IdmServerDelayed,
8507 ) {
8508 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8509 let (_secret, _uat, ident, _) =
8510 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8511
8512 let idms_prox_read = idms.proxy_read().await.unwrap();
8513 let pkce_secret = PkceS256Secret::default();
8514
8515 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
8516
8517 let auth_req = auth_req_with_prompt(
8518 pkce_secret.to_request(),
8519 Vec::from([
8520 Prompt::Login,
8521 Prompt::Consent,
8522 Prompt::SelectAccount,
8523 Prompt::None,
8524 Prompt::Invalid("bagel".into()),
8525 Prompt::Invalid("panko".into()),
8526 ]),
8527 );
8528
8529 let result =
8531 idms_prox_read.check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct);
8532
8533 assert_eq!(
8534 result.unwrap_err(),
8535 Oauth2Error::InvalidRequest,
8536 "An invalid/unrecognised prompt value must return InvalidRequest"
8537 );
8538 }
8539
8540 #[idm_test]
8546 async fn test_idm_oauth2_prompt_invalid_returns_error(
8547 idms: &IdmServer,
8548 _idms_delayed: &mut IdmServerDelayed,
8549 ) {
8550 let invalid_value = "bor-sirhc".to_string();
8551
8552 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8553 let (_secret, _uat, ident, _) =
8554 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8555
8556 let idms_prox_read = idms.proxy_read().await.unwrap();
8557 let pkce_secret = PkceS256Secret::default();
8558
8559 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
8560
8561 let auth_req = auth_req_with_prompt(
8562 pkce_secret.to_request(),
8563 Vec::from([Prompt::Invalid(invalid_value)]),
8564 );
8565
8566 let result =
8567 idms_prox_read.check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct);
8568
8569 assert_eq!(
8570 result.unwrap_err(),
8571 Oauth2Error::InvalidRequest,
8572 "An invalid/unrecognised prompt value must return InvalidRequest"
8573 );
8574 }
8575
8576 #[idm_test]
8584 async fn test_idm_oauth2_prompt_none_unauthenticated_returns_login_required(
8585 idms: &IdmServer,
8586 _idms_delayed: &mut IdmServerDelayed,
8587 ) {
8588 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8589 let (_secret, _uat, _ident, _) =
8590 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8591
8592 let idms_prox_read = idms.proxy_read().await.unwrap();
8593 let pkce_secret = PkceS256Secret::default();
8594
8595 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
8596
8597 let auth_req = auth_req_with_prompt(pkce_secret.to_request(), Vec::from([Prompt::None]));
8598
8599 let result = idms_prox_read.check_oauth2_authorisation(None, &auth_req, &auth_req_ctx, ct);
8601
8602 assert!(
8603 result.unwrap_err() == Oauth2Error::LoginRequired,
8604 "prompt=none without authentication must return login_required"
8605 );
8606 }
8607
8608 #[idm_test]
8616 async fn test_idm_oauth2_prompt_none_no_consent_returns_interaction_required(
8617 idms: &IdmServer,
8618 _idms_delayed: &mut IdmServerDelayed,
8619 ) {
8620 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8621 let (_secret, _uat, ident, _) =
8622 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8623
8624 let idms_prox_read = idms.proxy_read().await.unwrap();
8625 let pkce_secret = PkceS256Secret::default();
8626
8627 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
8628
8629 let auth_req = auth_req_with_prompt(pkce_secret.to_request(), Vec::from([Prompt::None]));
8630
8631 let result =
8633 idms_prox_read.check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct);
8634
8635 assert!(
8636 result.unwrap_err() == Oauth2Error::InteractionRequired,
8637 "prompt=none with authenticated user but no prior consent must return interaction_required"
8638 );
8639 }
8640
8641 #[idm_test]
8649 async fn test_idm_oauth2_prompt_none_with_prior_consent_succeeds(
8650 idms: &IdmServer,
8651 _idms_delayed: &mut IdmServerDelayed,
8652 ) {
8653 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8654 let (_secret, uat, ident, _) =
8655 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8656
8657 let ident = grant_consent_for_identity(idms, &ident, &uat, ct).await;
8659
8660 let idms_prox_read = idms.proxy_read().await.unwrap();
8662 let pkce_secret = PkceS256Secret::default();
8663
8664 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
8665
8666 let auth_req = auth_req_with_prompt(pkce_secret.to_request(), Vec::from([Prompt::None]));
8667
8668 let result = idms_prox_read
8669 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
8670 .expect("prompt=none with prior consent should succeed");
8671
8672 assert!(
8673 matches!(result, AuthoriseResponse::Permitted(_)),
8674 "prompt=none with authenticated user and prior consent must return Permitted"
8675 );
8676 }
8677
8678 #[idm_test]
8684 async fn test_idm_oauth2_prompt_login_forces_reauthentication(
8685 idms: &IdmServer,
8686 _idms_delayed: &mut IdmServerDelayed,
8687 ) {
8688 const OAUTH2_OIDC_PROMPT_LOGIN_GRACE: i64 = 300;
8689
8690 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8691 let (_secret, _uat, ident, _) =
8692 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8693
8694 let idms_prox_read = idms.proxy_read().await.unwrap();
8695 let pkce_secret = PkceS256Secret::default();
8696
8697 let auth_req_ctx = AuthorisationRequestContext { resumed: false };
8698
8699 let mut auth_req = auth_req_with_prompt(pkce_secret.to_request(), Vec::from([]));
8700 auth_req.max_age = Some(OAUTH2_OIDC_PROMPT_LOGIN_GRACE);
8701
8702 let result = idms_prox_read
8704 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
8705 .expect("prompt=login should not error");
8706
8707 assert!(
8708 matches!(result, AuthoriseResponse::ConsentRequested { .. }),
8709 "prompt=login should not have been forced"
8710 );
8711
8712 let ct = ct + Duration::from_secs(OAUTH2_OIDC_PROMPT_LOGIN_GRACE as u64);
8715
8716 let result = idms_prox_read
8717 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
8718 .expect("prompt=login should not error");
8719
8720 assert!(
8721 matches!(result, AuthoriseResponse::ReauthenticationRequired { .. }),
8722 "max_age must force re-authentication even when user is already authenticated"
8723 );
8724
8725 let auth_req = auth_req_with_prompt(pkce_secret.to_request(), Vec::from([Prompt::Login]));
8727
8728 let result = idms_prox_read
8729 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
8730 .expect("prompt=login should not error");
8731
8732 assert!(
8733 matches!(result, AuthoriseResponse::ReauthenticationRequired { .. }),
8734 "prompt=login must force re-authentication even when user is already authenticated"
8735 );
8736
8737 let auth_req_ctx = AuthorisationRequestContext { resumed: true };
8740
8741 let result = idms_prox_read
8742 .check_oauth2_authorisation(Some(&ident), &auth_req, &auth_req_ctx, ct)
8743 .expect("prompt=login should not error");
8744
8745 assert!(
8746 matches!(result, AuthoriseResponse::ConsentRequested { .. }),
8747 "prompt=login should not have been forced"
8748 );
8749 }
8750
8751 }