mas_storage/oauth2/
client.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2022-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::collections::{BTreeMap, BTreeSet};
8
9use async_trait::async_trait;
10use mas_data_model::{Client, User};
11use mas_iana::{jose::JsonWebSignatureAlg, oauth::OAuthClientAuthenticationMethod};
12use mas_jose::jwk::PublicJsonWebKeySet;
13use oauth2_types::{oidc::ApplicationType, requests::GrantType, scope::Scope};
14use rand_core::RngCore;
15use ulid::Ulid;
16use url::Url;
17
18use crate::{Clock, repository_impl};
19
20/// An [`OAuth2ClientRepository`] helps interacting with [`Client`] saved in the
21/// storage backend
22#[async_trait]
23pub trait OAuth2ClientRepository: Send + Sync {
24    /// The error type returned by the repository
25    type Error;
26
27    /// Lookup an OAuth2 client by its ID
28    ///
29    /// Returns `None` if the client does not exist
30    ///
31    /// # Parameters
32    ///
33    /// * `id`: The ID of the client to lookup
34    ///
35    /// # Errors
36    ///
37    /// Returns [`Self::Error`] if the underlying repository fails
38    async fn lookup(&mut self, id: Ulid) -> Result<Option<Client>, Self::Error>;
39
40    /// Find an OAuth2 client by its client ID
41    async fn find_by_client_id(&mut self, client_id: &str) -> Result<Option<Client>, Self::Error> {
42        let Ok(id) = client_id.parse() else {
43            return Ok(None);
44        };
45        self.lookup(id).await
46    }
47
48    /// Load a batch of OAuth2 clients by their IDs
49    ///
50    /// Returns a map of client IDs to clients. If a client does not exist, it
51    /// is not present in the map.
52    ///
53    /// # Parameters
54    ///
55    /// * `ids`: The IDs of the clients to load
56    ///
57    /// # Errors
58    ///
59    /// Returns [`Self::Error`] if the underlying repository fails
60    async fn load_batch(
61        &mut self,
62        ids: BTreeSet<Ulid>,
63    ) -> Result<BTreeMap<Ulid, Client>, Self::Error>;
64
65    /// Add a new OAuth2 client
66    ///
67    /// Returns the client that was added
68    ///
69    /// # Parameters
70    ///
71    /// * `rng`: The random number generator to use
72    /// * `clock`: The clock used to generate timestamps
73    /// * `redirect_uris`: The list of redirect URIs used by this client
74    /// * `encrypted_client_secret`: The encrypted client secret, if any
75    /// * `application_type`: The application type of this client
76    /// * `grant_types`: The list of grant types this client can use
77    /// * `client_name`: The human-readable name of this client, if given
78    /// * `logo_uri`: The URI of the logo of this client, if given
79    /// * `client_uri`: The URI of a website of this client, if given
80    /// * `policy_uri`: The URI of the privacy policy of this client, if given
81    /// * `tos_uri`: The URI of the terms of service of this client, if given
82    /// * `jwks_uri`: The URI of the JWKS of this client, if given
83    /// * `jwks`: The JWKS of this client, if given
84    /// * `id_token_signed_response_alg`: The algorithm used to sign the ID
85    ///   token
86    /// * `userinfo_signed_response_alg`: The algorithm used to sign the user
87    ///   info. If none, the user info endpoint will not sign the response
88    /// * `token_endpoint_auth_method`: The authentication method used by this
89    ///   client when calling the token endpoint
90    /// * `token_endpoint_auth_signing_alg`: The algorithm used to sign the JWT
91    ///   when using the `client_secret_jwt` or `private_key_jwt` authentication
92    ///   methods
93    /// * `initiate_login_uri`: The URI used to initiate a login, if given
94    ///
95    /// # Errors
96    ///
97    /// Returns [`Self::Error`] if the underlying repository fails
98    #[allow(clippy::too_many_arguments)]
99    async fn add(
100        &mut self,
101        rng: &mut (dyn RngCore + Send),
102        clock: &dyn Clock,
103        redirect_uris: Vec<Url>,
104        encrypted_client_secret: Option<String>,
105        application_type: Option<ApplicationType>,
106        grant_types: Vec<GrantType>,
107        client_name: Option<String>,
108        logo_uri: Option<Url>,
109        client_uri: Option<Url>,
110        policy_uri: Option<Url>,
111        tos_uri: Option<Url>,
112        jwks_uri: Option<Url>,
113        jwks: Option<PublicJsonWebKeySet>,
114        id_token_signed_response_alg: Option<JsonWebSignatureAlg>,
115        userinfo_signed_response_alg: Option<JsonWebSignatureAlg>,
116        token_endpoint_auth_method: Option<OAuthClientAuthenticationMethod>,
117        token_endpoint_auth_signing_alg: Option<JsonWebSignatureAlg>,
118        initiate_login_uri: Option<Url>,
119    ) -> Result<Client, Self::Error>;
120
121    /// Add or replace a static client
122    ///
123    /// Returns the client that was added or replaced
124    ///
125    /// # Parameters
126    ///
127    /// * `client_id`: The client ID
128    /// * `client_auth_method`: The authentication method this client uses
129    /// * `encrypted_client_secret`: The encrypted client secret, if any
130    /// * `jwks`: The client JWKS, if any
131    /// * `jwks_uri`: The client JWKS URI, if any
132    /// * `redirect_uris`: The list of redirect URIs used by this client
133    ///
134    /// # Errors
135    ///
136    /// Returns [`Self::Error`] if the underlying repository fails
137    #[allow(clippy::too_many_arguments)]
138    async fn upsert_static(
139        &mut self,
140        client_id: Ulid,
141        client_auth_method: OAuthClientAuthenticationMethod,
142        encrypted_client_secret: Option<String>,
143        jwks: Option<PublicJsonWebKeySet>,
144        jwks_uri: Option<Url>,
145        redirect_uris: Vec<Url>,
146    ) -> Result<Client, Self::Error>;
147
148    /// List all static clients
149    ///
150    /// # Errors
151    ///
152    /// Returns [`Self::Error`] if the underlying repository fails
153    async fn all_static(&mut self) -> Result<Vec<Client>, Self::Error>;
154
155    /// Get the list of scopes that the user has given consent for the given
156    /// client
157    ///
158    /// # Parameters
159    ///
160    /// * `client`: The client to get the consent for
161    /// * `user`: The user to get the consent for
162    ///
163    /// # Errors
164    ///
165    /// Returns [`Self::Error`] if the underlying repository fails
166    async fn get_consent_for_user(
167        &mut self,
168        client: &Client,
169        user: &User,
170    ) -> Result<Scope, Self::Error>;
171
172    /// Give consent for a set of scopes for the given client and user
173    ///
174    /// # Parameters
175    ///
176    /// * `rng`: The random number generator to use
177    /// * `clock`: The clock used to generate timestamps
178    /// * `client`: The client to give the consent for
179    /// * `user`: The user to give the consent for
180    /// * `scope`: The scope to give consent for
181    ///
182    /// # Errors
183    ///
184    /// Returns [`Self::Error`] if the underlying repository fails
185    async fn give_consent_for_user(
186        &mut self,
187        rng: &mut (dyn RngCore + Send),
188        clock: &dyn Clock,
189        client: &Client,
190        user: &User,
191        scope: &Scope,
192    ) -> Result<(), Self::Error>;
193
194    /// Delete a client
195    ///
196    /// # Parameters
197    ///
198    /// * `client`: The client to delete
199    ///
200    /// # Errors
201    ///
202    /// Returns [`Self::Error`] if the underlying repository fails, or if the
203    /// client does not exist
204    async fn delete(&mut self, client: Client) -> Result<(), Self::Error> {
205        self.delete_by_id(client.id).await
206    }
207
208    /// Delete a client by ID
209    ///
210    /// # Parameters
211    ///
212    /// * `id`: The ID of the client to delete
213    ///
214    /// # Errors
215    ///
216    /// Returns [`Self::Error`] if the underlying repository fails, or if the
217    /// client does not exist
218    async fn delete_by_id(&mut self, id: Ulid) -> Result<(), Self::Error>;
219}
220
221repository_impl!(OAuth2ClientRepository:
222    async fn lookup(&mut self, id: Ulid) -> Result<Option<Client>, Self::Error>;
223
224    async fn load_batch(
225        &mut self,
226        ids: BTreeSet<Ulid>,
227    ) -> Result<BTreeMap<Ulid, Client>, Self::Error>;
228
229    async fn add(
230        &mut self,
231        rng: &mut (dyn RngCore + Send),
232        clock: &dyn Clock,
233        redirect_uris: Vec<Url>,
234        encrypted_client_secret: Option<String>,
235        application_type: Option<ApplicationType>,
236        grant_types: Vec<GrantType>,
237        client_name: Option<String>,
238        logo_uri: Option<Url>,
239        client_uri: Option<Url>,
240        policy_uri: Option<Url>,
241        tos_uri: Option<Url>,
242        jwks_uri: Option<Url>,
243        jwks: Option<PublicJsonWebKeySet>,
244        id_token_signed_response_alg: Option<JsonWebSignatureAlg>,
245        userinfo_signed_response_alg: Option<JsonWebSignatureAlg>,
246        token_endpoint_auth_method: Option<OAuthClientAuthenticationMethod>,
247        token_endpoint_auth_signing_alg: Option<JsonWebSignatureAlg>,
248        initiate_login_uri: Option<Url>,
249    ) -> Result<Client, Self::Error>;
250
251    async fn upsert_static(
252        &mut self,
253        client_id: Ulid,
254        client_auth_method: OAuthClientAuthenticationMethod,
255        encrypted_client_secret: Option<String>,
256        jwks: Option<PublicJsonWebKeySet>,
257        jwks_uri: Option<Url>,
258        redirect_uris: Vec<Url>,
259    ) -> Result<Client, Self::Error>;
260
261    async fn all_static(&mut self) -> Result<Vec<Client>, Self::Error>;
262
263    async fn delete(&mut self, client: Client) -> Result<(), Self::Error>;
264
265    async fn delete_by_id(&mut self, id: Ulid) -> Result<(), Self::Error>;
266
267    async fn get_consent_for_user(
268        &mut self,
269        client: &Client,
270        user: &User,
271    ) -> Result<Scope, Self::Error>;
272
273    async fn give_consent_for_user(
274        &mut self,
275        rng: &mut (dyn RngCore + Send),
276        clock: &dyn Clock,
277        client: &Client,
278        user: &User,
279        scope: &Scope,
280    ) -> Result<(), Self::Error>;
281);