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