Skip to main content

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