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