diff --git a/crates/cli/src/commands/config.rs b/crates/cli/src/commands/config.rs index afbdf8a4..a1b57d3e 100644 --- a/crates/cli/src/commands/config.rs +++ b/crates/cli/src/commands/config.rs @@ -310,6 +310,8 @@ async fn sync(root: &super::Options, prune: bool, dry_run: bool) -> anyhow::Resu jwks_uri_override: provider.jwks_uri, discovery_mode, pkce_mode, + // TODO: get that from the config + additional_authorization_parameters: Vec::new(), }, ) .await?; diff --git a/crates/data-model/src/upstream_oauth2/provider.rs b/crates/data-model/src/upstream_oauth2/provider.rs index e9aea449..af0d26bc 100644 --- a/crates/data-model/src/upstream_oauth2/provider.rs +++ b/crates/data-model/src/upstream_oauth2/provider.rs @@ -142,6 +142,7 @@ pub struct UpstreamOAuthProvider { pub token_endpoint_auth_method: OAuthClientAuthenticationMethod, pub created_at: DateTime, pub claims_imports: ClaimsImports, + pub additional_authorization_parameters: Vec<(String, String)>, } /// Whether to set the email as verified when importing it from the upstream diff --git a/crates/handlers/src/upstream_oauth2/cache.rs b/crates/handlers/src/upstream_oauth2/cache.rs index cf10280a..69fcc857 100644 --- a/crates/handlers/src/upstream_oauth2/cache.rs +++ b/crates/handlers/src/upstream_oauth2/cache.rs @@ -505,6 +505,7 @@ mod tests { token_endpoint_auth_method: OAuthClientAuthenticationMethod::None, created_at: clock.now(), claims_imports: UpstreamOAuthProviderClaimsImports::default(), + additional_authorization_parameters: Vec::new(), }; // Without any override, it should just use discovery diff --git a/crates/handlers/src/upstream_oauth2/link.rs b/crates/handlers/src/upstream_oauth2/link.rs index b03c02f5..4719e7e2 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -925,6 +925,7 @@ mod tests { jwks_uri_override: None, discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + additional_authorization_parameters: Vec::new(), }, ) .await diff --git a/crates/handlers/src/views/login.rs b/crates/handlers/src/views/login.rs index fdca38dd..513a9327 100644 --- a/crates/handlers/src/views/login.rs +++ b/crates/handlers/src/views/login.rs @@ -363,6 +363,7 @@ mod test { jwks_uri_override: None, discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + additional_authorization_parameters: Vec::new(), }, ) .await @@ -397,6 +398,7 @@ mod test { jwks_uri_override: None, discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + additional_authorization_parameters: Vec::new(), }, ) .await diff --git a/crates/storage-pg/.sqlx/query-21132afc29be5394a03680dd27d2aff5e2249a973083c0675935dc658f73b1f4.json b/crates/storage-pg/.sqlx/query-21132afc29be5394a03680dd27d2aff5e2249a973083c0675935dc658f73b1f4.json new file mode 100644 index 00000000..403bee8e --- /dev/null +++ b/crates/storage-pg/.sqlx/query-21132afc29be5394a03680dd27d2aff5e2249a973083c0675935dc658f73b1f4.json @@ -0,0 +1,38 @@ +{ + "db_name": "PostgreSQL", + "query": "\n INSERT INTO upstream_oauth_providers (\n upstream_oauth_provider_id,\n issuer,\n human_name,\n brand_name,\n scope,\n token_endpoint_auth_method,\n token_endpoint_signing_alg,\n client_id,\n encrypted_client_secret,\n claims_imports,\n authorization_endpoint_override,\n token_endpoint_override,\n jwks_uri_override,\n discovery_mode,\n pkce_mode,\n additional_parameters,\n created_at\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9,\n $10, $11, $12, $13, $14, $15, $16, $17)\n ON CONFLICT (upstream_oauth_provider_id) \n DO UPDATE\n SET\n issuer = EXCLUDED.issuer,\n human_name = EXCLUDED.human_name,\n brand_name = EXCLUDED.brand_name,\n scope = EXCLUDED.scope,\n token_endpoint_auth_method = EXCLUDED.token_endpoint_auth_method,\n token_endpoint_signing_alg = EXCLUDED.token_endpoint_signing_alg,\n client_id = EXCLUDED.client_id,\n encrypted_client_secret = EXCLUDED.encrypted_client_secret,\n claims_imports = EXCLUDED.claims_imports,\n authorization_endpoint_override = EXCLUDED.authorization_endpoint_override,\n token_endpoint_override = EXCLUDED.token_endpoint_override,\n jwks_uri_override = EXCLUDED.jwks_uri_override,\n discovery_mode = EXCLUDED.discovery_mode,\n pkce_mode = EXCLUDED.pkce_mode,\n additional_parameters = EXCLUDED.additional_parameters\n RETURNING created_at\n ", + "describe": { + "columns": [ + { + "ordinal": 0, + "name": "created_at", + "type_info": "Timestamptz" + } + ], + "parameters": { + "Left": [ + "Uuid", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Jsonb", + "Text", + "Text", + "Text", + "Text", + "Text", + "Jsonb", + "Timestamptz" + ] + }, + "nullable": [ + false + ] + }, + "hash": "21132afc29be5394a03680dd27d2aff5e2249a973083c0675935dc658f73b1f4" +} diff --git a/crates/storage-pg/.sqlx/query-4668abf6520ecca2fa71a26b02d206600624bbba57985d4a7fba2763478cd065.json b/crates/storage-pg/.sqlx/query-4668abf6520ecca2fa71a26b02d206600624bbba57985d4a7fba2763478cd065.json deleted file mode 100644 index 0752d5a6..00000000 --- a/crates/storage-pg/.sqlx/query-4668abf6520ecca2fa71a26b02d206600624bbba57985d4a7fba2763478cd065.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "\n INSERT INTO upstream_oauth_providers (\n upstream_oauth_provider_id,\n issuer,\n human_name,\n brand_name,\n scope,\n token_endpoint_auth_method,\n token_endpoint_signing_alg,\n client_id,\n encrypted_client_secret,\n claims_imports,\n authorization_endpoint_override,\n token_endpoint_override,\n jwks_uri_override,\n discovery_mode,\n pkce_mode,\n created_at\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9,\n $10, $11, $12, $13, $14, $15, $16)\n ON CONFLICT (upstream_oauth_provider_id) \n DO UPDATE\n SET\n issuer = EXCLUDED.issuer,\n human_name = EXCLUDED.human_name,\n brand_name = EXCLUDED.brand_name,\n scope = EXCLUDED.scope,\n token_endpoint_auth_method = EXCLUDED.token_endpoint_auth_method,\n token_endpoint_signing_alg = EXCLUDED.token_endpoint_signing_alg,\n client_id = EXCLUDED.client_id,\n encrypted_client_secret = EXCLUDED.encrypted_client_secret,\n claims_imports = EXCLUDED.claims_imports,\n authorization_endpoint_override = EXCLUDED.authorization_endpoint_override,\n token_endpoint_override = EXCLUDED.token_endpoint_override,\n jwks_uri_override = EXCLUDED.jwks_uri_override,\n discovery_mode = EXCLUDED.discovery_mode,\n pkce_mode = EXCLUDED.pkce_mode\n RETURNING created_at\n ", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "created_at", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Uuid", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Jsonb", - "Text", - "Text", - "Text", - "Text", - "Text", - "Timestamptz" - ] - }, - "nullable": [ - false - ] - }, - "hash": "4668abf6520ecca2fa71a26b02d206600624bbba57985d4a7fba2763478cd065" -} diff --git a/crates/storage-pg/.sqlx/query-2098ff49e9f4b847543b3186dbc4a8cdf1d21f788a533477c233455c57a1b755.json b/crates/storage-pg/.sqlx/query-ccf4965aa84c497ac9759cb31f3ecba59fdf18085791f799dfd398bef4f8eb8c.json similarity index 86% rename from crates/storage-pg/.sqlx/query-2098ff49e9f4b847543b3186dbc4a8cdf1d21f788a533477c233455c57a1b755.json rename to crates/storage-pg/.sqlx/query-ccf4965aa84c497ac9759cb31f3ecba59fdf18085791f799dfd398bef4f8eb8c.json index 06940c1a..a30ed5c7 100644 --- a/crates/storage-pg/.sqlx/query-2098ff49e9f4b847543b3186dbc4a8cdf1d21f788a533477c233455c57a1b755.json +++ b/crates/storage-pg/.sqlx/query-ccf4965aa84c497ac9759cb31f3ecba59fdf18085791f799dfd398bef4f8eb8c.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n upstream_oauth_provider_id,\n issuer,\n human_name,\n brand_name,\n scope,\n client_id,\n encrypted_client_secret,\n token_endpoint_signing_alg,\n token_endpoint_auth_method,\n created_at,\n claims_imports as \"claims_imports: Json\",\n jwks_uri_override,\n authorization_endpoint_override,\n token_endpoint_override,\n discovery_mode,\n pkce_mode\n FROM upstream_oauth_providers\n ", + "query": "\n SELECT\n upstream_oauth_provider_id,\n issuer,\n human_name,\n brand_name,\n scope,\n client_id,\n encrypted_client_secret,\n token_endpoint_signing_alg,\n token_endpoint_auth_method,\n created_at,\n claims_imports as \"claims_imports: Json\",\n jwks_uri_override,\n authorization_endpoint_override,\n token_endpoint_override,\n discovery_mode,\n pkce_mode,\n additional_parameters as \"additional_parameters: Json>\"\n FROM upstream_oauth_providers\n ", "describe": { "columns": [ { @@ -82,6 +82,11 @@ "ordinal": 15, "name": "pkce_mode", "type_info": "Text" + }, + { + "ordinal": 16, + "name": "additional_parameters: Json>", + "type_info": "Jsonb" } ], "parameters": { @@ -103,8 +108,9 @@ true, true, false, - false + false, + true ] }, - "hash": "2098ff49e9f4b847543b3186dbc4a8cdf1d21f788a533477c233455c57a1b755" + "hash": "ccf4965aa84c497ac9759cb31f3ecba59fdf18085791f799dfd398bef4f8eb8c" } diff --git a/crates/storage-pg/.sqlx/query-eb5af2b52b65eb9fba94418075dac0ddb04752d374e1905d1d935313090375c9.json b/crates/storage-pg/.sqlx/query-d8d9e49227b7945b4c3bcd842b59a7af6a21f7e9e2d715dc6360c3d691373903.json similarity index 85% rename from crates/storage-pg/.sqlx/query-eb5af2b52b65eb9fba94418075dac0ddb04752d374e1905d1d935313090375c9.json rename to crates/storage-pg/.sqlx/query-d8d9e49227b7945b4c3bcd842b59a7af6a21f7e9e2d715dc6360c3d691373903.json index f7e51556..e923947b 100644 --- a/crates/storage-pg/.sqlx/query-eb5af2b52b65eb9fba94418075dac0ddb04752d374e1905d1d935313090375c9.json +++ b/crates/storage-pg/.sqlx/query-d8d9e49227b7945b4c3bcd842b59a7af6a21f7e9e2d715dc6360c3d691373903.json @@ -1,6 +1,6 @@ { "db_name": "PostgreSQL", - "query": "\n SELECT\n upstream_oauth_provider_id,\n issuer,\n human_name,\n brand_name,\n scope,\n client_id,\n encrypted_client_secret,\n token_endpoint_signing_alg,\n token_endpoint_auth_method,\n created_at,\n claims_imports as \"claims_imports: Json\",\n jwks_uri_override,\n authorization_endpoint_override,\n token_endpoint_override,\n discovery_mode,\n pkce_mode\n FROM upstream_oauth_providers\n WHERE upstream_oauth_provider_id = $1\n ", + "query": "\n SELECT\n upstream_oauth_provider_id,\n issuer,\n human_name,\n brand_name,\n scope,\n client_id,\n encrypted_client_secret,\n token_endpoint_signing_alg,\n token_endpoint_auth_method,\n created_at,\n claims_imports as \"claims_imports: Json\",\n jwks_uri_override,\n authorization_endpoint_override,\n token_endpoint_override,\n discovery_mode,\n pkce_mode,\n additional_parameters as \"additional_parameters: Json>\"\n FROM upstream_oauth_providers\n WHERE upstream_oauth_provider_id = $1\n ", "describe": { "columns": [ { @@ -82,6 +82,11 @@ "ordinal": 15, "name": "pkce_mode", "type_info": "Text" + }, + { + "ordinal": 16, + "name": "additional_parameters: Json>", + "type_info": "Jsonb" } ], "parameters": { @@ -105,8 +110,9 @@ true, true, false, - false + false, + true ] }, - "hash": "eb5af2b52b65eb9fba94418075dac0ddb04752d374e1905d1d935313090375c9" + "hash": "d8d9e49227b7945b4c3bcd842b59a7af6a21f7e9e2d715dc6360c3d691373903" } diff --git a/crates/storage-pg/migrations/20240301091201_upstream_oauth_additional_parameters.sql b/crates/storage-pg/migrations/20240301091201_upstream_oauth_additional_parameters.sql new file mode 100644 index 00000000..e941636f --- /dev/null +++ b/crates/storage-pg/migrations/20240301091201_upstream_oauth_additional_parameters.sql @@ -0,0 +1,18 @@ +-- Copyright 2024 The Matrix.org Foundation C.I.C. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. + +-- Adds a column to the upstream_oauth_providers table to store additional parameters to be sent to the OAuth provider. +-- Parameters are stored as [["key", "value"], ["key", "value"], ...] in a JSONB column to keep key ordering. +ALTER TABLE upstream_oauth_providers + ADD COLUMN additional_parameters JSONB; diff --git a/crates/storage-pg/src/iden.rs b/crates/storage-pg/src/iden.rs index 194d335c..18f745e5 100644 --- a/crates/storage-pg/src/iden.rs +++ b/crates/storage-pg/src/iden.rs @@ -110,6 +110,7 @@ pub enum UpstreamOAuthProviders { ClaimsImports, DiscoveryMode, PkceMode, + AdditionalParameters, JwksUriOverride, TokenEndpointOverride, AuthorizationEndpointOverride, diff --git a/crates/storage-pg/src/upstream_oauth2/mod.rs b/crates/storage-pg/src/upstream_oauth2/mod.rs index 2876fe55..3b41998f 100644 --- a/crates/storage-pg/src/upstream_oauth2/mod.rs +++ b/crates/storage-pg/src/upstream_oauth2/mod.rs @@ -76,6 +76,7 @@ mod tests { jwks_uri_override: None, discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + additional_authorization_parameters: Vec::new(), }, ) .await @@ -259,6 +260,7 @@ mod tests { jwks_uri_override: None, discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc, pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, + additional_authorization_parameters: Vec::new(), }, ) .await diff --git a/crates/storage-pg/src/upstream_oauth2/provider.rs b/crates/storage-pg/src/upstream_oauth2/provider.rs index 8f66a1e0..a7ae86cd 100644 --- a/crates/storage-pg/src/upstream_oauth2/provider.rs +++ b/crates/storage-pg/src/upstream_oauth2/provider.rs @@ -67,6 +67,7 @@ struct ProviderLookup { token_endpoint_override: Option, discovery_mode: String, pkce_mode: String, + additional_parameters: Option>>, } impl TryFrom for UpstreamOAuthProvider { @@ -143,6 +144,11 @@ impl TryFrom for UpstreamOAuthProvider { .source(e) })?; + let additional_authorization_parameters = value + .additional_parameters + .map(|Json(x)| x) + .unwrap_or_default(); + Ok(UpstreamOAuthProvider { id, issuer: value.issuer, @@ -160,6 +166,7 @@ impl TryFrom for UpstreamOAuthProvider { jwks_uri_override, discovery_mode, pkce_mode, + additional_authorization_parameters, }) } } @@ -197,7 +204,8 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<' authorization_endpoint_override, token_endpoint_override, discovery_mode, - pkce_mode + pkce_mode, + additional_parameters as "additional_parameters: Json>" FROM upstream_oauth_providers WHERE upstream_oauth_provider_id = $1 "#, @@ -305,6 +313,7 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<' jwks_uri_override: params.jwks_uri_override, discovery_mode: params.discovery_mode, pkce_mode: params.pkce_mode, + additional_authorization_parameters: params.additional_authorization_parameters, }) } @@ -411,9 +420,10 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<' jwks_uri_override, discovery_mode, pkce_mode, + additional_parameters, created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, - $10, $11, $12, $13, $14, $15, $16) + $10, $11, $12, $13, $14, $15, $16, $17) ON CONFLICT (upstream_oauth_provider_id) DO UPDATE SET @@ -430,7 +440,8 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<' token_endpoint_override = EXCLUDED.token_endpoint_override, jwks_uri_override = EXCLUDED.jwks_uri_override, discovery_mode = EXCLUDED.discovery_mode, - pkce_mode = EXCLUDED.pkce_mode + pkce_mode = EXCLUDED.pkce_mode, + additional_parameters = EXCLUDED.additional_parameters RETURNING created_at "#, Uuid::from(id), @@ -457,6 +468,7 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<' params.jwks_uri_override.as_ref().map(ToString::to_string), params.discovery_mode.as_str(), params.pkce_mode.as_str(), + Json(¶ms.additional_authorization_parameters) as _, created_at, ) .traced() @@ -480,6 +492,7 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<' jwks_uri_override: params.jwks_uri_override, discovery_mode: params.discovery_mode, pkce_mode: params.pkce_mode, + additional_authorization_parameters: params.additional_authorization_parameters, }) } @@ -607,6 +620,13 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<' )), ProviderLookupIden::PkceMode, ) + .expr_as( + Expr::col(( + UpstreamOAuthProviders::Table, + UpstreamOAuthProviders::AdditionalParameters, + )), + ProviderLookupIden::AdditionalParameters, + ) .from(UpstreamOAuthProviders::Table) .generate_pagination( ( @@ -691,7 +711,8 @@ impl<'c> UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<' authorization_endpoint_override, token_endpoint_override, discovery_mode, - pkce_mode + pkce_mode, + additional_parameters as "additional_parameters: Json>" FROM upstream_oauth_providers "#, ) diff --git a/crates/storage/src/upstream_oauth2/provider.rs b/crates/storage/src/upstream_oauth2/provider.rs index 5de174ae..3458bfa8 100644 --- a/crates/storage/src/upstream_oauth2/provider.rs +++ b/crates/storage/src/upstream_oauth2/provider.rs @@ -74,6 +74,9 @@ pub struct UpstreamOAuthProviderParams { /// How should PKCE be used pub pkce_mode: UpstreamOAuthProviderPkceMode, + + /// Additional parameters to include in the authorization request + pub additional_authorization_parameters: Vec<(String, String)>, } /// Filter parameters for listing upstream OAuth 2.0 providers