kanidmd_lib/idm/
oauth2.rs

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