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