1use crate::idm::account::Account;
8use crate::idm::server::{
9 IdmServerProxyReadTransaction, IdmServerProxyWriteTransaction, IdmServerTransaction, Token,
10};
11use crate::prelude::*;
12use crate::server::keys::{KeyObject, KeyProvidersTransaction, KeyProvidersWriteTransaction};
13use crate::utils;
14use crate::value::{Oauth2Session, OauthClaimMapJoin, SessionState, OAUTHSCOPE_RE};
15use base64::{engine::general_purpose, Engine as _};
16pub use compact_jwt::{compact::JwkKeySet, OidcToken};
17use compact_jwt::{
18 crypto::{JweA128GCMEncipher, JweA128KWEncipher},
19 jwe::Jwe,
20 jws::JwsBuilder,
21 JweCompact, JwsCompact, OidcClaims, OidcSubject,
22};
23use concread::cowcell::*;
24use crypto_glue::{s256::Sha256, traits::Digest};
25use hashbrown::HashMap;
26use hashbrown::HashSet;
27use kanidm_proto::constants::*;
28use kanidm_proto::oauth2::IssuedTokenType;
29pub use kanidm_proto::oauth2::{
30 AccessTokenIntrospectRequest, AccessTokenIntrospectResponse, AccessTokenRequest,
31 AccessTokenResponse, AccessTokenType, AuthorisationRequest, ClaimType, ClientAuth,
32 ClientPostAuth, CodeChallengeMethod, DeviceAuthorizationResponse, DisplayValue, ErrorResponse,
33 GrantType, GrantTypeReq, IdTokenSignAlg, OAuth2RFC9068Token, OAuth2RFC9068TokenExtensions,
34 Oauth2Rfc8414MetadataResponse, OidcDiscoveryResponse, OidcWebfingerRel, OidcWebfingerResponse,
35 PkceAlg, PkceRequest, ResponseMode, ResponseType, SubjectType, TokenEndpointAuthMethod,
36 TokenRevokeRequest, OAUTH2_TOKEN_TYPE_ACCESS_TOKEN,
37};
38use serde::{Deserialize, Serialize};
39use serde_with::{formats, serde_as};
40use std::collections::btree_map::Entry as BTreeEntry;
41use std::collections::{BTreeMap, BTreeSet};
42use std::fmt;
43use std::str::FromStr;
44use std::sync::Arc;
45use std::time::Duration;
46use time::OffsetDateTime;
47use tracing::trace;
48use uri::{OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
49use url::{Host, Origin, Url};
50use utoipa::ToSchema;
51
52const TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS: &str = OAUTH2_TOKEN_TYPE_ACCESS_TOKEN;
53
54#[derive(Serialize, Deserialize, Debug, PartialEq, ToSchema)]
55#[serde(rename_all = "snake_case")]
56pub enum Oauth2Error {
57 AuthenticationRequired,
59 InvalidClientId,
60 InvalidOrigin,
61 InvalidRequest,
63 InvalidGrant,
64 UnauthorizedClient,
65 AccessDenied,
66 UnsupportedResponseType,
67 InvalidScope,
68 InvalidTarget,
69 ServerError(OperationError),
70 TemporarilyUnavailable,
71 InvalidToken,
73 InsufficientScope,
74 UnsupportedTokenType,
76 SlowDown,
80 AuthorizationPending,
90 ExpiredToken,
95}
96
97impl std::fmt::Display for Oauth2Error {
98 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
99 f.write_str(match self {
100 Oauth2Error::AuthenticationRequired => "authentication_required",
101 Oauth2Error::InvalidClientId => "invalid_client_id",
102 Oauth2Error::InvalidOrigin => "invalid_origin",
103 Oauth2Error::InvalidGrant => "invalid_grant",
104 Oauth2Error::InvalidRequest => "invalid_request",
105 Oauth2Error::UnauthorizedClient => "unauthorized_client",
106 Oauth2Error::AccessDenied => "access_denied",
107 Oauth2Error::UnsupportedResponseType => "unsupported_response_type",
108 Oauth2Error::InvalidScope => "invalid_scope",
109 Oauth2Error::InvalidTarget => "invalid_target",
110 Oauth2Error::ServerError(_) => "server_error",
111 Oauth2Error::TemporarilyUnavailable => "temporarily_unavailable",
112 Oauth2Error::InvalidToken => "invalid_token",
113 Oauth2Error::InsufficientScope => "insufficient_scope",
114 Oauth2Error::UnsupportedTokenType => "unsupported_token_type",
115 Oauth2Error::SlowDown => "slow_down",
116 Oauth2Error::AuthorizationPending => "authorization_pending",
117 Oauth2Error::ExpiredToken => "expired_token",
118 })
119 }
120}
121
122pub struct PkceS256Secret {
123 secret: String,
124}
125
126impl Default for PkceS256Secret {
127 fn default() -> Self {
128 Self {
129 secret: utils::password_from_random(),
130 }
131 }
132}
133
134impl From<String> for PkceS256Secret {
135 fn from(secret: String) -> Self {
136 Self { secret }
137 }
138}
139
140impl PkceS256Secret {
141 pub fn to_request(&self) -> PkceRequest {
142 let mut hasher = Sha256::new();
143 hasher.update(self.secret.as_bytes());
144 let code_challenge = hasher.finalize();
145
146 PkceRequest {
147 code_challenge: code_challenge.to_vec(),
148 code_challenge_method: CodeChallengeMethod::S256,
149 }
150 }
151
152 pub(crate) fn verifier(&self) -> &str {
153 &self.secret
154 }
155
156 pub fn to_verifier(self) -> String {
157 self.secret
158 }
159
160 pub fn verify<V: AsRef<[u8]>>(&self, challenge: V) -> bool {
161 let mut hasher = Sha256::new();
162 hasher.update(self.secret.as_bytes());
163 let code_challenge = hasher.finalize();
164
165 challenge.as_ref() == code_challenge.as_slice()
166 }
167}
168
169#[derive(Serialize, Deserialize, Debug, PartialEq)]
171enum SupportedResponseMode {
172 Query,
173 Fragment,
174}
175
176#[serde_as]
177#[derive(Serialize, Deserialize, Debug, PartialEq)]
178struct ConsentToken {
179 pub client_id: String,
180 pub session_id: Uuid,
182 pub expiry: u64,
183
184 pub ident_id: IdentityId,
186 pub state: Option<String>,
188 #[serde_as(
190 as = "Option<serde_with::base64::Base64<serde_with::base64::UrlSafe, formats::Unpadded>>"
191 )]
192 pub code_challenge: Option<Vec<u8>>,
193 pub redirect_uri: Url,
195 pub scopes: BTreeSet<String>,
197 pub nonce: Option<String>,
199 pub response_mode: SupportedResponseMode,
201}
202
203#[serde_as]
204#[derive(Serialize, Deserialize, Debug)]
205struct TokenExchangeCode {
206 pub account_uuid: Uuid,
209 pub session_id: Uuid,
210
211 pub expiry: u64,
212
213 #[serde_as(
215 as = "Option<serde_with::base64::Base64<serde_with::base64::UrlSafe, formats::Unpadded>>"
216 )]
217 pub code_challenge: Option<Vec<u8>>,
218 pub redirect_uri: Url,
220 pub scopes: BTreeSet<String>,
222 pub nonce: Option<String>,
224}
225
226#[derive(Serialize, Deserialize, Debug)]
227pub(crate) enum Oauth2TokenType {
228 Refresh {
229 scopes: BTreeSet<String>,
230 parent_session_id: Uuid,
231 session_id: Uuid,
232 exp: i64,
233 uuid: Uuid,
234 iat: i64,
236 nbf: i64,
237 nonce: Option<String>,
239 },
240 ClientAccess {
241 scopes: BTreeSet<String>,
242 session_id: Uuid,
243 uuid: Uuid,
244 exp: i64,
245 iat: i64,
246 nbf: i64,
247 },
248}
249
250impl fmt::Display for Oauth2TokenType {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 match self {
253 Oauth2TokenType::Refresh { session_id, .. } => {
254 write!(f, "refresh_token ({session_id}) ")
255 }
256 Oauth2TokenType::ClientAccess { session_id, .. } => {
257 write!(f, "client_access_token ({session_id})")
258 }
259 }
260 }
261}
262
263#[derive(Debug)]
264pub enum AuthoriseResponse {
265 AuthenticationRequired {
266 client_name: String,
268 login_hint: Option<String>,
270 },
271 ConsentRequested {
272 client_name: String,
274 scopes: BTreeSet<String>,
276 pii_scopes: BTreeSet<String>,
278 consent_token: String,
282 },
283 Permitted(AuthorisePermitSuccess),
284}
285
286#[derive(Debug)]
287pub struct AuthorisePermitSuccess {
288 pub redirect_uri: Url,
290 pub state: Option<String>,
292 pub code: String,
294 response_mode: SupportedResponseMode,
296}
297
298impl AuthorisePermitSuccess {
299 pub fn build_redirect_uri(&self) -> Url {
302 let mut redirect_uri = self.redirect_uri.clone();
303
304 redirect_uri.set_fragment(None);
306
307 match self.response_mode {
308 SupportedResponseMode::Query => {
309 redirect_uri
310 .query_pairs_mut()
311 .append_pair("code", &self.code);
312
313 if let Some(state) = self.state.as_ref() {
314 redirect_uri.query_pairs_mut().append_pair("state", state);
315 };
316 }
317 SupportedResponseMode::Fragment => {
318 redirect_uri.set_query(None);
319
320 let mut uri_builder = url::form_urlencoded::Serializer::new(String::new());
322 uri_builder.append_pair("code", &self.code);
323 if let Some(state) = self.state.as_ref() {
324 uri_builder.append_pair("state", state);
325 };
326 let encoded = uri_builder.finish();
327
328 redirect_uri.set_fragment(Some(&encoded))
329 }
330 }
331
332 redirect_uri
333 }
334}
335
336#[derive(Debug)]
337pub struct AuthoriseReject {
338 pub redirect_uri: Url,
340 response_mode: SupportedResponseMode,
342}
343
344impl AuthoriseReject {
345 pub fn build_redirect_uri(&self) -> Url {
348 let mut redirect_uri = self.redirect_uri.clone();
349
350 redirect_uri.set_query(None);
352 redirect_uri.set_fragment(None);
353
354 let encoded = url::form_urlencoded::Serializer::new(String::new())
356 .append_pair("error", "access_denied")
357 .append_pair("error_description", "authorisation rejected")
358 .finish();
359
360 match self.response_mode {
361 SupportedResponseMode::Query => redirect_uri.set_query(Some(&encoded)),
362 SupportedResponseMode::Fragment => redirect_uri.set_fragment(Some(&encoded)),
363 }
364
365 redirect_uri
366 }
367}
368
369#[derive(Clone)]
370enum OauthRSType {
371 Basic {
372 authz_secret: String,
373 enable_pkce: bool,
374 enable_consent_prompt: bool,
375 },
376 Public {
378 allow_localhost_redirect: bool,
379 },
380}
381
382impl OauthRSType {
383 fn allow_localhost_redirect(&self) -> bool {
385 match self {
386 OauthRSType::Basic { .. } => false,
387 OauthRSType::Public {
388 allow_localhost_redirect,
389 } => *allow_localhost_redirect,
390 }
391 }
392}
393
394impl std::fmt::Debug for OauthRSType {
395 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
396 let mut ds = f.debug_struct("OauthRSType");
397 match self {
398 OauthRSType::Basic {
399 enable_pkce,
400 enable_consent_prompt,
401 ..
402 } => ds
403 .field("type", &"basic")
404 .field("pkce", enable_pkce)
405 .field("consent_prompt", enable_consent_prompt),
406 OauthRSType::Public {
407 allow_localhost_redirect,
408 } => ds
409 .field("type", &"public")
410 .field("allow_localhost_redirect", allow_localhost_redirect),
411 };
412 ds.finish()
413 }
414}
415
416#[derive(Clone, Debug)]
417struct ClaimValue {
418 join: OauthClaimMapJoin,
419 values: BTreeSet<String>,
420}
421
422impl ClaimValue {
423 fn merge(&mut self, other: &Self) {
424 self.values.extend(other.values.iter().cloned())
425 }
426
427 fn to_json_value(&self) -> serde_json::Value {
428 let join_str = match self.join {
429 OauthClaimMapJoin::JsonArray => {
430 let arr: Vec<_> = self
431 .values
432 .iter()
433 .cloned()
434 .map(serde_json::Value::String)
435 .collect();
436
437 return serde_json::Value::Array(arr);
439 }
440 joiner => joiner.to_str(),
441 };
442
443 let joined = str_concat!(&self.values, join_str);
444
445 serde_json::Value::String(joined)
446 }
447}
448
449#[derive(Clone, Copy, Debug)]
450enum SignatureAlgo {
451 Es256,
452 Rs256,
453}
454
455#[derive(Clone)]
456pub struct Oauth2RS {
457 name: String,
458 displayname: String,
459 uuid: Uuid,
460
461 origins: HashSet<Origin>,
462 opaque_origins: HashSet<Url>,
463 redirect_uris: HashSet<Url>,
464 origin_https_required: bool,
465 strict_redirect_uri: bool,
466
467 claim_map: BTreeMap<Uuid, Vec<(String, ClaimValue)>>,
468 scope_maps: BTreeMap<Uuid, BTreeSet<String>>,
469 sup_scope_maps: BTreeMap<Uuid, BTreeSet<String>>,
470 client_scopes: BTreeSet<String>,
471 client_sup_scopes: BTreeSet<String>,
472 sign_alg: SignatureAlgo,
474 key_object: Arc<KeyObject>,
475
476 iss: Url,
478 authorization_endpoint: Url,
480 token_endpoint: Url,
481 revocation_endpoint: Url,
482 introspection_endpoint: Url,
483 userinfo_endpoint: Url,
484 jwks_uri: Url,
485 scopes_supported: BTreeSet<String>,
486 prefer_short_username: bool,
487 type_: OauthRSType,
488 has_custom_image: bool,
490
491 device_authorization_endpoint: Option<Url>,
492}
493
494impl Oauth2RS {
495 pub fn is_basic(&self) -> bool {
496 match self.type_ {
497 OauthRSType::Basic { .. } => true,
498 OauthRSType::Public { .. } => false,
499 }
500 }
501
502 pub fn is_pkce(&self) -> bool {
503 match self.type_ {
504 OauthRSType::Basic { .. } => false,
505 OauthRSType::Public { .. } => true,
506 }
507 }
508
509 pub fn require_pkce(&self) -> bool {
511 match &self.type_ {
512 OauthRSType::Basic { enable_pkce, .. } => *enable_pkce,
513 OauthRSType::Public { .. } => true,
514 }
515 }
516
517 pub fn device_flow_enabled(&self) -> bool {
519 self.device_authorization_endpoint.is_some()
520 }
521
522 pub fn enable_consent_prompt(&self) -> bool {
525 match &self.type_ {
526 OauthRSType::Basic {
527 enable_consent_prompt,
528 ..
529 } => *enable_consent_prompt,
530 OauthRSType::Public { .. } => true,
531 }
532 }
533}
534
535impl std::fmt::Debug for Oauth2RS {
536 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
537 f.debug_struct("Oauth2RS")
538 .field("name", &self.name)
539 .field("displayname", &self.displayname)
540 .field("uuid", &self.uuid)
541 .field("type", &self.type_)
542 .field("origins", &self.origins)
543 .field("opaque_origins", &self.opaque_origins)
544 .field("scope_maps", &self.scope_maps)
545 .field("sup_scope_maps", &self.sup_scope_maps)
546 .field("claim_map", &self.claim_map)
547 .field("has_custom_image", &self.has_custom_image)
548 .finish()
549 }
550}
551
552#[derive(Clone)]
553struct Oauth2RSInner {
554 origin: Url,
555 consent_key: JweA128KWEncipher,
556 private_rs_set: HashMap<String, Oauth2RS>,
557}
558
559impl Oauth2RSInner {
560 fn rs_set_get(&self, client_id: &str) -> Option<&Oauth2RS> {
561 self.private_rs_set.get(client_id.to_lowercase().as_str())
562 }
563}
564
565pub struct Oauth2ResourceServers {
566 inner: CowCell<Oauth2RSInner>,
567}
568
569pub struct Oauth2ResourceServersReadTransaction {
570 inner: CowCellReadTxn<Oauth2RSInner>,
571}
572
573pub struct Oauth2ResourceServersWriteTransaction<'a> {
574 inner: CowCellWriteTxn<'a, Oauth2RSInner>,
575}
576
577impl Oauth2ResourceServers {
578 pub fn new(origin: Url) -> Result<Self, OperationError> {
579 let consent_key = JweA128KWEncipher::generate_ephemeral()
580 .map_err(|_| OperationError::CryptographyError)?;
581
582 Ok(Oauth2ResourceServers {
583 inner: CowCell::new(Oauth2RSInner {
584 origin,
585 consent_key,
586 private_rs_set: HashMap::new(),
587 }),
588 })
589 }
590
591 pub fn read(&self) -> Oauth2ResourceServersReadTransaction {
592 Oauth2ResourceServersReadTransaction {
593 inner: self.inner.read(),
594 }
595 }
596
597 pub fn write(&self) -> Oauth2ResourceServersWriteTransaction<'_> {
598 Oauth2ResourceServersWriteTransaction {
599 inner: self.inner.write(),
600 }
601 }
602}
603
604fn get_client_auth(
606 client_auth_info: &ClientAuthInfo,
607 client_post_auth: &ClientPostAuth,
608) -> Result<ClientAuth, Oauth2Error> {
609 if let Some(client_authz) = client_auth_info.basic_authz.as_ref() {
610 parse_basic_authz(client_authz.as_str())
611 } else if let Some(client_id) = &client_post_auth.client_id {
612 Ok(ClientAuth {
613 client_id: client_id.clone(),
614 client_secret: client_post_auth.client_secret.clone(),
615 })
616 } else {
617 admin_warn!("OAuth2 client authentication not provided");
618 Err(Oauth2Error::AuthenticationRequired)
619 }
620}
621
622impl Oauth2ResourceServersWriteTransaction<'_> {
623 #[instrument(level = "debug", name = "oauth2::reload", skip_all)]
624 pub fn reload(
625 &mut self,
626 value: Vec<Arc<EntrySealedCommitted>>,
627 key_providers: &KeyProvidersWriteTransaction,
628 domain_level: DomainVersion,
629 ) -> Result<(), OperationError> {
630 let rs_set: Result<HashMap<_, _>, _> = value
631 .into_iter()
632 .map(|ent| {
633 let uuid = ent.get_uuid();
634 trace!(?uuid, "Checking OAuth2 configuration");
635 if !ent
637 .attribute_equality(Attribute::Class, &EntryClass::OAuth2ResourceServer.into())
638 {
639 error!("Missing class oauth2_resource_server");
640 return Err(OperationError::InvalidEntryState);
642 }
643
644 let Some(key_object) = key_providers.get_key_object_handle(uuid) else {
645 error!("OAuth2 RS is missing its key object!");
646 return Err(OperationError::InvalidEntryState);
647 };
648
649 let type_ = if ent.attribute_equality(
650 Attribute::Class,
651 &EntryClass::OAuth2ResourceServerBasic.into(),
652 ) {
653 let authz_secret = ent
654 .get_ava_single_secret(Attribute::OAuth2RsBasicSecret)
655 .map(str::to_string)
656 .ok_or(OperationError::InvalidValueState)?;
657
658 let enable_pkce = ent
659 .get_ava_single_bool(Attribute::OAuth2AllowInsecureClientDisablePkce)
660 .map(|e| !e)
661 .unwrap_or(true);
662
663 let enable_consent_prompt = ent
664 .get_ava_single_bool(Attribute::OAuth2ConsentPromptEnable)
665 .unwrap_or(true);
666
667 OauthRSType::Basic {
668 authz_secret,
669 enable_pkce,
670 enable_consent_prompt,
671 }
672 } else if ent.attribute_equality(
673 Attribute::Class,
674 &EntryClass::OAuth2ResourceServerPublic.into(),
675 ) {
676 let allow_localhost_redirect = ent
677 .get_ava_single_bool(Attribute::OAuth2AllowLocalhostRedirect)
678 .unwrap_or(false);
679
680 OauthRSType::Public {
681 allow_localhost_redirect,
682 }
683 } else {
684 error!("Missing class determining OAuth2 rs type");
685 return Err(OperationError::InvalidEntryState);
686 };
687
688 let name = ent
690 .get_ava_single_iname(Attribute::Name)
691 .map(str::to_string)
692 .ok_or(OperationError::InvalidValueState)?;
693
694 let displayname = ent
695 .get_ava_single_utf8(Attribute::DisplayName)
696 .map(str::to_string)
697 .ok_or(OperationError::InvalidValueState)?;
698
699 let landing_url = ent
702 .get_ava_single_url(Attribute::OAuth2RsOriginLanding)
703 .cloned()
704 .ok_or(OperationError::InvalidValueState)?;
705
706 let maybe_extra_urls = ent
707 .get_ava_set(Attribute::OAuth2RsOrigin)
708 .and_then(|s| s.as_url_set());
709
710 let len_uris = maybe_extra_urls.map(|s| s.len() + 1).unwrap_or(1);
711
712 let strict_redirect_uri = cfg!(test)
714 || domain_level >= DOMAIN_LEVEL_8
715 || ent
716 .get_ava_single_bool(Attribute::OAuth2StrictRedirectUri)
717 .unwrap_or(false);
718
719 let mut redirect_uris_v = Vec::with_capacity(len_uris);
722
723 redirect_uris_v.push(landing_url);
724 if let Some(extra_origins) = maybe_extra_urls {
725 for x_origin in extra_origins {
726 redirect_uris_v.push(x_origin.clone());
727 }
728 }
729
730 let mut origins = HashSet::with_capacity(len_uris);
734 let mut redirect_uris = HashSet::with_capacity(len_uris);
735 let mut opaque_origins = HashSet::with_capacity(len_uris);
736 let mut origin_https_required = false;
737
738 for mut uri in redirect_uris_v.into_iter() {
739 uri.set_fragment(None);
742 if uri.scheme() == "https" {
744 origin_https_required = true;
745 origins.insert(uri.origin());
746 redirect_uris.insert(uri);
747 } else if uri.scheme() == "http" {
748 origins.insert(uri.origin());
749 redirect_uris.insert(uri);
750 } else {
751 opaque_origins.insert(uri);
752 }
753 }
754
755 let scope_maps = ent
756 .get_ava_as_oauthscopemaps(Attribute::OAuth2RsScopeMap)
757 .cloned()
758 .unwrap_or_default();
759
760 let sup_scope_maps = ent
761 .get_ava_as_oauthscopemaps(Attribute::OAuth2RsSupScopeMap)
762 .cloned()
763 .unwrap_or_default();
764
765 let (client_scopes, client_sup_scopes) =
768 if let Some(client_member_of) = ent.get_ava_refer(Attribute::MemberOf) {
769 let client_scopes = scope_maps
770 .iter()
771 .filter_map(|(u, m)| {
772 if client_member_of.contains(u) {
773 Some(m.iter())
774 } else {
775 None
776 }
777 })
778 .flatten()
779 .cloned()
780 .collect::<BTreeSet<_>>();
781
782 let client_sup_scopes = sup_scope_maps
783 .iter()
784 .filter_map(|(u, m)| {
785 if client_member_of.contains(u) {
786 Some(m.iter())
787 } else {
788 None
789 }
790 })
791 .flatten()
792 .cloned()
793 .collect::<BTreeSet<_>>();
794
795 (client_scopes, client_sup_scopes)
796 } else {
797 (BTreeSet::default(), BTreeSet::default())
798 };
799
800 let e_claim_maps = ent
801 .get_ava_set(Attribute::OAuth2RsClaimMap)
802 .and_then(|vs| vs.as_oauthclaim_map());
803
804 let claim_map = if let Some(e_claim_maps) = e_claim_maps {
809 let mut claim_map = BTreeMap::default();
810
811 for (claim_name, claim_mapping) in e_claim_maps.iter() {
812 for (group_uuid, claim_values) in claim_mapping.values().iter() {
813 match claim_map.entry(*group_uuid) {
816 BTreeEntry::Vacant(e) => {
817 e.insert(vec![(
818 claim_name.clone(),
819 ClaimValue {
820 join: claim_mapping.join(),
821 values: claim_values.clone(),
822 },
823 )]);
824 }
825 BTreeEntry::Occupied(mut e) => {
826 e.get_mut().push((
827 claim_name.clone(),
828 ClaimValue {
829 join: claim_mapping.join(),
830 values: claim_values.clone(),
831 },
832 ));
833 }
834 }
835 }
836 }
837
838 claim_map
839 } else {
840 BTreeMap::default()
841 };
842
843 let sign_alg = if ent
844 .get_ava_single_bool(Attribute::OAuth2JwtLegacyCryptoEnable)
845 .unwrap_or(false)
846 {
847 SignatureAlgo::Rs256
848 } else {
849 SignatureAlgo::Es256
850 };
851
852 let prefer_short_username = ent
853 .get_ava_single_bool(Attribute::OAuth2PreferShortUsername)
854 .unwrap_or(false);
855
856 let has_custom_image = ent.get_ava_single_image(Attribute::Image).is_some();
857
858 let mut authorization_endpoint = self.inner.origin.clone();
859 authorization_endpoint.set_path("/ui/oauth2");
860
861 let mut token_endpoint = self.inner.origin.clone();
862 token_endpoint.set_path(uri::OAUTH2_TOKEN_ENDPOINT);
863
864 let mut revocation_endpoint = self.inner.origin.clone();
865 revocation_endpoint.set_path(OAUTH2_TOKEN_REVOKE_ENDPOINT);
866
867 let mut introspection_endpoint = self.inner.origin.clone();
868 introspection_endpoint.set_path(OAUTH2_TOKEN_INTROSPECT_ENDPOINT);
869
870 let mut userinfo_endpoint = self.inner.origin.clone();
871 userinfo_endpoint.set_path(&format!("/oauth2/openid/{name}/userinfo"));
872
873 let mut jwks_uri = self.inner.origin.clone();
874 jwks_uri.set_path(&format!("/oauth2/openid/{name}/public_key.jwk"));
875
876 let mut iss = self.inner.origin.clone();
877 iss.set_path(&format!("/oauth2/openid/{name}"));
878
879 let scopes_supported: BTreeSet<String> = scope_maps
880 .values()
881 .flat_map(|bts| bts.iter())
882 .chain(sup_scope_maps.values().flat_map(|bts| bts.iter()))
883 .cloned()
884 .collect();
885
886 let device_authorization_endpoint: Option<Url> =
887 match cfg!(feature = "dev-oauth2-device-flow") {
888 true => {
889 match ent
890 .get_ava_single_bool(Attribute::OAuth2DeviceFlowEnable)
891 .unwrap_or(false)
892 {
893 true => {
894 let mut device_authorization_endpoint =
895 self.inner.origin.clone();
896 device_authorization_endpoint
897 .set_path(uri::OAUTH2_AUTHORISE_DEVICE);
898 Some(device_authorization_endpoint)
899 }
900 false => None,
901 }
902 }
903 false => None,
904 };
905
906 let client_id = name.clone();
907 let rscfg = Oauth2RS {
908 name,
909 displayname,
910 uuid,
911 origins,
912 opaque_origins,
913 redirect_uris,
914 origin_https_required,
915 strict_redirect_uri,
916 scope_maps,
917 sup_scope_maps,
918 client_scopes,
919 client_sup_scopes,
920 claim_map,
921 sign_alg,
922 key_object,
923 iss,
924 authorization_endpoint,
925 token_endpoint,
926 revocation_endpoint,
927 introspection_endpoint,
928 userinfo_endpoint,
929 jwks_uri,
930 scopes_supported,
931 prefer_short_username,
932 type_,
933 has_custom_image,
934 device_authorization_endpoint,
935 };
936
937 Ok((client_id, rscfg))
938 })
939 .collect();
940
941 rs_set.map(|mut rs_set| {
942 let inner_ref = self.inner.get_mut();
944 std::mem::swap(&mut inner_ref.private_rs_set, &mut rs_set);
946 })
947 }
948
949 pub fn commit(self) {
950 self.inner.commit();
951 }
952}
953
954impl IdmServerProxyWriteTransaction<'_> {
955 #[instrument(level = "debug", skip_all)]
956 pub fn oauth2_token_revoke(
957 &mut self,
958 client_auth_info: &ClientAuthInfo,
959 revoke_req: &TokenRevokeRequest,
960 ct: Duration,
961 ) -> Result<(), Oauth2Error> {
962 let client_auth = get_client_auth(client_auth_info, &revoke_req.client_post_auth)?;
963
964 let o2rs = self
966 .oauth2rs
967 .inner
968 .rs_set_get(client_auth.client_id.as_str())
969 .ok_or_else(|| {
970 warn!("Invalid OAuth2 client_id");
971 Oauth2Error::AuthenticationRequired
972 })?;
973
974 match &o2rs.type_ {
976 OauthRSType::Basic { authz_secret, .. } => {
977 if Some(authz_secret) != client_auth.client_secret.as_ref() {
978 info!("Invalid OAuth2 client_id secret, this can happen if your RS is public but you configured a 'basic' type.");
979 return Err(Oauth2Error::AuthenticationRequired);
980 }
981 }
982 OauthRSType::Public { .. } => {}
984 };
985
986 let (session_id, expiry, uuid) = if let Ok(jwsc) = JwsCompact::from_str(&revoke_req.token) {
992 let access_token = o2rs
993 .key_object
994 .jws_verify(&jwsc)
995 .map_err(|err| {
996 admin_error!(?err, "Unable to verify access token");
997 Oauth2Error::InvalidRequest
998 })
999 .and_then(|jws| {
1000 jws.from_json().map_err(|err| {
1001 admin_error!(?err, "Unable to deserialise access token");
1002 Oauth2Error::InvalidRequest
1003 })
1004 })?;
1005
1006 let OAuth2RFC9068Token::<_> {
1007 sub: uuid,
1008 exp,
1009 extensions: OAuth2RFC9068TokenExtensions { session_id, .. },
1010 ..
1011 } = access_token;
1012
1013 (session_id, exp, uuid)
1014 } else {
1015 let jwe_compact = JweCompact::from_str(&revoke_req.token).map_err(|_| {
1017 error!("Failed to deserialise a valid JWE");
1018 Oauth2Error::InvalidRequest
1019 })?;
1020
1021 let token: Oauth2TokenType = o2rs
1022 .key_object
1023 .jwe_decrypt(&jwe_compact)
1024 .map_err(|_| {
1025 error!("Failed to decrypt token revoke request");
1026 Oauth2Error::InvalidRequest
1027 })
1028 .and_then(|jwe| {
1029 jwe.from_json().map_err(|err| {
1030 error!(?err, "Failed to deserialise token");
1031 Oauth2Error::InvalidRequest
1032 })
1033 })?;
1034
1035 match token {
1036 Oauth2TokenType::ClientAccess {
1037 session_id,
1038 exp,
1039 uuid,
1040 ..
1041 }
1042 | Oauth2TokenType::Refresh {
1043 session_id,
1044 exp,
1045 uuid,
1046 ..
1047 } => (session_id, exp, uuid),
1048 }
1049 };
1050
1051 if expiry <= ct.as_secs() as i64 {
1053 security_info!(?uuid, "token has expired, returning inactive");
1054 return Ok(());
1055 }
1056
1057 let modlist: ModifyList<ModifyInvalid> = ModifyList::new_list(vec![Modify::Removed(
1066 Attribute::OAuth2Session,
1067 PartialValue::Refer(session_id),
1068 )]);
1069
1070 self.qs_write
1071 .internal_modify(
1072 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid))),
1073 &modlist,
1074 )
1075 .map_err(|e| {
1076 admin_error!("Failed to modify - revoke OAuth2 session {:?}", e);
1077 Oauth2Error::ServerError(e)
1078 })
1079 }
1080
1081 #[instrument(level = "debug", skip_all)]
1082 pub fn check_oauth2_token_exchange(
1083 &mut self,
1084 client_auth_info: &ClientAuthInfo,
1085 token_req: &AccessTokenRequest,
1086 ct: Duration,
1087 ) -> Result<AccessTokenResponse, Oauth2Error> {
1088 let client_auth = get_client_auth(client_auth_info, &token_req.client_post_auth)?;
1090
1091 let o2rs = self.get_client(&client_auth.client_id)?;
1092 let is_token_exchange = matches!(token_req.grant_type, GrantTypeReq::TokenExchange { .. });
1093
1094 let client_authentication_valid = match (&o2rs.type_, is_token_exchange) {
1096 (OauthRSType::Basic { .. }, true) => {
1097 if client_auth.client_secret.is_some() {
1098 security_info!(
1099 "Client secret is not accepted when exchanging a service account token"
1100 );
1101 return Err(Oauth2Error::InvalidRequest);
1102 }
1103 true
1104 }
1105 (OauthRSType::Basic { authz_secret, .. }, false) => {
1106 match client_auth.client_secret {
1107 Some(secret) => {
1108 if authz_secret == &secret {
1109 true
1110 } else {
1111 info!("Invalid OAuth2 client_id secret");
1112 return Err(Oauth2Error::AuthenticationRequired);
1113 }
1114 }
1115 None => {
1116 info!(
1118 "Invalid OAuth2 authentication - no secret in access token request - this can happen if you're expecting a public client and configured a basic one."
1119 );
1120 return Err(Oauth2Error::AuthenticationRequired);
1121 }
1122 }
1123 }
1124 (OauthRSType::Public { .. }, _) => false,
1126 };
1127
1128 match &token_req.grant_type {
1130 GrantTypeReq::AuthorizationCode {
1131 code,
1132 redirect_uri,
1133 code_verifier,
1134 } => self.check_oauth2_token_exchange_authorization_code(
1135 &o2rs,
1136 code,
1137 redirect_uri,
1138 code_verifier.as_deref(),
1139 ct,
1140 ),
1141 GrantTypeReq::ClientCredentials { scope } => {
1142 if client_authentication_valid {
1143 self.check_oauth2_token_client_credentials(&o2rs, scope.as_ref(), ct)
1144 } else {
1145 security_info!(
1146 "Unable to proceed with client credentials grant unless client authentication is provided and valid"
1147 );
1148 Err(Oauth2Error::AuthenticationRequired)
1149 }
1150 }
1151 GrantTypeReq::RefreshToken {
1152 refresh_token,
1153 scope,
1154 } => self.check_oauth2_token_refresh(&o2rs, refresh_token, scope.as_ref(), ct),
1155 GrantTypeReq::TokenExchange {
1156 subject_token,
1157 subject_token_type,
1158 requested_token_type,
1159 audience,
1160 resource,
1161 actor_token,
1162 actor_token_type,
1163 scope,
1164 } => {
1165 if actor_token.is_some() || actor_token_type.is_some() {
1166 warn!("actor_token is not supported for token exchange");
1167 return Err(Oauth2Error::InvalidRequest);
1168 }
1169
1170 self.check_oauth2_token_exchange_service_account(
1171 &o2rs,
1172 subject_token,
1173 subject_token_type,
1174 requested_token_type.as_deref(),
1175 audience.as_deref(),
1176 resource.as_deref(),
1177 scope.as_ref(),
1178 ct,
1179 )
1180 }
1181 GrantTypeReq::DeviceCode { device_code, scope } => {
1182 self.check_oauth2_device_code_status(device_code, scope)
1183 }
1184 }
1185 }
1186
1187 fn get_client(&self, client_id: &str) -> Result<Oauth2RS, Oauth2Error> {
1188 let s = self
1189 .oauth2rs
1190 .inner
1191 .rs_set_get(client_id)
1192 .ok_or_else(|| {
1193 warn!("Invalid OAuth2 client_id {}", client_id);
1194 Oauth2Error::AuthenticationRequired
1195 })?
1196 .clone();
1197 Ok(s)
1198 }
1199
1200 #[instrument(level = "info", skip(self))]
1201 pub fn handle_oauth2_start_device_flow(
1202 &mut self,
1203 _client_auth_info: ClientAuthInfo,
1204 _client_id: &str,
1205 _scope: &Option<BTreeSet<String>>,
1206 _eventid: Uuid,
1207 ) -> Result<DeviceAuthorizationResponse, Oauth2Error> {
1208 Err(Oauth2Error::InvalidGrant)
1238
1239 }
1246
1247 #[instrument(level = "info", skip(self))]
1248 fn check_oauth2_device_code_status(
1249 &mut self,
1250 device_code: &str,
1251 scope: &Option<BTreeSet<String>>,
1252 ) -> Result<AccessTokenResponse, Oauth2Error> {
1253 error!(
1256 "haven't done the device grant yet! Got device_code={} scope={:?}",
1257 device_code, scope
1258 );
1259 Err(Oauth2Error::AuthorizationPending)
1260
1261 }
1264
1265 #[instrument(level = "debug", skip_all)]
1266 pub fn check_oauth2_authorise_permit(
1267 &mut self,
1268 ident: &Identity,
1269 consent_token: &str,
1270 ct: Duration,
1271 ) -> Result<AuthorisePermitSuccess, OperationError> {
1272 let Some(account_uuid) = ident.get_uuid() else {
1273 error!("consent request ident does not have a valid uuid, unable to proceed");
1274 return Err(OperationError::InvalidSessionState);
1275 };
1276
1277 let consent_token_jwe = JweCompact::from_str(consent_token).map_err(|err| {
1278 error!(?err, "Consent token is not a valid jwe compact");
1279 OperationError::InvalidSessionState
1280 })?;
1281
1282 let consent_req: ConsentToken = self
1283 .oauth2rs
1284 .inner
1285 .consent_key
1286 .decipher(&consent_token_jwe)
1287 .map_err(|err| {
1288 error!(?err, "Failed to decrypt consent request");
1289 OperationError::CryptographyError
1290 })
1291 .and_then(|jwe| {
1292 jwe.from_json().map_err(|err| {
1293 error!(?err, "Failed to deserialise consent request");
1294 OperationError::SerdeJsonError
1295 })
1296 })?;
1297
1298 if consent_req.ident_id != ident.get_event_origin_id() {
1300 security_info!("consent request ident id does not match the identity of our UAT.");
1301 return Err(OperationError::InvalidSessionState);
1302 }
1303
1304 if consent_req.session_id != ident.get_session_id() {
1306 security_info!("consent request session id does not match the session id of our UAT.");
1307 return Err(OperationError::InvalidSessionState);
1308 }
1309
1310 if consent_req.expiry <= ct.as_secs() {
1311 error!("Failed to decrypt consent request");
1313 return Err(OperationError::CryptographyError);
1314 }
1315
1316 let expiry = ct.as_secs() + 60;
1318
1319 let o2rs = self
1321 .oauth2rs
1322 .inner
1323 .rs_set_get(&consent_req.client_id)
1324 .ok_or_else(|| {
1325 admin_error!("Invalid consent request OAuth2 client_id");
1326 OperationError::InvalidRequestState
1327 })?;
1328
1329 let xchg_code = TokenExchangeCode {
1331 account_uuid,
1332 session_id: ident.get_session_id(),
1333 expiry,
1334 code_challenge: consent_req.code_challenge,
1335 redirect_uri: consent_req.redirect_uri.clone(),
1336 scopes: consent_req.scopes.clone(),
1337 nonce: consent_req.nonce,
1338 };
1339
1340 let code_data_jwe = Jwe::into_json(&xchg_code).map_err(|err| {
1342 error!(?err, "Unable to encode xchg_code data");
1343 OperationError::SerdeJsonError
1344 })?;
1345
1346 let code = o2rs
1347 .key_object
1348 .jwe_a128gcm_encrypt(&code_data_jwe, ct)
1349 .map(|code| code.to_string())
1350 .map_err(|err| {
1351 error!(?err, "Unable to encrypt xchg_code");
1352 OperationError::CryptographyError
1353 })?;
1354
1355 let modlist = ModifyList::new_list(vec![
1360 Modify::Removed(
1361 Attribute::OAuth2ConsentScopeMap,
1362 PartialValue::Refer(o2rs.uuid),
1363 ),
1364 Modify::Present(
1365 Attribute::OAuth2ConsentScopeMap,
1366 Value::OauthScopeMap(o2rs.uuid, consent_req.scopes.iter().cloned().collect()),
1367 ),
1368 ]);
1369
1370 self.qs_write.internal_modify(
1371 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(account_uuid))),
1372 &modlist,
1373 )?;
1374
1375 Ok(AuthorisePermitSuccess {
1376 redirect_uri: consent_req.redirect_uri,
1377 state: consent_req.state,
1378 code,
1379 response_mode: consent_req.response_mode,
1380 })
1381 }
1382
1383 #[instrument(level = "debug", skip_all)]
1384 fn check_oauth2_token_exchange_authorization_code(
1385 &mut self,
1386 o2rs: &Oauth2RS,
1387 token_req_code: &str,
1388 token_req_redirect_uri: &Url,
1389 token_req_code_verifier: Option<&str>,
1390 ct: Duration,
1391 ) -> Result<AccessTokenResponse, Oauth2Error> {
1392 let jwe_compact = JweCompact::from_str(token_req_code).map_err(|_| {
1395 error!("Failed to deserialise a valid JWE");
1396 Oauth2Error::InvalidRequest
1397 })?;
1398
1399 let code_xchg: TokenExchangeCode = o2rs
1400 .key_object
1401 .jwe_decrypt(&jwe_compact)
1402 .map_err(|_| {
1403 admin_error!("Failed to decrypt token exchange request");
1404 Oauth2Error::InvalidRequest
1405 })
1406 .and_then(|jwe| {
1407 debug!(?jwe);
1408 jwe.from_json::<TokenExchangeCode>().map_err(|err| {
1409 error!(?err, "Failed to deserialise token exchange code");
1410 Oauth2Error::InvalidRequest
1411 })
1412 })?;
1413
1414 if code_xchg.expiry <= ct.as_secs() {
1415 error!("Expired token exchange request");
1416 return Err(Oauth2Error::InvalidRequest);
1417 }
1418
1419 if let Some(code_challenge) = code_xchg.code_challenge {
1423 let code_verifier = token_req_code_verifier
1425 .ok_or_else(|| {
1426 security_info!("PKCE code verification failed - code challenge is present, but no verifier was provided");
1427 Oauth2Error::InvalidRequest
1428 })?;
1429
1430 let verifier_secret = PkceS256Secret::from(code_verifier.to_string());
1431
1432 if !verifier_secret.verify(code_challenge) {
1433 security_info!(
1434 "PKCE code verification failed - this may indicate malicious activity"
1435 );
1436 return Err(Oauth2Error::InvalidRequest);
1437 }
1438 } else if o2rs.require_pkce() {
1439 security_info!(
1440 "PKCE code verification failed - no code challenge present in PKCE enforced mode"
1441 );
1442 return Err(Oauth2Error::InvalidRequest);
1443 } else if token_req_code_verifier.is_some() {
1444 security_info!(
1445 "PKCE code verification failed - a code verifier is present, but no code challenge in exchange"
1446 );
1447 return Err(Oauth2Error::InvalidRequest);
1448 }
1449
1450 if token_req_redirect_uri != &code_xchg.redirect_uri {
1452 security_info!("Invalid OAuth2 redirect_uri (differs from original request uri)");
1453 return Err(Oauth2Error::InvalidOrigin);
1454 }
1455
1456 let parent_session_id = code_xchg.session_id;
1475 let session_id = Uuid::new_v4();
1476
1477 let scopes = code_xchg.scopes;
1478 let account_uuid = code_xchg.account_uuid;
1479 let nonce = code_xchg.nonce;
1480
1481 self.generate_access_token_response(
1482 o2rs,
1483 ct,
1484 scopes,
1485 account_uuid,
1486 parent_session_id,
1487 session_id,
1488 nonce,
1489 )
1490 }
1491
1492 #[instrument(level = "debug", skip_all)]
1493 fn check_oauth2_token_refresh(
1494 &mut self,
1495 o2rs: &Oauth2RS,
1496 refresh_token: &str,
1497 req_scopes: Option<&BTreeSet<String>>,
1498 ct: Duration,
1499 ) -> Result<AccessTokenResponse, Oauth2Error> {
1500 let jwe_compact = JweCompact::from_str(refresh_token).map_err(|_| {
1501 error!("Failed to deserialise a valid JWE");
1502 Oauth2Error::InvalidRequest
1503 })?;
1504
1505 let token: Oauth2TokenType = o2rs
1507 .key_object
1508 .jwe_decrypt(&jwe_compact)
1509 .map_err(|_| {
1510 admin_error!("Failed to decrypt refresh token request");
1511 Oauth2Error::InvalidRequest
1512 })
1513 .and_then(|jwe| {
1514 jwe.from_json().map_err(|err| {
1515 error!(?err, "Failed to deserialise token");
1516 Oauth2Error::InvalidRequest
1517 })
1518 })?;
1519
1520 match token {
1521 Oauth2TokenType::ClientAccess { .. } => {
1523 admin_error!("attempt to refresh with an access token");
1524 Err(Oauth2Error::InvalidRequest)
1525 }
1526 Oauth2TokenType::Refresh {
1527 scopes,
1528 parent_session_id,
1529 session_id,
1530 exp,
1531 uuid,
1532 iat,
1533 nbf: _,
1534 nonce,
1535 } => {
1536 if exp <= ct.as_secs() as i64 {
1537 security_info!(?uuid, "refresh token has expired, ");
1538 return Err(Oauth2Error::InvalidGrant);
1539 }
1540
1541 let valid = self
1544 .check_oauth2_account_uuid_valid(
1545 uuid,
1546 session_id,
1547 Some(parent_session_id),
1548 iat,
1549 ct,
1550 )
1551 .map_err(|_| admin_error!("Account is not valid"));
1552
1553 let Ok(Some(entry)) = valid else {
1554 security_info!(
1555 ?uuid,
1556 "access token has no account not valid, returning inactive"
1557 );
1558 return Err(Oauth2Error::InvalidGrant);
1559 };
1560
1561 let oauth2_session = entry
1563 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
1564 .and_then(|map| map.get(&session_id))
1565 .ok_or_else(|| {
1566 security_info!(
1567 ?session_id,
1568 "No OAuth2 session found, unable to proceed with refresh"
1569 );
1570 Oauth2Error::InvalidGrant
1571 })?;
1572
1573 if iat < oauth2_session.issued_at.unix_timestamp() {
1578 security_info!(
1579 ?session_id,
1580 "Attempt to reuse a refresh token detected, destroying session"
1581 );
1582
1583 let modlist = ModifyList::new_list(vec![Modify::Removed(
1585 Attribute::OAuth2Session,
1586 PartialValue::Refer(session_id),
1587 )]);
1588
1589 self.qs_write
1590 .internal_modify(
1591 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid))),
1592 &modlist,
1593 )
1594 .map_err(|e| {
1595 admin_error!("Failed to modify - revoke OAuth2 session {:?}", e);
1596 Oauth2Error::ServerError(e)
1597 })?;
1598
1599 return Err(Oauth2Error::InvalidGrant);
1600 }
1601
1602 let update_scopes = if let Some(req_scopes) = req_scopes {
1604 if req_scopes.is_subset(&scopes) {
1605 debug!("oauth2 scopes requested, checked as valid.");
1606 req_scopes.clone()
1609 } else {
1610 warn!("oauth2 scopes requested, invalid.");
1611 return Err(Oauth2Error::InvalidScope);
1612 }
1613 } else {
1614 debug!("No OAuth2 scopes requested, this is valid.");
1615 scopes
1617 };
1618
1619 let account_uuid = uuid;
1623
1624 self.generate_access_token_response(
1625 o2rs,
1626 ct,
1627 update_scopes,
1628 account_uuid,
1629 parent_session_id,
1630 session_id,
1631 nonce,
1632 )
1633 }
1634 }
1635 }
1636
1637 #[allow(clippy::too_many_arguments)]
1638 #[instrument(level = "debug", skip_all)]
1639 fn check_oauth2_token_exchange_service_account(
1640 &mut self,
1641 o2rs: &Oauth2RS,
1642 subject_token: &str,
1643 subject_token_type: &str,
1644 requested_token_type: Option<&str>,
1645 audience: Option<&str>,
1646 resource: Option<&str>,
1647 req_scopes: Option<&BTreeSet<String>>,
1648 ct: Duration,
1649 ) -> Result<AccessTokenResponse, Oauth2Error> {
1650 if let Some(rtt) = requested_token_type {
1651 if rtt != OAUTH2_TOKEN_TYPE_ACCESS_TOKEN {
1652 warn!(
1653 requested_token_type = rtt,
1654 "Unsupported requested_token_type in token exchange"
1655 );
1656 return Err(Oauth2Error::InvalidRequest);
1657 }
1658 }
1659
1660 if let Some(aud) = audience {
1661 if aud != o2rs.name {
1662 warn!(expected = %o2rs.name, requested = aud, "Token exchange audience mismatch");
1663 return Err(Oauth2Error::InvalidTarget);
1664 }
1665 }
1666
1667 if let Some(res) = resource {
1668 let parsed_resource = Url::parse(res).map_err(|_| {
1669 warn!(
1670 requested = res,
1671 "Invalid resource parameter in token exchange"
1672 );
1673 Oauth2Error::InvalidRequest
1674 })?;
1675
1676 if parsed_resource.fragment().is_some() {
1677 warn!(
1678 requested = res,
1679 "Resource parameter must not contain a fragment"
1680 );
1681 return Err(Oauth2Error::InvalidRequest);
1682 }
1683
1684 let origin = parsed_resource.origin();
1685 let target_allowed =
1686 o2rs.origins.contains(&origin) || o2rs.opaque_origins.contains(&parsed_resource);
1687 if !target_allowed {
1688 admin_warn!(requested = res, "Token exchange resource target mismatch");
1689 return Err(Oauth2Error::InvalidTarget);
1690 }
1691 }
1692
1693 if subject_token_type != TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS {
1694 security_info!(
1695 ?subject_token_type,
1696 "Unsupported subject_token_type in token exchange"
1697 );
1698 return Err(Oauth2Error::InvalidRequest);
1699 }
1700
1701 let jwsc = JwsCompact::from_str(subject_token).map_err(|_| {
1702 error!("Failed to deserialise subject token for token exchange");
1703 Oauth2Error::InvalidRequest
1704 })?;
1705
1706 let token = self
1707 .validate_and_parse_token_to_identity_token(&jwsc, ct)
1708 .map_err(|err| {
1709 security_info!(?err, "Unable to validate subject token for token exchange");
1710 Oauth2Error::InvalidRequest
1711 })?;
1712
1713 let (apit, entry) = match token {
1714 Token::ApiToken(apit, entry) => (apit, entry),
1715 Token::UserAuthToken(_) => {
1716 security_info!("Token exchange subject_token must be a service account api token");
1717 return Err(Oauth2Error::InvalidRequest);
1718 }
1719 };
1720
1721 let ident = self
1722 .process_apit_to_identity(&apit, Source::Internal, entry, ct)
1723 .map_err(|err| match err {
1724 OperationError::SessionExpired | OperationError::NotAuthenticated => {
1725 security_info!(
1726 ?err,
1727 "Service account api token rejected during token exchange"
1728 );
1729 Oauth2Error::InvalidRequest
1730 }
1731 err => Oauth2Error::ServerError(err),
1732 })?;
1733
1734 let (_req_scopes, granted_scopes) =
1735 process_requested_scopes_for_identity(o2rs, &ident, req_scopes)?;
1736
1737 let session_id = Uuid::new_v4();
1738 let parent_session_id = apit.token_id;
1739 let account_uuid = apit.account_id;
1740
1741 self.generate_access_token_response(
1742 o2rs,
1743 ct,
1744 granted_scopes,
1745 account_uuid,
1746 parent_session_id,
1747 session_id,
1748 None,
1749 )
1750 }
1751
1752 fn check_oauth2_token_client_credentials(
1753 &mut self,
1754 o2rs: &Oauth2RS,
1755 req_scopes: Option<&BTreeSet<String>>,
1756 ct: Duration,
1757 ) -> Result<AccessTokenResponse, Oauth2Error> {
1758 let req_scopes = req_scopes.cloned().unwrap_or_default();
1759
1760 validate_scopes(&req_scopes)?;
1762
1763 let avail_scopes: Vec<String> = req_scopes
1765 .intersection(&o2rs.client_scopes)
1766 .map(|s| s.to_string())
1767 .collect();
1768
1769 if avail_scopes.len() != req_scopes.len() {
1770 admin_warn!(
1771 ident = %o2rs.name,
1772 requested_scopes = ?req_scopes,
1773 available_scopes = ?o2rs.client_scopes,
1774 "Client does not have access to the requested scopes"
1775 );
1776 return Err(Oauth2Error::AccessDenied);
1777 }
1778
1779 let granted_scopes = avail_scopes
1782 .into_iter()
1783 .chain(o2rs.client_sup_scopes.iter().cloned())
1784 .collect::<BTreeSet<_>>();
1785
1786 let odt_ct = OffsetDateTime::UNIX_EPOCH + ct;
1787 let iat = ct.as_secs() as i64;
1788 let exp = iat + OAUTH2_ACCESS_TOKEN_EXPIRY as i64;
1789 let odt_exp = odt_ct + Duration::from_secs(OAUTH2_ACCESS_TOKEN_EXPIRY as u64);
1790 let expires_in = OAUTH2_ACCESS_TOKEN_EXPIRY;
1791
1792 let session_id = Uuid::new_v4();
1793
1794 let scope = granted_scopes.clone();
1795
1796 let uuid = o2rs.uuid;
1797
1798 let access_token_raw = Oauth2TokenType::ClientAccess {
1799 scopes: granted_scopes,
1800 session_id,
1801 uuid,
1802 exp,
1803 iat,
1804 nbf: iat,
1805 };
1806
1807 let access_token_data = Jwe::into_json(&access_token_raw).map_err(|err| {
1808 error!(?err, "Unable to encode token data");
1809 Oauth2Error::ServerError(OperationError::SerdeJsonError)
1810 })?;
1811
1812 let access_token = o2rs
1813 .key_object
1814 .jwe_a128gcm_encrypt(&access_token_data, ct)
1815 .map(|jwe| jwe.to_string())
1816 .map_err(|err| {
1817 error!(?err, "Unable to encode token data");
1818 Oauth2Error::ServerError(OperationError::CryptographyError)
1819 })?;
1820
1821 let session = Value::Oauth2Session(
1823 session_id,
1824 Oauth2Session {
1825 parent: None,
1826 state: SessionState::ExpiresAt(odt_exp),
1827 issued_at: odt_ct,
1828 rs_uuid: o2rs.uuid,
1829 },
1830 );
1831
1832 let modlist =
1834 ModifyList::new_list(vec![Modify::Present(Attribute::OAuth2Session, session)]);
1835
1836 self.qs_write
1837 .internal_modify(
1838 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(uuid))),
1839 &modlist,
1840 )
1841 .map_err(|e| {
1842 admin_error!("Failed to persist OAuth2 session record {:?}", e);
1843 Oauth2Error::ServerError(e)
1844 })?;
1845
1846 Ok(AccessTokenResponse {
1847 access_token,
1848 token_type: AccessTokenType::Bearer,
1849 issued_token_type: Some(IssuedTokenType::AccessToken),
1850 expires_in,
1851 refresh_token: None,
1852 scope,
1853 id_token: None,
1854 })
1855 }
1856
1857 fn generate_access_token_response(
1858 &mut self,
1859 o2rs: &Oauth2RS,
1860 ct: Duration,
1861 scopes: BTreeSet<String>,
1863 account_uuid: Uuid,
1864 parent_session_id: Uuid,
1865 session_id: Uuid,
1866 nonce: Option<String>,
1867 ) -> Result<AccessTokenResponse, Oauth2Error> {
1868 let odt_ct = OffsetDateTime::UNIX_EPOCH + ct;
1869 let iat = ct.as_secs() as i64;
1870
1871 let expiry = odt_ct + Duration::from_secs(OAUTH2_ACCESS_TOKEN_EXPIRY as u64);
1881 let expires_in = OAUTH2_ACCESS_TOKEN_EXPIRY;
1882 let refresh_expiry = iat + OAUTH_REFRESH_TOKEN_EXPIRY as i64;
1883 let odt_refresh_expiry = odt_ct + Duration::from_secs(OAUTH_REFRESH_TOKEN_EXPIRY);
1884
1885 let scope = scopes.clone();
1886
1887 let iss = o2rs.iss.clone();
1888
1889 let exp = expiry.unix_timestamp();
1891
1892 let aud = o2rs.name.clone();
1893
1894 let client_id = o2rs.name.clone();
1895
1896 let id_token = if scopes.contains(OAUTH2_SCOPE_OPENID) {
1897 let amr = None;
1915
1916 let entry = match self.qs_write.internal_search_uuid(account_uuid) {
1917 Ok(entry) => entry,
1918 Err(err) => return Err(Oauth2Error::ServerError(err)),
1919 };
1920
1921 let account = match Account::try_from_entry_rw(&entry, &mut self.qs_write) {
1922 Ok(account) => account,
1923 Err(err) => return Err(Oauth2Error::ServerError(err)),
1924 };
1925
1926 let s_claims = s_claims_for_account(o2rs, &account, &scopes);
1927 let extra_claims = extra_claims_for_account(&account, &o2rs.claim_map, &scopes);
1928
1929 let oidc = OidcToken {
1930 iss: iss.clone(),
1931 sub: OidcSubject::U(account_uuid),
1932 aud: aud.clone(),
1933 iat,
1934 nbf: Some(iat),
1935 exp,
1936 auth_time: None,
1937 nonce: nonce.clone(),
1938 at_hash: None,
1939 acr: None,
1940 amr,
1941 azp: Some(o2rs.name.clone()),
1942 jti: Some(session_id.to_string()),
1943 s_claims,
1944 claims: extra_claims,
1945 };
1946
1947 trace!(?oidc);
1948 let oidc = JwsBuilder::into_json(&oidc)
1949 .map(|builder| builder.build())
1950 .map_err(|err| {
1951 admin_error!(?err, "Unable to encode access token data");
1952 Oauth2Error::ServerError(OperationError::InvalidState)
1953 })?;
1954
1955 let jwt_signed = match o2rs.sign_alg {
1956 SignatureAlgo::Es256 => o2rs.key_object.jws_es256_sign(&oidc, ct),
1957 SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_sign(&oidc, ct),
1958 }
1959 .map_err(|err| {
1960 error!(?err, "Unable to encode oidc token data");
1961 Oauth2Error::ServerError(OperationError::InvalidState)
1962 })?;
1963
1964 Some(jwt_signed.to_string())
1965 } else {
1966 None
1968 };
1969
1970 let access_token_data = OAuth2RFC9068Token {
1972 iss: iss.to_string(),
1973 sub: account_uuid,
1974 aud,
1975 exp,
1976 nbf: iat,
1977 iat,
1978 jti: session_id,
1979 client_id,
1980 extensions: OAuth2RFC9068TokenExtensions {
1981 auth_time: None,
1982 acr: None,
1983 amr: None,
1984 scope: scopes.clone(),
1985 nonce: nonce.clone(),
1986 session_id,
1987 parent_session_id: Some(parent_session_id),
1988 },
1989 };
1990
1991 let access_token_data = JwsBuilder::into_json(&access_token_data)
1992 .map(|builder| builder.set_typ(Some("at+jwt")).build())
1993 .map_err(|err| {
1994 error!(?err, "Unable to encode access token data");
1995 Oauth2Error::ServerError(OperationError::InvalidState)
1996 })?;
1997
1998 let access_token = match o2rs.sign_alg {
1999 SignatureAlgo::Es256 => o2rs.key_object.jws_es256_sign(&access_token_data, ct),
2000 SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_sign(&access_token_data, ct),
2001 }
2002 .map_err(|e| {
2003 admin_error!(err = ?e, "Unable to sign access token data");
2004 Oauth2Error::ServerError(OperationError::InvalidState)
2005 })?;
2006
2007 let refresh_token_raw = Oauth2TokenType::Refresh {
2008 scopes,
2009 parent_session_id,
2010 session_id,
2011 exp: refresh_expiry,
2012 uuid: account_uuid,
2013 iat,
2014 nbf: iat,
2015 nonce,
2016 };
2017
2018 let refresh_token_data = Jwe::into_json(&refresh_token_raw).map_err(|err| {
2019 error!(?err, "Unable to encode token data");
2020 Oauth2Error::ServerError(OperationError::SerdeJsonError)
2021 })?;
2022
2023 let refresh_token = o2rs
2024 .key_object
2025 .jwe_a128gcm_encrypt(&refresh_token_data, ct)
2026 .map(|jwe| jwe.to_string())
2027 .map_err(|err| {
2028 error!(?err, "Unable to encrypt token data");
2029 Oauth2Error::ServerError(OperationError::CryptographyError)
2030 })?;
2031
2032 let session = Value::Oauth2Session(
2035 session_id,
2036 Oauth2Session {
2037 parent: Some(parent_session_id),
2038 state: SessionState::ExpiresAt(odt_refresh_expiry),
2039 issued_at: odt_ct,
2040 rs_uuid: o2rs.uuid,
2041 },
2042 );
2043
2044 let modlist = ModifyList::new_list(vec![
2046 Modify::Present(Attribute::OAuth2Session, session),
2050 ]);
2051
2052 self.qs_write
2053 .internal_modify(
2054 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(account_uuid))),
2055 &modlist,
2056 )
2057 .map_err(|e| {
2058 admin_error!("Failed to persist OAuth2 session record {:?}", e);
2059 Oauth2Error::ServerError(e)
2060 })?;
2061
2062 Ok(AccessTokenResponse {
2063 access_token: access_token.to_string(),
2064 token_type: AccessTokenType::Bearer,
2065 issued_token_type: Some(IssuedTokenType::AccessToken),
2066 expires_in,
2067 refresh_token: Some(refresh_token),
2068 scope,
2069 id_token,
2070 })
2071 }
2072
2073 #[cfg(test)]
2074 fn reflect_oauth2_token(
2075 &mut self,
2076 client_auth_info: &ClientAuthInfo,
2077 token: &str,
2078 ) -> Result<Oauth2TokenType, OperationError> {
2079 let Some(client_authz) = client_auth_info.basic_authz.as_ref() else {
2080 warn!("OAuth2 client_id not provided by basic authz");
2081 return Err(OperationError::InvalidSessionState);
2082 };
2083
2084 let client_auth = parse_basic_authz(client_authz.as_str()).map_err(|_| {
2085 warn!("Invalid client_authz base64");
2086 OperationError::InvalidSessionState
2087 })?;
2088
2089 let o2rs = self
2091 .oauth2rs
2092 .inner
2093 .rs_set_get(&client_auth.client_id)
2094 .ok_or_else(|| {
2095 warn!("Invalid OAuth2 client_id");
2096 OperationError::InvalidSessionState
2097 })?;
2098
2099 if let OauthRSType::Basic { authz_secret, .. } = &o2rs.type_ {
2101 if o2rs.is_basic() && Some(authz_secret) != client_auth.client_secret.as_ref() {
2102 info!(
2103 "Invalid OAuth2 secret for client_id={}",
2104 client_auth.client_id
2105 );
2106 return Err(OperationError::InvalidSessionState);
2107 }
2108 }
2109
2110 let jwe_compact = JweCompact::from_str(token).map_err(|err| {
2111 error!(?err, "Failed to deserialise a valid JWE");
2112 OperationError::InvalidSessionState
2113 })?;
2114
2115 o2rs.key_object
2116 .jwe_decrypt(&jwe_compact)
2117 .map_err(|err| {
2118 error!(?err, "Failed to decrypt token reflection request");
2119 OperationError::CryptographyError
2120 })
2121 .and_then(|jwe| {
2122 jwe.from_json().map_err(|err| {
2123 error!(?err, "Failed to deserialise token for reflection");
2124 OperationError::SerdeJsonError
2125 })
2126 })
2127 }
2128}
2129
2130impl IdmServerProxyReadTransaction<'_> {
2131 #[instrument(level = "debug", skip_all)]
2132 pub fn check_oauth2_authorisation(
2133 &self,
2134 maybe_ident: Option<&Identity>,
2135 auth_req: &AuthorisationRequest,
2136 ct: Duration,
2137 ) -> Result<AuthoriseResponse, Oauth2Error> {
2138 trace!(?auth_req);
2142
2143 if auth_req.response_type != ResponseType::Code {
2144 admin_warn!("Unsupported OAuth2 response_type (should be 'code')");
2145 return Err(Oauth2Error::UnsupportedResponseType);
2146 }
2147
2148 let Some(response_mode) = auth_req.get_response_mode() else {
2149 warn!(
2150 "Invalid response_mode {:?} for response_type {:?}",
2151 auth_req.response_mode, auth_req.response_type
2152 );
2153 return Err(Oauth2Error::InvalidRequest);
2154 };
2155
2156 let response_mode = match response_mode {
2157 ResponseMode::Query => SupportedResponseMode::Query,
2158 ResponseMode::Fragment => SupportedResponseMode::Fragment,
2159 ResponseMode::FormPost => {
2160 warn!(
2161 "Invalid response mode form_post requested - many clients request this incorrectly but proceed with response_mode=query. Remapping to query."
2162 );
2163 warn!("This behaviour WILL BE REMOVED in a future release.");
2164 SupportedResponseMode::Query
2165 }
2166 ResponseMode::Invalid => {
2167 warn!("Invalid response mode requested, unable to proceed");
2168 return Err(Oauth2Error::InvalidRequest);
2169 }
2170 };
2171
2172 let o2rs = self
2184 .oauth2rs
2185 .inner
2186 .rs_set_get(&auth_req.client_id)
2187 .ok_or_else(|| {
2188 warn!(
2189 "Invalid OAuth2 client_id ({}) Have you configured the OAuth2 resource server?",
2190 &auth_req.client_id
2191 );
2192 Oauth2Error::InvalidClientId
2193 })?;
2194
2195 if o2rs.type_.allow_localhost_redirect() && check_is_loopback(&auth_req.redirect_uri) {
2198 debug!("Loopback redirect_uri detected, allowing for localhost");
2199 } else {
2200 let origin_uri_matched =
2202 !o2rs.strict_redirect_uri && o2rs.origins.contains(&auth_req.redirect_uri.origin());
2203 let strict_redirect_uri_matched =
2205 o2rs.strict_redirect_uri && o2rs.redirect_uris.contains(&auth_req.redirect_uri);
2206 let opaque_origin_matched = o2rs.opaque_origins.contains(&auth_req.redirect_uri);
2208
2209 if !(strict_redirect_uri_matched || origin_uri_matched || opaque_origin_matched) {
2211 if o2rs.strict_redirect_uri {
2212 warn!(
2213 "Invalid OAuth2 redirect_uri (must be an exact match to a redirect-url) - got {} from resource server but configured uris do not match (check oauth2_rs_origin entries)",
2214 auth_req.redirect_uri.as_str()
2215 );
2216 } else {
2217 warn!(
2218 "Invalid OAuth2 redirect_uri (must be related to origin) - got {:?} from resource server but configured uris differ (compare oauth2_rs_origin_landing with oauth2_rs_origin entries)",
2219 auth_req.redirect_uri.origin()
2220 );
2221 }
2222 return Err(Oauth2Error::InvalidOrigin);
2223 }
2224 if (o2rs.origin_https_required && auth_req.redirect_uri.scheme() != "https")
2227 && !opaque_origin_matched
2228 {
2229 admin_warn!(
2230 "Invalid OAuth2 redirect_uri scheme (must be https for secure origin) - got {} from resource server instead",
2231 auth_req.redirect_uri.to_string()
2232 );
2233 return Err(Oauth2Error::InvalidOrigin);
2234 }
2235 }
2236
2237 let code_challenge = if let Some(pkce_request) = &auth_req.pkce_request {
2238 if !o2rs.require_pkce() {
2239 security_info!(?o2rs.name, "Insecure OAuth2 client configuration - PKCE is not enforced, but client is requesting it!");
2240 }
2241 if pkce_request.code_challenge_method != CodeChallengeMethod::S256 {
2243 admin_warn!("Invalid OAuth2 code_challenge_method (must be 'S256')");
2244 return Err(Oauth2Error::InvalidRequest);
2245 }
2246 Some(pkce_request.code_challenge.clone())
2247 } else if o2rs.require_pkce() {
2248 security_error!(?o2rs.name, "No PKCE code challenge was provided with client in enforced PKCE mode");
2249 return Err(Oauth2Error::InvalidRequest);
2250 } else {
2251 security_info!(?o2rs.name, "Insecure client configuration - PKCE is not enforced");
2252 None
2253 };
2254
2255 let Some(ident) = maybe_ident else {
2280 debug!("No identity available, assume authentication required");
2281 return Ok(AuthoriseResponse::AuthenticationRequired {
2282 client_name: o2rs.displayname.clone(),
2283 login_hint: auth_req.oidc_ext.login_hint.clone(),
2284 });
2285 };
2286
2287 let Some(account_uuid) = ident.get_uuid() else {
2288 error!("Consent request ident does not have a valid UUID, unable to proceed");
2289 return Err(Oauth2Error::InvalidRequest);
2290 };
2291
2292 if account_uuid == UUID_ANONYMOUS {
2294 admin_error!(
2295 "Invalid OAuth2 request - refusing to allow user that authenticated with anonymous"
2296 );
2297 return Err(Oauth2Error::AccessDenied);
2298 }
2299
2300 let (req_scopes, granted_scopes) =
2302 process_requested_scopes_for_identity(o2rs, ident, Some(&auth_req.scope))?;
2303
2304 let openid_requested = req_scopes.contains(OAUTH2_SCOPE_OPENID);
2307
2308 let consent_previously_granted =
2309 if let Some(consent_scopes) = ident.get_oauth2_consent_scopes(o2rs.uuid) {
2310 trace!(?granted_scopes);
2311 trace!(?consent_scopes);
2312 granted_scopes.eq(consent_scopes)
2313 } else {
2314 false
2315 };
2316
2317 let session_id = ident.get_session_id();
2318
2319 if consent_previously_granted || !o2rs.enable_consent_prompt() {
2320 if event_enabled!(tracing::Level::DEBUG) {
2321 let pretty_scopes: Vec<String> =
2322 granted_scopes.iter().map(|s| s.to_owned()).collect();
2323 debug!(
2324 pretty_scopes = pretty_scopes.join(","),
2325 prompt_enabled = o2rs.enable_consent_prompt(),
2326 previously_granted = consent_previously_granted,
2327 "Consent flow passed"
2328 );
2329 }
2330
2331 let expiry = ct.as_secs() + 60;
2333
2334 let xchg_code = TokenExchangeCode {
2336 account_uuid,
2337 session_id,
2338 expiry,
2339 code_challenge,
2340 redirect_uri: auth_req.redirect_uri.clone(),
2341 scopes: granted_scopes.into_iter().collect(),
2342 nonce: auth_req.nonce.clone(),
2343 };
2344
2345 let code_data_jwe = Jwe::into_json(&xchg_code).map_err(|err| {
2347 error!(?err, "Unable to encode xchg_code data");
2348 Oauth2Error::ServerError(OperationError::SerdeJsonError)
2349 })?;
2350
2351 let code = o2rs
2352 .key_object
2353 .jwe_a128gcm_encrypt(&code_data_jwe, ct)
2354 .map(|jwe| jwe.to_string())
2355 .map_err(|err| {
2356 error!(?err, "Unable to encrypt xchg_code data");
2357 Oauth2Error::ServerError(OperationError::CryptographyError)
2358 })?;
2359
2360 Ok(AuthoriseResponse::Permitted(AuthorisePermitSuccess {
2361 redirect_uri: auth_req.redirect_uri.clone(),
2362 state: auth_req.state.clone(),
2363 code,
2364 response_mode,
2365 }))
2366 } else {
2367 let mut pii_scopes = BTreeSet::default();
2381 if openid_requested {
2382 if granted_scopes.contains(OAUTH2_SCOPE_EMAIL) {
2384 pii_scopes.insert(OAUTH2_SCOPE_EMAIL.to_string());
2385 pii_scopes.insert("email_verified".to_string());
2386 }
2387 };
2388
2389 if granted_scopes.contains(OAUTH2_SCOPE_SSH_PUBLICKEYS) {
2390 pii_scopes.insert(OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string());
2391 }
2392
2393 let expiry = ct.as_secs() + 300;
2395
2396 let consent_req = ConsentToken {
2402 client_id: auth_req.client_id.clone(),
2403 ident_id: ident.get_event_origin_id(),
2404 expiry,
2405 session_id,
2406 state: auth_req.state.clone(),
2407 code_challenge,
2408 redirect_uri: auth_req.redirect_uri.clone(),
2409 scopes: granted_scopes.iter().cloned().collect(),
2410 nonce: auth_req.nonce.clone(),
2411 response_mode,
2412 };
2413
2414 let consent_jwe = Jwe::into_json(&consent_req).map_err(|err| {
2415 error!(?err, "Unable to encode consent data");
2416 Oauth2Error::ServerError(OperationError::SerdeJsonError)
2417 })?;
2418
2419 let consent_token = self
2420 .oauth2rs
2421 .inner
2422 .consent_key
2423 .encipher::<JweA128GCMEncipher>(&consent_jwe)
2424 .map(|jwe_compact| jwe_compact.to_string())
2425 .map_err(|err| {
2426 error!(?err, "Unable to encrypt jwe");
2427 Oauth2Error::ServerError(OperationError::CryptographyError)
2428 })?;
2429
2430 Ok(AuthoriseResponse::ConsentRequested {
2431 client_name: o2rs.displayname.clone(),
2432 scopes: granted_scopes.into_iter().collect(),
2433 pii_scopes,
2434 consent_token,
2435 })
2436 }
2437 }
2438
2439 #[instrument(level = "debug", skip_all)]
2440 pub fn check_oauth2_authorise_reject(
2441 &self,
2442 ident: &Identity,
2443 consent_token: &str,
2444 ct: Duration,
2445 ) -> Result<AuthoriseReject, OperationError> {
2446 let jwe_compact = JweCompact::from_str(consent_token).map_err(|_| {
2447 error!("Failed to deserialise a valid JWE");
2448 OperationError::CryptographyError
2449 })?;
2450
2451 let consent_req: ConsentToken = self
2453 .oauth2rs
2454 .inner
2455 .consent_key
2456 .decipher(&jwe_compact)
2457 .map_err(|_| {
2458 admin_error!("Failed to decrypt consent request");
2459 OperationError::CryptographyError
2460 })
2461 .and_then(|jwe| {
2462 jwe.from_json().map_err(|err| {
2463 error!(?err, "Failed to deserialise consent request");
2464 OperationError::SerdeJsonError
2465 })
2466 })?;
2467
2468 if consent_req.ident_id != ident.get_event_origin_id() {
2470 security_info!("consent request ident id does not match the identity of our UAT.");
2471 return Err(OperationError::InvalidSessionState);
2472 }
2473
2474 if consent_req.session_id != ident.get_session_id() {
2476 security_info!("consent request sessien id does not match the session id of our UAT.");
2477 return Err(OperationError::InvalidSessionState);
2478 }
2479
2480 if consent_req.expiry <= ct.as_secs() {
2481 error!("Failed to decrypt consent request");
2483 return Err(OperationError::CryptographyError);
2484 }
2485
2486 let _o2rs = self
2488 .oauth2rs
2489 .inner
2490 .rs_set_get(&consent_req.client_id)
2491 .ok_or_else(|| {
2492 admin_error!("Invalid consent request OAuth2 client_id");
2493 OperationError::InvalidRequestState
2494 })?;
2495
2496 Ok(AuthoriseReject {
2498 redirect_uri: consent_req.redirect_uri,
2499 response_mode: consent_req.response_mode,
2500 })
2501 }
2502
2503 #[instrument(level = "debug", skip_all)]
2504 pub fn check_oauth2_token_introspect(
2505 &mut self,
2506 client_auth_info: &ClientAuthInfo,
2507 intr_req: &AccessTokenIntrospectRequest,
2508 ct: Duration,
2509 ) -> Result<AccessTokenIntrospectResponse, Oauth2Error> {
2510 let client_auth = get_client_auth(client_auth_info, &intr_req.client_post_auth)?;
2511
2512 let o2rs = self
2514 .oauth2rs
2515 .inner
2516 .rs_set_get(&client_auth.client_id)
2517 .ok_or_else(|| {
2518 warn!("Invalid OAuth2 client_id");
2519 Oauth2Error::AuthenticationRequired
2520 })?;
2521
2522 match &o2rs.type_ {
2524 OauthRSType::Basic { authz_secret, .. } => {
2525 if Some(authz_secret) != client_auth.client_secret.as_ref() {
2526 info!("Invalid OAuth2 client_id secret");
2527 return Err(Oauth2Error::AuthenticationRequired);
2528 }
2529 }
2530 OauthRSType::Public { .. } => {}
2532 };
2533
2534 let prefer_short_username = o2rs.prefer_short_username;
2537
2538 if let Ok(jwsc) = JwsCompact::from_str(&intr_req.token) {
2539 let access_token = o2rs
2540 .key_object
2541 .jws_verify(&jwsc)
2542 .map_err(|err| {
2543 error!(?err, "Unable to verify access token");
2544 Oauth2Error::InvalidRequest
2545 })
2546 .and_then(|jws| {
2547 jws.from_json().map_err(|err| {
2548 error!(?err, "Unable to deserialise access token");
2549 Oauth2Error::InvalidRequest
2550 })
2551 })?;
2552
2553 let OAuth2RFC9068Token::<_> {
2554 iss: _,
2555 sub,
2556 aud: _,
2557 exp,
2558 nbf,
2559 iat,
2560 jti,
2561 client_id: _,
2562 extensions:
2563 OAuth2RFC9068TokenExtensions {
2564 auth_time: _,
2565 acr: _,
2566 amr: _,
2567 scope: scopes,
2568 nonce: _,
2569 session_id,
2570 parent_session_id,
2571 },
2572 } = access_token;
2573
2574 if exp <= ct.as_secs() as i64 {
2576 security_info!(?sub, "access token has expired, returning inactive");
2577 return Ok(AccessTokenIntrospectResponse::inactive(jti));
2578 }
2579
2580 let valid = self
2582 .check_oauth2_account_uuid_valid(sub, session_id, parent_session_id, iat, ct)
2583 .map_err(|_| admin_error!("Account is not valid"));
2584
2585 let Ok(Some(entry)) = valid else {
2586 security_info!(
2587 ?sub,
2588 "access token account is not valid, returning inactive"
2589 );
2590 return Ok(AccessTokenIntrospectResponse::inactive(jti));
2591 };
2592
2593 let account = match Account::try_from_entry_ro(&entry, &mut self.qs_read) {
2594 Ok(account) => account,
2595 Err(err) => return Err(Oauth2Error::ServerError(err)),
2596 };
2597
2598 let scope = scopes.clone();
2601
2602 let preferred_username = if prefer_short_username {
2603 Some(account.name().into())
2604 } else {
2605 Some(account.spn().into())
2606 };
2607
2608 let token_type = Some(AccessTokenType::Bearer);
2609 Ok(AccessTokenIntrospectResponse {
2610 active: true,
2611 scope,
2612 client_id: Some(client_auth.client_id.clone()),
2613 username: preferred_username,
2614 token_type,
2615 iat: Some(iat),
2616 exp: Some(exp),
2617 nbf: Some(nbf),
2618 sub: Some(sub.to_string()),
2619 aud: Some(client_auth.client_id),
2620 iss: None,
2621 jti,
2622 })
2623 } else {
2624 let jwe_compact = JweCompact::from_str(&intr_req.token).map_err(|_| {
2625 error!("Failed to deserialise a valid JWE");
2626 Oauth2Error::InvalidRequest
2627 })?;
2628
2629 let token: Oauth2TokenType = o2rs
2630 .key_object
2631 .jwe_decrypt(&jwe_compact)
2632 .map_err(|_| {
2633 admin_error!("Failed to decrypt token introspection request");
2634 Oauth2Error::InvalidRequest
2635 })
2636 .and_then(|jwe| {
2637 jwe.from_json().map_err(|err| {
2638 error!(?err, "Failed to deserialise token");
2639 Oauth2Error::InvalidRequest
2640 })
2641 })?;
2642
2643 match token {
2644 Oauth2TokenType::ClientAccess {
2645 scopes,
2646 session_id,
2647 uuid,
2648 exp,
2649 iat,
2650 nbf,
2651 } => {
2652 if exp <= ct.as_secs() as i64 {
2654 security_info!(?uuid, "access token has expired, returning inactive");
2655 return Ok(AccessTokenIntrospectResponse::inactive(session_id));
2656 }
2657
2658 let valid = self
2660 .check_oauth2_account_uuid_valid(uuid, session_id, None, iat, ct)
2661 .map_err(|_| admin_error!("Account is not valid"));
2662
2663 let Ok(Some(entry)) = valid else {
2664 security_info!(
2665 ?uuid,
2666 "access token account is not valid, returning inactive"
2667 );
2668 return Ok(AccessTokenIntrospectResponse::inactive(session_id));
2669 };
2670
2671 let scope = scopes.clone();
2672
2673 let token_type = Some(AccessTokenType::Bearer);
2674
2675 let username = if prefer_short_username {
2676 entry
2677 .get_ava_single_iname(Attribute::Name)
2678 .map(|s| s.to_string())
2679 } else {
2680 entry.get_ava_single_proto_string(Attribute::Spn)
2681 };
2682
2683 Ok(AccessTokenIntrospectResponse {
2684 active: true,
2685 scope,
2686 client_id: Some(client_auth.client_id.clone()),
2687 username,
2688 token_type,
2689 iat: Some(iat),
2690 exp: Some(exp),
2691 nbf: Some(nbf),
2692 sub: Some(uuid.to_string()),
2693 aud: Some(client_auth.client_id),
2694 iss: None,
2695 jti: session_id,
2696 })
2697 }
2698 Oauth2TokenType::Refresh { session_id, .. } => {
2699 Ok(AccessTokenIntrospectResponse::inactive(session_id))
2700 }
2701 }
2702 }
2703 }
2704
2705 #[instrument(level = "debug", skip_all)]
2706 pub fn oauth2_openid_userinfo(
2707 &mut self,
2708 client_id: &str,
2709 token: &JwsCompact,
2710 ct: Duration,
2711 ) -> Result<OidcToken, Oauth2Error> {
2712 let o2rs: &Oauth2RS = unsafe {
2719 let s = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
2720 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
2721 Oauth2Error::InvalidClientId
2722 })?;
2723 &*(s as *const _)
2724 };
2725
2726 let access_token = o2rs
2727 .key_object
2728 .jws_verify(token)
2729 .map_err(|err| {
2730 error!(?err, "Unable to verify access token");
2731 Oauth2Error::InvalidRequest
2732 })
2733 .and_then(|jws| {
2734 jws.from_json().map_err(|err| {
2735 error!(?err, "Unable to deserialise access token");
2736 Oauth2Error::InvalidRequest
2737 })
2738 })?;
2739
2740 let OAuth2RFC9068Token::<_> {
2741 iss: _,
2742 sub,
2743 aud: _,
2744 exp,
2745 nbf,
2746 iat,
2747 jti: _,
2748 client_id: _,
2749 extensions:
2750 OAuth2RFC9068TokenExtensions {
2751 auth_time: _,
2752 acr: _,
2753 amr: _,
2754 scope: scopes,
2755 nonce,
2756 session_id,
2757 parent_session_id,
2758 },
2759 } = access_token;
2760 if exp <= ct.as_secs() as i64 {
2762 security_info!(?sub, "access token has expired, returning inactive");
2763 return Err(Oauth2Error::InvalidToken);
2764 }
2765
2766 let valid = self
2768 .check_oauth2_account_uuid_valid(sub, session_id, parent_session_id, iat, ct)
2769 .map_err(|_| admin_error!("Account is not valid"));
2770
2771 let Ok(Some(entry)) = valid else {
2772 security_info!(
2773 ?sub,
2774 "access token has account not valid, returning inactive"
2775 );
2776 return Err(Oauth2Error::InvalidToken);
2777 };
2778
2779 let account = match Account::try_from_entry_ro(&entry, &mut self.qs_read) {
2780 Ok(account) => account,
2781 Err(err) => return Err(Oauth2Error::ServerError(err)),
2782 };
2783
2784 let amr = None;
2785
2786 let iss = o2rs.iss.clone();
2787
2788 let s_claims = s_claims_for_account(o2rs, &account, &scopes);
2789 let extra_claims = extra_claims_for_account(&account, &o2rs.claim_map, &scopes);
2790
2791 Ok(OidcToken {
2794 iss,
2795 sub: OidcSubject::U(sub),
2796 aud: client_id.to_string(),
2797 iat,
2798 nbf: Some(nbf),
2799 exp,
2800 auth_time: None,
2801 nonce,
2802 at_hash: None,
2803 acr: None,
2804 amr,
2805 azp: Some(client_id.to_string()),
2806 jti: Some(session_id.to_string()),
2807 s_claims,
2808 claims: extra_claims,
2809 })
2810 }
2811
2812 #[instrument(level = "debug", skip_all)]
2813 pub fn oauth2_rfc8414_metadata(
2814 &self,
2815 client_id: &str,
2816 ) -> Result<Oauth2Rfc8414MetadataResponse, OperationError> {
2817 let o2rs = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
2818 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
2819 OperationError::NoMatchingEntries
2820 })?;
2821
2822 let issuer = o2rs.iss.clone();
2823 let authorization_endpoint = o2rs.authorization_endpoint.clone();
2824 let token_endpoint = o2rs.token_endpoint.clone();
2825 let revocation_endpoint = Some(o2rs.revocation_endpoint.clone());
2826 let introspection_endpoint = Some(o2rs.introspection_endpoint.clone());
2827 let jwks_uri = Some(o2rs.jwks_uri.clone());
2828 let scopes_supported = Some(o2rs.scopes_supported.iter().cloned().collect());
2829 let response_types_supported = vec![ResponseType::Code];
2830 let response_modes_supported = vec![ResponseMode::Query, ResponseMode::Fragment];
2831 let grant_types_supported = vec![GrantType::AuthorisationCode, GrantType::TokenExchange];
2832
2833 let token_endpoint_auth_methods_supported = vec![
2834 TokenEndpointAuthMethod::ClientSecretBasic,
2835 TokenEndpointAuthMethod::ClientSecretPost,
2836 ];
2837
2838 let revocation_endpoint_auth_methods_supported = vec![
2839 TokenEndpointAuthMethod::ClientSecretBasic,
2840 TokenEndpointAuthMethod::ClientSecretPost,
2841 ];
2842
2843 let introspection_endpoint_auth_methods_supported = vec![
2844 TokenEndpointAuthMethod::ClientSecretBasic,
2845 TokenEndpointAuthMethod::ClientSecretPost,
2846 ];
2847
2848 let service_documentation = Some(URL_SERVICE_DOCUMENTATION.clone());
2849
2850 let code_challenge_methods_supported = if o2rs.require_pkce() {
2851 vec![PkceAlg::S256]
2852 } else {
2853 Vec::with_capacity(0)
2854 };
2855
2856 Ok(Oauth2Rfc8414MetadataResponse {
2857 issuer,
2858 authorization_endpoint,
2859 token_endpoint,
2860 jwks_uri,
2861 registration_endpoint: None,
2862 scopes_supported,
2863 response_types_supported,
2864 response_modes_supported,
2865 grant_types_supported,
2866 token_endpoint_auth_methods_supported,
2867 token_endpoint_auth_signing_alg_values_supported: None,
2868 service_documentation,
2869 ui_locales_supported: None,
2870 op_policy_uri: None,
2871 op_tos_uri: None,
2872 revocation_endpoint,
2873 revocation_endpoint_auth_methods_supported,
2874 introspection_endpoint,
2875 introspection_endpoint_auth_methods_supported,
2876 introspection_endpoint_auth_signing_alg_values_supported: None,
2877 code_challenge_methods_supported,
2878 })
2879 }
2880
2881 #[instrument(level = "debug", skip_all)]
2882 pub fn oauth2_openid_discovery(
2883 &self,
2884 client_id: &str,
2885 ) -> Result<OidcDiscoveryResponse, OperationError> {
2886 let o2rs = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
2887 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
2888 OperationError::NoMatchingEntries
2889 })?;
2890
2891 let issuer = o2rs.iss.clone();
2892
2893 let authorization_endpoint = o2rs.authorization_endpoint.clone();
2894 let token_endpoint = o2rs.token_endpoint.clone();
2895 let userinfo_endpoint = Some(o2rs.userinfo_endpoint.clone());
2896 let jwks_uri = o2rs.jwks_uri.clone();
2897 let scopes_supported = Some(o2rs.scopes_supported.iter().cloned().collect());
2898 let response_types_supported = vec![ResponseType::Code];
2899 let response_modes_supported = vec![ResponseMode::Query, ResponseMode::Fragment];
2900
2901 let grant_types_supported = vec![GrantType::AuthorisationCode, GrantType::TokenExchange];
2904
2905 let subject_types_supported = vec![SubjectType::Public];
2906
2907 let id_token_signing_alg_values_supported = match &o2rs.sign_alg {
2908 SignatureAlgo::Es256 => vec![IdTokenSignAlg::ES256],
2909 SignatureAlgo::Rs256 => vec![IdTokenSignAlg::RS256],
2910 };
2911
2912 let userinfo_signing_alg_values_supported = None;
2913 let token_endpoint_auth_methods_supported = vec![
2914 TokenEndpointAuthMethod::ClientSecretBasic,
2915 TokenEndpointAuthMethod::ClientSecretPost,
2916 ];
2917 let display_values_supported = Some(vec![DisplayValue::Page]);
2918 let claim_types_supported = vec![ClaimType::Normal];
2919 let claims_supported = None;
2921 let service_documentation = Some(URL_SERVICE_DOCUMENTATION.clone());
2922
2923 let code_challenge_methods_supported = if o2rs.require_pkce() {
2924 vec![PkceAlg::S256]
2925 } else {
2926 Vec::with_capacity(0)
2927 };
2928
2929 let revocation_endpoint = Some(o2rs.revocation_endpoint.clone());
2932 let revocation_endpoint_auth_methods_supported = vec![
2933 TokenEndpointAuthMethod::ClientSecretBasic,
2934 TokenEndpointAuthMethod::ClientSecretPost,
2935 ];
2936
2937 let introspection_endpoint = Some(o2rs.introspection_endpoint.clone());
2938 let introspection_endpoint_auth_methods_supported = vec![
2939 TokenEndpointAuthMethod::ClientSecretBasic,
2940 TokenEndpointAuthMethod::ClientSecretPost,
2941 ];
2942
2943 Ok(OidcDiscoveryResponse {
2944 issuer,
2945 authorization_endpoint,
2946 token_endpoint,
2947 userinfo_endpoint,
2948 jwks_uri,
2949 registration_endpoint: None,
2950 scopes_supported,
2951 response_types_supported,
2952 response_modes_supported,
2953 grant_types_supported,
2954 acr_values_supported: None,
2955 subject_types_supported,
2956 id_token_signing_alg_values_supported,
2957 id_token_encryption_alg_values_supported: None,
2958 id_token_encryption_enc_values_supported: None,
2959 userinfo_signing_alg_values_supported,
2960 userinfo_encryption_alg_values_supported: None,
2961 userinfo_encryption_enc_values_supported: None,
2962 request_object_signing_alg_values_supported: None,
2963 request_object_encryption_alg_values_supported: None,
2964 request_object_encryption_enc_values_supported: None,
2965 token_endpoint_auth_methods_supported,
2966 token_endpoint_auth_signing_alg_values_supported: None,
2967 display_values_supported,
2968 claim_types_supported,
2969 claims_supported,
2970 service_documentation,
2971 claims_locales_supported: None,
2972 ui_locales_supported: None,
2973 claims_parameter_supported: false,
2974 request_parameter_supported: false,
2976 request_uri_parameter_supported: false,
2978 require_request_uri_registration: false,
2980 op_policy_uri: None,
2981 op_tos_uri: None,
2982 code_challenge_methods_supported,
2983 revocation_endpoint,
2985 revocation_endpoint_auth_methods_supported,
2986 introspection_endpoint,
2987 introspection_endpoint_auth_methods_supported,
2988 introspection_endpoint_auth_signing_alg_values_supported: None,
2989 device_authorization_endpoint: o2rs.device_authorization_endpoint.clone(),
2990 })
2991 }
2992
2993 #[instrument(level = "debug", skip_all)]
2994 pub fn oauth2_openid_webfinger(
2995 &mut self,
2996 client_id: &str,
2997 resource_id: &str,
2998 ) -> Result<OidcWebfingerResponse, OperationError> {
2999 let o2rs = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
3000 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
3001 OperationError::NoMatchingEntries
3002 })?;
3003
3004 let Some(spn) = PartialValue::new_spn_s(resource_id) else {
3005 return Err(OperationError::NoMatchingEntries);
3006 };
3007
3008 if !self
3010 .qs_read
3011 .internal_exists(&Filter::new(f_eq(Attribute::Spn, spn)))?
3012 {
3013 return Err(OperationError::NoMatchingEntries);
3014 }
3015
3016 let issuer = o2rs.iss.clone();
3017
3018 Ok(OidcWebfingerResponse {
3019 subject: resource_id.to_string(),
3022 links: vec![OidcWebfingerRel {
3023 rel: "http://openid.net/specs/connect/1.0/issuer".into(),
3024 href: issuer.into(),
3025 }],
3026 })
3027 }
3028
3029 #[instrument(level = "debug", skip_all)]
3030 pub fn oauth2_openid_publickey(&self, client_id: &str) -> Result<JwkKeySet, OperationError> {
3031 let o2rs = self.oauth2rs.inner.rs_set_get(client_id).ok_or_else(|| {
3032 warn!("Invalid OAuth2 client_id (have you configured the OAuth2 resource server?)");
3033 OperationError::NoMatchingEntries
3034 })?;
3035
3036 trace!(sign_alg = ?o2rs.sign_alg);
3037
3038 match o2rs.sign_alg {
3039 SignatureAlgo::Es256 => o2rs.key_object.jws_es256_jwks(),
3040 SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_jwks(),
3041 }
3042 .ok_or_else(|| {
3043 error!(o2_client = ?o2rs.name, "Unable to retrieve public keys");
3044 OperationError::InvalidState
3045 })
3046 }
3047}
3048
3049fn parse_basic_authz(client_authz: &str) -> Result<ClientAuth, Oauth2Error> {
3050 let authz = general_purpose::STANDARD
3052 .decode(client_authz)
3053 .map_err(|_| {
3054 admin_error!("Basic authz invalid base64");
3055 Oauth2Error::AuthenticationRequired
3056 })
3057 .and_then(|data| {
3058 String::from_utf8(data).map_err(|_| {
3059 admin_error!("Basic authz invalid utf8");
3060 Oauth2Error::AuthenticationRequired
3061 })
3062 })?;
3063
3064 let mut split_iter = authz.split(':');
3067
3068 let client_id = split_iter.next().ok_or_else(|| {
3069 admin_error!("Basic authz invalid format (corrupt input?)");
3070 Oauth2Error::AuthenticationRequired
3071 })?;
3072 let secret = split_iter.next().ok_or_else(|| {
3073 admin_error!("Basic authz invalid format (missing ':' separator?)");
3074 Oauth2Error::AuthenticationRequired
3075 })?;
3076
3077 Ok((client_id, Some(secret)).into())
3078}
3079
3080fn s_claims_for_account(
3081 o2rs: &Oauth2RS,
3082 account: &Account,
3083 scopes: &BTreeSet<String>,
3084) -> OidcClaims {
3085 let preferred_username = if o2rs.prefer_short_username {
3086 Some(account.name().into())
3087 } else {
3088 Some(account.spn().into())
3089 };
3090
3091 let (email, email_verified) = if scopes.contains(OAUTH2_SCOPE_EMAIL) {
3092 if let Some(mp) = &account.mail_primary {
3093 (Some(mp.clone()), Some(true))
3094 } else {
3095 (None, None)
3096 }
3097 } else {
3098 (None, None)
3099 };
3100
3101 OidcClaims {
3102 name: Some(account.displayname.clone()),
3104 scopes: scopes.iter().cloned().collect(),
3105 preferred_username,
3106 email,
3107 email_verified,
3108 ..Default::default()
3109 }
3110}
3111
3112fn extra_claims_for_account(
3113 account: &Account,
3114
3115 claim_map: &BTreeMap<Uuid, Vec<(String, ClaimValue)>>,
3116
3117 scopes: &BTreeSet<String>,
3118) -> BTreeMap<String, serde_json::Value> {
3119 let mut extra_claims = BTreeMap::new();
3120
3121 let mut account_claims: BTreeMap<&str, ClaimValue> = BTreeMap::new();
3122
3123 for group_uuid in account.groups.iter().map(|g| g.uuid()) {
3125 if let Some(claim) = claim_map.get(group_uuid) {
3127 for (claim_name, claim_value) in claim.iter() {
3129 match account_claims.entry(claim_name.as_str()) {
3131 BTreeEntry::Vacant(e) => {
3132 e.insert(claim_value.clone());
3133 }
3134 BTreeEntry::Occupied(mut e) => {
3135 let mut_claim_value = e.get_mut();
3136 mut_claim_value.merge(claim_value);
3138 }
3139 }
3140 }
3141 }
3142 }
3143
3144 for (claim_name, claim_value) in account_claims {
3146 extra_claims.insert(claim_name.to_string(), claim_value.to_json_value());
3147 }
3148
3149 if scopes.contains(OAUTH2_SCOPE_SSH_PUBLICKEYS) {
3153 extra_claims.insert(
3154 OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string(),
3155 account
3156 .sshkeys()
3157 .values()
3158 .map(|pub_key| serde_json::Value::String(pub_key.to_string()))
3159 .collect(),
3160 );
3161 }
3162
3163 let wants_groups = scopes.contains(OAUTH2_SCOPE_GROUPS);
3164 let wants_groups_uuid = wants_groups || scopes.contains(OAUTH2_SCOPE_GROUPS_UUID);
3166 let wants_groups_spn = wants_groups || scopes.contains(OAUTH2_SCOPE_GROUPS_SPN);
3167 let wants_groups_name = scopes.contains(OAUTH2_SCOPE_GROUPS_NAME);
3168
3169 if wants_groups_uuid || wants_groups_name || wants_groups_spn {
3170 extra_claims.insert(
3171 OAUTH2_SCOPE_GROUPS.to_string(),
3172 account
3173 .groups
3174 .iter()
3175 .flat_map(|group| {
3176 let mut attrs = Vec::with_capacity(3);
3177
3178 if wants_groups_uuid {
3179 attrs.push(group.uuid().as_hyphenated().to_string())
3180 }
3181
3182 if wants_groups_spn {
3183 attrs.push(group.spn().clone())
3184 }
3185
3186 if wants_groups_name {
3187 if let Some(name) = group.name() {
3188 attrs.push(name.into())
3189 }
3190 }
3191
3192 attrs
3193 })
3194 .collect(),
3195 );
3196 }
3197
3198 trace!(?extra_claims);
3199
3200 extra_claims
3201}
3202
3203fn process_requested_scopes_for_identity(
3204 o2rs: &Oauth2RS,
3205 ident: &Identity,
3206 req_scopes: Option<&BTreeSet<String>>,
3207) -> Result<(BTreeSet<String>, BTreeSet<String>), Oauth2Error> {
3208 let req_scopes = req_scopes.cloned().unwrap_or_default();
3209
3210 if req_scopes.is_empty() {
3211 admin_error!("Invalid OAuth2 request - must contain at least one requested scope");
3212 return Err(Oauth2Error::InvalidRequest);
3213 }
3214
3215 validate_scopes(&req_scopes)?;
3216
3217 let available_scopes: BTreeSet<String> = o2rs
3218 .scope_maps
3219 .iter()
3220 .filter_map(|(u, m)| ident.is_memberof(*u).then_some(m.iter()))
3221 .flatten()
3222 .cloned()
3223 .collect();
3224
3225 if !req_scopes.is_subset(&available_scopes) {
3226 admin_warn!(
3227 %ident,
3228 requested_scopes = ?req_scopes,
3229 available_scopes = ?available_scopes,
3230 "Identity does not have access to the requested scopes"
3231 );
3232 return Err(Oauth2Error::AccessDenied);
3233 }
3234
3235 let granted_scopes: BTreeSet<String> = o2rs
3236 .sup_scope_maps
3237 .iter()
3238 .filter_map(|(u, m)| ident.is_memberof(*u).then_some(m.iter()))
3239 .flatten()
3240 .cloned()
3241 .chain(req_scopes.iter().cloned())
3242 .collect();
3243
3244 Ok((req_scopes, granted_scopes))
3245}
3246
3247fn validate_scopes(req_scopes: &BTreeSet<String>) -> Result<(), Oauth2Error> {
3248 let failed_scopes = req_scopes
3249 .iter()
3250 .filter(|&s| !OAUTHSCOPE_RE.is_match(s))
3251 .cloned()
3252 .collect::<Vec<String>>();
3253
3254 if !failed_scopes.is_empty() {
3255 let requested_scopes_string = req_scopes
3256 .iter()
3257 .cloned()
3258 .collect::<Vec<String>>()
3259 .join(",");
3260 admin_error!(
3261 "Invalid OAuth2 request - requested scopes ({}) but ({}) failed to pass validation rules - all must match the regex {}",
3262 requested_scopes_string,
3263 failed_scopes.join(","),
3264 OAUTHSCOPE_RE.as_str()
3265 );
3266 return Err(Oauth2Error::InvalidScope);
3267 }
3268 Ok(())
3269}
3270
3271#[inline]
3273#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3274#[allow(dead_code)]
3275fn gen_device_code() -> Result<[u8; 16], Oauth2Error> {
3276 use rand::TryRngCore;
3277
3278 let mut rng = rand::rng();
3279 let mut result = [0u8; 16];
3280 if let Err(err) = rng.try_fill_bytes(&mut result) {
3282 error!("Failed to generate device code! {:?}", err);
3283 return Err(Oauth2Error::ServerError(OperationError::Backend));
3284 }
3285 Ok(result)
3286}
3287
3288#[inline]
3289#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3290#[allow(dead_code)]
3291fn gen_user_code() -> (String, u32) {
3293 use rand::Rng;
3294 let mut rng = rand::rng();
3295 let num: u32 = rng.random_range(0..=999999999);
3296 let result = format!("{num:09}");
3297 (
3298 format!("{}-{}-{}", &result[0..3], &result[3..6], &result[6..9]),
3299 num,
3300 )
3301}
3302
3303#[allow(dead_code)]
3305fn parse_user_code(val: &str) -> Result<u32, Oauth2Error> {
3306 let mut val = val.to_string();
3307 val.retain(|c| c.is_ascii_digit());
3308 val.parse().map_err(|err| {
3309 debug!("Failed to parse value={} as u32: {:?}", val, err);
3310 Oauth2Error::InvalidRequest
3311 })
3312}
3313
3314fn host_is_local(host: &Host<&str>) -> bool {
3316 match host {
3317 Host::Ipv4(ip) => ip.is_loopback(),
3318 Host::Ipv6(ip) => ip.is_loopback(),
3319 Host::Domain(domain) => *domain == "localhost",
3320 }
3321}
3322
3323fn check_is_loopback(redirect_uri: &Url) -> bool {
3325 redirect_uri.host().is_some_and(|host| {
3326 host_is_local(&host)
3328 })
3329}
3330
3331#[cfg(test)]
3332mod tests {
3333 use super::{Oauth2TokenType, PkceS256Secret, TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS};
3334 use crate::credential::Credential;
3335 use crate::idm::accountpolicy::ResolvedAccountPolicy;
3336 use crate::idm::oauth2::{host_is_local, AuthoriseResponse, Oauth2Error, OauthRSType};
3337 use crate::idm::server::{IdmServer, IdmServerTransaction};
3338 use crate::idm::serviceaccount::GenerateApiTokenEvent;
3339 use crate::prelude::*;
3340 use crate::value::{AuthType, OauthClaimMapJoin, SessionState};
3341 use crate::valueset::{ValueSetOauthScopeMap, ValueSetSshKey};
3342 use base64::{engine::general_purpose, Engine as _};
3343 use compact_jwt::{
3344 compact::JwkUse, crypto::JwsRs256Verifier, dangernoverify::JwsDangerReleaseWithoutVerify,
3345 JwaAlg, Jwk, JwsCompact, JwsEs256Verifier, JwsVerifier, OidcSubject, OidcToken,
3346 OidcUnverified,
3347 };
3348 use kanidm_lib_crypto::CryptoPolicy;
3349 use kanidm_proto::constants::*;
3350 use kanidm_proto::internal::{SshPublicKey, UserAuthToken};
3351 use kanidm_proto::oauth2::*;
3352 use std::collections::{BTreeMap, BTreeSet};
3353 use std::convert::TryFrom;
3354 use std::str::FromStr;
3355 use std::time::Duration;
3356 use uri::{OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
3357
3358 const TEST_CURRENT_TIME: u64 = 6000;
3359 const UAT_EXPIRE: u64 = 5;
3360 const TOKEN_EXPIRE: u64 = 900;
3361
3362 const UUID_TESTGROUP: Uuid = uuid!("a3028223-bf20-47d5-8b65-967b5d2bb3eb");
3363
3364 macro_rules! good_authorisation_request {
3365 (
3366 $idms_prox_read:expr,
3367 $ident:expr,
3368 $ct:expr,
3369 $pkce_request:expr,
3370 $scope:expr
3371 ) => {{
3372 #[allow(clippy::unnecessary_to_owned)]
3373 let scope: BTreeSet<String> = $scope.split(" ").map(|s| s.to_string()).collect();
3374
3375 let auth_req = AuthorisationRequest {
3376 response_type: ResponseType::Code,
3377 response_mode: None,
3378 client_id: "test_resource_server".to_string(),
3379 state: Some("123".to_string()),
3380 pkce_request: Some($pkce_request),
3381 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3382 scope,
3383 nonce: Some("abcdef".to_string()),
3384 oidc_ext: Default::default(),
3385 max_age: None,
3386 unknown_keys: Default::default(),
3387 };
3388
3389 $idms_prox_read
3390 .check_oauth2_authorisation(Some($ident), &auth_req, $ct)
3391 .expect("OAuth2 authorisation failed")
3392 }};
3393 }
3394
3395 async fn setup_oauth2_resource_server_basic(
3397 idms: &IdmServer,
3398 ct: Duration,
3399 enable_pkce: bool,
3400 enable_legacy_crypto: bool,
3401 prefer_short_username: bool,
3402 ) -> (String, UserAuthToken, Identity, Uuid) {
3403 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3404
3405 let rs_uuid = Uuid::new_v4();
3406
3407 let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3408 (Attribute::Class, EntryClass::Group.to_value()),
3409 (Attribute::Name, Value::new_iname("testgroup")),
3410 (Attribute::Description, Value::new_utf8s("testgroup")),
3411 (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3412 (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3413 );
3414
3415 let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3416 (Attribute::Class, EntryClass::Object.to_value()),
3417 (Attribute::Class, EntryClass::Account.to_value()),
3418 (
3419 Attribute::Class,
3420 EntryClass::OAuth2ResourceServer.to_value()
3421 ),
3422 (
3423 Attribute::Class,
3424 EntryClass::OAuth2ResourceServerBasic.to_value()
3425 ),
3426 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3427 (Attribute::Name, Value::new_iname("test_resource_server")),
3428 (
3429 Attribute::DisplayName,
3430 Value::new_utf8s("test_resource_server")
3431 ),
3432 (
3433 Attribute::OAuth2RsOriginLanding,
3434 Value::new_url_s("https://demo.example.com").unwrap()
3435 ),
3436 (
3438 Attribute::OAuth2RsOrigin,
3439 Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3440 ),
3441 (
3442 Attribute::OAuth2RsOrigin,
3443 Value::new_url_s("https://portal.example.com/?custom=foo").unwrap()
3444 ),
3445 (
3446 Attribute::OAuth2RsOrigin,
3447 Value::new_url_s("app://cheese").unwrap()
3448 ),
3449 (
3451 Attribute::OAuth2RsScopeMap,
3452 Value::new_oauthscopemap(
3453 UUID_TESTGROUP,
3454 btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3455 )
3456 .expect("invalid oauthscope")
3457 ),
3458 (
3459 Attribute::OAuth2RsScopeMap,
3460 Value::new_oauthscopemap(
3461 UUID_IDM_ALL_ACCOUNTS,
3462 btreeset![OAUTH2_SCOPE_OPENID.to_string()]
3463 )
3464 .expect("invalid oauthscope")
3465 ),
3466 (
3467 Attribute::OAuth2RsSupScopeMap,
3468 Value::new_oauthscopemap(
3469 UUID_IDM_ALL_ACCOUNTS,
3470 btreeset!["supplement".to_string()]
3471 )
3472 .expect("invalid oauthscope")
3473 ),
3474 (
3475 Attribute::OAuth2AllowInsecureClientDisablePkce,
3476 Value::new_bool(!enable_pkce)
3477 ),
3478 (
3479 Attribute::OAuth2JwtLegacyCryptoEnable,
3480 Value::new_bool(enable_legacy_crypto)
3481 ),
3482 (
3483 Attribute::OAuth2PreferShortUsername,
3484 Value::new_bool(prefer_short_username)
3485 )
3486 );
3487
3488 let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3489 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3490
3491 let entry = idms_prox_write
3492 .qs_write
3493 .internal_search_uuid(rs_uuid)
3494 .expect("Failed to retrieve OAuth2 resource entry ");
3495 let secret = entry
3496 .get_ava_single_secret(Attribute::OAuth2RsBasicSecret)
3497 .map(str::to_string)
3498 .expect("No oauth2_rs_basic_secret found");
3499
3500 let session_id = uuid::Uuid::new_v4();
3503
3504 let account = idms_prox_write
3505 .target_to_account(UUID_TESTPERSON_1)
3506 .expect("account must exist");
3507
3508 let uat = account
3509 .to_userauthtoken(
3510 session_id,
3511 SessionScope::ReadWrite,
3512 ct,
3513 &ResolvedAccountPolicy::test_policy(),
3514 )
3515 .expect("Unable to create uat");
3516
3517 let state = uat
3519 .expiry
3520 .map(SessionState::ExpiresAt)
3521 .unwrap_or(SessionState::NeverExpires);
3522
3523 let p = CryptoPolicy::minimum();
3524 let cred = Credential::new_password_only(&p, "test_password").unwrap();
3525 let cred_id = cred.uuid;
3526
3527 let session = Value::Session(
3528 session_id,
3529 crate::value::Session {
3530 label: "label".to_string(),
3531 state,
3532 issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3533 issued_by: IdentityId::Internal,
3534 cred_id,
3535 scope: SessionScope::ReadWrite,
3536 type_: AuthType::Passkey,
3537 ext_metadata: Default::default(),
3538 },
3539 );
3540
3541 let modlist = ModifyList::new_list(vec![
3543 Modify::Present(Attribute::UserAuthTokenSession, session),
3544 Modify::Present(
3545 Attribute::PrimaryCredential,
3546 Value::Cred("primary".to_string(), cred),
3547 ),
3548 ]);
3549
3550 idms_prox_write
3551 .qs_write
3552 .internal_modify(
3553 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3554 &modlist,
3555 )
3556 .expect("Failed to modify user");
3557
3558 let ident = idms_prox_write
3559 .process_uat_to_identity(&uat, ct, Source::Internal)
3560 .expect("Unable to process uat");
3561
3562 idms_prox_write.commit().expect("failed to commit");
3563
3564 (secret, uat, ident, rs_uuid)
3565 }
3566
3567 async fn setup_oauth2_resource_server_public(
3568 idms: &IdmServer,
3569 ct: Duration,
3570 ) -> (UserAuthToken, Identity, Uuid) {
3571 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3572
3573 let rs_uuid = Uuid::new_v4();
3574
3575 let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3576 (Attribute::Class, EntryClass::Group.to_value()),
3577 (Attribute::Name, Value::new_iname("testgroup")),
3578 (Attribute::Description, Value::new_utf8s("testgroup")),
3579 (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3580 (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3581 );
3582
3583 let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3584 (Attribute::Class, EntryClass::Object.to_value()),
3585 (Attribute::Class, EntryClass::Account.to_value()),
3586 (
3587 Attribute::Class,
3588 EntryClass::OAuth2ResourceServer.to_value()
3589 ),
3590 (
3591 Attribute::Class,
3592 EntryClass::OAuth2ResourceServerPublic.to_value()
3593 ),
3594 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3595 (Attribute::Name, Value::new_iname("test_resource_server")),
3596 (
3597 Attribute::DisplayName,
3598 Value::new_utf8s("test_resource_server")
3599 ),
3600 (
3601 Attribute::OAuth2RsOriginLanding,
3602 Value::new_url_s("https://demo.example.com").unwrap()
3603 ),
3604 (
3605 Attribute::OAuth2RsOrigin,
3606 Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3607 ),
3608 (
3610 Attribute::OAuth2RsScopeMap,
3611 Value::new_oauthscopemap(
3612 UUID_TESTGROUP,
3613 btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3614 )
3615 .expect("invalid oauthscope")
3616 ),
3617 (
3618 Attribute::OAuth2RsScopeMap,
3619 Value::new_oauthscopemap(
3620 UUID_IDM_ALL_ACCOUNTS,
3621 btreeset![OAUTH2_SCOPE_OPENID.to_string()]
3622 )
3623 .expect("invalid oauthscope")
3624 ),
3625 (
3626 Attribute::OAuth2RsSupScopeMap,
3627 Value::new_oauthscopemap(
3628 UUID_IDM_ALL_ACCOUNTS,
3629 btreeset!["supplement".to_string()]
3630 )
3631 .expect("invalid oauthscope")
3632 )
3633 );
3634 let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3635 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3636
3637 let session_id = uuid::Uuid::new_v4();
3641
3642 let account = idms_prox_write
3643 .target_to_account(UUID_TESTPERSON_1)
3644 .expect("account must exist");
3645 let uat = account
3646 .to_userauthtoken(
3647 session_id,
3648 SessionScope::ReadWrite,
3649 ct,
3650 &ResolvedAccountPolicy::test_policy(),
3651 )
3652 .expect("Unable to create uat");
3653
3654 let state = uat
3656 .expiry
3657 .map(SessionState::ExpiresAt)
3658 .unwrap_or(SessionState::NeverExpires);
3659
3660 let p = CryptoPolicy::minimum();
3661 let cred = Credential::new_password_only(&p, "test_password").unwrap();
3662 let cred_id = cred.uuid;
3663
3664 let session = Value::Session(
3665 session_id,
3666 crate::value::Session {
3667 label: "label".to_string(),
3668 state,
3669 issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3670 issued_by: IdentityId::Internal,
3671 cred_id,
3672 scope: SessionScope::ReadWrite,
3673 type_: AuthType::Passkey,
3674 ext_metadata: Default::default(),
3675 },
3676 );
3677
3678 let modlist = ModifyList::new_list(vec![
3680 Modify::Present(Attribute::UserAuthTokenSession, session),
3681 Modify::Present(
3682 Attribute::PrimaryCredential,
3683 Value::Cred("primary".to_string(), cred),
3684 ),
3685 ]);
3686
3687 idms_prox_write
3688 .qs_write
3689 .internal_modify(
3690 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3691 &modlist,
3692 )
3693 .expect("Failed to modify user");
3694
3695 let ident = idms_prox_write
3696 .process_uat_to_identity(&uat, ct, Source::Internal)
3697 .expect("Unable to process uat");
3698
3699 idms_prox_write.commit().expect("failed to commit");
3700
3701 (uat, ident, rs_uuid)
3702 }
3703
3704 async fn perform_oauth2_exchange(
3706 idms: &IdmServer,
3707 ident: &Identity,
3708 ct: Duration,
3709 client_authz: ClientAuthInfo,
3710 scopes: String,
3711 ) -> AccessTokenResponse {
3712 let idms_prox_read = idms.proxy_read().await.unwrap();
3713
3714 let pkce_secret = PkceS256Secret::default();
3715
3716 let consent_request = good_authorisation_request!(
3717 idms_prox_read,
3718 ident,
3719 ct,
3720 pkce_secret.to_request(),
3721 scopes
3722 );
3723
3724 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3725 unreachable!();
3726 };
3727
3728 drop(idms_prox_read);
3730 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3731
3732 let permit_success = idms_prox_write
3733 .check_oauth2_authorise_permit(ident, &consent_token, ct)
3734 .expect("Failed to perform OAuth2 permit");
3735
3736 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
3738 code: permit_success.code,
3739 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3740 code_verifier: Some(pkce_secret.to_verifier()),
3741 }
3742 .into();
3743
3744 let token_response = idms_prox_write
3745 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
3746 .expect("Failed to perform OAuth2 token exchange");
3747
3748 assert!(idms_prox_write.commit().is_ok());
3749
3750 token_response
3751 }
3752
3753 async fn validate_id_token(idms: &IdmServer, ct: Duration, id_token: &str) -> OidcToken {
3754 let idms_prox_read = idms.proxy_read().await.unwrap();
3755
3756 let mut jwkset = idms_prox_read
3757 .oauth2_openid_publickey("test_resource_server")
3758 .expect("Failed to get public key");
3759 let public_jwk = jwkset.keys.pop().expect("no such jwk");
3760
3761 let jws_validator =
3762 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
3763
3764 let oidc_unverified = OidcUnverified::from_str(id_token).expect("Failed to parse id_token");
3765
3766 let iat = ct.as_secs() as i64;
3767
3768 jws_validator
3769 .verify(&oidc_unverified)
3770 .unwrap()
3771 .verify_exp(iat)
3772 .expect("Failed to verify oidc")
3773 }
3774
3775 async fn setup_idm_admin(idms: &IdmServer, ct: Duration) -> (UserAuthToken, Identity) {
3776 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3777 let account = idms_prox_write
3778 .target_to_account(UUID_IDM_ADMIN)
3779 .expect("account must exist");
3780 let session_id = uuid::Uuid::new_v4();
3781 let uat = account
3782 .to_userauthtoken(
3783 session_id,
3784 SessionScope::ReadWrite,
3785 ct,
3786 &ResolvedAccountPolicy::test_policy(),
3787 )
3788 .expect("Unable to create uat");
3789 let ident = idms_prox_write
3790 .process_uat_to_identity(&uat, ct, Source::Internal)
3791 .expect("Unable to process uat");
3792
3793 idms_prox_write.commit().expect("failed to commit");
3794
3795 (uat, ident)
3796 }
3797
3798 #[idm_test]
3799 async fn test_idm_oauth2_basic_function(
3800 idms: &IdmServer,
3801 _idms_delayed: &mut IdmServerDelayed,
3802 ) {
3803 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3804 let (secret, _uat, ident, _) =
3805 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
3806
3807 let idms_prox_read = idms.proxy_read().await.unwrap();
3808
3809 let pkce_secret = PkceS256Secret::default();
3811
3812 let consent_request = good_authorisation_request!(
3813 idms_prox_read,
3814 &ident,
3815 ct,
3816 pkce_secret.to_request(),
3817 OAUTH2_SCOPE_OPENID.to_string()
3818 );
3819
3820 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3822 unreachable!();
3823 };
3824
3825 drop(idms_prox_read);
3827 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3828
3829 let permit_success = idms_prox_write
3830 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
3831 .expect("Failed to perform OAuth2 permit");
3832
3833 assert_eq!(permit_success.state.as_deref(), Some("123"));
3835
3836 let token_req = AccessTokenRequest {
3839 grant_type: GrantTypeReq::AuthorizationCode {
3840 code: permit_success.code,
3841 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3842 code_verifier: Some(pkce_secret.to_verifier()),
3843 },
3844 client_post_auth: ClientPostAuth {
3845 client_id: Some("test_resource_server".to_string()),
3846 client_secret: Some(secret),
3847 },
3848 };
3849
3850 let token_response = idms_prox_write
3851 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
3852 .expect("Failed to perform OAuth2 token exchange");
3853
3854 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
3856
3857 assert!(idms_prox_write.commit().is_ok());
3858 }
3859
3860 #[idm_test]
3861 async fn test_idm_oauth2_public_function(
3862 idms: &IdmServer,
3863 _idms_delayed: &mut IdmServerDelayed,
3864 ) {
3865 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3866 let (_uat, ident, _) = setup_oauth2_resource_server_public(idms, ct).await;
3867
3868 let idms_prox_read = idms.proxy_read().await.unwrap();
3869
3870 let pkce_secret = PkceS256Secret::default();
3874
3875 let consent_request = good_authorisation_request!(
3876 idms_prox_read,
3877 &ident,
3878 ct,
3879 pkce_secret.to_request(),
3880 OAUTH2_SCOPE_OPENID.to_string()
3881 );
3882
3883 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3885 unreachable!();
3886 };
3887
3888 drop(idms_prox_read);
3890 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3891
3892 let permit_success = idms_prox_write
3893 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
3894 .expect("Failed to perform OAuth2 permit");
3895
3896 assert_eq!(permit_success.state.as_deref(), Some("123"));
3898
3899 let token_req = AccessTokenRequest {
3902 grant_type: GrantTypeReq::AuthorizationCode {
3903 code: permit_success.code,
3904 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3905 code_verifier: Some(pkce_secret.to_verifier()),
3907 },
3908
3909 client_post_auth: ClientPostAuth {
3910 client_id: Some("Test_Resource_Server".to_string()),
3911 client_secret: None,
3912 },
3913 };
3914
3915 let token_response = idms_prox_write
3916 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
3917 .expect("Failed to perform OAuth2 token exchange");
3918
3919 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
3921
3922 assert!(idms_prox_write.commit().is_ok());
3923 }
3924
3925 #[idm_test]
3926 async fn test_idm_oauth2_invalid_authorisation_requests(
3927 idms: &IdmServer,
3928 _idms_delayed: &mut IdmServerDelayed,
3929 ) {
3930 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3932 let (_secret, _uat, ident, _) =
3933 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
3934
3935 let (_anon_uat, anon_ident) = setup_idm_admin(idms, ct).await;
3936 let (_idm_admin_uat, idm_admin_ident) = setup_idm_admin(idms, ct).await;
3937
3938 let idms_prox_read = idms.proxy_read().await.unwrap();
3940
3941 let pkce_secret = PkceS256Secret::default();
3942
3943 let pkce_request = pkce_secret.to_request();
3944
3945 let auth_req = AuthorisationRequest {
3947 response_type: ResponseType::Token,
3949 response_mode: None,
3950 client_id: "test_resource_server".to_string(),
3951 state: Some("123".to_string()),
3952 pkce_request: Some(pkce_request.clone()),
3953 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3954 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3955 nonce: None,
3956 oidc_ext: Default::default(),
3957 max_age: None,
3958 unknown_keys: Default::default(),
3959 };
3960
3961 assert!(
3962 idms_prox_read
3963 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3964 .unwrap_err()
3965 == Oauth2Error::UnsupportedResponseType
3966 );
3967
3968 let auth_req = AuthorisationRequest {
3970 response_type: ResponseType::Code,
3971 response_mode: None,
3972 client_id: "test_resource_server".to_string(),
3973 state: Some("123".to_string()),
3974 pkce_request: None,
3975 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3976 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3977 nonce: None,
3978 oidc_ext: Default::default(),
3979 max_age: None,
3980 unknown_keys: Default::default(),
3981 };
3982
3983 assert!(
3984 idms_prox_read
3985 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3986 .unwrap_err()
3987 == Oauth2Error::InvalidRequest
3988 );
3989
3990 let auth_req = AuthorisationRequest {
3992 response_type: ResponseType::Code,
3993 response_mode: None,
3994 client_id: "NOT A REAL RESOURCE SERVER".to_string(),
3995 state: Some("123".to_string()),
3996 pkce_request: Some(pkce_request.clone()),
3997 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3998 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3999 nonce: None,
4000 oidc_ext: Default::default(),
4001 max_age: None,
4002 unknown_keys: Default::default(),
4003 };
4004
4005 assert!(
4006 idms_prox_read
4007 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4008 .unwrap_err()
4009 == Oauth2Error::InvalidClientId
4010 );
4011
4012 let auth_req = AuthorisationRequest {
4014 response_type: ResponseType::Code,
4015 response_mode: None,
4016 client_id: "test_resource_server".to_string(),
4017 state: Some("123".to_string()),
4018 pkce_request: Some(pkce_request.clone()),
4019 redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4020 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4021 nonce: None,
4022 oidc_ext: Default::default(),
4023 max_age: None,
4024 unknown_keys: Default::default(),
4025 };
4026
4027 assert!(
4028 idms_prox_read
4029 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4030 .unwrap_err()
4031 == Oauth2Error::InvalidOrigin
4032 );
4033
4034 let auth_req = AuthorisationRequest {
4036 response_type: ResponseType::Code,
4037 response_mode: None,
4038 client_id: "test_resource_server".to_string(),
4039 state: Some("123".to_string()),
4040 pkce_request: Some(pkce_request.clone()),
4041 redirect_uri: Url::parse("https://demo.example.com/oauth2/wrong_place").unwrap(),
4042 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4043 nonce: None,
4044 oidc_ext: Default::default(),
4045 max_age: None,
4046 unknown_keys: Default::default(),
4047 };
4048
4049 assert!(
4050 idms_prox_read
4051 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4052 .unwrap_err()
4053 == Oauth2Error::InvalidOrigin
4054 );
4055
4056 let auth_req = AuthorisationRequest {
4058 response_type: ResponseType::Code,
4059 response_mode: None,
4060 client_id: "test_resource_server".to_string(),
4061 state: Some("123".to_string()),
4062 pkce_request: Some(pkce_request.clone()),
4063 redirect_uri: Url::parse("https://portal.example.com/?custom=foo&too=many").unwrap(),
4064 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4065 nonce: None,
4066 oidc_ext: Default::default(),
4067 max_age: None,
4068 unknown_keys: Default::default(),
4069 };
4070
4071 assert!(
4072 idms_prox_read
4073 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4074 .unwrap_err()
4075 == Oauth2Error::InvalidOrigin
4076 );
4077
4078 let auth_req = AuthorisationRequest {
4079 response_type: ResponseType::Code,
4080 response_mode: None,
4081 client_id: "test_resource_server".to_string(),
4082 state: Some("123".to_string()),
4083 pkce_request: Some(pkce_request.clone()),
4084 redirect_uri: Url::parse("https://portal.example.com").unwrap(),
4085 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4086 nonce: None,
4087 oidc_ext: Default::default(),
4088 max_age: None,
4089 unknown_keys: Default::default(),
4090 };
4091
4092 assert!(
4093 idms_prox_read
4094 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4095 .unwrap_err()
4096 == Oauth2Error::InvalidOrigin
4097 );
4098
4099 let auth_req = AuthorisationRequest {
4100 response_type: ResponseType::Code,
4101 response_mode: None,
4102 client_id: "test_resource_server".to_string(),
4103 state: Some("123".to_string()),
4104 pkce_request: Some(pkce_request.clone()),
4105 redirect_uri: Url::parse("https://portal.example.com/?wrong=queryparam").unwrap(),
4106 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4107 nonce: None,
4108 oidc_ext: Default::default(),
4109 max_age: None,
4110 unknown_keys: Default::default(),
4111 };
4112
4113 assert!(
4114 idms_prox_read
4115 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4116 .unwrap_err()
4117 == Oauth2Error::InvalidOrigin
4118 );
4119
4120 let auth_req = AuthorisationRequest {
4122 response_type: ResponseType::Code,
4123 response_mode: None,
4124 client_id: "test_resource_server".to_string(),
4125 state: Some("123".to_string()),
4126 pkce_request: Some(pkce_request.clone()),
4127 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4128 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4129 nonce: None,
4130 oidc_ext: Default::default(),
4131 max_age: None,
4132 unknown_keys: Default::default(),
4133 };
4134
4135 let req = idms_prox_read
4136 .check_oauth2_authorisation(None, &auth_req, ct)
4137 .unwrap();
4138
4139 assert!(matches!(
4140 req,
4141 AuthoriseResponse::AuthenticationRequired { .. }
4142 ));
4143
4144 let auth_req = AuthorisationRequest {
4146 response_type: ResponseType::Code,
4147 response_mode: None,
4148 client_id: "test_resource_server".to_string(),
4149 state: Some("123".to_string()),
4150 pkce_request: Some(pkce_request.clone()),
4151 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4152 scope: btreeset!["invalid_scope".to_string(), "read".to_string()],
4153 nonce: None,
4154 oidc_ext: Default::default(),
4155 max_age: None,
4156 unknown_keys: Default::default(),
4157 };
4158
4159 assert!(
4160 idms_prox_read
4161 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4162 .unwrap_err()
4163 == Oauth2Error::AccessDenied
4164 );
4165
4166 let auth_req = AuthorisationRequest {
4168 response_type: ResponseType::Code,
4169 response_mode: None,
4170 client_id: "test_resource_server".to_string(),
4171 state: Some("123".to_string()),
4172 pkce_request: Some(pkce_request.clone()),
4173 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4174 scope: btreeset!["openid".to_string(), "read".to_string()],
4175 nonce: None,
4176 oidc_ext: Default::default(),
4177 max_age: None,
4178 unknown_keys: Default::default(),
4179 };
4180
4181 assert!(
4182 idms_prox_read
4183 .check_oauth2_authorisation(Some(&idm_admin_ident), &auth_req, ct)
4184 .unwrap_err()
4185 == Oauth2Error::AccessDenied
4186 );
4187
4188 let auth_req = AuthorisationRequest {
4190 response_type: ResponseType::Code,
4191 response_mode: None,
4192 client_id: "test_resource_server".to_string(),
4193 state: Some("123".to_string()),
4194 pkce_request: Some(pkce_request),
4195 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4196 scope: btreeset!["openid".to_string(), "read".to_string()],
4197 nonce: None,
4198 oidc_ext: Default::default(),
4199 max_age: None,
4200 unknown_keys: Default::default(),
4201 };
4202
4203 assert!(
4204 idms_prox_read
4205 .check_oauth2_authorisation(Some(&anon_ident), &auth_req, ct)
4206 .unwrap_err()
4207 == Oauth2Error::AccessDenied
4208 );
4209 }
4210
4211 #[idm_test]
4212 async fn test_idm_oauth2_invalid_authorisation_permit_requests(
4213 idms: &IdmServer,
4214 _idms_delayed: &mut IdmServerDelayed,
4215 ) {
4216 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4218 let (_secret, uat, ident, _) =
4219 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4220
4221 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4222
4223 let mut uat_wrong_session_id = uat.clone();
4224 uat_wrong_session_id.session_id = uuid::Uuid::new_v4();
4225 let ident_wrong_session_id = idms_prox_write
4226 .process_uat_to_identity(&uat_wrong_session_id, ct, Source::Internal)
4227 .expect("Unable to process uat");
4228
4229 let account = idms_prox_write
4230 .target_to_account(UUID_IDM_ADMIN)
4231 .expect("account must exist");
4232 let session_id = uuid::Uuid::new_v4();
4233 let uat2 = account
4234 .to_userauthtoken(
4235 session_id,
4236 SessionScope::ReadWrite,
4237 ct,
4238 &ResolvedAccountPolicy::test_policy(),
4239 )
4240 .expect("Unable to create uat");
4241 let ident2 = idms_prox_write
4242 .process_uat_to_identity(&uat2, ct, Source::Internal)
4243 .expect("Unable to process uat");
4244
4245 assert!(idms_prox_write.commit().is_ok());
4246
4247 let idms_prox_read = idms.proxy_read().await.unwrap();
4250
4251 let pkce_secret = PkceS256Secret::default();
4252
4253 let consent_request = good_authorisation_request!(
4254 idms_prox_read,
4255 &ident,
4256 ct,
4257 pkce_secret.to_request(),
4258 OAUTH2_SCOPE_OPENID.to_string()
4259 );
4260
4261 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4262 unreachable!();
4263 };
4264
4265 drop(idms_prox_read);
4266 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4267
4268 assert!(
4271 idms_prox_write
4272 .check_oauth2_authorise_permit(
4273 &ident,
4274 &consent_token,
4275 ct + Duration::from_secs(TOKEN_EXPIRE),
4276 )
4277 .unwrap_err()
4278 == OperationError::CryptographyError
4279 );
4280
4281 assert!(
4286 idms_prox_write
4287 .check_oauth2_authorise_permit(&ident2, &consent_token, ct,)
4288 .unwrap_err()
4289 == OperationError::InvalidSessionState
4290 );
4291
4292 assert!(
4294 idms_prox_write
4295 .check_oauth2_authorise_permit(&ident_wrong_session_id, &consent_token, ct,)
4296 .unwrap_err()
4297 == OperationError::InvalidSessionState
4298 );
4299
4300 assert!(idms_prox_write.commit().is_ok());
4301 }
4302
4303 #[idm_test]
4304 async fn test_idm_oauth2_invalid_token_exchange_requests(
4305 idms: &IdmServer,
4306 _idms_delayed: &mut IdmServerDelayed,
4307 ) {
4308 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4309 let (secret, mut uat, ident, _) =
4310 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4311
4312 uat.expiry = Some(
4322 time::OffsetDateTime::UNIX_EPOCH
4323 + Duration::from_secs(TEST_CURRENT_TIME + UAT_EXPIRE - 1),
4324 );
4325
4326 let idms_prox_read = idms.proxy_read().await.unwrap();
4327
4328 let pkce_secret = PkceS256Secret::default();
4330
4331 let consent_request = good_authorisation_request!(
4332 idms_prox_read,
4333 &ident,
4334 ct,
4335 pkce_secret.to_request(),
4336 OAUTH2_SCOPE_OPENID.to_string()
4337 );
4338
4339 let code_verifier = Some(pkce_secret.to_verifier());
4340
4341 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4342 unreachable!();
4343 };
4344
4345 drop(idms_prox_read);
4346 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4347
4348 let permit_success = idms_prox_write
4350 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4351 .expect("Failed to perform OAuth2 permit");
4352
4353 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4358 code: permit_success.code.clone(),
4359 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4360 code_verifier: code_verifier.clone(),
4361 }
4362 .into();
4363
4364 let client_authz = ClientAuthInfo::from("not base64");
4365
4366 assert!(
4367 idms_prox_write
4368 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4369 .unwrap_err()
4370 == Oauth2Error::AuthenticationRequired
4371 );
4372
4373 let client_authz =
4375 general_purpose::STANDARD.encode(format!("test_resource_server {secret}"));
4376 let client_authz = ClientAuthInfo::from(client_authz.as_str());
4377
4378 assert!(
4379 idms_prox_write
4380 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4381 .unwrap_err()
4382 == Oauth2Error::AuthenticationRequired
4383 );
4384
4385 let client_authz = ClientAuthInfo::encode_basic("NOT A REAL SERVER", secret.as_str());
4387
4388 assert!(
4389 idms_prox_write
4390 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4391 .unwrap_err()
4392 == Oauth2Error::AuthenticationRequired
4393 );
4394
4395 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
4397
4398 assert!(
4399 idms_prox_write
4400 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4401 .unwrap_err()
4402 == Oauth2Error::AuthenticationRequired
4403 );
4404
4405 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4407
4408 assert!(
4410 idms_prox_write
4411 .check_oauth2_token_exchange(
4412 &client_authz,
4413 &token_req,
4414 ct + Duration::from_secs(TOKEN_EXPIRE)
4415 )
4416 .unwrap_err()
4417 == Oauth2Error::InvalidRequest
4418 );
4419
4420 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4441 code: permit_success.code.clone(),
4442 redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4443 code_verifier: code_verifier.clone(),
4444 }
4445 .into();
4446 assert!(
4447 idms_prox_write
4448 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4449 .unwrap_err()
4450 == Oauth2Error::InvalidOrigin
4451 );
4452
4453 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4455 code: permit_success.code,
4456 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4457 code_verifier: Some("12345".to_string()),
4458 }
4459 .into();
4460 assert!(
4461 idms_prox_write
4462 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4463 .unwrap_err()
4464 == Oauth2Error::InvalidRequest
4465 );
4466
4467 assert!(idms_prox_write.commit().is_ok());
4468 }
4469
4470 #[idm_test]
4471 async fn test_idm_oauth2_supplemental_origin_redirect(
4472 idms: &IdmServer,
4473 _idms_delayed: &mut IdmServerDelayed,
4474 ) {
4475 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4476 let (secret, uat, ident, _) =
4477 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4478
4479 let idms_prox_read = idms.proxy_read().await.unwrap();
4480
4481 let pkce_secret = PkceS256Secret::default();
4483
4484 let redirect_uri = Url::parse("https://portal.example.com/?custom=foo").unwrap();
4485
4486 let auth_req = AuthorisationRequest {
4487 response_type: ResponseType::Code,
4488 response_mode: None,
4489 client_id: "test_resource_server".to_string(),
4490 state: None,
4491 pkce_request: Some(pkce_secret.to_request()),
4492 redirect_uri: redirect_uri.clone(),
4493 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4494 nonce: Some("abcdef".to_string()),
4495 oidc_ext: Default::default(),
4496 max_age: None,
4497 unknown_keys: Default::default(),
4498 };
4499
4500 let consent_request = idms_prox_read
4501 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4502 .expect("OAuth2 authorisation failed");
4503
4504 trace!(?consent_request);
4505
4506 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4508 unreachable!();
4509 };
4510
4511 drop(idms_prox_read);
4513 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4514
4515 let permit_success = idms_prox_write
4516 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4517 .expect("Failed to perform OAuth2 permit");
4518
4519 assert_eq!(permit_success.state.as_deref(), None);
4521
4522 let permit_redirect_uri = permit_success.build_redirect_uri();
4525
4526 assert_eq!(permit_redirect_uri.origin(), redirect_uri.origin());
4527 assert_eq!(permit_redirect_uri.path(), redirect_uri.path());
4528 let query = BTreeMap::from_iter(permit_redirect_uri.query_pairs().into_owned());
4529 assert_eq!(query.get("custom").map(|s| s.as_str()), Some("foo"));
4531
4532 let token_req = AccessTokenRequest {
4535 grant_type: GrantTypeReq::AuthorizationCode {
4536 code: permit_success.code,
4537 redirect_uri,
4538 code_verifier: Some(pkce_secret.to_verifier()),
4539 },
4540
4541 client_post_auth: ClientPostAuth {
4542 client_id: Some("test_resource_server".to_string()),
4543 client_secret: Some(secret.clone()),
4544 },
4545 };
4546
4547 let token_response = idms_prox_write
4548 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4549 .expect("Failed to perform OAuth2 token exchange");
4550
4551 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4553
4554 assert!(idms_prox_write.commit().is_ok());
4555
4556 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4560
4561 let ident = idms_prox_read
4563 .process_uat_to_identity(&uat, ct, Source::Internal)
4564 .expect("Unable to process uat");
4565
4566 let pkce_secret = PkceS256Secret::default();
4567
4568 let auth_req = AuthorisationRequest {
4569 response_type: ResponseType::Code,
4570 response_mode: None,
4571 client_id: "test_resource_server".to_string(),
4572 state: Some("123".to_string()),
4573 pkce_request: Some(pkce_secret.to_request()),
4574 redirect_uri: Url::parse("app://cheese").unwrap(),
4575 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4576 nonce: Some("abcdef".to_string()),
4577 oidc_ext: Default::default(),
4578 max_age: None,
4579 unknown_keys: Default::default(),
4580 };
4581
4582 let consent_request = idms_prox_read
4583 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4584 .expect("OAuth2 authorisation failed");
4585
4586 trace!(?consent_request);
4587
4588 let AuthoriseResponse::Permitted(permit_success) = consent_request else {
4589 unreachable!();
4590 };
4591
4592 assert_eq!(permit_success.state.as_deref(), Some("123"));
4595
4596 let token_req = AccessTokenRequest {
4599 grant_type: GrantTypeReq::AuthorizationCode {
4600 code: permit_success.code,
4601 redirect_uri: Url::parse("app://cheese").unwrap(),
4602 code_verifier: Some(pkce_secret.to_verifier()),
4603 },
4604
4605 client_post_auth: ClientPostAuth {
4606 client_id: Some("test_resource_server".to_string()),
4607 client_secret: Some(secret),
4608 },
4609 };
4610
4611 drop(idms_prox_read);
4612 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4613
4614 let token_response = idms_prox_write
4615 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4616 .expect("Failed to perform OAuth2 token exchange");
4617
4618 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4620 }
4621
4622 #[idm_test]
4623 async fn test_idm_oauth2_token_introspect(
4624 idms: &IdmServer,
4625 _idms_delayed: &mut IdmServerDelayed,
4626 ) {
4627 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4628 let (secret, _uat, ident, _) =
4629 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4630 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4631
4632 let idms_prox_read = idms.proxy_read().await.unwrap();
4633
4634 let pkce_secret = PkceS256Secret::default();
4636 let consent_request = good_authorisation_request!(
4637 idms_prox_read,
4638 &ident,
4639 ct,
4640 pkce_secret.to_request(),
4641 OAUTH2_SCOPE_OPENID.to_string()
4642 );
4643
4644 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4645 unreachable!();
4646 };
4647
4648 drop(idms_prox_read);
4650 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4651
4652 let permit_success = idms_prox_write
4653 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4654 .expect("Failed to perform OAuth2 permit");
4655
4656 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4657 code: permit_success.code,
4658 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4659 code_verifier: Some(pkce_secret.to_verifier()),
4660 }
4661 .into();
4662 let oauth2_token = idms_prox_write
4663 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4664 .expect("Unable to exchange for OAuth2 token");
4665
4666 assert!(idms_prox_write.commit().is_ok());
4667
4668 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4670
4671 let intr_request = AccessTokenIntrospectRequest {
4672 token: oauth2_token.access_token,
4673 token_type_hint: None,
4674 client_post_auth: ClientPostAuth::default(),
4675 };
4676 let intr_response = idms_prox_read
4677 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4678 .expect("Failed to inspect token");
4679
4680 eprintln!("👉 {intr_response:?}");
4681 assert!(intr_response.active);
4682 assert_eq!(
4683 intr_response.scope,
4684 btreeset!["openid".to_string(), "supplement".to_string()]
4685 );
4686 assert_eq!(
4687 intr_response.client_id.as_deref(),
4688 Some("test_resource_server")
4689 );
4690 assert_eq!(
4691 intr_response.username.as_deref(),
4692 Some("testperson1@example.com")
4693 );
4694 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
4695 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
4696 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
4697
4698 drop(idms_prox_read);
4699 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4702 let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_CURRENT_TIME - 1));
4704 let me_inv_m = ModifyEvent::new_internal_invalid(
4705 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
4706 ModifyList::new_list(vec![Modify::Present(Attribute::AccountExpire, v_expire)]),
4707 );
4708 assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok());
4710 assert!(idms_prox_write.commit().is_ok());
4711
4712 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4715 let intr_response = idms_prox_read
4716 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4717 .expect("Failed to inspect token");
4718
4719 assert!(!intr_response.active);
4720 }
4721
4722 #[idm_test]
4723 async fn test_idm_oauth2_token_revoke(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
4724 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4726 let (secret, _uat, ident, _) =
4727 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4728 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4729
4730 let idms_prox_read = idms.proxy_read().await.unwrap();
4731
4732 let pkce_secret = PkceS256Secret::default();
4734
4735 let consent_request = good_authorisation_request!(
4736 idms_prox_read,
4737 &ident,
4738 ct,
4739 pkce_secret.to_request(),
4740 OAUTH2_SCOPE_OPENID.to_string()
4741 );
4742
4743 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4744 unreachable!();
4745 };
4746
4747 drop(idms_prox_read);
4749 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4750
4751 let permit_success = idms_prox_write
4752 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4753 .expect("Failed to perform OAuth2 permit");
4754
4755 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4757 code: permit_success.code,
4758 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4759 code_verifier: Some(pkce_secret.to_verifier()),
4760 }
4761 .into();
4762 let oauth2_token = idms_prox_write
4763 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4764 .expect("Unable to exchange for OAuth2 token");
4765
4766 assert!(idms_prox_write.commit().is_ok());
4767
4768 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4772 let intr_request = AccessTokenIntrospectRequest {
4773 token: oauth2_token.access_token.clone(),
4774 token_type_hint: None,
4775 client_post_auth: ClientPostAuth::default(),
4776 };
4777 let intr_response = idms_prox_read
4778 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4779 .expect("Failed to inspect token");
4780 eprintln!("👉 {intr_response:?}");
4781 assert!(intr_response.active);
4782 drop(idms_prox_read);
4783
4784 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4786
4787 let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
4788
4789 let revoke_request = TokenRevokeRequest {
4790 token: oauth2_token.access_token.clone(),
4791 token_type_hint: None,
4792 client_post_auth: ClientPostAuth::default(),
4793 };
4794 let e = idms_prox_write
4795 .oauth2_token_revoke(&bad_client_authz, &revoke_request, ct)
4796 .unwrap_err();
4797 assert!(matches!(e, Oauth2Error::AuthenticationRequired));
4798 assert!(idms_prox_write.commit().is_ok());
4799
4800 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4802 let revoke_request = TokenRevokeRequest {
4803 token: "this is an invalid token, nothing will happen!".to_string(),
4804 token_type_hint: None,
4805 client_post_auth: ClientPostAuth::default(),
4806 };
4807 let e = idms_prox_write
4808 .oauth2_token_revoke(&client_authz, &revoke_request, ct)
4809 .unwrap_err();
4810 assert!(matches!(e, Oauth2Error::InvalidRequest));
4811 assert!(idms_prox_write.commit().is_ok());
4812
4813 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4815 let intr_response = idms_prox_read
4816 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4817 .expect("Failed to inspect token");
4818 assert!(intr_response.active);
4819 drop(idms_prox_read);
4820
4821 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4823 let revoke_request = TokenRevokeRequest {
4824 token: oauth2_token.access_token.clone(),
4825 token_type_hint: None,
4826 client_post_auth: ClientPostAuth::default(),
4827 };
4828 assert!(idms_prox_write
4829 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
4830 .is_ok());
4831 assert!(idms_prox_write.commit().is_ok());
4832
4833 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4835 let intr_response = idms_prox_read
4836 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4837 .expect("Failed to inspect token");
4838
4839 assert!(!intr_response.active);
4840 drop(idms_prox_read);
4841
4842 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4844 let filt = filter!(f_eq(
4845 Attribute::Uuid,
4846 PartialValue::Uuid(ident.get_uuid().unwrap())
4847 ));
4848 let mut work_set = idms_prox_write
4849 .qs_write
4850 .internal_search_writeable(&filt)
4851 .expect("Failed to perform internal search writeable");
4852 for (_, entry) in work_set.iter_mut() {
4853 let _ = entry.force_trim_ava(Attribute::OAuth2Session);
4854 }
4855 assert!(idms_prox_write
4856 .qs_write
4857 .internal_apply_writable(work_set)
4858 .is_ok());
4859
4860 assert!(idms_prox_write.commit().is_ok());
4861
4862 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4863 let intr_response = idms_prox_read
4865 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4866 .expect("Failed to inspect token");
4867 assert!(intr_response.active);
4868
4869 let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
4871 let intr_response = idms_prox_read
4872 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4873 .expect("Failed to inspect token");
4874 assert!(!intr_response.active);
4875
4876 drop(idms_prox_read);
4877
4878 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4880 let revoke_request = TokenRevokeRequest {
4881 token: oauth2_token.access_token,
4882 token_type_hint: None,
4883 client_post_auth: ClientPostAuth::default(),
4884 };
4885 assert!(idms_prox_write
4886 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
4887 .is_ok());
4888 assert!(idms_prox_write.commit().is_ok());
4889 }
4890
4891 #[idm_test]
4892 async fn test_idm_oauth2_session_cleanup_post_rs_delete(
4893 idms: &IdmServer,
4894 _idms_delayed: &mut IdmServerDelayed,
4895 ) {
4896 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4898 let (secret, _uat, ident, _) =
4899 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4900 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4901
4902 let idms_prox_read = idms.proxy_read().await.unwrap();
4903
4904 let pkce_secret = PkceS256Secret::default();
4906
4907 let consent_request = good_authorisation_request!(
4908 idms_prox_read,
4909 &ident,
4910 ct,
4911 pkce_secret.to_request(),
4912 OAUTH2_SCOPE_OPENID.to_string()
4913 );
4914
4915 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4916 unreachable!();
4917 };
4918
4919 drop(idms_prox_read);
4921 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4922
4923 let permit_success = idms_prox_write
4924 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4925 .expect("Failed to perform OAuth2 permit");
4926
4927 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4928 code: permit_success.code,
4929 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4930 code_verifier: Some(pkce_secret.to_verifier()),
4931 }
4932 .into();
4933
4934 let oauth2_token = idms_prox_write
4935 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4936 .expect("Unable to exchange for OAuth2 token");
4937
4938 let access_token =
4939 JwsCompact::from_str(&oauth2_token.access_token).expect("Invalid Access Token");
4940
4941 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
4942
4943 let reflected_token = jws_verifier
4944 .verify(&access_token)
4945 .unwrap()
4946 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
4947 .expect("Failed to access internals of the refresh token");
4948
4949 let session_id = reflected_token.extensions.session_id;
4950
4951 assert!(idms_prox_write.commit().is_ok());
4952
4953 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4955
4956 let entry = idms_prox_write
4958 .qs_write
4959 .internal_search_uuid(UUID_TESTPERSON_1)
4960 .expect("failed");
4961 let valid = entry
4962 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
4963 .map(|map| map.get(&session_id).is_some())
4964 .unwrap_or(false);
4965 assert!(valid);
4966
4967 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
4970 Attribute::Name,
4971 PartialValue::new_iname("test_resource_server")
4972 )));
4973
4974 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
4975
4976 let entry = idms_prox_write
4980 .qs_write
4981 .internal_search_uuid(UUID_TESTPERSON_1)
4982 .expect("failed");
4983 let revoked = entry
4984 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
4985 .and_then(|sessions| sessions.get(&session_id))
4986 .map(|session| matches!(session.state, SessionState::RevokedAt(_)))
4987 .unwrap_or(false);
4988 assert!(revoked);
4989
4990 assert!(idms_prox_write.commit().is_ok());
4991 }
4992
4993 #[idm_test]
4994 async fn test_idm_oauth2_authorisation_reject(
4995 idms: &IdmServer,
4996 _idms_delayed: &mut IdmServerDelayed,
4997 ) {
4998 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4999 let (_secret, _uat, ident, _) =
5000 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5001
5002 let ident2 = {
5003 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5004 let account = idms_prox_write
5005 .target_to_account(UUID_IDM_ADMIN)
5006 .expect("account must exist");
5007 let session_id = uuid::Uuid::new_v4();
5008 let uat2 = account
5009 .to_userauthtoken(
5010 session_id,
5011 SessionScope::ReadWrite,
5012 ct,
5013 &ResolvedAccountPolicy::test_policy(),
5014 )
5015 .expect("Unable to create uat");
5016
5017 idms_prox_write
5018 .process_uat_to_identity(&uat2, ct, Source::Internal)
5019 .expect("Unable to process uat")
5020 };
5021
5022 let idms_prox_read = idms.proxy_read().await.unwrap();
5023 let redirect_uri = Url::parse("https://demo.example.com/oauth2/result").unwrap();
5024
5025 let pkce_secret = PkceS256Secret::default();
5026
5027 let consent_request = good_authorisation_request!(
5029 idms_prox_read,
5030 &ident,
5031 ct,
5032 pkce_secret.to_request(),
5033 OAUTH2_SCOPE_OPENID.to_string()
5034 );
5035
5036 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5037 unreachable!();
5038 };
5039
5040 let reject_success = idms_prox_read
5041 .check_oauth2_authorise_reject(&ident, &consent_token, ct)
5042 .expect("Failed to perform OAuth2 reject");
5043
5044 assert_eq!(reject_success.redirect_uri, redirect_uri);
5045
5046 let past_ct = Duration::from_secs(TEST_CURRENT_TIME + 301);
5048 assert!(
5049 idms_prox_read
5050 .check_oauth2_authorise_reject(&ident, &consent_token, past_ct)
5051 .unwrap_err()
5052 == OperationError::CryptographyError
5053 );
5054
5055 assert_eq!(
5057 idms_prox_read
5058 .check_oauth2_authorise_reject(&ident, "not a token", ct)
5059 .unwrap_err(),
5060 OperationError::CryptographyError
5061 );
5062
5063 assert!(
5065 idms_prox_read
5066 .check_oauth2_authorise_reject(&ident2, &consent_token, ct)
5067 .unwrap_err()
5068 == OperationError::InvalidSessionState
5069 );
5070 }
5071
5072 #[idm_test]
5073 async fn test_idm_oauth2_rfc8414_metadata(
5074 idms: &IdmServer,
5075 _idms_delayed: &mut IdmServerDelayed,
5076 ) {
5077 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5078 let (_secret, _uat, _ident, _) =
5079 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5080
5081 let idms_prox_read = idms.proxy_read().await.unwrap();
5082
5083 assert!(
5085 idms_prox_read
5086 .oauth2_rfc8414_metadata("nosuchclient")
5087 .unwrap_err()
5088 == OperationError::NoMatchingEntries
5089 );
5090
5091 let discovery = idms_prox_read
5092 .oauth2_rfc8414_metadata("test_resource_server")
5093 .expect("Failed to get discovery");
5094
5095 assert!(
5096 discovery.issuer
5097 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5098 .unwrap()
5099 );
5100
5101 assert!(
5102 discovery.authorization_endpoint
5103 == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
5104 );
5105
5106 assert!(
5107 discovery.token_endpoint
5108 == Url::parse(&format!(
5109 "https://idm.example.com{}",
5110 uri::OAUTH2_TOKEN_ENDPOINT
5111 ))
5112 .unwrap()
5113 );
5114
5115 assert!(
5116 discovery.jwks_uri
5117 == Some(
5118 Url::parse(
5119 "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
5120 )
5121 .unwrap()
5122 )
5123 );
5124
5125 assert!(discovery.registration_endpoint.is_none());
5126
5127 assert!(
5128 discovery.scopes_supported
5129 == Some(vec![
5130 OAUTH2_SCOPE_GROUPS.to_string(),
5131 OAUTH2_SCOPE_OPENID.to_string(),
5132 "supplement".to_string(),
5133 ])
5134 );
5135
5136 assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5137 assert_eq!(
5138 discovery.response_modes_supported,
5139 vec![ResponseMode::Query, ResponseMode::Fragment]
5140 );
5141 assert_eq!(
5142 discovery.grant_types_supported,
5143 vec![GrantType::AuthorisationCode, GrantType::TokenExchange]
5144 );
5145 assert!(
5146 discovery.token_endpoint_auth_methods_supported
5147 == vec![
5148 TokenEndpointAuthMethod::ClientSecretBasic,
5149 TokenEndpointAuthMethod::ClientSecretPost
5150 ]
5151 );
5152 assert!(discovery.service_documentation.is_some());
5153
5154 assert!(discovery.ui_locales_supported.is_none());
5155 assert!(discovery.op_policy_uri.is_none());
5156 assert!(discovery.op_tos_uri.is_none());
5157
5158 assert!(
5159 discovery.revocation_endpoint
5160 == Some(
5161 Url::parse(&format!(
5162 "https://idm.example.com{OAUTH2_TOKEN_REVOKE_ENDPOINT}"
5163 ))
5164 .unwrap()
5165 )
5166 );
5167 assert!(
5168 discovery.revocation_endpoint_auth_methods_supported
5169 == vec![
5170 TokenEndpointAuthMethod::ClientSecretBasic,
5171 TokenEndpointAuthMethod::ClientSecretPost
5172 ]
5173 );
5174
5175 assert!(
5176 discovery.introspection_endpoint
5177 == Some(
5178 Url::parse(&format!(
5179 "https://idm.example.com{}",
5180 kanidm_proto::constants::uri::OAUTH2_TOKEN_INTROSPECT_ENDPOINT
5181 ))
5182 .unwrap()
5183 )
5184 );
5185 assert!(
5186 discovery.introspection_endpoint_auth_methods_supported
5187 == vec![
5188 TokenEndpointAuthMethod::ClientSecretBasic,
5189 TokenEndpointAuthMethod::ClientSecretPost
5190 ]
5191 );
5192 assert!(discovery
5193 .introspection_endpoint_auth_signing_alg_values_supported
5194 .is_none());
5195
5196 assert_eq!(
5197 discovery.code_challenge_methods_supported,
5198 vec![PkceAlg::S256]
5199 )
5200 }
5201
5202 #[idm_test]
5203 async fn test_idm_oauth2_openid_discovery(
5204 idms: &IdmServer,
5205 _idms_delayed: &mut IdmServerDelayed,
5206 ) {
5207 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
5211 let idms_prox_read = idms.proxy_read().await.unwrap();
5212
5213 assert!(
5215 idms_prox_read
5216 .oauth2_openid_discovery("nosuchclient")
5217 .unwrap_err()
5218 == OperationError::NoMatchingEntries
5219 );
5220
5221 assert!(
5222 idms_prox_read
5223 .oauth2_openid_publickey("nosuchclient")
5224 .unwrap_err()
5225 == OperationError::NoMatchingEntries
5226 );
5227
5228 let discovery = idms_prox_read
5229 .oauth2_openid_discovery("test_resource_server")
5230 .expect("Failed to get discovery");
5231
5232 let mut jwkset = idms_prox_read
5233 .oauth2_openid_publickey("test_resource_server")
5234 .expect("Failed to get public key");
5235
5236 let jwk = jwkset.keys.pop().expect("no such jwk");
5237
5238 match jwk {
5239 Jwk::EC { alg, use_, kid, .. } => {
5240 match (
5241 alg.unwrap(),
5242 &discovery.id_token_signing_alg_values_supported[0],
5243 ) {
5244 (JwaAlg::ES256, IdTokenSignAlg::ES256) => {}
5245 _ => panic!(),
5246 };
5247 assert_eq!(use_.unwrap(), JwkUse::Sig);
5248 assert!(kid.is_some())
5249 }
5250 _ => panic!(),
5251 };
5252
5253 assert!(
5254 discovery.issuer
5255 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5256 .unwrap()
5257 );
5258
5259 assert!(
5260 discovery.authorization_endpoint
5261 == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
5262 );
5263
5264 assert!(
5265 discovery.token_endpoint == Url::parse("https://idm.example.com/oauth2/token").unwrap()
5266 );
5267
5268 assert!(
5269 discovery.userinfo_endpoint
5270 == Some(
5271 Url::parse(
5272 "https://idm.example.com/oauth2/openid/test_resource_server/userinfo"
5273 )
5274 .unwrap()
5275 )
5276 );
5277
5278 assert!(
5279 discovery.jwks_uri
5280 == Url::parse(
5281 "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
5282 )
5283 .unwrap()
5284 );
5285
5286 assert!(
5287 discovery.scopes_supported
5288 == Some(vec![
5289 OAUTH2_SCOPE_GROUPS.to_string(),
5290 OAUTH2_SCOPE_OPENID.to_string(),
5291 "supplement".to_string(),
5292 ])
5293 );
5294
5295 assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5296 assert_eq!(
5297 discovery.response_modes_supported,
5298 vec![ResponseMode::Query, ResponseMode::Fragment]
5299 );
5300 assert_eq!(
5301 discovery.grant_types_supported,
5302 vec![GrantType::AuthorisationCode, GrantType::TokenExchange]
5303 );
5304 assert_eq!(discovery.subject_types_supported, vec![SubjectType::Public]);
5305 assert_eq!(
5306 discovery.id_token_signing_alg_values_supported,
5307 vec![IdTokenSignAlg::ES256]
5308 );
5309 assert!(discovery.userinfo_signing_alg_values_supported.is_none());
5310 assert!(
5311 discovery.token_endpoint_auth_methods_supported
5312 == vec![
5313 TokenEndpointAuthMethod::ClientSecretBasic,
5314 TokenEndpointAuthMethod::ClientSecretPost
5315 ]
5316 );
5317 assert_eq!(
5318 discovery.display_values_supported,
5319 Some(vec![DisplayValue::Page])
5320 );
5321 assert_eq!(discovery.claim_types_supported, vec![ClaimType::Normal]);
5322 assert!(discovery.claims_supported.is_none());
5323 assert!(discovery.service_documentation.is_some());
5324
5325 assert!(discovery.registration_endpoint.is_none());
5326 assert!(discovery.acr_values_supported.is_none());
5327 assert!(discovery.id_token_encryption_alg_values_supported.is_none());
5328 assert!(discovery.id_token_encryption_enc_values_supported.is_none());
5329 assert!(discovery.userinfo_encryption_alg_values_supported.is_none());
5330 assert!(discovery.userinfo_encryption_enc_values_supported.is_none());
5331 assert!(discovery
5332 .request_object_signing_alg_values_supported
5333 .is_none());
5334 assert!(discovery
5335 .request_object_encryption_alg_values_supported
5336 .is_none());
5337 assert!(discovery
5338 .request_object_encryption_enc_values_supported
5339 .is_none());
5340 assert!(discovery
5341 .token_endpoint_auth_signing_alg_values_supported
5342 .is_none());
5343 assert!(discovery.claims_locales_supported.is_none());
5344 assert!(discovery.ui_locales_supported.is_none());
5345 assert!(discovery.op_policy_uri.is_none());
5346 assert!(discovery.op_tos_uri.is_none());
5347 assert!(!discovery.claims_parameter_supported);
5348 assert!(!discovery.request_uri_parameter_supported);
5349 assert!(!discovery.require_request_uri_registration);
5350 assert!(!discovery.request_parameter_supported);
5351 assert_eq!(
5352 discovery.code_challenge_methods_supported,
5353 vec![PkceAlg::S256]
5354 );
5355
5356 assert!(
5358 discovery.revocation_endpoint
5359 == Some(
5360 Url::parse(&format!(
5361 "https://idm.example.com{OAUTH2_TOKEN_REVOKE_ENDPOINT}"
5362 ))
5363 .unwrap()
5364 )
5365 );
5366 assert!(
5367 discovery.revocation_endpoint_auth_methods_supported
5368 == vec![
5369 TokenEndpointAuthMethod::ClientSecretBasic,
5370 TokenEndpointAuthMethod::ClientSecretPost
5371 ]
5372 );
5373
5374 assert!(
5375 discovery.introspection_endpoint
5376 == Some(
5377 Url::parse(&format!(
5378 "https://idm.example.com{OAUTH2_TOKEN_INTROSPECT_ENDPOINT}"
5379 ))
5380 .unwrap()
5381 )
5382 );
5383 assert!(
5384 discovery.introspection_endpoint_auth_methods_supported
5385 == vec![
5386 TokenEndpointAuthMethod::ClientSecretBasic,
5387 TokenEndpointAuthMethod::ClientSecretPost
5388 ]
5389 );
5390 assert!(discovery
5391 .introspection_endpoint_auth_signing_alg_values_supported
5392 .is_none());
5393 }
5394
5395 #[idm_test]
5396 async fn test_idm_oauth2_openid_extensions(
5397 idms: &IdmServer,
5398 _idms_delayed: &mut IdmServerDelayed,
5399 ) {
5400 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5401 let (secret, _uat, ident, _) =
5402 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5403 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5404
5405 let idms_prox_read = idms.proxy_read().await.unwrap();
5406
5407 let pkce_secret = PkceS256Secret::default();
5408
5409 let consent_request = good_authorisation_request!(
5410 idms_prox_read,
5411 &ident,
5412 ct,
5413 pkce_secret.to_request(),
5414 OAUTH2_SCOPE_OPENID.to_string()
5415 );
5416
5417 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5418 unreachable!();
5419 };
5420
5421 drop(idms_prox_read);
5423 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5424
5425 let permit_success = idms_prox_write
5426 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5427 .expect("Failed to perform OAuth2 permit");
5428
5429 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5431 code: permit_success.code,
5432 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5433 code_verifier: Some(pkce_secret.to_verifier()),
5434 }
5435 .into();
5436
5437 let token_response = idms_prox_write
5438 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5439 .expect("Failed to perform OAuth2 token exchange");
5440
5441 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
5443
5444 let id_token = token_response.id_token.expect("No id_token in response!");
5445
5446 let access_token =
5447 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5448
5449 let refresh_token = token_response
5450 .refresh_token
5451 .as_ref()
5452 .expect("no refresh token was issued")
5453 .clone();
5454
5455 assert!(idms_prox_write.commit().is_ok());
5457
5458 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5459
5460 let mut jwkset = idms_prox_read
5461 .oauth2_openid_publickey("test_resource_server")
5462 .expect("Failed to get public key");
5463
5464 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5465
5466 let jws_validator =
5467 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5468
5469 let oidc_unverified =
5470 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5471
5472 let iat = ct.as_secs() as i64;
5473
5474 let oidc = jws_validator
5475 .verify(&oidc_unverified)
5476 .unwrap()
5477 .verify_exp(iat)
5478 .expect("Failed to verify oidc");
5479
5480 assert!(
5482 oidc.iss
5483 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5484 .unwrap()
5485 );
5486 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
5487 assert_eq!(oidc.aud, "test_resource_server");
5488 assert_eq!(oidc.iat, iat);
5489 assert_eq!(oidc.nbf, Some(iat));
5490 assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
5492 assert!(oidc.auth_time.is_none());
5493 assert_eq!(oidc.nonce, Some("abcdef".to_string()));
5495 assert!(oidc.at_hash.is_none());
5496 assert!(oidc.acr.is_none());
5497 assert!(oidc.amr.is_none());
5498 assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
5499 assert!(oidc.jti.is_some());
5500 if let Some(jti) = &oidc.jti {
5501 assert!(Uuid::from_str(jti).is_ok());
5502 }
5503 assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
5504 assert_eq!(
5505 oidc.s_claims.preferred_username,
5506 Some("testperson1@example.com".to_string())
5507 );
5508 assert!(
5509 oidc.s_claims.scopes == vec![OAUTH2_SCOPE_OPENID.to_string(), "supplement".to_string()]
5510 );
5511 assert!(oidc.claims.is_empty());
5512 let userinfo = idms_prox_read
5515 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5516 .expect("failed to get userinfo");
5517
5518 assert_eq!(oidc.iss, userinfo.iss);
5519 assert_eq!(oidc.sub, userinfo.sub);
5520 assert_eq!(oidc.aud, userinfo.aud);
5521 assert_eq!(oidc.iat, userinfo.iat);
5522 assert_eq!(oidc.nbf, userinfo.nbf);
5523 assert_eq!(oidc.exp, userinfo.exp);
5524 assert!(userinfo.auth_time.is_none());
5525 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5526 assert!(userinfo.at_hash.is_none());
5527 assert!(userinfo.acr.is_none());
5528 assert_eq!(oidc.amr, userinfo.amr);
5529 assert_eq!(oidc.azp, userinfo.azp);
5530 assert!(userinfo.jti.is_some());
5531 if let Some(jti) = &userinfo.jti {
5532 assert!(Uuid::from_str(jti).is_ok());
5533 }
5534 assert_eq!(oidc.s_claims, userinfo.s_claims);
5535 assert!(userinfo.claims.is_empty());
5536
5537 drop(idms_prox_read);
5538
5539 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5543
5544 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
5545 refresh_token,
5546 scope: None,
5547 }
5548 .into();
5549
5550 let token_response = idms_prox_write
5551 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5552 .expect("Unable to exchange for OAuth2 token");
5553
5554 let access_token =
5555 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5556
5557 assert!(idms_prox_write.commit().is_ok());
5558
5559 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5561
5562 let userinfo = idms_prox_read
5563 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5564 .expect("failed to get userinfo");
5565
5566 assert_eq!(oidc.iss, userinfo.iss);
5567 assert_eq!(oidc.sub, userinfo.sub);
5568 assert_eq!(oidc.aud, userinfo.aud);
5569 assert_eq!(oidc.iat, userinfo.iat);
5570 assert_eq!(oidc.nbf, userinfo.nbf);
5571 assert_eq!(oidc.exp, userinfo.exp);
5572 assert!(userinfo.auth_time.is_none());
5573 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5574 assert!(userinfo.at_hash.is_none());
5575 assert!(userinfo.acr.is_none());
5576 assert_eq!(oidc.amr, userinfo.amr);
5577 assert_eq!(oidc.azp, userinfo.azp);
5578 assert!(userinfo.jti.is_some());
5579 if let Some(jti) = &userinfo.jti {
5580 assert!(Uuid::from_str(jti).is_ok());
5581 }
5582 assert_eq!(oidc.s_claims, userinfo.s_claims);
5583 assert!(userinfo.claims.is_empty());
5584 }
5585
5586 #[idm_test]
5587 async fn test_idm_oauth2_openid_short_username(
5588 idms: &IdmServer,
5589 _idms_delayed: &mut IdmServerDelayed,
5590 ) {
5591 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5594 let (secret, _uat, ident, _) =
5595 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5596 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5597
5598 let idms_prox_read = idms.proxy_read().await.unwrap();
5599
5600 let pkce_secret = PkceS256Secret::default();
5601
5602 let consent_request = good_authorisation_request!(
5603 idms_prox_read,
5604 &ident,
5605 ct,
5606 pkce_secret.to_request(),
5607 OAUTH2_SCOPE_OPENID.to_string()
5608 );
5609
5610 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5611 unreachable!();
5612 };
5613
5614 drop(idms_prox_read);
5616 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5617
5618 let permit_success = idms_prox_write
5619 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5620 .expect("Failed to perform OAuth2 permit");
5621
5622 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5624 code: permit_success.code,
5625 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5626 code_verifier: Some(pkce_secret.to_verifier()),
5627 }
5628 .into();
5629
5630 let token_response = idms_prox_write
5631 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5632 .expect("Failed to perform OAuth2 token exchange");
5633
5634 let id_token = token_response.id_token.expect("No id_token in response!");
5635 let access_token =
5636 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5637
5638 assert!(idms_prox_write.commit().is_ok());
5639 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5640
5641 let mut jwkset = idms_prox_read
5642 .oauth2_openid_publickey("test_resource_server")
5643 .expect("Failed to get public key");
5644 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5645
5646 let jws_validator =
5647 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5648
5649 let oidc_unverified =
5650 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5651
5652 let iat = ct.as_secs() as i64;
5653
5654 let oidc = jws_validator
5655 .verify(&oidc_unverified)
5656 .unwrap()
5657 .verify_exp(iat)
5658 .expect("Failed to verify oidc");
5659
5660 assert_eq!(
5662 oidc.s_claims.preferred_username,
5663 Some("testperson1".to_string())
5664 );
5665 let userinfo = idms_prox_read
5667 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5668 .expect("failed to get userinfo");
5669
5670 assert_eq!(oidc.s_claims, userinfo.s_claims);
5671 }
5672
5673 #[idm_test]
5674 async fn test_idm_oauth2_openid_group_claims(
5675 idms: &IdmServer,
5676 _idms_delayed: &mut IdmServerDelayed,
5677 ) {
5678 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5681 let (secret, _uat, ident, _) =
5682 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5683
5684 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5685
5686 let token_response = perform_oauth2_exchange(
5687 idms,
5688 &ident,
5689 ct,
5690 client_authz,
5691 format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_GROUPS}"),
5692 )
5693 .await;
5694
5695 let id_token = token_response.id_token.expect("No id_token in response!");
5696 let access_token =
5697 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5698
5699 let oidc = validate_id_token(idms, ct, &id_token).await;
5700
5701 assert!(oidc.claims.contains_key("groups"));
5703
5704 assert!(oidc
5705 .claims
5706 .get("groups")
5707 .expect("unable to find key")
5708 .as_array()
5709 .unwrap()
5710 .contains(&serde_json::json!(STR_UUID_IDM_ALL_ACCOUNTS)));
5711
5712 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5713
5714 let userinfo = idms_prox_read
5716 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5717 .expect("failed to get userinfo");
5718
5719 assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
5721 }
5722
5723 #[idm_test]
5724 async fn test_idm_oauth2_openid_group_extended_claims(
5725 idms: &IdmServer,
5726 _idms_delayed: &mut IdmServerDelayed,
5727 ) {
5728 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5731 let (secret, _uat, ident, oauth2_client_uuid) =
5732 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5733
5734 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5736
5737 let modlist = ModifyList::new_list(vec![
5738 Modify::Removed(
5739 Attribute::OAuth2RsScopeMap,
5740 PartialValue::Refer(UUID_TESTGROUP),
5741 ),
5742 Modify::Present(
5743 Attribute::OAuth2RsScopeMap,
5744 Value::new_oauthscopemap(
5745 UUID_TESTGROUP,
5746 btreeset![OAUTH2_SCOPE_GROUPS_NAME.to_string()],
5747 )
5748 .expect("invalid oauthscope"),
5749 ),
5750 ]);
5751
5752 idms_prox_write
5753 .qs_write
5754 .internal_modify_uuid(oauth2_client_uuid, &modlist)
5755 .expect("Failed to modify scopes");
5756
5757 idms_prox_write.commit().expect("failed to commit");
5758
5759 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5761
5762 let token_response = perform_oauth2_exchange(
5763 idms,
5764 &ident,
5765 ct,
5766 client_authz,
5767 format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_GROUPS_NAME}"),
5768 )
5769 .await;
5770
5771 let id_token = token_response.id_token.expect("No id_token in response!");
5772 let access_token =
5773 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5774
5775 let oidc = validate_id_token(idms, ct, &id_token).await;
5776
5777 assert!(oidc.claims.contains_key("groups"));
5779
5780 assert!(oidc
5781 .claims
5782 .get("groups")
5783 .expect("unable to find key")
5784 .as_array()
5785 .unwrap()
5786 .contains(&serde_json::json!("testgroup")));
5787
5788 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5789
5790 let userinfo = idms_prox_read
5792 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5793 .expect("failed to get userinfo");
5794
5795 assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
5797 }
5798
5799 #[idm_test]
5800 async fn test_idm_oauth2_openid_ssh_publickey_claim(
5801 idms: &IdmServer,
5802 _idms_delayed: &mut IdmServerDelayed,
5803 ) {
5804 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5805 let (secret, _uat, ident, client_uuid) =
5806 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5807
5808 const ECDSA_SSH_PUBLIC_KEY: &str = "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3BtOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81lzPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@amethyst";
5811 let ssh_pubkey = SshPublicKey::from_string(ECDSA_SSH_PUBLIC_KEY).unwrap();
5812
5813 let scope_set = BTreeSet::from([OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string()]);
5814
5815 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5816
5817 idms_prox_write
5818 .qs_write
5819 .internal_batch_modify(
5820 [
5821 (
5822 UUID_TESTPERSON_1,
5823 ModifyList::new_set(
5824 Attribute::SshPublicKey,
5825 ValueSetSshKey::new("label".to_string(), ssh_pubkey),
5826 ),
5827 ),
5828 (
5829 client_uuid,
5830 ModifyList::new_set(
5831 Attribute::OAuth2RsSupScopeMap,
5832 ValueSetOauthScopeMap::new(UUID_IDM_ALL_ACCOUNTS, scope_set),
5833 ),
5834 ),
5835 ]
5836 .into_iter(),
5837 )
5838 .expect("Failed to modify test entries");
5839
5840 assert!(idms_prox_write.commit().is_ok());
5841
5842 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5843
5844 let idms_prox_read = idms.proxy_read().await.unwrap();
5845
5846 let pkce_secret = PkceS256Secret::default();
5847
5848 let consent_request = good_authorisation_request!(
5849 idms_prox_read,
5850 &ident,
5851 ct,
5852 pkce_secret.to_request(),
5853 "openid groups".to_string()
5854 );
5855
5856 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5857 unreachable!();
5858 };
5859
5860 drop(idms_prox_read);
5862 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5863
5864 let permit_success = idms_prox_write
5865 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5866 .expect("Failed to perform OAuth2 permit");
5867
5868 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5870 code: permit_success.code,
5871 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5872 code_verifier: Some(pkce_secret.to_verifier()),
5873 }
5874 .into();
5875
5876 let token_response = idms_prox_write
5877 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5878 .expect("Failed to perform OAuth2 token exchange");
5879
5880 let id_token = token_response.id_token.expect("No id_token in response!");
5881 let access_token =
5882 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5883
5884 assert!(idms_prox_write.commit().is_ok());
5885 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5886
5887 let mut jwkset = idms_prox_read
5888 .oauth2_openid_publickey("test_resource_server")
5889 .expect("Failed to get public key");
5890 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5891
5892 let jws_validator =
5893 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5894
5895 let oidc_unverified =
5896 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5897
5898 let iat = ct.as_secs() as i64;
5899
5900 let oidc = jws_validator
5901 .verify(&oidc_unverified)
5902 .unwrap()
5903 .verify_exp(iat)
5904 .expect("Failed to verify oidc");
5905
5906 assert!(oidc.claims.contains_key(OAUTH2_SCOPE_SSH_PUBLICKEYS));
5908
5909 assert!(oidc
5910 .claims
5911 .get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
5912 .expect("unable to find key")
5913 .as_array()
5914 .unwrap()
5915 .contains(&serde_json::json!(ECDSA_SSH_PUBLIC_KEY)));
5916
5917 let userinfo = idms_prox_read
5919 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5920 .expect("failed to get userinfo");
5921
5922 assert_eq!(
5924 oidc.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS),
5925 userinfo.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
5926 );
5927 }
5928
5929 #[idm_test]
5931 async fn test_idm_oauth2_insecure_pkce(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
5932 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5933 let (_secret, _uat, ident, _) =
5934 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
5935
5936 let idms_prox_read = idms.proxy_read().await.unwrap();
5937
5938 let pkce_secret = PkceS256Secret::default();
5940
5941 let _consent_request = good_authorisation_request!(
5943 idms_prox_read,
5944 &ident,
5945 ct,
5946 pkce_secret.to_request(),
5947 OAUTH2_SCOPE_OPENID.to_string()
5948 );
5949
5950 let auth_req = AuthorisationRequest {
5952 response_type: ResponseType::Code,
5953 response_mode: None,
5954 client_id: "test_resource_server".to_string(),
5955 state: Some("123".to_string()),
5956 pkce_request: None,
5957 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5958 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
5959 nonce: Some("abcdef".to_string()),
5960 oidc_ext: Default::default(),
5961 max_age: None,
5962 unknown_keys: Default::default(),
5963 };
5964
5965 idms_prox_read
5966 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
5967 .expect("Oauth2 authorisation failed");
5968 }
5969
5970 #[idm_test]
5971 async fn test_idm_oauth2_webfinger(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
5972 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5973 let (_secret, _uat, _ident, _) =
5974 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5975 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5976
5977 let user = "testperson1@example.com";
5978
5979 let webfinger = idms_prox_read
5980 .oauth2_openid_webfinger("test_resource_server", user)
5981 .expect("Failed to get webfinger");
5982
5983 assert_eq!(webfinger.subject, user);
5984 assert_eq!(webfinger.links.len(), 1);
5985
5986 let link = &webfinger.links[0];
5987 assert_eq!(link.rel, "http://openid.net/specs/connect/1.0/issuer");
5988 assert_eq!(
5989 link.href,
5990 "https://idm.example.com/oauth2/openid/test_resource_server"
5991 );
5992
5993 let failed_webfinger = idms_prox_read
5994 .oauth2_openid_webfinger("test_resource_server", "someone@another.domain");
5995 assert!(failed_webfinger.is_err());
5996 }
5997
5998 #[idm_test]
5999 async fn test_idm_oauth2_openid_legacy_crypto(
6000 idms: &IdmServer,
6001 _idms_delayed: &mut IdmServerDelayed,
6002 ) {
6003 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6004 let (secret, _uat, ident, _) =
6005 setup_oauth2_resource_server_basic(idms, ct, false, true, false).await;
6006 let idms_prox_read = idms.proxy_read().await.unwrap();
6007 let discovery = idms_prox_read
6010 .oauth2_openid_discovery("test_resource_server")
6011 .expect("Failed to get discovery");
6012
6013 let mut jwkset = idms_prox_read
6014 .oauth2_openid_publickey("test_resource_server")
6015 .expect("Failed to get public key");
6016
6017 let jwk = jwkset.keys.pop().expect("no such jwk");
6018 let public_jwk = jwk.clone();
6019
6020 match jwk {
6021 Jwk::RSA { alg, use_, kid, .. } => {
6022 match (
6023 alg.unwrap(),
6024 &discovery.id_token_signing_alg_values_supported[0],
6025 ) {
6026 (JwaAlg::RS256, IdTokenSignAlg::RS256) => {}
6027 _ => panic!(),
6028 };
6029 assert_eq!(use_.unwrap(), JwkUse::Sig);
6030 assert!(kid.is_some());
6031 }
6032 _ => panic!(),
6033 };
6034
6035 let pkce_secret = PkceS256Secret::default();
6037
6038 let consent_request = good_authorisation_request!(
6039 idms_prox_read,
6040 &ident,
6041 ct,
6042 pkce_secret.to_request(),
6043 OAUTH2_SCOPE_OPENID.to_string()
6044 );
6045
6046 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6047 unreachable!();
6048 };
6049
6050 drop(idms_prox_read);
6052 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6053
6054 let permit_success = idms_prox_write
6055 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6056 .expect("Failed to perform OAuth2 permit");
6057
6058 let token_req = AccessTokenRequest {
6060 grant_type: GrantTypeReq::AuthorizationCode {
6061 code: permit_success.code,
6062 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6063 code_verifier: Some(pkce_secret.to_verifier()),
6064 },
6065
6066 client_post_auth: ClientPostAuth {
6067 client_id: Some("test_resource_server".to_string()),
6068 client_secret: Some(secret),
6069 },
6070 };
6071
6072 let token_response = idms_prox_write
6073 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
6074 .expect("Failed to perform OAuth2 token exchange");
6075
6076 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
6078 let id_token = token_response.id_token.expect("No id_token in response!");
6079
6080 let jws_validator =
6081 JwsRs256Verifier::try_from(&public_jwk).expect("failed to build validator");
6082
6083 let oidc_unverified =
6084 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
6085
6086 let iat = ct.as_secs() as i64;
6087
6088 let oidc = jws_validator
6089 .verify(&oidc_unverified)
6090 .unwrap()
6091 .verify_exp(iat)
6092 .expect("Failed to verify oidc");
6093
6094 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
6095
6096 assert!(idms_prox_write.commit().is_ok());
6097 }
6098
6099 #[idm_test]
6100 async fn test_idm_oauth2_consent_granted_and_changed_workflow(
6101 idms: &IdmServer,
6102 _idms_delayed: &mut IdmServerDelayed,
6103 ) {
6104 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6105 let (_secret, uat, ident, _) =
6106 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6107
6108 let idms_prox_read = idms.proxy_read().await.unwrap();
6109
6110 let pkce_secret = PkceS256Secret::default();
6111
6112 let consent_request = good_authorisation_request!(
6113 idms_prox_read,
6114 &ident,
6115 ct,
6116 pkce_secret.to_request(),
6117 OAUTH2_SCOPE_OPENID.to_string()
6118 );
6119
6120 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6122 unreachable!();
6123 };
6124
6125 drop(idms_prox_read);
6127 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6128
6129 let _permit_success = idms_prox_write
6130 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6131 .expect("Failed to perform OAuth2 permit");
6132
6133 assert!(idms_prox_write.commit().is_ok());
6134
6135 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6137
6138 let ident = idms_prox_read
6140 .process_uat_to_identity(&uat, ct, Source::Internal)
6141 .expect("Unable to process uat");
6142
6143 let pkce_secret = PkceS256Secret::default();
6144
6145 let consent_request = good_authorisation_request!(
6146 idms_prox_read,
6147 &ident,
6148 ct,
6149 pkce_secret.to_request(),
6150 OAUTH2_SCOPE_OPENID.to_string()
6151 );
6152
6153 let AuthoriseResponse::Permitted(_permit_success) = consent_request else {
6155 unreachable!();
6156 };
6157
6158 drop(idms_prox_read);
6159
6160 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6162
6163 let me_extend_scopes = ModifyEvent::new_internal_invalid(
6164 filter!(f_eq(
6165 Attribute::Name,
6166 PartialValue::new_iname("test_resource_server")
6167 )),
6168 ModifyList::new_list(vec![Modify::Present(
6169 Attribute::OAuth2RsScopeMap,
6170 Value::new_oauthscopemap(
6171 UUID_IDM_ALL_ACCOUNTS,
6172 btreeset![
6173 OAUTH2_SCOPE_EMAIL.to_string(),
6174 OAUTH2_SCOPE_OPENID.to_string()
6175 ],
6176 )
6177 .expect("invalid oauthscope"),
6178 )]),
6179 );
6180
6181 assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
6182 assert!(idms_prox_write.commit().is_ok());
6183
6184 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6187
6188 let ident = idms_prox_read
6190 .process_uat_to_identity(&uat, ct, Source::Internal)
6191 .expect("Unable to process uat");
6192
6193 let pkce_secret = PkceS256Secret::default();
6194
6195 let auth_req = AuthorisationRequest {
6196 response_type: ResponseType::Code,
6197 response_mode: None,
6198 client_id: "test_resource_server".to_string(),
6199 state: Some("123".to_string()),
6200 pkce_request: Some(pkce_secret.to_request()),
6201 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6202 scope: btreeset!["openid".to_string(), "email".to_string()],
6203 nonce: Some("abcdef".to_string()),
6204 oidc_ext: Default::default(),
6205 max_age: None,
6206 unknown_keys: Default::default(),
6207 };
6208
6209 let consent_request = idms_prox_read
6210 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6211 .expect("Oauth2 authorisation failed");
6212
6213 let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
6215 unreachable!();
6216 };
6217
6218 drop(idms_prox_read);
6219
6220 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6224
6225 let me_extend_scopes = ModifyEvent::new_internal_invalid(
6226 filter!(f_eq(
6227 Attribute::Name,
6228 PartialValue::new_iname("test_resource_server")
6229 )),
6230 ModifyList::new_list(vec![Modify::Present(
6231 Attribute::OAuth2RsSupScopeMap,
6232 Value::new_oauthscopemap(UUID_IDM_ALL_ACCOUNTS, btreeset!["newscope".to_string()])
6233 .expect("invalid oauthscope"),
6234 )]),
6235 );
6236
6237 assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
6238 assert!(idms_prox_write.commit().is_ok());
6239
6240 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6243
6244 let ident = idms_prox_read
6246 .process_uat_to_identity(&uat, ct, Source::Internal)
6247 .expect("Unable to process uat");
6248
6249 let pkce_secret = PkceS256Secret::default();
6250
6251 let auth_req = AuthorisationRequest {
6252 response_type: ResponseType::Code,
6253 response_mode: None,
6254 client_id: "test_resource_server".to_string(),
6255 state: Some("123".to_string()),
6256 pkce_request: Some(pkce_secret.to_request()),
6257 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6258 scope: btreeset!["openid".to_string(), "email".to_string()],
6260 nonce: Some("abcdef".to_string()),
6261 oidc_ext: Default::default(),
6262 max_age: None,
6263 unknown_keys: Default::default(),
6264 };
6265
6266 let consent_request = idms_prox_read
6267 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6268 .expect("Oauth2 authorisation failed");
6269
6270 let _consent_token = if let AuthoriseResponse::ConsentRequested {
6272 consent_token,
6273 scopes,
6274 ..
6275 } = consent_request
6276 {
6277 assert!(scopes.contains("newscope"));
6278 consent_token
6279 } else {
6280 unreachable!();
6281 };
6282 }
6283
6284 #[idm_test]
6285 async fn test_idm_oauth2_consent_granted_refint_cleanup_on_delete(
6286 idms: &IdmServer,
6287 _idms_delayed: &mut IdmServerDelayed,
6288 ) {
6289 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6290 let (_secret, uat, ident, o2rs_uuid) =
6291 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6292
6293 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
6295
6296 let idms_prox_read = idms.proxy_read().await.unwrap();
6297
6298 let pkce_secret = PkceS256Secret::default();
6299 let consent_request = good_authorisation_request!(
6300 idms_prox_read,
6301 &ident,
6302 ct,
6303 pkce_secret.to_request(),
6304 OAUTH2_SCOPE_OPENID.to_string()
6305 );
6306
6307 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6309 unreachable!();
6310 };
6311
6312 drop(idms_prox_read);
6314 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6315
6316 let _permit_success = idms_prox_write
6317 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6318 .expect("Failed to perform OAuth2 permit");
6319
6320 let ident = idms_prox_write
6321 .process_uat_to_identity(&uat, ct, Source::Internal)
6322 .expect("Unable to process uat");
6323
6324 assert!(
6326 ident.get_oauth2_consent_scopes(o2rs_uuid)
6327 == Some(&btreeset![
6328 OAUTH2_SCOPE_OPENID.to_string(),
6329 "supplement".to_string()
6330 ])
6331 );
6332
6333 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
6335 Attribute::Name,
6336 PartialValue::new_iname("test_resource_server")
6337 )));
6338
6339 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
6340 let ident = idms_prox_write
6342 .process_uat_to_identity(&uat, ct, Source::Internal)
6343 .expect("Unable to process uat");
6344 dbg!(&o2rs_uuid);
6345 dbg!(&ident);
6346 let consent_scopes = ident.get_oauth2_consent_scopes(o2rs_uuid);
6347 dbg!(consent_scopes);
6348 assert!(consent_scopes.is_none());
6349
6350 assert!(idms_prox_write.commit().is_ok());
6351 }
6352
6353 #[idm_test]
6373 async fn test_idm_oauth2_1076_pkce_downgrade(
6374 idms: &IdmServer,
6375 _idms_delayed: &mut IdmServerDelayed,
6376 ) {
6377 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6378 let (secret, _uat, ident, _) =
6380 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6381
6382 let idms_prox_read = idms.proxy_read().await.unwrap();
6383
6384 let pkce_secret = PkceS256Secret::default();
6389
6390 let auth_req = AuthorisationRequest {
6392 response_type: ResponseType::Code,
6393 response_mode: None,
6394 client_id: "test_resource_server".to_string(),
6395 state: Some("123".to_string()),
6396 pkce_request: None,
6397 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6398 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6399 nonce: None,
6400 oidc_ext: Default::default(),
6401 max_age: None,
6402 unknown_keys: Default::default(),
6403 };
6404
6405 let consent_request = idms_prox_read
6406 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6407 .expect("Failed to perform OAuth2 authorisation request.");
6408
6409 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6411 unreachable!();
6412 };
6413
6414 drop(idms_prox_read);
6416 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6417
6418 let permit_success = idms_prox_write
6419 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6420 .expect("Failed to perform OAuth2 permit");
6421
6422 let token_req = AccessTokenRequest {
6426 grant_type: GrantTypeReq::AuthorizationCode {
6427 code: permit_success.code,
6428 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6429 code_verifier: Some(pkce_secret.to_verifier()),
6430 },
6431 client_post_auth: ClientPostAuth {
6432 client_id: Some("test_resource_server".to_string()),
6433 client_secret: Some(secret),
6434 },
6435 };
6436
6437 assert!(matches!(
6439 idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6440 Err(Oauth2Error::InvalidRequest)
6441 ));
6442
6443 assert!(idms_prox_write.commit().is_ok());
6444 }
6445
6446 #[idm_test]
6447 async fn test_idm_oauth2_redir_http_downgrade(
6451 idms: &IdmServer,
6452 _idms_delayed: &mut IdmServerDelayed,
6453 ) {
6454 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6455 let (secret, _uat, ident, _) =
6457 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6458
6459 let idms_prox_read = idms.proxy_read().await.unwrap();
6460
6461 let pkce_secret = PkceS256Secret::default();
6466
6467 let auth_req = AuthorisationRequest {
6469 response_type: ResponseType::Code,
6470 response_mode: None,
6471 client_id: "test_resource_server".to_string(),
6472 state: Some("123".to_string()),
6473 pkce_request: Some(pkce_secret.to_request()),
6474 redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6475 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6476 nonce: None,
6477 oidc_ext: Default::default(),
6478 max_age: None,
6479 unknown_keys: Default::default(),
6480 };
6481
6482 assert!(
6483 idms_prox_read
6484 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6485 .unwrap_err()
6486 == Oauth2Error::InvalidOrigin
6487 );
6488
6489 let consent_request = good_authorisation_request!(
6491 idms_prox_read,
6492 &ident,
6493 ct,
6494 pkce_secret.to_request(),
6495 OAUTH2_SCOPE_OPENID.to_string()
6496 );
6497
6498 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6500 unreachable!();
6501 };
6502
6503 drop(idms_prox_read);
6505 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6506
6507 let permit_success = idms_prox_write
6508 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6509 .expect("Failed to perform OAuth2 permit");
6510
6511 let token_req = AccessTokenRequest {
6514 grant_type: GrantTypeReq::AuthorizationCode {
6515 code: permit_success.code,
6516 redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6517 code_verifier: Some(pkce_secret.to_verifier()),
6518 },
6519
6520 client_post_auth: ClientPostAuth {
6521 client_id: Some("test_resource_server".to_string()),
6522 client_secret: Some(secret),
6523 },
6524 };
6525
6526 assert!(matches!(
6528 idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6529 Err(Oauth2Error::InvalidOrigin)
6530 ));
6531
6532 assert!(idms_prox_write.commit().is_ok());
6533 }
6534
6535 async fn setup_refresh_token(
6536 idms: &IdmServer,
6537 _idms_delayed: &mut IdmServerDelayed,
6538 ct: Duration,
6539 ) -> (AccessTokenResponse, ClientAuthInfo) {
6540 let (secret, _uat, ident, _) =
6542 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6543 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6544
6545 let idms_prox_read = idms.proxy_read().await.unwrap();
6546
6547 let pkce_secret = PkceS256Secret::default();
6549
6550 let consent_request = good_authorisation_request!(
6551 idms_prox_read,
6552 &ident,
6553 ct,
6554 pkce_secret.to_request(),
6555 OAUTH2_SCOPE_OPENID.to_string()
6556 );
6557
6558 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6559 unreachable!();
6560 };
6561
6562 drop(idms_prox_read);
6564 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6565
6566 let permit_success = idms_prox_write
6567 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6568 .expect("Failed to perform OAuth2 permit");
6569
6570 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
6571 code: permit_success.code,
6572 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6573 code_verifier: Some(pkce_secret.to_verifier()),
6574 }
6575 .into();
6576 let access_token_response_1 = idms_prox_write
6577 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6578 .expect("Unable to exchange for OAuth2 token");
6579
6580 assert!(idms_prox_write.commit().is_ok());
6581
6582 trace!(?access_token_response_1);
6583
6584 (access_token_response_1, client_authz)
6585 }
6586
6587 #[idm_test]
6588 async fn test_idm_oauth2_refresh_token_basic(
6589 idms: &IdmServer,
6590 idms_delayed: &mut IdmServerDelayed,
6591 ) {
6592 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6594
6595 let (access_token_response_1, client_authz) =
6596 setup_refresh_token(idms, idms_delayed, ct).await;
6597
6598 let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
6602 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6603
6604 let refresh_token = access_token_response_1
6605 .refresh_token
6606 .as_ref()
6607 .expect("no refresh token was issued")
6608 .clone();
6609
6610 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6611 refresh_token,
6612 scope: None,
6613 }
6614 .into();
6615
6616 let access_token_response_2 = idms_prox_write
6617 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6618 .expect("Unable to exchange for OAuth2 token");
6619
6620 assert!(idms_prox_write.commit().is_ok());
6621
6622 trace!(?access_token_response_2);
6623
6624 assert!(access_token_response_1.access_token != access_token_response_2.access_token);
6625 assert!(access_token_response_1.refresh_token != access_token_response_2.refresh_token);
6626 assert!(access_token_response_1.id_token != access_token_response_2.id_token);
6627
6628 let ct =
6631 Duration::from_secs(TEST_CURRENT_TIME + 20 + access_token_response_2.expires_in as u64);
6632
6633 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6634
6635 let refresh_token = access_token_response_2
6636 .refresh_token
6637 .as_ref()
6638 .expect("no refresh token was issued")
6639 .clone();
6640
6641 let reflected_token = idms_prox_write
6643 .reflect_oauth2_token(&client_authz, &refresh_token)
6644 .expect("Failed to access internals of the refresh token");
6645
6646 let refresh_exp = match reflected_token {
6647 Oauth2TokenType::Refresh { exp, .. } => exp,
6648 Oauth2TokenType::ClientAccess { .. } => unreachable!(),
6650 };
6651
6652 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6653 refresh_token,
6654 scope: None,
6655 }
6656 .into();
6657
6658 let access_token_response_3 = idms_prox_write
6659 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6660 .expect("Unable to exchange for OAuth2 token");
6661
6662 let entry = idms_prox_write
6665 .qs_write
6666 .internal_search_uuid(UUID_TESTPERSON_1)
6667 .expect("failed");
6668 let session = entry
6669 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
6670 .and_then(|sessions| sessions.first_key_value())
6671 .unwrap();
6673
6674 trace!(?session);
6675 assert_eq!(
6677 SessionState::ExpiresAt(
6678 time::OffsetDateTime::UNIX_EPOCH
6679 + ct
6680 + Duration::from_secs(OAUTH_REFRESH_TOKEN_EXPIRY)
6681 ),
6682 session.1.state
6683 );
6684
6685 assert!(idms_prox_write.commit().is_ok());
6686
6687 trace!(?access_token_response_3);
6688
6689 assert!(access_token_response_3.access_token != access_token_response_2.access_token);
6690 assert!(access_token_response_3.refresh_token != access_token_response_2.refresh_token);
6691 assert!(access_token_response_3.id_token != access_token_response_2.id_token);
6692
6693 let ct = Duration::from_secs(
6699 TEST_CURRENT_TIME + refresh_exp as u64 + access_token_response_3.expires_in as u64,
6700 );
6701
6702 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6703
6704 let refresh_token = access_token_response_3
6705 .refresh_token
6706 .as_ref()
6707 .expect("no refresh token was issued")
6708 .clone();
6709
6710 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6711 refresh_token,
6712 scope: None,
6713 }
6714 .into();
6715 let access_token_response_4 = idms_prox_write
6716 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6717 .unwrap_err();
6718
6719 assert_eq!(access_token_response_4, Oauth2Error::InvalidGrant);
6720
6721 assert!(idms_prox_write.commit().is_ok());
6722 }
6723
6724 #[idm_test]
6726 async fn test_idm_oauth2_refresh_token_oauth2_session_expired(
6727 idms: &IdmServer,
6728 idms_delayed: &mut IdmServerDelayed,
6729 ) {
6730 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6732
6733 let (access_token_response_1, client_authz) =
6734 setup_refresh_token(idms, idms_delayed, ct).await;
6735
6736 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6740 let revoke_request = TokenRevokeRequest {
6741 token: access_token_response_1.access_token.clone(),
6742 token_type_hint: None,
6743 client_post_auth: ClientPostAuth::default(),
6744 };
6745 assert!(idms_prox_write
6746 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
6747 .is_ok());
6748 assert!(idms_prox_write.commit().is_ok());
6749
6750 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6753
6754 let refresh_token = access_token_response_1
6755 .refresh_token
6756 .as_ref()
6757 .expect("no refresh token was issued")
6758 .clone();
6759
6760 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6761 refresh_token,
6762 scope: None,
6763 }
6764 .into();
6765 let access_token_response_2 = idms_prox_write
6766 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6767 .unwrap_err();
6769
6770 assert_eq!(access_token_response_2, Oauth2Error::InvalidGrant);
6771
6772 assert!(idms_prox_write.commit().is_ok());
6773 }
6774
6775 #[idm_test]
6777 async fn test_idm_oauth2_refresh_token_invalid_client_authz(
6778 idms: &IdmServer,
6779 idms_delayed: &mut IdmServerDelayed,
6780 ) {
6781 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6783
6784 let (access_token_response_1, _client_authz) =
6785 setup_refresh_token(idms, idms_delayed, ct).await;
6786
6787 let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
6788
6789 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6793
6794 let refresh_token = access_token_response_1
6795 .refresh_token
6796 .as_ref()
6797 .expect("no refresh token was issued")
6798 .clone();
6799
6800 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6801 refresh_token,
6802 scope: None,
6803 }
6804 .into();
6805 let access_token_response_2 = idms_prox_write
6806 .check_oauth2_token_exchange(&bad_client_authz, &token_req, ct)
6807 .unwrap_err();
6808
6809 assert_eq!(access_token_response_2, Oauth2Error::AuthenticationRequired);
6810
6811 assert!(idms_prox_write.commit().is_ok());
6812 }
6813
6814 #[idm_test]
6816 async fn test_idm_oauth2_refresh_token_inconsistent_scopes(
6817 idms: &IdmServer,
6818 idms_delayed: &mut IdmServerDelayed,
6819 ) {
6820 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6822
6823 let (access_token_response_1, client_authz) =
6824 setup_refresh_token(idms, idms_delayed, ct).await;
6825
6826 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6830
6831 let refresh_token = access_token_response_1
6832 .refresh_token
6833 .as_ref()
6834 .expect("no refresh token was issued")
6835 .clone();
6836
6837 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6838 refresh_token,
6839 scope: Some(btreeset!["invalid_scope".to_string()]),
6840 }
6841 .into();
6842 let access_token_response_2 = idms_prox_write
6843 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6844 .unwrap_err();
6845
6846 assert_eq!(access_token_response_2, Oauth2Error::InvalidScope);
6847
6848 assert!(idms_prox_write.commit().is_ok());
6849 }
6850
6851 #[idm_test]
6855 async fn test_idm_oauth2_refresh_token_reuse_invalidates_session(
6856 idms: &IdmServer,
6857 idms_delayed: &mut IdmServerDelayed,
6858 ) {
6859 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6861
6862 let (access_token_response_1, client_authz) =
6863 setup_refresh_token(idms, idms_delayed, ct).await;
6864
6865 let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
6868 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6869
6870 let refresh_token = access_token_response_1
6871 .refresh_token
6872 .as_ref()
6873 .expect("no refresh token was issued")
6874 .clone();
6875
6876 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6877 refresh_token,
6878 scope: None,
6879 }
6880 .into();
6881
6882 let _access_token_response_2 = idms_prox_write
6883 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6884 .expect("Unable to exchange for OAuth2 token");
6885
6886 assert!(idms_prox_write.commit().is_ok());
6887
6888 let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
6890 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6891
6892 let refresh_token = access_token_response_1
6893 .refresh_token
6894 .as_ref()
6895 .expect("no refresh token was issued")
6896 .clone();
6897
6898 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6899 refresh_token,
6900 scope: None,
6901 }
6902 .into();
6903
6904 let access_token_response_3 = idms_prox_write
6905 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6906 .unwrap_err();
6907
6908 assert_eq!(access_token_response_3, Oauth2Error::InvalidGrant);
6909
6910 let entry = idms_prox_write
6911 .qs_write
6912 .internal_search_uuid(UUID_TESTPERSON_1)
6913 .expect("failed");
6914 let valid = entry
6915 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
6916 .and_then(|sessions| sessions.first_key_value())
6917 .map(|(_, session)| !matches!(session.state, SessionState::RevokedAt(_)))
6918 .unwrap();
6920 assert!(!valid);
6922
6923 assert!(idms_prox_write.commit().is_ok());
6924 }
6925
6926 #[idm_test]
6933 async fn test_idm_oauth2_refresh_token_divergence(
6934 idms: &IdmServer,
6935 idms_delayed: &mut IdmServerDelayed,
6936 ) {
6937 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6939
6940 let (access_token_response_1, client_authz) =
6941 setup_refresh_token(idms, idms_delayed, ct).await;
6942
6943 let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
6946 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6947
6948 let refresh_token = access_token_response_1
6949 .refresh_token
6950 .as_ref()
6951 .expect("no refresh token was issued")
6952 .clone();
6953
6954 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6955 refresh_token,
6956 scope: None,
6957 }
6958 .into();
6959
6960 let access_token_response_2 = idms_prox_write
6961 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6962 .expect("Unable to exchange for OAuth2 token");
6963
6964 drop(idms_prox_write);
6967
6968 let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
6970 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6971
6972 let refresh_token = access_token_response_2
6973 .refresh_token
6974 .as_ref()
6975 .expect("no refresh token was issued")
6976 .clone();
6977
6978 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6979 refresh_token,
6980 scope: None,
6981 }
6982 .into();
6983
6984 let _access_token_response_3 = idms_prox_write
6985 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6986 .expect("Unable to exchange for OAuth2 token");
6987
6988 assert!(idms_prox_write.commit().is_ok());
6989
6990 }
6992
6993 #[idm_test]
6994 async fn test_idm_oauth2_refresh_token_scope_constraints(
6995 idms: &IdmServer,
6996 idms_delayed: &mut IdmServerDelayed,
6997 ) {
6998 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7000
7001 let (access_token_response_1, client_authz) =
7002 setup_refresh_token(idms, idms_delayed, ct).await;
7003
7004 let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
7013 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7014
7015 let refresh_token = access_token_response_1
7016 .refresh_token
7017 .as_ref()
7018 .expect("no refresh token was issued")
7019 .clone();
7020
7021 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
7023
7024 let access_token_unverified = JwsCompact::from_str(&access_token_response_1.access_token)
7025 .expect("Invalid Access Token");
7026
7027 let reflected_token = jws_verifier
7028 .verify(&access_token_unverified)
7029 .unwrap()
7030 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7031 .expect("Failed to access internals of the refresh token");
7032
7033 trace!(?reflected_token);
7034 let initial_scopes = reflected_token.extensions.scope;
7035 trace!(?initial_scopes);
7036
7037 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7039 refresh_token,
7040 scope: None,
7041 }
7042 .into();
7043
7044 let access_token_response_2 = idms_prox_write
7045 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7046 .expect("Unable to exchange for OAuth2 token");
7047
7048 let access_token_unverified = JwsCompact::from_str(&access_token_response_2.access_token)
7049 .expect("Invalid Access Token");
7050
7051 let reflected_token = jws_verifier
7052 .verify(&access_token_unverified)
7053 .unwrap()
7054 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7055 .expect("Failed to access internals of the refresh token");
7056
7057 assert_eq!(initial_scopes, reflected_token.extensions.scope);
7058
7059 let refresh_token = access_token_response_2
7060 .refresh_token
7061 .as_ref()
7062 .expect("no refresh token was issued")
7063 .clone();
7064
7065 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7067 refresh_token,
7068 scope: Some(["openid".to_string()].into()),
7069 }
7070 .into();
7071
7072 let access_token_response_3 = idms_prox_write
7073 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7074 .expect("Unable to exchange for OAuth2 token");
7075
7076 let access_token_unverified = JwsCompact::from_str(&access_token_response_3.access_token)
7077 .expect("Invalid Access Token");
7078
7079 let reflected_token = jws_verifier
7080 .verify(&access_token_unverified)
7081 .unwrap()
7082 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7083 .expect("Failed to access internals of the refresh token");
7084
7085 assert_ne!(initial_scopes, reflected_token.extensions.scope);
7086
7087 let constrained_scopes = reflected_token.extensions.scope;
7089
7090 let refresh_token = access_token_response_3
7091 .refresh_token
7092 .as_ref()
7093 .expect("no refresh token was issued")
7094 .clone();
7095
7096 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7098 refresh_token,
7099 scope: None,
7100 }
7101 .into();
7102
7103 let access_token_response_4 = idms_prox_write
7104 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7105 .expect("Unable to exchange for OAuth2 token");
7106
7107 let access_token_unverified = JwsCompact::from_str(&access_token_response_4.access_token)
7108 .expect("Invalid Access Token");
7109
7110 let reflected_token = jws_verifier
7111 .verify(&access_token_unverified)
7112 .unwrap()
7113 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7114 .expect("Failed to access internals of the refresh token");
7115
7116 assert_ne!(initial_scopes, reflected_token.extensions.scope);
7117 assert_eq!(constrained_scopes, reflected_token.extensions.scope);
7118
7119 let refresh_token = access_token_response_4
7120 .refresh_token
7121 .as_ref()
7122 .expect("no refresh token was issued")
7123 .clone();
7124
7125 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7127 refresh_token,
7128 scope: Some(initial_scopes),
7129 }
7130 .into();
7131
7132 let access_token_response_5_err = idms_prox_write
7133 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7134 .unwrap_err();
7135
7136 assert_eq!(access_token_response_5_err, Oauth2Error::InvalidScope);
7137
7138 assert!(idms_prox_write.commit().is_ok());
7139 }
7140
7141 #[test]
7142 fn compliant_serialization_test() {
7145 let token_req: Result<AccessTokenRequest, serde_json::Error> = serde_json::from_str(
7146 r#"
7147 {
7148 "grant_type": "refresh_token",
7149 "refresh_token": "some_dumb_refresh_token",
7150 "scope": "invalid_scope vasd asd"
7151 }
7152 "#,
7153 );
7154 assert!(token_req.is_ok());
7155 }
7156
7157 #[idm_test]
7158 async fn test_idm_oauth2_custom_claims(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
7159 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7160 let (secret, _uat, ident, oauth2_rs_uuid) =
7161 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7162
7163 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7165
7166 let modlist = ModifyList::new_list(vec![
7167 Modify::Present(
7169 Attribute::OAuth2RsClaimMap,
7170 Value::OauthClaimMap(
7171 "custom_a".to_string(),
7172 OauthClaimMapJoin::CommaSeparatedValue,
7173 ),
7174 ),
7175 Modify::Present(
7176 Attribute::OAuth2RsClaimMap,
7177 Value::OauthClaimValue(
7178 "custom_a".to_string(),
7179 UUID_TESTGROUP,
7180 btreeset!["value_a".to_string()],
7181 ),
7182 ),
7183 Modify::Present(
7185 Attribute::OAuth2RsClaimMap,
7186 Value::OauthClaimValue(
7187 "custom_a".to_string(),
7188 UUID_IDM_ALL_ACCOUNTS,
7189 btreeset!["value_b".to_string()],
7190 ),
7191 ),
7192 Modify::Present(
7194 Attribute::OAuth2RsClaimMap,
7195 Value::OauthClaimMap(
7196 "custom_b".to_string(),
7197 OauthClaimMapJoin::SpaceSeparatedValue,
7198 ),
7199 ),
7200 Modify::Present(
7201 Attribute::OAuth2RsClaimMap,
7202 Value::OauthClaimValue(
7203 "custom_b".to_string(),
7204 UUID_TESTGROUP,
7205 btreeset!["value_a".to_string()],
7206 ),
7207 ),
7208 Modify::Present(
7209 Attribute::OAuth2RsClaimMap,
7210 Value::OauthClaimValue(
7211 "custom_b".to_string(),
7212 UUID_IDM_ALL_ACCOUNTS,
7213 btreeset!["value_b".to_string()],
7214 ),
7215 ),
7216 Modify::Present(
7218 Attribute::OAuth2RsClaimMap,
7219 Value::OauthClaimValue(
7220 "custom_b".to_string(),
7221 UUID_IDM_ADMINS,
7222 btreeset!["value_c".to_string()],
7223 ),
7224 ),
7225 ]);
7226
7227 assert!(idms_prox_write
7228 .qs_write
7229 .internal_modify(
7230 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7231 &modlist,
7232 )
7233 .is_ok());
7234
7235 assert!(idms_prox_write.commit().is_ok());
7236
7237 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7239
7240 let idms_prox_read = idms.proxy_read().await.unwrap();
7241
7242 let pkce_secret = PkceS256Secret::default();
7243
7244 let consent_request = good_authorisation_request!(
7245 idms_prox_read,
7246 &ident,
7247 ct,
7248 pkce_secret.to_request(),
7249 OAUTH2_SCOPE_OPENID.to_string()
7250 );
7251
7252 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7253 unreachable!();
7254 };
7255
7256 drop(idms_prox_read);
7258 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7259
7260 let permit_success = idms_prox_write
7261 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7262 .expect("Failed to perform OAuth2 permit");
7263
7264 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
7266 code: permit_success.code,
7267 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
7268 code_verifier: Some(pkce_secret.to_verifier()),
7269 }
7270 .into();
7271
7272 let token_response = idms_prox_write
7273 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7274 .expect("Failed to perform OAuth2 token exchange");
7275
7276 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7278
7279 let id_token = token_response.id_token.expect("No id_token in response!");
7280 let access_token =
7281 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
7282
7283 assert!(idms_prox_write.commit().is_ok());
7285
7286 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7287
7288 let mut jwkset = idms_prox_read
7289 .oauth2_openid_publickey("test_resource_server")
7290 .expect("Failed to get public key");
7291
7292 let public_jwk = jwkset.keys.pop().expect("no such jwk");
7293
7294 let jws_validator =
7295 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
7296
7297 let oidc_unverified =
7298 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
7299
7300 let iat = ct.as_secs() as i64;
7301
7302 let oidc = jws_validator
7303 .verify(&oidc_unverified)
7304 .unwrap()
7305 .verify_exp(iat)
7306 .expect("Failed to verify oidc");
7307
7308 assert!(
7310 oidc.iss
7311 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
7312 .unwrap()
7313 );
7314 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
7315 assert_eq!(oidc.aud, "test_resource_server");
7316 assert_eq!(oidc.iat, iat);
7317 assert_eq!(oidc.nbf, Some(iat));
7318 assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
7320 assert!(oidc.auth_time.is_none());
7321 assert_eq!(oidc.nonce, Some("abcdef".to_string()));
7323 assert!(oidc.at_hash.is_none());
7324 assert!(oidc.acr.is_none());
7325 assert!(oidc.amr.is_none());
7326 assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
7327 assert!(oidc.jti.is_some());
7328 if let Some(jti) = &oidc.jti {
7329 assert!(Uuid::from_str(jti).is_ok());
7330 }
7331 assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
7332 assert_eq!(
7333 oidc.s_claims.preferred_username,
7334 Some("testperson1@example.com".to_string())
7335 );
7336 assert!(
7337 oidc.s_claims.scopes == vec![OAUTH2_SCOPE_OPENID.to_string(), "supplement".to_string()]
7338 );
7339
7340 assert_eq!(
7341 oidc.claims.get("custom_a").and_then(|v| v.as_str()),
7342 Some("value_a,value_b")
7343 );
7344 assert_eq!(
7345 oidc.claims.get("custom_b").and_then(|v| v.as_str()),
7346 Some("value_a value_b")
7347 );
7348
7349 let userinfo = idms_prox_read
7352 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
7353 .expect("failed to get userinfo");
7354
7355 assert_eq!(oidc.iss, userinfo.iss);
7356 assert_eq!(oidc.sub, userinfo.sub);
7357 assert_eq!(oidc.aud, userinfo.aud);
7358 assert_eq!(oidc.iat, userinfo.iat);
7359 assert_eq!(oidc.nbf, userinfo.nbf);
7360 assert_eq!(oidc.exp, userinfo.exp);
7361 assert!(userinfo.auth_time.is_none());
7362 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
7363 assert!(userinfo.at_hash.is_none());
7364 assert!(userinfo.jti.is_some());
7365 if let Some(jti) = &userinfo.jti {
7366 assert!(Uuid::from_str(jti).is_ok());
7367 }
7368 assert_eq!(oidc.amr, userinfo.amr);
7369 assert_eq!(oidc.azp, userinfo.azp);
7370 assert!(userinfo.jti.is_some());
7371 if let Some(jti) = &userinfo.jti {
7372 assert!(Uuid::from_str(jti).is_ok());
7373 }
7374 assert_eq!(oidc.s_claims, userinfo.s_claims);
7375 assert_eq!(oidc.claims, userinfo.claims);
7376
7377 let intr_request = AccessTokenIntrospectRequest {
7379 token: token_response.access_token.clone(),
7380 token_type_hint: None,
7381 client_post_auth: ClientPostAuth::default(),
7382 };
7383 let intr_response = idms_prox_read
7384 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7385 .expect("Failed to inspect token");
7386
7387 eprintln!("👉 {intr_response:?}");
7388 assert!(intr_response.active);
7389 assert_eq!(
7390 intr_response.scope,
7391 btreeset!["openid".to_string(), "supplement".to_string()]
7392 );
7393 assert_eq!(
7394 intr_response.client_id.as_deref(),
7395 Some("test_resource_server")
7396 );
7397 assert_eq!(
7398 intr_response.username.as_deref(),
7399 Some("testperson1@example.com")
7400 );
7401 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7402 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7403 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7404 drop(idms_prox_read);
7407 }
7408
7409 #[idm_test]
7410 async fn test_idm_oauth2_public_allow_localhost_redirect(
7411 idms: &IdmServer,
7412 _idms_delayed: &mut IdmServerDelayed,
7413 ) {
7414 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7415 let (_uat, ident, oauth2_rs_uuid) = setup_oauth2_resource_server_public(idms, ct).await;
7416
7417 let mut idms_prox_write: crate::idm::server::IdmServerProxyWriteTransaction<'_> =
7418 idms.proxy_write(ct).await.unwrap();
7419
7420 let redirect_uri = Url::parse("http://localhost:8765/oauth2/result")
7421 .expect("Failed to parse redirect URL");
7422
7423 let modlist = ModifyList::new_list(vec![
7424 Modify::Present(Attribute::OAuth2AllowLocalhostRedirect, Value::Bool(true)),
7425 Modify::Present(Attribute::OAuth2RsOrigin, Value::Url(redirect_uri.clone())),
7426 ]);
7427
7428 assert!(idms_prox_write
7429 .qs_write
7430 .internal_modify(
7431 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7432 &modlist,
7433 )
7434 .is_ok());
7435
7436 assert!(idms_prox_write.commit().is_ok());
7437
7438 let idms_prox_read = idms.proxy_read().await.unwrap();
7439
7440 let pkce_secret = PkceS256Secret::default();
7442
7443 let auth_req = AuthorisationRequest {
7444 response_type: ResponseType::Code,
7445 response_mode: None,
7446 client_id: "test_resource_server".to_string(),
7447 state: Some("123".to_string()),
7448 pkce_request: Some(pkce_secret.to_request()),
7449 redirect_uri: Url::parse("http://localhost:8765/oauth2/result").unwrap(),
7450 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
7451 nonce: Some("abcdef".to_string()),
7452 oidc_ext: Default::default(),
7453 max_age: None,
7454 unknown_keys: Default::default(),
7455 };
7456
7457 let consent_request = idms_prox_read
7458 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
7459 .expect("OAuth2 authorisation failed");
7460
7461 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7463 unreachable!();
7464 };
7465
7466 drop(idms_prox_read);
7468 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7469
7470 let permit_success = idms_prox_write
7471 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7472 .expect("Failed to perform OAuth2 permit");
7473
7474 assert_eq!(permit_success.state.as_deref(), Some("123"));
7476
7477 let token_req = AccessTokenRequest {
7479 grant_type: GrantTypeReq::AuthorizationCode {
7480 code: permit_success.code,
7481 redirect_uri,
7482 code_verifier: Some(pkce_secret.to_verifier()),
7483 },
7484 client_post_auth: ClientPostAuth {
7485 client_id: Some("test_resource_server".to_string()),
7486 client_secret: None,
7487 },
7488 };
7489
7490 let token_response = idms_prox_write
7491 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7492 .expect("Failed to perform OAuth2 token exchange");
7493
7494 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7496
7497 assert!(idms_prox_write.commit().is_ok());
7498 }
7499
7500 #[idm_test]
7501 async fn test_idm_oauth2_service_account_token_exchange(
7502 idms: &IdmServer,
7503 _idms_delayed: &mut IdmServerDelayed,
7504 ) {
7505 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7506 let (secret, _uat, _ident, _) =
7507 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7508
7509 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7510
7511 let service_account_uuid = Uuid::new_v4();
7512 let sa_entry: Entry<EntryInit, EntryNew> = entry_init!(
7513 (Attribute::Class, EntryClass::Object.to_value()),
7514 (Attribute::Class, EntryClass::Account.to_value()),
7515 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
7516 (Attribute::Name, Value::new_iname("test_sa_oauth2")),
7517 (Attribute::Uuid, Value::Uuid(service_account_uuid)),
7518 (Attribute::DisplayName, Value::new_utf8s("test_sa_oauth2")),
7519 (Attribute::Description, Value::new_utf8s("test_sa_oauth2"))
7520 );
7521
7522 idms_prox_write
7523 .qs_write
7524 .internal_create(vec![sa_entry])
7525 .expect("Failed to create service account");
7526
7527 idms_prox_write
7528 .qs_write
7529 .internal_modify(
7530 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTGROUP))),
7531 &ModifyList::new_list(vec![Modify::Present(
7532 Attribute::Member,
7533 Value::Refer(service_account_uuid),
7534 )]),
7535 )
7536 .expect("Failed to add service account to scope group");
7537
7538 let gte = GenerateApiTokenEvent::new_internal(service_account_uuid, "sa-token", None);
7539
7540 let api_token = idms_prox_write
7541 .service_account_generate_api_token(>e, ct)
7542 .expect("failed to generate api token");
7543
7544 assert!(idms_prox_write.commit().is_ok());
7545
7546 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7547
7548 let scopes: BTreeSet<String> =
7549 btreeset![OAUTH2_SCOPE_OPENID.into(), OAUTH2_SCOPE_GROUPS.into()];
7550
7551 let build_exchange_request =
7552 |requested_scopes: BTreeSet<String>, client_secret: Option<String>| {
7553 AccessTokenRequest {
7554 grant_type: GrantTypeReq::TokenExchange {
7555 subject_token: api_token.to_string(),
7556 subject_token_type: TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS.into(),
7557 requested_token_type: None,
7558 audience: Some("test_resource_server".into()),
7559 resource: None,
7560 actor_token: None,
7561 actor_token_type: None,
7562 scope: Some(requested_scopes),
7563 },
7564 client_post_auth: ClientPostAuth {
7565 client_id: Some("test_resource_server".into()),
7566 client_secret,
7567 },
7568 }
7569 };
7570
7571 let token_req = build_exchange_request(scopes.clone(), None);
7572 let forbidden_secret_req = build_exchange_request(scopes.clone(), Some(secret.clone()));
7573 let empty_scope_req = build_exchange_request(BTreeSet::new(), None);
7574
7575 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7576
7577 assert_eq!(
7578 idms_prox_write
7579 .check_oauth2_token_exchange(&client_authz, &forbidden_secret_req, ct)
7580 .unwrap_err(),
7581 Oauth2Error::InvalidRequest
7582 );
7583
7584 assert_eq!(
7585 idms_prox_write
7586 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &empty_scope_req, ct)
7587 .unwrap_err(),
7588 Oauth2Error::InvalidRequest
7589 );
7590
7591 let token_response = idms_prox_write
7592 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7593 .expect("Failed to perform OAuth2 token exchange for service account");
7594
7595 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7596 assert_eq!(
7597 token_response.issued_token_type,
7598 Some(IssuedTokenType::AccessToken)
7599 );
7600 assert!(token_response.refresh_token.is_some());
7601 assert!(token_response.id_token.is_some());
7602 let response_scopes = token_response.scope.clone();
7603 assert!(response_scopes.contains(OAUTH2_SCOPE_OPENID));
7604 assert!(response_scopes.contains(OAUTH2_SCOPE_GROUPS));
7605
7606 assert!(idms_prox_write.commit().is_ok());
7607
7608 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7609 let intr_request = AccessTokenIntrospectRequest {
7610 token: token_response.access_token.clone(),
7611 token_type_hint: None,
7612 client_post_auth: ClientPostAuth::default(),
7613 };
7614 let intr_response = idms_prox_read
7615 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7616 .expect("Failed to introspect service account token");
7617
7618 assert!(intr_response.active);
7619 assert_eq!(
7620 intr_response.client_id.as_deref(),
7621 Some("test_resource_server")
7622 );
7623 assert_eq!(
7624 intr_response.sub.as_deref(),
7625 Some(service_account_uuid.to_string().as_str())
7626 );
7627 }
7628
7629 #[idm_test]
7630 async fn test_idm_oauth2_basic_client_credentials_grant_valid(
7631 idms: &IdmServer,
7632 _idms_delayed: &mut IdmServerDelayed,
7633 ) {
7634 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7635 let (secret, _uat, _ident, _) =
7636 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7637 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7638
7639 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7641
7642 let token_req = AccessTokenRequest {
7643 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7644
7645 client_post_auth: ClientPostAuth {
7646 client_id: Some("test_resource_server".to_string()),
7647 client_secret: Some(secret),
7648 },
7649 };
7650
7651 let oauth2_token = idms_prox_write
7652 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7653 .expect("Failed to perform OAuth2 token exchange");
7654
7655 assert!(idms_prox_write.commit().is_ok());
7656
7657 assert_eq!(oauth2_token.token_type, AccessTokenType::Bearer);
7659
7660 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7662
7663 let intr_request = AccessTokenIntrospectRequest {
7664 token: oauth2_token.access_token.clone(),
7665 token_type_hint: None,
7666 client_post_auth: ClientPostAuth::default(),
7667 };
7668 let intr_response = idms_prox_read
7669 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7670 .expect("Failed to inspect token");
7671
7672 eprintln!("👉 {intr_response:?}");
7673 assert!(intr_response.active);
7674 assert_eq!(intr_response.scope, btreeset!["supplement".to_string()]);
7675 assert_eq!(
7676 intr_response.client_id.as_deref(),
7677 Some("test_resource_server")
7678 );
7679 assert_eq!(
7680 intr_response.username.as_deref(),
7681 Some("test_resource_server@example.com")
7682 );
7683 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7684 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7685 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7686
7687 drop(idms_prox_read);
7688
7689 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7691 let revoke_request = TokenRevokeRequest {
7692 token: oauth2_token.access_token.clone(),
7693 token_type_hint: None,
7694 client_post_auth: ClientPostAuth::default(),
7695 };
7696 assert!(idms_prox_write
7697 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
7698 .is_ok());
7699 assert!(idms_prox_write.commit().is_ok());
7700
7701 let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
7703 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7704
7705 let intr_request = AccessTokenIntrospectRequest {
7706 token: oauth2_token.access_token.clone(),
7707 token_type_hint: None,
7708 client_post_auth: ClientPostAuth::default(),
7709 };
7710
7711 let intr_response = idms_prox_read
7712 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7713 .expect("Failed to inspect token");
7714 assert!(!intr_response.active);
7715
7716 drop(idms_prox_read);
7717 }
7718
7719 #[idm_test]
7720 async fn test_idm_oauth2_basic_client_credentials_grant_invalid(
7721 idms: &IdmServer,
7722 _idms_delayed: &mut IdmServerDelayed,
7723 ) {
7724 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7725 let (secret, _uat, _ident, _) =
7726 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7727
7728 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7729
7730 let token_req = AccessTokenRequest {
7732 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7733 client_post_auth: ClientPostAuth {
7734 client_id: Some("test_resource_server".to_string()),
7735 client_secret: None,
7736 },
7737 };
7738
7739 assert_eq!(
7740 idms_prox_write
7741 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7742 .unwrap_err(),
7743 Oauth2Error::AuthenticationRequired
7744 );
7745
7746 let token_req = AccessTokenRequest {
7748 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7749
7750 client_post_auth: ClientPostAuth {
7751 client_id: Some("test_resource_server".to_string()),
7752 client_secret: Some("wrong password".to_string()),
7753 },
7754 };
7755
7756 assert_eq!(
7757 idms_prox_write
7758 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7759 .unwrap_err(),
7760 Oauth2Error::AuthenticationRequired
7761 );
7762
7763 let scope = Some(btreeset!["💅".to_string()]);
7765 let token_req = AccessTokenRequest {
7766 grant_type: GrantTypeReq::ClientCredentials { scope },
7767
7768 client_post_auth: ClientPostAuth {
7769 client_id: Some("test_resource_server".to_string()),
7770 client_secret: Some(secret.clone()),
7771 },
7772 };
7773
7774 assert_eq!(
7775 idms_prox_write
7776 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7777 .unwrap_err(),
7778 Oauth2Error::InvalidScope
7779 );
7780
7781 let scope = Some(btreeset!["invalid_scope".to_string()]);
7783 let token_req = AccessTokenRequest {
7784 grant_type: GrantTypeReq::ClientCredentials { scope },
7785
7786 client_post_auth: ClientPostAuth {
7787 client_id: Some("test_resource_server".to_string()),
7788 client_secret: Some(secret.clone()),
7789 },
7790 };
7791
7792 assert_eq!(
7793 idms_prox_write
7794 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7795 .unwrap_err(),
7796 Oauth2Error::AccessDenied
7797 );
7798
7799 assert!(idms_prox_write.commit().is_ok());
7800 }
7801
7802 #[test]
7803 fn test_get_code() {
7804 use super::{gen_device_code, gen_user_code, parse_user_code};
7805
7806 assert!(gen_device_code().is_ok());
7807
7808 let (res_string, res_value) = gen_user_code();
7809
7810 assert!(res_string.split('-').count() == 3);
7811
7812 let res_string_clean = res_string.replace("-", "");
7813 let res_string_as_num = res_string_clean
7814 .parse::<u32>()
7815 .expect("Failed to parse as number");
7816 assert_eq!(res_string_as_num, res_value);
7817
7818 assert_eq!(
7819 parse_user_code(&res_string).expect("Failed to parse code"),
7820 res_value
7821 );
7822 }
7823
7824 #[idm_test]
7825 async fn handle_oauth2_start_device_flow(
7826 idms: &IdmServer,
7827 _idms_delayed: &mut IdmServerDelayed,
7828 ) {
7829 let ct = duration_from_epoch_now();
7830
7831 let client_auth_info = ClientAuthInfo::from(Source::Https(
7832 "127.0.0.1"
7833 .parse()
7834 .expect("Failed to parse 127.0.0.1 as an IP!"),
7835 ));
7836 let eventid = Uuid::new_v4();
7837
7838 let res = idms
7839 .proxy_write(ct)
7840 .await
7841 .expect("Failed to get idmspwt")
7842 .handle_oauth2_start_device_flow(client_auth_info, "test_rs_id", &None, eventid);
7843 dbg!(&res);
7844 assert!(res.is_err());
7845 }
7846
7847 #[test]
7848 fn test_url_localhost_domain() {
7849 let example_is_not_local = "https://example.com/sdfsdf";
7853 println!("Ensuring that {example_is_not_local} is not local");
7854 assert!(!host_is_local(
7855 &Url::parse(example_is_not_local)
7856 .expect("Failed to parse example.com as a host?")
7857 .host()
7858 .unwrap_or_else(|| panic!("Couldn't get a host from {example_is_not_local}"))
7859 ));
7860
7861 let test_urls = [
7862 ("http://localhost:8080/oauth2/callback", "/oauth2/callback"),
7863 ("https://localhost/foo/bar", "/foo/bar"),
7864 ("http://127.0.0.1:12345/foo", "/foo"),
7865 ("http://[::1]:12345/foo", "/foo"),
7866 ];
7867
7868 for (url, path) in test_urls.into_iter() {
7869 println!("Testing URL: {url}");
7870 let url = Url::parse(url).expect("One of the test values failed!");
7871 assert!(host_is_local(
7872 &url.host().expect("Didn't parse a host out?")
7873 ));
7874
7875 assert_eq!(url.path(), path);
7876 }
7877 }
7878
7879 #[test]
7880 fn test_oauth2_rs_type_allow_localhost_redirect() {
7881 let test_cases = [
7882 (
7883 OauthRSType::Public {
7884 allow_localhost_redirect: true,
7885 },
7886 true,
7887 ),
7888 (
7889 OauthRSType::Public {
7890 allow_localhost_redirect: false,
7891 },
7892 false,
7893 ),
7894 (
7895 OauthRSType::Basic {
7896 authz_secret: "supersecret".to_string(),
7897 enable_pkce: false,
7898 enable_consent_prompt: true,
7899 },
7900 false,
7901 ),
7902 ];
7903
7904 assert!(test_cases.iter().all(|(rs_type, expected)| {
7905 let actual = rs_type.allow_localhost_redirect();
7906 println!("Testing {rs_type:?} -> {expected}");
7907 actual == *expected
7908 }));
7909 }
7910
7911 #[idm_test]
7912 async fn test_oauth2_auth_with_no_state(
7913 idms: &IdmServer,
7914 _idms_delayed: &mut IdmServerDelayed,
7915 ) {
7916 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7917 let (_secret, _uat, ident, _) =
7918 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7919
7920 let idms_prox_read = idms.proxy_read().await.unwrap();
7921
7922 let pkce_secret = PkceS256Secret::default();
7924
7925 let scope: BTreeSet<String> = OAUTH2_SCOPE_OPENID
7926 .split(" ")
7927 .map(|s| s.to_string())
7928 .collect();
7929
7930 let auth_req = AuthorisationRequest {
7931 response_type: ResponseType::Code,
7932 response_mode: None,
7933 client_id: "test_resource_server".to_string(),
7934 state: None,
7935 pkce_request: Some(pkce_secret.to_request()),
7936 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
7937 scope,
7938 nonce: Some("abcdef".to_string()),
7939 oidc_ext: Default::default(),
7940 max_age: None,
7941 unknown_keys: Default::default(),
7942 };
7943 println!("{auth_req:?}");
7944
7945 let consent_request = idms_prox_read
7946 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
7947 .expect("OAuth2 authorisation failed");
7948
7949 let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
7951 unreachable!("Expected a ConsentRequested response, got: {consent_request:?}");
7952 };
7953 }
7954
7955 #[idm_test]
7956 async fn test_idm_oauth2_consent_prompt_disabled(
7957 idms: &IdmServer,
7958 _idms_delayed: &mut IdmServerDelayed,
7959 ) {
7960 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7961 let (_secret, _uat, ident, o2rs_uuid) =
7962 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7963
7964 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7965 idms_prox_write
7966 .qs_write
7967 .internal_modify_uuid(
7968 o2rs_uuid,
7969 &ModifyList::new_purge_and_set(
7970 Attribute::OAuth2ConsentPromptEnable,
7971 Value::new_bool(false),
7972 ),
7973 )
7974 .expect("Unable to disable consent prompt");
7975 assert!(idms_prox_write.commit().is_ok());
7976
7977 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
7979
7980 let idms_prox_read = idms.proxy_read().await.unwrap();
7981
7982 let pkce_secret = PkceS256Secret::default();
7983 let consent_request = good_authorisation_request!(
7984 idms_prox_read,
7985 &ident,
7986 ct,
7987 pkce_secret.to_request(),
7988 OAUTH2_SCOPE_OPENID.to_string()
7989 );
7990
7991 let AuthoriseResponse::Permitted(_permitted) = consent_request else {
7993 unreachable!();
7994 };
7995
7996 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
7998 }
7999}