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