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