mas_handlers/oauth2/
token.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2021-2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7use std::sync::{Arc, LazyLock};
8
9use axum::{Json, extract::State, response::IntoResponse};
10use axum_extra::typed_header::TypedHeader;
11use chrono::Duration;
12use headers::{CacheControl, HeaderMap, HeaderMapExt, Pragma};
13use hyper::StatusCode;
14use mas_axum_utils::{
15    client_authorization::{ClientAuthorization, CredentialsVerificationError},
16    record_error,
17};
18use mas_data_model::{
19    AuthorizationGrantStage, Client, Device, DeviceCodeGrantState, SiteConfig, TokenType,
20};
21use mas_keystore::{Encrypter, Keystore};
22use mas_matrix::HomeserverConnection;
23use mas_oidc_client::types::scope::ScopeToken;
24use mas_policy::Policy;
25use mas_router::UrlBuilder;
26use mas_storage::{
27    BoxClock, BoxRepository, BoxRng, Clock, RepositoryAccess,
28    oauth2::{
29        OAuth2AccessTokenRepository, OAuth2AuthorizationGrantRepository,
30        OAuth2RefreshTokenRepository, OAuth2SessionRepository,
31    },
32    user::BrowserSessionRepository,
33};
34use oauth2_types::{
35    errors::{ClientError, ClientErrorCode},
36    pkce::CodeChallengeError,
37    requests::{
38        AccessTokenRequest, AccessTokenResponse, AuthorizationCodeGrant, ClientCredentialsGrant,
39        DeviceCodeGrant, GrantType, RefreshTokenGrant,
40    },
41    scope,
42};
43use opentelemetry::{Key, KeyValue, metrics::Counter};
44use thiserror::Error;
45use tracing::{debug, info, warn};
46use ulid::Ulid;
47
48use super::{generate_id_token, generate_token_pair};
49use crate::{BoundActivityTracker, METER, impl_from_error_for_route};
50
51static TOKEN_REQUEST_COUNTER: LazyLock<Counter<u64>> = LazyLock::new(|| {
52    METER
53        .u64_counter("mas.oauth2.token_request")
54        .with_description("How many OAuth 2.0 token requests have gone through")
55        .with_unit("{request}")
56        .build()
57});
58const GRANT_TYPE: Key = Key::from_static_str("grant_type");
59const RESULT: Key = Key::from_static_str("successful");
60
61#[derive(Debug, Error)]
62pub(crate) enum RouteError {
63    #[error(transparent)]
64    Internal(Box<dyn std::error::Error + Send + Sync + 'static>),
65
66    #[error("bad request")]
67    BadRequest,
68
69    #[error("pkce verification failed")]
70    PkceVerification(#[from] CodeChallengeError),
71
72    #[error("client not found")]
73    ClientNotFound,
74
75    #[error("client not allowed to use the token endpoint: {0}")]
76    ClientNotAllowed(Ulid),
77
78    #[error("invalid client credentials for client {client_id}")]
79    InvalidClientCredentials {
80        client_id: Ulid,
81        #[source]
82        source: CredentialsVerificationError,
83    },
84
85    #[error("could not verify client credentials for client {client_id}")]
86    ClientCredentialsVerification {
87        client_id: Ulid,
88        #[source]
89        source: CredentialsVerificationError,
90    },
91
92    #[error("grant not found")]
93    GrantNotFound,
94
95    #[error("invalid grant {0}")]
96    InvalidGrant(Ulid),
97
98    #[error("refresh token not found")]
99    RefreshTokenNotFound,
100
101    #[error("refresh token {0} is invalid")]
102    RefreshTokenInvalid(Ulid),
103
104    #[error("session {0} is invalid")]
105    SessionInvalid(Ulid),
106
107    #[error("client id mismatch: expected {expected}, got {actual}")]
108    ClientIDMismatch { expected: Ulid, actual: Ulid },
109
110    #[error("policy denied the request: {0}")]
111    DeniedByPolicy(mas_policy::EvaluationResult),
112
113    #[error("unsupported grant type")]
114    UnsupportedGrantType,
115
116    #[error("client {0} is not authorized to use this grant type")]
117    UnauthorizedClient(Ulid),
118
119    #[error("unexpected client {was} (expected {expected})")]
120    UnexptectedClient { was: Ulid, expected: Ulid },
121
122    #[error("failed to load browser session {0}")]
123    NoSuchBrowserSession(Ulid),
124
125    #[error("failed to load oauth session {0}")]
126    NoSuchOAuthSession(Ulid),
127
128    #[error(
129        "failed to load the next refresh token ({next:?}) from the previous one ({previous:?})"
130    )]
131    NoSuchNextRefreshToken { next: Ulid, previous: Ulid },
132
133    #[error(
134        "failed to load the access token ({access_token:?}) associated with the next refresh token ({refresh_token:?})"
135    )]
136    NoSuchNextAccessToken {
137        access_token: Ulid,
138        refresh_token: Ulid,
139    },
140
141    #[error("no access token associated with the refresh token {refresh_token:?}")]
142    NoAccessTokenOnRefreshToken { refresh_token: Ulid },
143
144    #[error("device code grant expired")]
145    DeviceCodeExpired,
146
147    #[error("device code grant is still pending")]
148    DeviceCodePending,
149
150    #[error("device code grant was rejected")]
151    DeviceCodeRejected,
152
153    #[error("device code grant was already exchanged")]
154    DeviceCodeExchanged,
155
156    #[error("failed to provision device")]
157    ProvisionDeviceFailed(#[source] anyhow::Error),
158}
159
160impl IntoResponse for RouteError {
161    fn into_response(self) -> axum::response::Response {
162        let sentry_event_id = record_error!(
163            self,
164            Self::Internal(_)
165                | Self::ClientCredentialsVerification { .. }
166                | Self::NoSuchBrowserSession(_)
167                | Self::NoSuchOAuthSession(_)
168                | Self::ProvisionDeviceFailed(_)
169                | Self::NoSuchNextRefreshToken { .. }
170                | Self::NoSuchNextAccessToken { .. }
171                | Self::NoAccessTokenOnRefreshToken { .. }
172        );
173
174        TOKEN_REQUEST_COUNTER.add(1, &[KeyValue::new(RESULT, "error")]);
175
176        let response = match self {
177            Self::Internal(_)
178            | Self::ClientCredentialsVerification { .. }
179            | Self::NoSuchBrowserSession(_)
180            | Self::NoSuchOAuthSession(_)
181            | Self::ProvisionDeviceFailed(_)
182            | Self::NoSuchNextRefreshToken { .. }
183            | Self::NoSuchNextAccessToken { .. }
184            | Self::NoAccessTokenOnRefreshToken { .. } => (
185                StatusCode::INTERNAL_SERVER_ERROR,
186                Json(ClientError::from(ClientErrorCode::ServerError)),
187            ),
188
189            Self::BadRequest => (
190                StatusCode::BAD_REQUEST,
191                Json(ClientError::from(ClientErrorCode::InvalidRequest)),
192            ),
193
194            Self::PkceVerification(err) => (
195                StatusCode::BAD_REQUEST,
196                Json(
197                    ClientError::from(ClientErrorCode::InvalidGrant)
198                        .with_description(format!("PKCE verification failed: {err}")),
199                ),
200            ),
201
202            Self::ClientNotFound | Self::InvalidClientCredentials { .. } => (
203                StatusCode::UNAUTHORIZED,
204                Json(ClientError::from(ClientErrorCode::InvalidClient)),
205            ),
206
207            Self::ClientNotAllowed(_)
208            | Self::UnauthorizedClient(_)
209            | Self::UnexptectedClient { .. } => (
210                StatusCode::UNAUTHORIZED,
211                Json(ClientError::from(ClientErrorCode::UnauthorizedClient)),
212            ),
213
214            Self::DeniedByPolicy(evaluation) => (
215                StatusCode::FORBIDDEN,
216                Json(
217                    ClientError::from(ClientErrorCode::InvalidScope).with_description(
218                        evaluation
219                            .violations
220                            .into_iter()
221                            .map(|violation| violation.msg)
222                            .collect::<Vec<_>>()
223                            .join(", "),
224                    ),
225                ),
226            ),
227
228            Self::DeviceCodeRejected => (
229                StatusCode::FORBIDDEN,
230                Json(ClientError::from(ClientErrorCode::AccessDenied)),
231            ),
232
233            Self::DeviceCodeExpired => (
234                StatusCode::FORBIDDEN,
235                Json(ClientError::from(ClientErrorCode::ExpiredToken)),
236            ),
237
238            Self::DeviceCodePending => (
239                StatusCode::FORBIDDEN,
240                Json(ClientError::from(ClientErrorCode::AuthorizationPending)),
241            ),
242
243            Self::InvalidGrant(_)
244            | Self::DeviceCodeExchanged
245            | Self::RefreshTokenNotFound
246            | Self::RefreshTokenInvalid(_)
247            | Self::SessionInvalid(_)
248            | Self::ClientIDMismatch { .. }
249            | Self::GrantNotFound => (
250                StatusCode::BAD_REQUEST,
251                Json(ClientError::from(ClientErrorCode::InvalidGrant)),
252            ),
253
254            Self::UnsupportedGrantType => (
255                StatusCode::BAD_REQUEST,
256                Json(ClientError::from(ClientErrorCode::UnsupportedGrantType)),
257            ),
258        };
259
260        (sentry_event_id, response).into_response()
261    }
262}
263
264impl_from_error_for_route!(mas_storage::RepositoryError);
265impl_from_error_for_route!(mas_policy::EvaluationError);
266impl_from_error_for_route!(super::IdTokenSignatureError);
267
268#[tracing::instrument(
269    name = "handlers.oauth2.token.post",
270    fields(client.id = client_authorization.client_id()),
271    skip_all,
272)]
273pub(crate) async fn post(
274    mut rng: BoxRng,
275    clock: BoxClock,
276    State(http_client): State<reqwest::Client>,
277    State(key_store): State<Keystore>,
278    State(url_builder): State<UrlBuilder>,
279    activity_tracker: BoundActivityTracker,
280    mut repo: BoxRepository,
281    State(homeserver): State<Arc<dyn HomeserverConnection>>,
282    State(site_config): State<SiteConfig>,
283    State(encrypter): State<Encrypter>,
284    policy: Policy,
285    user_agent: Option<TypedHeader<headers::UserAgent>>,
286    client_authorization: ClientAuthorization<AccessTokenRequest>,
287) -> Result<impl IntoResponse, RouteError> {
288    let user_agent = user_agent.map(|ua| ua.as_str().to_owned());
289    let client = client_authorization
290        .credentials
291        .fetch(&mut repo)
292        .await?
293        .ok_or(RouteError::ClientNotFound)?;
294
295    let method = client
296        .token_endpoint_auth_method
297        .as_ref()
298        .ok_or(RouteError::ClientNotAllowed(client.id))?;
299
300    client_authorization
301        .credentials
302        .verify(&http_client, &encrypter, method, &client)
303        .await
304        .map_err(|err| {
305            // Classify the error differntly, depending on whether it's an 'internal' error,
306            // or just because the client presented invalid credentials.
307            if err.is_internal() {
308                RouteError::ClientCredentialsVerification {
309                    client_id: client.id,
310                    source: err,
311                }
312            } else {
313                RouteError::InvalidClientCredentials {
314                    client_id: client.id,
315                    source: err,
316                }
317            }
318        })?;
319
320    let form = client_authorization.form.ok_or(RouteError::BadRequest)?;
321
322    let grant_type = form.grant_type();
323
324    let (reply, repo) = match form {
325        AccessTokenRequest::AuthorizationCode(grant) => {
326            authorization_code_grant(
327                &mut rng,
328                &clock,
329                &activity_tracker,
330                &grant,
331                &client,
332                &key_store,
333                &url_builder,
334                &site_config,
335                repo,
336                &homeserver,
337                user_agent,
338            )
339            .await?
340        }
341        AccessTokenRequest::RefreshToken(grant) => {
342            refresh_token_grant(
343                &mut rng,
344                &clock,
345                &activity_tracker,
346                &grant,
347                &client,
348                &site_config,
349                repo,
350                user_agent,
351            )
352            .await?
353        }
354        AccessTokenRequest::ClientCredentials(grant) => {
355            client_credentials_grant(
356                &mut rng,
357                &clock,
358                &activity_tracker,
359                &grant,
360                &client,
361                &site_config,
362                repo,
363                policy,
364                user_agent,
365            )
366            .await?
367        }
368        AccessTokenRequest::DeviceCode(grant) => {
369            device_code_grant(
370                &mut rng,
371                &clock,
372                &activity_tracker,
373                &grant,
374                &client,
375                &key_store,
376                &url_builder,
377                &site_config,
378                repo,
379                &homeserver,
380                user_agent,
381            )
382            .await?
383        }
384        _ => {
385            return Err(RouteError::UnsupportedGrantType);
386        }
387    };
388
389    repo.save().await?;
390
391    TOKEN_REQUEST_COUNTER.add(
392        1,
393        &[
394            KeyValue::new(GRANT_TYPE, grant_type),
395            KeyValue::new(RESULT, "success"),
396        ],
397    );
398
399    let mut headers = HeaderMap::new();
400    headers.typed_insert(CacheControl::new().with_no_store());
401    headers.typed_insert(Pragma::no_cache());
402
403    Ok((headers, Json(reply)))
404}
405
406#[allow(clippy::too_many_lines)] // TODO: refactor some parts out
407async fn authorization_code_grant(
408    mut rng: &mut BoxRng,
409    clock: &impl Clock,
410    activity_tracker: &BoundActivityTracker,
411    grant: &AuthorizationCodeGrant,
412    client: &Client,
413    key_store: &Keystore,
414    url_builder: &UrlBuilder,
415    site_config: &SiteConfig,
416    mut repo: BoxRepository,
417    homeserver: &Arc<dyn HomeserverConnection>,
418    user_agent: Option<String>,
419) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
420    // Check that the client is allowed to use this grant type
421    if !client.grant_types.contains(&GrantType::AuthorizationCode) {
422        return Err(RouteError::UnauthorizedClient(client.id));
423    }
424
425    let authz_grant = repo
426        .oauth2_authorization_grant()
427        .find_by_code(&grant.code)
428        .await?
429        .ok_or(RouteError::GrantNotFound)?;
430
431    let now = clock.now();
432
433    let session_id = match authz_grant.stage {
434        AuthorizationGrantStage::Cancelled { cancelled_at } => {
435            debug!(%cancelled_at, "Authorization grant was cancelled");
436            return Err(RouteError::InvalidGrant(authz_grant.id));
437        }
438        AuthorizationGrantStage::Exchanged {
439            exchanged_at,
440            fulfilled_at,
441            session_id,
442        } => {
443            warn!(%exchanged_at, %fulfilled_at, "Authorization code was already exchanged");
444
445            // Ending the session if the token was already exchanged more than 20s ago
446            if now - exchanged_at > Duration::microseconds(20 * 1000 * 1000) {
447                warn!(oauth_session.id = %session_id, "Ending potentially compromised session");
448                let session = repo
449                    .oauth2_session()
450                    .lookup(session_id)
451                    .await?
452                    .ok_or(RouteError::NoSuchOAuthSession(session_id))?;
453
454                //if !session.is_finished() {
455                repo.oauth2_session().finish(clock, session).await?;
456                repo.save().await?;
457                //}
458            }
459
460            return Err(RouteError::InvalidGrant(authz_grant.id));
461        }
462        AuthorizationGrantStage::Pending => {
463            warn!("Authorization grant has not been fulfilled yet");
464            return Err(RouteError::InvalidGrant(authz_grant.id));
465        }
466        AuthorizationGrantStage::Fulfilled {
467            session_id,
468            fulfilled_at,
469        } => {
470            if now - fulfilled_at > Duration::microseconds(10 * 60 * 1000 * 1000) {
471                warn!("Code exchange took more than 10 minutes");
472                return Err(RouteError::InvalidGrant(authz_grant.id));
473            }
474
475            session_id
476        }
477    };
478
479    let mut session = repo
480        .oauth2_session()
481        .lookup(session_id)
482        .await?
483        .ok_or(RouteError::NoSuchOAuthSession(session_id))?;
484
485    if let Some(user_agent) = user_agent {
486        session = repo
487            .oauth2_session()
488            .record_user_agent(session, user_agent)
489            .await?;
490    }
491
492    // This should never happen, since we looked up in the database using the code
493    let code = authz_grant
494        .code
495        .as_ref()
496        .ok_or(RouteError::InvalidGrant(authz_grant.id))?;
497
498    if client.id != session.client_id {
499        return Err(RouteError::UnexptectedClient {
500            was: client.id,
501            expected: session.client_id,
502        });
503    }
504
505    match (code.pkce.as_ref(), grant.code_verifier.as_ref()) {
506        (None, None) => {}
507        // We have a challenge but no verifier (or vice-versa)? Bad request.
508        (Some(_), None) | (None, Some(_)) => return Err(RouteError::BadRequest),
509        // If we have both, we need to check the code validity
510        (Some(pkce), Some(verifier)) => {
511            pkce.verify(verifier)?;
512        }
513    }
514
515    let Some(user_session_id) = session.user_session_id else {
516        tracing::warn!("No user session associated with this OAuth2 session");
517        return Err(RouteError::InvalidGrant(authz_grant.id));
518    };
519
520    let browser_session = repo
521        .browser_session()
522        .lookup(user_session_id)
523        .await?
524        .ok_or(RouteError::NoSuchBrowserSession(user_session_id))?;
525
526    let last_authentication = repo
527        .browser_session()
528        .get_last_authentication(&browser_session)
529        .await?;
530
531    let ttl = site_config.access_token_ttl;
532    let (access_token, refresh_token) =
533        generate_token_pair(&mut rng, clock, &mut repo, &session, ttl).await?;
534
535    let id_token = if session.scope.contains(&scope::OPENID) {
536        Some(generate_id_token(
537            &mut rng,
538            clock,
539            url_builder,
540            key_store,
541            client,
542            Some(&authz_grant),
543            &browser_session,
544            Some(&access_token),
545            last_authentication.as_ref(),
546        )?)
547    } else {
548        None
549    };
550
551    let mut params = AccessTokenResponse::new(access_token.access_token)
552        .with_expires_in(ttl)
553        .with_refresh_token(refresh_token.refresh_token)
554        .with_scope(session.scope.clone());
555
556    if let Some(id_token) = id_token {
557        params = params.with_id_token(id_token);
558    }
559
560    // Lock the user sync to make sure we don't get into a race condition
561    repo.user()
562        .acquire_lock_for_sync(&browser_session.user)
563        .await?;
564
565    // Look for device to provision
566    let mxid = homeserver.mxid(&browser_session.user.username);
567    for scope in &*session.scope {
568        if let Some(device) = Device::from_scope_token(scope) {
569            homeserver
570                .create_device(&mxid, device.as_str())
571                .await
572                .map_err(RouteError::ProvisionDeviceFailed)?;
573        }
574    }
575
576    repo.oauth2_authorization_grant()
577        .exchange(clock, authz_grant)
578        .await?;
579
580    // XXX: there is a potential (but unlikely) race here, where the activity for
581    // the session is recorded before the transaction is committed. We would have to
582    // save the repository here to fix that.
583    activity_tracker
584        .record_oauth2_session(clock, &session)
585        .await;
586
587    Ok((params, repo))
588}
589
590#[allow(clippy::too_many_lines)]
591async fn refresh_token_grant(
592    rng: &mut BoxRng,
593    clock: &impl Clock,
594    activity_tracker: &BoundActivityTracker,
595    grant: &RefreshTokenGrant,
596    client: &Client,
597    site_config: &SiteConfig,
598    mut repo: BoxRepository,
599    user_agent: Option<String>,
600) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
601    // Check that the client is allowed to use this grant type
602    if !client.grant_types.contains(&GrantType::RefreshToken) {
603        return Err(RouteError::UnauthorizedClient(client.id));
604    }
605
606    let refresh_token = repo
607        .oauth2_refresh_token()
608        .find_by_token(&grant.refresh_token)
609        .await?
610        .ok_or(RouteError::RefreshTokenNotFound)?;
611
612    let mut session = repo
613        .oauth2_session()
614        .lookup(refresh_token.session_id)
615        .await?
616        .ok_or(RouteError::NoSuchOAuthSession(refresh_token.session_id))?;
617
618    // Let's for now record the user agent on each refresh, that should be
619    // responsive enough and not too much of a burden on the database.
620    if let Some(user_agent) = user_agent {
621        session = repo
622            .oauth2_session()
623            .record_user_agent(session, user_agent)
624            .await?;
625    }
626
627    if !session.is_valid() {
628        return Err(RouteError::SessionInvalid(session.id));
629    }
630
631    if client.id != session.client_id {
632        // As per https://datatracker.ietf.org/doc/html/rfc6749#section-5.2
633        return Err(RouteError::ClientIDMismatch {
634            expected: session.client_id,
635            actual: client.id,
636        });
637    }
638
639    if !refresh_token.is_valid() {
640        // We're seing a refresh token that already has been consumed, this might be a
641        // double-refresh or a replay attack
642
643        // First, get the next refresh token
644        let Some(next_refresh_token_id) = refresh_token.next_refresh_token_id() else {
645            // If we don't have a 'next' refresh token, it may just be because this was
646            // before we were recording those. Let's just treat it as a replay.
647            return Err(RouteError::RefreshTokenInvalid(refresh_token.id));
648        };
649
650        let Some(next_refresh_token) = repo
651            .oauth2_refresh_token()
652            .lookup(next_refresh_token_id)
653            .await?
654        else {
655            return Err(RouteError::NoSuchNextRefreshToken {
656                next: next_refresh_token_id,
657                previous: refresh_token.id,
658            });
659        };
660
661        // Check if the next refresh token was already consumed or not
662        if !next_refresh_token.is_valid() {
663            // XXX: This is a replay, we *may* want to invalidate the session
664            return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
665        }
666
667        // Check if the associated access token was already used
668        let Some(access_token_id) = next_refresh_token.access_token_id else {
669            // This should in theory not happen: this means an access token got cleaned up,
670            // but the refresh token was still valid.
671            return Err(RouteError::NoAccessTokenOnRefreshToken {
672                refresh_token: next_refresh_token.id,
673            });
674        };
675
676        // Load it
677        let next_access_token = repo
678            .oauth2_access_token()
679            .lookup(access_token_id)
680            .await?
681            .ok_or(RouteError::NoSuchNextAccessToken {
682                access_token: access_token_id,
683                refresh_token: next_refresh_token_id,
684            })?;
685
686        if next_access_token.is_used() {
687            // XXX: This is a replay, we *may* want to invalidate the session
688            return Err(RouteError::RefreshTokenInvalid(next_refresh_token.id));
689        }
690
691        // Looks like it's a double-refresh, client lost their refresh token on
692        // the way back. Let's revoke the unused access and refresh tokens, and
693        // issue new ones
694        info!(
695            oauth2_session.id = %session.id,
696            oauth2_client.id = %client.id,
697            %refresh_token.id,
698            "Refresh token already used, but issued refresh and access tokens are unused. Assuming those were lost; revoking those and reissuing new ones."
699        );
700
701        repo.oauth2_access_token()
702            .revoke(clock, next_access_token)
703            .await?;
704
705        repo.oauth2_refresh_token()
706            .revoke(clock, next_refresh_token)
707            .await?;
708    }
709
710    activity_tracker
711        .record_oauth2_session(clock, &session)
712        .await;
713
714    let ttl = site_config.access_token_ttl;
715    let (new_access_token, new_refresh_token) =
716        generate_token_pair(rng, clock, &mut repo, &session, ttl).await?;
717
718    let refresh_token = repo
719        .oauth2_refresh_token()
720        .consume(clock, refresh_token, &new_refresh_token)
721        .await?;
722
723    if let Some(access_token_id) = refresh_token.access_token_id {
724        let access_token = repo.oauth2_access_token().lookup(access_token_id).await?;
725        if let Some(access_token) = access_token {
726            // If it is a double-refresh, it might already be revoked
727            if !access_token.state.is_revoked() {
728                repo.oauth2_access_token()
729                    .revoke(clock, access_token)
730                    .await?;
731            }
732        }
733    }
734
735    let params = AccessTokenResponse::new(new_access_token.access_token)
736        .with_expires_in(ttl)
737        .with_refresh_token(new_refresh_token.refresh_token)
738        .with_scope(session.scope);
739
740    Ok((params, repo))
741}
742
743async fn client_credentials_grant(
744    rng: &mut BoxRng,
745    clock: &impl Clock,
746    activity_tracker: &BoundActivityTracker,
747    grant: &ClientCredentialsGrant,
748    client: &Client,
749    site_config: &SiteConfig,
750    mut repo: BoxRepository,
751    mut policy: Policy,
752    user_agent: Option<String>,
753) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
754    // Check that the client is allowed to use this grant type
755    if !client.grant_types.contains(&GrantType::ClientCredentials) {
756        return Err(RouteError::UnauthorizedClient(client.id));
757    }
758
759    // Default to an empty scope if none is provided
760    let scope = grant
761        .scope
762        .clone()
763        .unwrap_or_else(|| std::iter::empty::<ScopeToken>().collect());
764
765    // Make the request go through the policy engine
766    let res = policy
767        .evaluate_authorization_grant(mas_policy::AuthorizationGrantInput {
768            user: None,
769            client,
770            scope: &scope,
771            grant_type: mas_policy::GrantType::ClientCredentials,
772            requester: mas_policy::Requester {
773                ip_address: activity_tracker.ip(),
774                user_agent: user_agent.clone(),
775            },
776        })
777        .await?;
778    if !res.valid() {
779        return Err(RouteError::DeniedByPolicy(res));
780    }
781
782    // Start the session
783    let mut session = repo
784        .oauth2_session()
785        .add_from_client_credentials(rng, clock, client, scope)
786        .await?;
787
788    if let Some(user_agent) = user_agent {
789        session = repo
790            .oauth2_session()
791            .record_user_agent(session, user_agent)
792            .await?;
793    }
794
795    let ttl = site_config.access_token_ttl;
796    let access_token_str = TokenType::AccessToken.generate(rng);
797
798    let access_token = repo
799        .oauth2_access_token()
800        .add(rng, clock, &session, access_token_str, Some(ttl))
801        .await?;
802
803    let mut params = AccessTokenResponse::new(access_token.access_token).with_expires_in(ttl);
804
805    // XXX: there is a potential (but unlikely) race here, where the activity for
806    // the session is recorded before the transaction is committed. We would have to
807    // save the repository here to fix that.
808    activity_tracker
809        .record_oauth2_session(clock, &session)
810        .await;
811
812    if !session.scope.is_empty() {
813        // We only return the scope if it's not empty
814        params = params.with_scope(session.scope);
815    }
816
817    Ok((params, repo))
818}
819
820async fn device_code_grant(
821    rng: &mut BoxRng,
822    clock: &impl Clock,
823    activity_tracker: &BoundActivityTracker,
824    grant: &DeviceCodeGrant,
825    client: &Client,
826    key_store: &Keystore,
827    url_builder: &UrlBuilder,
828    site_config: &SiteConfig,
829    mut repo: BoxRepository,
830    homeserver: &Arc<dyn HomeserverConnection>,
831    user_agent: Option<String>,
832) -> Result<(AccessTokenResponse, BoxRepository), RouteError> {
833    // Check that the client is allowed to use this grant type
834    if !client.grant_types.contains(&GrantType::DeviceCode) {
835        return Err(RouteError::UnauthorizedClient(client.id));
836    }
837
838    let grant = repo
839        .oauth2_device_code_grant()
840        .find_by_device_code(&grant.device_code)
841        .await?
842        .ok_or(RouteError::GrantNotFound)?;
843
844    // Check that the client match
845    if client.id != grant.client_id {
846        return Err(RouteError::ClientIDMismatch {
847            expected: grant.client_id,
848            actual: client.id,
849        });
850    }
851
852    if grant.expires_at < clock.now() {
853        return Err(RouteError::DeviceCodeExpired);
854    }
855
856    let browser_session_id = match &grant.state {
857        DeviceCodeGrantState::Pending => {
858            return Err(RouteError::DeviceCodePending);
859        }
860        DeviceCodeGrantState::Rejected { .. } => {
861            return Err(RouteError::DeviceCodeRejected);
862        }
863        DeviceCodeGrantState::Exchanged { .. } => {
864            return Err(RouteError::DeviceCodeExchanged);
865        }
866        DeviceCodeGrantState::Fulfilled {
867            browser_session_id, ..
868        } => *browser_session_id,
869    };
870
871    let browser_session = repo
872        .browser_session()
873        .lookup(browser_session_id)
874        .await?
875        .ok_or(RouteError::NoSuchBrowserSession(browser_session_id))?;
876
877    // Start the session
878    let mut session = repo
879        .oauth2_session()
880        .add_from_browser_session(rng, clock, client, &browser_session, grant.scope.clone())
881        .await?;
882
883    repo.oauth2_device_code_grant()
884        .exchange(clock, grant, &session)
885        .await?;
886
887    // XXX: should we get the user agent from the device code grant instead?
888    if let Some(user_agent) = user_agent {
889        session = repo
890            .oauth2_session()
891            .record_user_agent(session, user_agent)
892            .await?;
893    }
894
895    let ttl = site_config.access_token_ttl;
896    let access_token_str = TokenType::AccessToken.generate(rng);
897
898    let access_token = repo
899        .oauth2_access_token()
900        .add(rng, clock, &session, access_token_str, Some(ttl))
901        .await?;
902
903    let mut params =
904        AccessTokenResponse::new(access_token.access_token.clone()).with_expires_in(ttl);
905
906    // If the client uses the refresh token grant type, we also generate a refresh
907    // token
908    if client.grant_types.contains(&GrantType::RefreshToken) {
909        let refresh_token_str = TokenType::RefreshToken.generate(rng);
910
911        let refresh_token = repo
912            .oauth2_refresh_token()
913            .add(rng, clock, &session, &access_token, refresh_token_str)
914            .await?;
915
916        params = params.with_refresh_token(refresh_token.refresh_token);
917    }
918
919    // If the client asked for an ID token, we generate one
920    if session.scope.contains(&scope::OPENID) {
921        let id_token = generate_id_token(
922            rng,
923            clock,
924            url_builder,
925            key_store,
926            client,
927            None,
928            &browser_session,
929            Some(&access_token),
930            None,
931        )?;
932
933        params = params.with_id_token(id_token);
934    }
935
936    // Lock the user sync to make sure we don't get into a race condition
937    repo.user()
938        .acquire_lock_for_sync(&browser_session.user)
939        .await?;
940
941    // Look for device to provision
942    let mxid = homeserver.mxid(&browser_session.user.username);
943    for scope in &*session.scope {
944        if let Some(device) = Device::from_scope_token(scope) {
945            homeserver
946                .create_device(&mxid, device.as_str())
947                .await
948                .map_err(RouteError::ProvisionDeviceFailed)?;
949        }
950    }
951
952    // XXX: there is a potential (but unlikely) race here, where the activity for
953    // the session is recorded before the transaction is committed. We would have to
954    // save the repository here to fix that.
955    activity_tracker
956        .record_oauth2_session(clock, &session)
957        .await;
958
959    if !session.scope.is_empty() {
960        // We only return the scope if it's not empty
961        params = params.with_scope(session.scope);
962    }
963
964    Ok((params, repo))
965}
966
967#[cfg(test)]
968mod tests {
969    use hyper::Request;
970    use mas_data_model::{AccessToken, AuthorizationCode, RefreshToken};
971    use mas_router::SimpleRoute;
972    use oauth2_types::{
973        registration::ClientRegistrationResponse,
974        requests::{DeviceAuthorizationResponse, ResponseMode},
975        scope::{OPENID, Scope},
976    };
977    use sqlx::PgPool;
978
979    use super::*;
980    use crate::test_utils::{RequestBuilderExt, ResponseExt, TestState, setup};
981
982    #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
983    async fn test_auth_code_grant(pool: PgPool) {
984        setup();
985        let state = TestState::from_pool(pool).await.unwrap();
986
987        // Provision a client
988        let request =
989            Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
990                "client_uri": "https://example.com/",
991                "redirect_uris": ["https://example.com/callback"],
992                "token_endpoint_auth_method": "none",
993                "response_types": ["code"],
994                "grant_types": ["authorization_code"],
995            }));
996
997        let response = state.request(request).await;
998        response.assert_status(StatusCode::CREATED);
999
1000        let ClientRegistrationResponse { client_id, .. } = response.json();
1001
1002        // Let's provision a user and create a session for them. This part is hard to
1003        // test with just HTTP requests, so we'll use the repository directly.
1004        let mut repo = state.repository().await.unwrap();
1005
1006        let user = repo
1007            .user()
1008            .add(&mut state.rng(), &state.clock, "alice".to_owned())
1009            .await
1010            .unwrap();
1011
1012        let browser_session = repo
1013            .browser_session()
1014            .add(&mut state.rng(), &state.clock, &user, None)
1015            .await
1016            .unwrap();
1017
1018        // Lookup the client in the database.
1019        let client = repo
1020            .oauth2_client()
1021            .find_by_client_id(&client_id)
1022            .await
1023            .unwrap()
1024            .unwrap();
1025
1026        // Start a grant
1027        let code = "thisisaverysecurecode";
1028        let grant = repo
1029            .oauth2_authorization_grant()
1030            .add(
1031                &mut state.rng(),
1032                &state.clock,
1033                &client,
1034                "https://example.com/redirect".parse().unwrap(),
1035                Scope::from_iter([OPENID]),
1036                Some(AuthorizationCode {
1037                    code: code.to_owned(),
1038                    pkce: None,
1039                }),
1040                Some("state".to_owned()),
1041                Some("nonce".to_owned()),
1042                ResponseMode::Query,
1043                false,
1044                None,
1045            )
1046            .await
1047            .unwrap();
1048
1049        let session = repo
1050            .oauth2_session()
1051            .add_from_browser_session(
1052                &mut state.rng(),
1053                &state.clock,
1054                &client,
1055                &browser_session,
1056                grant.scope.clone(),
1057            )
1058            .await
1059            .unwrap();
1060
1061        // And fulfill it
1062        let grant = repo
1063            .oauth2_authorization_grant()
1064            .fulfill(&state.clock, &session, grant)
1065            .await
1066            .unwrap();
1067
1068        repo.save().await.unwrap();
1069
1070        // Now call the token endpoint to get an access token.
1071        let request =
1072            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1073                "grant_type": "authorization_code",
1074                "code": code,
1075                "redirect_uri": grant.redirect_uri,
1076                "client_id": client.client_id,
1077            }));
1078
1079        let response = state.request(request).await;
1080        response.assert_status(StatusCode::OK);
1081
1082        let AccessTokenResponse { access_token, .. } = response.json();
1083
1084        // Check that the token is valid
1085        assert!(state.is_access_token_valid(&access_token).await);
1086
1087        // Exchange it again, this it should fail
1088        let request =
1089            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1090                "grant_type": "authorization_code",
1091                "code": code,
1092                "redirect_uri": grant.redirect_uri,
1093                "client_id": client.client_id,
1094            }));
1095
1096        let response = state.request(request).await;
1097        response.assert_status(StatusCode::BAD_REQUEST);
1098        let error: ClientError = response.json();
1099        assert_eq!(error.error, ClientErrorCode::InvalidGrant);
1100
1101        // The token should still be valid
1102        assert!(state.is_access_token_valid(&access_token).await);
1103
1104        // Now wait a bit
1105        state.clock.advance(Duration::try_minutes(1).unwrap());
1106
1107        // Exchange it again, this it should fail
1108        let request =
1109            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1110                "grant_type": "authorization_code",
1111                "code": code,
1112                "redirect_uri": grant.redirect_uri,
1113                "client_id": client.client_id,
1114            }));
1115
1116        let response = state.request(request).await;
1117        response.assert_status(StatusCode::BAD_REQUEST);
1118        let error: ClientError = response.json();
1119        assert_eq!(error.error, ClientErrorCode::InvalidGrant);
1120
1121        // And it should have revoked the token we got
1122        assert!(!state.is_access_token_valid(&access_token).await);
1123
1124        // Try another one and wait for too long before exchanging it
1125        let mut repo = state.repository().await.unwrap();
1126        let code = "thisisanothercode";
1127        let grant = repo
1128            .oauth2_authorization_grant()
1129            .add(
1130                &mut state.rng(),
1131                &state.clock,
1132                &client,
1133                "https://example.com/redirect".parse().unwrap(),
1134                Scope::from_iter([OPENID]),
1135                Some(AuthorizationCode {
1136                    code: code.to_owned(),
1137                    pkce: None,
1138                }),
1139                Some("state".to_owned()),
1140                Some("nonce".to_owned()),
1141                ResponseMode::Query,
1142                false,
1143                None,
1144            )
1145            .await
1146            .unwrap();
1147
1148        let session = repo
1149            .oauth2_session()
1150            .add_from_browser_session(
1151                &mut state.rng(),
1152                &state.clock,
1153                &client,
1154                &browser_session,
1155                grant.scope.clone(),
1156            )
1157            .await
1158            .unwrap();
1159
1160        // And fulfill it
1161        let grant = repo
1162            .oauth2_authorization_grant()
1163            .fulfill(&state.clock, &session, grant)
1164            .await
1165            .unwrap();
1166
1167        repo.save().await.unwrap();
1168
1169        // Now wait a bit
1170        state
1171            .clock
1172            .advance(Duration::microseconds(15 * 60 * 1000 * 1000));
1173
1174        // Exchange it, it should fail
1175        let request =
1176            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1177                "grant_type": "authorization_code",
1178                "code": code,
1179                "redirect_uri": grant.redirect_uri,
1180                "client_id": client.client_id,
1181            }));
1182
1183        let response = state.request(request).await;
1184        response.assert_status(StatusCode::BAD_REQUEST);
1185        let ClientError { error, .. } = response.json();
1186        assert_eq!(error, ClientErrorCode::InvalidGrant);
1187    }
1188
1189    #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1190    async fn test_refresh_token_grant(pool: PgPool) {
1191        setup();
1192        let state = TestState::from_pool(pool).await.unwrap();
1193
1194        // Provision a client
1195        let request =
1196            Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1197                "client_uri": "https://example.com/",
1198                "redirect_uris": ["https://example.com/callback"],
1199                "token_endpoint_auth_method": "none",
1200                "response_types": ["code"],
1201                "grant_types": ["authorization_code", "refresh_token"],
1202            }));
1203
1204        let response = state.request(request).await;
1205        response.assert_status(StatusCode::CREATED);
1206
1207        let ClientRegistrationResponse { client_id, .. } = response.json();
1208
1209        // Let's provision a user and create a session for them. This part is hard to
1210        // test with just HTTP requests, so we'll use the repository directly.
1211        let mut repo = state.repository().await.unwrap();
1212
1213        let user = repo
1214            .user()
1215            .add(&mut state.rng(), &state.clock, "alice".to_owned())
1216            .await
1217            .unwrap();
1218
1219        let browser_session = repo
1220            .browser_session()
1221            .add(&mut state.rng(), &state.clock, &user, None)
1222            .await
1223            .unwrap();
1224
1225        // Lookup the client in the database.
1226        let client = repo
1227            .oauth2_client()
1228            .find_by_client_id(&client_id)
1229            .await
1230            .unwrap()
1231            .unwrap();
1232
1233        // Get a token pair
1234        let session = repo
1235            .oauth2_session()
1236            .add_from_browser_session(
1237                &mut state.rng(),
1238                &state.clock,
1239                &client,
1240                &browser_session,
1241                Scope::from_iter([OPENID]),
1242            )
1243            .await
1244            .unwrap();
1245
1246        let (AccessToken { access_token, .. }, RefreshToken { refresh_token, .. }) =
1247            generate_token_pair(
1248                &mut state.rng(),
1249                &state.clock,
1250                &mut repo,
1251                &session,
1252                Duration::microseconds(5 * 60 * 1000 * 1000),
1253            )
1254            .await
1255            .unwrap();
1256
1257        repo.save().await.unwrap();
1258
1259        // First check that the token is valid
1260        assert!(state.is_access_token_valid(&access_token).await);
1261
1262        // Now call the token endpoint to get an access token.
1263        let request =
1264            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1265                "grant_type": "refresh_token",
1266                "refresh_token": refresh_token,
1267                "client_id": client.client_id,
1268            }));
1269
1270        let response = state.request(request).await;
1271        response.assert_status(StatusCode::OK);
1272
1273        let old_access_token = access_token;
1274        let old_refresh_token = refresh_token;
1275        let response: AccessTokenResponse = response.json();
1276        let access_token = response.access_token;
1277        let refresh_token = response.refresh_token.expect("to have a refresh token");
1278
1279        // Check that the new token is valid
1280        assert!(state.is_access_token_valid(&access_token).await);
1281
1282        // Check that the old token is no longer valid
1283        assert!(!state.is_access_token_valid(&old_access_token).await);
1284
1285        // Call it again with the old token, it should fail
1286        let request =
1287            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1288                "grant_type": "refresh_token",
1289                "refresh_token": old_refresh_token,
1290                "client_id": client.client_id,
1291            }));
1292
1293        let response = state.request(request).await;
1294        response.assert_status(StatusCode::BAD_REQUEST);
1295        let ClientError { error, .. } = response.json();
1296        assert_eq!(error, ClientErrorCode::InvalidGrant);
1297
1298        // Call it again with the new token, it should work
1299        let request =
1300            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1301                "grant_type": "refresh_token",
1302                "refresh_token": refresh_token,
1303                "client_id": client.client_id,
1304            }));
1305
1306        let response = state.request(request).await;
1307        response.assert_status(StatusCode::OK);
1308        let _: AccessTokenResponse = response.json();
1309    }
1310
1311    #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1312    async fn test_double_refresh(pool: PgPool) {
1313        setup();
1314        let state = TestState::from_pool(pool).await.unwrap();
1315
1316        // Provision a client
1317        let request =
1318            Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1319                "client_uri": "https://example.com/",
1320                "redirect_uris": ["https://example.com/callback"],
1321                "token_endpoint_auth_method": "none",
1322                "response_types": ["code"],
1323                "grant_types": ["authorization_code", "refresh_token"],
1324            }));
1325
1326        let response = state.request(request).await;
1327        response.assert_status(StatusCode::CREATED);
1328
1329        let ClientRegistrationResponse { client_id, .. } = response.json();
1330
1331        // Let's provision a user and create a session for them. This part is hard to
1332        // test with just HTTP requests, so we'll use the repository directly.
1333        let mut repo = state.repository().await.unwrap();
1334
1335        let user = repo
1336            .user()
1337            .add(&mut state.rng(), &state.clock, "alice".to_owned())
1338            .await
1339            .unwrap();
1340
1341        let browser_session = repo
1342            .browser_session()
1343            .add(&mut state.rng(), &state.clock, &user, None)
1344            .await
1345            .unwrap();
1346
1347        // Lookup the client in the database.
1348        let client = repo
1349            .oauth2_client()
1350            .find_by_client_id(&client_id)
1351            .await
1352            .unwrap()
1353            .unwrap();
1354
1355        // Get a token pair
1356        let session = repo
1357            .oauth2_session()
1358            .add_from_browser_session(
1359                &mut state.rng(),
1360                &state.clock,
1361                &client,
1362                &browser_session,
1363                Scope::from_iter([OPENID]),
1364            )
1365            .await
1366            .unwrap();
1367
1368        let (AccessToken { access_token, .. }, RefreshToken { refresh_token, .. }) =
1369            generate_token_pair(
1370                &mut state.rng(),
1371                &state.clock,
1372                &mut repo,
1373                &session,
1374                Duration::microseconds(5 * 60 * 1000 * 1000),
1375            )
1376            .await
1377            .unwrap();
1378
1379        repo.save().await.unwrap();
1380
1381        // First check that the token is valid
1382        assert!(state.is_access_token_valid(&access_token).await);
1383
1384        // Now call the token endpoint to get an access token.
1385        let request =
1386            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1387                "grant_type": "refresh_token",
1388                "refresh_token": refresh_token,
1389                "client_id": client.client_id,
1390            }));
1391
1392        let first_response = state.request(request).await;
1393        first_response.assert_status(StatusCode::OK);
1394        let first_response: AccessTokenResponse = first_response.json();
1395
1396        // Call a second time, it should work, as we haven't done anything yet with the
1397        // token
1398        let request =
1399            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1400                "grant_type": "refresh_token",
1401                "refresh_token": refresh_token,
1402                "client_id": client.client_id,
1403            }));
1404
1405        let second_response = state.request(request).await;
1406        second_response.assert_status(StatusCode::OK);
1407        let second_response: AccessTokenResponse = second_response.json();
1408
1409        // Check that we got new tokens
1410        assert_ne!(first_response.access_token, second_response.access_token);
1411        assert_ne!(first_response.refresh_token, second_response.refresh_token);
1412
1413        // Check that the old-new token is invalid
1414        assert!(
1415            !state
1416                .is_access_token_valid(&first_response.access_token)
1417                .await
1418        );
1419
1420        // Check that the new-new token is valid
1421        assert!(
1422            state
1423                .is_access_token_valid(&second_response.access_token)
1424                .await
1425        );
1426
1427        // Do a third refresh, this one should not work, as we've used the new
1428        // access token
1429        let request =
1430            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1431                "grant_type": "refresh_token",
1432                "refresh_token": refresh_token,
1433                "client_id": client.client_id,
1434            }));
1435
1436        let third_response = state.request(request).await;
1437        third_response.assert_status(StatusCode::BAD_REQUEST);
1438
1439        // The other reason we consider a new refresh token to be 'used' is if
1440        // it was already used in a refresh
1441        // So, if we do a refresh with the second_response.refresh_token, then
1442        // another refresh with the result, redoing one with
1443        // second_response.refresh_token again should fail
1444        let request =
1445            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1446                "grant_type": "refresh_token",
1447                "refresh_token": second_response.refresh_token,
1448                "client_id": client.client_id,
1449            }));
1450
1451        // This one is fine
1452        let fourth_response = state.request(request).await;
1453        fourth_response.assert_status(StatusCode::OK);
1454        let fourth_response: AccessTokenResponse = fourth_response.json();
1455
1456        // Do another one, it should be fine as well
1457        let request =
1458            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1459                "grant_type": "refresh_token",
1460                "refresh_token": fourth_response.refresh_token,
1461                "client_id": client.client_id,
1462            }));
1463
1464        let fifth_response = state.request(request).await;
1465        fifth_response.assert_status(StatusCode::OK);
1466
1467        // But now, if we re-do with the second_response.refresh_token, it should
1468        // fail
1469        let request =
1470            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1471                "grant_type": "refresh_token",
1472                "refresh_token": second_response.refresh_token,
1473                "client_id": client.client_id,
1474            }));
1475
1476        let sixth_response = state.request(request).await;
1477        sixth_response.assert_status(StatusCode::BAD_REQUEST);
1478    }
1479
1480    #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1481    async fn test_client_credentials(pool: PgPool) {
1482        setup();
1483        let state = TestState::from_pool(pool).await.unwrap();
1484
1485        // Provision a client
1486        let request =
1487            Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1488                "client_uri": "https://example.com/",
1489                "token_endpoint_auth_method": "client_secret_post",
1490                "grant_types": ["client_credentials"],
1491            }));
1492
1493        let response = state.request(request).await;
1494        response.assert_status(StatusCode::CREATED);
1495
1496        let response: ClientRegistrationResponse = response.json();
1497        let client_id = response.client_id;
1498        let client_secret = response.client_secret.expect("to have a client secret");
1499
1500        // Call the token endpoint with an empty scope
1501        let request =
1502            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1503                "grant_type": "client_credentials",
1504                "client_id": client_id,
1505                "client_secret": client_secret,
1506            }));
1507
1508        let response = state.request(request).await;
1509        response.assert_status(StatusCode::OK);
1510
1511        let response: AccessTokenResponse = response.json();
1512        assert!(response.refresh_token.is_none());
1513        assert!(response.expires_in.is_some());
1514        assert!(response.scope.is_none());
1515
1516        // Revoke the token
1517        let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1518            "token": response.access_token,
1519            "client_id": client_id,
1520            "client_secret": client_secret,
1521        }));
1522
1523        let response = state.request(request).await;
1524        response.assert_status(StatusCode::OK);
1525
1526        // We should be allowed to ask for the GraphQL API scope
1527        let request =
1528            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1529                "grant_type": "client_credentials",
1530                "client_id": client_id,
1531                "client_secret": client_secret,
1532                "scope": "urn:mas:graphql:*"
1533            }));
1534
1535        let response = state.request(request).await;
1536        response.assert_status(StatusCode::OK);
1537
1538        let response: AccessTokenResponse = response.json();
1539        assert!(response.refresh_token.is_none());
1540        assert!(response.expires_in.is_some());
1541        assert_eq!(response.scope, Some("urn:mas:graphql:*".parse().unwrap()));
1542
1543        // Revoke the token
1544        let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1545            "token": response.access_token,
1546            "client_id": client_id,
1547            "client_secret": client_secret,
1548        }));
1549
1550        let response = state.request(request).await;
1551        response.assert_status(StatusCode::OK);
1552
1553        // We should be NOT allowed to ask for the MAS admin scope
1554        let request =
1555            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1556                "grant_type": "client_credentials",
1557                "client_id": client_id,
1558                "client_secret": client_secret,
1559                "scope": "urn:mas:admin"
1560            }));
1561
1562        let response = state.request(request).await;
1563        response.assert_status(StatusCode::FORBIDDEN);
1564
1565        let ClientError { error, .. } = response.json();
1566        assert_eq!(error, ClientErrorCode::InvalidScope);
1567
1568        // Now, if we add the client to the admin list in the policy, it should work
1569        let state = {
1570            let mut state = state;
1571            state.policy_factory = crate::test_utils::policy_factory(
1572                "example.com",
1573                serde_json::json!({
1574                    "admin_clients": [client_id]
1575                }),
1576            )
1577            .await
1578            .unwrap();
1579            state
1580        };
1581
1582        let request =
1583            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1584                "grant_type": "client_credentials",
1585                "client_id": client_id,
1586                "client_secret": client_secret,
1587                "scope": "urn:mas:admin"
1588            }));
1589
1590        let response = state.request(request).await;
1591        response.assert_status(StatusCode::OK);
1592
1593        let response: AccessTokenResponse = response.json();
1594        assert!(response.refresh_token.is_none());
1595        assert!(response.expires_in.is_some());
1596        assert_eq!(response.scope, Some("urn:mas:admin".parse().unwrap()));
1597
1598        // Revoke the token
1599        let request = Request::post(mas_router::OAuth2Revocation::PATH).form(serde_json::json!({
1600            "token": response.access_token,
1601            "client_id": client_id,
1602            "client_secret": client_secret,
1603        }));
1604
1605        let response = state.request(request).await;
1606        response.assert_status(StatusCode::OK);
1607    }
1608
1609    #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1610    async fn test_device_code_grant(pool: PgPool) {
1611        setup();
1612        let state = TestState::from_pool(pool).await.unwrap();
1613
1614        // Provision a client
1615        let request =
1616            Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1617                "client_uri": "https://example.com/",
1618                "token_endpoint_auth_method": "none",
1619                "grant_types": ["urn:ietf:params:oauth:grant-type:device_code", "refresh_token"],
1620                "response_types": [],
1621            }));
1622
1623        let response = state.request(request).await;
1624        response.assert_status(StatusCode::CREATED);
1625
1626        let response: ClientRegistrationResponse = response.json();
1627        let client_id = response.client_id;
1628
1629        // Start a device code grant
1630        let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1631            serde_json::json!({
1632                "client_id": client_id,
1633                "scope": "openid",
1634            }),
1635        );
1636        let response = state.request(request).await;
1637        response.assert_status(StatusCode::OK);
1638
1639        let device_grant: DeviceAuthorizationResponse = response.json();
1640
1641        // Poll the token endpoint, it should be pending
1642        let request =
1643            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1644                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1645                "device_code": device_grant.device_code,
1646                "client_id": client_id,
1647            }));
1648        let response = state.request(request).await;
1649        response.assert_status(StatusCode::FORBIDDEN);
1650
1651        let ClientError { error, .. } = response.json();
1652        assert_eq!(error, ClientErrorCode::AuthorizationPending);
1653
1654        // Let's provision a user and create a browser session for them. This part is
1655        // hard to test with just HTTP requests, so we'll use the repository
1656        // directly.
1657        let mut repo = state.repository().await.unwrap();
1658
1659        let user = repo
1660            .user()
1661            .add(&mut state.rng(), &state.clock, "alice".to_owned())
1662            .await
1663            .unwrap();
1664
1665        let browser_session = repo
1666            .browser_session()
1667            .add(&mut state.rng(), &state.clock, &user, None)
1668            .await
1669            .unwrap();
1670
1671        // Find the grant
1672        let grant = repo
1673            .oauth2_device_code_grant()
1674            .find_by_user_code(&device_grant.user_code)
1675            .await
1676            .unwrap()
1677            .unwrap();
1678
1679        // And fulfill it
1680        let grant = repo
1681            .oauth2_device_code_grant()
1682            .fulfill(&state.clock, grant, &browser_session)
1683            .await
1684            .unwrap();
1685
1686        repo.save().await.unwrap();
1687
1688        // Now call the token endpoint to get an access token.
1689        let request =
1690            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1691                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1692                "device_code": grant.device_code,
1693                "client_id": client_id,
1694            }));
1695
1696        let response = state.request(request).await;
1697        response.assert_status(StatusCode::OK);
1698
1699        let response: AccessTokenResponse = response.json();
1700
1701        // Check that the token is valid
1702        assert!(state.is_access_token_valid(&response.access_token).await);
1703        // We advertised the refresh token grant type, so we should have a refresh token
1704        assert!(response.refresh_token.is_some());
1705        // We asked for the openid scope, so we should have an ID token
1706        assert!(response.id_token.is_some());
1707
1708        // Calling it again should fail
1709        let request =
1710            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1711                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1712                "device_code": grant.device_code,
1713                "client_id": client_id,
1714            }));
1715        let response = state.request(request).await;
1716        response.assert_status(StatusCode::BAD_REQUEST);
1717
1718        let ClientError { error, .. } = response.json();
1719        assert_eq!(error, ClientErrorCode::InvalidGrant);
1720
1721        // Do another grant and make it expire
1722        let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1723            serde_json::json!({
1724                "client_id": client_id,
1725                "scope": "openid",
1726            }),
1727        );
1728        let response = state.request(request).await;
1729        response.assert_status(StatusCode::OK);
1730
1731        let device_grant: DeviceAuthorizationResponse = response.json();
1732
1733        // Poll the token endpoint, it should be pending
1734        let request =
1735            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1736                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1737                "device_code": device_grant.device_code,
1738                "client_id": client_id,
1739            }));
1740        let response = state.request(request).await;
1741        response.assert_status(StatusCode::FORBIDDEN);
1742
1743        let ClientError { error, .. } = response.json();
1744        assert_eq!(error, ClientErrorCode::AuthorizationPending);
1745
1746        state.clock.advance(Duration::try_hours(1).unwrap());
1747
1748        // Poll again, it should be expired
1749        let request =
1750            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1751                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1752                "device_code": device_grant.device_code,
1753                "client_id": client_id,
1754            }));
1755        let response = state.request(request).await;
1756        response.assert_status(StatusCode::FORBIDDEN);
1757
1758        let ClientError { error, .. } = response.json();
1759        assert_eq!(error, ClientErrorCode::ExpiredToken);
1760
1761        // Do another grant and reject it
1762        let request = Request::post(mas_router::OAuth2DeviceAuthorizationEndpoint::PATH).form(
1763            serde_json::json!({
1764                "client_id": client_id,
1765                "scope": "openid",
1766            }),
1767        );
1768        let response = state.request(request).await;
1769        response.assert_status(StatusCode::OK);
1770
1771        let device_grant: DeviceAuthorizationResponse = response.json();
1772
1773        // Find the grant and reject it
1774        let mut repo = state.repository().await.unwrap();
1775
1776        // Find the grant
1777        let grant = repo
1778            .oauth2_device_code_grant()
1779            .find_by_user_code(&device_grant.user_code)
1780            .await
1781            .unwrap()
1782            .unwrap();
1783
1784        // And reject it
1785        let grant = repo
1786            .oauth2_device_code_grant()
1787            .reject(&state.clock, grant, &browser_session)
1788            .await
1789            .unwrap();
1790
1791        repo.save().await.unwrap();
1792
1793        // Poll the token endpoint, it should be rejected
1794        let request =
1795            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1796                "grant_type": "urn:ietf:params:oauth:grant-type:device_code",
1797                "device_code": grant.device_code,
1798                "client_id": client_id,
1799            }));
1800        let response = state.request(request).await;
1801        response.assert_status(StatusCode::FORBIDDEN);
1802
1803        let ClientError { error, .. } = response.json();
1804        assert_eq!(error, ClientErrorCode::AccessDenied);
1805    }
1806
1807    #[sqlx::test(migrator = "mas_storage_pg::MIGRATOR")]
1808    async fn test_unsupported_grant(pool: PgPool) {
1809        setup();
1810        let state = TestState::from_pool(pool).await.unwrap();
1811
1812        // Provision a client
1813        let request =
1814            Request::post(mas_router::OAuth2RegistrationEndpoint::PATH).json(serde_json::json!({
1815                "client_uri": "https://example.com/",
1816                "redirect_uris": ["https://example.com/callback"],
1817                "token_endpoint_auth_method": "client_secret_post",
1818                "grant_types": ["password"],
1819                "response_types": [],
1820            }));
1821
1822        let response = state.request(request).await;
1823        response.assert_status(StatusCode::CREATED);
1824
1825        let response: ClientRegistrationResponse = response.json();
1826        let client_id = response.client_id;
1827        let client_secret = response.client_secret.expect("to have a client secret");
1828
1829        // Call the token endpoint with an unsupported grant type
1830        let request =
1831            Request::post(mas_router::OAuth2TokenEndpoint::PATH).form(serde_json::json!({
1832                "grant_type": "password",
1833                "client_id": client_id,
1834                "client_secret": client_secret,
1835                "username": "john",
1836                "password": "hunter2",
1837            }));
1838
1839        let response = state.request(request).await;
1840        response.assert_status(StatusCode::BAD_REQUEST);
1841        let ClientError { error, .. } = response.json();
1842        assert_eq!(error, ClientErrorCode::UnsupportedGrantType);
1843    }
1844}