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