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