kanidmd_lib/idm/
oauth2.rs

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