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    Ok((client_id, Some(secret)).into())
3119}
3120
3121fn s_claims_for_account(
3122    o2rs: &Oauth2RS,
3123    account: &Account,
3124    scopes: &BTreeSet<String>,
3125) -> OidcClaims {
3126    let preferred_username = if o2rs.prefer_short_username {
3127        Some(account.name().into())
3128    } else {
3129        Some(account.spn().into())
3130    };
3131
3132    let (email, email_verified) = if scopes.contains(OAUTH2_SCOPE_EMAIL) {
3133        if let Some(mp) = &account.mail_primary {
3134            (Some(mp.clone()), Some(true))
3135        } else {
3136            (None, None)
3137        }
3138    } else {
3139        (None, None)
3140    };
3141
3142    let updated_at: Option<OffsetDateTime> = if scopes.contains(OAUTH2_SCOPE_PROFILE) {
3143        account
3144            .updated_at
3145            .as_ref()
3146            .map(OffsetDateTime::from)
3147            .and_then(|odt| odt.replace_nanosecond(0).ok())
3148    } else {
3149        None
3150    };
3151    OidcClaims {
3152        // Map from displayname
3153        name: Some(account.displayname.clone()),
3154        scopes: scopes.iter().cloned().collect(),
3155        preferred_username,
3156        email,
3157        email_verified,
3158        updated_at,
3159        ..Default::default()
3160    }
3161}
3162
3163fn extra_claims_for_account(
3164    account: &Account,
3165
3166    claim_map: &BTreeMap<Uuid, Vec<(String, ClaimValue)>>,
3167
3168    scopes: &BTreeSet<String>,
3169) -> BTreeMap<String, serde_json::Value> {
3170    let mut extra_claims = BTreeMap::new();
3171
3172    let mut account_claims: BTreeMap<&str, ClaimValue> = BTreeMap::new();
3173
3174    // for each group
3175    for group_uuid in account.groups.iter().map(|g| g.uuid()) {
3176        // Does this group have any custom claims?
3177        if let Some(claim) = claim_map.get(group_uuid) {
3178            // If so, iterate over the set of claims and values.
3179            for (claim_name, claim_value) in claim.iter() {
3180                // Does this claim name already exist in our in-progress map?
3181                match account_claims.entry(claim_name.as_str()) {
3182                    BTreeEntry::Vacant(e) => {
3183                        e.insert(claim_value.clone());
3184                    }
3185                    BTreeEntry::Occupied(mut e) => {
3186                        let mut_claim_value = e.get_mut();
3187                        // Merge the extra details into this.
3188                        mut_claim_value.merge(claim_value);
3189                    }
3190                }
3191            }
3192        }
3193    }
3194
3195    // Now, flatten all these into the final structure.
3196    for (claim_name, claim_value) in account_claims {
3197        extra_claims.insert(claim_name.to_string(), claim_value.to_json_value());
3198    }
3199
3200    // Now perform our custom claim's from scopes. We do these second so that
3201    // a user can't stomp our claim names.
3202
3203    if scopes.contains(OAUTH2_SCOPE_SSH_PUBLICKEYS) {
3204        extra_claims.insert(
3205            OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string(),
3206            account
3207                .sshkeys()
3208                .values()
3209                .map(|pub_key| serde_json::Value::String(pub_key.to_string()))
3210                .collect(),
3211        );
3212    }
3213
3214    let wants_groups = scopes.contains(OAUTH2_SCOPE_GROUPS);
3215    // groups implies uuid + spn to match current behaviour.
3216    let wants_groups_uuid = wants_groups || scopes.contains(OAUTH2_SCOPE_GROUPS_UUID);
3217    let wants_groups_spn = wants_groups || scopes.contains(OAUTH2_SCOPE_GROUPS_SPN);
3218    let wants_groups_name = scopes.contains(OAUTH2_SCOPE_GROUPS_NAME);
3219
3220    if wants_groups_uuid || wants_groups_name || wants_groups_spn {
3221        extra_claims.insert(
3222            OAUTH2_SCOPE_GROUPS.to_string(),
3223            account
3224                .groups
3225                .iter()
3226                .flat_map(|group| {
3227                    let mut attrs = Vec::with_capacity(3);
3228
3229                    if wants_groups_uuid {
3230                        attrs.push(group.uuid().as_hyphenated().to_string())
3231                    }
3232
3233                    if wants_groups_spn {
3234                        attrs.push(group.spn().clone())
3235                    }
3236
3237                    if wants_groups_name {
3238                        if let Some(name) = group.name() {
3239                            attrs.push(name.into())
3240                        }
3241                    }
3242
3243                    attrs
3244                })
3245                .collect(),
3246        );
3247    }
3248
3249    trace!(?extra_claims);
3250
3251    extra_claims
3252}
3253
3254fn process_requested_scopes_for_identity(
3255    o2rs: &Oauth2RS,
3256    ident: &Identity,
3257    req_scopes: Option<&BTreeSet<String>>,
3258) -> Result<(BTreeSet<String>, BTreeSet<String>), Oauth2Error> {
3259    let req_scopes = req_scopes.cloned().unwrap_or_default();
3260
3261    if req_scopes.is_empty() {
3262        admin_error!("Invalid OAuth2 request - must contain at least one requested scope");
3263        return Err(Oauth2Error::InvalidRequest);
3264    }
3265
3266    validate_scopes(&req_scopes)?;
3267
3268    let available_scopes: BTreeSet<String> = o2rs
3269        .scope_maps
3270        .iter()
3271        .filter_map(|(u, m)| ident.is_memberof(*u).then_some(m.iter()))
3272        .flatten()
3273        .cloned()
3274        .collect();
3275
3276    if !req_scopes.is_subset(&available_scopes) {
3277        admin_warn!(
3278            %ident,
3279            requested_scopes = ?req_scopes,
3280            available_scopes = ?available_scopes,
3281            "Identity does not have access to the requested scopes"
3282        );
3283        return Err(Oauth2Error::AccessDenied);
3284    }
3285
3286    let granted_scopes: BTreeSet<String> = o2rs
3287        .sup_scope_maps
3288        .iter()
3289        .filter_map(|(u, m)| ident.is_memberof(*u).then_some(m.iter()))
3290        .flatten()
3291        .cloned()
3292        .chain(req_scopes.iter().cloned())
3293        .collect();
3294
3295    Ok((req_scopes, granted_scopes))
3296}
3297
3298fn validate_scopes(req_scopes: &BTreeSet<String>) -> Result<(), Oauth2Error> {
3299    let failed_scopes = req_scopes
3300        .iter()
3301        .filter(|&s| !OAUTHSCOPE_RE.is_match(s))
3302        .cloned()
3303        .collect::<Vec<String>>();
3304
3305    if !failed_scopes.is_empty() {
3306        let requested_scopes_string = req_scopes
3307            .iter()
3308            .cloned()
3309            .collect::<Vec<String>>()
3310            .join(",");
3311        admin_error!(
3312                "Invalid OAuth2 request - requested scopes ({}) but ({}) failed to pass validation rules - all must match the regex {}",
3313                    requested_scopes_string,
3314                    failed_scopes.join(","),
3315                    OAUTHSCOPE_RE.as_str()
3316            );
3317        return Err(Oauth2Error::InvalidScope);
3318    }
3319    Ok(())
3320}
3321
3322/// device code is a random bucket of bytes used in the device flow
3323#[inline]
3324#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3325#[allow(dead_code)]
3326fn gen_device_code() -> Result<[u8; 16], Oauth2Error> {
3327    use rand::TryRng;
3328
3329    let mut rng = rand::rng();
3330    let mut result = [0u8; 16];
3331    // doing it here because of feature-shenanigans.
3332    if let Err(err) = rng.try_fill_bytes(&mut result) {
3333        error!("Failed to generate device code! {:?}", err);
3334        return Err(Oauth2Error::ServerError(OperationError::Backend));
3335    }
3336    Ok(result)
3337}
3338
3339#[inline]
3340#[cfg(any(feature = "dev-oauth2-device-flow", test))]
3341#[allow(dead_code)]
3342/// Returns (xxx-yyy-zzz, digits) where one's the human-facing code, the other is what we store in the DB.
3343fn gen_user_code() -> (String, u32) {
3344    use rand::RngExt;
3345    let mut rng = rand::rng();
3346    let num: u32 = rng.random_range(0..=999999999);
3347    let result = format!("{num:09}");
3348    (
3349        format!("{}-{}-{}", &result[0..3], &result[3..6], &result[6..9]),
3350        num,
3351    )
3352}
3353
3354/// Take the supplied user code and check it's a valid u32
3355#[allow(dead_code)]
3356fn parse_user_code(val: &str) -> Result<u32, Oauth2Error> {
3357    let mut val = val.to_string();
3358    val.retain(|c| c.is_ascii_digit());
3359    val.parse().map_err(|err| {
3360        debug!("Failed to parse value={} as u32: {:?}", val, err);
3361        Oauth2Error::InvalidRequest
3362    })
3363}
3364
3365/// Check if a host is local (loopback or localhost)
3366fn host_is_local(host: &Host<&str>) -> bool {
3367    match host {
3368        Host::Ipv4(ip) => ip.is_loopback(),
3369        Host::Ipv6(ip) => ip.is_loopback(),
3370        Host::Domain(domain) => *domain == "localhost",
3371    }
3372}
3373
3374/// Ensure that the redirect URI is a loopback/localhost address
3375fn check_is_loopback(redirect_uri: &Url) -> bool {
3376    redirect_uri.host().is_some_and(|host| {
3377        // Check if the host is a loopback/localhost address.
3378        host_is_local(&host)
3379    })
3380}
3381
3382#[cfg(test)]
3383mod tests {
3384    use super::{Oauth2TokenType, PkceS256Secret, TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS};
3385    use crate::credential::Credential;
3386    use crate::idm::accountpolicy::ResolvedAccountPolicy;
3387    use crate::idm::oauth2::{host_is_local, AuthoriseResponse, Oauth2Error, OauthRSType};
3388    use crate::idm::server::{IdmServer, IdmServerTransaction};
3389    use crate::idm::serviceaccount::GenerateApiTokenEvent;
3390    use crate::prelude::*;
3391    use crate::value::{AuthType, OauthClaimMapJoin, SessionState};
3392    use crate::valueset::{ValueSetOauthScopeMap, ValueSetSshKey};
3393    use base64::{engine::general_purpose, Engine as _};
3394    use compact_jwt::{
3395        compact::JwkUse, crypto::JwsRs256Verifier, dangernoverify::JwsDangerReleaseWithoutVerify,
3396        JwaAlg, Jwk, JwsCompact, JwsEs256Verifier, JwsVerifier, OidcSubject, OidcToken,
3397        OidcUnverified,
3398    };
3399    use kanidm_lib_crypto::CryptoPolicy;
3400    use kanidm_proto::constants::*;
3401    use kanidm_proto::internal::{SshPublicKey, UserAuthToken};
3402    use kanidm_proto::oauth2::*;
3403    use std::collections::{BTreeMap, BTreeSet};
3404    use std::convert::TryFrom;
3405    use std::str::FromStr;
3406    use std::time::Duration;
3407    use uri::{OAUTH2_TOKEN_INTROSPECT_ENDPOINT, OAUTH2_TOKEN_REVOKE_ENDPOINT};
3408
3409    const TEST_CURRENT_TIME: u64 = 6000;
3410    const UAT_EXPIRE: u64 = 5;
3411    const TOKEN_EXPIRE: u64 = 900;
3412
3413    const UUID_TESTGROUP: Uuid = uuid!("a3028223-bf20-47d5-8b65-967b5d2bb3eb");
3414
3415    macro_rules! good_authorisation_request {
3416        (
3417            $idms_prox_read:expr,
3418            $ident:expr,
3419            $ct:expr,
3420            $pkce_request:expr,
3421            $scope:expr
3422        ) => {{
3423            #[allow(clippy::unnecessary_to_owned)]
3424            let scope: BTreeSet<String> = $scope.split(" ").map(|s| s.to_string()).collect();
3425
3426            let auth_req = AuthorisationRequest {
3427                response_type: ResponseType::Code,
3428                response_mode: None,
3429                client_id: "test_resource_server".to_string(),
3430                state: Some("123".to_string()),
3431                pkce_request: Some($pkce_request),
3432                redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3433                scope,
3434                nonce: Some("abcdef".to_string()),
3435                oidc_ext: Default::default(),
3436                max_age: None,
3437                unknown_keys: Default::default(),
3438            };
3439
3440            $idms_prox_read
3441                .check_oauth2_authorisation(Some($ident), &auth_req, $ct)
3442                .expect("OAuth2 authorisation failed")
3443        }};
3444    }
3445
3446    // setup an OAuth2 instance.
3447    async fn setup_oauth2_resource_server_basic(
3448        idms: &IdmServer,
3449        ct: Duration,
3450        enable_pkce: bool,
3451        enable_legacy_crypto: bool,
3452        prefer_short_username: bool,
3453    ) -> (String, UserAuthToken, Identity, Uuid) {
3454        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3455
3456        let rs_uuid = Uuid::new_v4();
3457
3458        let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3459            (Attribute::Class, EntryClass::Group.to_value()),
3460            (Attribute::Name, Value::new_iname("testgroup")),
3461            (Attribute::Description, Value::new_utf8s("testgroup")),
3462            (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3463            (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3464        );
3465
3466        let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3467            (Attribute::Class, EntryClass::Object.to_value()),
3468            (Attribute::Class, EntryClass::Account.to_value()),
3469            (
3470                Attribute::Class,
3471                EntryClass::OAuth2ResourceServer.to_value()
3472            ),
3473            (
3474                Attribute::Class,
3475                EntryClass::OAuth2ResourceServerBasic.to_value()
3476            ),
3477            (Attribute::Uuid, Value::Uuid(rs_uuid)),
3478            (Attribute::Name, Value::new_iname("test_resource_server")),
3479            (
3480                Attribute::DisplayName,
3481                Value::new_utf8s("test_resource_server")
3482            ),
3483            (
3484                Attribute::OAuth2RsOriginLanding,
3485                Value::new_url_s("https://demo.example.com").unwrap()
3486            ),
3487            // Supplemental origins
3488            (
3489                Attribute::OAuth2RsOrigin,
3490                Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3491            ),
3492            (
3493                Attribute::OAuth2RsOrigin,
3494                Value::new_url_s("https://portal.example.com/?custom=foo").unwrap()
3495            ),
3496            (
3497                Attribute::OAuth2RsOrigin,
3498                Value::new_url_s("app://cheese").unwrap()
3499            ),
3500            // System admins
3501            (
3502                Attribute::OAuth2RsScopeMap,
3503                Value::new_oauthscopemap(
3504                    UUID_TESTGROUP,
3505                    btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3506                )
3507                .expect("invalid oauthscope")
3508            ),
3509            (
3510                Attribute::OAuth2RsScopeMap,
3511                Value::new_oauthscopemap(
3512                    UUID_IDM_ALL_ACCOUNTS,
3513                    btreeset![
3514                        OAUTH2_SCOPE_OPENID.to_string(),
3515                        OAUTH2_SCOPE_PROFILE.to_string()
3516                    ]
3517                )
3518                .expect("invalid oauthscope")
3519            ),
3520            (
3521                Attribute::OAuth2RsSupScopeMap,
3522                Value::new_oauthscopemap(
3523                    UUID_IDM_ALL_ACCOUNTS,
3524                    btreeset!["supplement".to_string()]
3525                )
3526                .expect("invalid oauthscope")
3527            ),
3528            (
3529                Attribute::OAuth2AllowInsecureClientDisablePkce,
3530                Value::new_bool(!enable_pkce)
3531            ),
3532            (
3533                Attribute::OAuth2JwtLegacyCryptoEnable,
3534                Value::new_bool(enable_legacy_crypto)
3535            ),
3536            (
3537                Attribute::OAuth2PreferShortUsername,
3538                Value::new_bool(prefer_short_username)
3539            )
3540        );
3541
3542        let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3543        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3544
3545        let entry = idms_prox_write
3546            .qs_write
3547            .internal_search_uuid(rs_uuid)
3548            .expect("Failed to retrieve OAuth2 resource entry ");
3549        let secret = entry
3550            .get_ava_single_secret(Attribute::OAuth2RsBasicSecret)
3551            .map(str::to_string)
3552            .expect("No oauth2_rs_basic_secret found");
3553
3554        // Setup the uat we'll be using - note for these tests they *require*
3555        // the parent session to be valid and present!
3556        let session_id = uuid::Uuid::new_v4();
3557
3558        let account = idms_prox_write
3559            .target_to_account(UUID_TESTPERSON_1)
3560            .expect("account must exist");
3561
3562        let uat = account
3563            .to_userauthtoken(
3564                session_id,
3565                SessionScope::ReadWrite,
3566                ct,
3567                &ResolvedAccountPolicy::test_policy(),
3568            )
3569            .expect("Unable to create uat");
3570
3571        // Need the uat first for expiry.
3572        let state = uat
3573            .expiry
3574            .map(SessionState::ExpiresAt)
3575            .unwrap_or(SessionState::NeverExpires);
3576
3577        let p = CryptoPolicy::minimum();
3578        let cred = Credential::new_password_only(&p, "test_password").unwrap();
3579        let cred_id = cred.uuid;
3580
3581        let session = Value::Session(
3582            session_id,
3583            crate::value::Session {
3584                label: "label".to_string(),
3585                state,
3586                issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3587                issued_by: IdentityId::Internal(UUID_SYSTEM),
3588                cred_id,
3589                scope: SessionScope::ReadWrite,
3590                type_: AuthType::Passkey,
3591                ext_metadata: Default::default(),
3592            },
3593        );
3594
3595        // Mod the user
3596        let modlist = ModifyList::new_list(vec![
3597            Modify::Present(Attribute::UserAuthTokenSession, session),
3598            Modify::Present(
3599                Attribute::PrimaryCredential,
3600                Value::Cred("primary".to_string(), cred),
3601            ),
3602        ]);
3603
3604        idms_prox_write
3605            .qs_write
3606            .internal_modify(
3607                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3608                &modlist,
3609            )
3610            .expect("Failed to modify user");
3611
3612        let ident = idms_prox_write
3613            .process_uat_to_identity(&uat, ct, Source::Internal)
3614            .expect("Unable to process uat");
3615
3616        idms_prox_write.commit().expect("failed to commit");
3617
3618        (secret, uat, ident, rs_uuid)
3619    }
3620
3621    async fn setup_oauth2_resource_server_public(
3622        idms: &IdmServer,
3623        ct: Duration,
3624    ) -> (UserAuthToken, Identity, Uuid) {
3625        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3626
3627        let rs_uuid = Uuid::new_v4();
3628
3629        let entry_group: Entry<EntryInit, EntryNew> = entry_init!(
3630            (Attribute::Class, EntryClass::Group.to_value()),
3631            (Attribute::Name, Value::new_iname("testgroup")),
3632            (Attribute::Description, Value::new_utf8s("testgroup")),
3633            (Attribute::Uuid, Value::Uuid(UUID_TESTGROUP)),
3634            (Attribute::Member, Value::Refer(UUID_TESTPERSON_1),)
3635        );
3636
3637        let entry_rs: Entry<EntryInit, EntryNew> = entry_init!(
3638            (Attribute::Class, EntryClass::Object.to_value()),
3639            (Attribute::Class, EntryClass::Account.to_value()),
3640            (
3641                Attribute::Class,
3642                EntryClass::OAuth2ResourceServer.to_value()
3643            ),
3644            (
3645                Attribute::Class,
3646                EntryClass::OAuth2ResourceServerPublic.to_value()
3647            ),
3648            (Attribute::Uuid, Value::Uuid(rs_uuid)),
3649            (Attribute::Name, Value::new_iname("test_resource_server")),
3650            (
3651                Attribute::DisplayName,
3652                Value::new_utf8s("test_resource_server")
3653            ),
3654            (
3655                Attribute::OAuth2RsOriginLanding,
3656                Value::new_url_s("https://demo.example.com").unwrap()
3657            ),
3658            (
3659                Attribute::OAuth2RsOrigin,
3660                Value::new_url_s("https://demo.example.com/oauth2/result").unwrap()
3661            ),
3662            // System admins
3663            (
3664                Attribute::OAuth2RsScopeMap,
3665                Value::new_oauthscopemap(
3666                    UUID_TESTGROUP,
3667                    btreeset![OAUTH2_SCOPE_GROUPS.to_string()]
3668                )
3669                .expect("invalid oauthscope")
3670            ),
3671            (
3672                Attribute::OAuth2RsScopeMap,
3673                Value::new_oauthscopemap(
3674                    UUID_IDM_ALL_ACCOUNTS,
3675                    btreeset![OAUTH2_SCOPE_OPENID.to_string()]
3676                )
3677                .expect("invalid oauthscope")
3678            ),
3679            (
3680                Attribute::OAuth2RsSupScopeMap,
3681                Value::new_oauthscopemap(
3682                    UUID_IDM_ALL_ACCOUNTS,
3683                    btreeset!["supplement".to_string()]
3684                )
3685                .expect("invalid oauthscope")
3686            )
3687        );
3688        let ce = CreateEvent::new_internal(vec![entry_rs, entry_group, E_TESTPERSON_1.clone()]);
3689        assert!(idms_prox_write.qs_write.create(&ce).is_ok());
3690
3691        // Setup the uat we'll be using - note for these tests they *require*
3692        // the parent session to be valid and present!
3693
3694        let session_id = uuid::Uuid::new_v4();
3695
3696        let account = idms_prox_write
3697            .target_to_account(UUID_TESTPERSON_1)
3698            .expect("account must exist");
3699        let uat = account
3700            .to_userauthtoken(
3701                session_id,
3702                SessionScope::ReadWrite,
3703                ct,
3704                &ResolvedAccountPolicy::test_policy(),
3705            )
3706            .expect("Unable to create uat");
3707
3708        // Need the uat first for expiry.
3709        let state = uat
3710            .expiry
3711            .map(SessionState::ExpiresAt)
3712            .unwrap_or(SessionState::NeverExpires);
3713
3714        let p = CryptoPolicy::minimum();
3715        let cred = Credential::new_password_only(&p, "test_password").unwrap();
3716        let cred_id = cred.uuid;
3717
3718        let session = Value::Session(
3719            session_id,
3720            crate::value::Session {
3721                label: "label".to_string(),
3722                state,
3723                issued_at: time::OffsetDateTime::UNIX_EPOCH + ct,
3724                issued_by: IdentityId::Internal(UUID_SYSTEM),
3725                cred_id,
3726                scope: SessionScope::ReadWrite,
3727                type_: AuthType::Passkey,
3728                ext_metadata: Default::default(),
3729            },
3730        );
3731
3732        // Mod the user
3733        let modlist = ModifyList::new_list(vec![
3734            Modify::Present(Attribute::UserAuthTokenSession, session),
3735            Modify::Present(
3736                Attribute::PrimaryCredential,
3737                Value::Cred("primary".to_string(), cred),
3738            ),
3739        ]);
3740
3741        idms_prox_write
3742            .qs_write
3743            .internal_modify(
3744                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
3745                &modlist,
3746            )
3747            .expect("Failed to modify user");
3748
3749        let ident = idms_prox_write
3750            .process_uat_to_identity(&uat, ct, Source::Internal)
3751            .expect("Unable to process uat");
3752
3753        idms_prox_write.commit().expect("failed to commit");
3754
3755        (uat, ident, rs_uuid)
3756    }
3757
3758    /// Perform an oauth2 exchange, assuming that it will succeed.
3759    async fn perform_oauth2_exchange(
3760        idms: &IdmServer,
3761        ident: &Identity,
3762        ct: Duration,
3763        client_authz: ClientAuthInfo,
3764        scopes: String,
3765    ) -> AccessTokenResponse {
3766        let idms_prox_read = idms.proxy_read().await.unwrap();
3767
3768        let pkce_secret = PkceS256Secret::default();
3769
3770        let consent_request = good_authorisation_request!(
3771            idms_prox_read,
3772            ident,
3773            ct,
3774            pkce_secret.to_request(),
3775            scopes
3776        );
3777
3778        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3779            unreachable!();
3780        };
3781
3782        // == Manually submit the consent token to the permit for the permit_success
3783        drop(idms_prox_read);
3784        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3785
3786        let permit_success = idms_prox_write
3787            .check_oauth2_authorise_permit(ident, &consent_token, ct)
3788            .expect("Failed to perform OAuth2 permit");
3789
3790        // == Submit the token exchange code.
3791        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
3792            code: permit_success.code,
3793            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3794            code_verifier: Some(pkce_secret.to_verifier()),
3795        }
3796        .into();
3797
3798        let token_response = idms_prox_write
3799            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
3800            .expect("Failed to perform OAuth2 token exchange");
3801
3802        assert!(idms_prox_write.commit().is_ok());
3803
3804        token_response
3805    }
3806
3807    async fn validate_id_token(idms: &IdmServer, ct: Duration, id_token: &str) -> OidcToken {
3808        let idms_prox_read = idms.proxy_read().await.unwrap();
3809
3810        let mut jwkset = idms_prox_read
3811            .oauth2_openid_publickey("test_resource_server")
3812            .expect("Failed to get public key");
3813        let public_jwk = jwkset.keys.pop().expect("no such jwk");
3814
3815        let jws_validator =
3816            JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
3817
3818        let oidc_unverified = OidcUnverified::from_str(id_token).expect("Failed to parse id_token");
3819
3820        let iat = ct.as_secs() as i64;
3821
3822        jws_validator
3823            .verify(&oidc_unverified)
3824            .unwrap()
3825            .verify_exp(iat)
3826            .expect("Failed to verify oidc")
3827    }
3828
3829    async fn setup_idm_admin(idms: &IdmServer, ct: Duration) -> (UserAuthToken, Identity) {
3830        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3831        let account = idms_prox_write
3832            .target_to_account(UUID_IDM_ADMIN)
3833            .expect("account must exist");
3834        let session_id = uuid::Uuid::new_v4();
3835        let uat = account
3836            .to_userauthtoken(
3837                session_id,
3838                SessionScope::ReadWrite,
3839                ct,
3840                &ResolvedAccountPolicy::test_policy(),
3841            )
3842            .expect("Unable to create uat");
3843        let ident = idms_prox_write
3844            .process_uat_to_identity(&uat, ct, Source::Internal)
3845            .expect("Unable to process uat");
3846
3847        idms_prox_write.commit().expect("failed to commit");
3848
3849        (uat, ident)
3850    }
3851
3852    #[idm_test]
3853    async fn test_idm_oauth2_basic_function(
3854        idms: &IdmServer,
3855        _idms_delayed: &mut IdmServerDelayed,
3856    ) {
3857        let ct = Duration::from_secs(TEST_CURRENT_TIME);
3858        let (secret, _uat, ident, _) =
3859            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
3860
3861        let idms_prox_read = idms.proxy_read().await.unwrap();
3862
3863        // == Setup the authorisation request
3864        let pkce_secret = PkceS256Secret::default();
3865
3866        let consent_request = good_authorisation_request!(
3867            idms_prox_read,
3868            &ident,
3869            ct,
3870            pkce_secret.to_request(),
3871            OAUTH2_SCOPE_OPENID.to_string()
3872        );
3873
3874        // Should be in the consent phase;
3875        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3876            unreachable!();
3877        };
3878
3879        // == Manually submit the consent token to the permit for the permit_success
3880        drop(idms_prox_read);
3881        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3882
3883        let permit_success = idms_prox_write
3884            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
3885            .expect("Failed to perform OAuth2 permit");
3886
3887        // Check we are reflecting the CSRF properly.
3888        assert_eq!(permit_success.state.as_deref(), Some("123"));
3889
3890        // == Submit the token exchange code.
3891
3892        let token_req = AccessTokenRequest {
3893            grant_type: GrantTypeReq::AuthorizationCode {
3894                code: permit_success.code,
3895                redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3896                code_verifier: Some(pkce_secret.to_verifier()),
3897            },
3898            client_post_auth: ClientPostAuth {
3899                client_id: Some("test_resource_server".to_string()),
3900                client_secret: Some(secret),
3901            },
3902        };
3903
3904        let token_response = idms_prox_write
3905            .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
3906            .expect("Failed to perform OAuth2 token exchange");
3907
3908        // 🎉 We got a token! In the future we can then check introspection from this point.
3909        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
3910
3911        assert!(idms_prox_write.commit().is_ok());
3912    }
3913
3914    #[idm_test]
3915    async fn test_idm_oauth2_public_function(
3916        idms: &IdmServer,
3917        _idms_delayed: &mut IdmServerDelayed,
3918    ) {
3919        let ct = Duration::from_secs(TEST_CURRENT_TIME);
3920        let (_uat, ident, _) = setup_oauth2_resource_server_public(idms, ct).await;
3921
3922        let idms_prox_read = idms.proxy_read().await.unwrap();
3923
3924        // Get an ident/uat for now.
3925
3926        // == Setup the authorisation request
3927        let pkce_secret = PkceS256Secret::default();
3928
3929        let consent_request = good_authorisation_request!(
3930            idms_prox_read,
3931            &ident,
3932            ct,
3933            pkce_secret.to_request(),
3934            OAUTH2_SCOPE_OPENID.to_string()
3935        );
3936
3937        // Should be in the consent phase;
3938        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
3939            unreachable!();
3940        };
3941
3942        // == Manually submit the consent token to the permit for the permit_success
3943        drop(idms_prox_read);
3944        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
3945
3946        let permit_success = idms_prox_write
3947            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
3948            .expect("Failed to perform OAuth2 permit");
3949
3950        // Check we are reflecting the CSRF properly.
3951        assert_eq!(permit_success.state.as_deref(), Some("123"));
3952
3953        // == Submit the token exchange code.
3954
3955        let token_req = AccessTokenRequest {
3956            grant_type: GrantTypeReq::AuthorizationCode {
3957                code: permit_success.code,
3958                redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
3959                // From the first step.
3960                code_verifier: Some(pkce_secret.to_verifier()),
3961            },
3962
3963            client_post_auth: ClientPostAuth {
3964                client_id: Some("Test_Resource_Server".to_string()),
3965                client_secret: None,
3966            },
3967        };
3968
3969        let token_response = idms_prox_write
3970            .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
3971            .expect("Failed to perform OAuth2 token exchange");
3972
3973        // 🎉 We got a token! In the future we can then check introspection from this point.
3974        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
3975
3976        assert!(idms_prox_write.commit().is_ok());
3977    }
3978
3979    #[idm_test]
3980    async fn test_idm_oauth2_invalid_authorisation_requests(
3981        idms: &IdmServer,
3982        _idms_delayed: &mut IdmServerDelayed,
3983    ) {
3984        // Test invalid OAuth2 authorisation states/requests.
3985        let ct = Duration::from_secs(TEST_CURRENT_TIME);
3986        let (_secret, _uat, ident, _) =
3987            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
3988
3989        let (_anon_uat, anon_ident) = setup_idm_admin(idms, ct).await;
3990        let (_idm_admin_uat, idm_admin_ident) = setup_idm_admin(idms, ct).await;
3991
3992        // Need a uat from a user not in the group. Probs anonymous.
3993        let idms_prox_read = idms.proxy_read().await.unwrap();
3994
3995        let pkce_secret = PkceS256Secret::default();
3996
3997        let pkce_request = pkce_secret.to_request();
3998
3999        //  * response type != code.
4000        let auth_req = AuthorisationRequest {
4001            // We're unlikely to support Implicit Grant
4002            response_type: ResponseType::Token,
4003            response_mode: None,
4004            client_id: "test_resource_server".to_string(),
4005            state: Some("123".to_string()),
4006            pkce_request: Some(pkce_request.clone()),
4007            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4008            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4009            nonce: None,
4010            oidc_ext: Default::default(),
4011            max_age: None,
4012            unknown_keys: Default::default(),
4013        };
4014
4015        assert!(
4016            idms_prox_read
4017                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4018                .unwrap_err()
4019                == Oauth2Error::UnsupportedResponseType
4020        );
4021
4022        // * No pkce in pkce enforced mode.
4023        let auth_req = AuthorisationRequest {
4024            response_type: ResponseType::Code,
4025            response_mode: None,
4026            client_id: "test_resource_server".to_string(),
4027            state: Some("123".to_string()),
4028            pkce_request: None,
4029            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4030            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4031            nonce: None,
4032            oidc_ext: Default::default(),
4033            max_age: None,
4034            unknown_keys: Default::default(),
4035        };
4036
4037        assert!(
4038            idms_prox_read
4039                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4040                .unwrap_err()
4041                == Oauth2Error::InvalidRequest
4042        );
4043
4044        //  * invalid rs name
4045        let auth_req = AuthorisationRequest {
4046            response_type: ResponseType::Code,
4047            response_mode: None,
4048            client_id: "NOT A REAL RESOURCE SERVER".to_string(),
4049            state: Some("123".to_string()),
4050            pkce_request: Some(pkce_request.clone()),
4051            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4052            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4053            nonce: None,
4054            oidc_ext: Default::default(),
4055            max_age: None,
4056            unknown_keys: Default::default(),
4057        };
4058
4059        assert!(
4060            idms_prox_read
4061                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4062                .unwrap_err()
4063                == Oauth2Error::InvalidClientId
4064        );
4065
4066        //  * mismatched origin in the redirect.
4067        let auth_req = AuthorisationRequest {
4068            response_type: ResponseType::Code,
4069            response_mode: None,
4070            client_id: "test_resource_server".to_string(),
4071            state: Some("123".to_string()),
4072            pkce_request: Some(pkce_request.clone()),
4073            redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4074            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4075            nonce: None,
4076            oidc_ext: Default::default(),
4077            max_age: None,
4078            unknown_keys: Default::default(),
4079        };
4080
4081        assert!(
4082            idms_prox_read
4083                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4084                .unwrap_err()
4085                == Oauth2Error::InvalidOrigin
4086        );
4087
4088        // * invalid uri in the redirect
4089        let auth_req = AuthorisationRequest {
4090            response_type: ResponseType::Code,
4091            response_mode: None,
4092            client_id: "test_resource_server".to_string(),
4093            state: Some("123".to_string()),
4094            pkce_request: Some(pkce_request.clone()),
4095            redirect_uri: Url::parse("https://demo.example.com/oauth2/wrong_place").unwrap(),
4096            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4097            nonce: None,
4098            oidc_ext: Default::default(),
4099            max_age: None,
4100            unknown_keys: Default::default(),
4101        };
4102
4103        assert!(
4104            idms_prox_read
4105                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4106                .unwrap_err()
4107                == Oauth2Error::InvalidOrigin
4108        );
4109
4110        // * invalid uri (doesn't match query params)
4111        let auth_req = AuthorisationRequest {
4112            response_type: ResponseType::Code,
4113            response_mode: None,
4114            client_id: "test_resource_server".to_string(),
4115            state: Some("123".to_string()),
4116            pkce_request: Some(pkce_request.clone()),
4117            redirect_uri: Url::parse("https://portal.example.com/?custom=foo&too=many").unwrap(),
4118            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4119            nonce: None,
4120            oidc_ext: Default::default(),
4121            max_age: None,
4122            unknown_keys: Default::default(),
4123        };
4124
4125        assert!(
4126            idms_prox_read
4127                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4128                .unwrap_err()
4129                == Oauth2Error::InvalidOrigin
4130        );
4131
4132        let auth_req = AuthorisationRequest {
4133            response_type: ResponseType::Code,
4134            response_mode: None,
4135            client_id: "test_resource_server".to_string(),
4136            state: Some("123".to_string()),
4137            pkce_request: Some(pkce_request.clone()),
4138            redirect_uri: Url::parse("https://portal.example.com").unwrap(),
4139            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4140            nonce: None,
4141            oidc_ext: Default::default(),
4142            max_age: None,
4143            unknown_keys: Default::default(),
4144        };
4145
4146        assert!(
4147            idms_prox_read
4148                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4149                .unwrap_err()
4150                == Oauth2Error::InvalidOrigin
4151        );
4152
4153        let auth_req = AuthorisationRequest {
4154            response_type: ResponseType::Code,
4155            response_mode: None,
4156            client_id: "test_resource_server".to_string(),
4157            state: Some("123".to_string()),
4158            pkce_request: Some(pkce_request.clone()),
4159            redirect_uri: Url::parse("https://portal.example.com/?wrong=queryparam").unwrap(),
4160            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4161            nonce: None,
4162            oidc_ext: Default::default(),
4163            max_age: None,
4164            unknown_keys: Default::default(),
4165        };
4166
4167        assert!(
4168            idms_prox_read
4169                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4170                .unwrap_err()
4171                == Oauth2Error::InvalidOrigin
4172        );
4173
4174        // Not Authenticated
4175        let auth_req = AuthorisationRequest {
4176            response_type: ResponseType::Code,
4177            response_mode: None,
4178            client_id: "test_resource_server".to_string(),
4179            state: Some("123".to_string()),
4180            pkce_request: Some(pkce_request.clone()),
4181            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4182            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
4183            nonce: None,
4184            oidc_ext: Default::default(),
4185            max_age: None,
4186            unknown_keys: Default::default(),
4187        };
4188
4189        let req = idms_prox_read
4190            .check_oauth2_authorisation(None, &auth_req, ct)
4191            .unwrap();
4192
4193        assert!(matches!(
4194            req,
4195            AuthoriseResponse::AuthenticationRequired { .. }
4196        ));
4197
4198        // Requested scope is not available
4199        let auth_req = AuthorisationRequest {
4200            response_type: ResponseType::Code,
4201            response_mode: None,
4202            client_id: "test_resource_server".to_string(),
4203            state: Some("123".to_string()),
4204            pkce_request: Some(pkce_request.clone()),
4205            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4206            scope: btreeset!["invalid_scope".to_string(), "read".to_string()],
4207            nonce: None,
4208            oidc_ext: Default::default(),
4209            max_age: None,
4210            unknown_keys: Default::default(),
4211        };
4212
4213        assert!(
4214            idms_prox_read
4215                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4216                .unwrap_err()
4217                == Oauth2Error::AccessDenied
4218        );
4219
4220        // Not a member of the group.
4221        let auth_req = AuthorisationRequest {
4222            response_type: ResponseType::Code,
4223            response_mode: None,
4224            client_id: "test_resource_server".to_string(),
4225            state: Some("123".to_string()),
4226            pkce_request: Some(pkce_request.clone()),
4227            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4228            scope: btreeset!["openid".to_string(), "read".to_string()],
4229            nonce: None,
4230            oidc_ext: Default::default(),
4231            max_age: None,
4232            unknown_keys: Default::default(),
4233        };
4234
4235        assert!(
4236            idms_prox_read
4237                .check_oauth2_authorisation(Some(&idm_admin_ident), &auth_req, ct)
4238                .unwrap_err()
4239                == Oauth2Error::AccessDenied
4240        );
4241
4242        // Deny Anonymous auth methods
4243        let auth_req = AuthorisationRequest {
4244            response_type: ResponseType::Code,
4245            response_mode: None,
4246            client_id: "test_resource_server".to_string(),
4247            state: Some("123".to_string()),
4248            pkce_request: Some(pkce_request),
4249            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4250            scope: btreeset!["openid".to_string(), "read".to_string()],
4251            nonce: None,
4252            oidc_ext: Default::default(),
4253            max_age: None,
4254            unknown_keys: Default::default(),
4255        };
4256
4257        assert!(
4258            idms_prox_read
4259                .check_oauth2_authorisation(Some(&anon_ident), &auth_req, ct)
4260                .unwrap_err()
4261                == Oauth2Error::AccessDenied
4262        );
4263    }
4264
4265    #[idm_test]
4266    async fn test_idm_oauth2_invalid_authorisation_permit_requests(
4267        idms: &IdmServer,
4268        _idms_delayed: &mut IdmServerDelayed,
4269    ) {
4270        // Test invalid OAuth2 authorisation states/requests.
4271        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4272        let (_secret, uat, ident, _) =
4273            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4274
4275        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4276
4277        let mut uat_wrong_session_id = uat.clone();
4278        uat_wrong_session_id.session_id = uuid::Uuid::new_v4();
4279        let ident_wrong_session_id = idms_prox_write
4280            .process_uat_to_identity(&uat_wrong_session_id, ct, Source::Internal)
4281            .expect("Unable to process uat");
4282
4283        let account = idms_prox_write
4284            .target_to_account(UUID_IDM_ADMIN)
4285            .expect("account must exist");
4286        let session_id = uuid::Uuid::new_v4();
4287        let uat2 = account
4288            .to_userauthtoken(
4289                session_id,
4290                SessionScope::ReadWrite,
4291                ct,
4292                &ResolvedAccountPolicy::test_policy(),
4293            )
4294            .expect("Unable to create uat");
4295        let ident2 = idms_prox_write
4296            .process_uat_to_identity(&uat2, ct, Source::Internal)
4297            .expect("Unable to process uat");
4298
4299        assert!(idms_prox_write.commit().is_ok());
4300
4301        // Now start the test
4302
4303        let idms_prox_read = idms.proxy_read().await.unwrap();
4304
4305        let pkce_secret = PkceS256Secret::default();
4306
4307        let consent_request = good_authorisation_request!(
4308            idms_prox_read,
4309            &ident,
4310            ct,
4311            pkce_secret.to_request(),
4312            OAUTH2_SCOPE_OPENID.to_string()
4313        );
4314
4315        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4316            unreachable!();
4317        };
4318
4319        drop(idms_prox_read);
4320        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4321
4322        // Invalid permits
4323        //  * expired token, aka past ttl.
4324        assert!(
4325            idms_prox_write
4326                .check_oauth2_authorise_permit(
4327                    &ident,
4328                    &consent_token,
4329                    ct + Duration::from_secs(TOKEN_EXPIRE),
4330                )
4331                .unwrap_err()
4332                == OperationError::CryptographyError
4333        );
4334
4335        //  * incorrect ident
4336        // We get another uat, but for a different user, and we'll introduce these
4337        // inconsistently to cause confusion.
4338
4339        assert!(
4340            idms_prox_write
4341                .check_oauth2_authorise_permit(&ident2, &consent_token, ct,)
4342                .unwrap_err()
4343                == OperationError::InvalidSessionState
4344        );
4345
4346        //  * incorrect session id
4347        assert!(
4348            idms_prox_write
4349                .check_oauth2_authorise_permit(&ident_wrong_session_id, &consent_token, ct,)
4350                .unwrap_err()
4351                == OperationError::InvalidSessionState
4352        );
4353
4354        assert!(idms_prox_write.commit().is_ok());
4355    }
4356
4357    #[idm_test]
4358    async fn test_idm_oauth2_invalid_token_exchange_requests(
4359        idms: &IdmServer,
4360        _idms_delayed: &mut IdmServerDelayed,
4361    ) {
4362        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4363        let (secret, mut uat, ident, _) =
4364            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4365
4366        // ⚠️  We set the uat expiry time to 5 seconds from TEST_CURRENT_TIME. This
4367        // allows all our other tests to pass, but it means when we specifically put the
4368        // clock forward a fraction, the fernet tokens are still valid, but the uat
4369        // is not.
4370        // IE
4371        //   |---------------------|------------------|
4372        //   TEST_CURRENT_TIME     UAT_EXPIRE         TOKEN_EXPIRE
4373        //
4374        // This lets us check a variety of time based cases.
4375        uat.expiry = Some(
4376            time::OffsetDateTime::UNIX_EPOCH
4377                + Duration::from_secs(TEST_CURRENT_TIME + UAT_EXPIRE - 1),
4378        );
4379
4380        let idms_prox_read = idms.proxy_read().await.unwrap();
4381
4382        // == Setup the authorisation request
4383        let pkce_secret = PkceS256Secret::default();
4384
4385        let consent_request = good_authorisation_request!(
4386            idms_prox_read,
4387            &ident,
4388            ct,
4389            pkce_secret.to_request(),
4390            OAUTH2_SCOPE_OPENID.to_string()
4391        );
4392
4393        let code_verifier = Some(pkce_secret.to_verifier());
4394
4395        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4396            unreachable!();
4397        };
4398
4399        drop(idms_prox_read);
4400        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4401
4402        // == Manually submit the consent token to the permit for the permit_success
4403        let permit_success = idms_prox_write
4404            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4405            .expect("Failed to perform OAuth2 permit");
4406
4407        // == Submit the token exchange code.
4408
4409        // Invalid token exchange
4410        //  * invalid client_authz (not base64)
4411        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4412            code: permit_success.code.clone(),
4413            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4414            code_verifier: code_verifier.clone(),
4415        }
4416        .into();
4417
4418        let client_authz = ClientAuthInfo::from("not base64");
4419
4420        assert!(
4421            idms_prox_write
4422                .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4423                .unwrap_err()
4424                == Oauth2Error::AuthenticationRequired
4425        );
4426
4427        //  * doesn't have ':'
4428        let client_authz =
4429            general_purpose::STANDARD.encode(format!("test_resource_server {secret}"));
4430        let client_authz = ClientAuthInfo::from(client_authz.as_str());
4431
4432        assert!(
4433            idms_prox_write
4434                .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4435                .unwrap_err()
4436                == Oauth2Error::AuthenticationRequired
4437        );
4438
4439        //  * invalid client_id
4440        let client_authz = ClientAuthInfo::encode_basic("NOT A REAL SERVER", secret.as_str());
4441
4442        assert!(
4443            idms_prox_write
4444                .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4445                .unwrap_err()
4446                == Oauth2Error::AuthenticationRequired
4447        );
4448
4449        //  * valid client_id, but invalid secret
4450        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
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        // ✅ Now the valid client_authz is in place.
4460        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4461
4462        //  * expired exchange code (took too long)
4463        assert!(
4464            idms_prox_write
4465                .check_oauth2_token_exchange(
4466                    &client_authz,
4467                    &token_req,
4468                    ct + Duration::from_secs(TOKEN_EXPIRE)
4469                )
4470                .unwrap_err()
4471                == Oauth2Error::InvalidRequest
4472        );
4473
4474        /*
4475        //  * incorrect grant_type
4476        // No longer possible due to changes in json api
4477        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4478            grant_type: "INCORRECT GRANT TYPE".to_string(),
4479            code: permit_success.code.clone(),
4480            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4481            client_id: None,
4482            client_secret: None,
4483            code_verifier: code_verifier.clone(),
4484        };
4485        assert!(
4486            idms_prox_read
4487                .check_oauth2_token_exchange(client_authz.as_deref(), &token_req, ct)
4488                .unwrap_err()
4489                == Oauth2Error::InvalidRequest
4490        );
4491        */
4492
4493        //  * Incorrect redirect uri
4494        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4495            code: permit_success.code.clone(),
4496            redirect_uri: Url::parse("https://totes.not.sus.org/oauth2/result").unwrap(),
4497            code_verifier: code_verifier.clone(),
4498        }
4499        .into();
4500        assert!(
4501            idms_prox_write
4502                .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4503                .unwrap_err()
4504                == Oauth2Error::InvalidOrigin
4505        );
4506
4507        //  * code verifier incorrect
4508        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4509            code: permit_success.code,
4510            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4511            code_verifier: Some("12345".to_string()),
4512        }
4513        .into();
4514        assert!(
4515            idms_prox_write
4516                .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4517                .unwrap_err()
4518                == Oauth2Error::InvalidRequest
4519        );
4520
4521        assert!(idms_prox_write.commit().is_ok());
4522    }
4523
4524    #[idm_test]
4525    async fn test_idm_oauth2_supplemental_origin_redirect(
4526        idms: &IdmServer,
4527        _idms_delayed: &mut IdmServerDelayed,
4528    ) {
4529        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4530        let (secret, uat, ident, _) =
4531            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4532
4533        let idms_prox_read = idms.proxy_read().await.unwrap();
4534
4535        // == Setup the authorisation request
4536        let pkce_secret = PkceS256Secret::default();
4537
4538        let redirect_uri = Url::parse("https://portal.example.com/?custom=foo").unwrap();
4539
4540        let auth_req = AuthorisationRequest {
4541            response_type: ResponseType::Code,
4542            response_mode: None,
4543            client_id: "test_resource_server".to_string(),
4544            state: None,
4545            pkce_request: Some(pkce_secret.to_request()),
4546            redirect_uri: redirect_uri.clone(),
4547            scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4548            nonce: Some("abcdef".to_string()),
4549            oidc_ext: Default::default(),
4550            max_age: None,
4551            unknown_keys: Default::default(),
4552        };
4553
4554        let consent_request = idms_prox_read
4555            .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4556            .expect("OAuth2 authorisation failed");
4557
4558        trace!(?consent_request);
4559
4560        // Should be in the consent phase;
4561        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4562            unreachable!();
4563        };
4564
4565        // == Manually submit the consent token to the permit for the permit_success
4566        drop(idms_prox_read);
4567        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4568
4569        let permit_success = idms_prox_write
4570            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4571            .expect("Failed to perform OAuth2 permit");
4572
4573        // Check we are reflecting the CSRF properly.
4574        assert_eq!(permit_success.state.as_deref(), None);
4575
4576        // Assert we followed the redirect uri including the query elements
4577        // we have in the url.
4578        let permit_redirect_uri = permit_success.build_redirect_uri();
4579
4580        assert_eq!(permit_redirect_uri.origin(), redirect_uri.origin());
4581        assert_eq!(permit_redirect_uri.path(), redirect_uri.path());
4582        let query = BTreeMap::from_iter(permit_redirect_uri.query_pairs().into_owned());
4583        // Assert the query pair wasn't changed
4584        assert_eq!(query.get("custom").map(|s| s.as_str()), Some("foo"));
4585
4586        // == Submit the token exchange code.
4587        // ⚠️  This is where we submit a different origin!
4588        let token_req = AccessTokenRequest {
4589            grant_type: GrantTypeReq::AuthorizationCode {
4590                code: permit_success.code,
4591                redirect_uri,
4592                code_verifier: Some(pkce_secret.to_verifier()),
4593            },
4594
4595            client_post_auth: ClientPostAuth {
4596                client_id: Some("test_resource_server".to_string()),
4597                client_secret: Some(secret.clone()),
4598            },
4599        };
4600
4601        let token_response = idms_prox_write
4602            .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4603            .expect("Failed to perform OAuth2 token exchange");
4604
4605        // 🎉 We got a token! In the future we can then check introspection from this point.
4606        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4607
4608        assert!(idms_prox_write.commit().is_ok());
4609
4610        // ============================================================================
4611        // Now repeat the test with the app url.
4612
4613        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4614
4615        // Reload the ident since it pins an entry in memory.
4616        let ident = idms_prox_read
4617            .process_uat_to_identity(&uat, ct, Source::Internal)
4618            .expect("Unable to process uat");
4619
4620        let pkce_secret = PkceS256Secret::default();
4621
4622        let auth_req = AuthorisationRequest {
4623            response_type: ResponseType::Code,
4624            response_mode: None,
4625            client_id: "test_resource_server".to_string(),
4626            state: Some("123".to_string()),
4627            pkce_request: Some(pkce_secret.to_request()),
4628            redirect_uri: Url::parse("app://cheese").unwrap(),
4629            scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
4630            nonce: Some("abcdef".to_string()),
4631            oidc_ext: Default::default(),
4632            max_age: None,
4633            unknown_keys: Default::default(),
4634        };
4635
4636        let consent_request = idms_prox_read
4637            .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
4638            .expect("OAuth2 authorisation failed");
4639
4640        trace!(?consent_request);
4641
4642        let AuthoriseResponse::Permitted(permit_success) = consent_request else {
4643            unreachable!();
4644        };
4645
4646        // == Manually submit the consent token to the permit for the permit_success
4647        // Check we are reflecting the CSRF properly.
4648        assert_eq!(permit_success.state.as_deref(), Some("123"));
4649
4650        // == Submit the token exchange code.
4651        // ⚠️  This is where we submit a different origin!
4652        let token_req = AccessTokenRequest {
4653            grant_type: GrantTypeReq::AuthorizationCode {
4654                code: permit_success.code,
4655                redirect_uri: Url::parse("app://cheese").unwrap(),
4656                code_verifier: Some(pkce_secret.to_verifier()),
4657            },
4658
4659            client_post_auth: ClientPostAuth {
4660                client_id: Some("test_resource_server".to_string()),
4661                client_secret: Some(secret),
4662            },
4663        };
4664
4665        drop(idms_prox_read);
4666        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4667
4668        let token_response = idms_prox_write
4669            .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
4670            .expect("Failed to perform OAuth2 token exchange");
4671
4672        // 🎉 We got a token! In the future we can then check introspection from this point.
4673        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
4674    }
4675
4676    #[idm_test]
4677    async fn test_idm_oauth2_token_introspect(
4678        idms: &IdmServer,
4679        _idms_delayed: &mut IdmServerDelayed,
4680    ) {
4681        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4682        let (secret, _uat, ident, _) =
4683            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4684        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4685
4686        let idms_prox_read = idms.proxy_read().await.unwrap();
4687
4688        // == Setup the authorisation request
4689        let pkce_secret = PkceS256Secret::default();
4690        let consent_request = good_authorisation_request!(
4691            idms_prox_read,
4692            &ident,
4693            ct,
4694            pkce_secret.to_request(),
4695            OAUTH2_SCOPE_OPENID.to_string()
4696        );
4697
4698        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4699            unreachable!();
4700        };
4701
4702        // == Manually submit the consent token to the permit for the permit_success
4703        drop(idms_prox_read);
4704        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4705
4706        let permit_success = idms_prox_write
4707            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4708            .expect("Failed to perform OAuth2 permit");
4709
4710        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4711            code: permit_success.code,
4712            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4713            code_verifier: Some(pkce_secret.to_verifier()),
4714        }
4715        .into();
4716        let oauth2_token = idms_prox_write
4717            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4718            .expect("Unable to exchange for OAuth2 token");
4719
4720        assert!(idms_prox_write.commit().is_ok());
4721
4722        // Okay, now we have the token, we can check it works with introspect.
4723        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4724
4725        let intr_request = AccessTokenIntrospectRequest {
4726            token: oauth2_token.access_token,
4727            token_type_hint: None,
4728            client_post_auth: ClientPostAuth::default(),
4729        };
4730        let intr_response = idms_prox_read
4731            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4732            .expect("Failed to inspect token");
4733
4734        eprintln!("👉  {intr_response:?}");
4735        assert!(intr_response.active);
4736        assert_eq!(
4737            intr_response.scope,
4738            btreeset!["openid".to_string(), "supplement".to_string()]
4739        );
4740        assert_eq!(
4741            intr_response.client_id.as_deref(),
4742            Some("test_resource_server")
4743        );
4744        assert_eq!(
4745            intr_response.username.as_deref(),
4746            Some("testperson1@example.com")
4747        );
4748        assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
4749        assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
4750        assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
4751
4752        drop(idms_prox_read);
4753        // start a write,
4754
4755        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4756        // Expire the account, should cause introspect to return inactive.
4757        let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_CURRENT_TIME - 1));
4758        let me_inv_m = ModifyEvent::new_internal_invalid(
4759            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTPERSON_1))),
4760            ModifyList::new_list(vec![Modify::Present(Attribute::AccountExpire, v_expire)]),
4761        );
4762        // go!
4763        assert!(idms_prox_write.qs_write.modify(&me_inv_m).is_ok());
4764        assert!(idms_prox_write.commit().is_ok());
4765
4766        // start a new read
4767        // check again.
4768        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4769        let intr_response = idms_prox_read
4770            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4771            .expect("Failed to inspect token");
4772
4773        assert!(!intr_response.active);
4774    }
4775
4776    #[idm_test]
4777    async fn test_idm_oauth2_token_revoke(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
4778        // First, setup to get a token.
4779        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4780        let (secret, _uat, ident, _) =
4781            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4782        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4783
4784        let idms_prox_read = idms.proxy_read().await.unwrap();
4785
4786        // == Setup the authorisation request
4787        let pkce_secret = PkceS256Secret::default();
4788
4789        let consent_request = good_authorisation_request!(
4790            idms_prox_read,
4791            &ident,
4792            ct,
4793            pkce_secret.to_request(),
4794            OAUTH2_SCOPE_OPENID.to_string()
4795        );
4796
4797        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4798            unreachable!();
4799        };
4800
4801        // == Manually submit the consent token to the permit for the permit_success
4802        drop(idms_prox_read);
4803        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4804
4805        let permit_success = idms_prox_write
4806            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4807            .expect("Failed to perform OAuth2 permit");
4808
4809        // Assert that the consent was submitted
4810        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4811            code: permit_success.code,
4812            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4813            code_verifier: Some(pkce_secret.to_verifier()),
4814        }
4815        .into();
4816        let oauth2_token = idms_prox_write
4817            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4818            .expect("Unable to exchange for OAuth2 token");
4819
4820        assert!(idms_prox_write.commit().is_ok());
4821
4822        // Okay, now we have the token, we can check behaviours with the revoke interface.
4823
4824        // First, assert it is valid, similar to the introspect api.
4825        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4826        let intr_request = AccessTokenIntrospectRequest {
4827            token: oauth2_token.access_token.clone(),
4828            token_type_hint: None,
4829            client_post_auth: ClientPostAuth::default(),
4830        };
4831        let intr_response = idms_prox_read
4832            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4833            .expect("Failed to inspect token");
4834        eprintln!("👉  {intr_response:?}");
4835        assert!(intr_response.active);
4836        drop(idms_prox_read);
4837
4838        // First, the revoke needs basic auth. Provide incorrect auth, and we fail.
4839        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4840
4841        let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
4842
4843        let revoke_request = TokenRevokeRequest {
4844            token: oauth2_token.access_token.clone(),
4845            token_type_hint: None,
4846            client_post_auth: ClientPostAuth::default(),
4847        };
4848        let e = idms_prox_write
4849            .oauth2_token_revoke(&bad_client_authz, &revoke_request, ct)
4850            .unwrap_err();
4851        assert!(matches!(e, Oauth2Error::AuthenticationRequired));
4852        assert!(idms_prox_write.commit().is_ok());
4853
4854        // Now submit a non-existent/invalid token. Does not affect our tokens validity.
4855        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4856        let revoke_request = TokenRevokeRequest {
4857            token: "this is an invalid token, nothing will happen!".to_string(),
4858            token_type_hint: None,
4859            client_post_auth: ClientPostAuth::default(),
4860        };
4861        let e = idms_prox_write
4862            .oauth2_token_revoke(&client_authz, &revoke_request, ct)
4863            .unwrap_err();
4864        assert!(matches!(e, Oauth2Error::InvalidRequest));
4865        assert!(idms_prox_write.commit().is_ok());
4866
4867        // Check our token is still valid.
4868        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4869        let intr_response = idms_prox_read
4870            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4871            .expect("Failed to inspect token");
4872        assert!(intr_response.active);
4873        drop(idms_prox_read);
4874
4875        // Finally revoke it.
4876        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4877        let revoke_request = TokenRevokeRequest {
4878            token: oauth2_token.access_token.clone(),
4879            token_type_hint: None,
4880            client_post_auth: ClientPostAuth::default(),
4881        };
4882        assert!(idms_prox_write
4883            .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
4884            .is_ok());
4885        assert!(idms_prox_write.commit().is_ok());
4886
4887        // Assert it is now invalid.
4888        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4889        let intr_response = idms_prox_read
4890            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4891            .expect("Failed to inspect token");
4892
4893        assert!(!intr_response.active);
4894        drop(idms_prox_read);
4895
4896        // Force trim the session and wait for the grace window to pass. The token will be invalidated
4897        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4898        let filt = filter!(f_eq(
4899            Attribute::Uuid,
4900            PartialValue::Uuid(ident.get_uuid().unwrap())
4901        ));
4902        let mut work_set = idms_prox_write
4903            .qs_write
4904            .internal_search_writeable(&filt)
4905            .expect("Failed to perform internal search writeable");
4906        for (_, entry) in work_set.iter_mut() {
4907            let _ = entry.force_trim_ava(Attribute::OAuth2Session);
4908        }
4909        assert!(idms_prox_write
4910            .qs_write
4911            .internal_apply_writable(work_set)
4912            .is_ok());
4913
4914        assert!(idms_prox_write.commit().is_ok());
4915
4916        let mut idms_prox_read = idms.proxy_read().await.unwrap();
4917        // Grace window in effect.
4918        let intr_response = idms_prox_read
4919            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4920            .expect("Failed to inspect token");
4921        assert!(intr_response.active);
4922
4923        // Grace window passed, it will now be invalid.
4924        let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
4925        let intr_response = idms_prox_read
4926            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
4927            .expect("Failed to inspect token");
4928        assert!(!intr_response.active);
4929
4930        drop(idms_prox_read);
4931
4932        // A second invalidation of the token "does nothing".
4933        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4934        let revoke_request = TokenRevokeRequest {
4935            token: oauth2_token.access_token,
4936            token_type_hint: None,
4937            client_post_auth: ClientPostAuth::default(),
4938        };
4939        assert!(idms_prox_write
4940            .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
4941            .is_ok());
4942        assert!(idms_prox_write.commit().is_ok());
4943    }
4944
4945    #[idm_test]
4946    async fn test_idm_oauth2_session_cleanup_post_rs_delete(
4947        idms: &IdmServer,
4948        _idms_delayed: &mut IdmServerDelayed,
4949    ) {
4950        // First, setup to get a token.
4951        let ct = Duration::from_secs(TEST_CURRENT_TIME);
4952        let (secret, _uat, ident, _) =
4953            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
4954        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
4955
4956        let idms_prox_read = idms.proxy_read().await.unwrap();
4957
4958        // == Setup the authorisation request
4959        let pkce_secret = PkceS256Secret::default();
4960
4961        let consent_request = good_authorisation_request!(
4962            idms_prox_read,
4963            &ident,
4964            ct,
4965            pkce_secret.to_request(),
4966            OAUTH2_SCOPE_OPENID.to_string()
4967        );
4968
4969        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
4970            unreachable!();
4971        };
4972
4973        // == Manually submit the consent token to the permit for the permit_success
4974        drop(idms_prox_read);
4975        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
4976
4977        let permit_success = idms_prox_write
4978            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
4979            .expect("Failed to perform OAuth2 permit");
4980
4981        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
4982            code: permit_success.code,
4983            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
4984            code_verifier: Some(pkce_secret.to_verifier()),
4985        }
4986        .into();
4987
4988        let oauth2_token = idms_prox_write
4989            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
4990            .expect("Unable to exchange for OAuth2 token");
4991
4992        let access_token =
4993            JwsCompact::from_str(&oauth2_token.access_token).expect("Invalid Access Token");
4994
4995        let jws_verifier = JwsDangerReleaseWithoutVerify::default();
4996
4997        let reflected_token = jws_verifier
4998            .verify(&access_token)
4999            .unwrap()
5000            .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
5001            .expect("Failed to access internals of the refresh token");
5002
5003        let session_id = reflected_token.extensions.session_id;
5004
5005        assert!(idms_prox_write.commit().is_ok());
5006
5007        // Process it to ensure the record exists.
5008        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5009
5010        // Check it is now there
5011        let entry = idms_prox_write
5012            .qs_write
5013            .internal_search_uuid(UUID_TESTPERSON_1)
5014            .expect("failed");
5015        let valid = entry
5016            .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
5017            .map(|map| map.get(&session_id).is_some())
5018            .unwrap_or(false);
5019        assert!(valid);
5020
5021        // Delete the resource server.
5022
5023        let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
5024            Attribute::Name,
5025            PartialValue::new_iname("test_resource_server")
5026        )));
5027
5028        assert!(idms_prox_write.qs_write.delete(&de).is_ok());
5029
5030        // Assert the session is revoked. This is cleaned up as an artifact of the referential
5031        // integrity plugin. Remember, refint doesn't consider revoked sessions once they are
5032        // revoked.
5033        let entry = idms_prox_write
5034            .qs_write
5035            .internal_search_uuid(UUID_TESTPERSON_1)
5036            .expect("failed");
5037        let revoked = entry
5038            .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
5039            .and_then(|sessions| sessions.get(&session_id))
5040            .map(|session| matches!(session.state, SessionState::RevokedAt(_)))
5041            .unwrap_or(false);
5042        assert!(revoked);
5043
5044        assert!(idms_prox_write.commit().is_ok());
5045    }
5046
5047    #[idm_test]
5048    async fn test_idm_oauth2_authorisation_reject(
5049        idms: &IdmServer,
5050        _idms_delayed: &mut IdmServerDelayed,
5051    ) {
5052        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5053        let (_secret, _uat, ident, _) =
5054            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5055
5056        let ident2 = {
5057            let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5058            let account = idms_prox_write
5059                .target_to_account(UUID_IDM_ADMIN)
5060                .expect("account must exist");
5061            let session_id = uuid::Uuid::new_v4();
5062            let uat2 = account
5063                .to_userauthtoken(
5064                    session_id,
5065                    SessionScope::ReadWrite,
5066                    ct,
5067                    &ResolvedAccountPolicy::test_policy(),
5068                )
5069                .expect("Unable to create uat");
5070
5071            idms_prox_write
5072                .process_uat_to_identity(&uat2, ct, Source::Internal)
5073                .expect("Unable to process uat")
5074        };
5075
5076        let idms_prox_read = idms.proxy_read().await.unwrap();
5077        let redirect_uri = Url::parse("https://demo.example.com/oauth2/result").unwrap();
5078
5079        let pkce_secret = PkceS256Secret::default();
5080
5081        // Check reject behaviour
5082        let consent_request = good_authorisation_request!(
5083            idms_prox_read,
5084            &ident,
5085            ct,
5086            pkce_secret.to_request(),
5087            OAUTH2_SCOPE_OPENID.to_string()
5088        );
5089
5090        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5091            unreachable!();
5092        };
5093
5094        let reject_success = idms_prox_read
5095            .check_oauth2_authorise_reject(&ident, &consent_token, ct)
5096            .expect("Failed to perform OAuth2 reject");
5097
5098        assert_eq!(reject_success.redirect_uri, redirect_uri);
5099
5100        // Too much time past to reject
5101        let past_ct = Duration::from_secs(TEST_CURRENT_TIME + 301);
5102        assert!(
5103            idms_prox_read
5104                .check_oauth2_authorise_reject(&ident, &consent_token, past_ct)
5105                .unwrap_err()
5106                == OperationError::CryptographyError
5107        );
5108
5109        // Invalid consent token
5110        assert_eq!(
5111            idms_prox_read
5112                .check_oauth2_authorise_reject(&ident, "not a token", ct)
5113                .unwrap_err(),
5114            OperationError::CryptographyError
5115        );
5116
5117        // Wrong ident
5118        assert!(
5119            idms_prox_read
5120                .check_oauth2_authorise_reject(&ident2, &consent_token, ct)
5121                .unwrap_err()
5122                == OperationError::InvalidSessionState
5123        );
5124    }
5125
5126    #[idm_test]
5127    async fn test_idm_oauth2_rfc8414_metadata(
5128        idms: &IdmServer,
5129        _idms_delayed: &mut IdmServerDelayed,
5130    ) {
5131        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5132        let (_secret, _uat, _ident, _) =
5133            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5134
5135        let idms_prox_read = idms.proxy_read().await.unwrap();
5136
5137        // check the discovery end point works as we expect
5138        assert!(
5139            idms_prox_read
5140                .oauth2_rfc8414_metadata("nosuchclient")
5141                .unwrap_err()
5142                == OperationError::NoMatchingEntries
5143        );
5144
5145        let discovery = idms_prox_read
5146            .oauth2_rfc8414_metadata("test_resource_server")
5147            .expect("Failed to get discovery");
5148
5149        assert!(
5150            discovery.issuer
5151                == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5152                    .unwrap()
5153        );
5154
5155        assert!(
5156            discovery.authorization_endpoint
5157                == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
5158        );
5159
5160        assert!(
5161            discovery.token_endpoint
5162                == Url::parse(&format!(
5163                    "https://idm.example.com{}",
5164                    uri::OAUTH2_TOKEN_ENDPOINT
5165                ))
5166                .unwrap()
5167        );
5168
5169        assert!(
5170            discovery.jwks_uri
5171                == Some(
5172                    Url::parse(
5173                        "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
5174                    )
5175                    .unwrap()
5176                )
5177        );
5178
5179        assert!(discovery.registration_endpoint.is_none());
5180
5181        assert!(
5182            discovery.scopes_supported
5183                == Some(vec![
5184                    OAUTH2_SCOPE_GROUPS.to_string(),
5185                    OAUTH2_SCOPE_OPENID.to_string(),
5186                    OAUTH2_SCOPE_PROFILE.to_string(),
5187                    "supplement".to_string(),
5188                ])
5189        );
5190
5191        assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5192        assert_eq!(
5193            discovery.response_modes_supported,
5194            vec![ResponseMode::Query, ResponseMode::Fragment]
5195        );
5196        assert_eq!(
5197            discovery.grant_types_supported,
5198            vec![GrantType::AuthorisationCode, GrantType::TokenExchange]
5199        );
5200        assert!(
5201            discovery.token_endpoint_auth_methods_supported
5202                == vec![
5203                    TokenEndpointAuthMethod::ClientSecretBasic,
5204                    TokenEndpointAuthMethod::ClientSecretPost
5205                ]
5206        );
5207        assert!(discovery.service_documentation.is_some());
5208
5209        assert!(discovery.ui_locales_supported.is_none());
5210        assert!(discovery.op_policy_uri.is_none());
5211        assert!(discovery.op_tos_uri.is_none());
5212
5213        assert!(
5214            discovery.revocation_endpoint
5215                == Some(
5216                    Url::parse(&format!(
5217                        "https://idm.example.com{OAUTH2_TOKEN_REVOKE_ENDPOINT}"
5218                    ))
5219                    .unwrap()
5220                )
5221        );
5222        assert!(
5223            discovery.revocation_endpoint_auth_methods_supported
5224                == vec![
5225                    TokenEndpointAuthMethod::ClientSecretBasic,
5226                    TokenEndpointAuthMethod::ClientSecretPost
5227                ]
5228        );
5229
5230        assert!(
5231            discovery.introspection_endpoint
5232                == Some(
5233                    Url::parse(&format!(
5234                        "https://idm.example.com{}",
5235                        kanidm_proto::constants::uri::OAUTH2_TOKEN_INTROSPECT_ENDPOINT
5236                    ))
5237                    .unwrap()
5238                )
5239        );
5240        assert!(
5241            discovery.introspection_endpoint_auth_methods_supported
5242                == vec![
5243                    TokenEndpointAuthMethod::ClientSecretBasic,
5244                    TokenEndpointAuthMethod::ClientSecretPost
5245                ]
5246        );
5247        assert!(discovery
5248            .introspection_endpoint_auth_signing_alg_values_supported
5249            .is_none());
5250
5251        assert_eq!(
5252            discovery.code_challenge_methods_supported,
5253            vec![PkceAlg::S256]
5254        )
5255    }
5256
5257    #[idm_test]
5258    async fn test_idm_oauth2_openid_discovery(
5259        idms: &IdmServer,
5260        _idms_delayed: &mut IdmServerDelayed,
5261    ) {
5262        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5263        let (_secret, _uat, _ident, _) =
5264            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5265
5266        let idms_prox_read = idms.proxy_read().await.unwrap();
5267
5268        // check the discovery end point works as we expect
5269        assert!(
5270            idms_prox_read
5271                .oauth2_openid_discovery("nosuchclient")
5272                .unwrap_err()
5273                == OperationError::NoMatchingEntries
5274        );
5275
5276        assert!(
5277            idms_prox_read
5278                .oauth2_openid_publickey("nosuchclient")
5279                .unwrap_err()
5280                == OperationError::NoMatchingEntries
5281        );
5282
5283        let discovery = idms_prox_read
5284            .oauth2_openid_discovery("test_resource_server")
5285            .expect("Failed to get discovery");
5286
5287        let mut jwkset = idms_prox_read
5288            .oauth2_openid_publickey("test_resource_server")
5289            .expect("Failed to get public key");
5290
5291        let jwk = jwkset.keys.pop().expect("no such jwk");
5292
5293        match jwk {
5294            Jwk::EC { alg, use_, kid, .. } => {
5295                match (
5296                    alg.unwrap(),
5297                    &discovery.id_token_signing_alg_values_supported[0],
5298                ) {
5299                    (JwaAlg::ES256, IdTokenSignAlg::ES256) => {}
5300                    _ => panic!(),
5301                };
5302                assert_eq!(use_.unwrap(), JwkUse::Sig);
5303                assert!(kid.is_some())
5304            }
5305            _ => panic!(),
5306        };
5307
5308        assert!(
5309            discovery.issuer
5310                == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5311                    .unwrap()
5312        );
5313
5314        assert!(
5315            discovery.authorization_endpoint
5316                == Url::parse("https://idm.example.com/ui/oauth2").unwrap()
5317        );
5318
5319        assert!(
5320            discovery.token_endpoint == Url::parse("https://idm.example.com/oauth2/token").unwrap()
5321        );
5322
5323        assert!(
5324            discovery.userinfo_endpoint
5325                == Some(
5326                    Url::parse(
5327                        "https://idm.example.com/oauth2/openid/test_resource_server/userinfo"
5328                    )
5329                    .unwrap()
5330                )
5331        );
5332
5333        assert!(
5334            discovery.jwks_uri
5335                == Url::parse(
5336                    "https://idm.example.com/oauth2/openid/test_resource_server/public_key.jwk"
5337                )
5338                .unwrap()
5339        );
5340
5341        assert!(
5342            discovery.scopes_supported
5343                == Some(vec![
5344                    OAUTH2_SCOPE_GROUPS.to_string(),
5345                    OAUTH2_SCOPE_OPENID.to_string(),
5346                    OAUTH2_SCOPE_PROFILE.to_string(),
5347                    "supplement".to_string(),
5348                ])
5349        );
5350
5351        assert_eq!(discovery.response_types_supported, vec![ResponseType::Code]);
5352        assert_eq!(
5353            discovery.response_modes_supported,
5354            vec![ResponseMode::Query, ResponseMode::Fragment]
5355        );
5356        assert_eq!(
5357            discovery.grant_types_supported,
5358            vec![GrantType::AuthorisationCode, GrantType::TokenExchange]
5359        );
5360        assert_eq!(discovery.subject_types_supported, vec![SubjectType::Public]);
5361        assert_eq!(
5362            discovery.id_token_signing_alg_values_supported,
5363            vec![IdTokenSignAlg::ES256]
5364        );
5365        assert!(discovery.userinfo_signing_alg_values_supported.is_none());
5366        assert!(
5367            discovery.token_endpoint_auth_methods_supported
5368                == vec![
5369                    TokenEndpointAuthMethod::ClientSecretBasic,
5370                    TokenEndpointAuthMethod::ClientSecretPost
5371                ]
5372        );
5373        assert_eq!(
5374            discovery.display_values_supported,
5375            Some(vec![DisplayValue::Page])
5376        );
5377        assert_eq!(discovery.claim_types_supported, vec![ClaimType::Normal]);
5378        assert!(discovery.claims_supported.is_none());
5379        assert!(discovery.service_documentation.is_some());
5380
5381        assert!(discovery.registration_endpoint.is_none());
5382        assert!(discovery.acr_values_supported.is_none());
5383        assert!(discovery.id_token_encryption_alg_values_supported.is_none());
5384        assert!(discovery.id_token_encryption_enc_values_supported.is_none());
5385        assert!(discovery.userinfo_encryption_alg_values_supported.is_none());
5386        assert!(discovery.userinfo_encryption_enc_values_supported.is_none());
5387        assert!(discovery
5388            .request_object_signing_alg_values_supported
5389            .is_none());
5390        assert!(discovery
5391            .request_object_encryption_alg_values_supported
5392            .is_none());
5393        assert!(discovery
5394            .request_object_encryption_enc_values_supported
5395            .is_none());
5396        assert!(discovery
5397            .token_endpoint_auth_signing_alg_values_supported
5398            .is_none());
5399        assert!(discovery.claims_locales_supported.is_none());
5400        assert!(discovery.ui_locales_supported.is_none());
5401        assert!(discovery.op_policy_uri.is_none());
5402        assert!(discovery.op_tos_uri.is_none());
5403        assert!(!discovery.claims_parameter_supported);
5404        assert!(!discovery.request_uri_parameter_supported);
5405        assert!(!discovery.require_request_uri_registration);
5406        assert!(!discovery.request_parameter_supported);
5407        assert_eq!(
5408            discovery.code_challenge_methods_supported,
5409            vec![PkceAlg::S256]
5410        );
5411
5412        // Extensions
5413        assert!(
5414            discovery.revocation_endpoint
5415                == Some(
5416                    Url::parse(&format!(
5417                        "https://idm.example.com{OAUTH2_TOKEN_REVOKE_ENDPOINT}"
5418                    ))
5419                    .unwrap()
5420                )
5421        );
5422        assert!(
5423            discovery.revocation_endpoint_auth_methods_supported
5424                == vec![
5425                    TokenEndpointAuthMethod::ClientSecretBasic,
5426                    TokenEndpointAuthMethod::ClientSecretPost
5427                ]
5428        );
5429
5430        assert!(
5431            discovery.introspection_endpoint
5432                == Some(
5433                    Url::parse(&format!(
5434                        "https://idm.example.com{OAUTH2_TOKEN_INTROSPECT_ENDPOINT}"
5435                    ))
5436                    .unwrap()
5437                )
5438        );
5439        assert!(
5440            discovery.introspection_endpoint_auth_methods_supported
5441                == vec![
5442                    TokenEndpointAuthMethod::ClientSecretBasic,
5443                    TokenEndpointAuthMethod::ClientSecretPost
5444                ]
5445        );
5446        assert!(discovery
5447            .introspection_endpoint_auth_signing_alg_values_supported
5448            .is_none());
5449    }
5450
5451    #[idm_test]
5452    async fn test_idm_oauth2_openid_extensions(
5453        idms: &IdmServer,
5454        _idms_delayed: &mut IdmServerDelayed,
5455    ) {
5456        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5457        let (secret, _uat, ident, _) =
5458            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
5459        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5460
5461        let idms_prox_read = idms.proxy_read().await.unwrap();
5462
5463        let pkce_secret = PkceS256Secret::default();
5464
5465        let consent_request = good_authorisation_request!(
5466            idms_prox_read,
5467            &ident,
5468            ct,
5469            pkce_secret.to_request(),
5470            format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_PROFILE}")
5471        );
5472
5473        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5474            unreachable!();
5475        };
5476
5477        // == Manually submit the consent token to the permit for the permit_success
5478        drop(idms_prox_read);
5479        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5480
5481        let permit_success = idms_prox_write
5482            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5483            .expect("Failed to perform OAuth2 permit");
5484
5485        // == Submit the token exchange code.
5486        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5487            code: permit_success.code,
5488            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5489            code_verifier: Some(pkce_secret.to_verifier()),
5490        }
5491        .into();
5492
5493        let token_response = idms_prox_write
5494            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5495            .expect("Failed to perform OAuth2 token exchange");
5496
5497        // 🎉 We got a token!
5498        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
5499
5500        let id_token = token_response.id_token.expect("No id_token in response!");
5501
5502        let access_token =
5503            JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5504
5505        let refresh_token = token_response
5506            .refresh_token
5507            .as_ref()
5508            .expect("no refresh token was issued")
5509            .clone();
5510
5511        // Get the read txn for inspecting the tokens
5512        assert!(idms_prox_write.commit().is_ok());
5513
5514        let mut idms_prox_read = idms.proxy_read().await.unwrap();
5515
5516        let mut jwkset = idms_prox_read
5517            .oauth2_openid_publickey("test_resource_server")
5518            .expect("Failed to get public key");
5519
5520        let public_jwk = jwkset.keys.pop().expect("no such jwk");
5521
5522        let jws_validator =
5523            JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5524
5525        let oidc_unverified =
5526            OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5527
5528        let iat = ct.as_secs() as i64;
5529
5530        let oidc = jws_validator
5531            .verify(&oidc_unverified)
5532            .unwrap()
5533            .verify_exp(iat)
5534            .expect("Failed to verify oidc");
5535
5536        // Are the id_token values what we expect?
5537        assert!(
5538            oidc.iss
5539                == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
5540                    .unwrap()
5541        );
5542        assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
5543        assert_eq!(oidc.aud, "test_resource_server");
5544        assert_eq!(oidc.iat, iat);
5545        assert_eq!(oidc.nbf, Some(iat));
5546        // Previously this was the auth session but it's now inline with the access token expiry.
5547        assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
5548        assert!(oidc.auth_time.is_none());
5549        // Is nonce correctly passed through?
5550        assert_eq!(oidc.nonce, Some("abcdef".to_string()));
5551        assert!(oidc.at_hash.is_none());
5552        assert!(oidc.acr.is_none());
5553        assert!(oidc.amr.is_none());
5554        assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
5555        assert!(oidc.jti.is_some());
5556        if let Some(jti) = &oidc.jti {
5557            assert!(Uuid::from_str(jti).is_ok());
5558        }
5559        assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
5560        assert_eq!(
5561            oidc.s_claims.preferred_username,
5562            Some("testperson1@example.com".to_string())
5563        );
5564        assert!(
5565            oidc.s_claims.scopes
5566                == vec![
5567                    OAUTH2_SCOPE_OPENID.to_string(),
5568                    OAUTH2_SCOPE_PROFILE.to_string(),
5569                    "supplement".to_string()
5570                ]
5571        );
5572        assert!(oidc.claims.is_empty());
5573        // Does our access token work with the userinfo endpoint?
5574        // Do the id_token details line up to the userinfo?
5575        let userinfo = idms_prox_read
5576            .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5577            .expect("failed to get userinfo");
5578
5579        assert_eq!(oidc.iss, userinfo.iss);
5580        assert_eq!(oidc.sub, userinfo.sub);
5581        assert_eq!(oidc.aud, userinfo.aud);
5582        assert_eq!(oidc.iat, userinfo.iat);
5583        assert_eq!(oidc.nbf, userinfo.nbf);
5584        assert_eq!(oidc.exp, userinfo.exp);
5585        assert!(userinfo.auth_time.is_none());
5586        assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5587        assert!(userinfo.at_hash.is_none());
5588        assert!(userinfo.acr.is_none());
5589        assert_eq!(oidc.amr, userinfo.amr);
5590        assert_eq!(oidc.azp, userinfo.azp);
5591        assert!(userinfo.jti.is_some());
5592        if let Some(jti) = &userinfo.jti {
5593            assert!(Uuid::from_str(jti).is_ok());
5594        }
5595        assert_eq!(oidc.s_claims, userinfo.s_claims);
5596        assert!(userinfo.claims.is_empty());
5597
5598        drop(idms_prox_read);
5599
5600        // Importantly, we need to persist the nonce through access/refresh token operations
5601        // because some clients like the rust openidconnect library require it always for claim
5602        // verification.
5603        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5604
5605        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
5606            refresh_token,
5607            scope: None,
5608        }
5609        .into();
5610
5611        let token_response = idms_prox_write
5612            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5613            .expect("Unable to exchange for OAuth2 token");
5614
5615        let access_token =
5616            JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5617
5618        assert!(idms_prox_write.commit().is_ok());
5619
5620        // Okay, refresh done, lets check it.
5621        let mut idms_prox_read = idms.proxy_read().await.unwrap();
5622
5623        let userinfo = idms_prox_read
5624            .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5625            .expect("failed to get userinfo");
5626
5627        assert_eq!(oidc.iss, userinfo.iss);
5628        assert_eq!(oidc.sub, userinfo.sub);
5629        assert_eq!(oidc.aud, userinfo.aud);
5630        assert_eq!(oidc.iat, userinfo.iat);
5631        assert_eq!(oidc.nbf, userinfo.nbf);
5632        assert_eq!(oidc.exp, userinfo.exp);
5633        assert!(userinfo.auth_time.is_none());
5634        assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
5635        assert!(userinfo.at_hash.is_none());
5636        assert!(userinfo.acr.is_none());
5637        assert_eq!(oidc.amr, userinfo.amr);
5638        assert_eq!(oidc.azp, userinfo.azp);
5639        assert!(userinfo.jti.is_some());
5640        if let Some(jti) = &userinfo.jti {
5641            assert!(Uuid::from_str(jti).is_ok());
5642        }
5643        assert_eq!(oidc.s_claims, userinfo.s_claims);
5644        assert!(userinfo.claims.is_empty());
5645    }
5646
5647    #[idm_test]
5648    async fn test_idm_oauth2_openid_short_username(
5649        idms: &IdmServer,
5650        _idms_delayed: &mut IdmServerDelayed,
5651    ) {
5652        // we run the same test as test_idm_oauth2_openid_extensions()
5653        // but change the preferred_username setting on the RS
5654        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5655        let (secret, _uat, ident, _) =
5656            setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5657        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5658
5659        let idms_prox_read = idms.proxy_read().await.unwrap();
5660
5661        let pkce_secret = PkceS256Secret::default();
5662
5663        let consent_request = good_authorisation_request!(
5664            idms_prox_read,
5665            &ident,
5666            ct,
5667            pkce_secret.to_request(),
5668            OAUTH2_SCOPE_OPENID.to_string()
5669        );
5670
5671        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5672            unreachable!();
5673        };
5674
5675        // == Manually submit the consent token to the permit for the permit_success
5676        drop(idms_prox_read);
5677        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5678
5679        let permit_success = idms_prox_write
5680            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5681            .expect("Failed to perform OAuth2 permit");
5682
5683        // == Submit the token exchange code.
5684        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5685            code: permit_success.code,
5686            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5687            code_verifier: Some(pkce_secret.to_verifier()),
5688        }
5689        .into();
5690
5691        let token_response = idms_prox_write
5692            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5693            .expect("Failed to perform OAuth2 token exchange");
5694
5695        let id_token = token_response.id_token.expect("No id_token in response!");
5696        let access_token =
5697            JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5698
5699        assert!(idms_prox_write.commit().is_ok());
5700        let mut idms_prox_read = idms.proxy_read().await.unwrap();
5701
5702        let mut jwkset = idms_prox_read
5703            .oauth2_openid_publickey("test_resource_server")
5704            .expect("Failed to get public key");
5705        let public_jwk = jwkset.keys.pop().expect("no such jwk");
5706
5707        let jws_validator =
5708            JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5709
5710        let oidc_unverified =
5711            OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5712
5713        let iat = ct.as_secs() as i64;
5714
5715        let oidc = jws_validator
5716            .verify(&oidc_unverified)
5717            .unwrap()
5718            .verify_exp(iat)
5719            .expect("Failed to verify oidc");
5720
5721        // Do we have the short username in the token claims?
5722        assert_eq!(
5723            oidc.s_claims.preferred_username,
5724            Some("testperson1".to_string())
5725        );
5726        // Do the id_token details line up to the userinfo?
5727        let userinfo = idms_prox_read
5728            .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5729            .expect("failed to get userinfo");
5730
5731        assert_eq!(oidc.s_claims, userinfo.s_claims);
5732    }
5733
5734    #[idm_test]
5735    async fn test_idm_oauth2_openid_group_claims(
5736        idms: &IdmServer,
5737        _idms_delayed: &mut IdmServerDelayed,
5738    ) {
5739        // we run the same test as test_idm_oauth2_openid_extensions()
5740        // but change the preferred_username setting on the RS
5741        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5742        let (secret, _uat, ident, _) =
5743            setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5744
5745        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5746
5747        let token_response = perform_oauth2_exchange(
5748            idms,
5749            &ident,
5750            ct,
5751            client_authz,
5752            format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_GROUPS}"),
5753        )
5754        .await;
5755
5756        let id_token = token_response.id_token.expect("No id_token in response!");
5757        let access_token =
5758            JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5759
5760        let oidc = validate_id_token(idms, ct, &id_token).await;
5761
5762        // does our id_token contain the expected groups?
5763        assert!(oidc.claims.contains_key("groups"));
5764
5765        assert!(oidc
5766            .claims
5767            .get("groups")
5768            .expect("unable to find key")
5769            .as_array()
5770            .unwrap()
5771            .contains(&serde_json::json!(STR_UUID_IDM_ALL_ACCOUNTS)));
5772
5773        let mut idms_prox_read = idms.proxy_read().await.unwrap();
5774
5775        // Do the id_token details line up to the userinfo?
5776        let userinfo = idms_prox_read
5777            .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5778            .expect("failed to get userinfo");
5779
5780        // does the userinfo endpoint provide the same groups?
5781        assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
5782    }
5783
5784    #[idm_test]
5785    async fn test_idm_oauth2_openid_group_extended_claims(
5786        idms: &IdmServer,
5787        _idms_delayed: &mut IdmServerDelayed,
5788    ) {
5789        // we run the same test as test_idm_oauth2_openid_extensions()
5790        // but change the preferred_username setting on the RS
5791        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5792        let (secret, _uat, ident, oauth2_client_uuid) =
5793            setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5794
5795        // Modify the oauth2 client to have different scope maps.
5796        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5797
5798        let modlist = ModifyList::new_list(vec![
5799            Modify::Removed(
5800                Attribute::OAuth2RsScopeMap,
5801                PartialValue::Refer(UUID_TESTGROUP),
5802            ),
5803            Modify::Present(
5804                Attribute::OAuth2RsScopeMap,
5805                Value::new_oauthscopemap(
5806                    UUID_TESTGROUP,
5807                    btreeset![OAUTH2_SCOPE_GROUPS_NAME.to_string()],
5808                )
5809                .expect("invalid oauthscope"),
5810            ),
5811        ]);
5812
5813        idms_prox_write
5814            .qs_write
5815            .internal_modify_uuid(oauth2_client_uuid, &modlist)
5816            .expect("Failed to modify scopes");
5817
5818        idms_prox_write.commit().expect("failed to commit");
5819
5820        // Now actually do the test.
5821        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5822
5823        let token_response = perform_oauth2_exchange(
5824            idms,
5825            &ident,
5826            ct,
5827            client_authz,
5828            format!("{OAUTH2_SCOPE_OPENID} {OAUTH2_SCOPE_GROUPS_NAME}"),
5829        )
5830        .await;
5831
5832        let id_token = token_response.id_token.expect("No id_token in response!");
5833        let access_token =
5834            JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5835
5836        let oidc = validate_id_token(idms, ct, &id_token).await;
5837
5838        // does our id_token contain the expected groups?
5839        assert!(oidc.claims.contains_key("groups"));
5840
5841        assert!(oidc
5842            .claims
5843            .get("groups")
5844            .expect("unable to find key")
5845            .as_array()
5846            .unwrap()
5847            .contains(&serde_json::json!("testgroup")));
5848
5849        let mut idms_prox_read = idms.proxy_read().await.unwrap();
5850
5851        // Do the id_token details line up to the userinfo?
5852        let userinfo = idms_prox_read
5853            .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5854            .expect("failed to get userinfo");
5855
5856        // does the userinfo endpoint provide the same groups?
5857        assert_eq!(oidc.claims.get("groups"), userinfo.claims.get("groups"));
5858    }
5859
5860    #[idm_test]
5861    async fn test_idm_oauth2_openid_ssh_publickey_claim(
5862        idms: &IdmServer,
5863        _idms_delayed: &mut IdmServerDelayed,
5864    ) {
5865        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5866        let (secret, _uat, ident, client_uuid) =
5867            setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
5868
5869        // Extra setup for our test - add the correct claim and give an ssh publickey
5870        // to our testperson
5871        const ECDSA_SSH_PUBLIC_KEY: &str = "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAGyIY7o3BtOzRiJ9vvjj96bRImwmyy5GvFSIUPlK00HitiAWGhiO1jGZKmK7220Oe4rqU3uAwA00a0758UODs+0OQHLMDRtl81lzPrVSdrYEDldxH9+a86dBZhdm0e15+ODDts2LHUknsJCRRldO4o9R9VrohlF7cbyBlnhJQrR4S+Oag== william@amethyst";
5872        let ssh_pubkey = SshPublicKey::from_string(ECDSA_SSH_PUBLIC_KEY).unwrap();
5873
5874        let scope_set = BTreeSet::from([OAUTH2_SCOPE_SSH_PUBLICKEYS.to_string()]);
5875
5876        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5877
5878        idms_prox_write
5879            .qs_write
5880            .internal_batch_modify(
5881                [
5882                    (
5883                        UUID_TESTPERSON_1,
5884                        ModifyList::new_set(
5885                            Attribute::SshPublicKey,
5886                            ValueSetSshKey::new("label".to_string(), ssh_pubkey),
5887                        ),
5888                    ),
5889                    (
5890                        client_uuid,
5891                        ModifyList::new_set(
5892                            Attribute::OAuth2RsSupScopeMap,
5893                            ValueSetOauthScopeMap::new(UUID_IDM_ALL_ACCOUNTS, scope_set),
5894                        ),
5895                    ),
5896                ]
5897                .into_iter(),
5898            )
5899            .expect("Failed to modify test entries");
5900
5901        assert!(idms_prox_write.commit().is_ok());
5902
5903        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
5904
5905        let idms_prox_read = idms.proxy_read().await.unwrap();
5906
5907        let pkce_secret = PkceS256Secret::default();
5908
5909        let consent_request = good_authorisation_request!(
5910            idms_prox_read,
5911            &ident,
5912            ct,
5913            pkce_secret.to_request(),
5914            "openid groups".to_string()
5915        );
5916
5917        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
5918            unreachable!();
5919        };
5920
5921        // == Manually submit the consent token to the permit for the permit_success
5922        drop(idms_prox_read);
5923        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
5924
5925        let permit_success = idms_prox_write
5926            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
5927            .expect("Failed to perform OAuth2 permit");
5928
5929        // == Submit the token exchange code.
5930        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
5931            code: permit_success.code,
5932            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
5933            code_verifier: Some(pkce_secret.to_verifier()),
5934        }
5935        .into();
5936
5937        let token_response = idms_prox_write
5938            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
5939            .expect("Failed to perform OAuth2 token exchange");
5940
5941        let id_token = token_response.id_token.expect("No id_token in response!");
5942        let access_token =
5943            JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
5944
5945        assert!(idms_prox_write.commit().is_ok());
5946        let mut idms_prox_read = idms.proxy_read().await.unwrap();
5947
5948        let mut jwkset = idms_prox_read
5949            .oauth2_openid_publickey("test_resource_server")
5950            .expect("Failed to get public key");
5951        let public_jwk = jwkset.keys.pop().expect("no such jwk");
5952
5953        let jws_validator =
5954            JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
5955
5956        let oidc_unverified =
5957            OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
5958
5959        let iat = ct.as_secs() as i64;
5960
5961        let oidc = jws_validator
5962            .verify(&oidc_unverified)
5963            .unwrap()
5964            .verify_exp(iat)
5965            .expect("Failed to verify oidc");
5966
5967        // does our id_token contain the expected groups?
5968        assert!(oidc.claims.contains_key(OAUTH2_SCOPE_SSH_PUBLICKEYS));
5969
5970        assert!(oidc
5971            .claims
5972            .get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
5973            .expect("unable to find key")
5974            .as_array()
5975            .unwrap()
5976            .contains(&serde_json::json!(ECDSA_SSH_PUBLIC_KEY)));
5977
5978        // Do the id_token details line up to the userinfo?
5979        let userinfo = idms_prox_read
5980            .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
5981            .expect("failed to get userinfo");
5982
5983        // does the userinfo endpoint provide the same groups?
5984        assert_eq!(
5985            oidc.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS),
5986            userinfo.claims.get(OAUTH2_SCOPE_SSH_PUBLICKEYS)
5987        );
5988    }
5989
5990    //  Check insecure pkce behaviour.
5991    #[idm_test]
5992    async fn test_idm_oauth2_insecure_pkce(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
5993        let ct = Duration::from_secs(TEST_CURRENT_TIME);
5994        let (_secret, _uat, ident, _) =
5995            setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
5996
5997        let idms_prox_read = idms.proxy_read().await.unwrap();
5998
5999        // == Setup the authorisation request
6000        let pkce_secret = PkceS256Secret::default();
6001
6002        // Even in disable pkce mode, we will allow pkce
6003        let _consent_request = good_authorisation_request!(
6004            idms_prox_read,
6005            &ident,
6006            ct,
6007            pkce_secret.to_request(),
6008            OAUTH2_SCOPE_OPENID.to_string()
6009        );
6010
6011        // Check we allow none.
6012        let auth_req = AuthorisationRequest {
6013            response_type: ResponseType::Code,
6014            response_mode: None,
6015            client_id: "test_resource_server".to_string(),
6016            state: Some("123".to_string()),
6017            pkce_request: None,
6018            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6019            scope: btreeset![OAUTH2_SCOPE_GROUPS.to_string()],
6020            nonce: Some("abcdef".to_string()),
6021            oidc_ext: Default::default(),
6022            max_age: None,
6023            unknown_keys: Default::default(),
6024        };
6025
6026        idms_prox_read
6027            .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6028            .expect("Oauth2 authorisation failed");
6029    }
6030
6031    #[idm_test]
6032    async fn test_idm_oauth2_webfinger(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
6033        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6034        let (_secret, _uat, _ident, _) =
6035            setup_oauth2_resource_server_basic(idms, ct, true, false, true).await;
6036        let mut idms_prox_read = idms.proxy_read().await.unwrap();
6037
6038        let user = "testperson1@example.com";
6039
6040        let webfinger = idms_prox_read
6041            .oauth2_openid_webfinger("test_resource_server", user)
6042            .expect("Failed to get webfinger");
6043
6044        assert_eq!(webfinger.subject, user);
6045        assert_eq!(webfinger.links.len(), 1);
6046
6047        let link = &webfinger.links[0];
6048        assert_eq!(link.rel, "http://openid.net/specs/connect/1.0/issuer");
6049        assert_eq!(
6050            link.href,
6051            "https://idm.example.com/oauth2/openid/test_resource_server"
6052        );
6053
6054        let failed_webfinger = idms_prox_read
6055            .oauth2_openid_webfinger("test_resource_server", "someone@another.domain");
6056        assert!(failed_webfinger.is_err());
6057    }
6058
6059    #[idm_test]
6060    async fn test_idm_oauth2_openid_legacy_crypto(
6061        idms: &IdmServer,
6062        _idms_delayed: &mut IdmServerDelayed,
6063    ) {
6064        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6065        let (secret, _uat, ident, _) =
6066            setup_oauth2_resource_server_basic(idms, ct, false, true, false).await;
6067        let idms_prox_read = idms.proxy_read().await.unwrap();
6068        // The public key url should offer an rs key
6069        // discovery should offer RS256
6070        let discovery = idms_prox_read
6071            .oauth2_openid_discovery("test_resource_server")
6072            .expect("Failed to get discovery");
6073
6074        let mut jwkset = idms_prox_read
6075            .oauth2_openid_publickey("test_resource_server")
6076            .expect("Failed to get public key");
6077
6078        let jwk = jwkset.keys.pop().expect("no such jwk");
6079        let public_jwk = jwk.clone();
6080
6081        match jwk {
6082            Jwk::RSA { alg, use_, kid, .. } => {
6083                match (
6084                    alg.unwrap(),
6085                    &discovery.id_token_signing_alg_values_supported[0],
6086                ) {
6087                    (JwaAlg::RS256, IdTokenSignAlg::RS256) => {}
6088                    _ => panic!(),
6089                };
6090                assert_eq!(use_.unwrap(), JwkUse::Sig);
6091                assert!(kid.is_some());
6092            }
6093            _ => panic!(),
6094        };
6095
6096        // Check that the id_token is signed with the correct key.
6097        let pkce_secret = PkceS256Secret::default();
6098
6099        let consent_request = good_authorisation_request!(
6100            idms_prox_read,
6101            &ident,
6102            ct,
6103            pkce_secret.to_request(),
6104            OAUTH2_SCOPE_OPENID.to_string()
6105        );
6106
6107        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6108            unreachable!();
6109        };
6110
6111        // == Manually submit the consent token to the permit for the permit_success
6112        drop(idms_prox_read);
6113        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6114
6115        let permit_success = idms_prox_write
6116            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6117            .expect("Failed to perform OAuth2 permit");
6118
6119        // == Submit the token exchange code.
6120        let token_req = AccessTokenRequest {
6121            grant_type: GrantTypeReq::AuthorizationCode {
6122                code: permit_success.code,
6123                redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6124                code_verifier: Some(pkce_secret.to_verifier()),
6125            },
6126
6127            client_post_auth: ClientPostAuth {
6128                client_id: Some("test_resource_server".to_string()),
6129                client_secret: Some(secret),
6130            },
6131        };
6132
6133        let token_response = idms_prox_write
6134            .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
6135            .expect("Failed to perform OAuth2 token exchange");
6136
6137        // 🎉 We got a token!
6138        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
6139        let id_token = token_response.id_token.expect("No id_token in response!");
6140
6141        let jws_validator =
6142            JwsRs256Verifier::try_from(&public_jwk).expect("failed to build validator");
6143
6144        let oidc_unverified =
6145            OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
6146
6147        let iat = ct.as_secs() as i64;
6148
6149        let oidc = jws_validator
6150            .verify(&oidc_unverified)
6151            .unwrap()
6152            .verify_exp(iat)
6153            .expect("Failed to verify oidc");
6154
6155        assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
6156
6157        assert!(idms_prox_write.commit().is_ok());
6158    }
6159
6160    #[idm_test]
6161    async fn test_idm_oauth2_consent_granted_and_changed_workflow(
6162        idms: &IdmServer,
6163        _idms_delayed: &mut IdmServerDelayed,
6164    ) {
6165        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6166        let (_secret, uat, ident, _) =
6167            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6168
6169        let idms_prox_read = idms.proxy_read().await.unwrap();
6170
6171        let pkce_secret = PkceS256Secret::default();
6172
6173        let consent_request = good_authorisation_request!(
6174            idms_prox_read,
6175            &ident,
6176            ct,
6177            pkce_secret.to_request(),
6178            OAUTH2_SCOPE_OPENID.to_string()
6179        );
6180
6181        // Should be in the consent phase;
6182        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6183            unreachable!();
6184        };
6185
6186        // == Manually submit the consent token to the permit for the permit_success
6187        drop(idms_prox_read);
6188        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6189
6190        let _permit_success = idms_prox_write
6191            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6192            .expect("Failed to perform OAuth2 permit");
6193
6194        assert!(idms_prox_write.commit().is_ok());
6195
6196        // == Now try the authorise again, should be in the permitted state.
6197        let mut idms_prox_read = idms.proxy_read().await.unwrap();
6198
6199        // We need to reload our identity
6200        let ident = idms_prox_read
6201            .process_uat_to_identity(&uat, ct, Source::Internal)
6202            .expect("Unable to process uat");
6203
6204        let pkce_secret = PkceS256Secret::default();
6205
6206        let consent_request = good_authorisation_request!(
6207            idms_prox_read,
6208            &ident,
6209            ct,
6210            pkce_secret.to_request(),
6211            OAUTH2_SCOPE_OPENID.to_string()
6212        );
6213
6214        // Should be in the consent phase;
6215        let AuthoriseResponse::Permitted(_permit_success) = consent_request else {
6216            unreachable!();
6217        };
6218
6219        drop(idms_prox_read);
6220
6221        // Great! Now change the scopes on the OAuth2 instance, this revokes the permit.
6222        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6223
6224        let me_extend_scopes = ModifyEvent::new_internal_invalid(
6225            filter!(f_eq(
6226                Attribute::Name,
6227                PartialValue::new_iname("test_resource_server")
6228            )),
6229            ModifyList::new_list(vec![Modify::Present(
6230                Attribute::OAuth2RsScopeMap,
6231                Value::new_oauthscopemap(
6232                    UUID_IDM_ALL_ACCOUNTS,
6233                    btreeset![
6234                        OAUTH2_SCOPE_EMAIL.to_string(),
6235                        OAUTH2_SCOPE_PROFILE.to_string(),
6236                        OAUTH2_SCOPE_OPENID.to_string()
6237                    ],
6238                )
6239                .expect("invalid oauthscope"),
6240            )]),
6241        );
6242
6243        assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
6244        assert!(idms_prox_write.commit().is_ok());
6245
6246        // And do the workflow once more to see if we need to consent again.
6247
6248        let mut idms_prox_read = idms.proxy_read().await.unwrap();
6249
6250        // We need to reload our identity
6251        let ident = idms_prox_read
6252            .process_uat_to_identity(&uat, ct, Source::Internal)
6253            .expect("Unable to process uat");
6254
6255        let pkce_secret = PkceS256Secret::default();
6256
6257        let auth_req = AuthorisationRequest {
6258            response_type: ResponseType::Code,
6259            response_mode: None,
6260            client_id: "test_resource_server".to_string(),
6261            state: Some("123".to_string()),
6262            pkce_request: Some(pkce_secret.to_request()),
6263            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6264            scope: btreeset![
6265                "openid".to_string(),
6266                "email".to_string(),
6267                "profile".to_string()
6268            ],
6269            nonce: Some("abcdef".to_string()),
6270            oidc_ext: Default::default(),
6271            max_age: None,
6272            unknown_keys: Default::default(),
6273        };
6274
6275        let consent_request = idms_prox_read
6276            .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6277            .expect("Oauth2 authorisation failed");
6278
6279        // Should be in the consent phase;
6280        let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
6281            unreachable!();
6282        };
6283
6284        drop(idms_prox_read);
6285
6286        // Success! We had to consent again due to the change :)
6287
6288        // Now change the supplemental scopes on the OAuth2 instance, this revokes the permit.
6289        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6290
6291        let me_extend_scopes = ModifyEvent::new_internal_invalid(
6292            filter!(f_eq(
6293                Attribute::Name,
6294                PartialValue::new_iname("test_resource_server")
6295            )),
6296            ModifyList::new_list(vec![Modify::Present(
6297                Attribute::OAuth2RsSupScopeMap,
6298                Value::new_oauthscopemap(UUID_IDM_ALL_ACCOUNTS, btreeset!["newscope".to_string()])
6299                    .expect("invalid oauthscope"),
6300            )]),
6301        );
6302
6303        assert!(idms_prox_write.qs_write.modify(&me_extend_scopes).is_ok());
6304        assert!(idms_prox_write.commit().is_ok());
6305
6306        // And do the workflow once more to see if we need to consent again.
6307
6308        let mut idms_prox_read = idms.proxy_read().await.unwrap();
6309
6310        // We need to reload our identity
6311        let ident = idms_prox_read
6312            .process_uat_to_identity(&uat, ct, Source::Internal)
6313            .expect("Unable to process uat");
6314
6315        let pkce_secret = PkceS256Secret::default();
6316
6317        let auth_req = AuthorisationRequest {
6318            response_type: ResponseType::Code,
6319            response_mode: None,
6320            client_id: "test_resource_server".to_string(),
6321            state: Some("123".to_string()),
6322            pkce_request: Some(pkce_secret.to_request()),
6323            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6324            // Note the scope isn't requested here!
6325            scope: btreeset![
6326                "openid".to_string(),
6327                "email".to_string(),
6328                "profile".to_string()
6329            ],
6330            nonce: Some("abcdef".to_string()),
6331            oidc_ext: Default::default(),
6332            max_age: None,
6333            unknown_keys: Default::default(),
6334        };
6335
6336        let consent_request = idms_prox_read
6337            .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6338            .expect("Oauth2 authorisation failed");
6339
6340        // Should be present in the consent phase however!
6341        let _consent_token = if let AuthoriseResponse::ConsentRequested {
6342            consent_token,
6343            scopes,
6344            ..
6345        } = consent_request
6346        {
6347            assert!(scopes.contains("newscope"));
6348            consent_token
6349        } else {
6350            unreachable!();
6351        };
6352    }
6353
6354    #[idm_test]
6355    async fn test_idm_oauth2_consent_granted_refint_cleanup_on_delete(
6356        idms: &IdmServer,
6357        _idms_delayed: &mut IdmServerDelayed,
6358    ) {
6359        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6360        let (_secret, uat, ident, o2rs_uuid) =
6361            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6362
6363        // Assert there are no consent maps yet.
6364        assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
6365
6366        let idms_prox_read = idms.proxy_read().await.unwrap();
6367
6368        let pkce_secret = PkceS256Secret::default();
6369        let consent_request = good_authorisation_request!(
6370            idms_prox_read,
6371            &ident,
6372            ct,
6373            pkce_secret.to_request(),
6374            OAUTH2_SCOPE_OPENID.to_string()
6375        );
6376
6377        // Should be in the consent phase;
6378        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6379            unreachable!();
6380        };
6381
6382        // == Manually submit the consent token to the permit for the permit_success
6383        drop(idms_prox_read);
6384        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6385
6386        let _permit_success = idms_prox_write
6387            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6388            .expect("Failed to perform OAuth2 permit");
6389
6390        let ident = idms_prox_write
6391            .process_uat_to_identity(&uat, ct, Source::Internal)
6392            .expect("Unable to process uat");
6393
6394        // Assert that the ident now has the consents.
6395        assert!(
6396            ident.get_oauth2_consent_scopes(o2rs_uuid)
6397                == Some(&btreeset![
6398                    OAUTH2_SCOPE_OPENID.to_string(),
6399                    "supplement".to_string()
6400                ])
6401        );
6402
6403        // Now trigger the delete of the RS
6404        let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
6405            Attribute::Name,
6406            PartialValue::new_iname("test_resource_server")
6407        )));
6408
6409        assert!(idms_prox_write.qs_write.delete(&de).is_ok());
6410        // Assert the consent maps are gone.
6411        let ident = idms_prox_write
6412            .process_uat_to_identity(&uat, ct, Source::Internal)
6413            .expect("Unable to process uat");
6414        dbg!(&o2rs_uuid);
6415        dbg!(&ident);
6416        let consent_scopes = ident.get_oauth2_consent_scopes(o2rs_uuid);
6417        dbg!(consent_scopes);
6418        assert!(consent_scopes.is_none());
6419
6420        assert!(idms_prox_write.commit().is_ok());
6421    }
6422
6423    // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-4.8
6424    //
6425    // It was reported we were vulnerable to this attack, but that isn't the case. First
6426    // this attack relies on stripping the *code_challenge* from the internals of the returned
6427    // code exchange token. This isn't possible due to our use of encryption of the code exchange
6428    // token. If that code challenge *could* be removed, then the attacker could use the code exchange
6429    // with no verifier or an incorrect verifier.
6430    //
6431    // Due to the logic in our server, if a code exchange contains a code challenge we always enforce
6432    // it is correctly used!
6433    //
6434    // This left a single odd case where if a client did an authorisation request without a pkce
6435    // verifier, but then a verifier was submitted during the code exchange, that the server would
6436    // *ignore* the verifier parameter. In this case, no stripping of the code challenge was done,
6437    // and the client could have simply also submitted *no* verifier anyway. It could be that
6438    // an attacker could gain a code exchange with no code challenge and then force a victim to
6439    // exchange that code exchange with out the verifier, but I'm not sure what damage that would
6440    // lead to? Regardless, we test for and close off that possible hole in this test.
6441    //
6442    #[idm_test]
6443    async fn test_idm_oauth2_1076_pkce_downgrade(
6444        idms: &IdmServer,
6445        _idms_delayed: &mut IdmServerDelayed,
6446    ) {
6447        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6448        // Enable pkce is set to FALSE
6449        let (secret, _uat, ident, _) =
6450            setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6451
6452        let idms_prox_read = idms.proxy_read().await.unwrap();
6453
6454        // Get an ident/uat for now.
6455
6456        // == Setup the authorisation request
6457        // We attempt pkce even though the rs is set to not support pkce.
6458        let pkce_secret = PkceS256Secret::default();
6459
6460        // First, the user does not request pkce in their exchange.
6461        let auth_req = AuthorisationRequest {
6462            response_type: ResponseType::Code,
6463            response_mode: None,
6464            client_id: "test_resource_server".to_string(),
6465            state: Some("123".to_string()),
6466            pkce_request: None,
6467            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6468            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6469            nonce: None,
6470            oidc_ext: Default::default(),
6471            max_age: None,
6472            unknown_keys: Default::default(),
6473        };
6474
6475        let consent_request = idms_prox_read
6476            .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6477            .expect("Failed to perform OAuth2 authorisation request.");
6478
6479        // Should be in the consent phase;
6480        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6481            unreachable!();
6482        };
6483
6484        // == Manually submit the consent token to the permit for the permit_success
6485        drop(idms_prox_read);
6486        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6487
6488        let permit_success = idms_prox_write
6489            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6490            .expect("Failed to perform OAuth2 permit");
6491
6492        // == Submit the token exchange code.
6493        // This exchange failed because we submitted a verifier when the code exchange
6494        // has NO code challenge present.
6495        let token_req = AccessTokenRequest {
6496            grant_type: GrantTypeReq::AuthorizationCode {
6497                code: permit_success.code,
6498                redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6499                code_verifier: Some(pkce_secret.to_verifier()),
6500            },
6501            client_post_auth: ClientPostAuth {
6502                client_id: Some("test_resource_server".to_string()),
6503                client_secret: Some(secret),
6504            },
6505        };
6506
6507        // Assert the exchange fails.
6508        assert!(matches!(
6509            idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6510            Err(Oauth2Error::InvalidRequest)
6511        ));
6512
6513        assert!(idms_prox_write.commit().is_ok());
6514    }
6515
6516    #[idm_test]
6517    // https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics#section-2.1
6518    //
6519    // If the origin configured is https, do not allow downgrading to http on redirect
6520    async fn test_idm_oauth2_redir_http_downgrade(
6521        idms: &IdmServer,
6522        _idms_delayed: &mut IdmServerDelayed,
6523    ) {
6524        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6525        // Enable pkce is set to FALSE
6526        let (secret, _uat, ident, _) =
6527            setup_oauth2_resource_server_basic(idms, ct, false, false, false).await;
6528
6529        let idms_prox_read = idms.proxy_read().await.unwrap();
6530
6531        // Get an ident/uat for now.
6532
6533        // == Setup the authorisation request
6534        // We attempt pkce even though the rs is set to not support pkce.
6535        let pkce_secret = PkceS256Secret::default();
6536
6537        // First, NOTE the lack of https on the redir uri.
6538        let auth_req = AuthorisationRequest {
6539            response_type: ResponseType::Code,
6540            response_mode: None,
6541            client_id: "test_resource_server".to_string(),
6542            state: Some("123".to_string()),
6543            pkce_request: Some(pkce_secret.to_request()),
6544            redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6545            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
6546            nonce: None,
6547            oidc_ext: Default::default(),
6548            max_age: None,
6549            unknown_keys: Default::default(),
6550        };
6551
6552        assert!(
6553            idms_prox_read
6554                .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
6555                .unwrap_err()
6556                == Oauth2Error::InvalidOrigin
6557        );
6558
6559        // This does have https
6560        let consent_request = good_authorisation_request!(
6561            idms_prox_read,
6562            &ident,
6563            ct,
6564            pkce_secret.to_request(),
6565            OAUTH2_SCOPE_OPENID.to_string()
6566        );
6567
6568        // Should be in the consent phase;
6569        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6570            unreachable!();
6571        };
6572
6573        // == Manually submit the consent token to the permit for the permit_success
6574        drop(idms_prox_read);
6575        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6576
6577        let permit_success = idms_prox_write
6578            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6579            .expect("Failed to perform OAuth2 permit");
6580
6581        // == Submit the token exchange code.
6582        // NOTE the url is http again
6583        let token_req = AccessTokenRequest {
6584            grant_type: GrantTypeReq::AuthorizationCode {
6585                code: permit_success.code,
6586                redirect_uri: Url::parse("http://demo.example.com/oauth2/result").unwrap(),
6587                code_verifier: Some(pkce_secret.to_verifier()),
6588            },
6589
6590            client_post_auth: ClientPostAuth {
6591                client_id: Some("test_resource_server".to_string()),
6592                client_secret: Some(secret),
6593            },
6594        };
6595
6596        // Assert the exchange fails.
6597        assert!(matches!(
6598            idms_prox_write.check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct),
6599            Err(Oauth2Error::InvalidOrigin)
6600        ));
6601
6602        assert!(idms_prox_write.commit().is_ok());
6603    }
6604
6605    async fn setup_refresh_token(
6606        idms: &IdmServer,
6607        _idms_delayed: &mut IdmServerDelayed,
6608        ct: Duration,
6609    ) -> (AccessTokenResponse, ClientAuthInfo) {
6610        // First, setup to get a token.
6611        let (secret, _uat, ident, _) =
6612            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
6613        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
6614
6615        let idms_prox_read = idms.proxy_read().await.unwrap();
6616
6617        // == Setup the authorisation request
6618        let pkce_secret = PkceS256Secret::default();
6619
6620        let consent_request = good_authorisation_request!(
6621            idms_prox_read,
6622            &ident,
6623            ct,
6624            pkce_secret.to_request(),
6625            OAUTH2_SCOPE_OPENID.to_string()
6626        );
6627
6628        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
6629            unreachable!();
6630        };
6631
6632        // == Manually submit the consent token to the permit for the permit_success
6633        drop(idms_prox_read);
6634        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6635
6636        let permit_success = idms_prox_write
6637            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
6638            .expect("Failed to perform OAuth2 permit");
6639
6640        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
6641            code: permit_success.code,
6642            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
6643            code_verifier: Some(pkce_secret.to_verifier()),
6644        }
6645        .into();
6646        let access_token_response_1 = idms_prox_write
6647            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6648            .expect("Unable to exchange for OAuth2 token");
6649
6650        assert!(idms_prox_write.commit().is_ok());
6651
6652        trace!(?access_token_response_1);
6653
6654        (access_token_response_1, client_authz)
6655    }
6656
6657    #[idm_test]
6658    async fn test_idm_oauth2_refresh_token_basic(
6659        idms: &IdmServer,
6660        idms_delayed: &mut IdmServerDelayed,
6661    ) {
6662        // First, setup to get a token.
6663        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6664
6665        let (access_token_response_1, client_authz) =
6666            setup_refresh_token(idms, idms_delayed, ct).await;
6667
6668        // ============================================
6669        // test basic refresh while access still valid.
6670
6671        let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
6672        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6673
6674        let refresh_token = access_token_response_1
6675            .refresh_token
6676            .as_ref()
6677            .expect("no refresh token was issued")
6678            .clone();
6679
6680        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6681            refresh_token,
6682            scope: None,
6683        }
6684        .into();
6685
6686        let access_token_response_2 = idms_prox_write
6687            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6688            .expect("Unable to exchange for OAuth2 token");
6689
6690        assert!(idms_prox_write.commit().is_ok());
6691
6692        trace!(?access_token_response_2);
6693
6694        assert!(access_token_response_1.access_token != access_token_response_2.access_token);
6695        assert!(access_token_response_1.refresh_token != access_token_response_2.refresh_token);
6696        assert!(access_token_response_1.id_token != access_token_response_2.id_token);
6697
6698        // ============================================
6699        // test basic refresh after access exp
6700        let ct =
6701            Duration::from_secs(TEST_CURRENT_TIME + 20 + access_token_response_2.expires_in as u64);
6702
6703        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6704
6705        let refresh_token = access_token_response_2
6706            .refresh_token
6707            .as_ref()
6708            .expect("no refresh token was issued")
6709            .clone();
6710
6711        // get the refresh token expiry now before we use it.
6712        let reflected_token = idms_prox_write
6713            .reflect_oauth2_token(&client_authz, &refresh_token)
6714            .expect("Failed to access internals of the refresh token");
6715
6716        let refresh_exp = match reflected_token {
6717            Oauth2TokenType::Refresh { exp, .. } => exp,
6718            // Oauth2TokenType::Access { .. } |
6719            Oauth2TokenType::ClientAccess { .. } => unreachable!(),
6720        };
6721
6722        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6723            refresh_token,
6724            scope: None,
6725        }
6726        .into();
6727
6728        let access_token_response_3 = idms_prox_write
6729            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6730            .expect("Unable to exchange for OAuth2 token");
6731
6732        // Get the user entry to check the session life was extended.
6733
6734        let entry = idms_prox_write
6735            .qs_write
6736            .internal_search_uuid(UUID_TESTPERSON_1)
6737            .expect("failed");
6738        let session = entry
6739            .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
6740            .and_then(|sessions| sessions.first_key_value())
6741            // If there is no map, then something is wrong.
6742            .unwrap();
6743
6744        trace!(?session);
6745        // The Oauth2 Session must be updated with a newer session time.
6746        assert_eq!(
6747            SessionState::ExpiresAt(
6748                time::OffsetDateTime::UNIX_EPOCH
6749                    + ct
6750                    + Duration::from_secs(OAUTH_REFRESH_TOKEN_EXPIRY)
6751            ),
6752            session.1.state
6753        );
6754
6755        assert!(idms_prox_write.commit().is_ok());
6756
6757        trace!(?access_token_response_3);
6758
6759        assert!(access_token_response_3.access_token != access_token_response_2.access_token);
6760        assert!(access_token_response_3.refresh_token != access_token_response_2.refresh_token);
6761        assert!(access_token_response_3.id_token != access_token_response_2.id_token);
6762
6763        // refresh after refresh has expired.
6764        // Refresh tokens have a max time limit - the session time limit still bounds it though, but
6765        // so does the refresh token limit. We check both, but the refresh time is checked first so
6766        // we can guarantee this in this test.
6767
6768        let ct = Duration::from_secs(
6769            TEST_CURRENT_TIME + refresh_exp as u64 + access_token_response_3.expires_in as u64,
6770        );
6771
6772        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6773
6774        let refresh_token = access_token_response_3
6775            .refresh_token
6776            .as_ref()
6777            .expect("no refresh token was issued")
6778            .clone();
6779
6780        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6781            refresh_token,
6782            scope: None,
6783        }
6784        .into();
6785        let access_token_response_4 = idms_prox_write
6786            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6787            .unwrap_err();
6788
6789        assert_eq!(access_token_response_4, Oauth2Error::InvalidGrant);
6790
6791        assert!(idms_prox_write.commit().is_ok());
6792    }
6793
6794    // refresh when OAuth2 parent session exp / missing.
6795    #[idm_test]
6796    async fn test_idm_oauth2_refresh_token_oauth2_session_expired(
6797        idms: &IdmServer,
6798        idms_delayed: &mut IdmServerDelayed,
6799    ) {
6800        // First, setup to get a token.
6801        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6802
6803        let (access_token_response_1, client_authz) =
6804            setup_refresh_token(idms, idms_delayed, ct).await;
6805
6806        // ============================================
6807        // Revoke the OAuth2 session
6808
6809        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6810        let revoke_request = TokenRevokeRequest {
6811            token: access_token_response_1.access_token.clone(),
6812            token_type_hint: None,
6813            client_post_auth: ClientPostAuth::default(),
6814        };
6815        assert!(idms_prox_write
6816            .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
6817            .is_ok());
6818        assert!(idms_prox_write.commit().is_ok());
6819
6820        // ============================================
6821        // then attempt a refresh.
6822        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6823
6824        let refresh_token = access_token_response_1
6825            .refresh_token
6826            .as_ref()
6827            .expect("no refresh token was issued")
6828            .clone();
6829
6830        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6831            refresh_token,
6832            scope: None,
6833        }
6834        .into();
6835        let access_token_response_2 = idms_prox_write
6836            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6837            // Should be unable to exchange.
6838            .unwrap_err();
6839
6840        assert_eq!(access_token_response_2, Oauth2Error::InvalidGrant);
6841
6842        assert!(idms_prox_write.commit().is_ok());
6843    }
6844
6845    // refresh with wrong client id/authz
6846    #[idm_test]
6847    async fn test_idm_oauth2_refresh_token_invalid_client_authz(
6848        idms: &IdmServer,
6849        idms_delayed: &mut IdmServerDelayed,
6850    ) {
6851        // First, setup to get a token.
6852        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6853
6854        let (access_token_response_1, _client_authz) =
6855            setup_refresh_token(idms, idms_delayed, ct).await;
6856
6857        let bad_client_authz = ClientAuthInfo::encode_basic("test_resource_server", "12345");
6858
6859        // ============================================
6860        // Refresh with invalid client authz
6861
6862        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6863
6864        let refresh_token = access_token_response_1
6865            .refresh_token
6866            .as_ref()
6867            .expect("no refresh token was issued")
6868            .clone();
6869
6870        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6871            refresh_token,
6872            scope: None,
6873        }
6874        .into();
6875        let access_token_response_2 = idms_prox_write
6876            .check_oauth2_token_exchange(&bad_client_authz, &token_req, ct)
6877            .unwrap_err();
6878
6879        assert_eq!(access_token_response_2, Oauth2Error::AuthenticationRequired);
6880
6881        assert!(idms_prox_write.commit().is_ok());
6882    }
6883
6884    // Incorrect scopes re-requested
6885    #[idm_test]
6886    async fn test_idm_oauth2_refresh_token_inconsistent_scopes(
6887        idms: &IdmServer,
6888        idms_delayed: &mut IdmServerDelayed,
6889    ) {
6890        // First, setup to get a token.
6891        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6892
6893        let (access_token_response_1, client_authz) =
6894            setup_refresh_token(idms, idms_delayed, ct).await;
6895
6896        // ============================================
6897        // Refresh with different scopes
6898
6899        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6900
6901        let refresh_token = access_token_response_1
6902            .refresh_token
6903            .as_ref()
6904            .expect("no refresh token was issued")
6905            .clone();
6906
6907        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6908            refresh_token,
6909            scope: Some(btreeset!["invalid_scope".to_string()]),
6910        }
6911        .into();
6912        let access_token_response_2 = idms_prox_write
6913            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6914            .unwrap_err();
6915
6916        assert_eq!(access_token_response_2, Oauth2Error::InvalidScope);
6917
6918        assert!(idms_prox_write.commit().is_ok());
6919    }
6920
6921    // Test that reuse of a refresh token is denied + terminates the session.
6922    //
6923    // https://www.ietf.org/archive/id/draft-ietf-oauth-security-topics-18.html#refresh_token_protection
6924    #[idm_test]
6925    async fn test_idm_oauth2_refresh_token_reuse_invalidates_session(
6926        idms: &IdmServer,
6927        idms_delayed: &mut IdmServerDelayed,
6928    ) {
6929        // First, setup to get a token.
6930        let ct = Duration::from_secs(TEST_CURRENT_TIME);
6931
6932        let (access_token_response_1, client_authz) =
6933            setup_refresh_token(idms, idms_delayed, ct).await;
6934
6935        // ============================================
6936        // Use the refresh token once
6937        let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
6938        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6939
6940        let refresh_token = access_token_response_1
6941            .refresh_token
6942            .as_ref()
6943            .expect("no refresh token was issued")
6944            .clone();
6945
6946        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6947            refresh_token,
6948            scope: None,
6949        }
6950        .into();
6951
6952        let _access_token_response_2 = idms_prox_write
6953            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6954            .expect("Unable to exchange for OAuth2 token");
6955
6956        assert!(idms_prox_write.commit().is_ok());
6957
6958        // Now use it again. - this will cause an error and the session to be terminated.
6959        let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
6960        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
6961
6962        let refresh_token = access_token_response_1
6963            .refresh_token
6964            .as_ref()
6965            .expect("no refresh token was issued")
6966            .clone();
6967
6968        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
6969            refresh_token,
6970            scope: None,
6971        }
6972        .into();
6973
6974        let access_token_response_3 = idms_prox_write
6975            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
6976            .unwrap_err();
6977
6978        assert_eq!(access_token_response_3, Oauth2Error::InvalidGrant);
6979
6980        let entry = idms_prox_write
6981            .qs_write
6982            .internal_search_uuid(UUID_TESTPERSON_1)
6983            .expect("failed");
6984        let valid = entry
6985            .get_ava_as_oauth2session_map(Attribute::OAuth2Session)
6986            .and_then(|sessions| sessions.first_key_value())
6987            .map(|(_, session)| !matches!(session.state, SessionState::RevokedAt(_)))
6988            // If there is no map, then something is wrong.
6989            .unwrap();
6990        // The session should be invalid at this point.
6991        assert!(!valid);
6992
6993        assert!(idms_prox_write.commit().is_ok());
6994    }
6995
6996    // Test session divergence. This means that we have to:
6997    // access + refresh 1
6998    // use refresh 1 -> access + refresh 2 // don't commit this txn.
6999    // use refresh 2 -> access + refresh 3
7000    //    check the session state.
7001
7002    #[idm_test]
7003    async fn test_idm_oauth2_refresh_token_divergence(
7004        idms: &IdmServer,
7005        idms_delayed: &mut IdmServerDelayed,
7006    ) {
7007        // First, setup to get a token.
7008        let ct = Duration::from_secs(TEST_CURRENT_TIME);
7009
7010        let (access_token_response_1, client_authz) =
7011            setup_refresh_token(idms, idms_delayed, ct).await;
7012
7013        // ============================================
7014        // Use the refresh token once
7015        let ct = Duration::from_secs(TEST_CURRENT_TIME + 1);
7016        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7017
7018        let refresh_token = access_token_response_1
7019            .refresh_token
7020            .as_ref()
7021            .expect("no refresh token was issued")
7022            .clone();
7023
7024        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7025            refresh_token,
7026            scope: None,
7027        }
7028        .into();
7029
7030        let access_token_response_2 = idms_prox_write
7031            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7032            .expect("Unable to exchange for OAuth2 token");
7033
7034        // DO NOT COMMIT HERE - this is what forces the session issued_at
7035        // time to stay at the original time!
7036        drop(idms_prox_write);
7037
7038        // ============================================
7039        let ct = Duration::from_secs(TEST_CURRENT_TIME + 2);
7040        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7041
7042        let refresh_token = access_token_response_2
7043            .refresh_token
7044            .as_ref()
7045            .expect("no refresh token was issued")
7046            .clone();
7047
7048        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7049            refresh_token,
7050            scope: None,
7051        }
7052        .into();
7053
7054        let _access_token_response_3 = idms_prox_write
7055            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7056            .expect("Unable to exchange for OAuth2 token");
7057
7058        assert!(idms_prox_write.commit().is_ok());
7059
7060        // Success!
7061    }
7062
7063    #[idm_test]
7064    async fn test_idm_oauth2_refresh_token_scope_constraints(
7065        idms: &IdmServer,
7066        idms_delayed: &mut IdmServerDelayed,
7067    ) {
7068        // First, setup to get a token.
7069        let ct = Duration::from_secs(TEST_CURRENT_TIME);
7070
7071        let (access_token_response_1, client_authz) =
7072            setup_refresh_token(idms, idms_delayed, ct).await;
7073
7074        // https://www.rfc-editor.org/rfc/rfc6749#section-1.5
7075        // Refresh tokens are issued to the client by the authorization
7076        // server and are used to obtain a new access token when the
7077        // current access token becomes invalid or expires, or to obtain
7078        // additional access tokens with identical or narrower scope
7079        // (access tokens may have a shorter lifetime and fewer
7080        // permissions than authorized by the resource owner).
7081
7082        let ct = Duration::from_secs(TEST_CURRENT_TIME + 10);
7083        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7084
7085        let refresh_token = access_token_response_1
7086            .refresh_token
7087            .as_ref()
7088            .expect("no refresh token was issued")
7089            .clone();
7090
7091        // Get the initial scopes.
7092        let jws_verifier = JwsDangerReleaseWithoutVerify::default();
7093
7094        let access_token_unverified = JwsCompact::from_str(&access_token_response_1.access_token)
7095            .expect("Invalid Access Token");
7096
7097        let reflected_token = jws_verifier
7098            .verify(&access_token_unverified)
7099            .unwrap()
7100            .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7101            .expect("Failed to access internals of the refresh token");
7102
7103        trace!(?reflected_token);
7104        let initial_scopes = reflected_token.extensions.scope;
7105        trace!(?initial_scopes);
7106
7107        // Should be the same scopes as initial.
7108        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7109            refresh_token,
7110            scope: None,
7111        }
7112        .into();
7113
7114        let access_token_response_2 = idms_prox_write
7115            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7116            .expect("Unable to exchange for OAuth2 token");
7117
7118        let access_token_unverified = JwsCompact::from_str(&access_token_response_2.access_token)
7119            .expect("Invalid Access Token");
7120
7121        let reflected_token = jws_verifier
7122            .verify(&access_token_unverified)
7123            .unwrap()
7124            .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7125            .expect("Failed to access internals of the refresh token");
7126
7127        assert_eq!(initial_scopes, reflected_token.extensions.scope);
7128
7129        let refresh_token = access_token_response_2
7130            .refresh_token
7131            .as_ref()
7132            .expect("no refresh token was issued")
7133            .clone();
7134
7135        // Now the scopes can be constrained.
7136        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7137            refresh_token,
7138            scope: Some(["openid".to_string()].into()),
7139        }
7140        .into();
7141
7142        let access_token_response_3 = idms_prox_write
7143            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7144            .expect("Unable to exchange for OAuth2 token");
7145
7146        let access_token_unverified = JwsCompact::from_str(&access_token_response_3.access_token)
7147            .expect("Invalid Access Token");
7148
7149        let reflected_token = jws_verifier
7150            .verify(&access_token_unverified)
7151            .unwrap()
7152            .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7153            .expect("Failed to access internals of the refresh token");
7154
7155        assert_ne!(initial_scopes, reflected_token.extensions.scope);
7156
7157        // Keep the constrained scopes.
7158        let constrained_scopes = reflected_token.extensions.scope;
7159
7160        let refresh_token = access_token_response_3
7161            .refresh_token
7162            .as_ref()
7163            .expect("no refresh token was issued")
7164            .clone();
7165
7166        // No scope request still issues the constrained values.
7167        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7168            refresh_token,
7169            scope: None,
7170        }
7171        .into();
7172
7173        let access_token_response_4 = idms_prox_write
7174            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7175            .expect("Unable to exchange for OAuth2 token");
7176
7177        let access_token_unverified = JwsCompact::from_str(&access_token_response_4.access_token)
7178            .expect("Invalid Access Token");
7179
7180        let reflected_token = jws_verifier
7181            .verify(&access_token_unverified)
7182            .unwrap()
7183            .from_json::<OAuth2RFC9068Token<OAuth2RFC9068TokenExtensions>>()
7184            .expect("Failed to access internals of the refresh token");
7185
7186        assert_ne!(initial_scopes, reflected_token.extensions.scope);
7187        assert_eq!(constrained_scopes, reflected_token.extensions.scope);
7188
7189        let refresh_token = access_token_response_4
7190            .refresh_token
7191            .as_ref()
7192            .expect("no refresh token was issued")
7193            .clone();
7194
7195        // We can't now extend back to the initial scopes.
7196        let token_req: AccessTokenRequest = GrantTypeReq::RefreshToken {
7197            refresh_token,
7198            scope: Some(initial_scopes),
7199        }
7200        .into();
7201
7202        let access_token_response_5_err = idms_prox_write
7203            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7204            .unwrap_err();
7205
7206        assert_eq!(access_token_response_5_err, Oauth2Error::InvalidScope);
7207
7208        assert!(idms_prox_write.commit().is_ok());
7209    }
7210
7211    #[test]
7212    // I know this looks kinda dumb but at some point someone pointed out that our scope syntax wasn't compliant with rfc6749
7213    //(https://datatracker.ietf.org/doc/html/rfc6749#section-3.3), so I'm just making sure that we don't break it again.
7214    fn compliant_serialization_test() {
7215        let token_req: Result<AccessTokenRequest, serde_json::Error> = serde_json::from_str(
7216            r#"
7217            {
7218                "grant_type": "refresh_token",
7219                "refresh_token": "some_dumb_refresh_token",
7220                "scope": "invalid_scope vasd asd"
7221            }
7222        "#,
7223        );
7224        assert!(token_req.is_ok());
7225    }
7226
7227    #[idm_test]
7228    async fn test_idm_oauth2_custom_claims(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
7229        let ct = Duration::from_secs(TEST_CURRENT_TIME);
7230        let (secret, _uat, ident, oauth2_rs_uuid) =
7231            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7232
7233        // Setup custom claim maps here.
7234        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7235
7236        let modlist = ModifyList::new_list(vec![
7237            // Member of a claim map.
7238            Modify::Present(
7239                Attribute::OAuth2RsClaimMap,
7240                Value::OauthClaimMap(
7241                    "custom_a".to_string(),
7242                    OauthClaimMapJoin::CommaSeparatedValue,
7243                ),
7244            ),
7245            Modify::Present(
7246                Attribute::OAuth2RsClaimMap,
7247                Value::OauthClaimValue(
7248                    "custom_a".to_string(),
7249                    UUID_TESTGROUP,
7250                    btreeset!["value_a".to_string()],
7251                ),
7252            ),
7253            // If you are a member of two groups, the claim maps merge.
7254            Modify::Present(
7255                Attribute::OAuth2RsClaimMap,
7256                Value::OauthClaimValue(
7257                    "custom_a".to_string(),
7258                    UUID_IDM_ALL_ACCOUNTS,
7259                    btreeset!["value_b".to_string()],
7260                ),
7261            ),
7262            // Map with a different separator
7263            Modify::Present(
7264                Attribute::OAuth2RsClaimMap,
7265                Value::OauthClaimMap(
7266                    "custom_b".to_string(),
7267                    OauthClaimMapJoin::SpaceSeparatedValue,
7268                ),
7269            ),
7270            Modify::Present(
7271                Attribute::OAuth2RsClaimMap,
7272                Value::OauthClaimValue(
7273                    "custom_b".to_string(),
7274                    UUID_TESTGROUP,
7275                    btreeset!["value_a".to_string()],
7276                ),
7277            ),
7278            Modify::Present(
7279                Attribute::OAuth2RsClaimMap,
7280                Value::OauthClaimValue(
7281                    "custom_b".to_string(),
7282                    UUID_IDM_ALL_ACCOUNTS,
7283                    btreeset!["value_b".to_string()],
7284                ),
7285            ),
7286            // Not a member of the claim map.
7287            Modify::Present(
7288                Attribute::OAuth2RsClaimMap,
7289                Value::OauthClaimValue(
7290                    "custom_b".to_string(),
7291                    UUID_IDM_ADMINS,
7292                    btreeset!["value_c".to_string()],
7293                ),
7294            ),
7295            // Extended claim name syntax, allows characters beyond scope names.
7296            Modify::Present(
7297                Attribute::OAuth2RsClaimMap,
7298                Value::OauthClaimMap(
7299                    "custom:claim-name".to_string(),
7300                    OauthClaimMapJoin::CommaSeparatedValue,
7301                ),
7302            ),
7303            Modify::Present(
7304                Attribute::OAuth2RsClaimMap,
7305                Value::OauthClaimValue(
7306                    "custom:claim-name".to_string(),
7307                    UUID_TESTGROUP,
7308                    btreeset!["value:a-a".to_string()],
7309                ),
7310            ),
7311        ]);
7312
7313        assert!(idms_prox_write
7314            .qs_write
7315            .internal_modify(
7316                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7317                &modlist,
7318            )
7319            .is_ok());
7320
7321        assert!(idms_prox_write.commit().is_ok());
7322
7323        // Claim maps setup, lets go.
7324        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7325
7326        let idms_prox_read = idms.proxy_read().await.unwrap();
7327
7328        let pkce_secret = PkceS256Secret::default();
7329
7330        let consent_request = good_authorisation_request!(
7331            idms_prox_read,
7332            &ident,
7333            ct,
7334            pkce_secret.to_request(),
7335            OAUTH2_SCOPE_OPENID.to_string()
7336        );
7337
7338        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7339            unreachable!();
7340        };
7341
7342        // == Manually submit the consent token to the permit for the permit_success
7343        drop(idms_prox_read);
7344        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7345
7346        let permit_success = idms_prox_write
7347            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7348            .expect("Failed to perform OAuth2 permit");
7349
7350        // == Submit the token exchange code.
7351        let token_req: AccessTokenRequest = GrantTypeReq::AuthorizationCode {
7352            code: permit_success.code,
7353            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
7354            code_verifier: Some(pkce_secret.to_verifier()),
7355        }
7356        .into();
7357
7358        let token_response = idms_prox_write
7359            .check_oauth2_token_exchange(&client_authz, &token_req, ct)
7360            .expect("Failed to perform OAuth2 token exchange");
7361
7362        // 🎉 We got a token!
7363        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7364
7365        let id_token = token_response.id_token.expect("No id_token in response!");
7366        let access_token =
7367            JwsCompact::from_str(&token_response.access_token).expect("Invalid Access Token");
7368
7369        // Get the read txn for inspecting the tokens
7370        assert!(idms_prox_write.commit().is_ok());
7371
7372        let mut idms_prox_read = idms.proxy_read().await.unwrap();
7373
7374        let mut jwkset = idms_prox_read
7375            .oauth2_openid_publickey("test_resource_server")
7376            .expect("Failed to get public key");
7377
7378        let public_jwk = jwkset.keys.pop().expect("no such jwk");
7379
7380        let jws_validator =
7381            JwsEs256Verifier::try_from(&public_jwk).expect("failed to build validator");
7382
7383        let oidc_unverified =
7384            OidcUnverified::from_str(&id_token).expect("Failed to parse id_token");
7385
7386        let iat = ct.as_secs() as i64;
7387
7388        let oidc = jws_validator
7389            .verify(&oidc_unverified)
7390            .unwrap()
7391            .verify_exp(iat)
7392            .expect("Failed to verify oidc");
7393
7394        // Are the id_token values what we expect?
7395        assert!(
7396            oidc.iss
7397                == Url::parse("https://idm.example.com/oauth2/openid/test_resource_server")
7398                    .unwrap()
7399        );
7400        assert_eq!(oidc.sub, OidcSubject::U(UUID_TESTPERSON_1));
7401        assert_eq!(oidc.aud, "test_resource_server");
7402        assert_eq!(oidc.iat, iat);
7403        assert_eq!(oidc.nbf, Some(iat));
7404        // Previously this was the auth session but it's now inline with the access token expiry.
7405        assert_eq!(oidc.exp, iat + (OAUTH2_ACCESS_TOKEN_EXPIRY as i64));
7406        assert!(oidc.auth_time.is_none());
7407        // Is nonce correctly passed through?
7408        assert_eq!(oidc.nonce, Some("abcdef".to_string()));
7409        assert!(oidc.at_hash.is_none());
7410        assert!(oidc.acr.is_none());
7411        assert!(oidc.amr.is_none());
7412        assert_eq!(oidc.azp, Some("test_resource_server".to_string()));
7413        assert!(oidc.jti.is_some());
7414        if let Some(jti) = &oidc.jti {
7415            assert!(Uuid::from_str(jti).is_ok());
7416        }
7417        assert_eq!(oidc.s_claims.name, Some("Test Person 1".to_string()));
7418        assert_eq!(
7419            oidc.s_claims.preferred_username,
7420            Some("testperson1@example.com".to_string())
7421        );
7422        assert!(
7423            oidc.s_claims.scopes == vec![OAUTH2_SCOPE_OPENID.to_string(), "supplement".to_string()]
7424        );
7425
7426        assert_eq!(
7427            oidc.claims.get("custom_a").and_then(|v| v.as_str()),
7428            Some("value_a,value_b")
7429        );
7430        assert_eq!(
7431            oidc.claims.get("custom_b").and_then(|v| v.as_str()),
7432            Some("value_a value_b")
7433        );
7434
7435        assert_eq!(
7436            oidc.claims
7437                .get("custom:claim-name")
7438                .and_then(|v| v.as_str()),
7439            Some("value:a-a")
7440        );
7441
7442        // Does our access token work with the userinfo endpoint?
7443        // Do the id_token details line up to the userinfo?
7444        let userinfo = idms_prox_read
7445            .oauth2_openid_userinfo("test_resource_server", &access_token, ct)
7446            .expect("failed to get userinfo");
7447
7448        assert_eq!(oidc.iss, userinfo.iss);
7449        assert_eq!(oidc.sub, userinfo.sub);
7450        assert_eq!(oidc.aud, userinfo.aud);
7451        assert_eq!(oidc.iat, userinfo.iat);
7452        assert_eq!(oidc.nbf, userinfo.nbf);
7453        assert_eq!(oidc.exp, userinfo.exp);
7454        assert!(userinfo.auth_time.is_none());
7455        assert_eq!(userinfo.nonce, Some("abcdef".to_string()));
7456        assert!(userinfo.at_hash.is_none());
7457        assert!(userinfo.jti.is_some());
7458        if let Some(jti) = &userinfo.jti {
7459            assert!(Uuid::from_str(jti).is_ok());
7460        }
7461        assert_eq!(oidc.amr, userinfo.amr);
7462        assert_eq!(oidc.azp, userinfo.azp);
7463        assert!(userinfo.jti.is_some());
7464        if let Some(jti) = &userinfo.jti {
7465            assert!(Uuid::from_str(jti).is_ok());
7466        }
7467        assert_eq!(oidc.s_claims, userinfo.s_claims);
7468        assert_eq!(oidc.claims, userinfo.claims);
7469
7470        // Check the oauth2 introspect bits.
7471        let intr_request = AccessTokenIntrospectRequest {
7472            token: token_response.access_token.clone(),
7473            token_type_hint: None,
7474            client_post_auth: ClientPostAuth::default(),
7475        };
7476        let intr_response = idms_prox_read
7477            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7478            .expect("Failed to inspect token");
7479
7480        eprintln!("👉  {intr_response:?}");
7481        assert!(intr_response.active);
7482        assert_eq!(
7483            intr_response.scope,
7484            btreeset!["openid".to_string(), "supplement".to_string()]
7485        );
7486        assert_eq!(
7487            intr_response.client_id.as_deref(),
7488            Some("test_resource_server")
7489        );
7490        assert_eq!(
7491            intr_response.username.as_deref(),
7492            Some("testperson1@example.com")
7493        );
7494        assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7495        assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7496        assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7497        // Introspect doesn't have custom claims.
7498
7499        drop(idms_prox_read);
7500    }
7501
7502    #[idm_test]
7503    async fn test_idm_oauth2_public_allow_localhost_redirect(
7504        idms: &IdmServer,
7505        _idms_delayed: &mut IdmServerDelayed,
7506    ) {
7507        let ct = Duration::from_secs(TEST_CURRENT_TIME);
7508        let (_uat, ident, oauth2_rs_uuid) = setup_oauth2_resource_server_public(idms, ct).await;
7509
7510        let mut idms_prox_write: crate::idm::server::IdmServerProxyWriteTransaction<'_> =
7511            idms.proxy_write(ct).await.unwrap();
7512
7513        let redirect_uri = Url::parse("http://localhost:8765/oauth2/result")
7514            .expect("Failed to parse redirect URL");
7515
7516        let modlist = ModifyList::new_list(vec![
7517            Modify::Present(Attribute::OAuth2AllowLocalhostRedirect, Value::Bool(true)),
7518            Modify::Present(Attribute::OAuth2RsOrigin, Value::Url(redirect_uri.clone())),
7519        ]);
7520
7521        assert!(idms_prox_write
7522            .qs_write
7523            .internal_modify(
7524                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(oauth2_rs_uuid))),
7525                &modlist,
7526            )
7527            .is_ok());
7528
7529        assert!(idms_prox_write.commit().is_ok());
7530
7531        let idms_prox_read = idms.proxy_read().await.unwrap();
7532
7533        // == Setup the authorisation request
7534        let pkce_secret = PkceS256Secret::default();
7535
7536        let auth_req = AuthorisationRequest {
7537            response_type: ResponseType::Code,
7538            response_mode: None,
7539            client_id: "test_resource_server".to_string(),
7540            state: Some("123".to_string()),
7541            pkce_request: Some(pkce_secret.to_request()),
7542            redirect_uri: Url::parse("http://localhost:8765/oauth2/result").unwrap(),
7543            scope: btreeset![OAUTH2_SCOPE_OPENID.to_string()],
7544            nonce: Some("abcdef".to_string()),
7545            oidc_ext: Default::default(),
7546            max_age: None,
7547            unknown_keys: Default::default(),
7548        };
7549
7550        let consent_request = idms_prox_read
7551            .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
7552            .expect("OAuth2 authorisation failed");
7553
7554        // Should be in the consent phase;
7555        let AuthoriseResponse::ConsentRequested { consent_token, .. } = consent_request else {
7556            unreachable!();
7557        };
7558
7559        // == Manually submit the consent token to the permit for the permit_success
7560        drop(idms_prox_read);
7561        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7562
7563        let permit_success = idms_prox_write
7564            .check_oauth2_authorise_permit(&ident, &consent_token, ct)
7565            .expect("Failed to perform OAuth2 permit");
7566
7567        // Check we are reflecting the CSRF properly.
7568        assert_eq!(permit_success.state.as_deref(), Some("123"));
7569
7570        // == Submit the token exchange code.
7571        let token_req = AccessTokenRequest {
7572            grant_type: GrantTypeReq::AuthorizationCode {
7573                code: permit_success.code,
7574                redirect_uri,
7575                code_verifier: Some(pkce_secret.to_verifier()),
7576            },
7577            client_post_auth: ClientPostAuth {
7578                client_id: Some("test_resource_server".to_string()),
7579                client_secret: None,
7580            },
7581        };
7582
7583        let token_response = idms_prox_write
7584            .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7585            .expect("Failed to perform OAuth2 token exchange");
7586
7587        // 🎉 We got a token! In the future we can then check introspection from this point.
7588        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7589
7590        assert!(idms_prox_write.commit().is_ok());
7591    }
7592
7593    #[idm_test]
7594    async fn test_idm_oauth2_service_account_token_exchange(
7595        idms: &IdmServer,
7596        _idms_delayed: &mut IdmServerDelayed,
7597    ) {
7598        let ct = Duration::from_secs(TEST_CURRENT_TIME);
7599        let (secret, _uat, _ident, _) =
7600            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7601
7602        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7603
7604        let service_account_uuid = Uuid::new_v4();
7605        let sa_entry: Entry<EntryInit, EntryNew> = entry_init!(
7606            (Attribute::Class, EntryClass::Object.to_value()),
7607            (Attribute::Class, EntryClass::Account.to_value()),
7608            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
7609            (Attribute::Name, Value::new_iname("test_sa_oauth2")),
7610            (Attribute::Uuid, Value::Uuid(service_account_uuid)),
7611            (Attribute::DisplayName, Value::new_utf8s("test_sa_oauth2")),
7612            (Attribute::Description, Value::new_utf8s("test_sa_oauth2"))
7613        );
7614
7615        idms_prox_write
7616            .qs_write
7617            .internal_create(vec![sa_entry])
7618            .expect("Failed to create service account");
7619
7620        idms_prox_write
7621            .qs_write
7622            .internal_modify(
7623                &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_TESTGROUP))),
7624                &ModifyList::new_list(vec![Modify::Present(
7625                    Attribute::Member,
7626                    Value::Refer(service_account_uuid),
7627                )]),
7628            )
7629            .expect("Failed to add service account to scope group");
7630
7631        let gte = GenerateApiTokenEvent::new_internal(service_account_uuid, "sa-token", None);
7632
7633        let api_token = idms_prox_write
7634            .service_account_generate_api_token(&gte, ct)
7635            .expect("failed to generate api token");
7636
7637        assert!(idms_prox_write.commit().is_ok());
7638
7639        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7640
7641        let scopes: BTreeSet<String> =
7642            btreeset![OAUTH2_SCOPE_OPENID.into(), OAUTH2_SCOPE_GROUPS.into()];
7643
7644        let build_exchange_request =
7645            |requested_scopes: BTreeSet<String>, client_secret: Option<String>| {
7646                AccessTokenRequest {
7647                    grant_type: GrantTypeReq::TokenExchange {
7648                        subject_token: api_token.to_string(),
7649                        subject_token_type: TOKEN_EXCHANGE_SUBJECT_TOKEN_TYPE_ACCESS.into(),
7650                        requested_token_type: None,
7651                        audience: Some("test_resource_server".into()),
7652                        resource: None,
7653                        actor_token: None,
7654                        actor_token_type: None,
7655                        scope: Some(requested_scopes),
7656                    },
7657                    client_post_auth: ClientPostAuth {
7658                        client_id: Some("test_resource_server".into()),
7659                        client_secret,
7660                    },
7661                }
7662            };
7663
7664        let token_req = build_exchange_request(scopes.clone(), None);
7665        let forbidden_secret_req = build_exchange_request(scopes.clone(), Some(secret.clone()));
7666        let empty_scope_req = build_exchange_request(BTreeSet::new(), None);
7667
7668        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7669
7670        assert_eq!(
7671            idms_prox_write
7672                .check_oauth2_token_exchange(&client_authz, &forbidden_secret_req, ct)
7673                .unwrap_err(),
7674            Oauth2Error::InvalidRequest
7675        );
7676
7677        assert_eq!(
7678            idms_prox_write
7679                .check_oauth2_token_exchange(&ClientAuthInfo::none(), &empty_scope_req, ct)
7680                .unwrap_err(),
7681            Oauth2Error::InvalidRequest
7682        );
7683
7684        let token_response = idms_prox_write
7685            .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7686            .expect("Failed to perform OAuth2 token exchange for service account");
7687
7688        assert_eq!(token_response.token_type, AccessTokenType::Bearer);
7689        assert_eq!(
7690            token_response.issued_token_type,
7691            Some(IssuedTokenType::AccessToken)
7692        );
7693        assert!(token_response.refresh_token.is_some());
7694        assert!(token_response.id_token.is_some());
7695        let response_scopes = token_response.scope.clone();
7696        assert!(response_scopes.contains(OAUTH2_SCOPE_OPENID));
7697        assert!(response_scopes.contains(OAUTH2_SCOPE_GROUPS));
7698
7699        assert!(idms_prox_write.commit().is_ok());
7700
7701        let mut idms_prox_read = idms.proxy_read().await.unwrap();
7702        let intr_request = AccessTokenIntrospectRequest {
7703            token: token_response.access_token.clone(),
7704            token_type_hint: None,
7705            client_post_auth: ClientPostAuth::default(),
7706        };
7707        let intr_response = idms_prox_read
7708            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7709            .expect("Failed to introspect service account token");
7710
7711        assert!(intr_response.active);
7712        assert_eq!(
7713            intr_response.client_id.as_deref(),
7714            Some("test_resource_server")
7715        );
7716        assert_eq!(
7717            intr_response.sub.as_deref(),
7718            Some(service_account_uuid.to_string().as_str())
7719        );
7720    }
7721
7722    #[idm_test]
7723    async fn test_idm_oauth2_basic_client_credentials_grant_valid(
7724        idms: &IdmServer,
7725        _idms_delayed: &mut IdmServerDelayed,
7726    ) {
7727        let ct = Duration::from_secs(TEST_CURRENT_TIME);
7728        let (secret, _uat, _ident, _) =
7729            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7730        let client_authz = ClientAuthInfo::encode_basic("test_resource_server", secret.as_str());
7731
7732        // scope: Some(btreeset!["invalid_scope".to_string()]),
7733        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7734
7735        let token_req = AccessTokenRequest {
7736            grant_type: GrantTypeReq::ClientCredentials { scope: None },
7737
7738            client_post_auth: ClientPostAuth {
7739                client_id: Some("test_resource_server".to_string()),
7740                client_secret: Some(secret),
7741            },
7742        };
7743
7744        let oauth2_token = idms_prox_write
7745            .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7746            .expect("Failed to perform OAuth2 token exchange");
7747
7748        assert!(idms_prox_write.commit().is_ok());
7749
7750        // 🎉 We got a token! In the future we can then check introspection from this point.
7751        assert_eq!(oauth2_token.token_type, AccessTokenType::Bearer);
7752
7753        // Check Oauth2 Token Introspection
7754        let mut idms_prox_read = idms.proxy_read().await.unwrap();
7755
7756        let intr_request = AccessTokenIntrospectRequest {
7757            token: oauth2_token.access_token.clone(),
7758            token_type_hint: None,
7759            client_post_auth: ClientPostAuth::default(),
7760        };
7761        let intr_response = idms_prox_read
7762            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7763            .expect("Failed to inspect token");
7764
7765        eprintln!("👉  {intr_response:?}");
7766        assert!(intr_response.active);
7767        assert_eq!(intr_response.scope, btreeset!["supplement".to_string()]);
7768        assert_eq!(
7769            intr_response.client_id.as_deref(),
7770            Some("test_resource_server")
7771        );
7772        assert_eq!(
7773            intr_response.username.as_deref(),
7774            Some("test_resource_server@example.com")
7775        );
7776        assert_eq!(intr_response.token_type, Some(AccessTokenType::Bearer));
7777        assert_eq!(intr_response.iat, Some(ct.as_secs() as i64));
7778        assert_eq!(intr_response.nbf, Some(ct.as_secs() as i64));
7779
7780        drop(idms_prox_read);
7781
7782        // Assert we can revoke.
7783        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7784        let revoke_request = TokenRevokeRequest {
7785            token: oauth2_token.access_token.clone(),
7786            token_type_hint: None,
7787            client_post_auth: ClientPostAuth::default(),
7788        };
7789        assert!(idms_prox_write
7790            .oauth2_token_revoke(&client_authz, &revoke_request, ct,)
7791            .is_ok());
7792        assert!(idms_prox_write.commit().is_ok());
7793
7794        // Now must be invalid.
7795        let ct = ct + AUTH_TOKEN_GRACE_WINDOW;
7796        let mut idms_prox_read = idms.proxy_read().await.unwrap();
7797
7798        let intr_request = AccessTokenIntrospectRequest {
7799            token: oauth2_token.access_token.clone(),
7800            token_type_hint: None,
7801            client_post_auth: ClientPostAuth::default(),
7802        };
7803
7804        let intr_response = idms_prox_read
7805            .check_oauth2_token_introspect(&client_authz, &intr_request, ct)
7806            .expect("Failed to inspect token");
7807        assert!(!intr_response.active);
7808
7809        drop(idms_prox_read);
7810    }
7811
7812    #[idm_test]
7813    async fn test_idm_oauth2_basic_client_credentials_grant_invalid(
7814        idms: &IdmServer,
7815        _idms_delayed: &mut IdmServerDelayed,
7816    ) {
7817        let ct = Duration::from_secs(TEST_CURRENT_TIME);
7818        let (secret, _uat, _ident, _) =
7819            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
7820
7821        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
7822
7823        // Public Client
7824        let token_req = AccessTokenRequest {
7825            grant_type: GrantTypeReq::ClientCredentials { scope: None },
7826            client_post_auth: ClientPostAuth {
7827                client_id: Some("test_resource_server".to_string()),
7828                client_secret: None,
7829            },
7830        };
7831
7832        assert_eq!(
7833            idms_prox_write
7834                .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7835                .unwrap_err(),
7836            Oauth2Error::AuthenticationRequired
7837        );
7838
7839        // Incorrect Password
7840        let token_req = AccessTokenRequest {
7841            grant_type: GrantTypeReq::ClientCredentials { scope: None },
7842
7843            client_post_auth: ClientPostAuth {
7844                client_id: Some("test_resource_server".to_string()),
7845                client_secret: Some("wrong password".to_string()),
7846            },
7847        };
7848
7849        assert_eq!(
7850            idms_prox_write
7851                .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7852                .unwrap_err(),
7853            Oauth2Error::AuthenticationRequired
7854        );
7855
7856        // Invalid scope
7857        let scope = Some(btreeset!["💅".to_string()]);
7858        let token_req = AccessTokenRequest {
7859            grant_type: GrantTypeReq::ClientCredentials { scope },
7860
7861            client_post_auth: ClientPostAuth {
7862                client_id: Some("test_resource_server".to_string()),
7863                client_secret: Some(secret.clone()),
7864            },
7865        };
7866
7867        assert_eq!(
7868            idms_prox_write
7869                .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7870                .unwrap_err(),
7871            Oauth2Error::InvalidScope
7872        );
7873
7874        // Scopes we aren't a member-of
7875        let scope = Some(btreeset!["invalid_scope".to_string()]);
7876        let token_req = AccessTokenRequest {
7877            grant_type: GrantTypeReq::ClientCredentials { scope },
7878
7879            client_post_auth: ClientPostAuth {
7880                client_id: Some("test_resource_server".to_string()),
7881                client_secret: Some(secret.clone()),
7882            },
7883        };
7884
7885        assert_eq!(
7886            idms_prox_write
7887                .check_oauth2_token_exchange(&ClientAuthInfo::none(), &token_req, ct)
7888                .unwrap_err(),
7889            Oauth2Error::AccessDenied
7890        );
7891
7892        assert!(idms_prox_write.commit().is_ok());
7893    }
7894
7895    #[test]
7896    fn test_get_code() {
7897        use super::{gen_device_code, gen_user_code, parse_user_code};
7898
7899        assert!(gen_device_code().is_ok());
7900
7901        let (res_string, res_value) = gen_user_code();
7902
7903        assert!(res_string.split('-').count() == 3);
7904
7905        let res_string_clean = res_string.replace("-", "");
7906        let res_string_as_num = res_string_clean
7907            .parse::<u32>()
7908            .expect("Failed to parse as number");
7909        assert_eq!(res_string_as_num, res_value);
7910
7911        assert_eq!(
7912            parse_user_code(&res_string).expect("Failed to parse code"),
7913            res_value
7914        );
7915    }
7916
7917    #[idm_test]
7918    async fn handle_oauth2_start_device_flow(
7919        idms: &IdmServer,
7920        _idms_delayed: &mut IdmServerDelayed,
7921    ) {
7922        let ct = duration_from_epoch_now();
7923
7924        let client_auth_info = ClientAuthInfo::from(Source::Https(
7925            "127.0.0.1"
7926                .parse()
7927                .expect("Failed to parse 127.0.0.1 as an IP!"),
7928        ));
7929        let eventid = Uuid::new_v4();
7930
7931        let res = idms
7932            .proxy_write(ct)
7933            .await
7934            .expect("Failed to get idmspwt")
7935            .handle_oauth2_start_device_flow(client_auth_info, "test_rs_id", &None, eventid);
7936        dbg!(&res);
7937        assert!(res.is_err());
7938    }
7939
7940    #[test]
7941    fn test_url_localhost_domain() {
7942        // ref #2390 - localhost with ports for OAuth2 redirect_uri
7943
7944        // ensure host_is_local isn't true for a non-local host
7945        let example_is_not_local = "https://example.com/sdfsdf";
7946        println!("Ensuring that {example_is_not_local} is not local");
7947        assert!(!host_is_local(
7948            &Url::parse(example_is_not_local)
7949                .expect("Failed to parse example.com as a host?")
7950                .host()
7951                .unwrap_or_else(|| panic!("Couldn't get a host from {example_is_not_local}"))
7952        ));
7953
7954        let test_urls = [
7955            ("http://localhost:8080/oauth2/callback", "/oauth2/callback"),
7956            ("https://localhost/foo/bar", "/foo/bar"),
7957            ("http://127.0.0.1:12345/foo", "/foo"),
7958            ("http://[::1]:12345/foo", "/foo"),
7959        ];
7960
7961        for (url, path) in test_urls.into_iter() {
7962            println!("Testing URL: {url}");
7963            let url = Url::parse(url).expect("One of the test values failed!");
7964            assert!(host_is_local(
7965                &url.host().expect("Didn't parse a host out?")
7966            ));
7967
7968            assert_eq!(url.path(), path);
7969        }
7970    }
7971
7972    #[test]
7973    fn test_oauth2_rs_type_allow_localhost_redirect() {
7974        let test_cases = [
7975            (
7976                OauthRSType::Public {
7977                    allow_localhost_redirect: true,
7978                },
7979                true,
7980            ),
7981            (
7982                OauthRSType::Public {
7983                    allow_localhost_redirect: false,
7984                },
7985                false,
7986            ),
7987            (
7988                OauthRSType::Basic {
7989                    authz_secret: "supersecret".to_string(),
7990                    enable_pkce: false,
7991                    enable_consent_prompt: true,
7992                },
7993                false,
7994            ),
7995        ];
7996
7997        assert!(test_cases.iter().all(|(rs_type, expected)| {
7998            let actual = rs_type.allow_localhost_redirect();
7999            println!("Testing {rs_type:?} -> {expected}");
8000            actual == *expected
8001        }));
8002    }
8003
8004    #[idm_test]
8005    async fn test_oauth2_auth_with_no_state(
8006        idms: &IdmServer,
8007        _idms_delayed: &mut IdmServerDelayed,
8008    ) {
8009        let ct = Duration::from_secs(TEST_CURRENT_TIME);
8010        let (_secret, _uat, ident, _) =
8011            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8012
8013        let idms_prox_read = idms.proxy_read().await.unwrap();
8014
8015        // == Setup the authorisation request
8016        let pkce_secret = PkceS256Secret::default();
8017
8018        let scope: BTreeSet<String> = OAUTH2_SCOPE_OPENID
8019            .split(" ")
8020            .map(|s| s.to_string())
8021            .collect();
8022
8023        let auth_req = AuthorisationRequest {
8024            response_type: ResponseType::Code,
8025            response_mode: None,
8026            client_id: "test_resource_server".to_string(),
8027            state: None,
8028            pkce_request: Some(pkce_secret.to_request()),
8029            redirect_uri: Url::parse("https://demo.example.com/oauth2/result").unwrap(),
8030            scope,
8031            nonce: Some("abcdef".to_string()),
8032            oidc_ext: Default::default(),
8033            max_age: None,
8034            unknown_keys: Default::default(),
8035        };
8036        println!("{auth_req:?}");
8037
8038        let consent_request = idms_prox_read
8039            .check_oauth2_authorisation(Some(&ident), &auth_req, ct)
8040            .expect("OAuth2 authorisation failed");
8041
8042        // Should be in the consent phase;
8043        let AuthoriseResponse::ConsentRequested { .. } = consent_request else {
8044            unreachable!("Expected a ConsentRequested response, got: {consent_request:?}");
8045        };
8046    }
8047
8048    #[idm_test]
8049    async fn test_idm_oauth2_consent_prompt_disabled(
8050        idms: &IdmServer,
8051        _idms_delayed: &mut IdmServerDelayed,
8052    ) {
8053        let ct = Duration::from_secs(TEST_CURRENT_TIME);
8054        let (_secret, _uat, ident, o2rs_uuid) =
8055            setup_oauth2_resource_server_basic(idms, ct, true, false, false).await;
8056
8057        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
8058        idms_prox_write
8059            .qs_write
8060            .internal_modify_uuid(
8061                o2rs_uuid,
8062                &ModifyList::new_purge_and_set(
8063                    Attribute::OAuth2ConsentPromptEnable,
8064                    Value::new_bool(false),
8065                ),
8066            )
8067            .expect("Unable to disable consent prompt");
8068        assert!(idms_prox_write.commit().is_ok());
8069
8070        // Assert there are no consent maps yet so consent should be required if not disabled
8071        assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
8072
8073        let idms_prox_read = idms.proxy_read().await.unwrap();
8074
8075        let pkce_secret = PkceS256Secret::default();
8076        let consent_request = good_authorisation_request!(
8077            idms_prox_read,
8078            &ident,
8079            ct,
8080            pkce_secret.to_request(),
8081            OAUTH2_SCOPE_OPENID.to_string()
8082        );
8083
8084        // Should be permitted
8085        let AuthoriseResponse::Permitted(_permitted) = consent_request else {
8086            unreachable!();
8087        };
8088
8089        // Assert that it still doesn't have any consent maps
8090        assert!(ident.get_oauth2_consent_scopes(o2rs_uuid).is_none());
8091    }
8092}