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