Skip to main content

kanidmd_lib/idm/
oauth2.rs

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