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 let client_id = client_id.replace("%2D", "-").replace("%5F", "_");
3132
3133 Ok((client_id.as_str(), Some(secret)).into())
3134}
3135
3136fn s_claims_for_account(
3137 o2rs: &Oauth2RS,
3138 account: &Account,
3139 scopes: &BTreeSet<String>,
3140) -> OidcClaims {
3141 let preferred_username = if o2rs.prefer_short_username {
3142 Some(account.name().into())
3143 } else {
3144 Some(account.spn().into())
3145 };
3146
3147 let (email, email_verified) = if scopes.contains(OAUTH2_SCOPE_EMAIL) {
3148 if let Some(mp) = &account.mail_primary {
3149 (Some(mp.clone()), Some(true))
3150 } else {
3151 (None, None)
3152 }
3153 } else {
3154 (None, None)
3155 };
3156
3157 let updated_at: Option<OffsetDateTime> = if scopes.contains(OAUTH2_SCOPE_PROFILE) {
3158 account
3159 .updated_at
3160 .as_ref()
3161 .map(OffsetDateTime::from)
3162 .and_then(|odt| odt.replace_nanosecond(0).ok())
3163 } else {
3164 None
3165 };
3166 OidcClaims {
3167 name: Some(account.displayname.clone()),
3169 scopes: scopes.iter().cloned().collect(),
3170 preferred_username,
3171 email,
3172 email_verified,
3173 updated_at,
3174 ..Default::default()
3175 }
3176}
3177
3178fn extra_claims_for_account(
3179 account: &Account,
3180
3181 claim_map: &BTreeMap<Uuid, Vec<(String, ClaimValue)>>,
3182
3183 scopes: &BTreeSet<String>,
3184) -> BTreeMap<String, serde_json::Value> {
3185 let mut extra_claims = BTreeMap::new();
3186
3187 let mut account_claims: BTreeMap<&str, ClaimValue> = BTreeMap::new();
3188
3189 for group_uuid in account.groups.iter().map(|g| g.uuid()) {
3191 if let Some(claim) = claim_map.get(group_uuid) {
3193 for (claim_name, claim_value) in claim.iter() {
3195 match account_claims.entry(claim_name.as_str()) {
3197 BTreeEntry::Vacant(e) => {
3198 e.insert(claim_value.clone());
3199 }
3200 BTreeEntry::Occupied(mut e) => {
3201 let mut_claim_value = e.get_mut();
3202 mut_claim_value.merge(claim_value);
3204 }
3205 }
3206 }
3207 }
3208 }
3209
3210 for (claim_name, claim_value) in account_claims {
3212 extra_claims.insert(claim_name.to_string(), claim_value.to_json_value());
3213 }
3214
3215 if scopes.contains(OAUTH2_SCOPE_SSH_PUBLICKEYS) {
3219 extra_claims.insert(
3220 OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string(),
3221 account
3222 .sshkeys()
3223 .values()
3224 .map(|pub_key| serde_json::Value::String(pub_key.to_string()))
3225 .collect(),
3226 );
3227 }
3228
3229 let wants_groups = scopes.contains(OAUTH2_SCOPE_GROUPS);
3230 let wants_groups_uuid = wants_groups || scopes.contains(OAUTH2_SCOPE_GROUPS_UUID);
3232 let wants_groups_spn = wants_groups || scopes.contains(OAUTH2_SCOPE_GROUPS_SPN);
3233 let wants_groups_name = scopes.contains(OAUTH2_SCOPE_GROUPS_NAME);
3234
3235 if wants_groups_uuid || wants_groups_name || wants_groups_spn {
3236 extra_claims.insert(
3237 OAUTH2_SCOPE_GROUPS.to_string(),
3238 account
3239 .groups
3240 .iter()
3241 .flat_map(|group| {
3242 let mut attrs = Vec::with_capacity(3);
3243
3244 if wants_groups_uuid {
3245 attrs.push(group.uuid().as_hyphenated().to_string())
3246 }
3247
3248 if wants_groups_spn {
3249 attrs.push(group.spn().clone())
3250 }
3251
3252 if wants_groups_name {
3253 if let Some(name) = group.name() {
3254 attrs.push(name.into())
3255 }
3256 }
3257
3258 attrs
3259 })
3260 .collect(),
3261 );
3262 }
3263
3264 trace!(?extra_claims);
3265
3266 extra_claims
3267}
3268
3269fn process_requested_scopes_for_identity(
3270 o2rs: &Oauth2RS,
3271 ident: &Identity,
3272 req_scopes: Option<&BTreeSet<String>>,
3273) -> Result<(BTreeSet<String>, BTreeSet<String>), Oauth2Error> {
3274 let req_scopes = req_scopes.cloned().unwrap_or_default();
3275
3276 if req_scopes.is_empty() {
3277 admin_error!("Invalid OAuth2 request - must contain at least one requested scope");
3278 return Err(Oauth2Error::InvalidRequest);
3279 }
3280
3281 validate_scopes(&req_scopes)?;
3282
3283 let available_scopes: BTreeSet<String> = o2rs
3284 .scope_maps
3285 .iter()
3286 .filter_map(|(u, m)| ident.is_memberof(*u).then_some(m.iter()))
3287 .flatten()
3288 .cloned()
3289 .collect();
3290
3291 if !req_scopes.is_subset(&available_scopes) {
3292 admin_warn!(
3293 %ident,
3294 requested_scopes = ?req_scopes,
3295 available_scopes = ?available_scopes,
3296 "Identity does not have access to the requested scopes"
3297 );
3298 return Err(Oauth2Error::AccessDenied);
3299 }
3300
3301 let granted_scopes: BTreeSet<String> = o2rs
3302 .sup_scope_maps
3303 .iter()
3304 .filter_map(|(u, m)| ident.is_memberof(*u).then_some(m.iter()))
3305 .flatten()
3306 .cloned()
3307 .chain(req_scopes.iter().cloned())
3308 .collect();
3309
3310 Ok((req_scopes, granted_scopes))
3311}
3312
3313fn validate_scopes(req_scopes: &BTreeSet<String>) -> Result<(), Oauth2Error> {
3314 let failed_scopes = req_scopes
3315 .iter()
3316 .filter(|&s| !OAUTHSCOPE_RE.is_match(s))
3317 .cloned()
3318 .collect::<Vec<String>>();
3319
3320 if !failed_scopes.is_empty() {
3321 let requested_scopes_string = req_scopes
3322 .iter()
3323 .cloned()
3324 .collect::<Vec<String>>()
3325 .join(",");
3326 admin_error!(
3327 "Invalid OAuth2 request - requested scopes ({}) but ({}) failed to pass validation rules - all must match the regex {}",
3328 requested_scopes_string,
3329 failed_scopes.join(","),
3330 OAUTHSCOPE_RE.as_str()
3331 );
3332 return Err(Oauth2Error::InvalidScope);
3333 }
3334 Ok(())
3335}
3336
3337#[inline]
3339#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3340#[allow(dead_code)]
3341fn gen_device_code() -> Result<[u8; 16], Oauth2Error> {
3342 use rand::TryRng;
3343
3344 let mut rng = rand::rng();
3345 let mut result = [0u8; 16];
3346 if let Err(err) = rng.try_fill_bytes(&mut result) {
3348 error!("Failed to generate device code! {:?}", err);
3349 return Err(Oauth2Error::ServerError(OperationError::Backend));
3350 }
3351 Ok(result)
3352}
3353
3354#[inline]
3355#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3356#[allow(dead_code)]
3357fn gen_user_code() -> (String, u32) {
3359 use rand::RngExt;
3360 let mut rng = rand::rng();
3361 let num: u32 = rng.random_range(0..=999999999);
3362 let result = format!("{num:09}");
3363 (
3364 format!("{}-{}-{}", &result[0..3], &result[3..6], &result[6..9]),
3365 num,
3366 )
3367}
3368
3369#[allow(dead_code)]
3371fn parse_user_code(val: &str) -> Result<u32, Oauth2Error> {
3372 let mut val = val.to_string();
3373 val.retain(|c| c.is_ascii_digit());
3374 val.parse().map_err(|err| {
3375 debug!("Failed to parse value={} as u32: {:?}", val, err);
3376 Oauth2Error::InvalidRequest
3377 })
3378}
3379
3380fn host_is_local(host: &Host<&str>) -> bool {
3382 match host {
3383 Host::Ipv4(ip) => ip.is_loopback(),
3384 Host::Ipv6(ip) => ip.is_loopback(),
3385 Host::Domain(domain) => *domain == "localhost",
3386 }
3387}
3388
3389fn check_is_loopback(redirect_uri: &Url) -> bool {
3391 redirect_uri.host().is_some_and(|host| {
3392 host_is_local(&host)
3394 })
3395}
3396
3397#[cfg(test)]
3398mod tests {
3399 use super::{Oauth2TokenType, PkceS256Secret, TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS};
3400 use crate::credential::Credential;
3401 use crate::idm::accountpolicy::ResolvedAccountPolicy;
3402 use crate::idm::oauth2::{
3403 host_is_local, parse_basic_authz, AuthoriseResponse, Oauth2Error, OauthRSType,
3404 };
3405 use crate::idm::server::{IdmServer, IdmServerTransaction};
3406 use crate::idm::serviceaccount::GenerateApiTokenEvent;
3407 use crate::prelude::*;
3408 use crate::value::{AuthType, OauthClaimMapJoin, SessionState};
3409 use crate::valueset::{ValueSetOauthScopeMap, ValueSetSshKey};
3410 use base64::{engine::general_purpose, Engine as _};
3411 use compact_jwt::{
3412 compact::JwkUse, crypto::JwsRs256Verifier, dangernoverify::JwsDangerReleaseWithoutVerify,
3413 JwaAlg, Jwk, JwsCompact, JwsEs256Verifier, JwsVerifier, OidcSubject, OidcToken,
3414 OidcUnverified,
3415 };
3416 use kanidm_lib_crypto::CryptoPolicy;
3417 use kanidm_proto::constants::*;
3418 use kanidm_proto::internal::{SshPublicKey, UserAuthToken};
3419 use kanidm_proto::oauth2::*;
3420 use std::collections::{BTreeMap, BTreeSet};
3421 use std::convert::TryFrom;
3422 use std::str::FromStr;
3423 use std::time::Duration;
3424 use time::OffsetDateTime;
3425 use uri::{OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
3426
3427 const TEST_CURRENT_TIME: u64 = 6000;
3428 const UAT_EXPIRE: u64 = 5;
3429 const TOKEN_EXPIRE: u64 = 900;
3430
3431 const UUID_TESTGROUP: Uuid = uuid!("a3028223-bf20-47d5-8b65-967b5d2bb3eb");
3432
3433 macro_rules! good_authorisation_request {
3434 (
3435 $idms_prox_read:expr,
3436 $ident:expr,
3437 $ct:expr,
3438 $pkce_request:expr,
3439 $scope:expr
3440 ) => {{
3441 #[allow(clippy::unnecessary_to_owned)]
3442 let scope: BTreeSet<String> = $scope.split(" ").map(|s| s.to_string()).collect();
3443
3444 let auth_req = AuthorisationRequest {
3445 response_type: ResponseType::Code,
3446 response_mode: None,
3447 client_id: "test_resource_server".to_string(),
3448 state: Some("123".to_string()),
3449 pkce_request: Some($pkce_request),
3450 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3451 scope,
3452 nonce: Some("abcdef".to_string()),
3453 oidc_ext: Default::default(),
3454 max_age: None,
3455 unknown_keys: Default::default(),
3456 };
3457
3458 $idms_prox_read
3459 .check_oauth2_authorisation(Some($ident), &auth_req, $ct)
3460 .expect("OAuth2 authorisation failed")
3461 }};
3462 }
3463
3464 async fn setup_oauth2_resource_server_basic(
3466 idms: &IdmServer,
3467 ct: Duration,
3468 enable_pkce: bool,
3469 enable_legacy_crypto: bool,
3470 prefer_short_username: bool,
3471 ) -> (String, UserAuthToken, Identity, Uuid) {
3472 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3473
3474 let rs_uuid = Uuid::new_v4();
3475
3476 let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3477 (Attribute::Class, EntryClass::Group.to_value()),
3478 (Attribute::Name, Value::new_iname("testgroup")),
3479 (Attribute::Description, Value::new_utf8s("testgroup")),
3480 (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3481 (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3482 );
3483
3484 let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3485 (Attribute::Class, EntryClass::Object.to_value()),
3486 (Attribute::Class, EntryClass::Account.to_value()),
3487 (
3488 Attribute::Class,
3489 EntryClass::OAuth2ResourceServer.to_value()
3490 ),
3491 (
3492 Attribute::Class,
3493 EntryClass::OAuth2ResourceServerBasic.to_value()
3494 ),
3495 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3496 (Attribute::Name, Value::new_iname("test_resource_server")),
3497 (
3498 Attribute::DisplayName,
3499 Value::new_utf8s("test_resource_server")
3500 ),
3501 (
3502 Attribute::OAuth2RsOriginLanding,
3503 Value::new_url_s("https://demo.example.com").unwrap()
3504 ),
3505 (
3507 Attribute::OAuth2RsOrigin,
3508 Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3509 ),
3510 (
3511 Attribute::OAuth2RsOrigin,
3512 Value::new_url_s("https://portal.example.com/?custom=foo").unwrap()
3513 ),
3514 (
3515 Attribute::OAuth2RsOrigin,
3516 Value::new_url_s("app://cheese").unwrap()
3517 ),
3518 (
3520 Attribute::OAuth2RsScopeMap,
3521 Value::new_oauthscopemap(
3522 UUID_TESTGROUP,
3523 btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3524 )
3525 .expect("invalid oauthscope")
3526 ),
3527 (
3528 Attribute::OAuth2RsScopeMap,
3529 Value::new_oauthscopemap(
3530 UUID_IDM_ALL_ACCOUNTS,
3531 btreeset![
3532 OAUTH2_SCOPE_OPENID.to_string(),
3533 OAUTH2_SCOPE_PROFILE.to_string()
3534 ]
3535 )
3536 .expect("invalid oauthscope")
3537 ),
3538 (
3539 Attribute::OAuth2RsSupScopeMap,
3540 Value::new_oauthscopemap(
3541 UUID_IDM_ALL_ACCOUNTS,
3542 btreeset!["supplement".to_string()]
3543 )
3544 .expect("invalid oauthscope")
3545 ),
3546 (
3547 Attribute::OAuth2AllowInsecureClientDisablePkce,
3548 Value::new_bool(!enable_pkce)
3549 ),
3550 (
3551 Attribute::OAuth2JwtLegacyCryptoEnable,
3552 Value::new_bool(enable_legacy_crypto)
3553 ),
3554 (
3555 Attribute::OAuth2PreferShortUsername,
3556 Value::new_bool(prefer_short_username)
3557 )
3558 );
3559
3560 let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3561 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3562
3563 let entry = idms_prox_write
3564 .qs_write
3565 .internal_search_uuid(rs_uuid)
3566 .expect("Failed to retrieve OAuth2 resource entry ");
3567 let secret = entry
3568 .get_ava_single_secret(Attribute::OAuth2RsBasicSecret)
3569 .map(str::to_string)
3570 .expect("No oauth2_rs_basic_secret found");
3571
3572 let session_id = uuid::Uuid::new_v4();
3575
3576 let account = idms_prox_write
3577 .target_to_account(UUID_TESTPERSON_1)
3578 .expect("account must exist");
3579
3580 let uat = account
3581 .to_userauthtoken(
3582 session_id,
3583 SessionScope::ReadWrite,
3584 ct,
3585 &ResolvedAccountPolicy::test_policy(),
3586 )
3587 .expect("Unable to create uat");
3588
3589 let state = uat
3591 .expiry
3592 .map(SessionState::ExpiresAt)
3593 .unwrap_or(SessionState::NeverExpires);
3594
3595 let p = CryptoPolicy::minimum();
3596 let cred =
3597 Credential::new_password_only(&p, "test_password", OffsetDateTime::UNIX_EPOCH).unwrap();
3598 let cred_id = cred.uuid;
3599
3600 let session = Value::Session(
3601 session_id,
3602 crate::value::Session {
3603 label: "label".to_string(),
3604 state,
3605 issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3606 issued_by: IdentityId::Internal(UUID_SYSTEM),
3607 cred_id,
3608 scope: SessionScope::ReadWrite,
3609 type_: AuthType::Passkey,
3610 ext_metadata: Default::default(),
3611 },
3612 );
3613
3614 let modlist = ModifyList::new_list(vec![
3616 Modify::Present(Attribute::UserAuthTokenSession, session),
3617 Modify::Present(
3618 Attribute::PrimaryCredential,
3619 Value::Cred("primary".to_string(), cred),
3620 ),
3621 ]);
3622
3623 idms_prox_write
3624 .qs_write
3625 .internal_modify(
3626 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3627 &modlist,
3628 )
3629 .expect("Failed to modify user");
3630
3631 let ident = idms_prox_write
3632 .process_uat_to_identity(&uat, ct, Source::Internal)
3633 .expect("Unable to process uat");
3634
3635 idms_prox_write.commit().expect("failed to commit");
3636
3637 (secret, uat, ident, rs_uuid)
3638 }
3639
3640 async fn setup_oauth2_resource_server_public(
3641 idms: &IdmServer,
3642 ct: Duration,
3643 ) -> (UserAuthToken, Identity, Uuid) {
3644 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3645
3646 let rs_uuid = Uuid::new_v4();
3647
3648 let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3649 (Attribute::Class, EntryClass::Group.to_value()),
3650 (Attribute::Name, Value::new_iname("testgroup")),
3651 (Attribute::Description, Value::new_utf8s("testgroup")),
3652 (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3653 (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3654 );
3655
3656 let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3657 (Attribute::Class, EntryClass::Object.to_value()),
3658 (Attribute::Class, EntryClass::Account.to_value()),
3659 (
3660 Attribute::Class,
3661 EntryClass::OAuth2ResourceServer.to_value()
3662 ),
3663 (
3664 Attribute::Class,
3665 EntryClass::OAuth2ResourceServerPublic.to_value()
3666 ),
3667 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3668 (Attribute::Name, Value::new_iname("test_resource_server")),
3669 (
3670 Attribute::DisplayName,
3671 Value::new_utf8s("test_resource_server")
3672 ),
3673 (
3674 Attribute::OAuth2RsOriginLanding,
3675 Value::new_url_s("https://demo.example.com").unwrap()
3676 ),
3677 (
3678 Attribute::OAuth2RsOrigin,
3679 Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3680 ),
3681 (
3683 Attribute::OAuth2RsScopeMap,
3684 Value::new_oauthscopemap(
3685 UUID_TESTGROUP,
3686 btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3687 )
3688 .expect("invalid oauthscope")
3689 ),
3690 (
3691 Attribute::OAuth2RsScopeMap,
3692 Value::new_oauthscopemap(
3693 UUID_IDM_ALL_ACCOUNTS,
3694 btreeset![OAUTH2_SCOPE_OPENID.to_string()]
3695 )
3696 .expect("invalid oauthscope")
3697 ),
3698 (
3699 Attribute::OAuth2RsSupScopeMap,
3700 Value::new_oauthscopemap(
3701 UUID_IDM_ALL_ACCOUNTS,
3702 btreeset!["supplement".to_string()]
3703 )
3704 .expect("invalid oauthscope")
3705 )
3706 );
3707 let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3708 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3709
3710 let session_id = uuid::Uuid::new_v4();
3714
3715 let account = idms_prox_write
3716 .target_to_account(UUID_TESTPERSON_1)
3717 .expect("account must exist");
3718 let uat = account
3719 .to_userauthtoken(
3720 session_id,
3721 SessionScope::ReadWrite,
3722 ct,
3723 &ResolvedAccountPolicy::test_policy(),
3724 )
3725 .expect("Unable to create uat");
3726
3727 let state = uat
3729 .expiry
3730 .map(SessionState::ExpiresAt)
3731 .unwrap_or(SessionState::NeverExpires);
3732
3733 let p = CryptoPolicy::minimum();
3734 let cred =
3735 Credential::new_password_only(&p, "test_password", OffsetDateTime::UNIX_EPOCH).unwrap();
3736 let cred_id = cred.uuid;
3737
3738 let session = Value::Session(
3739 session_id,
3740 crate::value::Session {
3741 label: "label".to_string(),
3742 state,
3743 issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3744 issued_by: IdentityId::Internal(UUID_SYSTEM),
3745 cred_id,
3746 scope: SessionScope::ReadWrite,
3747 type_: AuthType::Passkey,
3748 ext_metadata: Default::default(),
3749 },
3750 );
3751
3752 let modlist = ModifyList::new_list(vec![
3754 Modify::Present(Attribute::UserAuthTokenSession, session),
3755 Modify::Present(
3756 Attribute::PrimaryCredential,
3757 Value::Cred("primary".to_string(), cred),
3758 ),
3759 ]);
3760
3761 idms_prox_write
3762 .qs_write
3763 .internal_modify(
3764 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3765 &modlist,
3766 )
3767 .expect("Failed to modify user");
3768
3769 let ident = idms_prox_write
3770 .process_uat_to_identity(&uat, ct, Source::Internal)
3771 .expect("Unable to process uat");
3772
3773 idms_prox_write.commit().expect("failed to commit");
3774
3775 (uat, ident, rs_uuid)
3776 }
3777
3778 async fn perform_oauth2_exchange(
3780 idms: &IdmServer,
3781 ident: &Identity,
3782 ct: Duration,
3783 client_authz: ClientAuthInfo,
3784 scopes: String,
3785 ) -> AccessTokenResponse {
3786 let idms_prox_read = idms.proxy_read().await.unwrap();
3787
3788 let pkce_secret = PkceS256Secret::default();
3789
3790 let consent_request = good_authorisation_request!(
3791 idms_prox_read,
3792 ident,
3793 ct,
3794 pkce_secret.to_request(),
3795 scopes
3796 );
3797
3798 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3799 unreachable!();
3800 };
3801
3802 drop(idms_prox_read);
3804 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3805
3806 let permit_success = idms_prox_write
3807 .check_oauth2_authorise_permit(ident, &consent_token, ct)
3808 .expect("Failed to perform OAuth2 permit");
3809
3810 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
3812 code: permit_success.code,
3813 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3814 code_verifier: Some(pkce_secret.to_verifier()),
3815 }
3816 .into();
3817
3818 let token_response = idms_prox_write
3819 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
3820 .expect("Failed to perform OAuth2 token exchange");
3821
3822 assert!(idms_prox_write.commit().is_ok());
3823
3824 token_response
3825 }
3826
3827 async fn validate_id_token(idms: &IdmServer, ct: Duration, id_token: &str) -> OidcToken {
3828 let idms_prox_read = idms.proxy_read().await.unwrap();
3829
3830 let mut jwkset = idms_prox_read
3831 .oauth2_openid_publickey("test_resource_server")
3832 .expect("Failed to get public key");
3833 let public_jwk = jwkset.keys.pop().expect("no such jwk");
3834
3835 let jws_validator =
3836 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
3837
3838 let oidc_unverified = OidcUnverified::from_str(id_token).expect("Failed to parse id_token");
3839
3840 let iat = ct.as_secs() as i64;
3841
3842 jws_validator
3843 .verify(&oidc_unverified)
3844 .unwrap()
3845 .verify_exp(iat)
3846 .expect("Failed to verify oidc")
3847 }
3848
3849 async fn setup_idm_admin(idms: &IdmServer, ct: Duration) -> (UserAuthToken, Identity) {
3850 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3851 let account = idms_prox_write
3852 .target_to_account(UUID_IDM_ADMIN)
3853 .expect("account must exist");
3854 let session_id = uuid::Uuid::new_v4();
3855 let uat = account
3856 .to_userauthtoken(
3857 session_id,
3858 SessionScope::ReadWrite,
3859 ct,
3860 &ResolvedAccountPolicy::test_policy(),
3861 )
3862 .expect("Unable to create uat");
3863 let ident = idms_prox_write
3864 .process_uat_to_identity(&uat, ct, Source::Internal)
3865 .expect("Unable to process uat");
3866
3867 idms_prox_write.commit().expect("failed to commit");
3868
3869 (uat, ident)
3870 }
3871
3872 #[test]
3873 fn oauth2_parse_basic_authz() {
3874 let r1 = parse_basic_authz("czZCaGRSa3F0Mzo3RmpmcDBaQnIxS3REUmJuZlZkbUl3").unwrap();
3875 assert_eq!(r1.client_id, "s6BhdRkqt3");
3876 assert_eq!(r1.client_secret.as_deref(), Some("7Fjfp0ZBr1KtDRbnfVdmIw"));
3877
3878 let r2 = parse_basic_authz("bXklMkRpZDpEZWk3dGhhaTFhaG5lNGE=").unwrap();
3880 assert_eq!(r2.client_id, "my-id");
3881 assert_eq!(r2.client_secret.as_deref(), Some("Dei7thai1ahne4a"));
3882 }
3883
3884 #[idm_test]
3885 async fn test_idm_oauth2_basic_function(
3886 idms: &IdmServer,
3887 _idms_delayed: &mut IdmServerDelayed,
3888 ) {
3889 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3890 let (secret, _uat, ident, _) =
3891 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
3892
3893 let idms_prox_read = idms.proxy_read().await.unwrap();
3894
3895 let pkce_secret = PkceS256Secret::default();
3897
3898 let consent_request = good_authorisation_request!(
3899 idms_prox_read,
3900 &ident,
3901 ct,
3902 pkce_secret.to_request(),
3903 OAUTH2_SCOPE_OPENID.to_string()
3904 );
3905
3906 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3908 unreachable!();
3909 };
3910
3911 drop(idms_prox_read);
3913 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3914
3915 let permit_success = idms_prox_write
3916 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
3917 .expect("Failed to perform OAuth2 permit");
3918
3919 assert_eq!(permit_success.state.as_deref(), Some("123"));
3921
3922 let token_req = AccessTokenRequest {
3925 grant_type: GrantTypeReq::AuthorizationCode {
3926 code: permit_success.code,
3927 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3928 code_verifier: Some(pkce_secret.to_verifier()),
3929 },
3930 client_post_auth: ClientPostAuth {
3931 client_id: Some("test_resource_server".to_string()),
3932 client_secret: Some(secret),
3933 },
3934 };
3935
3936 let token_response = idms_prox_write
3937 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
3938 .expect("Failed to perform OAuth2 token exchange");
3939
3940 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
3942
3943 assert!(idms_prox_write.commit().is_ok());
3944 }
3945
3946 #[idm_test]
3947 async fn test_idm_oauth2_public_function(
3948 idms: &IdmServer,
3949 _idms_delayed: &mut IdmServerDelayed,
3950 ) {
3951 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3952 let (_uat, ident, _) = setup_oauth2_resource_server_public(idms, ct).await;
3953
3954 let idms_prox_read = idms.proxy_read().await.unwrap();
3955
3956 let pkce_secret = PkceS256Secret::default();
3960
3961 let consent_request = good_authorisation_request!(
3962 idms_prox_read,
3963 &ident,
3964 ct,
3965 pkce_secret.to_request(),
3966 OAUTH2_SCOPE_OPENID.to_string()
3967 );
3968
3969 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3971 unreachable!();
3972 };
3973
3974 drop(idms_prox_read);
3976 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3977
3978 let permit_success = idms_prox_write
3979 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
3980 .expect("Failed to perform OAuth2 permit");
3981
3982 assert_eq!(permit_success.state.as_deref(), Some("123"));
3984
3985 let token_req = AccessTokenRequest {
3988 grant_type: GrantTypeReq::AuthorizationCode {
3989 code: permit_success.code,
3990 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3991 code_verifier: Some(pkce_secret.to_verifier()),
3993 },
3994
3995 client_post_auth: ClientPostAuth {
3996 client_id: Some("Test_Resource_Server".to_string()),
3997 client_secret: None,
3998 },
3999 };
4000
4001 let token_response = idms_prox_write
4002 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4003 .expect("Failed to perform OAuth2 token exchange");
4004
4005 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4007
4008 assert!(idms_prox_write.commit().is_ok());
4009 }
4010
4011 #[idm_test]
4012 async fn test_idm_oauth2_invalid_authorisation_requests(
4013 idms: &IdmServer,
4014 _idms_delayed: &mut IdmServerDelayed,
4015 ) {
4016 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4018 let (_secret, _uat, ident, _) =
4019 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4020
4021 let (_anon_uat, anon_ident) = setup_idm_admin(idms, ct).await;
4022 let (_idm_admin_uat, idm_admin_ident) = setup_idm_admin(idms, ct).await;
4023
4024 let idms_prox_read = idms.proxy_read().await.unwrap();
4026
4027 let pkce_secret = PkceS256Secret::default();
4028
4029 let pkce_request = pkce_secret.to_request();
4030
4031 let auth_req = AuthorisationRequest {
4033 response_type: ResponseType::Token,
4035 response_mode: None,
4036 client_id: "test_resource_server".to_string(),
4037 state: Some("123".to_string()),
4038 pkce_request: Some(pkce_request.clone()),
4039 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4040 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4041 nonce: None,
4042 oidc_ext: Default::default(),
4043 max_age: None,
4044 unknown_keys: Default::default(),
4045 };
4046
4047 assert!(
4048 idms_prox_read
4049 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4050 .unwrap_err()
4051 == Oauth2Error::UnsupportedResponseType
4052 );
4053
4054 let auth_req = AuthorisationRequest {
4056 response_type: ResponseType::Code,
4057 response_mode: None,
4058 client_id: "test_resource_server".to_string(),
4059 state: Some("123".to_string()),
4060 pkce_request: None,
4061 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4062 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4063 nonce: None,
4064 oidc_ext: Default::default(),
4065 max_age: None,
4066 unknown_keys: Default::default(),
4067 };
4068
4069 assert!(
4070 idms_prox_read
4071 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4072 .unwrap_err()
4073 == Oauth2Error::InvalidRequest
4074 );
4075
4076 let auth_req = AuthorisationRequest {
4078 response_type: ResponseType::Code,
4079 response_mode: None,
4080 client_id: "NOT A REAL RESOURCE SERVER".to_string(),
4081 state: Some("123".to_string()),
4082 pkce_request: Some(pkce_request.clone()),
4083 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4084 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4085 nonce: None,
4086 oidc_ext: Default::default(),
4087 max_age: None,
4088 unknown_keys: Default::default(),
4089 };
4090
4091 assert!(
4092 idms_prox_read
4093 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4094 .unwrap_err()
4095 == Oauth2Error::InvalidClientId
4096 );
4097
4098 let auth_req = AuthorisationRequest {
4100 response_type: ResponseType::Code,
4101 response_mode: None,
4102 client_id: "test_resource_server".to_string(),
4103 state: Some("123".to_string()),
4104 pkce_request: Some(pkce_request.clone()),
4105 redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4106 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4107 nonce: None,
4108 oidc_ext: Default::default(),
4109 max_age: None,
4110 unknown_keys: Default::default(),
4111 };
4112
4113 assert!(
4114 idms_prox_read
4115 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4116 .unwrap_err()
4117 == Oauth2Error::InvalidOrigin
4118 );
4119
4120 let auth_req = AuthorisationRequest {
4122 response_type: ResponseType::Code,
4123 response_mode: None,
4124 client_id: "test_resource_server".to_string(),
4125 state: Some("123".to_string()),
4126 pkce_request: Some(pkce_request.clone()),
4127 redirect_uri: Url::parse("https://demo.example.com/oauth2/wrong_place").unwrap(),
4128 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4129 nonce: None,
4130 oidc_ext: Default::default(),
4131 max_age: None,
4132 unknown_keys: Default::default(),
4133 };
4134
4135 assert!(
4136 idms_prox_read
4137 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4138 .unwrap_err()
4139 == Oauth2Error::InvalidOrigin
4140 );
4141
4142 let auth_req = AuthorisationRequest {
4144 response_type: ResponseType::Code,
4145 response_mode: None,
4146 client_id: "test_resource_server".to_string(),
4147 state: Some("123".to_string()),
4148 pkce_request: Some(pkce_request.clone()),
4149 redirect_uri: Url::parse("https://portal.example.com/?custom=foo&too=many").unwrap(),
4150 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4151 nonce: None,
4152 oidc_ext: Default::default(),
4153 max_age: None,
4154 unknown_keys: Default::default(),
4155 };
4156
4157 assert!(
4158 idms_prox_read
4159 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4160 .unwrap_err()
4161 == Oauth2Error::InvalidOrigin
4162 );
4163
4164 let auth_req = AuthorisationRequest {
4165 response_type: ResponseType::Code,
4166 response_mode: None,
4167 client_id: "test_resource_server".to_string(),
4168 state: Some("123".to_string()),
4169 pkce_request: Some(pkce_request.clone()),
4170 redirect_uri: Url::parse("https://portal.example.com").unwrap(),
4171 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4172 nonce: None,
4173 oidc_ext: Default::default(),
4174 max_age: None,
4175 unknown_keys: Default::default(),
4176 };
4177
4178 assert!(
4179 idms_prox_read
4180 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4181 .unwrap_err()
4182 == Oauth2Error::InvalidOrigin
4183 );
4184
4185 let auth_req = AuthorisationRequest {
4186 response_type: ResponseType::Code,
4187 response_mode: None,
4188 client_id: "test_resource_server".to_string(),
4189 state: Some("123".to_string()),
4190 pkce_request: Some(pkce_request.clone()),
4191 redirect_uri: Url::parse("https://portal.example.com/?wrong=queryparam").unwrap(),
4192 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4193 nonce: None,
4194 oidc_ext: Default::default(),
4195 max_age: None,
4196 unknown_keys: Default::default(),
4197 };
4198
4199 assert!(
4200 idms_prox_read
4201 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4202 .unwrap_err()
4203 == Oauth2Error::InvalidOrigin
4204 );
4205
4206 let auth_req = AuthorisationRequest {
4208 response_type: ResponseType::Code,
4209 response_mode: None,
4210 client_id: "test_resource_server".to_string(),
4211 state: Some("123".to_string()),
4212 pkce_request: Some(pkce_request.clone()),
4213 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4214 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4215 nonce: None,
4216 oidc_ext: Default::default(),
4217 max_age: None,
4218 unknown_keys: Default::default(),
4219 };
4220
4221 let req = idms_prox_read
4222 .check_oauth2_authorisation(None, &auth_req, ct)
4223 .unwrap();
4224
4225 assert!(matches!(
4226 req,
4227 AuthoriseResponse::AuthenticationRequired { .. }
4228 ));
4229
4230 let auth_req = AuthorisationRequest {
4232 response_type: ResponseType::Code,
4233 response_mode: None,
4234 client_id: "test_resource_server".to_string(),
4235 state: Some("123".to_string()),
4236 pkce_request: Some(pkce_request.clone()),
4237 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4238 scope: btreeset!["invalid_scope".to_string(), "read".to_string()],
4239 nonce: None,
4240 oidc_ext: Default::default(),
4241 max_age: None,
4242 unknown_keys: Default::default(),
4243 };
4244
4245 assert!(
4246 idms_prox_read
4247 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4248 .unwrap_err()
4249 == Oauth2Error::AccessDenied
4250 );
4251
4252 let auth_req = AuthorisationRequest {
4254 response_type: ResponseType::Code,
4255 response_mode: None,
4256 client_id: "test_resource_server".to_string(),
4257 state: Some("123".to_string()),
4258 pkce_request: Some(pkce_request.clone()),
4259 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4260 scope: btreeset!["openid".to_string(), "read".to_string()],
4261 nonce: None,
4262 oidc_ext: Default::default(),
4263 max_age: None,
4264 unknown_keys: Default::default(),
4265 };
4266
4267 assert!(
4268 idms_prox_read
4269 .check_oauth2_authorisation(Some(&idm_admin_ident), &auth_req, ct)
4270 .unwrap_err()
4271 == Oauth2Error::AccessDenied
4272 );
4273
4274 let auth_req = AuthorisationRequest {
4276 response_type: ResponseType::Code,
4277 response_mode: None,
4278 client_id: "test_resource_server".to_string(),
4279 state: Some("123".to_string()),
4280 pkce_request: Some(pkce_request),
4281 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4282 scope: btreeset!["openid".to_string(), "read".to_string()],
4283 nonce: None,
4284 oidc_ext: Default::default(),
4285 max_age: None,
4286 unknown_keys: Default::default(),
4287 };
4288
4289 assert!(
4290 idms_prox_read
4291 .check_oauth2_authorisation(Some(&anon_ident), &auth_req, ct)
4292 .unwrap_err()
4293 == Oauth2Error::AccessDenied
4294 );
4295 }
4296
4297 #[idm_test]
4298 async fn test_idm_oauth2_invalid_authorisation_permit_requests(
4299 idms: &IdmServer,
4300 _idms_delayed: &mut IdmServerDelayed,
4301 ) {
4302 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4304 let (_secret, uat, ident, _) =
4305 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4306
4307 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4308
4309 let mut uat_wrong_session_id = uat.clone();
4310 uat_wrong_session_id.session_id = uuid::Uuid::new_v4();
4311 let ident_wrong_session_id = idms_prox_write
4312 .process_uat_to_identity(&uat_wrong_session_id, ct, Source::Internal)
4313 .expect("Unable to process uat");
4314
4315 let account = idms_prox_write
4316 .target_to_account(UUID_IDM_ADMIN)
4317 .expect("account must exist");
4318 let session_id = uuid::Uuid::new_v4();
4319 let uat2 = account
4320 .to_userauthtoken(
4321 session_id,
4322 SessionScope::ReadWrite,
4323 ct,
4324 &ResolvedAccountPolicy::test_policy(),
4325 )
4326 .expect("Unable to create uat");
4327 let ident2 = idms_prox_write
4328 .process_uat_to_identity(&uat2, ct, Source::Internal)
4329 .expect("Unable to process uat");
4330
4331 assert!(idms_prox_write.commit().is_ok());
4332
4333 let idms_prox_read = idms.proxy_read().await.unwrap();
4336
4337 let pkce_secret = PkceS256Secret::default();
4338
4339 let consent_request = good_authorisation_request!(
4340 idms_prox_read,
4341 &ident,
4342 ct,
4343 pkce_secret.to_request(),
4344 OAUTH2_SCOPE_OPENID.to_string()
4345 );
4346
4347 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4348 unreachable!();
4349 };
4350
4351 drop(idms_prox_read);
4352 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4353
4354 assert!(
4357 idms_prox_write
4358 .check_oauth2_authorise_permit(
4359 &ident,
4360 &consent_token,
4361 ct + Duration::from_secs(TOKEN_EXPIRE),
4362 )
4363 .unwrap_err()
4364 == OperationError::CryptographyError
4365 );
4366
4367 assert!(
4372 idms_prox_write
4373 .check_oauth2_authorise_permit(&ident2, &consent_token, ct,)
4374 .unwrap_err()
4375 == OperationError::InvalidSessionState
4376 );
4377
4378 assert!(
4380 idms_prox_write
4381 .check_oauth2_authorise_permit(&ident_wrong_session_id, &consent_token, ct,)
4382 .unwrap_err()
4383 == OperationError::InvalidSessionState
4384 );
4385
4386 assert!(idms_prox_write.commit().is_ok());
4387 }
4388
4389 #[idm_test]
4390 async fn test_idm_oauth2_invalid_token_exchange_requests(
4391 idms: &IdmServer,
4392 _idms_delayed: &mut IdmServerDelayed,
4393 ) {
4394 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4395 let (secret, mut uat, ident, _) =
4396 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4397
4398 uat.expiry = Some(
4408 time::OffsetDateTime::UNIX_EPOCH
4409 + Duration::from_secs(TEST_CURRENT_TIME + UAT_EXPIRE - 1),
4410 );
4411
4412 let idms_prox_read = idms.proxy_read().await.unwrap();
4413
4414 let pkce_secret = PkceS256Secret::default();
4416
4417 let consent_request = good_authorisation_request!(
4418 idms_prox_read,
4419 &ident,
4420 ct,
4421 pkce_secret.to_request(),
4422 OAUTH2_SCOPE_OPENID.to_string()
4423 );
4424
4425 let code_verifier = Some(pkce_secret.to_verifier());
4426
4427 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4428 unreachable!();
4429 };
4430
4431 drop(idms_prox_read);
4432 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4433
4434 let permit_success = idms_prox_write
4436 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4437 .expect("Failed to perform OAuth2 permit");
4438
4439 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4444 code: permit_success.code.clone(),
4445 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4446 code_verifier: code_verifier.clone(),
4447 }
4448 .into();
4449
4450 let client_authz = ClientAuthInfo::from("not base64");
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 =
4461 general_purpose::STANDARD.encode(format!("test_resource_server {secret}"));
4462 let client_authz = ClientAuthInfo::from(client_authz.as_str());
4463
4464 assert!(
4465 idms_prox_write
4466 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4467 .unwrap_err()
4468 == Oauth2Error::AuthenticationRequired
4469 );
4470
4471 let client_authz = ClientAuthInfo::encode_basic("NOT A REAL SERVER", secret.as_str());
4473
4474 assert!(
4475 idms_prox_write
4476 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4477 .unwrap_err()
4478 == Oauth2Error::AuthenticationRequired
4479 );
4480
4481 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
4483
4484 assert!(
4485 idms_prox_write
4486 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4487 .unwrap_err()
4488 == Oauth2Error::AuthenticationRequired
4489 );
4490
4491 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4493
4494 assert!(
4496 idms_prox_write
4497 .check_oauth2_token_exchange(
4498 &client_authz,
4499 &token_req,
4500 ct + Duration::from_secs(TOKEN_EXPIRE)
4501 )
4502 .unwrap_err()
4503 == Oauth2Error::InvalidRequest
4504 );
4505
4506 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4527 code: permit_success.code.clone(),
4528 redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4529 code_verifier: code_verifier.clone(),
4530 }
4531 .into();
4532 assert!(
4533 idms_prox_write
4534 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4535 .unwrap_err()
4536 == Oauth2Error::InvalidOrigin
4537 );
4538
4539 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4541 code: permit_success.code,
4542 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4543 code_verifier: Some("12345".to_string()),
4544 }
4545 .into();
4546 assert!(
4547 idms_prox_write
4548 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4549 .unwrap_err()
4550 == Oauth2Error::InvalidRequest
4551 );
4552
4553 assert!(idms_prox_write.commit().is_ok());
4554 }
4555
4556 #[idm_test]
4557 async fn test_idm_oauth2_supplemental_origin_redirect(
4558 idms: &IdmServer,
4559 _idms_delayed: &mut IdmServerDelayed,
4560 ) {
4561 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4562 let (secret, uat, ident, _) =
4563 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4564
4565 let idms_prox_read = idms.proxy_read().await.unwrap();
4566
4567 let pkce_secret = PkceS256Secret::default();
4569
4570 let redirect_uri = Url::parse("https://portal.example.com/?custom=foo").unwrap();
4571
4572 let auth_req = AuthorisationRequest {
4573 response_type: ResponseType::Code,
4574 response_mode: None,
4575 client_id: "test_resource_server".to_string(),
4576 state: None,
4577 pkce_request: Some(pkce_secret.to_request()),
4578 redirect_uri: redirect_uri.clone(),
4579 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4580 nonce: Some("abcdef".to_string()),
4581 oidc_ext: Default::default(),
4582 max_age: None,
4583 unknown_keys: Default::default(),
4584 };
4585
4586 let consent_request = idms_prox_read
4587 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4588 .expect("OAuth2 authorisation failed");
4589
4590 trace!(?consent_request);
4591
4592 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4594 unreachable!();
4595 };
4596
4597 drop(idms_prox_read);
4599 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4600
4601 let permit_success = idms_prox_write
4602 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4603 .expect("Failed to perform OAuth2 permit");
4604
4605 assert_eq!(permit_success.state.as_deref(), None);
4607
4608 let permit_redirect_uri = permit_success.build_redirect_uri();
4611
4612 assert_eq!(permit_redirect_uri.origin(), redirect_uri.origin());
4613 assert_eq!(permit_redirect_uri.path(), redirect_uri.path());
4614 let query = BTreeMap::from_iter(permit_redirect_uri.query_pairs().into_owned());
4615 assert_eq!(query.get("custom").map(|s| s.as_str()), Some("foo"));
4617
4618 let token_req = AccessTokenRequest {
4621 grant_type: GrantTypeReq::AuthorizationCode {
4622 code: permit_success.code,
4623 redirect_uri,
4624 code_verifier: Some(pkce_secret.to_verifier()),
4625 },
4626
4627 client_post_auth: ClientPostAuth {
4628 client_id: Some("test_resource_server".to_string()),
4629 client_secret: Some(secret.clone()),
4630 },
4631 };
4632
4633 let token_response = idms_prox_write
4634 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4635 .expect("Failed to perform OAuth2 token exchange");
4636
4637 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4639
4640 assert!(idms_prox_write.commit().is_ok());
4641
4642 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4646
4647 let ident = idms_prox_read
4649 .process_uat_to_identity(&uat, ct, Source::Internal)
4650 .expect("Unable to process uat");
4651
4652 let pkce_secret = PkceS256Secret::default();
4653
4654 let auth_req = AuthorisationRequest {
4655 response_type: ResponseType::Code,
4656 response_mode: None,
4657 client_id: "test_resource_server".to_string(),
4658 state: Some("123".to_string()),
4659 pkce_request: Some(pkce_secret.to_request()),
4660 redirect_uri: Url::parse("app://cheese").unwrap(),
4661 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4662 nonce: Some("abcdef".to_string()),
4663 oidc_ext: Default::default(),
4664 max_age: None,
4665 unknown_keys: Default::default(),
4666 };
4667
4668 let consent_request = idms_prox_read
4669 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4670 .expect("OAuth2 authorisation failed");
4671
4672 trace!(?consent_request);
4673
4674 let AuthoriseResponse::Permitted(permit_success) = consent_request else {
4675 unreachable!();
4676 };
4677
4678 assert_eq!(permit_success.state.as_deref(), Some("123"));
4681
4682 let token_req = AccessTokenRequest {
4685 grant_type: GrantTypeReq::AuthorizationCode {
4686 code: permit_success.code,
4687 redirect_uri: Url::parse("app://cheese").unwrap(),
4688 code_verifier: Some(pkce_secret.to_verifier()),
4689 },
4690
4691 client_post_auth: ClientPostAuth {
4692 client_id: Some("test_resource_server".to_string()),
4693 client_secret: Some(secret),
4694 },
4695 };
4696
4697 drop(idms_prox_read);
4698 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4699
4700 let token_response = idms_prox_write
4701 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4702 .expect("Failed to perform OAuth2 token exchange");
4703
4704 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4706 }
4707
4708 #[idm_test]
4709 async fn test_idm_oauth2_token_introspect(
4710 idms: &IdmServer,
4711 _idms_delayed: &mut IdmServerDelayed,
4712 ) {
4713 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4714 let (secret, _uat, ident, _) =
4715 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4716 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4717
4718 let idms_prox_read = idms.proxy_read().await.unwrap();
4719
4720 let pkce_secret = PkceS256Secret::default();
4722 let consent_request = good_authorisation_request!(
4723 idms_prox_read,
4724 &ident,
4725 ct,
4726 pkce_secret.to_request(),
4727 OAUTH2_SCOPE_OPENID.to_string()
4728 );
4729
4730 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4731 unreachable!();
4732 };
4733
4734 drop(idms_prox_read);
4736 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4737
4738 let permit_success = idms_prox_write
4739 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4740 .expect("Failed to perform OAuth2 permit");
4741
4742 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4743 code: permit_success.code,
4744 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4745 code_verifier: Some(pkce_secret.to_verifier()),
4746 }
4747 .into();
4748 let oauth2_token = idms_prox_write
4749 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4750 .expect("Unable to exchange for OAuth2 token");
4751
4752 assert!(idms_prox_write.commit().is_ok());
4753
4754 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4756
4757 let intr_request = AccessTokenIntrospectRequest {
4758 token: oauth2_token.access_token,
4759 token_type_hint: None,
4760 client_post_auth: ClientPostAuth::default(),
4761 };
4762 let intr_response = idms_prox_read
4763 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4764 .expect("Failed to inspect token");
4765
4766 eprintln!("👉 {intr_response:?}");
4767 assert!(intr_response.active);
4768 assert_eq!(
4769 intr_response.scope,
4770 btreeset!["openid".to_string(), "supplement".to_string()]
4771 );
4772 assert_eq!(
4773 intr_response.client_id.as_deref(),
4774 Some("test_resource_server")
4775 );
4776 assert_eq!(
4777 intr_response.username.as_deref(),
4778 Some("testperson1@example.com")
4779 );
4780 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
4781 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
4782 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
4783
4784 drop(idms_prox_read);
4785 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4788 let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_CURRENT_TIME - 1));
4790 let me_inv_m = ModifyEvent::new_internal_invalid(
4791 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
4792 ModifyList::new_list(vec![Modify::Present(Attribute::AccountExpire, v_expire)]),
4793 );
4794 assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok());
4796 assert!(idms_prox_write.commit().is_ok());
4797
4798 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4801 let intr_response = idms_prox_read
4802 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4803 .expect("Failed to inspect token");
4804
4805 assert!(!intr_response.active);
4806 }
4807
4808 #[idm_test]
4809 async fn test_idm_oauth2_token_revoke(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
4810 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4812 let (secret, _uat, ident, _) =
4813 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4814 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4815
4816 let idms_prox_read = idms.proxy_read().await.unwrap();
4817
4818 let pkce_secret = PkceS256Secret::default();
4820
4821 let consent_request = good_authorisation_request!(
4822 idms_prox_read,
4823 &ident,
4824 ct,
4825 pkce_secret.to_request(),
4826 OAUTH2_SCOPE_OPENID.to_string()
4827 );
4828
4829 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4830 unreachable!();
4831 };
4832
4833 drop(idms_prox_read);
4835 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4836
4837 let permit_success = idms_prox_write
4838 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4839 .expect("Failed to perform OAuth2 permit");
4840
4841 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4843 code: permit_success.code,
4844 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4845 code_verifier: Some(pkce_secret.to_verifier()),
4846 }
4847 .into();
4848 let oauth2_token = idms_prox_write
4849 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4850 .expect("Unable to exchange for OAuth2 token");
4851
4852 assert!(idms_prox_write.commit().is_ok());
4853
4854 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4858 let intr_request = AccessTokenIntrospectRequest {
4859 token: oauth2_token.access_token.clone(),
4860 token_type_hint: None,
4861 client_post_auth: ClientPostAuth::default(),
4862 };
4863 let intr_response = idms_prox_read
4864 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4865 .expect("Failed to inspect token");
4866 eprintln!("👉 {intr_response:?}");
4867 assert!(intr_response.active);
4868 drop(idms_prox_read);
4869
4870 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4872
4873 let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
4874
4875 let revoke_request = TokenRevokeRequest {
4876 token: oauth2_token.access_token.clone(),
4877 token_type_hint: None,
4878 client_post_auth: ClientPostAuth::default(),
4879 };
4880 let e = idms_prox_write
4881 .oauth2_token_revoke(&bad_client_authz, &revoke_request, ct)
4882 .unwrap_err();
4883 assert!(matches!(e, Oauth2Error::AuthenticationRequired));
4884 assert!(idms_prox_write.commit().is_ok());
4885
4886 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4888 let revoke_request = TokenRevokeRequest {
4889 token: "this is an invalid token, nothing will happen!".to_string(),
4890 token_type_hint: None,
4891 client_post_auth: ClientPostAuth::default(),
4892 };
4893 let e = idms_prox_write
4894 .oauth2_token_revoke(&client_authz, &revoke_request, ct)
4895 .unwrap_err();
4896 assert!(matches!(e, Oauth2Error::InvalidRequest));
4897 assert!(idms_prox_write.commit().is_ok());
4898
4899 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4901 let intr_response = idms_prox_read
4902 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4903 .expect("Failed to inspect token");
4904 assert!(intr_response.active);
4905 drop(idms_prox_read);
4906
4907 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4909 let revoke_request = TokenRevokeRequest {
4910 token: oauth2_token.access_token.clone(),
4911 token_type_hint: None,
4912 client_post_auth: ClientPostAuth::default(),
4913 };
4914 assert!(idms_prox_write
4915 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
4916 .is_ok());
4917 assert!(idms_prox_write.commit().is_ok());
4918
4919 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4921 let intr_response = idms_prox_read
4922 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4923 .expect("Failed to inspect token");
4924
4925 assert!(!intr_response.active);
4926 drop(idms_prox_read);
4927
4928 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4930 let filt = filter!(f_eq(
4931 Attribute::Uuid,
4932 PartialValue::Uuid(ident.get_uuid().unwrap())
4933 ));
4934 let mut work_set = idms_prox_write
4935 .qs_write
4936 .internal_search_writeable(&filt)
4937 .expect("Failed to perform internal search writeable");
4938 for (_, entry) in work_set.iter_mut() {
4939 let _ = entry.force_trim_ava(Attribute::OAuth2Session);
4940 }
4941 assert!(idms_prox_write
4942 .qs_write
4943 .internal_apply_writable(work_set)
4944 .is_ok());
4945
4946 assert!(idms_prox_write.commit().is_ok());
4947
4948 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4949 let intr_response = idms_prox_read
4951 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4952 .expect("Failed to inspect token");
4953 assert!(intr_response.active);
4954
4955 let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
4957 let intr_response = idms_prox_read
4958 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4959 .expect("Failed to inspect token");
4960 assert!(!intr_response.active);
4961
4962 drop(idms_prox_read);
4963
4964 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4966 let revoke_request = TokenRevokeRequest {
4967 token: oauth2_token.access_token,
4968 token_type_hint: None,
4969 client_post_auth: ClientPostAuth::default(),
4970 };
4971 assert!(idms_prox_write
4972 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
4973 .is_ok());
4974 assert!(idms_prox_write.commit().is_ok());
4975 }
4976
4977 #[idm_test]
4978 async fn test_idm_oauth2_session_cleanup_post_rs_delete(
4979 idms: &IdmServer,
4980 _idms_delayed: &mut IdmServerDelayed,
4981 ) {
4982 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4984 let (secret, _uat, ident, _) =
4985 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4986 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4987
4988 let idms_prox_read = idms.proxy_read().await.unwrap();
4989
4990 let pkce_secret = PkceS256Secret::default();
4992
4993 let consent_request = good_authorisation_request!(
4994 idms_prox_read,
4995 &ident,
4996 ct,
4997 pkce_secret.to_request(),
4998 OAUTH2_SCOPE_OPENID.to_string()
4999 );
5000
5001 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5002 unreachable!();
5003 };
5004
5005 drop(idms_prox_read);
5007 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5008
5009 let permit_success = idms_prox_write
5010 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5011 .expect("Failed to perform OAuth2 permit");
5012
5013 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5014 code: permit_success.code,
5015 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5016 code_verifier: Some(pkce_secret.to_verifier()),
5017 }
5018 .into();
5019
5020 let oauth2_token = idms_prox_write
5021 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5022 .expect("Unable to exchange for OAuth2 token");
5023
5024 let access_token =
5025 JwsCompact::from_str(&oauth2_token.access_token).expect("Invalid Access Token");
5026
5027 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
5028
5029 let reflected_token = jws_verifier
5030 .verify(&access_token)
5031 .unwrap()
5032 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
5033 .expect("Failed to access internals of the refresh token");
5034
5035 let session_id = reflected_token.extensions.session_id;
5036
5037 assert!(idms_prox_write.commit().is_ok());
5038
5039 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5041
5042 let entry = idms_prox_write
5044 .qs_write
5045 .internal_search_uuid(UUID_TESTPERSON_1)
5046 .expect("failed");
5047 let valid = entry
5048 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
5049 .map(|map| map.get(&session_id).is_some())
5050 .unwrap_or(false);
5051 assert!(valid);
5052
5053 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
5056 Attribute::Name,
5057 PartialValue::new_iname("test_resource_server")
5058 )));
5059
5060 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
5061
5062 let entry = idms_prox_write
5066 .qs_write
5067 .internal_search_uuid(UUID_TESTPERSON_1)
5068 .expect("failed");
5069 let revoked = entry
5070 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
5071 .and_then(|sessions| sessions.get(&session_id))
5072 .map(|session| matches!(session.state, SessionState::RevokedAt(_)))
5073 .unwrap_or(false);
5074 assert!(revoked);
5075
5076 assert!(idms_prox_write.commit().is_ok());
5077 }
5078
5079 #[idm_test]
5080 async fn test_idm_oauth2_authorisation_reject(
5081 idms: &IdmServer,
5082 _idms_delayed: &mut IdmServerDelayed,
5083 ) {
5084 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5085 let (_secret, _uat, ident, _) =
5086 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5087
5088 let ident2 = {
5089 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5090 let account = idms_prox_write
5091 .target_to_account(UUID_IDM_ADMIN)
5092 .expect("account must exist");
5093 let session_id = uuid::Uuid::new_v4();
5094 let uat2 = account
5095 .to_userauthtoken(
5096 session_id,
5097 SessionScope::ReadWrite,
5098 ct,
5099 &ResolvedAccountPolicy::test_policy(),
5100 )
5101 .expect("Unable to create uat");
5102
5103 idms_prox_write
5104 .process_uat_to_identity(&uat2, ct, Source::Internal)
5105 .expect("Unable to process uat")
5106 };
5107
5108 let idms_prox_read = idms.proxy_read().await.unwrap();
5109 let redirect_uri = Url::parse("https://demo.example.com/oauth2/result").unwrap();
5110
5111 let pkce_secret = PkceS256Secret::default();
5112
5113 let consent_request = good_authorisation_request!(
5115 idms_prox_read,
5116 &ident,
5117 ct,
5118 pkce_secret.to_request(),
5119 OAUTH2_SCOPE_OPENID.to_string()
5120 );
5121
5122 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5123 unreachable!();
5124 };
5125
5126 let reject_success = idms_prox_read
5127 .check_oauth2_authorise_reject(&ident, &consent_token, ct)
5128 .expect("Failed to perform OAuth2 reject");
5129
5130 assert_eq!(reject_success.redirect_uri, redirect_uri);
5131
5132 let past_ct = Duration::from_secs(TEST_CURRENT_TIME + 301);
5134 assert!(
5135 idms_prox_read
5136 .check_oauth2_authorise_reject(&ident, &consent_token, past_ct)
5137 .unwrap_err()
5138 == OperationError::CryptographyError
5139 );
5140
5141 assert_eq!(
5143 idms_prox_read
5144 .check_oauth2_authorise_reject(&ident, "not a token", ct)
5145 .unwrap_err(),
5146 OperationError::CryptographyError
5147 );
5148
5149 assert!(
5151 idms_prox_read
5152 .check_oauth2_authorise_reject(&ident2, &consent_token, ct)
5153 .unwrap_err()
5154 == OperationError::InvalidSessionState
5155 );
5156 }
5157
5158 #[idm_test]
5159 async fn test_idm_oauth2_rfc8414_metadata(
5160 idms: &IdmServer,
5161 _idms_delayed: &mut IdmServerDelayed,
5162 ) {
5163 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5164 let (_secret, _uat, _ident, _) =
5165 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5166
5167 let idms_prox_read = idms.proxy_read().await.unwrap();
5168
5169 assert!(
5171 idms_prox_read
5172 .oauth2_rfc8414_metadata("nosuchclient")
5173 .unwrap_err()
5174 == OperationError::NoMatchingEntries
5175 );
5176
5177 let discovery = idms_prox_read
5178 .oauth2_rfc8414_metadata("test_resource_server")
5179 .expect("Failed to get discovery");
5180
5181 assert!(
5182 discovery.issuer
5183 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5184 .unwrap()
5185 );
5186
5187 assert!(
5188 discovery.authorization_endpoint
5189 == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
5190 );
5191
5192 assert!(
5193 discovery.token_endpoint
5194 == Url::parse(&format!(
5195 "https://idm.example.com{}",
5196 uri::OAUTH2_TOKEN_ENDPOINT
5197 ))
5198 .unwrap()
5199 );
5200
5201 assert!(
5202 discovery.jwks_uri
5203 == Some(
5204 Url::parse(
5205 "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
5206 )
5207 .unwrap()
5208 )
5209 );
5210
5211 assert!(discovery.registration_endpoint.is_none());
5212
5213 assert!(
5214 discovery.scopes_supported
5215 == Some(vec![
5216 OAUTH2_SCOPE_GROUPS.to_string(),
5217 OAUTH2_SCOPE_OPENID.to_string(),
5218 OAUTH2_SCOPE_PROFILE.to_string(),
5219 "supplement".to_string(),
5220 ])
5221 );
5222
5223 assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5224 assert_eq!(
5225 discovery.response_modes_supported,
5226 vec![ResponseMode::Query, ResponseMode::Fragment]
5227 );
5228 assert_eq!(
5229 discovery.grant_types_supported,
5230 vec![GrantType::AuthorisationCode, GrantType::TokenExchange]
5231 );
5232 assert!(
5233 discovery.token_endpoint_auth_methods_supported
5234 == vec![
5235 TokenEndpointAuthMethod::ClientSecretBasic,
5236 TokenEndpointAuthMethod::ClientSecretPost
5237 ]
5238 );
5239 assert!(discovery.service_documentation.is_some());
5240
5241 assert!(discovery.ui_locales_supported.is_none());
5242 assert!(discovery.op_policy_uri.is_none());
5243 assert!(discovery.op_tos_uri.is_none());
5244
5245 assert!(
5246 discovery.revocation_endpoint
5247 == Some(
5248 Url::parse(&format!(
5249 "https://idm.example.com{OAUTH2_TOKEN_REVOKE_ENDPOINT}"
5250 ))
5251 .unwrap()
5252 )
5253 );
5254 assert!(
5255 discovery.revocation_endpoint_auth_methods_supported
5256 == vec![
5257 TokenEndpointAuthMethod::ClientSecretBasic,
5258 TokenEndpointAuthMethod::ClientSecretPost
5259 ]
5260 );
5261
5262 assert!(
5263 discovery.introspection_endpoint
5264 == Some(
5265 Url::parse(&format!(
5266 "https://idm.example.com{}",
5267 kanidm_proto::constants::uri::OAUTH2_TOKEN_INTROSPECT_ENDPOINT
5268 ))
5269 .unwrap()
5270 )
5271 );
5272 assert!(
5273 discovery.introspection_endpoint_auth_methods_supported
5274 == vec![
5275 TokenEndpointAuthMethod::ClientSecretBasic,
5276 TokenEndpointAuthMethod::ClientSecretPost
5277 ]
5278 );
5279 assert!(discovery
5280 .introspection_endpoint_auth_signing_alg_values_supported
5281 .is_none());
5282
5283 assert_eq!(
5284 discovery.code_challenge_methods_supported,
5285 vec![PkceAlg::S256]
5286 )
5287 }
5288
5289 #[idm_test]
5290 async fn test_idm_oauth2_openid_discovery(
5291 idms: &IdmServer,
5292 _idms_delayed: &mut IdmServerDelayed,
5293 ) {
5294 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5295 let (_secret, _uat, _ident, _) =
5296 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5297
5298 let idms_prox_read = idms.proxy_read().await.unwrap();
5299
5300 assert!(
5302 idms_prox_read
5303 .oauth2_openid_discovery("nosuchclient")
5304 .unwrap_err()
5305 == OperationError::NoMatchingEntries
5306 );
5307
5308 assert!(
5309 idms_prox_read
5310 .oauth2_openid_publickey("nosuchclient")
5311 .unwrap_err()
5312 == OperationError::NoMatchingEntries
5313 );
5314
5315 let discovery = idms_prox_read
5316 .oauth2_openid_discovery("test_resource_server")
5317 .expect("Failed to get discovery");
5318
5319 let mut jwkset = idms_prox_read
5320 .oauth2_openid_publickey("test_resource_server")
5321 .expect("Failed to get public key");
5322
5323 let jwk = jwkset.keys.pop().expect("no such jwk");
5324
5325 match jwk {
5326 Jwk::EC { alg, use_, kid, .. } => {
5327 match (
5328 alg.unwrap(),
5329 &discovery.id_token_signing_alg_values_supported[0],
5330 ) {
5331 (JwaAlg::ES256, IdTokenSignAlg::ES256) => {}
5332 _ => panic!(),
5333 };
5334 assert_eq!(use_.unwrap(), JwkUse::Sig);
5335 assert!(kid.is_some())
5336 }
5337 _ => panic!(),
5338 };
5339
5340 assert!(
5341 discovery.issuer
5342 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5343 .unwrap()
5344 );
5345
5346 assert!(
5347 discovery.authorization_endpoint
5348 == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
5349 );
5350
5351 assert!(
5352 discovery.token_endpoint == Url::parse("https://idm.example.com/oauth2/token").unwrap()
5353 );
5354
5355 assert!(
5356 discovery.userinfo_endpoint
5357 == Some(
5358 Url::parse(
5359 "https://idm.example.com/oauth2/openid/test_resource_server/userinfo"
5360 )
5361 .unwrap()
5362 )
5363 );
5364
5365 assert!(
5366 discovery.jwks_uri
5367 == Url::parse(
5368 "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
5369 )
5370 .unwrap()
5371 );
5372
5373 assert!(
5374 discovery.scopes_supported
5375 == Some(vec![
5376 OAUTH2_SCOPE_GROUPS.to_string(),
5377 OAUTH2_SCOPE_OPENID.to_string(),
5378 OAUTH2_SCOPE_PROFILE.to_string(),
5379 "supplement".to_string(),
5380 ])
5381 );
5382
5383 assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5384 assert_eq!(
5385 discovery.response_modes_supported,
5386 vec![ResponseMode::Query, ResponseMode::Fragment]
5387 );
5388 assert_eq!(
5389 discovery.grant_types_supported,
5390 vec![GrantType::AuthorisationCode, GrantType::TokenExchange]
5391 );
5392 assert_eq!(discovery.subject_types_supported, vec![SubjectType::Public]);
5393 assert_eq!(
5394 discovery.id_token_signing_alg_values_supported,
5395 vec![IdTokenSignAlg::ES256]
5396 );
5397 assert!(discovery.userinfo_signing_alg_values_supported.is_none());
5398 assert!(
5399 discovery.token_endpoint_auth_methods_supported
5400 == vec![
5401 TokenEndpointAuthMethod::ClientSecretBasic,
5402 TokenEndpointAuthMethod::ClientSecretPost
5403 ]
5404 );
5405 assert_eq!(
5406 discovery.display_values_supported,
5407 Some(vec![DisplayValue::Page])
5408 );
5409 assert_eq!(discovery.claim_types_supported, vec![ClaimType::Normal]);
5410 assert!(discovery.claims_supported.is_none());
5411 assert!(discovery.service_documentation.is_some());
5412
5413 assert!(discovery.registration_endpoint.is_none());
5414 assert!(discovery.acr_values_supported.is_none());
5415 assert!(discovery.id_token_encryption_alg_values_supported.is_none());
5416 assert!(discovery.id_token_encryption_enc_values_supported.is_none());
5417 assert!(discovery.userinfo_encryption_alg_values_supported.is_none());
5418 assert!(discovery.userinfo_encryption_enc_values_supported.is_none());
5419 assert!(discovery
5420 .request_object_signing_alg_values_supported
5421 .is_none());
5422 assert!(discovery
5423 .request_object_encryption_alg_values_supported
5424 .is_none());
5425 assert!(discovery
5426 .request_object_encryption_enc_values_supported
5427 .is_none());
5428 assert!(discovery
5429 .token_endpoint_auth_signing_alg_values_supported
5430 .is_none());
5431 assert!(discovery.claims_locales_supported.is_none());
5432 assert!(discovery.ui_locales_supported.is_none());
5433 assert!(discovery.op_policy_uri.is_none());
5434 assert!(discovery.op_tos_uri.is_none());
5435 assert!(!discovery.claims_parameter_supported);
5436 assert!(!discovery.request_uri_parameter_supported);
5437 assert!(!discovery.require_request_uri_registration);
5438 assert!(!discovery.request_parameter_supported);
5439 assert_eq!(
5440 discovery.code_challenge_methods_supported,
5441 vec![PkceAlg::S256]
5442 );
5443
5444 assert!(
5446 discovery.revocation_endpoint
5447 == Some(
5448 Url::parse(&format!(
5449 "https://idm.example.com{OAUTH2_TOKEN_REVOKE_ENDPOINT}"
5450 ))
5451 .unwrap()
5452 )
5453 );
5454 assert!(
5455 discovery.revocation_endpoint_auth_methods_supported
5456 == vec![
5457 TokenEndpointAuthMethod::ClientSecretBasic,
5458 TokenEndpointAuthMethod::ClientSecretPost
5459 ]
5460 );
5461
5462 assert!(
5463 discovery.introspection_endpoint
5464 == Some(
5465 Url::parse(&format!(
5466 "https://idm.example.com{OAUTH2_TOKEN_INTROSPECT_ENDPOINT}"
5467 ))
5468 .unwrap()
5469 )
5470 );
5471 assert!(
5472 discovery.introspection_endpoint_auth_methods_supported
5473 == vec![
5474 TokenEndpointAuthMethod::ClientSecretBasic,
5475 TokenEndpointAuthMethod::ClientSecretPost
5476 ]
5477 );
5478 assert!(discovery
5479 .introspection_endpoint_auth_signing_alg_values_supported
5480 .is_none());
5481 }
5482
5483 #[idm_test]
5484 async fn test_idm_oauth2_openid_extensions(
5485 idms: &IdmServer,
5486 _idms_delayed: &mut IdmServerDelayed,
5487 ) {
5488 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5489 let (secret, _uat, ident, _) =
5490 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5491 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5492
5493 let idms_prox_read = idms.proxy_read().await.unwrap();
5494
5495 let pkce_secret = PkceS256Secret::default();
5496
5497 let consent_request = good_authorisation_request!(
5498 idms_prox_read,
5499 &ident,
5500 ct,
5501 pkce_secret.to_request(),
5502 format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_PROFILE}")
5503 );
5504
5505 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5506 unreachable!();
5507 };
5508
5509 drop(idms_prox_read);
5511 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5512
5513 let permit_success = idms_prox_write
5514 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5515 .expect("Failed to perform OAuth2 permit");
5516
5517 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5519 code: permit_success.code,
5520 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5521 code_verifier: Some(pkce_secret.to_verifier()),
5522 }
5523 .into();
5524
5525 let token_response = idms_prox_write
5526 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5527 .expect("Failed to perform OAuth2 token exchange");
5528
5529 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
5531
5532 let id_token = token_response.id_token.expect("No id_token in response!");
5533
5534 let access_token =
5535 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5536
5537 let refresh_token = token_response
5538 .refresh_token
5539 .as_ref()
5540 .expect("no refresh token was issued")
5541 .clone();
5542
5543 assert!(idms_prox_write.commit().is_ok());
5545
5546 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5547
5548 let mut jwkset = idms_prox_read
5549 .oauth2_openid_publickey("test_resource_server")
5550 .expect("Failed to get public key");
5551
5552 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5553
5554 let jws_validator =
5555 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5556
5557 let oidc_unverified =
5558 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5559
5560 let iat = ct.as_secs() as i64;
5561
5562 let oidc = jws_validator
5563 .verify(&oidc_unverified)
5564 .unwrap()
5565 .verify_exp(iat)
5566 .expect("Failed to verify oidc");
5567
5568 assert!(
5570 oidc.iss
5571 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5572 .unwrap()
5573 );
5574 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
5575 assert_eq!(oidc.aud, "test_resource_server");
5576 assert_eq!(oidc.iat, iat);
5577 assert_eq!(oidc.nbf, Some(iat));
5578 assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
5580 assert!(oidc.auth_time.is_none());
5581 assert_eq!(oidc.nonce, Some("abcdef".to_string()));
5583 assert!(oidc.at_hash.is_none());
5584 assert!(oidc.acr.is_none());
5585 assert!(oidc.amr.is_none());
5586 assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
5587 assert!(oidc.jti.is_some());
5588 if let Some(jti) = &oidc.jti {
5589 assert!(Uuid::from_str(jti).is_ok());
5590 }
5591 assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
5592 assert_eq!(
5593 oidc.s_claims.preferred_username,
5594 Some("testperson1@example.com".to_string())
5595 );
5596 assert!(
5597 oidc.s_claims.scopes
5598 == vec![
5599 OAUTH2_SCOPE_OPENID.to_string(),
5600 OAUTH2_SCOPE_PROFILE.to_string(),
5601 "supplement".to_string()
5602 ]
5603 );
5604 assert!(oidc.claims.is_empty());
5605 let userinfo = idms_prox_read
5608 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5609 .expect("failed to get userinfo");
5610
5611 assert_eq!(oidc.iss, userinfo.iss);
5612 assert_eq!(oidc.sub, userinfo.sub);
5613 assert_eq!(oidc.aud, userinfo.aud);
5614 assert_eq!(oidc.iat, userinfo.iat);
5615 assert_eq!(oidc.nbf, userinfo.nbf);
5616 assert_eq!(oidc.exp, userinfo.exp);
5617 assert!(userinfo.auth_time.is_none());
5618 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5619 assert!(userinfo.at_hash.is_none());
5620 assert!(userinfo.acr.is_none());
5621 assert_eq!(oidc.amr, userinfo.amr);
5622 assert_eq!(oidc.azp, userinfo.azp);
5623 assert!(userinfo.jti.is_some());
5624 if let Some(jti) = &userinfo.jti {
5625 assert!(Uuid::from_str(jti).is_ok());
5626 }
5627 assert_eq!(oidc.s_claims, userinfo.s_claims);
5628 assert!(userinfo.claims.is_empty());
5629
5630 drop(idms_prox_read);
5631
5632 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5636
5637 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
5638 refresh_token,
5639 scope: None,
5640 }
5641 .into();
5642
5643 let token_response = idms_prox_write
5644 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5645 .expect("Unable to exchange for OAuth2 token");
5646
5647 let access_token =
5648 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5649
5650 assert!(idms_prox_write.commit().is_ok());
5651
5652 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5654
5655 let userinfo = idms_prox_read
5656 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5657 .expect("failed to get userinfo");
5658
5659 assert_eq!(oidc.iss, userinfo.iss);
5660 assert_eq!(oidc.sub, userinfo.sub);
5661 assert_eq!(oidc.aud, userinfo.aud);
5662 assert_eq!(oidc.iat, userinfo.iat);
5663 assert_eq!(oidc.nbf, userinfo.nbf);
5664 assert_eq!(oidc.exp, userinfo.exp);
5665 assert!(userinfo.auth_time.is_none());
5666 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5667 assert!(userinfo.at_hash.is_none());
5668 assert!(userinfo.acr.is_none());
5669 assert_eq!(oidc.amr, userinfo.amr);
5670 assert_eq!(oidc.azp, userinfo.azp);
5671 assert!(userinfo.jti.is_some());
5672 if let Some(jti) = &userinfo.jti {
5673 assert!(Uuid::from_str(jti).is_ok());
5674 }
5675 assert_eq!(oidc.s_claims, userinfo.s_claims);
5676 assert!(userinfo.claims.is_empty());
5677 }
5678
5679 #[idm_test]
5680 async fn test_idm_oauth2_openid_short_username(
5681 idms: &IdmServer,
5682 _idms_delayed: &mut IdmServerDelayed,
5683 ) {
5684 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5687 let (secret, _uat, ident, _) =
5688 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5689 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5690
5691 let idms_prox_read = idms.proxy_read().await.unwrap();
5692
5693 let pkce_secret = PkceS256Secret::default();
5694
5695 let consent_request = good_authorisation_request!(
5696 idms_prox_read,
5697 &ident,
5698 ct,
5699 pkce_secret.to_request(),
5700 OAUTH2_SCOPE_OPENID.to_string()
5701 );
5702
5703 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5704 unreachable!();
5705 };
5706
5707 drop(idms_prox_read);
5709 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5710
5711 let permit_success = idms_prox_write
5712 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5713 .expect("Failed to perform OAuth2 permit");
5714
5715 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5717 code: permit_success.code,
5718 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5719 code_verifier: Some(pkce_secret.to_verifier()),
5720 }
5721 .into();
5722
5723 let token_response = idms_prox_write
5724 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5725 .expect("Failed to perform OAuth2 token exchange");
5726
5727 let id_token = token_response.id_token.expect("No id_token in response!");
5728 let access_token =
5729 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5730
5731 assert!(idms_prox_write.commit().is_ok());
5732 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5733
5734 let mut jwkset = idms_prox_read
5735 .oauth2_openid_publickey("test_resource_server")
5736 .expect("Failed to get public key");
5737 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5738
5739 let jws_validator =
5740 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5741
5742 let oidc_unverified =
5743 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5744
5745 let iat = ct.as_secs() as i64;
5746
5747 let oidc = jws_validator
5748 .verify(&oidc_unverified)
5749 .unwrap()
5750 .verify_exp(iat)
5751 .expect("Failed to verify oidc");
5752
5753 assert_eq!(
5755 oidc.s_claims.preferred_username,
5756 Some("testperson1".to_string())
5757 );
5758 let userinfo = idms_prox_read
5760 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5761 .expect("failed to get userinfo");
5762
5763 assert_eq!(oidc.s_claims, userinfo.s_claims);
5764 }
5765
5766 #[idm_test]
5767 async fn test_idm_oauth2_openid_group_claims(
5768 idms: &IdmServer,
5769 _idms_delayed: &mut IdmServerDelayed,
5770 ) {
5771 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5774 let (secret, _uat, ident, _) =
5775 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5776
5777 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5778
5779 let token_response = perform_oauth2_exchange(
5780 idms,
5781 &ident,
5782 ct,
5783 client_authz,
5784 format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_GROUPS}"),
5785 )
5786 .await;
5787
5788 let id_token = token_response.id_token.expect("No id_token in response!");
5789 let access_token =
5790 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5791
5792 let oidc = validate_id_token(idms, ct, &id_token).await;
5793
5794 assert!(oidc.claims.contains_key("groups"));
5796
5797 assert!(oidc
5798 .claims
5799 .get("groups")
5800 .expect("unable to find key")
5801 .as_array()
5802 .unwrap()
5803 .contains(&serde_json::json!(STR_UUID_IDM_ALL_ACCOUNTS)));
5804
5805 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5806
5807 let userinfo = idms_prox_read
5809 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5810 .expect("failed to get userinfo");
5811
5812 assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
5814 }
5815
5816 #[idm_test]
5817 async fn test_idm_oauth2_openid_group_extended_claims(
5818 idms: &IdmServer,
5819 _idms_delayed: &mut IdmServerDelayed,
5820 ) {
5821 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5824 let (secret, _uat, ident, oauth2_client_uuid) =
5825 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5826
5827 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5829
5830 let modlist = ModifyList::new_list(vec![
5831 Modify::Removed(
5832 Attribute::OAuth2RsScopeMap,
5833 PartialValue::Refer(UUID_TESTGROUP),
5834 ),
5835 Modify::Present(
5836 Attribute::OAuth2RsScopeMap,
5837 Value::new_oauthscopemap(
5838 UUID_TESTGROUP,
5839 btreeset![OAUTH2_SCOPE_GROUPS_NAME.to_string()],
5840 )
5841 .expect("invalid oauthscope"),
5842 ),
5843 ]);
5844
5845 idms_prox_write
5846 .qs_write
5847 .internal_modify_uuid(oauth2_client_uuid, &modlist)
5848 .expect("Failed to modify scopes");
5849
5850 idms_prox_write.commit().expect("failed to commit");
5851
5852 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5854
5855 let token_response = perform_oauth2_exchange(
5856 idms,
5857 &ident,
5858 ct,
5859 client_authz,
5860 format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_GROUPS_NAME}"),
5861 )
5862 .await;
5863
5864 let id_token = token_response.id_token.expect("No id_token in response!");
5865 let access_token =
5866 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5867
5868 let oidc = validate_id_token(idms, ct, &id_token).await;
5869
5870 assert!(oidc.claims.contains_key("groups"));
5872
5873 assert!(oidc
5874 .claims
5875 .get("groups")
5876 .expect("unable to find key")
5877 .as_array()
5878 .unwrap()
5879 .contains(&serde_json::json!("testgroup")));
5880
5881 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5882
5883 let userinfo = idms_prox_read
5885 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5886 .expect("failed to get userinfo");
5887
5888 assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
5890 }
5891
5892 #[idm_test]
5893 async fn test_idm_oauth2_openid_ssh_publickey_claim(
5894 idms: &IdmServer,
5895 _idms_delayed: &mut IdmServerDelayed,
5896 ) {
5897 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5898 let (secret, _uat, ident, client_uuid) =
5899 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5900
5901 const ECDSA_SSH_PUBLIC_KEY: &str = "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3BtOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81lzPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@amethyst";
5904 let ssh_pubkey = SshPublicKey::from_string(ECDSA_SSH_PUBLIC_KEY).unwrap();
5905
5906 let scope_set = BTreeSet::from([OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string()]);
5907
5908 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5909
5910 idms_prox_write
5911 .qs_write
5912 .internal_batch_modify(
5913 [
5914 (
5915 UUID_TESTPERSON_1,
5916 ModifyList::new_set(
5917 Attribute::SshPublicKey,
5918 ValueSetSshKey::new("label".to_string(), ssh_pubkey),
5919 ),
5920 ),
5921 (
5922 client_uuid,
5923 ModifyList::new_set(
5924 Attribute::OAuth2RsSupScopeMap,
5925 ValueSetOauthScopeMap::new(UUID_IDM_ALL_ACCOUNTS, scope_set),
5926 ),
5927 ),
5928 ]
5929 .into_iter(),
5930 )
5931 .expect("Failed to modify test entries");
5932
5933 assert!(idms_prox_write.commit().is_ok());
5934
5935 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5936
5937 let idms_prox_read = idms.proxy_read().await.unwrap();
5938
5939 let pkce_secret = PkceS256Secret::default();
5940
5941 let consent_request = good_authorisation_request!(
5942 idms_prox_read,
5943 &ident,
5944 ct,
5945 pkce_secret.to_request(),
5946 "openid groups".to_string()
5947 );
5948
5949 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5950 unreachable!();
5951 };
5952
5953 drop(idms_prox_read);
5955 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5956
5957 let permit_success = idms_prox_write
5958 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5959 .expect("Failed to perform OAuth2 permit");
5960
5961 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5963 code: permit_success.code,
5964 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5965 code_verifier: Some(pkce_secret.to_verifier()),
5966 }
5967 .into();
5968
5969 let token_response = idms_prox_write
5970 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5971 .expect("Failed to perform OAuth2 token exchange");
5972
5973 let id_token = token_response.id_token.expect("No id_token in response!");
5974 let access_token =
5975 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5976
5977 assert!(idms_prox_write.commit().is_ok());
5978 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5979
5980 let mut jwkset = idms_prox_read
5981 .oauth2_openid_publickey("test_resource_server")
5982 .expect("Failed to get public key");
5983 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5984
5985 let jws_validator =
5986 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5987
5988 let oidc_unverified =
5989 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5990
5991 let iat = ct.as_secs() as i64;
5992
5993 let oidc = jws_validator
5994 .verify(&oidc_unverified)
5995 .unwrap()
5996 .verify_exp(iat)
5997 .expect("Failed to verify oidc");
5998
5999 assert!(oidc.claims.contains_key(OAUTH2_SCOPE_SSH_PUBLICKEYS));
6001
6002 assert!(oidc
6003 .claims
6004 .get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
6005 .expect("unable to find key")
6006 .as_array()
6007 .unwrap()
6008 .contains(&serde_json::json!(ECDSA_SSH_PUBLIC_KEY)));
6009
6010 let userinfo = idms_prox_read
6012 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
6013 .expect("failed to get userinfo");
6014
6015 assert_eq!(
6017 oidc.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS),
6018 userinfo.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
6019 );
6020 }
6021
6022 #[idm_test]
6024 async fn test_idm_oauth2_insecure_pkce(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
6025 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6026 let (_secret, _uat, ident, _) =
6027 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6028
6029 let idms_prox_read = idms.proxy_read().await.unwrap();
6030
6031 let pkce_secret = PkceS256Secret::default();
6033
6034 let _consent_request = good_authorisation_request!(
6036 idms_prox_read,
6037 &ident,
6038 ct,
6039 pkce_secret.to_request(),
6040 OAUTH2_SCOPE_OPENID.to_string()
6041 );
6042
6043 let auth_req = AuthorisationRequest {
6045 response_type: ResponseType::Code,
6046 response_mode: None,
6047 client_id: "test_resource_server".to_string(),
6048 state: Some("123".to_string()),
6049 pkce_request: None,
6050 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6051 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
6052 nonce: Some("abcdef".to_string()),
6053 oidc_ext: Default::default(),
6054 max_age: None,
6055 unknown_keys: Default::default(),
6056 };
6057
6058 idms_prox_read
6059 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6060 .expect("Oauth2 authorisation failed");
6061 }
6062
6063 #[idm_test]
6064 async fn test_idm_oauth2_webfinger(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
6065 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6066 let (_secret, _uat, _ident, _) =
6067 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
6068 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6069
6070 let user = "testperson1@example.com";
6071
6072 let webfinger = idms_prox_read
6073 .oauth2_openid_webfinger("test_resource_server", user)
6074 .expect("Failed to get webfinger");
6075
6076 assert_eq!(webfinger.subject, user);
6077 assert_eq!(webfinger.links.len(), 1);
6078
6079 let link = &webfinger.links[0];
6080 assert_eq!(link.rel, "http://openid.net/specs/connect/1.0/issuer");
6081 assert_eq!(
6082 link.href,
6083 "https://idm.example.com/oauth2/openid/test_resource_server"
6084 );
6085
6086 let failed_webfinger = idms_prox_read
6087 .oauth2_openid_webfinger("test_resource_server", "someone@another.domain");
6088 assert!(failed_webfinger.is_err());
6089 }
6090
6091 #[idm_test]
6092 async fn test_idm_oauth2_openid_legacy_crypto(
6093 idms: &IdmServer,
6094 _idms_delayed: &mut IdmServerDelayed,
6095 ) {
6096 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6097 let (secret, _uat, ident, _) =
6098 setup_oauth2_resource_server_basic(idms, ct, false, true, false).await;
6099 let idms_prox_read = idms.proxy_read().await.unwrap();
6100 let discovery = idms_prox_read
6103 .oauth2_openid_discovery("test_resource_server")
6104 .expect("Failed to get discovery");
6105
6106 let mut jwkset = idms_prox_read
6107 .oauth2_openid_publickey("test_resource_server")
6108 .expect("Failed to get public key");
6109
6110 let jwk = jwkset.keys.pop().expect("no such jwk");
6111 let public_jwk = jwk.clone();
6112
6113 match jwk {
6114 Jwk::RSA { alg, use_, kid, .. } => {
6115 match (
6116 alg.unwrap(),
6117 &discovery.id_token_signing_alg_values_supported[0],
6118 ) {
6119 (JwaAlg::RS256, IdTokenSignAlg::RS256) => {}
6120 _ => panic!(),
6121 };
6122 assert_eq!(use_.unwrap(), JwkUse::Sig);
6123 assert!(kid.is_some());
6124 }
6125 _ => panic!(),
6126 };
6127
6128 let pkce_secret = PkceS256Secret::default();
6130
6131 let consent_request = good_authorisation_request!(
6132 idms_prox_read,
6133 &ident,
6134 ct,
6135 pkce_secret.to_request(),
6136 OAUTH2_SCOPE_OPENID.to_string()
6137 );
6138
6139 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6140 unreachable!();
6141 };
6142
6143 drop(idms_prox_read);
6145 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6146
6147 let permit_success = idms_prox_write
6148 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6149 .expect("Failed to perform OAuth2 permit");
6150
6151 let token_req = AccessTokenRequest {
6153 grant_type: GrantTypeReq::AuthorizationCode {
6154 code: permit_success.code,
6155 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6156 code_verifier: Some(pkce_secret.to_verifier()),
6157 },
6158
6159 client_post_auth: ClientPostAuth {
6160 client_id: Some("test_resource_server".to_string()),
6161 client_secret: Some(secret),
6162 },
6163 };
6164
6165 let token_response = idms_prox_write
6166 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
6167 .expect("Failed to perform OAuth2 token exchange");
6168
6169 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
6171 let id_token = token_response.id_token.expect("No id_token in response!");
6172
6173 let jws_validator =
6174 JwsRs256Verifier::try_from(&public_jwk).expect("failed to build validator");
6175
6176 let oidc_unverified =
6177 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
6178
6179 let iat = ct.as_secs() as i64;
6180
6181 let oidc = jws_validator
6182 .verify(&oidc_unverified)
6183 .unwrap()
6184 .verify_exp(iat)
6185 .expect("Failed to verify oidc");
6186
6187 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
6188
6189 assert!(idms_prox_write.commit().is_ok());
6190 }
6191
6192 #[idm_test]
6193 async fn test_idm_oauth2_consent_granted_and_changed_workflow(
6194 idms: &IdmServer,
6195 _idms_delayed: &mut IdmServerDelayed,
6196 ) {
6197 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6198 let (_secret, uat, ident, _) =
6199 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6200
6201 let idms_prox_read = idms.proxy_read().await.unwrap();
6202
6203 let pkce_secret = PkceS256Secret::default();
6204
6205 let consent_request = good_authorisation_request!(
6206 idms_prox_read,
6207 &ident,
6208 ct,
6209 pkce_secret.to_request(),
6210 OAUTH2_SCOPE_OPENID.to_string()
6211 );
6212
6213 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6215 unreachable!();
6216 };
6217
6218 drop(idms_prox_read);
6220 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6221
6222 let _permit_success = idms_prox_write
6223 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6224 .expect("Failed to perform OAuth2 permit");
6225
6226 assert!(idms_prox_write.commit().is_ok());
6227
6228 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6230
6231 let ident = idms_prox_read
6233 .process_uat_to_identity(&uat, ct, Source::Internal)
6234 .expect("Unable to process uat");
6235
6236 let pkce_secret = PkceS256Secret::default();
6237
6238 let consent_request = good_authorisation_request!(
6239 idms_prox_read,
6240 &ident,
6241 ct,
6242 pkce_secret.to_request(),
6243 OAUTH2_SCOPE_OPENID.to_string()
6244 );
6245
6246 let AuthoriseResponse::Permitted(_permit_success) = consent_request else {
6248 unreachable!();
6249 };
6250
6251 drop(idms_prox_read);
6252
6253 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6255
6256 let me_extend_scopes = ModifyEvent::new_internal_invalid(
6257 filter!(f_eq(
6258 Attribute::Name,
6259 PartialValue::new_iname("test_resource_server")
6260 )),
6261 ModifyList::new_list(vec![Modify::Present(
6262 Attribute::OAuth2RsScopeMap,
6263 Value::new_oauthscopemap(
6264 UUID_IDM_ALL_ACCOUNTS,
6265 btreeset![
6266 OAUTH2_SCOPE_EMAIL.to_string(),
6267 OAUTH2_SCOPE_PROFILE.to_string(),
6268 OAUTH2_SCOPE_OPENID.to_string()
6269 ],
6270 )
6271 .expect("invalid oauthscope"),
6272 )]),
6273 );
6274
6275 assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
6276 assert!(idms_prox_write.commit().is_ok());
6277
6278 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6281
6282 let ident = idms_prox_read
6284 .process_uat_to_identity(&uat, ct, Source::Internal)
6285 .expect("Unable to process uat");
6286
6287 let pkce_secret = PkceS256Secret::default();
6288
6289 let auth_req = AuthorisationRequest {
6290 response_type: ResponseType::Code,
6291 response_mode: None,
6292 client_id: "test_resource_server".to_string(),
6293 state: Some("123".to_string()),
6294 pkce_request: Some(pkce_secret.to_request()),
6295 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6296 scope: btreeset![
6297 "openid".to_string(),
6298 "email".to_string(),
6299 "profile".to_string()
6300 ],
6301 nonce: Some("abcdef".to_string()),
6302 oidc_ext: Default::default(),
6303 max_age: None,
6304 unknown_keys: Default::default(),
6305 };
6306
6307 let consent_request = idms_prox_read
6308 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6309 .expect("Oauth2 authorisation failed");
6310
6311 let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
6313 unreachable!();
6314 };
6315
6316 drop(idms_prox_read);
6317
6318 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6322
6323 let me_extend_scopes = ModifyEvent::new_internal_invalid(
6324 filter!(f_eq(
6325 Attribute::Name,
6326 PartialValue::new_iname("test_resource_server")
6327 )),
6328 ModifyList::new_list(vec![Modify::Present(
6329 Attribute::OAuth2RsSupScopeMap,
6330 Value::new_oauthscopemap(UUID_IDM_ALL_ACCOUNTS, btreeset!["newscope".to_string()])
6331 .expect("invalid oauthscope"),
6332 )]),
6333 );
6334
6335 assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
6336 assert!(idms_prox_write.commit().is_ok());
6337
6338 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6341
6342 let ident = idms_prox_read
6344 .process_uat_to_identity(&uat, ct, Source::Internal)
6345 .expect("Unable to process uat");
6346
6347 let pkce_secret = PkceS256Secret::default();
6348
6349 let auth_req = AuthorisationRequest {
6350 response_type: ResponseType::Code,
6351 response_mode: None,
6352 client_id: "test_resource_server".to_string(),
6353 state: Some("123".to_string()),
6354 pkce_request: Some(pkce_secret.to_request()),
6355 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6356 scope: btreeset![
6358 "openid".to_string(),
6359 "email".to_string(),
6360 "profile".to_string()
6361 ],
6362 nonce: Some("abcdef".to_string()),
6363 oidc_ext: Default::default(),
6364 max_age: None,
6365 unknown_keys: Default::default(),
6366 };
6367
6368 let consent_request = idms_prox_read
6369 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6370 .expect("Oauth2 authorisation failed");
6371
6372 let _consent_token = if let AuthoriseResponse::ConsentRequested {
6374 consent_token,
6375 scopes,
6376 ..
6377 } = consent_request
6378 {
6379 assert!(scopes.contains("newscope"));
6380 consent_token
6381 } else {
6382 unreachable!();
6383 };
6384 }
6385
6386 #[idm_test]
6387 async fn test_idm_oauth2_consent_granted_refint_cleanup_on_delete(
6388 idms: &IdmServer,
6389 _idms_delayed: &mut IdmServerDelayed,
6390 ) {
6391 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6392 let (_secret, uat, ident, o2rs_uuid) =
6393 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6394
6395 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
6397
6398 let idms_prox_read = idms.proxy_read().await.unwrap();
6399
6400 let pkce_secret = PkceS256Secret::default();
6401 let consent_request = good_authorisation_request!(
6402 idms_prox_read,
6403 &ident,
6404 ct,
6405 pkce_secret.to_request(),
6406 OAUTH2_SCOPE_OPENID.to_string()
6407 );
6408
6409 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6411 unreachable!();
6412 };
6413
6414 drop(idms_prox_read);
6416 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6417
6418 let _permit_success = idms_prox_write
6419 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6420 .expect("Failed to perform OAuth2 permit");
6421
6422 let ident = idms_prox_write
6423 .process_uat_to_identity(&uat, ct, Source::Internal)
6424 .expect("Unable to process uat");
6425
6426 assert!(
6428 ident.get_oauth2_consent_scopes(o2rs_uuid)
6429 == Some(&btreeset![
6430 OAUTH2_SCOPE_OPENID.to_string(),
6431 "supplement".to_string()
6432 ])
6433 );
6434
6435 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
6437 Attribute::Name,
6438 PartialValue::new_iname("test_resource_server")
6439 )));
6440
6441 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
6442 let ident = idms_prox_write
6444 .process_uat_to_identity(&uat, ct, Source::Internal)
6445 .expect("Unable to process uat");
6446 dbg!(&o2rs_uuid);
6447 dbg!(&ident);
6448 let consent_scopes = ident.get_oauth2_consent_scopes(o2rs_uuid);
6449 dbg!(consent_scopes);
6450 assert!(consent_scopes.is_none());
6451
6452 assert!(idms_prox_write.commit().is_ok());
6453 }
6454
6455 #[idm_test]
6475 async fn test_idm_oauth2_1076_pkce_downgrade(
6476 idms: &IdmServer,
6477 _idms_delayed: &mut IdmServerDelayed,
6478 ) {
6479 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6480 let (secret, _uat, ident, _) =
6482 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6483
6484 let idms_prox_read = idms.proxy_read().await.unwrap();
6485
6486 let pkce_secret = PkceS256Secret::default();
6491
6492 let auth_req = AuthorisationRequest {
6494 response_type: ResponseType::Code,
6495 response_mode: None,
6496 client_id: "test_resource_server".to_string(),
6497 state: Some("123".to_string()),
6498 pkce_request: None,
6499 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6500 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6501 nonce: None,
6502 oidc_ext: Default::default(),
6503 max_age: None,
6504 unknown_keys: Default::default(),
6505 };
6506
6507 let consent_request = idms_prox_read
6508 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6509 .expect("Failed to perform OAuth2 authorisation request.");
6510
6511 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6513 unreachable!();
6514 };
6515
6516 drop(idms_prox_read);
6518 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6519
6520 let permit_success = idms_prox_write
6521 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6522 .expect("Failed to perform OAuth2 permit");
6523
6524 let token_req = AccessTokenRequest {
6528 grant_type: GrantTypeReq::AuthorizationCode {
6529 code: permit_success.code,
6530 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6531 code_verifier: Some(pkce_secret.to_verifier()),
6532 },
6533 client_post_auth: ClientPostAuth {
6534 client_id: Some("test_resource_server".to_string()),
6535 client_secret: Some(secret),
6536 },
6537 };
6538
6539 assert!(matches!(
6541 idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6542 Err(Oauth2Error::InvalidRequest)
6543 ));
6544
6545 assert!(idms_prox_write.commit().is_ok());
6546 }
6547
6548 #[idm_test]
6549 async fn test_idm_oauth2_redir_http_downgrade(
6553 idms: &IdmServer,
6554 _idms_delayed: &mut IdmServerDelayed,
6555 ) {
6556 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6557 let (secret, _uat, ident, _) =
6559 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6560
6561 let idms_prox_read = idms.proxy_read().await.unwrap();
6562
6563 let pkce_secret = PkceS256Secret::default();
6568
6569 let auth_req = AuthorisationRequest {
6571 response_type: ResponseType::Code,
6572 response_mode: None,
6573 client_id: "test_resource_server".to_string(),
6574 state: Some("123".to_string()),
6575 pkce_request: Some(pkce_secret.to_request()),
6576 redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6577 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6578 nonce: None,
6579 oidc_ext: Default::default(),
6580 max_age: None,
6581 unknown_keys: Default::default(),
6582 };
6583
6584 assert!(
6585 idms_prox_read
6586 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6587 .unwrap_err()
6588 == Oauth2Error::InvalidOrigin
6589 );
6590
6591 let consent_request = good_authorisation_request!(
6593 idms_prox_read,
6594 &ident,
6595 ct,
6596 pkce_secret.to_request(),
6597 OAUTH2_SCOPE_OPENID.to_string()
6598 );
6599
6600 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6602 unreachable!();
6603 };
6604
6605 drop(idms_prox_read);
6607 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6608
6609 let permit_success = idms_prox_write
6610 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6611 .expect("Failed to perform OAuth2 permit");
6612
6613 let token_req = AccessTokenRequest {
6616 grant_type: GrantTypeReq::AuthorizationCode {
6617 code: permit_success.code,
6618 redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6619 code_verifier: Some(pkce_secret.to_verifier()),
6620 },
6621
6622 client_post_auth: ClientPostAuth {
6623 client_id: Some("test_resource_server".to_string()),
6624 client_secret: Some(secret),
6625 },
6626 };
6627
6628 assert!(matches!(
6630 idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6631 Err(Oauth2Error::InvalidOrigin)
6632 ));
6633
6634 assert!(idms_prox_write.commit().is_ok());
6635 }
6636
6637 async fn setup_refresh_token(
6638 idms: &IdmServer,
6639 _idms_delayed: &mut IdmServerDelayed,
6640 ct: Duration,
6641 ) -> (AccessTokenResponse, ClientAuthInfo) {
6642 let (secret, _uat, ident, _) =
6644 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6645 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6646
6647 let idms_prox_read = idms.proxy_read().await.unwrap();
6648
6649 let pkce_secret = PkceS256Secret::default();
6651
6652 let consent_request = good_authorisation_request!(
6653 idms_prox_read,
6654 &ident,
6655 ct,
6656 pkce_secret.to_request(),
6657 OAUTH2_SCOPE_OPENID.to_string()
6658 );
6659
6660 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6661 unreachable!();
6662 };
6663
6664 drop(idms_prox_read);
6666 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6667
6668 let permit_success = idms_prox_write
6669 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6670 .expect("Failed to perform OAuth2 permit");
6671
6672 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
6673 code: permit_success.code,
6674 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6675 code_verifier: Some(pkce_secret.to_verifier()),
6676 }
6677 .into();
6678 let access_token_response_1 = idms_prox_write
6679 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6680 .expect("Unable to exchange for OAuth2 token");
6681
6682 assert!(idms_prox_write.commit().is_ok());
6683
6684 trace!(?access_token_response_1);
6685
6686 (access_token_response_1, client_authz)
6687 }
6688
6689 #[idm_test]
6690 async fn test_idm_oauth2_refresh_token_basic(
6691 idms: &IdmServer,
6692 idms_delayed: &mut IdmServerDelayed,
6693 ) {
6694 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6696
6697 let (access_token_response_1, client_authz) =
6698 setup_refresh_token(idms, idms_delayed, ct).await;
6699
6700 let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
6704 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6705
6706 let refresh_token = access_token_response_1
6707 .refresh_token
6708 .as_ref()
6709 .expect("no refresh token was issued")
6710 .clone();
6711
6712 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6713 refresh_token,
6714 scope: None,
6715 }
6716 .into();
6717
6718 let access_token_response_2 = idms_prox_write
6719 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6720 .expect("Unable to exchange for OAuth2 token");
6721
6722 assert!(idms_prox_write.commit().is_ok());
6723
6724 trace!(?access_token_response_2);
6725
6726 assert!(access_token_response_1.access_token != access_token_response_2.access_token);
6727 assert!(access_token_response_1.refresh_token != access_token_response_2.refresh_token);
6728 assert!(access_token_response_1.id_token != access_token_response_2.id_token);
6729
6730 let ct =
6733 Duration::from_secs(TEST_CURRENT_TIME + 20 + access_token_response_2.expires_in as u64);
6734
6735 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6736
6737 let refresh_token = access_token_response_2
6738 .refresh_token
6739 .as_ref()
6740 .expect("no refresh token was issued")
6741 .clone();
6742
6743 let reflected_token = idms_prox_write
6745 .reflect_oauth2_token(&client_authz, &refresh_token)
6746 .expect("Failed to access internals of the refresh token");
6747
6748 let refresh_exp = match reflected_token {
6749 Oauth2TokenType::Refresh { exp, .. } => exp,
6750 Oauth2TokenType::ClientAccess { .. } => unreachable!(),
6752 };
6753
6754 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6755 refresh_token,
6756 scope: None,
6757 }
6758 .into();
6759
6760 let access_token_response_3 = idms_prox_write
6761 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6762 .expect("Unable to exchange for OAuth2 token");
6763
6764 let entry = idms_prox_write
6767 .qs_write
6768 .internal_search_uuid(UUID_TESTPERSON_1)
6769 .expect("failed");
6770 let session = entry
6771 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
6772 .and_then(|sessions| sessions.first_key_value())
6773 .unwrap();
6775
6776 trace!(?session);
6777 assert_eq!(
6779 SessionState::ExpiresAt(
6780 time::OffsetDateTime::UNIX_EPOCH
6781 + ct
6782 + Duration::from_secs(OAUTH_REFRESH_TOKEN_EXPIRY)
6783 ),
6784 session.1.state
6785 );
6786
6787 assert!(idms_prox_write.commit().is_ok());
6788
6789 trace!(?access_token_response_3);
6790
6791 assert!(access_token_response_3.access_token != access_token_response_2.access_token);
6792 assert!(access_token_response_3.refresh_token != access_token_response_2.refresh_token);
6793 assert!(access_token_response_3.id_token != access_token_response_2.id_token);
6794
6795 let ct = Duration::from_secs(
6801 TEST_CURRENT_TIME + refresh_exp as u64 + access_token_response_3.expires_in as u64,
6802 );
6803
6804 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6805
6806 let refresh_token = access_token_response_3
6807 .refresh_token
6808 .as_ref()
6809 .expect("no refresh token was issued")
6810 .clone();
6811
6812 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6813 refresh_token,
6814 scope: None,
6815 }
6816 .into();
6817 let access_token_response_4 = idms_prox_write
6818 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6819 .unwrap_err();
6820
6821 assert_eq!(access_token_response_4, Oauth2Error::InvalidGrant);
6822
6823 assert!(idms_prox_write.commit().is_ok());
6824 }
6825
6826 #[idm_test]
6828 async fn test_idm_oauth2_refresh_token_oauth2_session_expired(
6829 idms: &IdmServer,
6830 idms_delayed: &mut IdmServerDelayed,
6831 ) {
6832 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6834
6835 let (access_token_response_1, client_authz) =
6836 setup_refresh_token(idms, idms_delayed, ct).await;
6837
6838 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6842 let revoke_request = TokenRevokeRequest {
6843 token: access_token_response_1.access_token.clone(),
6844 token_type_hint: None,
6845 client_post_auth: ClientPostAuth::default(),
6846 };
6847 assert!(idms_prox_write
6848 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
6849 .is_ok());
6850 assert!(idms_prox_write.commit().is_ok());
6851
6852 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6855
6856 let refresh_token = access_token_response_1
6857 .refresh_token
6858 .as_ref()
6859 .expect("no refresh token was issued")
6860 .clone();
6861
6862 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6863 refresh_token,
6864 scope: None,
6865 }
6866 .into();
6867 let access_token_response_2 = idms_prox_write
6868 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6869 .unwrap_err();
6871
6872 assert_eq!(access_token_response_2, Oauth2Error::InvalidGrant);
6873
6874 assert!(idms_prox_write.commit().is_ok());
6875 }
6876
6877 #[idm_test]
6879 async fn test_idm_oauth2_refresh_token_invalid_client_authz(
6880 idms: &IdmServer,
6881 idms_delayed: &mut IdmServerDelayed,
6882 ) {
6883 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6885
6886 let (access_token_response_1, _client_authz) =
6887 setup_refresh_token(idms, idms_delayed, ct).await;
6888
6889 let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
6890
6891 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6895
6896 let refresh_token = access_token_response_1
6897 .refresh_token
6898 .as_ref()
6899 .expect("no refresh token was issued")
6900 .clone();
6901
6902 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6903 refresh_token,
6904 scope: None,
6905 }
6906 .into();
6907 let access_token_response_2 = idms_prox_write
6908 .check_oauth2_token_exchange(&bad_client_authz, &token_req, ct)
6909 .unwrap_err();
6910
6911 assert_eq!(access_token_response_2, Oauth2Error::AuthenticationRequired);
6912
6913 assert!(idms_prox_write.commit().is_ok());
6914 }
6915
6916 #[idm_test]
6918 async fn test_idm_oauth2_refresh_token_inconsistent_scopes(
6919 idms: &IdmServer,
6920 idms_delayed: &mut IdmServerDelayed,
6921 ) {
6922 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6924
6925 let (access_token_response_1, client_authz) =
6926 setup_refresh_token(idms, idms_delayed, ct).await;
6927
6928 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6932
6933 let refresh_token = access_token_response_1
6934 .refresh_token
6935 .as_ref()
6936 .expect("no refresh token was issued")
6937 .clone();
6938
6939 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6940 refresh_token,
6941 scope: Some(btreeset!["invalid_scope".to_string()]),
6942 }
6943 .into();
6944 let access_token_response_2 = idms_prox_write
6945 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6946 .unwrap_err();
6947
6948 assert_eq!(access_token_response_2, Oauth2Error::InvalidScope);
6949
6950 assert!(idms_prox_write.commit().is_ok());
6951 }
6952
6953 #[idm_test]
6957 async fn test_idm_oauth2_refresh_token_reuse_invalidates_session(
6958 idms: &IdmServer,
6959 idms_delayed: &mut IdmServerDelayed,
6960 ) {
6961 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6963
6964 let (access_token_response_1, client_authz) =
6965 setup_refresh_token(idms, idms_delayed, ct).await;
6966
6967 let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
6970 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6971
6972 let refresh_token = access_token_response_1
6973 .refresh_token
6974 .as_ref()
6975 .expect("no refresh token was issued")
6976 .clone();
6977
6978 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6979 refresh_token,
6980 scope: None,
6981 }
6982 .into();
6983
6984 let _access_token_response_2 = idms_prox_write
6985 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6986 .expect("Unable to exchange for OAuth2 token");
6987
6988 assert!(idms_prox_write.commit().is_ok());
6989
6990 let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
6992 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6993
6994 let refresh_token = access_token_response_1
6995 .refresh_token
6996 .as_ref()
6997 .expect("no refresh token was issued")
6998 .clone();
6999
7000 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7001 refresh_token,
7002 scope: None,
7003 }
7004 .into();
7005
7006 let access_token_response_3 = idms_prox_write
7007 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7008 .unwrap_err();
7009
7010 assert_eq!(access_token_response_3, Oauth2Error::InvalidGrant);
7011
7012 let entry = idms_prox_write
7013 .qs_write
7014 .internal_search_uuid(UUID_TESTPERSON_1)
7015 .expect("failed");
7016 let valid = entry
7017 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
7018 .and_then(|sessions| sessions.first_key_value())
7019 .map(|(_, session)| !matches!(session.state, SessionState::RevokedAt(_)))
7020 .unwrap();
7022 assert!(!valid);
7024
7025 assert!(idms_prox_write.commit().is_ok());
7026 }
7027
7028 #[idm_test]
7035 async fn test_idm_oauth2_refresh_token_divergence(
7036 idms: &IdmServer,
7037 idms_delayed: &mut IdmServerDelayed,
7038 ) {
7039 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7041
7042 let (access_token_response_1, client_authz) =
7043 setup_refresh_token(idms, idms_delayed, ct).await;
7044
7045 let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
7048 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7049
7050 let refresh_token = access_token_response_1
7051 .refresh_token
7052 .as_ref()
7053 .expect("no refresh token was issued")
7054 .clone();
7055
7056 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7057 refresh_token,
7058 scope: None,
7059 }
7060 .into();
7061
7062 let access_token_response_2 = idms_prox_write
7063 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7064 .expect("Unable to exchange for OAuth2 token");
7065
7066 drop(idms_prox_write);
7069
7070 let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
7072 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7073
7074 let refresh_token = access_token_response_2
7075 .refresh_token
7076 .as_ref()
7077 .expect("no refresh token was issued")
7078 .clone();
7079
7080 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7081 refresh_token,
7082 scope: None,
7083 }
7084 .into();
7085
7086 let _access_token_response_3 = idms_prox_write
7087 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7088 .expect("Unable to exchange for OAuth2 token");
7089
7090 assert!(idms_prox_write.commit().is_ok());
7091
7092 }
7094
7095 #[idm_test]
7096 async fn test_idm_oauth2_refresh_token_scope_constraints(
7097 idms: &IdmServer,
7098 idms_delayed: &mut IdmServerDelayed,
7099 ) {
7100 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7102
7103 let (access_token_response_1, client_authz) =
7104 setup_refresh_token(idms, idms_delayed, ct).await;
7105
7106 let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
7115 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7116
7117 let refresh_token = access_token_response_1
7118 .refresh_token
7119 .as_ref()
7120 .expect("no refresh token was issued")
7121 .clone();
7122
7123 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
7125
7126 let access_token_unverified = JwsCompact::from_str(&access_token_response_1.access_token)
7127 .expect("Invalid Access Token");
7128
7129 let reflected_token = jws_verifier
7130 .verify(&access_token_unverified)
7131 .unwrap()
7132 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7133 .expect("Failed to access internals of the refresh token");
7134
7135 trace!(?reflected_token);
7136 let initial_scopes = reflected_token.extensions.scope;
7137 trace!(?initial_scopes);
7138
7139 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7141 refresh_token,
7142 scope: None,
7143 }
7144 .into();
7145
7146 let access_token_response_2 = idms_prox_write
7147 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7148 .expect("Unable to exchange for OAuth2 token");
7149
7150 let access_token_unverified = JwsCompact::from_str(&access_token_response_2.access_token)
7151 .expect("Invalid Access Token");
7152
7153 let reflected_token = jws_verifier
7154 .verify(&access_token_unverified)
7155 .unwrap()
7156 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7157 .expect("Failed to access internals of the refresh token");
7158
7159 assert_eq!(initial_scopes, reflected_token.extensions.scope);
7160
7161 let refresh_token = access_token_response_2
7162 .refresh_token
7163 .as_ref()
7164 .expect("no refresh token was issued")
7165 .clone();
7166
7167 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7169 refresh_token,
7170 scope: Some(["openid".to_string()].into()),
7171 }
7172 .into();
7173
7174 let access_token_response_3 = idms_prox_write
7175 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7176 .expect("Unable to exchange for OAuth2 token");
7177
7178 let access_token_unverified = JwsCompact::from_str(&access_token_response_3.access_token)
7179 .expect("Invalid Access Token");
7180
7181 let reflected_token = jws_verifier
7182 .verify(&access_token_unverified)
7183 .unwrap()
7184 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7185 .expect("Failed to access internals of the refresh token");
7186
7187 assert_ne!(initial_scopes, reflected_token.extensions.scope);
7188
7189 let constrained_scopes = reflected_token.extensions.scope;
7191
7192 let refresh_token = access_token_response_3
7193 .refresh_token
7194 .as_ref()
7195 .expect("no refresh token was issued")
7196 .clone();
7197
7198 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7200 refresh_token,
7201 scope: None,
7202 }
7203 .into();
7204
7205 let access_token_response_4 = idms_prox_write
7206 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7207 .expect("Unable to exchange for OAuth2 token");
7208
7209 let access_token_unverified = JwsCompact::from_str(&access_token_response_4.access_token)
7210 .expect("Invalid Access Token");
7211
7212 let reflected_token = jws_verifier
7213 .verify(&access_token_unverified)
7214 .unwrap()
7215 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7216 .expect("Failed to access internals of the refresh token");
7217
7218 assert_ne!(initial_scopes, reflected_token.extensions.scope);
7219 assert_eq!(constrained_scopes, reflected_token.extensions.scope);
7220
7221 let refresh_token = access_token_response_4
7222 .refresh_token
7223 .as_ref()
7224 .expect("no refresh token was issued")
7225 .clone();
7226
7227 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7229 refresh_token,
7230 scope: Some(initial_scopes),
7231 }
7232 .into();
7233
7234 let access_token_response_5_err = idms_prox_write
7235 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7236 .unwrap_err();
7237
7238 assert_eq!(access_token_response_5_err, Oauth2Error::InvalidScope);
7239
7240 assert!(idms_prox_write.commit().is_ok());
7241 }
7242
7243 #[test]
7244 fn compliant_serialization_test() {
7247 let token_req: Result<AccessTokenRequest, serde_json::Error> = serde_json::from_str(
7248 r#"
7249 {
7250 "grant_type": "refresh_token",
7251 "refresh_token": "some_dumb_refresh_token",
7252 "scope": "invalid_scope vasd asd"
7253 }
7254 "#,
7255 );
7256 assert!(token_req.is_ok());
7257 }
7258
7259 #[idm_test]
7260 async fn test_idm_oauth2_custom_claims(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
7261 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7262 let (secret, _uat, ident, oauth2_rs_uuid) =
7263 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7264
7265 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7267
7268 let modlist = ModifyList::new_list(vec![
7269 Modify::Present(
7271 Attribute::OAuth2RsClaimMap,
7272 Value::OauthClaimMap(
7273 "custom_a".to_string(),
7274 OauthClaimMapJoin::CommaSeparatedValue,
7275 ),
7276 ),
7277 Modify::Present(
7278 Attribute::OAuth2RsClaimMap,
7279 Value::OauthClaimValue(
7280 "custom_a".to_string(),
7281 UUID_TESTGROUP,
7282 btreeset!["value_a".to_string()],
7283 ),
7284 ),
7285 Modify::Present(
7287 Attribute::OAuth2RsClaimMap,
7288 Value::OauthClaimValue(
7289 "custom_a".to_string(),
7290 UUID_IDM_ALL_ACCOUNTS,
7291 btreeset!["value_b".to_string()],
7292 ),
7293 ),
7294 Modify::Present(
7296 Attribute::OAuth2RsClaimMap,
7297 Value::OauthClaimMap(
7298 "custom_b".to_string(),
7299 OauthClaimMapJoin::SpaceSeparatedValue,
7300 ),
7301 ),
7302 Modify::Present(
7303 Attribute::OAuth2RsClaimMap,
7304 Value::OauthClaimValue(
7305 "custom_b".to_string(),
7306 UUID_TESTGROUP,
7307 btreeset!["value_a".to_string()],
7308 ),
7309 ),
7310 Modify::Present(
7311 Attribute::OAuth2RsClaimMap,
7312 Value::OauthClaimValue(
7313 "custom_b".to_string(),
7314 UUID_IDM_ALL_ACCOUNTS,
7315 btreeset!["value_b".to_string()],
7316 ),
7317 ),
7318 Modify::Present(
7320 Attribute::OAuth2RsClaimMap,
7321 Value::OauthClaimValue(
7322 "custom_b".to_string(),
7323 UUID_IDM_ADMINS,
7324 btreeset!["value_c".to_string()],
7325 ),
7326 ),
7327 Modify::Present(
7329 Attribute::OAuth2RsClaimMap,
7330 Value::OauthClaimMap(
7331 "custom:claim-name".to_string(),
7332 OauthClaimMapJoin::CommaSeparatedValue,
7333 ),
7334 ),
7335 Modify::Present(
7336 Attribute::OAuth2RsClaimMap,
7337 Value::OauthClaimValue(
7338 "custom:claim-name".to_string(),
7339 UUID_TESTGROUP,
7340 btreeset!["value:a-a".to_string()],
7341 ),
7342 ),
7343 ]);
7344
7345 assert!(idms_prox_write
7346 .qs_write
7347 .internal_modify(
7348 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7349 &modlist,
7350 )
7351 .is_ok());
7352
7353 assert!(idms_prox_write.commit().is_ok());
7354
7355 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7357
7358 let idms_prox_read = idms.proxy_read().await.unwrap();
7359
7360 let pkce_secret = PkceS256Secret::default();
7361
7362 let consent_request = good_authorisation_request!(
7363 idms_prox_read,
7364 &ident,
7365 ct,
7366 pkce_secret.to_request(),
7367 OAUTH2_SCOPE_OPENID.to_string()
7368 );
7369
7370 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7371 unreachable!();
7372 };
7373
7374 drop(idms_prox_read);
7376 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7377
7378 let permit_success = idms_prox_write
7379 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7380 .expect("Failed to perform OAuth2 permit");
7381
7382 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
7384 code: permit_success.code,
7385 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
7386 code_verifier: Some(pkce_secret.to_verifier()),
7387 }
7388 .into();
7389
7390 let token_response = idms_prox_write
7391 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7392 .expect("Failed to perform OAuth2 token exchange");
7393
7394 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7396
7397 let id_token = token_response.id_token.expect("No id_token in response!");
7398 let access_token =
7399 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
7400
7401 assert!(idms_prox_write.commit().is_ok());
7403
7404 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7405
7406 let mut jwkset = idms_prox_read
7407 .oauth2_openid_publickey("test_resource_server")
7408 .expect("Failed to get public key");
7409
7410 let public_jwk = jwkset.keys.pop().expect("no such jwk");
7411
7412 let jws_validator =
7413 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
7414
7415 let oidc_unverified =
7416 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
7417
7418 let iat = ct.as_secs() as i64;
7419
7420 let oidc = jws_validator
7421 .verify(&oidc_unverified)
7422 .unwrap()
7423 .verify_exp(iat)
7424 .expect("Failed to verify oidc");
7425
7426 assert!(
7428 oidc.iss
7429 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
7430 .unwrap()
7431 );
7432 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
7433 assert_eq!(oidc.aud, "test_resource_server");
7434 assert_eq!(oidc.iat, iat);
7435 assert_eq!(oidc.nbf, Some(iat));
7436 assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
7438 assert!(oidc.auth_time.is_none());
7439 assert_eq!(oidc.nonce, Some("abcdef".to_string()));
7441 assert!(oidc.at_hash.is_none());
7442 assert!(oidc.acr.is_none());
7443 assert!(oidc.amr.is_none());
7444 assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
7445 assert!(oidc.jti.is_some());
7446 if let Some(jti) = &oidc.jti {
7447 assert!(Uuid::from_str(jti).is_ok());
7448 }
7449 assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
7450 assert_eq!(
7451 oidc.s_claims.preferred_username,
7452 Some("testperson1@example.com".to_string())
7453 );
7454 assert!(
7455 oidc.s_claims.scopes == vec![OAUTH2_SCOPE_OPENID.to_string(), "supplement".to_string()]
7456 );
7457
7458 assert_eq!(
7459 oidc.claims.get("custom_a").and_then(|v| v.as_str()),
7460 Some("value_a,value_b")
7461 );
7462 assert_eq!(
7463 oidc.claims.get("custom_b").and_then(|v| v.as_str()),
7464 Some("value_a value_b")
7465 );
7466
7467 assert_eq!(
7468 oidc.claims
7469 .get("custom:claim-name")
7470 .and_then(|v| v.as_str()),
7471 Some("value:a-a")
7472 );
7473
7474 let userinfo = idms_prox_read
7477 .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
7478 .expect("failed to get userinfo");
7479
7480 assert_eq!(oidc.iss, userinfo.iss);
7481 assert_eq!(oidc.sub, userinfo.sub);
7482 assert_eq!(oidc.aud, userinfo.aud);
7483 assert_eq!(oidc.iat, userinfo.iat);
7484 assert_eq!(oidc.nbf, userinfo.nbf);
7485 assert_eq!(oidc.exp, userinfo.exp);
7486 assert!(userinfo.auth_time.is_none());
7487 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
7488 assert!(userinfo.at_hash.is_none());
7489 assert!(userinfo.jti.is_some());
7490 if let Some(jti) = &userinfo.jti {
7491 assert!(Uuid::from_str(jti).is_ok());
7492 }
7493 assert_eq!(oidc.amr, userinfo.amr);
7494 assert_eq!(oidc.azp, userinfo.azp);
7495 assert!(userinfo.jti.is_some());
7496 if let Some(jti) = &userinfo.jti {
7497 assert!(Uuid::from_str(jti).is_ok());
7498 }
7499 assert_eq!(oidc.s_claims, userinfo.s_claims);
7500 assert_eq!(oidc.claims, userinfo.claims);
7501
7502 let intr_request = AccessTokenIntrospectRequest {
7504 token: token_response.access_token.clone(),
7505 token_type_hint: None,
7506 client_post_auth: ClientPostAuth::default(),
7507 };
7508 let intr_response = idms_prox_read
7509 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7510 .expect("Failed to inspect token");
7511
7512 eprintln!("👉 {intr_response:?}");
7513 assert!(intr_response.active);
7514 assert_eq!(
7515 intr_response.scope,
7516 btreeset!["openid".to_string(), "supplement".to_string()]
7517 );
7518 assert_eq!(
7519 intr_response.client_id.as_deref(),
7520 Some("test_resource_server")
7521 );
7522 assert_eq!(
7523 intr_response.username.as_deref(),
7524 Some("testperson1@example.com")
7525 );
7526 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7527 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7528 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7529 drop(idms_prox_read);
7532 }
7533
7534 #[idm_test]
7535 async fn test_idm_oauth2_public_allow_localhost_redirect(
7536 idms: &IdmServer,
7537 _idms_delayed: &mut IdmServerDelayed,
7538 ) {
7539 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7540 let (_uat, ident, oauth2_rs_uuid) = setup_oauth2_resource_server_public(idms, ct).await;
7541
7542 let mut idms_prox_write: crate::idm::server::IdmServerProxyWriteTransaction<'_> =
7543 idms.proxy_write(ct).await.unwrap();
7544
7545 let redirect_uri = Url::parse("http://localhost:8765/oauth2/result")
7546 .expect("Failed to parse redirect URL");
7547
7548 let modlist = ModifyList::new_list(vec![
7549 Modify::Present(Attribute::OAuth2AllowLocalhostRedirect, Value::Bool(true)),
7550 Modify::Present(Attribute::OAuth2RsOrigin, Value::Url(redirect_uri.clone())),
7551 ]);
7552
7553 assert!(idms_prox_write
7554 .qs_write
7555 .internal_modify(
7556 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7557 &modlist,
7558 )
7559 .is_ok());
7560
7561 assert!(idms_prox_write.commit().is_ok());
7562
7563 let idms_prox_read = idms.proxy_read().await.unwrap();
7564
7565 let pkce_secret = PkceS256Secret::default();
7567
7568 let auth_req = AuthorisationRequest {
7569 response_type: ResponseType::Code,
7570 response_mode: None,
7571 client_id: "test_resource_server".to_string(),
7572 state: Some("123".to_string()),
7573 pkce_request: Some(pkce_secret.to_request()),
7574 redirect_uri: Url::parse("http://localhost:8765/oauth2/result").unwrap(),
7575 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
7576 nonce: Some("abcdef".to_string()),
7577 oidc_ext: Default::default(),
7578 max_age: None,
7579 unknown_keys: Default::default(),
7580 };
7581
7582 let consent_request = idms_prox_read
7583 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
7584 .expect("OAuth2 authorisation failed");
7585
7586 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7588 unreachable!();
7589 };
7590
7591 drop(idms_prox_read);
7593 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7594
7595 let permit_success = idms_prox_write
7596 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7597 .expect("Failed to perform OAuth2 permit");
7598
7599 assert_eq!(permit_success.state.as_deref(), Some("123"));
7601
7602 let token_req = AccessTokenRequest {
7604 grant_type: GrantTypeReq::AuthorizationCode {
7605 code: permit_success.code,
7606 redirect_uri,
7607 code_verifier: Some(pkce_secret.to_verifier()),
7608 },
7609 client_post_auth: ClientPostAuth {
7610 client_id: Some("test_resource_server".to_string()),
7611 client_secret: None,
7612 },
7613 };
7614
7615 let token_response = idms_prox_write
7616 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7617 .expect("Failed to perform OAuth2 token exchange");
7618
7619 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7621
7622 assert!(idms_prox_write.commit().is_ok());
7623 }
7624
7625 #[idm_test]
7626 async fn test_idm_oauth2_service_account_token_exchange(
7627 idms: &IdmServer,
7628 _idms_delayed: &mut IdmServerDelayed,
7629 ) {
7630 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7631 let (secret, _uat, _ident, _) =
7632 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7633
7634 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7635
7636 let service_account_uuid = Uuid::new_v4();
7637 let sa_entry: Entry<EntryInit, EntryNew> = entry_init!(
7638 (Attribute::Class, EntryClass::Object.to_value()),
7639 (Attribute::Class, EntryClass::Account.to_value()),
7640 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
7641 (Attribute::Name, Value::new_iname("test_sa_oauth2")),
7642 (Attribute::Uuid, Value::Uuid(service_account_uuid)),
7643 (Attribute::DisplayName, Value::new_utf8s("test_sa_oauth2")),
7644 (Attribute::Description, Value::new_utf8s("test_sa_oauth2"))
7645 );
7646
7647 idms_prox_write
7648 .qs_write
7649 .internal_create(vec![sa_entry])
7650 .expect("Failed to create service account");
7651
7652 idms_prox_write
7653 .qs_write
7654 .internal_modify(
7655 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTGROUP))),
7656 &ModifyList::new_list(vec![Modify::Present(
7657 Attribute::Member,
7658 Value::Refer(service_account_uuid),
7659 )]),
7660 )
7661 .expect("Failed to add service account to scope group");
7662
7663 let gte = GenerateApiTokenEvent::new_internal(service_account_uuid, "sa-token", None);
7664
7665 let api_token = idms_prox_write
7666 .service_account_generate_api_token(>e, ct)
7667 .expect("failed to generate api token");
7668
7669 assert!(idms_prox_write.commit().is_ok());
7670
7671 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7672
7673 let scopes: BTreeSet<String> =
7674 btreeset![OAUTH2_SCOPE_OPENID.into(), OAUTH2_SCOPE_GROUPS.into()];
7675
7676 let build_exchange_request =
7677 |requested_scopes: BTreeSet<String>, client_secret: Option<String>| {
7678 AccessTokenRequest {
7679 grant_type: GrantTypeReq::TokenExchange {
7680 subject_token: api_token.to_string(),
7681 subject_token_type: TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS.into(),
7682 requested_token_type: None,
7683 audience: Some("test_resource_server".into()),
7684 resource: None,
7685 actor_token: None,
7686 actor_token_type: None,
7687 scope: Some(requested_scopes),
7688 },
7689 client_post_auth: ClientPostAuth {
7690 client_id: Some("test_resource_server".into()),
7691 client_secret,
7692 },
7693 }
7694 };
7695
7696 let token_req = build_exchange_request(scopes.clone(), None);
7697 let forbidden_secret_req = build_exchange_request(scopes.clone(), Some(secret.clone()));
7698 let empty_scope_req = build_exchange_request(BTreeSet::new(), None);
7699
7700 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7701
7702 assert_eq!(
7703 idms_prox_write
7704 .check_oauth2_token_exchange(&client_authz, &forbidden_secret_req, ct)
7705 .unwrap_err(),
7706 Oauth2Error::InvalidRequest
7707 );
7708
7709 assert_eq!(
7710 idms_prox_write
7711 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &empty_scope_req, ct)
7712 .unwrap_err(),
7713 Oauth2Error::InvalidRequest
7714 );
7715
7716 let token_response = idms_prox_write
7717 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7718 .expect("Failed to perform OAuth2 token exchange for service account");
7719
7720 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7721 assert_eq!(
7722 token_response.issued_token_type,
7723 Some(IssuedTokenType::AccessToken)
7724 );
7725 assert!(token_response.refresh_token.is_some());
7726 assert!(token_response.id_token.is_some());
7727 let response_scopes = token_response.scope.clone();
7728 assert!(response_scopes.contains(OAUTH2_SCOPE_OPENID));
7729 assert!(response_scopes.contains(OAUTH2_SCOPE_GROUPS));
7730
7731 assert!(idms_prox_write.commit().is_ok());
7732
7733 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7734 let intr_request = AccessTokenIntrospectRequest {
7735 token: token_response.access_token.clone(),
7736 token_type_hint: None,
7737 client_post_auth: ClientPostAuth::default(),
7738 };
7739 let intr_response = idms_prox_read
7740 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7741 .expect("Failed to introspect service account token");
7742
7743 assert!(intr_response.active);
7744 assert_eq!(
7745 intr_response.client_id.as_deref(),
7746 Some("test_resource_server")
7747 );
7748 assert_eq!(
7749 intr_response.sub.as_deref(),
7750 Some(service_account_uuid.to_string().as_str())
7751 );
7752 }
7753
7754 #[idm_test]
7755 async fn test_idm_oauth2_basic_client_credentials_grant_valid(
7756 idms: &IdmServer,
7757 _idms_delayed: &mut IdmServerDelayed,
7758 ) {
7759 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7760 let (secret, _uat, _ident, _) =
7761 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7762 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7763
7764 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7766
7767 let token_req = AccessTokenRequest {
7768 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7769
7770 client_post_auth: ClientPostAuth {
7771 client_id: Some("test_resource_server".to_string()),
7772 client_secret: Some(secret),
7773 },
7774 };
7775
7776 let oauth2_token = idms_prox_write
7777 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7778 .expect("Failed to perform OAuth2 token exchange");
7779
7780 assert!(idms_prox_write.commit().is_ok());
7781
7782 assert_eq!(oauth2_token.token_type, AccessTokenType::Bearer);
7784
7785 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7787
7788 let intr_request = AccessTokenIntrospectRequest {
7789 token: oauth2_token.access_token.clone(),
7790 token_type_hint: None,
7791 client_post_auth: ClientPostAuth::default(),
7792 };
7793 let intr_response = idms_prox_read
7794 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7795 .expect("Failed to inspect token");
7796
7797 eprintln!("👉 {intr_response:?}");
7798 assert!(intr_response.active);
7799 assert_eq!(intr_response.scope, btreeset!["supplement".to_string()]);
7800 assert_eq!(
7801 intr_response.client_id.as_deref(),
7802 Some("test_resource_server")
7803 );
7804 assert_eq!(
7805 intr_response.username.as_deref(),
7806 Some("test_resource_server@example.com")
7807 );
7808 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7809 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7810 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7811
7812 drop(idms_prox_read);
7813
7814 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7816 let revoke_request = TokenRevokeRequest {
7817 token: oauth2_token.access_token.clone(),
7818 token_type_hint: None,
7819 client_post_auth: ClientPostAuth::default(),
7820 };
7821 assert!(idms_prox_write
7822 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
7823 .is_ok());
7824 assert!(idms_prox_write.commit().is_ok());
7825
7826 let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
7828 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7829
7830 let intr_request = AccessTokenIntrospectRequest {
7831 token: oauth2_token.access_token.clone(),
7832 token_type_hint: None,
7833 client_post_auth: ClientPostAuth::default(),
7834 };
7835
7836 let intr_response = idms_prox_read
7837 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7838 .expect("Failed to inspect token");
7839 assert!(!intr_response.active);
7840
7841 drop(idms_prox_read);
7842 }
7843
7844 #[idm_test]
7845 async fn test_idm_oauth2_basic_client_credentials_grant_invalid(
7846 idms: &IdmServer,
7847 _idms_delayed: &mut IdmServerDelayed,
7848 ) {
7849 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7850 let (secret, _uat, _ident, _) =
7851 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7852
7853 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7854
7855 let token_req = AccessTokenRequest {
7857 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7858 client_post_auth: ClientPostAuth {
7859 client_id: Some("test_resource_server".to_string()),
7860 client_secret: None,
7861 },
7862 };
7863
7864 assert_eq!(
7865 idms_prox_write
7866 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7867 .unwrap_err(),
7868 Oauth2Error::AuthenticationRequired
7869 );
7870
7871 let token_req = AccessTokenRequest {
7873 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7874
7875 client_post_auth: ClientPostAuth {
7876 client_id: Some("test_resource_server".to_string()),
7877 client_secret: Some("wrong password".to_string()),
7878 },
7879 };
7880
7881 assert_eq!(
7882 idms_prox_write
7883 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7884 .unwrap_err(),
7885 Oauth2Error::AuthenticationRequired
7886 );
7887
7888 let scope = Some(btreeset!["💅".to_string()]);
7890 let token_req = AccessTokenRequest {
7891 grant_type: GrantTypeReq::ClientCredentials { scope },
7892
7893 client_post_auth: ClientPostAuth {
7894 client_id: Some("test_resource_server".to_string()),
7895 client_secret: Some(secret.clone()),
7896 },
7897 };
7898
7899 assert_eq!(
7900 idms_prox_write
7901 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7902 .unwrap_err(),
7903 Oauth2Error::InvalidScope
7904 );
7905
7906 let scope = Some(btreeset!["invalid_scope".to_string()]);
7908 let token_req = AccessTokenRequest {
7909 grant_type: GrantTypeReq::ClientCredentials { scope },
7910
7911 client_post_auth: ClientPostAuth {
7912 client_id: Some("test_resource_server".to_string()),
7913 client_secret: Some(secret.clone()),
7914 },
7915 };
7916
7917 assert_eq!(
7918 idms_prox_write
7919 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7920 .unwrap_err(),
7921 Oauth2Error::AccessDenied
7922 );
7923
7924 assert!(idms_prox_write.commit().is_ok());
7925 }
7926
7927 #[test]
7928 fn test_get_code() {
7929 use super::{gen_device_code, gen_user_code, parse_user_code};
7930
7931 assert!(gen_device_code().is_ok());
7932
7933 let (res_string, res_value) = gen_user_code();
7934
7935 assert!(res_string.split('-').count() == 3);
7936
7937 let res_string_clean = res_string.replace("-", "");
7938 let res_string_as_num = res_string_clean
7939 .parse::<u32>()
7940 .expect("Failed to parse as number");
7941 assert_eq!(res_string_as_num, res_value);
7942
7943 assert_eq!(
7944 parse_user_code(&res_string).expect("Failed to parse code"),
7945 res_value
7946 );
7947 }
7948
7949 #[idm_test]
7950 async fn handle_oauth2_start_device_flow(
7951 idms: &IdmServer,
7952 _idms_delayed: &mut IdmServerDelayed,
7953 ) {
7954 let ct = duration_from_epoch_now();
7955
7956 let client_auth_info = ClientAuthInfo::from(Source::Https(
7957 "127.0.0.1"
7958 .parse()
7959 .expect("Failed to parse 127.0.0.1 as an IP!"),
7960 ));
7961 let eventid = Uuid::new_v4();
7962
7963 let res = idms
7964 .proxy_write(ct)
7965 .await
7966 .expect("Failed to get idmspwt")
7967 .handle_oauth2_start_device_flow(client_auth_info, "test_rs_id", &None, eventid);
7968 dbg!(&res);
7969 assert!(res.is_err());
7970 }
7971
7972 #[test]
7973 fn test_url_localhost_domain() {
7974 let example_is_not_local = "https://example.com/sdfsdf";
7978 println!("Ensuring that {example_is_not_local} is not local");
7979 assert!(!host_is_local(
7980 &Url::parse(example_is_not_local)
7981 .expect("Failed to parse example.com as a host?")
7982 .host()
7983 .unwrap_or_else(|| panic!("Couldn't get a host from {example_is_not_local}"))
7984 ));
7985
7986 let test_urls = [
7987 ("http://localhost:8080/oauth2/callback", "/oauth2/callback"),
7988 ("https://localhost/foo/bar", "/foo/bar"),
7989 ("http://127.0.0.1:12345/foo", "/foo"),
7990 ("http://[::1]:12345/foo", "/foo"),
7991 ];
7992
7993 for (url, path) in test_urls.into_iter() {
7994 println!("Testing URL: {url}");
7995 let url = Url::parse(url).expect("One of the test values failed!");
7996 assert!(host_is_local(
7997 &url.host().expect("Didn't parse a host out?")
7998 ));
7999
8000 assert_eq!(url.path(), path);
8001 }
8002 }
8003
8004 #[test]
8005 fn test_oauth2_rs_type_allow_localhost_redirect() {
8006 let test_cases = [
8007 (
8008 OauthRSType::Public {
8009 allow_localhost_redirect: true,
8010 },
8011 true,
8012 ),
8013 (
8014 OauthRSType::Public {
8015 allow_localhost_redirect: false,
8016 },
8017 false,
8018 ),
8019 (
8020 OauthRSType::Basic {
8021 authz_secret: "supersecret".to_string(),
8022 enable_pkce: false,
8023 enable_consent_prompt: true,
8024 },
8025 false,
8026 ),
8027 ];
8028
8029 assert!(test_cases.iter().all(|(rs_type, expected)| {
8030 let actual = rs_type.allow_localhost_redirect();
8031 println!("Testing {rs_type:?} -> {expected}");
8032 actual == *expected
8033 }));
8034 }
8035
8036 #[idm_test]
8037 async fn test_oauth2_auth_with_no_state(
8038 idms: &IdmServer,
8039 _idms_delayed: &mut IdmServerDelayed,
8040 ) {
8041 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8042 let (_secret, _uat, ident, _) =
8043 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8044
8045 let idms_prox_read = idms.proxy_read().await.unwrap();
8046
8047 let pkce_secret = PkceS256Secret::default();
8049
8050 let scope: BTreeSet<String> = OAUTH2_SCOPE_OPENID
8051 .split(" ")
8052 .map(|s| s.to_string())
8053 .collect();
8054
8055 let auth_req = AuthorisationRequest {
8056 response_type: ResponseType::Code,
8057 response_mode: None,
8058 client_id: "test_resource_server".to_string(),
8059 state: None,
8060 pkce_request: Some(pkce_secret.to_request()),
8061 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
8062 scope,
8063 nonce: Some("abcdef".to_string()),
8064 oidc_ext: Default::default(),
8065 max_age: None,
8066 unknown_keys: Default::default(),
8067 };
8068 println!("{auth_req:?}");
8069
8070 let consent_request = idms_prox_read
8071 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
8072 .expect("OAuth2 authorisation failed");
8073
8074 let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
8076 unreachable!("Expected a ConsentRequested response, got: {consent_request:?}");
8077 };
8078 }
8079
8080 #[idm_test]
8081 async fn test_idm_oauth2_consent_prompt_disabled(
8082 idms: &IdmServer,
8083 _idms_delayed: &mut IdmServerDelayed,
8084 ) {
8085 let ct = Duration::from_secs(TEST_CURRENT_TIME);
8086 let (_secret, _uat, ident, o2rs_uuid) =
8087 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8088
8089 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
8090 idms_prox_write
8091 .qs_write
8092 .internal_modify_uuid(
8093 o2rs_uuid,
8094 &ModifyList::new_purge_and_set(
8095 Attribute::OAuth2ConsentPromptEnable,
8096 Value::new_bool(false),
8097 ),
8098 )
8099 .expect("Unable to disable consent prompt");
8100 assert!(idms_prox_write.commit().is_ok());
8101
8102 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
8104
8105 let idms_prox_read = idms.proxy_read().await.unwrap();
8106
8107 let pkce_secret = PkceS256Secret::default();
8108 let consent_request = good_authorisation_request!(
8109 idms_prox_read,
8110 &ident,
8111 ct,
8112 pkce_secret.to_request(),
8113 OAUTH2_SCOPE_OPENID.to_string()
8114 );
8115
8116 let AuthoriseResponse::Permitted(_permitted) = consent_request else {
8118 unreachable!();
8119 };
8120
8121 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
8123 }
8124}