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