matrix_sdk/authentication/oidc/backend/
server.rs

1// Copyright 2023 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for that specific language governing permissions and
13// limitations under the License.
14
15//! Actual implementation of the OIDC backend, using the mas_oidc_client
16//! implementation.
17
18use chrono::Utc;
19use http::StatusCode;
20use mas_oidc_client::{
21    http_service::HttpService,
22    jose::jwk::PublicJsonWebKeySet,
23    requests::{
24        authorization_code::{
25            access_token_with_authorization_code, build_par_authorization_url,
26            AuthorizationRequestData, AuthorizationValidationData,
27        },
28        discovery::{discover, insecure_discover},
29        jose::{fetch_jwks, JwtVerificationData},
30        refresh_token::refresh_access_token,
31        registration::register_client,
32        revocation::revoke_token,
33    },
34    types::{
35        client_credentials::ClientCredentials,
36        iana::oauth::OAuthTokenTypeHint,
37        oidc::{ProviderMetadata, ProviderMetadataVerificationError, VerifiedProviderMetadata},
38        registration::{ClientRegistrationResponse, VerifiedClientMetadata},
39        IdToken,
40    },
41};
42use ruma::api::client::discovery::{get_authentication_issuer, get_authorization_server_metadata};
43use url::Url;
44
45use super::{OidcBackend, OidcError, RefreshedSessionTokens};
46use crate::{
47    authentication::oidc::{rng, AuthorizationCode, OauthDiscoveryError, OidcSessionTokens},
48    Client,
49};
50
51#[derive(Debug)]
52pub(crate) struct OidcServer {
53    client: Client,
54}
55
56impl OidcServer {
57    pub(crate) fn new(client: Client) -> Self {
58        Self { client }
59    }
60
61    fn http_service(&self) -> HttpService {
62        HttpService::new(self.client.inner.http_client.clone())
63    }
64
65    /// Fetch the OpenID Connect JSON Web Key Set at the given URI.
66    ///
67    /// Returns an error if the client registration was not restored, or if an
68    /// error occurred when fetching the data.
69    async fn fetch_jwks(&self, uri: &Url) -> Result<PublicJsonWebKeySet, OidcError> {
70        fetch_jwks(&self.http_service(), uri).await.map_err(Into::into)
71    }
72}
73
74#[async_trait::async_trait]
75impl OidcBackend for OidcServer {
76    async fn discover(
77        &self,
78        insecure: bool,
79    ) -> Result<VerifiedProviderMetadata, OauthDiscoveryError> {
80        match self.client.send(get_authorization_server_metadata::msc2965::Request::new()).await {
81            Ok(response) => {
82                let metadata = response.metadata.deserialize_as::<ProviderMetadata>()?;
83
84                let result = if insecure {
85                    metadata.insecure_verify_metadata()
86                } else {
87                    // The mas-oidc-client method needs to compare the issuer for validation. It's a
88                    // bit unnecessary because we take it from the metadata, oh well.
89                    let issuer = metadata.issuer.clone().ok_or(
90                        mas_oidc_client::error::DiscoveryError::Validation(
91                            ProviderMetadataVerificationError::MissingIssuer,
92                        ),
93                    )?;
94                    metadata.validate(&issuer)
95                };
96
97                return Ok(result.map_err(mas_oidc_client::error::DiscoveryError::Validation)?);
98            }
99            Err(error)
100                if error
101                    .as_client_api_error()
102                    .is_some_and(|err| err.status_code == StatusCode::NOT_FOUND) =>
103            {
104                // Fallback to OIDC discovery.
105            }
106            Err(error) => return Err(error.into()),
107        };
108
109        // TODO: remove this fallback behavior when the metadata endpoint has wider
110        // support.
111        #[allow(deprecated)]
112        let issuer =
113            match self.client.send(get_authentication_issuer::msc2965::Request::new()).await {
114                Ok(response) => response.issuer,
115                Err(error)
116                    if error
117                        .as_client_api_error()
118                        .is_some_and(|err| err.status_code == StatusCode::NOT_FOUND) =>
119                {
120                    return Err(OauthDiscoveryError::NotSupported);
121                }
122                Err(error) => return Err(error.into()),
123            };
124
125        if insecure {
126            insecure_discover(&self.http_service(), &issuer).await.map_err(Into::into)
127        } else {
128            discover(&self.http_service(), &issuer).await.map_err(Into::into)
129        }
130    }
131
132    async fn trade_authorization_code_for_tokens(
133        &self,
134        provider_metadata: VerifiedProviderMetadata,
135        credentials: ClientCredentials,
136        metadata: VerifiedClientMetadata,
137        auth_code: AuthorizationCode,
138        validation_data: AuthorizationValidationData,
139    ) -> Result<OidcSessionTokens, OidcError> {
140        let jwks = self.fetch_jwks(provider_metadata.jwks_uri()).await?;
141
142        let id_token_verification_data = JwtVerificationData {
143            issuer: provider_metadata.issuer(),
144            jwks: &jwks,
145            client_id: &credentials.client_id().to_owned(),
146            signing_algorithm: metadata.id_token_signed_response_alg(),
147        };
148
149        let (response, id_token) = access_token_with_authorization_code(
150            &self.http_service(),
151            credentials.clone(),
152            provider_metadata.token_endpoint(),
153            auth_code.code,
154            validation_data,
155            Some(id_token_verification_data),
156            Utc::now(),
157            &mut rng()?,
158        )
159        .await?;
160
161        Ok(OidcSessionTokens {
162            access_token: response.access_token,
163            refresh_token: response.refresh_token,
164            latest_id_token: id_token,
165        })
166    }
167
168    async fn refresh_access_token(
169        &self,
170        provider_metadata: VerifiedProviderMetadata,
171        credentials: ClientCredentials,
172        metadata: &VerifiedClientMetadata,
173        refresh_token: String,
174        latest_id_token: Option<IdToken<'static>>,
175    ) -> Result<RefreshedSessionTokens, OidcError> {
176        let jwks = self.fetch_jwks(provider_metadata.jwks_uri()).await?;
177
178        let id_token_verification_data = JwtVerificationData {
179            issuer: provider_metadata.issuer(),
180            jwks: &jwks,
181            client_id: &credentials.client_id().to_owned(),
182            signing_algorithm: &metadata.id_token_signed_response_alg().clone(),
183        };
184
185        refresh_access_token(
186            &self.http_service(),
187            credentials,
188            provider_metadata.token_endpoint(),
189            refresh_token,
190            None,
191            Some(id_token_verification_data),
192            latest_id_token.as_ref(),
193            Utc::now(),
194            &mut rng()?,
195        )
196        .await
197        .map(|(response, _id_token)| RefreshedSessionTokens {
198            access_token: response.access_token,
199            refresh_token: response.refresh_token,
200        })
201        .map_err(Into::into)
202    }
203
204    async fn register_client(
205        &self,
206        registration_endpoint: &Url,
207        client_metadata: VerifiedClientMetadata,
208        software_statement: Option<String>,
209    ) -> Result<ClientRegistrationResponse, OidcError> {
210        register_client(
211            &self.http_service(),
212            registration_endpoint,
213            client_metadata,
214            software_statement,
215        )
216        .await
217        .map_err(Into::into)
218    }
219
220    async fn build_par_authorization_url(
221        &self,
222        client_credentials: ClientCredentials,
223        par_endpoint: &Url,
224        authorization_endpoint: Url,
225        authorization_data: AuthorizationRequestData,
226    ) -> Result<(Url, AuthorizationValidationData), OidcError> {
227        Ok(build_par_authorization_url(
228            &self.http_service(),
229            client_credentials,
230            par_endpoint,
231            authorization_endpoint,
232            authorization_data,
233            Utc::now(),
234            &mut rng()?,
235        )
236        .await?)
237    }
238
239    async fn revoke_token(
240        &self,
241        client_credentials: ClientCredentials,
242        revocation_endpoint: &Url,
243        token: String,
244        token_type_hint: Option<OAuthTokenTypeHint>,
245    ) -> Result<(), OidcError> {
246        Ok(revoke_token(
247            &self.http_service(),
248            client_credentials,
249            revocation_endpoint,
250            token,
251            token_type_hint,
252            Utc::now(),
253            &mut rng()?,
254        )
255        .await?)
256    }
257}