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