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