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 error!(sign_alg = ?o2rs.sign_alg);
2878
2879 match o2rs.sign_alg {
2880 SignatureAlgo::Es256 => o2rs.key_object.jws_es256_jwks(),
2881 SignatureAlgo::Rs256 => o2rs.key_object.jws_rs256_jwks(),
2882 }
2883 .ok_or_else(|| {
2884 error!(o2_client = ?o2rs.name, "Unable to retrieve public keys");
2885 OperationError::InvalidState
2886 })
2887 }
2888}
2889
2890fn parse_basic_authz(client_authz: &str) -> Result<(String, String), Oauth2Error> {
2891 let authz = general_purpose::STANDARD
2893 .decode(client_authz)
2894 .map_err(|_| {
2895 admin_error!("Basic authz invalid base64");
2896 Oauth2Error::AuthenticationRequired
2897 })
2898 .and_then(|data| {
2899 String::from_utf8(data).map_err(|_| {
2900 admin_error!("Basic authz invalid utf8");
2901 Oauth2Error::AuthenticationRequired
2902 })
2903 })?;
2904
2905 let mut split_iter = authz.split(':');
2908
2909 let client_id = split_iter.next().ok_or_else(|| {
2910 admin_error!("Basic authz invalid format (corrupt input?)");
2911 Oauth2Error::AuthenticationRequired
2912 })?;
2913 let secret = split_iter.next().ok_or_else(|| {
2914 admin_error!("Basic authz invalid format (missing ':' separator?)");
2915 Oauth2Error::AuthenticationRequired
2916 })?;
2917
2918 Ok((client_id.to_string(), secret.to_string()))
2919}
2920
2921fn s_claims_for_account(
2922 o2rs: &Oauth2RS,
2923 account: &Account,
2924 scopes: &BTreeSet<String>,
2925) -> OidcClaims {
2926 let preferred_username = if o2rs.prefer_short_username {
2927 Some(account.name.clone())
2928 } else {
2929 Some(account.spn.clone())
2930 };
2931
2932 let (email, email_verified) = if scopes.contains(OAUTH2_SCOPE_EMAIL) {
2933 if let Some(mp) = &account.mail_primary {
2934 (Some(mp.clone()), Some(true))
2935 } else {
2936 (None, None)
2937 }
2938 } else {
2939 (None, None)
2940 };
2941
2942 OidcClaims {
2943 name: Some(account.displayname.clone()),
2945 scopes: scopes.iter().cloned().collect(),
2946 preferred_username,
2947 email,
2948 email_verified,
2949 ..Default::default()
2950 }
2951}
2952
2953fn extra_claims_for_account(
2954 account: &Account,
2955
2956 claim_map: &BTreeMap<Uuid, Vec<(String, ClaimValue)>>,
2957
2958 scopes: &BTreeSet<String>,
2959) -> BTreeMap<String, serde_json::Value> {
2960 let mut extra_claims = BTreeMap::new();
2961
2962 let mut account_claims: BTreeMap<&str, ClaimValue> = BTreeMap::new();
2963
2964 for group_uuid in account.groups.iter().map(|g| g.uuid()) {
2966 if let Some(claim) = claim_map.get(group_uuid) {
2968 for (claim_name, claim_value) in claim.iter() {
2970 match account_claims.entry(claim_name.as_str()) {
2972 BTreeEntry::Vacant(e) => {
2973 e.insert(claim_value.clone());
2974 }
2975 BTreeEntry::Occupied(mut e) => {
2976 let mut_claim_value = e.get_mut();
2977 mut_claim_value.merge(claim_value);
2979 }
2980 }
2981 }
2982 }
2983 }
2984
2985 for (claim_name, claim_value) in account_claims {
2987 extra_claims.insert(claim_name.to_string(), claim_value.to_json_value());
2988 }
2989
2990 if scopes.contains(OAUTH2_SCOPE_SSH_PUBLICKEYS) {
2994 extra_claims.insert(
2995 OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string(),
2996 account
2997 .sshkeys()
2998 .values()
2999 .map(|pub_key| serde_json::Value::String(pub_key.to_string()))
3000 .collect(),
3001 );
3002 }
3003
3004 if scopes.contains(OAUTH2_SCOPE_GROUPS) {
3005 extra_claims.insert(
3006 OAUTH2_SCOPE_GROUPS.to_string(),
3007 account
3008 .groups
3009 .iter()
3010 .flat_map(|x| {
3011 let proto_group = x.to_proto();
3012 [proto_group.spn, proto_group.uuid]
3013 })
3014 .collect(),
3015 );
3016 }
3017
3018 trace!(?extra_claims);
3019
3020 extra_claims
3021}
3022
3023fn validate_scopes(req_scopes: &BTreeSet<String>) -> Result<(), Oauth2Error> {
3024 let failed_scopes = req_scopes
3025 .iter()
3026 .filter(|&s| !OAUTHSCOPE_RE.is_match(s))
3027 .cloned()
3028 .collect::<Vec<String>>();
3029
3030 if !failed_scopes.is_empty() {
3031 let requested_scopes_string = req_scopes
3032 .iter()
3033 .cloned()
3034 .collect::<Vec<String>>()
3035 .join(",");
3036 admin_error!(
3037 "Invalid OAuth2 request - requested scopes ({}) but ({}) failed to pass validation rules - all must match the regex {}",
3038 requested_scopes_string,
3039 failed_scopes.join(","),
3040 OAUTHSCOPE_RE.as_str()
3041 );
3042 return Err(Oauth2Error::InvalidScope);
3043 }
3044 Ok(())
3045}
3046
3047#[inline]
3049#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3050#[allow(dead_code)]
3051fn gen_device_code() -> Result<[u8; 16], Oauth2Error> {
3052 use rand::TryRngCore;
3053
3054 let mut rng = rand::rng();
3055 let mut result = [0u8; 16];
3056 if let Err(err) = rng.try_fill_bytes(&mut result) {
3058 error!("Failed to generate device code! {:?}", err);
3059 return Err(Oauth2Error::ServerError(OperationError::Backend));
3060 }
3061 Ok(result)
3062}
3063
3064#[inline]
3065#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3066#[allow(dead_code)]
3067fn gen_user_code() -> (String, u32) {
3069 use rand::Rng;
3070 let mut rng = rand::rng();
3071 let num: u32 = rng.random_range(0..=999999999);
3072 let result = format!("{:09}", num);
3073 (
3074 format!("{}-{}-{}", &result[0..3], &result[3..6], &result[6..9]),
3075 num,
3076 )
3077}
3078
3079#[allow(dead_code)]
3081fn parse_user_code(val: &str) -> Result<u32, Oauth2Error> {
3082 let mut val = val.to_string();
3083 val.retain(|c| c.is_ascii_digit());
3084 val.parse().map_err(|err| {
3085 debug!("Failed to parse value={} as u32: {:?}", val, err);
3086 Oauth2Error::InvalidRequest
3087 })
3088}
3089
3090fn host_is_local(host: &Host<&str>) -> bool {
3092 match host {
3093 Host::Ipv4(ip) => ip.is_loopback(),
3094 Host::Ipv6(ip) => ip.is_loopback(),
3095 Host::Domain(domain) => *domain == "localhost",
3096 }
3097}
3098
3099fn check_is_loopback(redirect_uri: &Url) -> bool {
3101 redirect_uri.host().is_some_and(|host| {
3102 host_is_local(&host)
3104 })
3105}
3106
3107#[cfg(test)]
3108mod tests {
3109 use base64::{engine::general_purpose, Engine as _};
3110 use std::collections::{BTreeMap, BTreeSet};
3111 use std::convert::TryFrom;
3112 use std::str::FromStr;
3113 use std::time::Duration;
3114 use uri::{OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
3115
3116 use compact_jwt::{
3117 compact::JwkUse, crypto::JwsRs256Verifier, dangernoverify::JwsDangerReleaseWithoutVerify,
3118 JwaAlg, Jwk, JwsCompact, JwsEs256Verifier, JwsVerifier, OidcSubject, OidcUnverified,
3119 };
3120 use kanidm_proto::constants::*;
3121 use kanidm_proto::internal::{SshPublicKey, UserAuthToken};
3122 use kanidm_proto::oauth2::*;
3123 use openssl::sha;
3124
3125 use crate::idm::accountpolicy::ResolvedAccountPolicy;
3126 use crate::idm::oauth2::{host_is_local, AuthoriseResponse, Oauth2Error, OauthRSType};
3127 use crate::idm::server::{IdmServer, IdmServerTransaction};
3128 use crate::prelude::*;
3129 use crate::value::{AuthType, OauthClaimMapJoin, SessionState};
3130 use crate::valueset::{ValueSetOauthScopeMap, ValueSetSshKey};
3131
3132 use crate::credential::Credential;
3133 use kanidm_lib_crypto::CryptoPolicy;
3134
3135 use super::Oauth2TokenType;
3136
3137 const TEST_CURRENT_TIME: u64 = 6000;
3138 const UAT_EXPIRE: u64 = 5;
3139 const TOKEN_EXPIRE: u64 = 900;
3140
3141 const UUID_TESTGROUP: Uuid = uuid!("a3028223-bf20-47d5-8b65-967b5d2bb3eb");
3142
3143 macro_rules! create_code_verifier {
3144 ($key:expr) => {{
3145 let code_verifier = $key.to_string();
3146 let mut hasher = sha::Sha256::new();
3147 hasher.update(code_verifier.as_bytes());
3148 let code_challenge: Vec<u8> = hasher.finish().iter().copied().collect();
3149 (Some(code_verifier), code_challenge)
3150 }};
3151 }
3152
3153 macro_rules! good_authorisation_request {
3154 (
3155 $idms_prox_read:expr,
3156 $ident:expr,
3157 $ct:expr,
3158 $code_challenge:expr,
3159 $scope:expr
3160 ) => {{
3161 #[allow(clippy::unnecessary_to_owned)]
3162 let scope: BTreeSet<String> = $scope.split(" ").map(|s| s.to_string()).collect();
3163
3164 let auth_req = AuthorisationRequest {
3165 response_type: ResponseType::Code,
3166 response_mode: None,
3167 client_id: "test_resource_server".to_string(),
3168 state: Some("123".to_string()),
3169 pkce_request: Some(PkceRequest {
3170 code_challenge: $code_challenge.into(),
3171 code_challenge_method: CodeChallengeMethod::S256,
3172 }),
3173 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3174 scope,
3175 nonce: Some("abcdef".to_string()),
3176 oidc_ext: Default::default(),
3177 max_age: None,
3178 unknown_keys: Default::default(),
3179 };
3180
3181 $idms_prox_read
3182 .check_oauth2_authorisation(Some($ident), &auth_req, $ct)
3183 .expect("OAuth2 authorisation failed")
3184 }};
3185 }
3186
3187 async fn setup_oauth2_resource_server_basic(
3189 idms: &IdmServer,
3190 ct: Duration,
3191 enable_pkce: bool,
3192 enable_legacy_crypto: bool,
3193 prefer_short_username: bool,
3194 ) -> (String, UserAuthToken, Identity, Uuid) {
3195 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3196
3197 let rs_uuid = Uuid::new_v4();
3198
3199 let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3200 (Attribute::Class, EntryClass::Group.to_value()),
3201 (Attribute::Name, Value::new_iname("testgroup")),
3202 (Attribute::Description, Value::new_utf8s("testgroup")),
3203 (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3204 (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3205 );
3206
3207 let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3208 (Attribute::Class, EntryClass::Object.to_value()),
3209 (Attribute::Class, EntryClass::Account.to_value()),
3210 (
3211 Attribute::Class,
3212 EntryClass::OAuth2ResourceServer.to_value()
3213 ),
3214 (
3215 Attribute::Class,
3216 EntryClass::OAuth2ResourceServerBasic.to_value()
3217 ),
3218 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3219 (Attribute::Name, Value::new_iname("test_resource_server")),
3220 (
3221 Attribute::DisplayName,
3222 Value::new_utf8s("test_resource_server")
3223 ),
3224 (
3225 Attribute::OAuth2RsOriginLanding,
3226 Value::new_url_s("https://demo.example.com").unwrap()
3227 ),
3228 (
3230 Attribute::OAuth2RsOrigin,
3231 Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3232 ),
3233 (
3234 Attribute::OAuth2RsOrigin,
3235 Value::new_url_s("https://portal.example.com/?custom=foo").unwrap()
3236 ),
3237 (
3238 Attribute::OAuth2RsOrigin,
3239 Value::new_url_s("app://cheese").unwrap()
3240 ),
3241 (
3243 Attribute::OAuth2RsScopeMap,
3244 Value::new_oauthscopemap(
3245 UUID_TESTGROUP,
3246 btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3247 )
3248 .expect("invalid oauthscope")
3249 ),
3250 (
3251 Attribute::OAuth2RsScopeMap,
3252 Value::new_oauthscopemap(
3253 UUID_IDM_ALL_ACCOUNTS,
3254 btreeset![OAUTH2_SCOPE_OPENID.to_string()]
3255 )
3256 .expect("invalid oauthscope")
3257 ),
3258 (
3259 Attribute::OAuth2RsSupScopeMap,
3260 Value::new_oauthscopemap(
3261 UUID_IDM_ALL_ACCOUNTS,
3262 btreeset!["supplement".to_string()]
3263 )
3264 .expect("invalid oauthscope")
3265 ),
3266 (
3267 Attribute::OAuth2AllowInsecureClientDisablePkce,
3268 Value::new_bool(!enable_pkce)
3269 ),
3270 (
3271 Attribute::OAuth2JwtLegacyCryptoEnable,
3272 Value::new_bool(enable_legacy_crypto)
3273 ),
3274 (
3275 Attribute::OAuth2PreferShortUsername,
3276 Value::new_bool(prefer_short_username)
3277 )
3278 );
3279
3280 let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3281 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3282
3283 let entry = idms_prox_write
3284 .qs_write
3285 .internal_search_uuid(rs_uuid)
3286 .expect("Failed to retrieve OAuth2 resource entry ");
3287 let secret = entry
3288 .get_ava_single_secret(Attribute::OAuth2RsBasicSecret)
3289 .map(str::to_string)
3290 .expect("No oauth2_rs_basic_secret found");
3291
3292 let session_id = uuid::Uuid::new_v4();
3295
3296 let account = idms_prox_write
3297 .target_to_account(UUID_TESTPERSON_1)
3298 .expect("account must exist");
3299
3300 let uat = account
3301 .to_userauthtoken(
3302 session_id,
3303 SessionScope::ReadWrite,
3304 ct,
3305 &ResolvedAccountPolicy::test_policy(),
3306 )
3307 .expect("Unable to create uat");
3308
3309 let state = uat
3311 .expiry
3312 .map(SessionState::ExpiresAt)
3313 .unwrap_or(SessionState::NeverExpires);
3314
3315 let p = CryptoPolicy::minimum();
3316 let cred = Credential::new_password_only(&p, "test_password").unwrap();
3317 let cred_id = cred.uuid;
3318
3319 let session = Value::Session(
3320 session_id,
3321 crate::value::Session {
3322 label: "label".to_string(),
3323 state,
3324 issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3325 issued_by: IdentityId::Internal,
3326 cred_id,
3327 scope: SessionScope::ReadWrite,
3328 type_: AuthType::Passkey,
3329 },
3330 );
3331
3332 let modlist = ModifyList::new_list(vec![
3334 Modify::Present(Attribute::UserAuthTokenSession, session),
3335 Modify::Present(
3336 Attribute::PrimaryCredential,
3337 Value::Cred("primary".to_string(), cred),
3338 ),
3339 ]);
3340
3341 idms_prox_write
3342 .qs_write
3343 .internal_modify(
3344 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3345 &modlist,
3346 )
3347 .expect("Failed to modify user");
3348
3349 let ident = idms_prox_write
3350 .process_uat_to_identity(&uat, ct, Source::Internal)
3351 .expect("Unable to process uat");
3352
3353 idms_prox_write.commit().expect("failed to commit");
3354
3355 (secret, uat, ident, rs_uuid)
3356 }
3357
3358 async fn setup_oauth2_resource_server_public(
3359 idms: &IdmServer,
3360 ct: Duration,
3361 ) -> (UserAuthToken, Identity, Uuid) {
3362 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3363
3364 let rs_uuid = Uuid::new_v4();
3365
3366 let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3367 (Attribute::Class, EntryClass::Group.to_value()),
3368 (Attribute::Name, Value::new_iname("testgroup")),
3369 (Attribute::Description, Value::new_utf8s("testgroup")),
3370 (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3371 (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3372 );
3373
3374 let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3375 (Attribute::Class, EntryClass::Object.to_value()),
3376 (Attribute::Class, EntryClass::Account.to_value()),
3377 (
3378 Attribute::Class,
3379 EntryClass::OAuth2ResourceServer.to_value()
3380 ),
3381 (
3382 Attribute::Class,
3383 EntryClass::OAuth2ResourceServerPublic.to_value()
3384 ),
3385 (Attribute::Uuid, Value::Uuid(rs_uuid)),
3386 (Attribute::Name, Value::new_iname("test_resource_server")),
3387 (
3388 Attribute::DisplayName,
3389 Value::new_utf8s("test_resource_server")
3390 ),
3391 (
3392 Attribute::OAuth2RsOriginLanding,
3393 Value::new_url_s("https://demo.example.com").unwrap()
3394 ),
3395 (
3396 Attribute::OAuth2RsOrigin,
3397 Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3398 ),
3399 (
3401 Attribute::OAuth2RsScopeMap,
3402 Value::new_oauthscopemap(UUID_TESTGROUP, btreeset!["groups".to_string()])
3403 .expect("invalid oauthscope")
3404 ),
3405 (
3406 Attribute::OAuth2RsScopeMap,
3407 Value::new_oauthscopemap(
3408 UUID_IDM_ALL_ACCOUNTS,
3409 btreeset![OAUTH2_SCOPE_OPENID.to_string()]
3410 )
3411 .expect("invalid oauthscope")
3412 ),
3413 (
3414 Attribute::OAuth2RsSupScopeMap,
3415 Value::new_oauthscopemap(
3416 UUID_IDM_ALL_ACCOUNTS,
3417 btreeset!["supplement".to_string()]
3418 )
3419 .expect("invalid oauthscope")
3420 )
3421 );
3422 let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3423 assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3424
3425 let session_id = uuid::Uuid::new_v4();
3429
3430 let account = idms_prox_write
3431 .target_to_account(UUID_TESTPERSON_1)
3432 .expect("account must exist");
3433 let uat = account
3434 .to_userauthtoken(
3435 session_id,
3436 SessionScope::ReadWrite,
3437 ct,
3438 &ResolvedAccountPolicy::test_policy(),
3439 )
3440 .expect("Unable to create uat");
3441
3442 let state = uat
3444 .expiry
3445 .map(SessionState::ExpiresAt)
3446 .unwrap_or(SessionState::NeverExpires);
3447
3448 let p = CryptoPolicy::minimum();
3449 let cred = Credential::new_password_only(&p, "test_password").unwrap();
3450 let cred_id = cred.uuid;
3451
3452 let session = Value::Session(
3453 session_id,
3454 crate::value::Session {
3455 label: "label".to_string(),
3456 state,
3457 issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3458 issued_by: IdentityId::Internal,
3459 cred_id,
3460 scope: SessionScope::ReadWrite,
3461 type_: AuthType::Passkey,
3462 },
3463 );
3464
3465 let modlist = ModifyList::new_list(vec![
3467 Modify::Present(Attribute::UserAuthTokenSession, session),
3468 Modify::Present(
3469 Attribute::PrimaryCredential,
3470 Value::Cred("primary".to_string(), cred),
3471 ),
3472 ]);
3473
3474 idms_prox_write
3475 .qs_write
3476 .internal_modify(
3477 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3478 &modlist,
3479 )
3480 .expect("Failed to modify user");
3481
3482 let ident = idms_prox_write
3483 .process_uat_to_identity(&uat, ct, Source::Internal)
3484 .expect("Unable to process uat");
3485
3486 idms_prox_write.commit().expect("failed to commit");
3487
3488 (uat, ident, rs_uuid)
3489 }
3490
3491 async fn setup_idm_admin(idms: &IdmServer, ct: Duration) -> (UserAuthToken, Identity) {
3492 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3493 let account = idms_prox_write
3494 .target_to_account(UUID_IDM_ADMIN)
3495 .expect("account must exist");
3496 let session_id = uuid::Uuid::new_v4();
3497 let uat = account
3498 .to_userauthtoken(
3499 session_id,
3500 SessionScope::ReadWrite,
3501 ct,
3502 &ResolvedAccountPolicy::test_policy(),
3503 )
3504 .expect("Unable to create uat");
3505 let ident = idms_prox_write
3506 .process_uat_to_identity(&uat, ct, Source::Internal)
3507 .expect("Unable to process uat");
3508
3509 idms_prox_write.commit().expect("failed to commit");
3510
3511 (uat, ident)
3512 }
3513
3514 #[idm_test]
3515 async fn test_idm_oauth2_basic_function(
3516 idms: &IdmServer,
3517 _idms_delayed: &mut IdmServerDelayed,
3518 ) {
3519 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3520 let (secret, _uat, ident, _) =
3521 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
3522
3523 let idms_prox_read = idms.proxy_read().await.unwrap();
3524
3525 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
3527
3528 let consent_request = good_authorisation_request!(
3529 idms_prox_read,
3530 &ident,
3531 ct,
3532 code_challenge,
3533 OAUTH2_SCOPE_OPENID.to_string()
3534 );
3535
3536 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3538 unreachable!();
3539 };
3540
3541 drop(idms_prox_read);
3543 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3544
3545 let permit_success = idms_prox_write
3546 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
3547 .expect("Failed to perform OAuth2 permit");
3548
3549 assert_eq!(permit_success.state.as_deref(), Some("123"));
3551
3552 let token_req = AccessTokenRequest {
3555 grant_type: GrantTypeReq::AuthorizationCode {
3556 code: permit_success.code,
3557 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3558 code_verifier,
3560 },
3561 client_id: Some("test_resource_server".to_string()),
3562 client_secret: Some(secret),
3563 };
3564
3565 let token_response = idms_prox_write
3566 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
3567 .expect("Failed to perform OAuth2 token exchange");
3568
3569 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
3571
3572 assert!(idms_prox_write.commit().is_ok());
3573 }
3574
3575 #[idm_test]
3576 async fn test_idm_oauth2_public_function(
3577 idms: &IdmServer,
3578 _idms_delayed: &mut IdmServerDelayed,
3579 ) {
3580 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3581 let (_uat, ident, _) = setup_oauth2_resource_server_public(idms, ct).await;
3582
3583 let idms_prox_read = idms.proxy_read().await.unwrap();
3584
3585 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
3589
3590 let consent_request = good_authorisation_request!(
3591 idms_prox_read,
3592 &ident,
3593 ct,
3594 code_challenge,
3595 OAUTH2_SCOPE_OPENID.to_string()
3596 );
3597
3598 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3600 unreachable!();
3601 };
3602
3603 drop(idms_prox_read);
3605 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3606
3607 let permit_success = idms_prox_write
3608 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
3609 .expect("Failed to perform OAuth2 permit");
3610
3611 assert_eq!(permit_success.state.as_deref(), Some("123"));
3613
3614 let token_req = AccessTokenRequest {
3617 grant_type: GrantTypeReq::AuthorizationCode {
3618 code: permit_success.code,
3619 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3620 code_verifier,
3622 },
3623 client_id: Some("Test_Resource_Server".to_string()),
3624 client_secret: None,
3625 };
3626
3627 let token_response = idms_prox_write
3628 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
3629 .expect("Failed to perform OAuth2 token exchange");
3630
3631 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
3633
3634 assert!(idms_prox_write.commit().is_ok());
3635 }
3636
3637 #[idm_test]
3638 async fn test_idm_oauth2_invalid_authorisation_requests(
3639 idms: &IdmServer,
3640 _idms_delayed: &mut IdmServerDelayed,
3641 ) {
3642 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3644 let (_secret, _uat, ident, _) =
3645 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
3646
3647 let (_anon_uat, anon_ident) = setup_idm_admin(idms, ct).await;
3648 let (_idm_admin_uat, idm_admin_ident) = setup_idm_admin(idms, ct).await;
3649
3650 let idms_prox_read = idms.proxy_read().await.unwrap();
3652
3653 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
3654
3655 let pkce_request = Some(PkceRequest {
3656 code_challenge,
3657 code_challenge_method: CodeChallengeMethod::S256,
3658 });
3659
3660 let auth_req = AuthorisationRequest {
3662 response_type: ResponseType::Token,
3664 response_mode: None,
3665 client_id: "test_resource_server".to_string(),
3666 state: Some("123".to_string()),
3667 pkce_request: pkce_request.clone(),
3668 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3669 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3670 nonce: None,
3671 oidc_ext: Default::default(),
3672 max_age: None,
3673 unknown_keys: Default::default(),
3674 };
3675
3676 assert!(
3677 idms_prox_read
3678 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3679 .unwrap_err()
3680 == Oauth2Error::UnsupportedResponseType
3681 );
3682
3683 let auth_req = AuthorisationRequest {
3685 response_type: ResponseType::Code,
3686 response_mode: None,
3687 client_id: "test_resource_server".to_string(),
3688 state: Some("123".to_string()),
3689 pkce_request: None,
3690 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3691 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3692 nonce: None,
3693 oidc_ext: Default::default(),
3694 max_age: None,
3695 unknown_keys: Default::default(),
3696 };
3697
3698 assert!(
3699 idms_prox_read
3700 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3701 .unwrap_err()
3702 == Oauth2Error::InvalidRequest
3703 );
3704
3705 let auth_req = AuthorisationRequest {
3707 response_type: ResponseType::Code,
3708 response_mode: None,
3709 client_id: "NOT A REAL RESOURCE SERVER".to_string(),
3710 state: Some("123".to_string()),
3711 pkce_request: pkce_request.clone(),
3712 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3713 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3714 nonce: None,
3715 oidc_ext: Default::default(),
3716 max_age: None,
3717 unknown_keys: Default::default(),
3718 };
3719
3720 assert!(
3721 idms_prox_read
3722 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3723 .unwrap_err()
3724 == Oauth2Error::InvalidClientId
3725 );
3726
3727 let auth_req = AuthorisationRequest {
3729 response_type: ResponseType::Code,
3730 response_mode: None,
3731 client_id: "test_resource_server".to_string(),
3732 state: Some("123".to_string()),
3733 pkce_request: pkce_request.clone(),
3734 redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
3735 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3736 nonce: None,
3737 oidc_ext: Default::default(),
3738 max_age: None,
3739 unknown_keys: Default::default(),
3740 };
3741
3742 assert!(
3743 idms_prox_read
3744 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3745 .unwrap_err()
3746 == Oauth2Error::InvalidOrigin
3747 );
3748
3749 let auth_req = AuthorisationRequest {
3751 response_type: ResponseType::Code,
3752 response_mode: None,
3753 client_id: "test_resource_server".to_string(),
3754 state: Some("123".to_string()),
3755 pkce_request: pkce_request.clone(),
3756 redirect_uri: Url::parse("https://demo.example.com/oauth2/wrong_place").unwrap(),
3757 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3758 nonce: None,
3759 oidc_ext: Default::default(),
3760 max_age: None,
3761 unknown_keys: Default::default(),
3762 };
3763
3764 assert!(
3765 idms_prox_read
3766 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3767 .unwrap_err()
3768 == Oauth2Error::InvalidOrigin
3769 );
3770
3771 let auth_req = AuthorisationRequest {
3773 response_type: ResponseType::Code,
3774 response_mode: None,
3775 client_id: "test_resource_server".to_string(),
3776 state: Some("123".to_string()),
3777 pkce_request: pkce_request.clone(),
3778 redirect_uri: Url::parse("https://portal.example.com/?custom=foo&too=many").unwrap(),
3779 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3780 nonce: None,
3781 oidc_ext: Default::default(),
3782 max_age: None,
3783 unknown_keys: Default::default(),
3784 };
3785
3786 assert!(
3787 idms_prox_read
3788 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3789 .unwrap_err()
3790 == Oauth2Error::InvalidOrigin
3791 );
3792
3793 let auth_req = AuthorisationRequest {
3794 response_type: ResponseType::Code,
3795 response_mode: None,
3796 client_id: "test_resource_server".to_string(),
3797 state: Some("123".to_string()),
3798 pkce_request: pkce_request.clone(),
3799 redirect_uri: Url::parse("https://portal.example.com").unwrap(),
3800 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3801 nonce: None,
3802 oidc_ext: Default::default(),
3803 max_age: None,
3804 unknown_keys: Default::default(),
3805 };
3806
3807 assert!(
3808 idms_prox_read
3809 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3810 .unwrap_err()
3811 == Oauth2Error::InvalidOrigin
3812 );
3813
3814 let auth_req = AuthorisationRequest {
3815 response_type: ResponseType::Code,
3816 response_mode: None,
3817 client_id: "test_resource_server".to_string(),
3818 state: Some("123".to_string()),
3819 pkce_request: pkce_request.clone(),
3820 redirect_uri: Url::parse("https://portal.example.com/?wrong=queryparam").unwrap(),
3821 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3822 nonce: None,
3823 oidc_ext: Default::default(),
3824 max_age: None,
3825 unknown_keys: Default::default(),
3826 };
3827
3828 assert!(
3829 idms_prox_read
3830 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3831 .unwrap_err()
3832 == Oauth2Error::InvalidOrigin
3833 );
3834
3835 let auth_req = AuthorisationRequest {
3837 response_type: ResponseType::Code,
3838 response_mode: None,
3839 client_id: "test_resource_server".to_string(),
3840 state: Some("123".to_string()),
3841 pkce_request: pkce_request.clone(),
3842 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3843 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
3844 nonce: None,
3845 oidc_ext: Default::default(),
3846 max_age: None,
3847 unknown_keys: Default::default(),
3848 };
3849
3850 let req = idms_prox_read
3851 .check_oauth2_authorisation(None, &auth_req, ct)
3852 .unwrap();
3853
3854 assert!(matches!(
3855 req,
3856 AuthoriseResponse::AuthenticationRequired { .. }
3857 ));
3858
3859 let auth_req = AuthorisationRequest {
3861 response_type: ResponseType::Code,
3862 response_mode: None,
3863 client_id: "test_resource_server".to_string(),
3864 state: Some("123".to_string()),
3865 pkce_request: pkce_request.clone(),
3866 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3867 scope: btreeset!["invalid_scope".to_string(), "read".to_string()],
3868 nonce: None,
3869 oidc_ext: Default::default(),
3870 max_age: None,
3871 unknown_keys: Default::default(),
3872 };
3873
3874 assert!(
3875 idms_prox_read
3876 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
3877 .unwrap_err()
3878 == Oauth2Error::AccessDenied
3879 );
3880
3881 let auth_req = AuthorisationRequest {
3883 response_type: ResponseType::Code,
3884 response_mode: None,
3885 client_id: "test_resource_server".to_string(),
3886 state: Some("123".to_string()),
3887 pkce_request: pkce_request.clone(),
3888 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3889 scope: btreeset!["openid".to_string(), "read".to_string()],
3890 nonce: None,
3891 oidc_ext: Default::default(),
3892 max_age: None,
3893 unknown_keys: Default::default(),
3894 };
3895
3896 assert!(
3897 idms_prox_read
3898 .check_oauth2_authorisation(Some(&idm_admin_ident), &auth_req, ct)
3899 .unwrap_err()
3900 == Oauth2Error::AccessDenied
3901 );
3902
3903 let auth_req = AuthorisationRequest {
3905 response_type: ResponseType::Code,
3906 response_mode: None,
3907 client_id: "test_resource_server".to_string(),
3908 state: Some("123".to_string()),
3909 pkce_request,
3910 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3911 scope: btreeset!["openid".to_string(), "read".to_string()],
3912 nonce: None,
3913 oidc_ext: Default::default(),
3914 max_age: None,
3915 unknown_keys: Default::default(),
3916 };
3917
3918 assert!(
3919 idms_prox_read
3920 .check_oauth2_authorisation(Some(&anon_ident), &auth_req, ct)
3921 .unwrap_err()
3922 == Oauth2Error::AccessDenied
3923 );
3924 }
3925
3926 #[idm_test]
3927 async fn test_idm_oauth2_invalid_authorisation_permit_requests(
3928 idms: &IdmServer,
3929 _idms_delayed: &mut IdmServerDelayed,
3930 ) {
3931 let ct = Duration::from_secs(TEST_CURRENT_TIME);
3933 let (_secret, uat, ident, _) =
3934 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
3935
3936 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3937
3938 let mut uat_wrong_session_id = uat.clone();
3939 uat_wrong_session_id.session_id = uuid::Uuid::new_v4();
3940 let ident_wrong_session_id = idms_prox_write
3941 .process_uat_to_identity(&uat_wrong_session_id, ct, Source::Internal)
3942 .expect("Unable to process uat");
3943
3944 let account = idms_prox_write
3945 .target_to_account(UUID_IDM_ADMIN)
3946 .expect("account must exist");
3947 let session_id = uuid::Uuid::new_v4();
3948 let uat2 = account
3949 .to_userauthtoken(
3950 session_id,
3951 SessionScope::ReadWrite,
3952 ct,
3953 &ResolvedAccountPolicy::test_policy(),
3954 )
3955 .expect("Unable to create uat");
3956 let ident2 = idms_prox_write
3957 .process_uat_to_identity(&uat2, ct, Source::Internal)
3958 .expect("Unable to process uat");
3959
3960 assert!(idms_prox_write.commit().is_ok());
3961
3962 let idms_prox_read = idms.proxy_read().await.unwrap();
3965
3966 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
3967
3968 let consent_request = good_authorisation_request!(
3969 idms_prox_read,
3970 &ident,
3971 ct,
3972 code_challenge,
3973 OAUTH2_SCOPE_OPENID.to_string()
3974 );
3975
3976 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3977 unreachable!();
3978 };
3979
3980 drop(idms_prox_read);
3981 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3982
3983 assert!(
3986 idms_prox_write
3987 .check_oauth2_authorise_permit(
3988 &ident,
3989 &consent_token,
3990 ct + Duration::from_secs(TOKEN_EXPIRE),
3991 )
3992 .unwrap_err()
3993 == OperationError::CryptographyError
3994 );
3995
3996 assert!(
4001 idms_prox_write
4002 .check_oauth2_authorise_permit(&ident2, &consent_token, ct,)
4003 .unwrap_err()
4004 == OperationError::InvalidSessionState
4005 );
4006
4007 assert!(
4009 idms_prox_write
4010 .check_oauth2_authorise_permit(&ident_wrong_session_id, &consent_token, ct,)
4011 .unwrap_err()
4012 == OperationError::InvalidSessionState
4013 );
4014
4015 assert!(idms_prox_write.commit().is_ok());
4016 }
4017
4018 #[idm_test]
4019 async fn test_idm_oauth2_invalid_token_exchange_requests(
4020 idms: &IdmServer,
4021 _idms_delayed: &mut IdmServerDelayed,
4022 ) {
4023 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4024 let (secret, mut uat, ident, _) =
4025 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4026
4027 uat.expiry = Some(
4037 time::OffsetDateTime::UNIX_EPOCH
4038 + Duration::from_secs(TEST_CURRENT_TIME + UAT_EXPIRE - 1),
4039 );
4040
4041 let idms_prox_read = idms.proxy_read().await.unwrap();
4042
4043 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
4045 let consent_request = good_authorisation_request!(
4046 idms_prox_read,
4047 &ident,
4048 ct,
4049 code_challenge,
4050 OAUTH2_SCOPE_OPENID.to_string()
4051 );
4052
4053 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4054 unreachable!();
4055 };
4056
4057 drop(idms_prox_read);
4058 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4059
4060 let permit_success = idms_prox_write
4062 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4063 .expect("Failed to perform OAuth2 permit");
4064
4065 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4070 code: permit_success.code.clone(),
4071 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4072 code_verifier: code_verifier.clone(),
4073 }
4074 .into();
4075
4076 let client_authz = ClientAuthInfo::from("not base64");
4077
4078 assert!(
4079 idms_prox_write
4080 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4081 .unwrap_err()
4082 == Oauth2Error::AuthenticationRequired
4083 );
4084
4085 let client_authz =
4087 general_purpose::STANDARD.encode(format!("test_resource_server {secret}"));
4088 let client_authz = ClientAuthInfo::from(client_authz.as_str());
4089
4090 assert!(
4091 idms_prox_write
4092 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4093 .unwrap_err()
4094 == Oauth2Error::AuthenticationRequired
4095 );
4096
4097 let client_authz = ClientAuthInfo::encode_basic("NOT A REAL SERVER", secret.as_str());
4099
4100 assert!(
4101 idms_prox_write
4102 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4103 .unwrap_err()
4104 == Oauth2Error::AuthenticationRequired
4105 );
4106
4107 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
4109
4110 assert!(
4111 idms_prox_write
4112 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4113 .unwrap_err()
4114 == Oauth2Error::AuthenticationRequired
4115 );
4116
4117 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4119
4120 assert!(
4122 idms_prox_write
4123 .check_oauth2_token_exchange(
4124 &client_authz,
4125 &token_req,
4126 ct + Duration::from_secs(TOKEN_EXPIRE)
4127 )
4128 .unwrap_err()
4129 == Oauth2Error::InvalidRequest
4130 );
4131
4132 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4153 code: permit_success.code.clone(),
4154 redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4155 code_verifier,
4156 }
4157 .into();
4158 assert!(
4159 idms_prox_write
4160 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4161 .unwrap_err()
4162 == Oauth2Error::InvalidOrigin
4163 );
4164
4165 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4167 code: permit_success.code,
4168 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4169 code_verifier: Some("12345".to_string()),
4170 }
4171 .into();
4172 assert!(
4173 idms_prox_write
4174 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4175 .unwrap_err()
4176 == Oauth2Error::InvalidRequest
4177 );
4178
4179 assert!(idms_prox_write.commit().is_ok());
4180 }
4181
4182 #[idm_test]
4183 async fn test_idm_oauth2_supplemental_origin_redirect(
4184 idms: &IdmServer,
4185 _idms_delayed: &mut IdmServerDelayed,
4186 ) {
4187 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4188 let (secret, uat, ident, _) =
4189 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4190
4191 let idms_prox_read = idms.proxy_read().await.unwrap();
4192
4193 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
4195
4196 let redirect_uri = Url::parse("https://portal.example.com/?custom=foo").unwrap();
4197
4198 let auth_req = AuthorisationRequest {
4199 response_type: ResponseType::Code,
4200 response_mode: None,
4201 client_id: "test_resource_server".to_string(),
4202 state: None,
4203 pkce_request: Some(PkceRequest {
4204 code_challenge: code_challenge.clone(),
4205 code_challenge_method: CodeChallengeMethod::S256,
4206 }),
4207 redirect_uri: redirect_uri.clone(),
4208 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4209 nonce: Some("abcdef".to_string()),
4210 oidc_ext: Default::default(),
4211 max_age: None,
4212 unknown_keys: Default::default(),
4213 };
4214
4215 let consent_request = idms_prox_read
4216 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4217 .expect("OAuth2 authorisation failed");
4218
4219 trace!(?consent_request);
4220
4221 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4223 unreachable!();
4224 };
4225
4226 drop(idms_prox_read);
4228 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4229
4230 let permit_success = idms_prox_write
4231 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4232 .expect("Failed to perform OAuth2 permit");
4233
4234 assert_eq!(permit_success.state.as_deref(), None);
4236
4237 let permit_redirect_uri = permit_success.build_redirect_uri();
4240
4241 assert_eq!(permit_redirect_uri.origin(), redirect_uri.origin());
4242 assert_eq!(permit_redirect_uri.path(), redirect_uri.path());
4243 let query = BTreeMap::from_iter(permit_redirect_uri.query_pairs().into_owned());
4244 assert_eq!(query.get("custom").map(|s| s.as_str()), Some("foo"));
4246
4247 let token_req = AccessTokenRequest {
4250 grant_type: GrantTypeReq::AuthorizationCode {
4251 code: permit_success.code,
4252 redirect_uri,
4253 code_verifier: code_verifier.clone(),
4255 },
4256 client_id: Some("test_resource_server".to_string()),
4257 client_secret: Some(secret.clone()),
4258 };
4259
4260 let token_response = idms_prox_write
4261 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4262 .expect("Failed to perform OAuth2 token exchange");
4263
4264 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4266
4267 assert!(idms_prox_write.commit().is_ok());
4268
4269 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4273
4274 let ident = idms_prox_read
4276 .process_uat_to_identity(&uat, ct, Source::Internal)
4277 .expect("Unable to process uat");
4278
4279 let auth_req = AuthorisationRequest {
4280 response_type: ResponseType::Code,
4281 response_mode: None,
4282 client_id: "test_resource_server".to_string(),
4283 state: Some("123".to_string()),
4284 pkce_request: Some(PkceRequest {
4285 code_challenge,
4286 code_challenge_method: CodeChallengeMethod::S256,
4287 }),
4288 redirect_uri: Url::parse("app://cheese").unwrap(),
4289 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4290 nonce: Some("abcdef".to_string()),
4291 oidc_ext: Default::default(),
4292 max_age: None,
4293 unknown_keys: Default::default(),
4294 };
4295
4296 let consent_request = idms_prox_read
4297 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4298 .expect("OAuth2 authorisation failed");
4299
4300 trace!(?consent_request);
4301
4302 let AuthoriseResponse::Permitted(permit_success) = consent_request else {
4303 unreachable!();
4304 };
4305
4306 assert_eq!(permit_success.state.as_deref(), Some("123"));
4309
4310 let token_req = AccessTokenRequest {
4313 grant_type: GrantTypeReq::AuthorizationCode {
4314 code: permit_success.code,
4315 redirect_uri: Url::parse("app://cheese").unwrap(),
4316 code_verifier,
4318 },
4319 client_id: Some("test_resource_server".to_string()),
4320 client_secret: Some(secret),
4321 };
4322
4323 drop(idms_prox_read);
4324 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4325
4326 let token_response = idms_prox_write
4327 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4328 .expect("Failed to perform OAuth2 token exchange");
4329
4330 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4332 }
4333
4334 #[idm_test]
4335 async fn test_idm_oauth2_token_introspect(
4336 idms: &IdmServer,
4337 _idms_delayed: &mut IdmServerDelayed,
4338 ) {
4339 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4340 let (secret, _uat, ident, _) =
4341 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4342 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4343
4344 let idms_prox_read = idms.proxy_read().await.unwrap();
4345
4346 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
4348 let consent_request = good_authorisation_request!(
4349 idms_prox_read,
4350 &ident,
4351 ct,
4352 code_challenge,
4353 OAUTH2_SCOPE_OPENID.to_string()
4354 );
4355
4356 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4357 unreachable!();
4358 };
4359
4360 drop(idms_prox_read);
4362 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4363
4364 let permit_success = idms_prox_write
4365 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4366 .expect("Failed to perform OAuth2 permit");
4367
4368 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4369 code: permit_success.code,
4370 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4371 code_verifier,
4372 }
4373 .into();
4374 let oauth2_token = idms_prox_write
4375 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4376 .expect("Unable to exchange for OAuth2 token");
4377
4378 assert!(idms_prox_write.commit().is_ok());
4379
4380 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4382
4383 let intr_request = AccessTokenIntrospectRequest {
4384 token: oauth2_token.access_token,
4385 token_type_hint: None,
4386 };
4387 let intr_response = idms_prox_read
4388 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4389 .expect("Failed to inspect token");
4390
4391 eprintln!("👉 {intr_response:?}");
4392 assert!(intr_response.active);
4393 assert_eq!(
4394 intr_response.scope,
4395 btreeset!["openid".to_string(), "supplement".to_string()]
4396 );
4397 assert_eq!(
4398 intr_response.client_id.as_deref(),
4399 Some("test_resource_server")
4400 );
4401 assert_eq!(
4402 intr_response.username.as_deref(),
4403 Some("testperson1@example.com")
4404 );
4405 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
4406 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
4407 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
4408
4409 drop(idms_prox_read);
4410 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4413 let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_CURRENT_TIME - 1));
4415 let me_inv_m = ModifyEvent::new_internal_invalid(
4416 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
4417 ModifyList::new_list(vec![Modify::Present(Attribute::AccountExpire, v_expire)]),
4418 );
4419 assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok());
4421 assert!(idms_prox_write.commit().is_ok());
4422
4423 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4426 let intr_response = idms_prox_read
4427 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4428 .expect("Failed to inspect token");
4429
4430 assert!(!intr_response.active);
4431 }
4432
4433 #[idm_test]
4434 async fn test_idm_oauth2_token_revoke(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
4435 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4437 let (secret, _uat, ident, _) =
4438 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4439 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4440
4441 let idms_prox_read = idms.proxy_read().await.unwrap();
4442
4443 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
4445 let consent_request = good_authorisation_request!(
4446 idms_prox_read,
4447 &ident,
4448 ct,
4449 code_challenge,
4450 OAUTH2_SCOPE_OPENID.to_string()
4451 );
4452
4453 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4454 unreachable!();
4455 };
4456
4457 drop(idms_prox_read);
4459 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4460
4461 let permit_success = idms_prox_write
4462 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4463 .expect("Failed to perform OAuth2 permit");
4464
4465 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4467 code: permit_success.code,
4468 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4469 code_verifier,
4470 }
4471 .into();
4472 let oauth2_token = idms_prox_write
4473 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4474 .expect("Unable to exchange for OAuth2 token");
4475
4476 assert!(idms_prox_write.commit().is_ok());
4477
4478 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4482 let intr_request = AccessTokenIntrospectRequest {
4483 token: oauth2_token.access_token.clone(),
4484 token_type_hint: None,
4485 };
4486 let intr_response = idms_prox_read
4487 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4488 .expect("Failed to inspect token");
4489 eprintln!("👉 {intr_response:?}");
4490 assert!(intr_response.active);
4491 drop(idms_prox_read);
4492
4493 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4495
4496 let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
4497
4498 let revoke_request = TokenRevokeRequest {
4499 token: oauth2_token.access_token.clone(),
4500 token_type_hint: None,
4501 };
4502 let e = idms_prox_write
4503 .oauth2_token_revoke(&bad_client_authz, &revoke_request, ct)
4504 .unwrap_err();
4505 assert!(matches!(e, Oauth2Error::AuthenticationRequired));
4506 assert!(idms_prox_write.commit().is_ok());
4507
4508 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4510 let revoke_request = TokenRevokeRequest {
4511 token: "this is an invalid token, nothing will happen!".to_string(),
4512 token_type_hint: None,
4513 };
4514 let e = idms_prox_write
4515 .oauth2_token_revoke(&client_authz, &revoke_request, ct)
4516 .unwrap_err();
4517 assert!(matches!(e, Oauth2Error::InvalidRequest));
4518 assert!(idms_prox_write.commit().is_ok());
4519
4520 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4522 let intr_response = idms_prox_read
4523 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4524 .expect("Failed to inspect token");
4525 assert!(intr_response.active);
4526 drop(idms_prox_read);
4527
4528 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4530 let revoke_request = TokenRevokeRequest {
4531 token: oauth2_token.access_token.clone(),
4532 token_type_hint: None,
4533 };
4534 assert!(idms_prox_write
4535 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
4536 .is_ok());
4537 assert!(idms_prox_write.commit().is_ok());
4538
4539 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4541 let intr_response = idms_prox_read
4542 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4543 .expect("Failed to inspect token");
4544
4545 assert!(!intr_response.active);
4546 drop(idms_prox_read);
4547
4548 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4550 let filt = filter!(f_eq(
4551 Attribute::Uuid,
4552 PartialValue::Uuid(ident.get_uuid().unwrap())
4553 ));
4554 let mut work_set = idms_prox_write
4555 .qs_write
4556 .internal_search_writeable(&filt)
4557 .expect("Failed to perform internal search writeable");
4558 for (_, entry) in work_set.iter_mut() {
4559 let _ = entry.force_trim_ava(Attribute::OAuth2Session);
4560 }
4561 assert!(idms_prox_write
4562 .qs_write
4563 .internal_apply_writable(work_set)
4564 .is_ok());
4565
4566 assert!(idms_prox_write.commit().is_ok());
4567
4568 let mut idms_prox_read = idms.proxy_read().await.unwrap();
4569 let intr_response = idms_prox_read
4571 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4572 .expect("Failed to inspect token");
4573 assert!(intr_response.active);
4574
4575 let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
4577 let intr_response = idms_prox_read
4578 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4579 .expect("Failed to inspect token");
4580 assert!(!intr_response.active);
4581
4582 drop(idms_prox_read);
4583
4584 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4586 let revoke_request = TokenRevokeRequest {
4587 token: oauth2_token.access_token,
4588 token_type_hint: None,
4589 };
4590 assert!(idms_prox_write
4591 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
4592 .is_ok());
4593 assert!(idms_prox_write.commit().is_ok());
4594 }
4595
4596 #[idm_test]
4597 async fn test_idm_oauth2_session_cleanup_post_rs_delete(
4598 idms: &IdmServer,
4599 _idms_delayed: &mut IdmServerDelayed,
4600 ) {
4601 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4603 let (secret, _uat, ident, _) =
4604 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4605 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4606
4607 let idms_prox_read = idms.proxy_read().await.unwrap();
4608
4609 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
4611 let consent_request = good_authorisation_request!(
4612 idms_prox_read,
4613 &ident,
4614 ct,
4615 code_challenge,
4616 OAUTH2_SCOPE_OPENID.to_string()
4617 );
4618
4619 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4620 unreachable!();
4621 };
4622
4623 drop(idms_prox_read);
4625 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4626
4627 let permit_success = idms_prox_write
4628 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4629 .expect("Failed to perform OAuth2 permit");
4630
4631 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4632 code: permit_success.code,
4633 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4634 code_verifier,
4635 }
4636 .into();
4637
4638 let oauth2_token = idms_prox_write
4639 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4640 .expect("Unable to exchange for OAuth2 token");
4641
4642 let access_token =
4643 JwsCompact::from_str(&oauth2_token.access_token).expect("Invalid Access Token");
4644
4645 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
4646
4647 let reflected_token = jws_verifier
4648 .verify(&access_token)
4649 .unwrap()
4650 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
4651 .expect("Failed to access internals of the refresh token");
4652
4653 let session_id = reflected_token.extensions.session_id;
4654
4655 assert!(idms_prox_write.commit().is_ok());
4656
4657 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4659
4660 let entry = idms_prox_write
4662 .qs_write
4663 .internal_search_uuid(UUID_TESTPERSON_1)
4664 .expect("failed");
4665 let valid = entry
4666 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
4667 .map(|map| map.get(&session_id).is_some())
4668 .unwrap_or(false);
4669 assert!(valid);
4670
4671 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
4674 Attribute::Name,
4675 PartialValue::new_iname("test_resource_server")
4676 )));
4677
4678 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
4679
4680 let entry = idms_prox_write
4684 .qs_write
4685 .internal_search_uuid(UUID_TESTPERSON_1)
4686 .expect("failed");
4687 let revoked = entry
4688 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
4689 .and_then(|sessions| sessions.get(&session_id))
4690 .map(|session| matches!(session.state, SessionState::RevokedAt(_)))
4691 .unwrap_or(false);
4692 assert!(revoked);
4693
4694 assert!(idms_prox_write.commit().is_ok());
4695 }
4696
4697 #[idm_test]
4698 async fn test_idm_oauth2_authorisation_reject(
4699 idms: &IdmServer,
4700 _idms_delayed: &mut IdmServerDelayed,
4701 ) {
4702 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4703 let (_secret, _uat, ident, _) =
4704 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4705
4706 let ident2 = {
4707 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4708 let account = idms_prox_write
4709 .target_to_account(UUID_IDM_ADMIN)
4710 .expect("account must exist");
4711 let session_id = uuid::Uuid::new_v4();
4712 let uat2 = account
4713 .to_userauthtoken(
4714 session_id,
4715 SessionScope::ReadWrite,
4716 ct,
4717 &ResolvedAccountPolicy::test_policy(),
4718 )
4719 .expect("Unable to create uat");
4720
4721 idms_prox_write
4722 .process_uat_to_identity(&uat2, ct, Source::Internal)
4723 .expect("Unable to process uat")
4724 };
4725
4726 let idms_prox_read = idms.proxy_read().await.unwrap();
4727 let redirect_uri = Url::parse("https://demo.example.com/oauth2/result").unwrap();
4728 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
4729
4730 let consent_request = good_authorisation_request!(
4732 idms_prox_read,
4733 &ident,
4734 ct,
4735 code_challenge,
4736 OAUTH2_SCOPE_OPENID.to_string()
4737 );
4738
4739 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4740 unreachable!();
4741 };
4742
4743 let reject_success = idms_prox_read
4744 .check_oauth2_authorise_reject(&ident, &consent_token, ct)
4745 .expect("Failed to perform OAuth2 reject");
4746
4747 assert_eq!(reject_success.redirect_uri, redirect_uri);
4748
4749 let past_ct = Duration::from_secs(TEST_CURRENT_TIME + 301);
4751 assert!(
4752 idms_prox_read
4753 .check_oauth2_authorise_reject(&ident, &consent_token, past_ct)
4754 .unwrap_err()
4755 == OperationError::CryptographyError
4756 );
4757
4758 assert_eq!(
4760 idms_prox_read
4761 .check_oauth2_authorise_reject(&ident, "not a token", ct)
4762 .unwrap_err(),
4763 OperationError::CryptographyError
4764 );
4765
4766 assert!(
4768 idms_prox_read
4769 .check_oauth2_authorise_reject(&ident2, &consent_token, ct)
4770 .unwrap_err()
4771 == OperationError::InvalidSessionState
4772 );
4773 }
4774
4775 #[idm_test]
4776 async fn test_idm_oauth2_rfc8414_metadata(
4777 idms: &IdmServer,
4778 _idms_delayed: &mut IdmServerDelayed,
4779 ) {
4780 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4781 let (_secret, _uat, _ident, _) =
4782 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4783
4784 let idms_prox_read = idms.proxy_read().await.unwrap();
4785
4786 assert!(
4788 idms_prox_read
4789 .oauth2_rfc8414_metadata("nosuchclient")
4790 .unwrap_err()
4791 == OperationError::NoMatchingEntries
4792 );
4793
4794 let discovery = idms_prox_read
4795 .oauth2_rfc8414_metadata("test_resource_server")
4796 .expect("Failed to get discovery");
4797
4798 assert!(
4799 discovery.issuer
4800 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
4801 .unwrap()
4802 );
4803
4804 assert!(
4805 discovery.authorization_endpoint
4806 == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
4807 );
4808
4809 assert!(
4810 discovery.token_endpoint
4811 == Url::parse(&format!(
4812 "https://idm.example.com{}",
4813 uri::OAUTH2_TOKEN_ENDPOINT
4814 ))
4815 .unwrap()
4816 );
4817
4818 assert!(
4819 discovery.jwks_uri
4820 == Some(
4821 Url::parse(
4822 "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
4823 )
4824 .unwrap()
4825 )
4826 );
4827
4828 assert!(discovery.registration_endpoint.is_none());
4829
4830 assert!(
4831 discovery.scopes_supported
4832 == Some(vec![
4833 "groups".to_string(),
4834 OAUTH2_SCOPE_OPENID.to_string(),
4835 "supplement".to_string(),
4836 ])
4837 );
4838
4839 assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
4840 assert_eq!(
4841 discovery.response_modes_supported,
4842 vec![ResponseMode::Query, ResponseMode::Fragment]
4843 );
4844 assert_eq!(
4845 discovery.grant_types_supported,
4846 vec![GrantType::AuthorisationCode]
4847 );
4848 assert!(
4849 discovery.token_endpoint_auth_methods_supported
4850 == vec![
4851 TokenEndpointAuthMethod::ClientSecretBasic,
4852 TokenEndpointAuthMethod::ClientSecretPost
4853 ]
4854 );
4855 assert!(discovery.service_documentation.is_some());
4856
4857 assert!(discovery.ui_locales_supported.is_none());
4858 assert!(discovery.op_policy_uri.is_none());
4859 assert!(discovery.op_tos_uri.is_none());
4860
4861 assert!(
4862 discovery.revocation_endpoint
4863 == Some(
4864 Url::parse(&format!(
4865 "https://idm.example.com{}",
4866 OAUTH2_TOKEN_REVOKE_ENDPOINT
4867 ))
4868 .unwrap()
4869 )
4870 );
4871 assert!(
4872 discovery.revocation_endpoint_auth_methods_supported
4873 == vec![
4874 TokenEndpointAuthMethod::ClientSecretBasic,
4875 TokenEndpointAuthMethod::ClientSecretPost
4876 ]
4877 );
4878
4879 assert!(
4880 discovery.introspection_endpoint
4881 == Some(
4882 Url::parse(&format!(
4883 "https://idm.example.com{}",
4884 kanidm_proto::constants::uri::OAUTH2_TOKEN_INTROSPECT_ENDPOINT
4885 ))
4886 .unwrap()
4887 )
4888 );
4889 assert!(
4890 discovery.introspection_endpoint_auth_methods_supported
4891 == vec![
4892 TokenEndpointAuthMethod::ClientSecretBasic,
4893 TokenEndpointAuthMethod::ClientSecretPost
4894 ]
4895 );
4896 assert!(discovery
4897 .introspection_endpoint_auth_signing_alg_values_supported
4898 .is_none());
4899
4900 assert_eq!(
4901 discovery.code_challenge_methods_supported,
4902 vec![PkceAlg::S256]
4903 )
4904 }
4905
4906 #[idm_test]
4907 async fn test_idm_oauth2_openid_discovery(
4908 idms: &IdmServer,
4909 _idms_delayed: &mut IdmServerDelayed,
4910 ) {
4911 let ct = Duration::from_secs(TEST_CURRENT_TIME);
4912 let (_secret, _uat, _ident, _) =
4913 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4914
4915 let idms_prox_read = idms.proxy_read().await.unwrap();
4916
4917 assert!(
4919 idms_prox_read
4920 .oauth2_openid_discovery("nosuchclient")
4921 .unwrap_err()
4922 == OperationError::NoMatchingEntries
4923 );
4924
4925 assert!(
4926 idms_prox_read
4927 .oauth2_openid_publickey("nosuchclient")
4928 .unwrap_err()
4929 == OperationError::NoMatchingEntries
4930 );
4931
4932 let discovery = idms_prox_read
4933 .oauth2_openid_discovery("test_resource_server")
4934 .expect("Failed to get discovery");
4935
4936 let mut jwkset = idms_prox_read
4937 .oauth2_openid_publickey("test_resource_server")
4938 .expect("Failed to get public key");
4939
4940 let jwk = jwkset.keys.pop().expect("no such jwk");
4941
4942 match jwk {
4943 Jwk::EC { alg, use_, kid, .. } => {
4944 match (
4945 alg.unwrap(),
4946 &discovery.id_token_signing_alg_values_supported[0],
4947 ) {
4948 (JwaAlg::ES256, IdTokenSignAlg::ES256) => {}
4949 _ => panic!(),
4950 };
4951 assert_eq!(use_.unwrap(), JwkUse::Sig);
4952 assert!(kid.is_some())
4953 }
4954 _ => panic!(),
4955 };
4956
4957 assert!(
4958 discovery.issuer
4959 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
4960 .unwrap()
4961 );
4962
4963 assert!(
4964 discovery.authorization_endpoint
4965 == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
4966 );
4967
4968 assert!(
4969 discovery.token_endpoint == Url::parse("https://idm.example.com/oauth2/token").unwrap()
4970 );
4971
4972 assert!(
4973 discovery.userinfo_endpoint
4974 == Some(
4975 Url::parse(
4976 "https://idm.example.com/oauth2/openid/test_resource_server/userinfo"
4977 )
4978 .unwrap()
4979 )
4980 );
4981
4982 assert!(
4983 discovery.jwks_uri
4984 == Url::parse(
4985 "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
4986 )
4987 .unwrap()
4988 );
4989
4990 assert!(
4991 discovery.scopes_supported
4992 == Some(vec![
4993 "groups".to_string(),
4994 OAUTH2_SCOPE_OPENID.to_string(),
4995 "supplement".to_string(),
4996 ])
4997 );
4998
4999 assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5000 assert_eq!(
5001 discovery.response_modes_supported,
5002 vec![ResponseMode::Query, ResponseMode::Fragment]
5003 );
5004 assert_eq!(
5005 discovery.grant_types_supported,
5006 vec![GrantType::AuthorisationCode]
5007 );
5008 assert_eq!(discovery.subject_types_supported, vec![SubjectType::Public]);
5009 assert_eq!(
5010 discovery.id_token_signing_alg_values_supported,
5011 vec![IdTokenSignAlg::ES256]
5012 );
5013 assert!(discovery.userinfo_signing_alg_values_supported.is_none());
5014 assert!(
5015 discovery.token_endpoint_auth_methods_supported
5016 == vec![
5017 TokenEndpointAuthMethod::ClientSecretBasic,
5018 TokenEndpointAuthMethod::ClientSecretPost
5019 ]
5020 );
5021 assert_eq!(
5022 discovery.display_values_supported,
5023 Some(vec![DisplayValue::Page])
5024 );
5025 assert_eq!(discovery.claim_types_supported, vec![ClaimType::Normal]);
5026 assert!(discovery.claims_supported.is_none());
5027 assert!(discovery.service_documentation.is_some());
5028
5029 assert!(discovery.registration_endpoint.is_none());
5030 assert!(discovery.acr_values_supported.is_none());
5031 assert!(discovery.id_token_encryption_alg_values_supported.is_none());
5032 assert!(discovery.id_token_encryption_enc_values_supported.is_none());
5033 assert!(discovery.userinfo_encryption_alg_values_supported.is_none());
5034 assert!(discovery.userinfo_encryption_enc_values_supported.is_none());
5035 assert!(discovery
5036 .request_object_signing_alg_values_supported
5037 .is_none());
5038 assert!(discovery
5039 .request_object_encryption_alg_values_supported
5040 .is_none());
5041 assert!(discovery
5042 .request_object_encryption_enc_values_supported
5043 .is_none());
5044 assert!(discovery
5045 .token_endpoint_auth_signing_alg_values_supported
5046 .is_none());
5047 assert!(discovery.claims_locales_supported.is_none());
5048 assert!(discovery.ui_locales_supported.is_none());
5049 assert!(discovery.op_policy_uri.is_none());
5050 assert!(discovery.op_tos_uri.is_none());
5051 assert!(!discovery.claims_parameter_supported);
5052 assert!(!discovery.request_uri_parameter_supported);
5053 assert!(!discovery.require_request_uri_registration);
5054 assert!(!discovery.request_parameter_supported);
5055 assert_eq!(
5056 discovery.code_challenge_methods_supported,
5057 vec![PkceAlg::S256]
5058 );
5059
5060 assert!(
5062 discovery.revocation_endpoint
5063 == Some(
5064 Url::parse(&format!(
5065 "https://idm.example.com{}",
5066 OAUTH2_TOKEN_REVOKE_ENDPOINT
5067 ))
5068 .unwrap()
5069 )
5070 );
5071 assert!(
5072 discovery.revocation_endpoint_auth_methods_supported
5073 == vec![
5074 TokenEndpointAuthMethod::ClientSecretBasic,
5075 TokenEndpointAuthMethod::ClientSecretPost
5076 ]
5077 );
5078
5079 assert!(
5080 discovery.introspection_endpoint
5081 == Some(
5082 Url::parse(&format!(
5083 "https://idm.example.com{}",
5084 OAUTH2_TOKEN_INTROSPECT_ENDPOINT
5085 ))
5086 .unwrap()
5087 )
5088 );
5089 assert!(
5090 discovery.introspection_endpoint_auth_methods_supported
5091 == vec![
5092 TokenEndpointAuthMethod::ClientSecretBasic,
5093 TokenEndpointAuthMethod::ClientSecretPost
5094 ]
5095 );
5096 assert!(discovery
5097 .introspection_endpoint_auth_signing_alg_values_supported
5098 .is_none());
5099 }
5100
5101 #[idm_test]
5102 async fn test_idm_oauth2_openid_extensions(
5103 idms: &IdmServer,
5104 _idms_delayed: &mut IdmServerDelayed,
5105 ) {
5106 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5107 let (secret, _uat, ident, _) =
5108 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5109 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5110
5111 let idms_prox_read = idms.proxy_read().await.unwrap();
5112
5113 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5114
5115 let consent_request = good_authorisation_request!(
5116 idms_prox_read,
5117 &ident,
5118 ct,
5119 code_challenge,
5120 OAUTH2_SCOPE_OPENID.to_string()
5121 );
5122
5123 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5124 unreachable!();
5125 };
5126
5127 drop(idms_prox_read);
5129 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5130
5131 let permit_success = idms_prox_write
5132 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5133 .expect("Failed to perform OAuth2 permit");
5134
5135 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5137 code: permit_success.code,
5138 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5139 code_verifier,
5141 }
5142 .into();
5143
5144 let token_response = idms_prox_write
5145 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5146 .expect("Failed to perform OAuth2 token exchange");
5147
5148 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
5150
5151 let id_token = token_response.id_token.expect("No id_token in response!");
5152
5153 let access_token =
5154 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5155
5156 let refresh_token = token_response
5157 .refresh_token
5158 .as_ref()
5159 .expect("no refresh token was issued")
5160 .clone();
5161
5162 assert!(idms_prox_write.commit().is_ok());
5164
5165 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5166
5167 let mut jwkset = idms_prox_read
5168 .oauth2_openid_publickey("test_resource_server")
5169 .expect("Failed to get public key");
5170
5171 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5172
5173 let jws_validator =
5174 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5175
5176 let oidc_unverified =
5177 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5178
5179 let iat = ct.as_secs() as i64;
5180
5181 let oidc = jws_validator
5182 .verify(&oidc_unverified)
5183 .unwrap()
5184 .verify_exp(iat)
5185 .expect("Failed to verify oidc");
5186
5187 assert!(
5189 oidc.iss
5190 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5191 .unwrap()
5192 );
5193 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
5194 assert_eq!(oidc.aud, "test_resource_server");
5195 assert_eq!(oidc.iat, iat);
5196 assert_eq!(oidc.nbf, Some(iat));
5197 assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
5199 assert!(oidc.auth_time.is_none());
5200 assert_eq!(oidc.nonce, Some("abcdef".to_string()));
5202 assert!(oidc.at_hash.is_none());
5203 assert!(oidc.acr.is_none());
5204 assert!(oidc.amr.is_none());
5205 assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
5206 assert!(oidc.jti.is_none());
5207 assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
5208 assert_eq!(
5209 oidc.s_claims.preferred_username,
5210 Some("testperson1@example.com".to_string())
5211 );
5212 assert!(
5213 oidc.s_claims.scopes == vec![OAUTH2_SCOPE_OPENID.to_string(), "supplement".to_string()]
5214 );
5215 assert!(oidc.claims.is_empty());
5216 let userinfo = idms_prox_read
5219 .oauth2_openid_userinfo("test_resource_server", access_token, ct)
5220 .expect("failed to get userinfo");
5221
5222 assert_eq!(oidc.iss, userinfo.iss);
5223 assert_eq!(oidc.sub, userinfo.sub);
5224 assert_eq!(oidc.aud, userinfo.aud);
5225 assert_eq!(oidc.iat, userinfo.iat);
5226 assert_eq!(oidc.nbf, userinfo.nbf);
5227 assert_eq!(oidc.exp, userinfo.exp);
5228 assert!(userinfo.auth_time.is_none());
5229 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5230 assert!(userinfo.at_hash.is_none());
5231 assert!(userinfo.acr.is_none());
5232 assert_eq!(oidc.amr, userinfo.amr);
5233 assert_eq!(oidc.azp, userinfo.azp);
5234 assert!(userinfo.jti.is_none());
5235 assert_eq!(oidc.s_claims, userinfo.s_claims);
5236 assert!(userinfo.claims.is_empty());
5237
5238 drop(idms_prox_read);
5239
5240 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5244
5245 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
5246 refresh_token,
5247 scope: None,
5248 }
5249 .into();
5250
5251 let token_response = idms_prox_write
5252 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5253 .expect("Unable to exchange for OAuth2 token");
5254
5255 let access_token =
5256 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5257
5258 assert!(idms_prox_write.commit().is_ok());
5259
5260 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5262
5263 let userinfo = idms_prox_read
5264 .oauth2_openid_userinfo("test_resource_server", access_token, ct)
5265 .expect("failed to get userinfo");
5266
5267 assert_eq!(oidc.iss, userinfo.iss);
5268 assert_eq!(oidc.sub, userinfo.sub);
5269 assert_eq!(oidc.aud, userinfo.aud);
5270 assert_eq!(oidc.iat, userinfo.iat);
5271 assert_eq!(oidc.nbf, userinfo.nbf);
5272 assert_eq!(oidc.exp, userinfo.exp);
5273 assert!(userinfo.auth_time.is_none());
5274 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5275 assert!(userinfo.at_hash.is_none());
5276 assert!(userinfo.acr.is_none());
5277 assert_eq!(oidc.amr, userinfo.amr);
5278 assert_eq!(oidc.azp, userinfo.azp);
5279 assert!(userinfo.jti.is_none());
5280 assert_eq!(oidc.s_claims, userinfo.s_claims);
5281 assert!(userinfo.claims.is_empty());
5282 }
5283
5284 #[idm_test]
5285 async fn test_idm_oauth2_openid_short_username(
5286 idms: &IdmServer,
5287 _idms_delayed: &mut IdmServerDelayed,
5288 ) {
5289 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5292 let (secret, _uat, ident, _) =
5293 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5294 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5295
5296 let idms_prox_read = idms.proxy_read().await.unwrap();
5297
5298 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5299
5300 let consent_request = good_authorisation_request!(
5301 idms_prox_read,
5302 &ident,
5303 ct,
5304 code_challenge,
5305 OAUTH2_SCOPE_OPENID.to_string()
5306 );
5307
5308 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5309 unreachable!();
5310 };
5311
5312 drop(idms_prox_read);
5314 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5315
5316 let permit_success = idms_prox_write
5317 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5318 .expect("Failed to perform OAuth2 permit");
5319
5320 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5322 code: permit_success.code,
5323 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5324 code_verifier,
5326 }
5327 .into();
5328
5329 let token_response = idms_prox_write
5330 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5331 .expect("Failed to perform OAuth2 token exchange");
5332
5333 let id_token = token_response.id_token.expect("No id_token in response!");
5334 let access_token =
5335 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5336
5337 assert!(idms_prox_write.commit().is_ok());
5338 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5339
5340 let mut jwkset = idms_prox_read
5341 .oauth2_openid_publickey("test_resource_server")
5342 .expect("Failed to get public key");
5343 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5344
5345 let jws_validator =
5346 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5347
5348 let oidc_unverified =
5349 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5350
5351 let iat = ct.as_secs() as i64;
5352
5353 let oidc = jws_validator
5354 .verify(&oidc_unverified)
5355 .unwrap()
5356 .verify_exp(iat)
5357 .expect("Failed to verify oidc");
5358
5359 assert_eq!(
5361 oidc.s_claims.preferred_username,
5362 Some("testperson1".to_string())
5363 );
5364 let userinfo = idms_prox_read
5366 .oauth2_openid_userinfo("test_resource_server", access_token, ct)
5367 .expect("failed to get userinfo");
5368
5369 assert_eq!(oidc.s_claims, userinfo.s_claims);
5370 }
5371
5372 #[idm_test]
5373 async fn test_idm_oauth2_openid_group_claims(
5374 idms: &IdmServer,
5375 _idms_delayed: &mut IdmServerDelayed,
5376 ) {
5377 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5380 let (secret, _uat, ident, _) =
5381 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5382 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5383
5384 let idms_prox_read = idms.proxy_read().await.unwrap();
5385
5386 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5387
5388 let consent_request = good_authorisation_request!(
5389 idms_prox_read,
5390 &ident,
5391 ct,
5392 code_challenge,
5393 "openid groups".to_string()
5394 );
5395
5396 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5397 unreachable!();
5398 };
5399
5400 drop(idms_prox_read);
5402 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5403
5404 let permit_success = idms_prox_write
5405 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5406 .expect("Failed to perform OAuth2 permit");
5407
5408 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5410 code: permit_success.code,
5411 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5412 code_verifier,
5414 }
5415 .into();
5416
5417 let token_response = idms_prox_write
5418 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5419 .expect("Failed to perform OAuth2 token exchange");
5420
5421 let id_token = token_response.id_token.expect("No id_token in response!");
5422 let access_token =
5423 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5424
5425 assert!(idms_prox_write.commit().is_ok());
5426 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5427
5428 let mut jwkset = idms_prox_read
5429 .oauth2_openid_publickey("test_resource_server")
5430 .expect("Failed to get public key");
5431 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5432
5433 let jws_validator =
5434 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5435
5436 let oidc_unverified =
5437 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5438
5439 let iat = ct.as_secs() as i64;
5440
5441 let oidc = jws_validator
5442 .verify(&oidc_unverified)
5443 .unwrap()
5444 .verify_exp(iat)
5445 .expect("Failed to verify oidc");
5446
5447 assert!(oidc.claims.contains_key("groups"));
5449
5450 assert!(oidc
5451 .claims
5452 .get("groups")
5453 .expect("unable to find key")
5454 .as_array()
5455 .unwrap()
5456 .contains(&serde_json::json!(STR_UUID_IDM_ALL_ACCOUNTS)));
5457
5458 let userinfo = idms_prox_read
5460 .oauth2_openid_userinfo("test_resource_server", access_token, ct)
5461 .expect("failed to get userinfo");
5462
5463 assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
5465 }
5466
5467 #[idm_test]
5468 async fn test_idm_oauth2_openid_ssh_publickey_claim(
5469 idms: &IdmServer,
5470 _idms_delayed: &mut IdmServerDelayed,
5471 ) {
5472 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5473 let (secret, _uat, ident, client_uuid) =
5474 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5475
5476 const ECDSA_SSH_PUBLIC_KEY: &str = "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3BtOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81lzPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@amethyst";
5479 let ssh_pubkey = SshPublicKey::from_string(ECDSA_SSH_PUBLIC_KEY).unwrap();
5480
5481 let scope_set = BTreeSet::from([OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string()]);
5482
5483 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5484
5485 idms_prox_write
5486 .qs_write
5487 .internal_batch_modify(
5488 [
5489 (
5490 UUID_TESTPERSON_1,
5491 ModifyList::new_set(
5492 Attribute::SshPublicKey,
5493 ValueSetSshKey::new("label".to_string(), ssh_pubkey),
5494 ),
5495 ),
5496 (
5497 client_uuid,
5498 ModifyList::new_set(
5499 Attribute::OAuth2RsSupScopeMap,
5500 ValueSetOauthScopeMap::new(UUID_IDM_ALL_ACCOUNTS, scope_set),
5501 ),
5502 ),
5503 ]
5504 .into_iter(),
5505 )
5506 .expect("Failed to modify test entries");
5507
5508 assert!(idms_prox_write.commit().is_ok());
5509
5510 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5511
5512 let idms_prox_read = idms.proxy_read().await.unwrap();
5513
5514 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5515
5516 let consent_request = good_authorisation_request!(
5517 idms_prox_read,
5518 &ident,
5519 ct,
5520 code_challenge,
5521 "openid groups".to_string()
5522 );
5523
5524 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5525 unreachable!();
5526 };
5527
5528 drop(idms_prox_read);
5530 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5531
5532 let permit_success = idms_prox_write
5533 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5534 .expect("Failed to perform OAuth2 permit");
5535
5536 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5538 code: permit_success.code,
5539 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5540 code_verifier,
5542 }
5543 .into();
5544
5545 let token_response = idms_prox_write
5546 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5547 .expect("Failed to perform OAuth2 token exchange");
5548
5549 let id_token = token_response.id_token.expect("No id_token in response!");
5550 let access_token =
5551 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5552
5553 assert!(idms_prox_write.commit().is_ok());
5554 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5555
5556 let mut jwkset = idms_prox_read
5557 .oauth2_openid_publickey("test_resource_server")
5558 .expect("Failed to get public key");
5559 let public_jwk = jwkset.keys.pop().expect("no such jwk");
5560
5561 let jws_validator =
5562 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5563
5564 let oidc_unverified =
5565 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5566
5567 let iat = ct.as_secs() as i64;
5568
5569 let oidc = jws_validator
5570 .verify(&oidc_unverified)
5571 .unwrap()
5572 .verify_exp(iat)
5573 .expect("Failed to verify oidc");
5574
5575 assert!(oidc.claims.contains_key(OAUTH2_SCOPE_SSH_PUBLICKEYS));
5577
5578 assert!(oidc
5579 .claims
5580 .get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
5581 .expect("unable to find key")
5582 .as_array()
5583 .unwrap()
5584 .contains(&serde_json::json!(ECDSA_SSH_PUBLIC_KEY)));
5585
5586 let userinfo = idms_prox_read
5588 .oauth2_openid_userinfo("test_resource_server", access_token, ct)
5589 .expect("failed to get userinfo");
5590
5591 assert_eq!(
5593 oidc.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS),
5594 userinfo.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
5595 );
5596 }
5597
5598 #[idm_test]
5600 async fn test_idm_oauth2_insecure_pkce(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
5601 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5602 let (_secret, _uat, ident, _) =
5603 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
5604
5605 let idms_prox_read = idms.proxy_read().await.unwrap();
5606
5607 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5609
5610 let _consent_request = good_authorisation_request!(
5612 idms_prox_read,
5613 &ident,
5614 ct,
5615 code_challenge,
5616 OAUTH2_SCOPE_OPENID.to_string()
5617 );
5618
5619 let auth_req = AuthorisationRequest {
5621 response_type: ResponseType::Code,
5622 response_mode: None,
5623 client_id: "test_resource_server".to_string(),
5624 state: Some("123".to_string()),
5625 pkce_request: None,
5626 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5627 scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
5628 nonce: Some("abcdef".to_string()),
5629 oidc_ext: Default::default(),
5630 max_age: None,
5631 unknown_keys: Default::default(),
5632 };
5633
5634 idms_prox_read
5635 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
5636 .expect("Oauth2 authorisation failed");
5637 }
5638
5639 #[idm_test]
5640 async fn test_idm_oauth2_webfinger(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
5641 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5642 let (_secret, _uat, _ident, _) =
5643 setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5644 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5645
5646 let user = "testperson1@example.com";
5647
5648 let webfinger = idms_prox_read
5649 .oauth2_openid_webfinger("test_resource_server", user)
5650 .expect("Failed to get webfinger");
5651
5652 assert_eq!(webfinger.subject, user);
5653 assert_eq!(webfinger.links.len(), 1);
5654
5655 let link = &webfinger.links[0];
5656 assert_eq!(link.rel, "http://openid.net/specs/connect/1.0/issuer");
5657 assert_eq!(
5658 link.href,
5659 "https://idm.example.com/oauth2/openid/test_resource_server"
5660 );
5661
5662 let failed_webfinger = idms_prox_read
5663 .oauth2_openid_webfinger("test_resource_server", "someone@another.domain");
5664 assert!(failed_webfinger.is_err());
5665 }
5666
5667 #[idm_test]
5668 async fn test_idm_oauth2_openid_legacy_crypto(
5669 idms: &IdmServer,
5670 _idms_delayed: &mut IdmServerDelayed,
5671 ) {
5672 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5673 let (secret, _uat, ident, _) =
5674 setup_oauth2_resource_server_basic(idms, ct, false, true, false).await;
5675 let idms_prox_read = idms.proxy_read().await.unwrap();
5676 let discovery = idms_prox_read
5679 .oauth2_openid_discovery("test_resource_server")
5680 .expect("Failed to get discovery");
5681
5682 let mut jwkset = idms_prox_read
5683 .oauth2_openid_publickey("test_resource_server")
5684 .expect("Failed to get public key");
5685
5686 let jwk = jwkset.keys.pop().expect("no such jwk");
5687 let public_jwk = jwk.clone();
5688
5689 match jwk {
5690 Jwk::RSA { alg, use_, kid, .. } => {
5691 match (
5692 alg.unwrap(),
5693 &discovery.id_token_signing_alg_values_supported[0],
5694 ) {
5695 (JwaAlg::RS256, IdTokenSignAlg::RS256) => {}
5696 _ => panic!(),
5697 };
5698 assert_eq!(use_.unwrap(), JwkUse::Sig);
5699 assert!(kid.is_some());
5700 }
5701 _ => panic!(),
5702 };
5703
5704 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5706
5707 let consent_request = good_authorisation_request!(
5708 idms_prox_read,
5709 &ident,
5710 ct,
5711 code_challenge,
5712 OAUTH2_SCOPE_OPENID.to_string()
5713 );
5714
5715 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5716 unreachable!();
5717 };
5718
5719 drop(idms_prox_read);
5721 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5722
5723 let permit_success = idms_prox_write
5724 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5725 .expect("Failed to perform OAuth2 permit");
5726
5727 let token_req = AccessTokenRequest {
5729 grant_type: GrantTypeReq::AuthorizationCode {
5730 code: permit_success.code,
5731 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5732 code_verifier,
5734 },
5735 client_id: Some("test_resource_server".to_string()),
5736 client_secret: Some(secret),
5737 };
5738
5739 let token_response = idms_prox_write
5740 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
5741 .expect("Failed to perform OAuth2 token exchange");
5742
5743 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
5745 let id_token = token_response.id_token.expect("No id_token in response!");
5746
5747 let jws_validator =
5748 JwsRs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5749
5750 let oidc_unverified =
5751 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5752
5753 let iat = ct.as_secs() as i64;
5754
5755 let oidc = jws_validator
5756 .verify(&oidc_unverified)
5757 .unwrap()
5758 .verify_exp(iat)
5759 .expect("Failed to verify oidc");
5760
5761 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
5762
5763 assert!(idms_prox_write.commit().is_ok());
5764 }
5765
5766 #[idm_test]
5767 async fn test_idm_oauth2_consent_granted_and_changed_workflow(
5768 idms: &IdmServer,
5769 _idms_delayed: &mut IdmServerDelayed,
5770 ) {
5771 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5772 let (_secret, uat, ident, _) =
5773 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5774
5775 let idms_prox_read = idms.proxy_read().await.unwrap();
5776
5777 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5778 let consent_request = good_authorisation_request!(
5779 idms_prox_read,
5780 &ident,
5781 ct,
5782 code_challenge,
5783 OAUTH2_SCOPE_OPENID.to_string()
5784 );
5785
5786 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5788 unreachable!();
5789 };
5790
5791 drop(idms_prox_read);
5793 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5794
5795 let _permit_success = idms_prox_write
5796 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5797 .expect("Failed to perform OAuth2 permit");
5798
5799 assert!(idms_prox_write.commit().is_ok());
5800
5801 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5803
5804 let ident = idms_prox_read
5806 .process_uat_to_identity(&uat, ct, Source::Internal)
5807 .expect("Unable to process uat");
5808
5809 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5810 let consent_request = good_authorisation_request!(
5811 idms_prox_read,
5812 &ident,
5813 ct,
5814 code_challenge,
5815 OAUTH2_SCOPE_OPENID.to_string()
5816 );
5817
5818 let AuthoriseResponse::Permitted(_permit_success) = consent_request else {
5820 unreachable!();
5821 };
5822
5823 drop(idms_prox_read);
5824
5825 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5827
5828 let me_extend_scopes = ModifyEvent::new_internal_invalid(
5829 filter!(f_eq(
5830 Attribute::Name,
5831 PartialValue::new_iname("test_resource_server")
5832 )),
5833 ModifyList::new_list(vec![Modify::Present(
5834 Attribute::OAuth2RsScopeMap,
5835 Value::new_oauthscopemap(
5836 UUID_IDM_ALL_ACCOUNTS,
5837 btreeset![
5838 OAUTH2_SCOPE_EMAIL.to_string(),
5839 OAUTH2_SCOPE_OPENID.to_string()
5840 ],
5841 )
5842 .expect("invalid oauthscope"),
5843 )]),
5844 );
5845
5846 assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
5847 assert!(idms_prox_write.commit().is_ok());
5848
5849 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5852
5853 let ident = idms_prox_read
5855 .process_uat_to_identity(&uat, ct, Source::Internal)
5856 .expect("Unable to process uat");
5857
5858 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5859
5860 let auth_req = AuthorisationRequest {
5861 response_type: ResponseType::Code,
5862 response_mode: None,
5863 client_id: "test_resource_server".to_string(),
5864 state: Some("123".to_string()),
5865 pkce_request: Some(PkceRequest {
5866 code_challenge,
5867 code_challenge_method: CodeChallengeMethod::S256,
5868 }),
5869 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5870 scope: btreeset!["openid".to_string(), "email".to_string()],
5871 nonce: Some("abcdef".to_string()),
5872 oidc_ext: Default::default(),
5873 max_age: None,
5874 unknown_keys: Default::default(),
5875 };
5876
5877 let consent_request = idms_prox_read
5878 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
5879 .expect("Oauth2 authorisation failed");
5880
5881 let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
5883 unreachable!();
5884 };
5885
5886 drop(idms_prox_read);
5887
5888 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5892
5893 let me_extend_scopes = ModifyEvent::new_internal_invalid(
5894 filter!(f_eq(
5895 Attribute::Name,
5896 PartialValue::new_iname("test_resource_server")
5897 )),
5898 ModifyList::new_list(vec![Modify::Present(
5899 Attribute::OAuth2RsSupScopeMap,
5900 Value::new_oauthscopemap(UUID_IDM_ALL_ACCOUNTS, btreeset!["newscope".to_string()])
5901 .expect("invalid oauthscope"),
5902 )]),
5903 );
5904
5905 assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
5906 assert!(idms_prox_write.commit().is_ok());
5907
5908 let mut idms_prox_read = idms.proxy_read().await.unwrap();
5911
5912 let ident = idms_prox_read
5914 .process_uat_to_identity(&uat, ct, Source::Internal)
5915 .expect("Unable to process uat");
5916
5917 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5918
5919 let auth_req = AuthorisationRequest {
5920 response_type: ResponseType::Code,
5921 response_mode: None,
5922 client_id: "test_resource_server".to_string(),
5923 state: Some("123".to_string()),
5924 pkce_request: Some(PkceRequest {
5925 code_challenge,
5926 code_challenge_method: CodeChallengeMethod::S256,
5927 }),
5928 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5929 scope: btreeset!["openid".to_string(), "email".to_string()],
5931 nonce: Some("abcdef".to_string()),
5932 oidc_ext: Default::default(),
5933 max_age: None,
5934 unknown_keys: Default::default(),
5935 };
5936
5937 let consent_request = idms_prox_read
5938 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
5939 .expect("Oauth2 authorisation failed");
5940
5941 let _consent_token = if let AuthoriseResponse::ConsentRequested {
5943 consent_token,
5944 scopes,
5945 ..
5946 } = consent_request
5947 {
5948 assert!(scopes.contains("newscope"));
5949 consent_token
5950 } else {
5951 unreachable!();
5952 };
5953 }
5954
5955 #[idm_test]
5956 async fn test_idm_oauth2_consent_granted_refint_cleanup_on_delete(
5957 idms: &IdmServer,
5958 _idms_delayed: &mut IdmServerDelayed,
5959 ) {
5960 let ct = Duration::from_secs(TEST_CURRENT_TIME);
5961 let (_secret, uat, ident, o2rs_uuid) =
5962 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5963
5964 assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
5966
5967 let idms_prox_read = idms.proxy_read().await.unwrap();
5968
5969 let (_code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
5970 let consent_request = good_authorisation_request!(
5971 idms_prox_read,
5972 &ident,
5973 ct,
5974 code_challenge,
5975 OAUTH2_SCOPE_OPENID.to_string()
5976 );
5977
5978 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5980 unreachable!();
5981 };
5982
5983 drop(idms_prox_read);
5985 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5986
5987 let _permit_success = idms_prox_write
5988 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5989 .expect("Failed to perform OAuth2 permit");
5990
5991 let ident = idms_prox_write
5992 .process_uat_to_identity(&uat, ct, Source::Internal)
5993 .expect("Unable to process uat");
5994
5995 assert!(
5997 ident.get_oauth2_consent_scopes(o2rs_uuid)
5998 == Some(&btreeset![
5999 OAUTH2_SCOPE_OPENID.to_string(),
6000 "supplement".to_string()
6001 ])
6002 );
6003
6004 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
6006 Attribute::Name,
6007 PartialValue::new_iname("test_resource_server")
6008 )));
6009
6010 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
6011 let ident = idms_prox_write
6013 .process_uat_to_identity(&uat, ct, Source::Internal)
6014 .expect("Unable to process uat");
6015 dbg!(&o2rs_uuid);
6016 dbg!(&ident);
6017 let consent_scopes = ident.get_oauth2_consent_scopes(o2rs_uuid);
6018 dbg!(consent_scopes);
6019 assert!(consent_scopes.is_none());
6020
6021 assert!(idms_prox_write.commit().is_ok());
6022 }
6023
6024 #[idm_test]
6044 async fn test_idm_oauth2_1076_pkce_downgrade(
6045 idms: &IdmServer,
6046 _idms_delayed: &mut IdmServerDelayed,
6047 ) {
6048 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6049 let (secret, _uat, ident, _) =
6051 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6052
6053 let idms_prox_read = idms.proxy_read().await.unwrap();
6054
6055 let (code_verifier, _code_challenge) = create_code_verifier!("Whar Garble");
6060
6061 let auth_req = AuthorisationRequest {
6063 response_type: ResponseType::Code,
6064 response_mode: None,
6065 client_id: "test_resource_server".to_string(),
6066 state: Some("123".to_string()),
6067 pkce_request: None,
6068 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6069 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6070 nonce: None,
6071 oidc_ext: Default::default(),
6072 max_age: None,
6073 unknown_keys: Default::default(),
6074 };
6075
6076 let consent_request = idms_prox_read
6077 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6078 .expect("Failed to perform OAuth2 authorisation request.");
6079
6080 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6082 unreachable!();
6083 };
6084
6085 drop(idms_prox_read);
6087 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6088
6089 let permit_success = idms_prox_write
6090 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6091 .expect("Failed to perform OAuth2 permit");
6092
6093 let token_req = AccessTokenRequest {
6097 grant_type: GrantTypeReq::AuthorizationCode {
6098 code: permit_success.code,
6099 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6100 code_verifier,
6102 },
6103 client_id: Some("test_resource_server".to_string()),
6104 client_secret: Some(secret),
6105 };
6106
6107 assert!(matches!(
6109 idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6110 Err(Oauth2Error::InvalidRequest)
6111 ));
6112
6113 assert!(idms_prox_write.commit().is_ok());
6114 }
6115
6116 #[idm_test]
6117 async fn test_idm_oauth2_redir_http_downgrade(
6121 idms: &IdmServer,
6122 _idms_delayed: &mut IdmServerDelayed,
6123 ) {
6124 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6125 let (secret, _uat, ident, _) =
6127 setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6128
6129 let idms_prox_read = idms.proxy_read().await.unwrap();
6130
6131 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
6136
6137 let auth_req = AuthorisationRequest {
6139 response_type: ResponseType::Code,
6140 response_mode: None,
6141 client_id: "test_resource_server".to_string(),
6142 state: Some("123".to_string()),
6143 pkce_request: Some(PkceRequest {
6144 code_challenge: code_challenge.clone(),
6145 code_challenge_method: CodeChallengeMethod::S256,
6146 }),
6147 redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6148 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6149 nonce: None,
6150 oidc_ext: Default::default(),
6151 max_age: None,
6152 unknown_keys: Default::default(),
6153 };
6154
6155 assert!(
6156 idms_prox_read
6157 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6158 .unwrap_err()
6159 == Oauth2Error::InvalidOrigin
6160 );
6161
6162 let consent_request = good_authorisation_request!(
6164 idms_prox_read,
6165 &ident,
6166 ct,
6167 code_challenge,
6168 OAUTH2_SCOPE_OPENID.to_string()
6169 );
6170
6171 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6173 unreachable!();
6174 };
6175
6176 drop(idms_prox_read);
6178 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6179
6180 let permit_success = idms_prox_write
6181 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6182 .expect("Failed to perform OAuth2 permit");
6183
6184 let token_req = AccessTokenRequest {
6187 grant_type: GrantTypeReq::AuthorizationCode {
6188 code: permit_success.code,
6189 redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6190 code_verifier,
6192 },
6193 client_id: Some("test_resource_server".to_string()),
6194 client_secret: Some(secret),
6195 };
6196
6197 assert!(matches!(
6199 idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6200 Err(Oauth2Error::InvalidOrigin)
6201 ));
6202
6203 assert!(idms_prox_write.commit().is_ok());
6204 }
6205
6206 async fn setup_refresh_token(
6207 idms: &IdmServer,
6208 _idms_delayed: &mut IdmServerDelayed,
6209 ct: Duration,
6210 ) -> (AccessTokenResponse, ClientAuthInfo) {
6211 let (secret, _uat, ident, _) =
6213 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6214 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6215
6216 let idms_prox_read = idms.proxy_read().await.unwrap();
6217
6218 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
6220 let consent_request = good_authorisation_request!(
6221 idms_prox_read,
6222 &ident,
6223 ct,
6224 code_challenge,
6225 OAUTH2_SCOPE_OPENID.to_string()
6226 );
6227
6228 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6229 unreachable!();
6230 };
6231
6232 drop(idms_prox_read);
6234 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6235
6236 let permit_success = idms_prox_write
6237 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6238 .expect("Failed to perform OAuth2 permit");
6239
6240 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
6241 code: permit_success.code,
6242 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6243 code_verifier,
6244 }
6245 .into();
6246 let access_token_response_1 = idms_prox_write
6247 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6248 .expect("Unable to exchange for OAuth2 token");
6249
6250 assert!(idms_prox_write.commit().is_ok());
6251
6252 trace!(?access_token_response_1);
6253
6254 (access_token_response_1, client_authz)
6255 }
6256
6257 #[idm_test]
6258 async fn test_idm_oauth2_refresh_token_basic(
6259 idms: &IdmServer,
6260 idms_delayed: &mut IdmServerDelayed,
6261 ) {
6262 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6264
6265 let (access_token_response_1, client_authz) =
6266 setup_refresh_token(idms, idms_delayed, ct).await;
6267
6268 let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
6272 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6273
6274 let refresh_token = access_token_response_1
6275 .refresh_token
6276 .as_ref()
6277 .expect("no refresh token was issued")
6278 .clone();
6279
6280 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6281 refresh_token,
6282 scope: None,
6283 }
6284 .into();
6285
6286 let access_token_response_2 = idms_prox_write
6287 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6288 .expect("Unable to exchange for OAuth2 token");
6289
6290 assert!(idms_prox_write.commit().is_ok());
6291
6292 trace!(?access_token_response_2);
6293
6294 assert!(access_token_response_1.access_token != access_token_response_2.access_token);
6295 assert!(access_token_response_1.refresh_token != access_token_response_2.refresh_token);
6296 assert!(access_token_response_1.id_token != access_token_response_2.id_token);
6297
6298 let ct =
6301 Duration::from_secs(TEST_CURRENT_TIME + 20 + access_token_response_2.expires_in as u64);
6302
6303 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6304
6305 let refresh_token = access_token_response_2
6306 .refresh_token
6307 .as_ref()
6308 .expect("no refresh token was issued")
6309 .clone();
6310
6311 let reflected_token = idms_prox_write
6313 .reflect_oauth2_token(&client_authz, &refresh_token)
6314 .expect("Failed to access internals of the refresh token");
6315
6316 let refresh_exp = match reflected_token {
6317 Oauth2TokenType::Refresh { exp, .. } => exp,
6318 Oauth2TokenType::ClientAccess { .. } => unreachable!(),
6320 };
6321
6322 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6323 refresh_token,
6324 scope: None,
6325 }
6326 .into();
6327
6328 let access_token_response_3 = idms_prox_write
6329 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6330 .expect("Unable to exchange for OAuth2 token");
6331
6332 let entry = idms_prox_write
6335 .qs_write
6336 .internal_search_uuid(UUID_TESTPERSON_1)
6337 .expect("failed");
6338 let session = entry
6339 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
6340 .and_then(|sessions| sessions.first_key_value())
6341 .unwrap();
6343
6344 trace!(?session);
6345 assert_eq!(
6347 SessionState::ExpiresAt(
6348 time::OffsetDateTime::UNIX_EPOCH
6349 + ct
6350 + Duration::from_secs(OAUTH_REFRESH_TOKEN_EXPIRY)
6351 ),
6352 session.1.state
6353 );
6354
6355 assert!(idms_prox_write.commit().is_ok());
6356
6357 trace!(?access_token_response_3);
6358
6359 assert!(access_token_response_3.access_token != access_token_response_2.access_token);
6360 assert!(access_token_response_3.refresh_token != access_token_response_2.refresh_token);
6361 assert!(access_token_response_3.id_token != access_token_response_2.id_token);
6362
6363 let ct = Duration::from_secs(
6369 TEST_CURRENT_TIME + refresh_exp as u64 + access_token_response_3.expires_in as u64,
6370 );
6371
6372 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6373
6374 let refresh_token = access_token_response_3
6375 .refresh_token
6376 .as_ref()
6377 .expect("no refresh token was issued")
6378 .clone();
6379
6380 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6381 refresh_token,
6382 scope: None,
6383 }
6384 .into();
6385 let access_token_response_4 = idms_prox_write
6386 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6387 .unwrap_err();
6388
6389 assert_eq!(access_token_response_4, Oauth2Error::InvalidGrant);
6390
6391 assert!(idms_prox_write.commit().is_ok());
6392 }
6393
6394 #[idm_test]
6396 async fn test_idm_oauth2_refresh_token_oauth2_session_expired(
6397 idms: &IdmServer,
6398 idms_delayed: &mut IdmServerDelayed,
6399 ) {
6400 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6402
6403 let (access_token_response_1, client_authz) =
6404 setup_refresh_token(idms, idms_delayed, ct).await;
6405
6406 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6410 let revoke_request = TokenRevokeRequest {
6411 token: access_token_response_1.access_token.clone(),
6412 token_type_hint: None,
6413 };
6414 assert!(idms_prox_write
6415 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
6416 .is_ok());
6417 assert!(idms_prox_write.commit().is_ok());
6418
6419 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6422
6423 let refresh_token = access_token_response_1
6424 .refresh_token
6425 .as_ref()
6426 .expect("no refresh token was issued")
6427 .clone();
6428
6429 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6430 refresh_token,
6431 scope: None,
6432 }
6433 .into();
6434 let access_token_response_2 = idms_prox_write
6435 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6436 .unwrap_err();
6438
6439 assert_eq!(access_token_response_2, Oauth2Error::InvalidGrant);
6440
6441 assert!(idms_prox_write.commit().is_ok());
6442 }
6443
6444 #[idm_test]
6446 async fn test_idm_oauth2_refresh_token_invalid_client_authz(
6447 idms: &IdmServer,
6448 idms_delayed: &mut IdmServerDelayed,
6449 ) {
6450 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6452
6453 let (access_token_response_1, _client_authz) =
6454 setup_refresh_token(idms, idms_delayed, ct).await;
6455
6456 let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
6457
6458 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6462
6463 let refresh_token = access_token_response_1
6464 .refresh_token
6465 .as_ref()
6466 .expect("no refresh token was issued")
6467 .clone();
6468
6469 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6470 refresh_token,
6471 scope: None,
6472 }
6473 .into();
6474 let access_token_response_2 = idms_prox_write
6475 .check_oauth2_token_exchange(&bad_client_authz, &token_req, ct)
6476 .unwrap_err();
6477
6478 assert_eq!(access_token_response_2, Oauth2Error::AuthenticationRequired);
6479
6480 assert!(idms_prox_write.commit().is_ok());
6481 }
6482
6483 #[idm_test]
6485 async fn test_idm_oauth2_refresh_token_inconsistent_scopes(
6486 idms: &IdmServer,
6487 idms_delayed: &mut IdmServerDelayed,
6488 ) {
6489 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6491
6492 let (access_token_response_1, client_authz) =
6493 setup_refresh_token(idms, idms_delayed, ct).await;
6494
6495 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6499
6500 let refresh_token = access_token_response_1
6501 .refresh_token
6502 .as_ref()
6503 .expect("no refresh token was issued")
6504 .clone();
6505
6506 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6507 refresh_token,
6508 scope: Some(btreeset!["invalid_scope".to_string()]),
6509 }
6510 .into();
6511 let access_token_response_2 = idms_prox_write
6512 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6513 .unwrap_err();
6514
6515 assert_eq!(access_token_response_2, Oauth2Error::InvalidScope);
6516
6517 assert!(idms_prox_write.commit().is_ok());
6518 }
6519
6520 #[idm_test]
6524 async fn test_idm_oauth2_refresh_token_reuse_invalidates_session(
6525 idms: &IdmServer,
6526 idms_delayed: &mut IdmServerDelayed,
6527 ) {
6528 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6530
6531 let (access_token_response_1, client_authz) =
6532 setup_refresh_token(idms, idms_delayed, ct).await;
6533
6534 let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
6537 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6538
6539 let refresh_token = access_token_response_1
6540 .refresh_token
6541 .as_ref()
6542 .expect("no refresh token was issued")
6543 .clone();
6544
6545 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6546 refresh_token,
6547 scope: None,
6548 }
6549 .into();
6550
6551 let _access_token_response_2 = idms_prox_write
6552 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6553 .expect("Unable to exchange for OAuth2 token");
6554
6555 assert!(idms_prox_write.commit().is_ok());
6556
6557 let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
6559 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6560
6561 let refresh_token = access_token_response_1
6562 .refresh_token
6563 .as_ref()
6564 .expect("no refresh token was issued")
6565 .clone();
6566
6567 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6568 refresh_token,
6569 scope: None,
6570 }
6571 .into();
6572
6573 let access_token_response_3 = idms_prox_write
6574 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6575 .unwrap_err();
6576
6577 assert_eq!(access_token_response_3, Oauth2Error::InvalidGrant);
6578
6579 let entry = idms_prox_write
6580 .qs_write
6581 .internal_search_uuid(UUID_TESTPERSON_1)
6582 .expect("failed");
6583 let valid = entry
6584 .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
6585 .and_then(|sessions| sessions.first_key_value())
6586 .map(|(_, session)| !matches!(session.state, SessionState::RevokedAt(_)))
6587 .unwrap();
6589 assert!(!valid);
6591
6592 assert!(idms_prox_write.commit().is_ok());
6593 }
6594
6595 #[idm_test]
6602 async fn test_idm_oauth2_refresh_token_divergence(
6603 idms: &IdmServer,
6604 idms_delayed: &mut IdmServerDelayed,
6605 ) {
6606 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6608
6609 let (access_token_response_1, client_authz) =
6610 setup_refresh_token(idms, idms_delayed, ct).await;
6611
6612 let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
6615 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6616
6617 let refresh_token = access_token_response_1
6618 .refresh_token
6619 .as_ref()
6620 .expect("no refresh token was issued")
6621 .clone();
6622
6623 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6624 refresh_token,
6625 scope: None,
6626 }
6627 .into();
6628
6629 let access_token_response_2 = idms_prox_write
6630 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6631 .expect("Unable to exchange for OAuth2 token");
6632
6633 drop(idms_prox_write);
6636
6637 let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
6639 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6640
6641 let refresh_token = access_token_response_2
6642 .refresh_token
6643 .as_ref()
6644 .expect("no refresh token was issued")
6645 .clone();
6646
6647 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6648 refresh_token,
6649 scope: None,
6650 }
6651 .into();
6652
6653 let _access_token_response_3 = idms_prox_write
6654 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6655 .expect("Unable to exchange for OAuth2 token");
6656
6657 assert!(idms_prox_write.commit().is_ok());
6658
6659 }
6661
6662 #[idm_test]
6663 async fn test_idm_oauth2_refresh_token_scope_constraints(
6664 idms: &IdmServer,
6665 idms_delayed: &mut IdmServerDelayed,
6666 ) {
6667 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6669
6670 let (access_token_response_1, client_authz) =
6671 setup_refresh_token(idms, idms_delayed, ct).await;
6672
6673 let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
6682 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6683
6684 let refresh_token = access_token_response_1
6685 .refresh_token
6686 .as_ref()
6687 .expect("no refresh token was issued")
6688 .clone();
6689
6690 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
6692
6693 let access_token_unverified = JwsCompact::from_str(&access_token_response_1.access_token)
6694 .expect("Invalid Access Token");
6695
6696 let reflected_token = jws_verifier
6697 .verify(&access_token_unverified)
6698 .unwrap()
6699 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
6700 .expect("Failed to access internals of the refresh token");
6701
6702 trace!(?reflected_token);
6703 let initial_scopes = reflected_token.extensions.scope;
6704 trace!(?initial_scopes);
6705
6706 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6708 refresh_token,
6709 scope: None,
6710 }
6711 .into();
6712
6713 let access_token_response_2 = idms_prox_write
6714 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6715 .expect("Unable to exchange for OAuth2 token");
6716
6717 let access_token_unverified = JwsCompact::from_str(&access_token_response_2.access_token)
6718 .expect("Invalid Access Token");
6719
6720 let reflected_token = jws_verifier
6721 .verify(&access_token_unverified)
6722 .unwrap()
6723 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
6724 .expect("Failed to access internals of the refresh token");
6725
6726 assert_eq!(initial_scopes, reflected_token.extensions.scope);
6727
6728 let refresh_token = access_token_response_2
6729 .refresh_token
6730 .as_ref()
6731 .expect("no refresh token was issued")
6732 .clone();
6733
6734 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6736 refresh_token,
6737 scope: Some(["openid".to_string()].into()),
6738 }
6739 .into();
6740
6741 let access_token_response_3 = idms_prox_write
6742 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6743 .expect("Unable to exchange for OAuth2 token");
6744
6745 let access_token_unverified = JwsCompact::from_str(&access_token_response_3.access_token)
6746 .expect("Invalid Access Token");
6747
6748 let reflected_token = jws_verifier
6749 .verify(&access_token_unverified)
6750 .unwrap()
6751 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
6752 .expect("Failed to access internals of the refresh token");
6753
6754 assert_ne!(initial_scopes, reflected_token.extensions.scope);
6755
6756 let constrained_scopes = reflected_token.extensions.scope;
6758
6759 let refresh_token = access_token_response_3
6760 .refresh_token
6761 .as_ref()
6762 .expect("no refresh token was issued")
6763 .clone();
6764
6765 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6767 refresh_token,
6768 scope: None,
6769 }
6770 .into();
6771
6772 let access_token_response_4 = idms_prox_write
6773 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6774 .expect("Unable to exchange for OAuth2 token");
6775
6776 let access_token_unverified = JwsCompact::from_str(&access_token_response_4.access_token)
6777 .expect("Invalid Access Token");
6778
6779 let reflected_token = jws_verifier
6780 .verify(&access_token_unverified)
6781 .unwrap()
6782 .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
6783 .expect("Failed to access internals of the refresh token");
6784
6785 assert_ne!(initial_scopes, reflected_token.extensions.scope);
6786 assert_eq!(constrained_scopes, reflected_token.extensions.scope);
6787
6788 let refresh_token = access_token_response_4
6789 .refresh_token
6790 .as_ref()
6791 .expect("no refresh token was issued")
6792 .clone();
6793
6794 let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6796 refresh_token,
6797 scope: Some(initial_scopes),
6798 }
6799 .into();
6800
6801 let access_token_response_5_err = idms_prox_write
6802 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6803 .unwrap_err();
6804
6805 assert_eq!(access_token_response_5_err, Oauth2Error::InvalidScope);
6806
6807 assert!(idms_prox_write.commit().is_ok());
6808 }
6809
6810 #[test]
6811 fn compliant_serialization_test() {
6814 let token_req: Result<AccessTokenRequest, serde_json::Error> = serde_json::from_str(
6815 r#"
6816 {
6817 "grant_type": "refresh_token",
6818 "refresh_token": "some_dumb_refresh_token",
6819 "scope": "invalid_scope vasd asd"
6820 }
6821 "#,
6822 );
6823 assert!(token_req.is_ok());
6824 }
6825
6826 #[idm_test]
6827 async fn test_idm_oauth2_custom_claims(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
6828 let ct = Duration::from_secs(TEST_CURRENT_TIME);
6829 let (secret, _uat, ident, oauth2_rs_uuid) =
6830 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6831
6832 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6834
6835 let modlist = ModifyList::new_list(vec![
6836 Modify::Present(
6838 Attribute::OAuth2RsClaimMap,
6839 Value::OauthClaimMap(
6840 "custom_a".to_string(),
6841 OauthClaimMapJoin::CommaSeparatedValue,
6842 ),
6843 ),
6844 Modify::Present(
6845 Attribute::OAuth2RsClaimMap,
6846 Value::OauthClaimValue(
6847 "custom_a".to_string(),
6848 UUID_TESTGROUP,
6849 btreeset!["value_a".to_string()],
6850 ),
6851 ),
6852 Modify::Present(
6854 Attribute::OAuth2RsClaimMap,
6855 Value::OauthClaimValue(
6856 "custom_a".to_string(),
6857 UUID_IDM_ALL_ACCOUNTS,
6858 btreeset!["value_b".to_string()],
6859 ),
6860 ),
6861 Modify::Present(
6863 Attribute::OAuth2RsClaimMap,
6864 Value::OauthClaimMap(
6865 "custom_b".to_string(),
6866 OauthClaimMapJoin::SpaceSeparatedValue,
6867 ),
6868 ),
6869 Modify::Present(
6870 Attribute::OAuth2RsClaimMap,
6871 Value::OauthClaimValue(
6872 "custom_b".to_string(),
6873 UUID_TESTGROUP,
6874 btreeset!["value_a".to_string()],
6875 ),
6876 ),
6877 Modify::Present(
6878 Attribute::OAuth2RsClaimMap,
6879 Value::OauthClaimValue(
6880 "custom_b".to_string(),
6881 UUID_IDM_ALL_ACCOUNTS,
6882 btreeset!["value_b".to_string()],
6883 ),
6884 ),
6885 Modify::Present(
6887 Attribute::OAuth2RsClaimMap,
6888 Value::OauthClaimValue(
6889 "custom_b".to_string(),
6890 UUID_IDM_ADMINS,
6891 btreeset!["value_c".to_string()],
6892 ),
6893 ),
6894 ]);
6895
6896 assert!(idms_prox_write
6897 .qs_write
6898 .internal_modify(
6899 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
6900 &modlist,
6901 )
6902 .is_ok());
6903
6904 assert!(idms_prox_write.commit().is_ok());
6905
6906 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6908
6909 let idms_prox_read = idms.proxy_read().await.unwrap();
6910
6911 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
6912
6913 let consent_request = good_authorisation_request!(
6914 idms_prox_read,
6915 &ident,
6916 ct,
6917 code_challenge,
6918 OAUTH2_SCOPE_OPENID.to_string()
6919 );
6920
6921 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6922 unreachable!();
6923 };
6924
6925 drop(idms_prox_read);
6927 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6928
6929 let permit_success = idms_prox_write
6930 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6931 .expect("Failed to perform OAuth2 permit");
6932
6933 let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
6935 code: permit_success.code,
6936 redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6937 code_verifier,
6939 }
6940 .into();
6941
6942 let token_response = idms_prox_write
6943 .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6944 .expect("Failed to perform OAuth2 token exchange");
6945
6946 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
6948
6949 let id_token = token_response.id_token.expect("No id_token in response!");
6950 let access_token =
6951 JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
6952
6953 assert!(idms_prox_write.commit().is_ok());
6955
6956 let mut idms_prox_read = idms.proxy_read().await.unwrap();
6957
6958 let mut jwkset = idms_prox_read
6959 .oauth2_openid_publickey("test_resource_server")
6960 .expect("Failed to get public key");
6961
6962 let public_jwk = jwkset.keys.pop().expect("no such jwk");
6963
6964 let jws_validator =
6965 JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
6966
6967 let oidc_unverified =
6968 OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
6969
6970 let iat = ct.as_secs() as i64;
6971
6972 let oidc = jws_validator
6973 .verify(&oidc_unverified)
6974 .unwrap()
6975 .verify_exp(iat)
6976 .expect("Failed to verify oidc");
6977
6978 assert!(
6980 oidc.iss
6981 == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
6982 .unwrap()
6983 );
6984 assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
6985 assert_eq!(oidc.aud, "test_resource_server");
6986 assert_eq!(oidc.iat, iat);
6987 assert_eq!(oidc.nbf, Some(iat));
6988 assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
6990 assert!(oidc.auth_time.is_none());
6991 assert_eq!(oidc.nonce, Some("abcdef".to_string()));
6993 assert!(oidc.at_hash.is_none());
6994 assert!(oidc.acr.is_none());
6995 assert!(oidc.amr.is_none());
6996 assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
6997 assert!(oidc.jti.is_none());
6998 assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
6999 assert_eq!(
7000 oidc.s_claims.preferred_username,
7001 Some("testperson1@example.com".to_string())
7002 );
7003 assert!(
7004 oidc.s_claims.scopes == vec![OAUTH2_SCOPE_OPENID.to_string(), "supplement".to_string()]
7005 );
7006
7007 assert_eq!(
7008 oidc.claims.get("custom_a").and_then(|v| v.as_str()),
7009 Some("value_a,value_b")
7010 );
7011 assert_eq!(
7012 oidc.claims.get("custom_b").and_then(|v| v.as_str()),
7013 Some("value_a value_b")
7014 );
7015
7016 let userinfo = idms_prox_read
7019 .oauth2_openid_userinfo("test_resource_server", access_token, ct)
7020 .expect("failed to get userinfo");
7021
7022 assert_eq!(oidc.iss, userinfo.iss);
7023 assert_eq!(oidc.sub, userinfo.sub);
7024 assert_eq!(oidc.aud, userinfo.aud);
7025 assert_eq!(oidc.iat, userinfo.iat);
7026 assert_eq!(oidc.nbf, userinfo.nbf);
7027 assert_eq!(oidc.exp, userinfo.exp);
7028 assert!(userinfo.auth_time.is_none());
7029 assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
7030 assert!(userinfo.at_hash.is_none());
7031 assert!(userinfo.acr.is_none());
7032 assert_eq!(oidc.amr, userinfo.amr);
7033 assert_eq!(oidc.azp, userinfo.azp);
7034 assert!(userinfo.jti.is_none());
7035 assert_eq!(oidc.s_claims, userinfo.s_claims);
7036 assert_eq!(oidc.claims, userinfo.claims);
7037
7038 let intr_request = AccessTokenIntrospectRequest {
7040 token: token_response.access_token.clone(),
7041 token_type_hint: None,
7042 };
7043 let intr_response = idms_prox_read
7044 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7045 .expect("Failed to inspect token");
7046
7047 eprintln!("👉 {intr_response:?}");
7048 assert!(intr_response.active);
7049 assert_eq!(
7050 intr_response.scope,
7051 btreeset!["openid".to_string(), "supplement".to_string()]
7052 );
7053 assert_eq!(
7054 intr_response.client_id.as_deref(),
7055 Some("test_resource_server")
7056 );
7057 assert_eq!(
7058 intr_response.username.as_deref(),
7059 Some("testperson1@example.com")
7060 );
7061 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7062 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7063 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7064 drop(idms_prox_read);
7067 }
7068
7069 #[idm_test]
7070 async fn test_idm_oauth2_public_allow_localhost_redirect(
7071 idms: &IdmServer,
7072 _idms_delayed: &mut IdmServerDelayed,
7073 ) {
7074 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7075 let (_uat, ident, oauth2_rs_uuid) = setup_oauth2_resource_server_public(idms, ct).await;
7076
7077 let mut idms_prox_write: crate::idm::server::IdmServerProxyWriteTransaction<'_> =
7078 idms.proxy_write(ct).await.unwrap();
7079
7080 let redirect_uri = Url::parse("http://localhost:8765/oauth2/result")
7081 .expect("Failed to parse redirect URL");
7082
7083 let modlist = ModifyList::new_list(vec![
7084 Modify::Present(Attribute::OAuth2AllowLocalhostRedirect, Value::Bool(true)),
7085 Modify::Present(Attribute::OAuth2RsOrigin, Value::Url(redirect_uri.clone())),
7086 ]);
7087
7088 assert!(idms_prox_write
7089 .qs_write
7090 .internal_modify(
7091 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7092 &modlist,
7093 )
7094 .is_ok());
7095
7096 assert!(idms_prox_write.commit().is_ok());
7097
7098 let idms_prox_read = idms.proxy_read().await.unwrap();
7099
7100 let (code_verifier, code_challenge) = create_code_verifier!("Whar Garble");
7102
7103 let auth_req = AuthorisationRequest {
7104 response_type: ResponseType::Code,
7105 response_mode: None,
7106 client_id: "test_resource_server".to_string(),
7107 state: Some("123".to_string()),
7108 pkce_request: Some(PkceRequest {
7109 code_challenge,
7110 code_challenge_method: CodeChallengeMethod::S256,
7111 }),
7112 redirect_uri: Url::parse("http://localhost:8765/oauth2/result").unwrap(),
7113 scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
7114 nonce: Some("abcdef".to_string()),
7115 oidc_ext: Default::default(),
7116 max_age: None,
7117 unknown_keys: Default::default(),
7118 };
7119
7120 let consent_request = idms_prox_read
7121 .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
7122 .expect("OAuth2 authorisation failed");
7123
7124 let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7126 unreachable!();
7127 };
7128
7129 drop(idms_prox_read);
7131 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7132
7133 let permit_success = idms_prox_write
7134 .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7135 .expect("Failed to perform OAuth2 permit");
7136
7137 assert_eq!(permit_success.state.as_deref(), Some("123"));
7139
7140 let token_req = AccessTokenRequest {
7142 grant_type: GrantTypeReq::AuthorizationCode {
7143 code: permit_success.code,
7144 redirect_uri,
7145 code_verifier,
7147 },
7148 client_id: Some("test_resource_server".to_string()),
7149 client_secret: None,
7150 };
7151
7152 let token_response = idms_prox_write
7153 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7154 .expect("Failed to perform OAuth2 token exchange");
7155
7156 assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7158
7159 assert!(idms_prox_write.commit().is_ok());
7160 }
7161
7162 #[idm_test]
7163 async fn test_idm_oauth2_basic_client_credentials_grant_valid(
7164 idms: &IdmServer,
7165 _idms_delayed: &mut IdmServerDelayed,
7166 ) {
7167 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7168 let (secret, _uat, _ident, _) =
7169 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7170 let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7171
7172 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7174
7175 let token_req = AccessTokenRequest {
7176 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7177 client_id: Some("test_resource_server".to_string()),
7178 client_secret: Some(secret),
7179 };
7180
7181 let oauth2_token = idms_prox_write
7182 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7183 .expect("Failed to perform OAuth2 token exchange");
7184
7185 assert!(idms_prox_write.commit().is_ok());
7186
7187 assert_eq!(oauth2_token.token_type, AccessTokenType::Bearer);
7189
7190 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7192
7193 let intr_request = AccessTokenIntrospectRequest {
7194 token: oauth2_token.access_token.clone(),
7195 token_type_hint: None,
7196 };
7197 let intr_response = idms_prox_read
7198 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7199 .expect("Failed to inspect token");
7200
7201 eprintln!("👉 {intr_response:?}");
7202 assert!(intr_response.active);
7203 assert_eq!(intr_response.scope, btreeset!["supplement".to_string()]);
7204 assert_eq!(
7205 intr_response.client_id.as_deref(),
7206 Some("test_resource_server")
7207 );
7208 assert_eq!(
7209 intr_response.username.as_deref(),
7210 Some("test_resource_server@example.com")
7211 );
7212 assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7213 assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7214 assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7215
7216 drop(idms_prox_read);
7217
7218 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7220 let revoke_request = TokenRevokeRequest {
7221 token: oauth2_token.access_token.clone(),
7222 token_type_hint: None,
7223 };
7224 assert!(idms_prox_write
7225 .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
7226 .is_ok());
7227 assert!(idms_prox_write.commit().is_ok());
7228
7229 let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
7231 let mut idms_prox_read = idms.proxy_read().await.unwrap();
7232
7233 let intr_request = AccessTokenIntrospectRequest {
7234 token: oauth2_token.access_token.clone(),
7235 token_type_hint: None,
7236 };
7237
7238 let intr_response = idms_prox_read
7239 .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7240 .expect("Failed to inspect token");
7241 assert!(!intr_response.active);
7242
7243 drop(idms_prox_read);
7244 }
7245
7246 #[idm_test]
7247 async fn test_idm_oauth2_basic_client_credentials_grant_invalid(
7248 idms: &IdmServer,
7249 _idms_delayed: &mut IdmServerDelayed,
7250 ) {
7251 let ct = Duration::from_secs(TEST_CURRENT_TIME);
7252 let (secret, _uat, _ident, _) =
7253 setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7254
7255 let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7256
7257 let token_req = AccessTokenRequest {
7259 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7260 client_id: Some("test_resource_server".to_string()),
7261 client_secret: None,
7262 };
7263
7264 assert_eq!(
7265 idms_prox_write
7266 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7267 .unwrap_err(),
7268 Oauth2Error::AuthenticationRequired
7269 );
7270
7271 let token_req = AccessTokenRequest {
7273 grant_type: GrantTypeReq::ClientCredentials { scope: None },
7274 client_id: Some("test_resource_server".to_string()),
7275 client_secret: Some("wrong password".to_string()),
7276 };
7277
7278 assert_eq!(
7279 idms_prox_write
7280 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7281 .unwrap_err(),
7282 Oauth2Error::AuthenticationRequired
7283 );
7284
7285 let scope = Some(btreeset!["💅".to_string()]);
7287 let token_req = AccessTokenRequest {
7288 grant_type: GrantTypeReq::ClientCredentials { scope },
7289 client_id: Some("test_resource_server".to_string()),
7290 client_secret: Some(secret.clone()),
7291 };
7292
7293 assert_eq!(
7294 idms_prox_write
7295 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7296 .unwrap_err(),
7297 Oauth2Error::InvalidScope
7298 );
7299
7300 let scope = Some(btreeset!["invalid_scope".to_string()]);
7302 let token_req = AccessTokenRequest {
7303 grant_type: GrantTypeReq::ClientCredentials { scope },
7304 client_id: Some("test_resource_server".to_string()),
7305 client_secret: Some(secret.clone()),
7306 };
7307
7308 assert_eq!(
7309 idms_prox_write
7310 .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7311 .unwrap_err(),
7312 Oauth2Error::AccessDenied
7313 );
7314
7315 assert!(idms_prox_write.commit().is_ok());
7316 }
7317
7318 #[test]
7319 fn test_get_code() {
7320 use super::{gen_device_code, gen_user_code, parse_user_code};
7321
7322 assert!(gen_device_code().is_ok());
7323
7324 let (res_string, res_value) = gen_user_code();
7325
7326 assert!(res_string.split('-').count() == 3);
7327
7328 let res_string_clean = res_string.replace("-", "");
7329 let res_string_as_num = res_string_clean
7330 .parse::<u32>()
7331 .expect("Failed to parse as number");
7332 assert_eq!(res_string_as_num, res_value);
7333
7334 assert_eq!(
7335 parse_user_code(&res_string).expect("Failed to parse code"),
7336 res_value
7337 );
7338 }
7339
7340 #[idm_test]
7341 async fn handle_oauth2_start_device_flow(
7342 idms: &IdmServer,
7343 _idms_delayed: &mut IdmServerDelayed,
7344 ) {
7345 let ct = duration_from_epoch_now();
7346
7347 let client_auth_info = ClientAuthInfo::from(Source::Https(
7348 "127.0.0.1"
7349 .parse()
7350 .expect("Failed to parse 127.0.0.1 as an IP!"),
7351 ));
7352 let eventid = Uuid::new_v4();
7353
7354 let res = idms
7355 .proxy_write(ct)
7356 .await
7357 .expect("Failed to get idmspwt")
7358 .handle_oauth2_start_device_flow(client_auth_info, "test_rs_id", &None, eventid);
7359 dbg!(&res);
7360 assert!(res.is_err());
7361 }
7362
7363 #[test]
7364 fn test_url_localhost_domain() {
7365 let example_is_not_local = "https://example.com/sdfsdf";
7369 println!("Ensuring that {} is not local", example_is_not_local);
7370 assert!(!host_is_local(
7371 &Url::parse(example_is_not_local)
7372 .expect("Failed to parse example.com as a host?")
7373 .host()
7374 .unwrap_or_else(|| panic!("Couldn't get a host from {}", example_is_not_local))
7375 ));
7376
7377 let test_urls = [
7378 ("http://localhost:8080/oauth2/callback", "/oauth2/callback"),
7379 ("https://localhost/foo/bar", "/foo/bar"),
7380 ("http://127.0.0.1:12345/foo", "/foo"),
7381 ("http://[::1]:12345/foo", "/foo"),
7382 ];
7383
7384 for (url, path) in test_urls.into_iter() {
7385 println!("Testing URL: {}", url);
7386 let url = Url::parse(url).expect("One of the test values failed!");
7387 assert!(host_is_local(
7388 &url.host().expect("Didn't parse a host out?")
7389 ));
7390
7391 assert_eq!(url.path(), path);
7392 }
7393 }
7394
7395 #[test]
7396 fn test_oauth2_rs_type_allow_localhost_redirect() {
7397 let test_cases = [
7398 (
7399 OauthRSType::Public {
7400 allow_localhost_redirect: true,
7401 },
7402 true,
7403 ),
7404 (
7405 OauthRSType::Public {
7406 allow_localhost_redirect: false,
7407 },
7408 false,
7409 ),
7410 (
7411 OauthRSType::Basic {
7412 authz_secret: "supersecret".to_string(),
7413 enable_pkce: false,
7414 },
7415 false,
7416 ),
7417 ];
7418
7419 assert!(test_cases.iter().all(|(rs_type, expected)| {
7420 let actual = rs_type.allow_localhost_redirect();
7421 println!("Testing {:?} -> {}", rs_type, expected);
7422 actual == *expected
7423 }));
7424 }
7425}