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