mas_storage/upstream_oauth2/provider.rs
1// Copyright 2025, 2026 Element Creations Ltd.
2// Copyright 2024, 2025 New Vector Ltd.
3// Copyright 2022-2024 The Matrix.org Foundation C.I.C.
4//
5// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
6// Please see LICENSE files in the repository root for full details.
7
8use std::marker::PhantomData;
9
10use async_trait::async_trait;
11use mas_data_model::{
12 Clock, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports,
13 UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderOnBackchannelLogout,
14 UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderResponseMode,
15 UpstreamOAuthProviderTokenAuthMethod,
16};
17use mas_iana::jose::JsonWebSignatureAlg;
18use oauth2_types::scope::Scope;
19use rand_core::Rng;
20use ulid::Ulid;
21use url::Url;
22
23use crate::{Pagination, pagination::Page, repository_impl};
24
25/// Structure which holds parameters when inserting or updating an upstream
26/// OAuth 2.0 provider
27pub struct UpstreamOAuthProviderParams {
28 /// The OIDC issuer of the provider
29 pub issuer: Option<String>,
30
31 /// A human-readable name for the provider
32 pub human_name: Option<String>,
33
34 /// A brand identifier, e.g. "apple" or "google"
35 pub brand_name: Option<String>,
36
37 /// The scope to request during the authorization flow
38 pub scope: Scope,
39
40 /// The token endpoint authentication method
41 pub token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod,
42
43 /// The JWT signing algorithm to use when then `client_secret_jwt` or
44 /// `private_key_jwt` authentication methods are used
45 pub token_endpoint_signing_alg: Option<JsonWebSignatureAlg>,
46
47 /// Expected signature for the JWT payload returned by the token
48 /// authentication endpoint.
49 ///
50 /// Defaults to `RS256`.
51 pub id_token_signed_response_alg: JsonWebSignatureAlg,
52
53 /// Whether to fetch the user profile from the userinfo endpoint,
54 /// or to rely on the data returned in the `id_token` from the
55 /// `token_endpoint`.
56 pub fetch_userinfo: bool,
57
58 /// Expected signature for the JWT payload returned by the userinfo
59 /// endpoint.
60 ///
61 /// If not specified, the response is expected to be an unsigned JSON
62 /// payload. Defaults to `None`.
63 pub userinfo_signed_response_alg: Option<JsonWebSignatureAlg>,
64
65 /// The client ID to use when authenticating to the upstream
66 pub client_id: String,
67
68 /// The encrypted client secret to use when authenticating to the upstream
69 pub encrypted_client_secret: Option<String>,
70
71 /// How claims should be imported from the upstream provider
72 pub claims_imports: UpstreamOAuthProviderClaimsImports,
73
74 /// The URL to use as the authorization endpoint. If `None`, the URL will be
75 /// discovered
76 pub authorization_endpoint_override: Option<Url>,
77
78 /// The URL to use as the token endpoint. If `None`, the URL will be
79 /// discovered
80 pub token_endpoint_override: Option<Url>,
81
82 /// The URL to use as the userinfo endpoint. If `None`, the URL will be
83 /// discovered
84 pub userinfo_endpoint_override: Option<Url>,
85
86 /// The URL to use when fetching JWKS. If `None`, the URL will be discovered
87 pub jwks_uri_override: Option<Url>,
88
89 /// How the provider metadata should be discovered
90 pub discovery_mode: UpstreamOAuthProviderDiscoveryMode,
91
92 /// How should PKCE be used
93 pub pkce_mode: UpstreamOAuthProviderPkceMode,
94
95 /// What response mode it should ask
96 pub response_mode: Option<UpstreamOAuthProviderResponseMode>,
97
98 /// Additional parameters to include in the authorization request
99 pub additional_authorization_parameters: Vec<(String, String)>,
100
101 /// Whether to forward the login hint to the upstream provider.
102 pub forward_login_hint: bool,
103
104 /// The position of the provider in the UI
105 pub ui_order: i32,
106
107 /// The behavior when receiving a backchannel logout notification
108 pub on_backchannel_logout: UpstreamOAuthProviderOnBackchannelLogout,
109
110 /// Whether or not to require a registration token on `OAuth2` auth
111 pub registration_token_required: bool,
112}
113
114/// Filter parameters for listing upstream OAuth 2.0 providers
115#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
116pub struct UpstreamOAuthProviderFilter<'a> {
117 /// Filter by whether the provider is enabled
118 ///
119 /// If `None`, all providers are returned
120 enabled: Option<bool>,
121
122 _lifetime: PhantomData<&'a ()>,
123}
124
125impl UpstreamOAuthProviderFilter<'_> {
126 /// Create a new [`UpstreamOAuthProviderFilter`] with default values
127 #[must_use]
128 pub fn new() -> Self {
129 Self::default()
130 }
131
132 /// Return only enabled providers
133 #[must_use]
134 pub const fn enabled_only(mut self) -> Self {
135 self.enabled = Some(true);
136 self
137 }
138
139 /// Return only disabled providers
140 #[must_use]
141 pub const fn disabled_only(mut self) -> Self {
142 self.enabled = Some(false);
143 self
144 }
145
146 /// Get the enabled filter
147 ///
148 /// Returns `None` if the filter is not set
149 #[must_use]
150 pub const fn enabled(&self) -> Option<bool> {
151 self.enabled
152 }
153}
154
155/// An [`UpstreamOAuthProviderRepository`] helps interacting with
156/// [`UpstreamOAuthProvider`] saved in the storage backend
157#[async_trait]
158pub trait UpstreamOAuthProviderRepository: Send + Sync {
159 /// The error type returned by the repository
160 type Error;
161
162 /// Lookup an upstream OAuth provider by its ID
163 ///
164 /// Returns `None` if the provider was not found
165 ///
166 /// # Parameters
167 ///
168 /// * `id`: The ID of the provider to lookup
169 ///
170 /// # Errors
171 ///
172 /// Returns [`Self::Error`] if the underlying repository fails
173 async fn lookup(&mut self, id: Ulid) -> Result<Option<UpstreamOAuthProvider>, Self::Error>;
174
175 /// Add a new upstream OAuth provider
176 ///
177 /// Returns the newly created provider
178 ///
179 /// # Parameters
180 ///
181 /// * `rng`: A random number generator
182 /// * `clock`: The clock used to generate timestamps
183 /// * `params`: The parameters of the provider to add
184 ///
185 /// # Errors
186 ///
187 /// Returns [`Self::Error`] if the underlying repository fails
188 async fn add(
189 &mut self,
190 rng: &mut (dyn Rng + Send),
191 clock: &dyn Clock,
192 params: UpstreamOAuthProviderParams,
193 ) -> Result<UpstreamOAuthProvider, Self::Error>;
194
195 /// Delete an upstream OAuth provider
196 ///
197 /// # Parameters
198 ///
199 /// * `provider`: The provider to delete
200 ///
201 /// # Errors
202 ///
203 /// Returns [`Self::Error`] if the underlying repository fails
204 async fn delete(&mut self, provider: UpstreamOAuthProvider) -> Result<(), Self::Error> {
205 self.delete_by_id(provider.id).await
206 }
207
208 /// Delete an upstream OAuth provider by its ID
209 ///
210 /// # Parameters
211 ///
212 /// * `id`: The ID of the provider to delete
213 ///
214 /// # Errors
215 ///
216 /// Returns [`Self::Error`] if the underlying repository fails
217 async fn delete_by_id(&mut self, id: Ulid) -> Result<(), Self::Error>;
218
219 /// Insert or update an upstream OAuth provider
220 ///
221 /// # Parameters
222 ///
223 /// * `clock`: The clock used to generate timestamps
224 /// * `id`: The ID of the provider to update
225 /// * `params`: The parameters of the provider to update
226 ///
227 /// # Errors
228 ///
229 /// Returns [`Self::Error`] if the underlying repository fails
230 async fn upsert(
231 &mut self,
232 clock: &dyn Clock,
233 id: Ulid,
234 params: UpstreamOAuthProviderParams,
235 ) -> Result<UpstreamOAuthProvider, Self::Error>;
236
237 /// Disable an upstream OAuth provider
238 ///
239 /// Returns the disabled provider
240 ///
241 /// # Parameters
242 ///
243 /// * `clock`: The clock used to generate timestamps
244 /// * `provider`: The provider to disable
245 ///
246 /// # Errors
247 ///
248 /// Returns [`Self::Error`] if the underlying repository fails
249 async fn disable(
250 &mut self,
251 clock: &dyn Clock,
252 provider: UpstreamOAuthProvider,
253 ) -> Result<UpstreamOAuthProvider, Self::Error>;
254
255 /// List [`UpstreamOAuthProvider`] with the given filter and pagination
256 ///
257 /// # Parameters
258 ///
259 /// * `filter`: The filter to apply
260 /// * `pagination`: The pagination parameters
261 ///
262 /// # Errors
263 ///
264 /// Returns [`Self::Error`] if the underlying repository fails
265 async fn list(
266 &mut self,
267 filter: UpstreamOAuthProviderFilter<'_>,
268 pagination: Pagination,
269 ) -> Result<Page<UpstreamOAuthProvider>, Self::Error>;
270
271 /// Count the number of [`UpstreamOAuthProvider`] with the given filter
272 ///
273 /// # Parameters
274 ///
275 /// * `filter`: The filter to apply
276 ///
277 /// # Errors
278 ///
279 /// Returns [`Self::Error`] if the underlying repository fails
280 async fn count(
281 &mut self,
282 filter: UpstreamOAuthProviderFilter<'_>,
283 ) -> Result<usize, Self::Error>;
284
285 /// Get all enabled upstream OAuth providers
286 ///
287 /// # Errors
288 ///
289 /// Returns [`Self::Error`] if the underlying repository fails
290 async fn all_enabled(&mut self) -> Result<Vec<UpstreamOAuthProvider>, Self::Error>;
291}
292
293repository_impl!(UpstreamOAuthProviderRepository:
294 async fn lookup(&mut self, id: Ulid) -> Result<Option<UpstreamOAuthProvider>, Self::Error>;
295
296 async fn add(
297 &mut self,
298 rng: &mut (dyn Rng + Send),
299 clock: &dyn Clock,
300 params: UpstreamOAuthProviderParams
301 ) -> Result<UpstreamOAuthProvider, Self::Error>;
302
303 async fn upsert(
304 &mut self,
305 clock: &dyn Clock,
306 id: Ulid,
307 params: UpstreamOAuthProviderParams
308 ) -> Result<UpstreamOAuthProvider, Self::Error>;
309
310 async fn delete(&mut self, provider: UpstreamOAuthProvider) -> Result<(), Self::Error>;
311
312 async fn delete_by_id(&mut self, id: Ulid) -> Result<(), Self::Error>;
313
314 async fn disable(
315 &mut self,
316 clock: &dyn Clock,
317 provider: UpstreamOAuthProvider
318 ) -> Result<UpstreamOAuthProvider, Self::Error>;
319
320 async fn list(
321 &mut self,
322 filter: UpstreamOAuthProviderFilter<'_>,
323 pagination: Pagination
324 ) -> Result<Page<UpstreamOAuthProvider>, Self::Error>;
325
326 async fn count(
327 &mut self,
328 filter: UpstreamOAuthProviderFilter<'_>
329 ) -> Result<usize, Self::Error>;
330
331 async fn all_enabled(&mut self) -> Result<Vec<UpstreamOAuthProvider>, Self::Error>;
332);