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