diff --git a/.gitignore b/.gitignore index 2f7896d1d..1046cc35a 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,9 @@ +# Rust target/ + +# Editors +.idea +.nova + +# OS garbage +.DS_Store diff --git a/crates/cli/src/sync.rs b/crates/cli/src/sync.rs index 50ffcc15c..0c1063607 100644 --- a/crates/cli/src/sync.rs +++ b/crates/cli/src/sync.rs @@ -304,6 +304,7 @@ pub async fn config_sync( .additional_authorization_parameters .into_iter() .collect(), + forward_login_hint: provider.forward_login_hint, ui_order, }, ) diff --git a/crates/config/src/sections/upstream_oauth2.rs b/crates/config/src/sections/upstream_oauth2.rs index 1183a6421..cf02aaa15 100644 --- a/crates/config/src/sections/upstream_oauth2.rs +++ b/crates/config/src/sections/upstream_oauth2.rs @@ -565,4 +565,11 @@ pub struct Provider { /// Orders of the keys are not preserved. #[serde(default, skip_serializing_if = "BTreeMap::is_empty")] pub additional_authorization_parameters: BTreeMap, + + /// Whether the `login_hint` should be forwarded to the provider in the + /// authorization request. + /// + /// Defaults to `false`. + #[serde(default)] + pub forward_login_hint: bool, } diff --git a/crates/data-model/src/upstream_oauth2/provider.rs b/crates/data-model/src/upstream_oauth2/provider.rs index b81704661..7362d807b 100644 --- a/crates/data-model/src/upstream_oauth2/provider.rs +++ b/crates/data-model/src/upstream_oauth2/provider.rs @@ -241,6 +241,7 @@ pub struct UpstreamOAuthProvider { pub disabled_at: Option>, pub claims_imports: ClaimsImports, pub additional_authorization_parameters: Vec<(String, String)>, + pub forward_login_hint: bool, } impl PartialOrd for UpstreamOAuthProvider { diff --git a/crates/handlers/src/admin/v1/upstream_oauth_links/mod.rs b/crates/handlers/src/admin/v1/upstream_oauth_links/mod.rs index 12792e3a6..6696a7109 100644 --- a/crates/handlers/src/admin/v1/upstream_oauth_links/mod.rs +++ b/crates/handlers/src/admin/v1/upstream_oauth_links/mod.rs @@ -47,6 +47,7 @@ mod test_utils { userinfo_endpoint_override: None, jwks_uri_override: None, additional_authorization_parameters: Vec::new(), + forward_login_hint: false, ui_order: 0, } } diff --git a/crates/handlers/src/upstream_oauth2/authorize.rs b/crates/handlers/src/upstream_oauth2/authorize.rs index bf1d76182..43403c137 100644 --- a/crates/handlers/src/upstream_oauth2/authorize.rs +++ b/crates/handlers/src/upstream_oauth2/authorize.rs @@ -12,7 +12,7 @@ use hyper::StatusCode; use mas_axum_utils::{cookies::CookieJar, record_error}; use mas_data_model::UpstreamOAuthProvider; use mas_oidc_client::requests::authorization_code::AuthorizationRequestData; -use mas_router::UrlBuilder; +use mas_router::{PostAuthAction, UrlBuilder}; use mas_storage::{ BoxClock, BoxRepository, BoxRng, upstream_oauth2::{UpstreamOAuthProviderRepository, UpstreamOAuthSessionRepository}, @@ -92,6 +92,21 @@ pub(crate) async fn get( data = data.with_response_mode(response_mode.into()); } + // Forward the raw login hint upstream for the provider to handle however it + // sees fit + if provider.forward_login_hint { + if let Some(PostAuthAction::ContinueAuthorizationGrant { id }) = &query.post_auth_action { + if let Some(login_hint) = repo + .oauth2_authorization_grant() + .lookup(*id) + .await? + .and_then(|grant| grant.login_hint) + { + data = data.with_login_hint(login_hint); + } + } + } + let data = if let Some(methods) = lazy_metadata.pkce_methods().await? { data.with_code_challenge_methods_supported(methods) } else { diff --git a/crates/handlers/src/upstream_oauth2/cache.rs b/crates/handlers/src/upstream_oauth2/cache.rs index 9b8d6360d..6c1b7de63 100644 --- a/crates/handlers/src/upstream_oauth2/cache.rs +++ b/crates/handlers/src/upstream_oauth2/cache.rs @@ -426,6 +426,7 @@ mod tests { disabled_at: None, claims_imports: UpstreamOAuthProviderClaimsImports::default(), additional_authorization_parameters: Vec::new(), + forward_login_hint: false, }; // 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 0648bcfb4..ce2138b2a 100644 --- a/crates/handlers/src/upstream_oauth2/link.rs +++ b/crates/handlers/src/upstream_oauth2/link.rs @@ -983,6 +983,7 @@ mod tests { pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, response_mode: None, additional_authorization_parameters: Vec::new(), + forward_login_hint: false, ui_order: 0, }, ) diff --git a/crates/handlers/src/views/login.rs b/crates/handlers/src/views/login.rs index bef1faf02..87884ddba 100644 --- a/crates/handlers/src/views/login.rs +++ b/crates/handlers/src/views/login.rs @@ -498,6 +498,7 @@ mod test { pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, response_mode: None, additional_authorization_parameters: Vec::new(), + forward_login_hint: false, ui_order: 0, }, ) @@ -539,6 +540,7 @@ mod test { pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, response_mode: None, additional_authorization_parameters: Vec::new(), + forward_login_hint: false, ui_order: 1, }, ) diff --git a/crates/storage-pg/.sqlx/query-585a1e78834c953c80a0af9215348b0f551b16f4cb57c022b50212cfc3d8431f.json b/crates/storage-pg/.sqlx/query-585a1e78834c953c80a0af9215348b0f551b16f4cb57c022b50212cfc3d8431f.json new file mode 100644 index 000000000..a7b63ca21 --- /dev/null +++ b/crates/storage-pg/.sqlx/query-585a1e78834c953c80a0af9215348b0f551b16f4cb57c022b50212cfc3d8431f.json @@ -0,0 +1,45 @@ +{ + "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 id_token_signed_response_alg,\n fetch_userinfo,\n userinfo_signed_response_alg,\n client_id,\n encrypted_client_secret,\n claims_imports,\n authorization_endpoint_override,\n token_endpoint_override,\n userinfo_endpoint_override,\n jwks_uri_override,\n discovery_mode,\n pkce_mode,\n response_mode,\n additional_parameters,\n forward_login_hint,\n ui_order,\n created_at\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11,\n $12, $13, $14, $15, $16, $17, $18, $19, $20,\n $21, $22, $23, $24)\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 id_token_signed_response_alg = EXCLUDED.id_token_signed_response_alg,\n fetch_userinfo = EXCLUDED.fetch_userinfo,\n userinfo_signed_response_alg = EXCLUDED.userinfo_signed_response_alg,\n disabled_at = NULL,\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 userinfo_endpoint_override = EXCLUDED.userinfo_endpoint_override,\n jwks_uri_override = EXCLUDED.jwks_uri_override,\n discovery_mode = EXCLUDED.discovery_mode,\n pkce_mode = EXCLUDED.pkce_mode,\n response_mode = EXCLUDED.response_mode,\n additional_parameters = EXCLUDED.additional_parameters,\n forward_login_hint = EXCLUDED.forward_login_hint,\n ui_order = EXCLUDED.ui_order\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", + "Bool", + "Text", + "Text", + "Text", + "Jsonb", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Text", + "Jsonb", + "Bool", + "Int4", + "Timestamptz" + ] + }, + "nullable": [ + false + ] + }, + "hash": "585a1e78834c953c80a0af9215348b0f551b16f4cb57c022b50212cfc3d8431f" +} diff --git a/crates/storage-pg/.sqlx/query-72de26d5e3c56f4b0658685a95b45b647bb6637e55b662a5a548aa3308c62a8a.json b/crates/storage-pg/.sqlx/query-72de26d5e3c56f4b0658685a95b45b647bb6637e55b662a5a548aa3308c62a8a.json deleted file mode 100644 index 7ab023046..000000000 --- a/crates/storage-pg/.sqlx/query-72de26d5e3c56f4b0658685a95b45b647bb6637e55b662a5a548aa3308c62a8a.json +++ /dev/null @@ -1,44 +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 id_token_signed_response_alg,\n fetch_userinfo,\n userinfo_signed_response_alg,\n client_id,\n encrypted_client_secret,\n claims_imports,\n authorization_endpoint_override,\n token_endpoint_override,\n userinfo_endpoint_override,\n jwks_uri_override,\n discovery_mode,\n pkce_mode,\n response_mode,\n additional_parameters,\n ui_order,\n created_at\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11,\n $12, $13, $14, $15, $16, $17, $18, $19, $20,\n $21, $22, $23)\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 id_token_signed_response_alg = EXCLUDED.id_token_signed_response_alg,\n fetch_userinfo = EXCLUDED.fetch_userinfo,\n userinfo_signed_response_alg = EXCLUDED.userinfo_signed_response_alg,\n disabled_at = NULL,\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 userinfo_endpoint_override = EXCLUDED.userinfo_endpoint_override,\n jwks_uri_override = EXCLUDED.jwks_uri_override,\n discovery_mode = EXCLUDED.discovery_mode,\n pkce_mode = EXCLUDED.pkce_mode,\n response_mode = EXCLUDED.response_mode,\n additional_parameters = EXCLUDED.additional_parameters,\n ui_order = EXCLUDED.ui_order\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", - "Bool", - "Text", - "Text", - "Text", - "Jsonb", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Text", - "Jsonb", - "Int4", - "Timestamptz" - ] - }, - "nullable": [ - false - ] - }, - "hash": "72de26d5e3c56f4b0658685a95b45b647bb6637e55b662a5a548aa3308c62a8a" -} diff --git a/crates/storage-pg/.sqlx/query-e25af41189846e26da99e5d8a1462eab5efe330f60ef8c6c813c747424ba7ec9.json b/crates/storage-pg/.sqlx/query-a711f4c6fa38b98c960ee565038d42ea16db436352b19fcd3b2c620c73d9cc0c.json similarity index 78% rename from crates/storage-pg/.sqlx/query-e25af41189846e26da99e5d8a1462eab5efe330f60ef8c6c813c747424ba7ec9.json rename to crates/storage-pg/.sqlx/query-a711f4c6fa38b98c960ee565038d42ea16db436352b19fcd3b2c620c73d9cc0c.json index 1a2a19d81..9944e855b 100644 --- a/crates/storage-pg/.sqlx/query-e25af41189846e26da99e5d8a1462eab5efe330f60ef8c6c813c747424ba7ec9.json +++ b/crates/storage-pg/.sqlx/query-a711f4c6fa38b98c960ee565038d42ea16db436352b19fcd3b2c620c73d9cc0c.json @@ -1,6 +1,6 @@ { "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 id_token_signed_response_alg,\n fetch_userinfo,\n userinfo_signed_response_alg,\n client_id,\n encrypted_client_secret,\n claims_imports,\n authorization_endpoint_override,\n token_endpoint_override,\n userinfo_endpoint_override,\n jwks_uri_override,\n discovery_mode,\n pkce_mode,\n response_mode,\n created_at\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,\n $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21)\n ", + "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 id_token_signed_response_alg,\n fetch_userinfo,\n userinfo_signed_response_alg,\n client_id,\n encrypted_client_secret,\n claims_imports,\n authorization_endpoint_override,\n token_endpoint_override,\n userinfo_endpoint_override,\n jwks_uri_override,\n discovery_mode,\n pkce_mode,\n response_mode,\n forward_login_hint,\n created_at\n ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,\n $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22)\n ", "describe": { "columns": [], "parameters": { @@ -25,10 +25,11 @@ "Text", "Text", "Text", + "Bool", "Timestamptz" ] }, "nullable": [] }, - "hash": "e25af41189846e26da99e5d8a1462eab5efe330f60ef8c6c813c747424ba7ec9" + "hash": "a711f4c6fa38b98c960ee565038d42ea16db436352b19fcd3b2c620c73d9cc0c" } diff --git a/crates/storage-pg/.sqlx/query-1d758df58ccfead4cb39ee8f88f60b382b7881e9c4ead31ff257ff5ff4414b6e.json b/crates/storage-pg/.sqlx/query-a82b87ccfaa1de9a8e6433aaa67382fbb5029d5f7adf95aaa0decd668d25ba89.json similarity index 90% rename from crates/storage-pg/.sqlx/query-1d758df58ccfead4cb39ee8f88f60b382b7881e9c4ead31ff257ff5ff4414b6e.json rename to crates/storage-pg/.sqlx/query-a82b87ccfaa1de9a8e6433aaa67382fbb5029d5f7adf95aaa0decd668d25ba89.json index 65b97215c..7c1a26a86 100644 --- a/crates/storage-pg/.sqlx/query-1d758df58ccfead4cb39ee8f88f60b382b7881e9c4ead31ff257ff5ff4414b6e.json +++ b/crates/storage-pg/.sqlx/query-a82b87ccfaa1de9a8e6433aaa67382fbb5029d5f7adf95aaa0decd668d25ba89.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 id_token_signed_response_alg,\n fetch_userinfo,\n userinfo_signed_response_alg,\n created_at,\n disabled_at,\n claims_imports as \"claims_imports: Json\",\n jwks_uri_override,\n authorization_endpoint_override,\n token_endpoint_override,\n userinfo_endpoint_override,\n discovery_mode,\n pkce_mode,\n response_mode,\n additional_parameters as \"additional_parameters: Json>\"\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 id_token_signed_response_alg,\n fetch_userinfo,\n userinfo_signed_response_alg,\n created_at,\n disabled_at,\n claims_imports as \"claims_imports: Json\",\n jwks_uri_override,\n authorization_endpoint_override,\n token_endpoint_override,\n userinfo_endpoint_override,\n discovery_mode,\n pkce_mode,\n response_mode,\n additional_parameters as \"additional_parameters: Json>\",\n forward_login_hint\n FROM upstream_oauth_providers\n WHERE upstream_oauth_provider_id = $1\n ", "describe": { "columns": [ { @@ -117,6 +117,11 @@ "ordinal": 22, "name": "additional_parameters: Json>", "type_info": "Jsonb" + }, + { + "ordinal": 23, + "name": "forward_login_hint", + "type_info": "Bool" } ], "parameters": { @@ -147,8 +152,9 @@ false, false, true, - true + true, + false ] }, - "hash": "1d758df58ccfead4cb39ee8f88f60b382b7881e9c4ead31ff257ff5ff4414b6e" + "hash": "a82b87ccfaa1de9a8e6433aaa67382fbb5029d5f7adf95aaa0decd668d25ba89" } diff --git a/crates/storage-pg/.sqlx/query-c1e55ffd09181c0d8ddd0df2843690aeae4a20329045ab23639181a0d0903178.json b/crates/storage-pg/.sqlx/query-e6d66a7980933c12ab046958e02d419129ef52ac45bea4345471838016cae917.json similarity index 88% rename from crates/storage-pg/.sqlx/query-c1e55ffd09181c0d8ddd0df2843690aeae4a20329045ab23639181a0d0903178.json rename to crates/storage-pg/.sqlx/query-e6d66a7980933c12ab046958e02d419129ef52ac45bea4345471838016cae917.json index b929df201..d544590c4 100644 --- a/crates/storage-pg/.sqlx/query-c1e55ffd09181c0d8ddd0df2843690aeae4a20329045ab23639181a0d0903178.json +++ b/crates/storage-pg/.sqlx/query-e6d66a7980933c12ab046958e02d419129ef52ac45bea4345471838016cae917.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 id_token_signed_response_alg,\n fetch_userinfo,\n userinfo_signed_response_alg,\n created_at,\n disabled_at,\n claims_imports as \"claims_imports: Json\",\n jwks_uri_override,\n authorization_endpoint_override,\n token_endpoint_override,\n userinfo_endpoint_override,\n discovery_mode,\n pkce_mode,\n response_mode,\n additional_parameters as \"additional_parameters: Json>\"\n FROM upstream_oauth_providers\n WHERE disabled_at IS NULL\n ORDER BY ui_order ASC, upstream_oauth_provider_id ASC\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 id_token_signed_response_alg,\n fetch_userinfo,\n userinfo_signed_response_alg,\n created_at,\n disabled_at,\n claims_imports as \"claims_imports: Json\",\n jwks_uri_override,\n authorization_endpoint_override,\n token_endpoint_override,\n userinfo_endpoint_override,\n discovery_mode,\n pkce_mode,\n response_mode,\n additional_parameters as \"additional_parameters: Json>\",\n forward_login_hint\n FROM upstream_oauth_providers\n WHERE disabled_at IS NULL\n ORDER BY ui_order ASC, upstream_oauth_provider_id ASC\n ", "describe": { "columns": [ { @@ -117,6 +117,11 @@ "ordinal": 22, "name": "additional_parameters: Json>", "type_info": "Jsonb" + }, + { + "ordinal": 23, + "name": "forward_login_hint", + "type_info": "Bool" } ], "parameters": { @@ -145,8 +150,9 @@ false, false, true, - true + true, + false ] }, - "hash": "c1e55ffd09181c0d8ddd0df2843690aeae4a20329045ab23639181a0d0903178" + "hash": "e6d66a7980933c12ab046958e02d419129ef52ac45bea4345471838016cae917" } diff --git a/crates/storage-pg/.sqlx/query-fcd8b4b9e003d1540357c6bf1ff9c715560d011d4c01112703a9c046170c84f1.json b/crates/storage-pg/.sqlx/query-fcd8b4b9e003d1540357c6bf1ff9c715560d011d4c01112703a9c046170c84f1.json index ef1ac0372..f5503fa0e 100644 --- a/crates/storage-pg/.sqlx/query-fcd8b4b9e003d1540357c6bf1ff9c715560d011d4c01112703a9c046170c84f1.json +++ b/crates/storage-pg/.sqlx/query-fcd8b4b9e003d1540357c6bf1ff9c715560d011d4c01112703a9c046170c84f1.json @@ -23,7 +23,7 @@ "Left": [] }, "nullable": [ - true, + false, true, null ] diff --git a/crates/storage-pg/migrations/20250506161158_upstream_oauth2_forward_login_hint.sql b/crates/storage-pg/migrations/20250506161158_upstream_oauth2_forward_login_hint.sql new file mode 100644 index 000000000..2aa29a821 --- /dev/null +++ b/crates/storage-pg/migrations/20250506161158_upstream_oauth2_forward_login_hint.sql @@ -0,0 +1,8 @@ +-- Copyright 2025 New Vector Ltd. +-- +-- SPDX-License-Identifier: AGPL-3.0-only +-- Please see LICENSE in the repository root for full details. + +-- Add the forward_login_hint column to the upstream_oauth_providers table +ALTER TABLE "upstream_oauth_providers" + ADD COLUMN "forward_login_hint" BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/crates/storage-pg/src/iden.rs b/crates/storage-pg/src/iden.rs index d64ce930e..76067b2fa 100644 --- a/crates/storage-pg/src/iden.rs +++ b/crates/storage-pg/src/iden.rs @@ -119,6 +119,7 @@ pub enum UpstreamOAuthProviders { PkceMode, ResponseMode, AdditionalParameters, + ForwardLoginHint, JwksUriOverride, TokenEndpointOverride, AuthorizationEndpointOverride, diff --git a/crates/storage-pg/src/upstream_oauth2/mod.rs b/crates/storage-pg/src/upstream_oauth2/mod.rs index d802e9bdb..2b2662df7 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 { pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, response_mode: None, additional_authorization_parameters: Vec::new(), + forward_login_hint: false, ui_order: 0, }, ) @@ -323,6 +324,7 @@ mod tests { pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto, response_mode: None, additional_authorization_parameters: Vec::new(), + forward_login_hint: false, ui_order: 0, }, ) diff --git a/crates/storage-pg/src/upstream_oauth2/provider.rs b/crates/storage-pg/src/upstream_oauth2/provider.rs index 2e5f7233f..879d7c658 100644 --- a/crates/storage-pg/src/upstream_oauth2/provider.rs +++ b/crates/storage-pg/src/upstream_oauth2/provider.rs @@ -70,6 +70,7 @@ struct ProviderLookup { pkce_mode: String, response_mode: Option, additional_parameters: Option>>, + forward_login_hint: bool, } impl TryFrom for UpstreamOAuthProvider { @@ -217,6 +218,7 @@ impl TryFrom for UpstreamOAuthProvider { pkce_mode, response_mode, additional_authorization_parameters, + forward_login_hint: value.forward_login_hint, }) } } @@ -274,7 +276,8 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { discovery_mode, pkce_mode, response_mode, - additional_parameters as "additional_parameters: Json>" + additional_parameters as "additional_parameters: Json>", + forward_login_hint FROM upstream_oauth_providers WHERE upstream_oauth_provider_id = $1 "#, @@ -336,9 +339,10 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { discovery_mode, pkce_mode, response_mode, + forward_login_hint, created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, - $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21) + $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22) "#, Uuid::from(id), params.issuer.as_deref(), @@ -375,6 +379,7 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { params.discovery_mode.as_str(), params.pkce_mode.as_str(), params.response_mode.as_ref().map(ToString::to_string), + params.forward_login_hint, created_at, ) .traced() @@ -405,6 +410,7 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { pkce_mode: params.pkce_mode, response_mode: params.response_mode, additional_authorization_parameters: params.additional_authorization_parameters, + forward_login_hint: params.forward_login_hint, }) } @@ -517,11 +523,12 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { pkce_mode, response_mode, additional_parameters, + forward_login_hint, ui_order, created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, - $21, $22, $23) + $21, $22, $23, $24) ON CONFLICT (upstream_oauth_provider_id) DO UPDATE SET @@ -546,6 +553,7 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { pkce_mode = EXCLUDED.pkce_mode, response_mode = EXCLUDED.response_mode, additional_parameters = EXCLUDED.additional_parameters, + forward_login_hint = EXCLUDED.forward_login_hint, ui_order = EXCLUDED.ui_order RETURNING created_at "#, @@ -585,6 +593,7 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { params.pkce_mode.as_str(), params.response_mode.as_ref().map(ToString::to_string), Json(¶ms.additional_authorization_parameters) as _, + params.forward_login_hint, params.ui_order, created_at, ) @@ -616,6 +625,7 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { pkce_mode: params.pkce_mode, response_mode: params.response_mode, additional_authorization_parameters: params.additional_authorization_parameters, + forward_login_hint: params.forward_login_hint, }) } @@ -826,6 +836,13 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { )), ProviderLookupIden::AdditionalParameters, ) + .expr_as( + Expr::col(( + UpstreamOAuthProviders::Table, + UpstreamOAuthProviders::ForwardLoginHint, + )), + ProviderLookupIden::ForwardLoginHint, + ) .from(UpstreamOAuthProviders::Table) .apply_filter(filter) .generate_pagination( @@ -918,7 +935,8 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> { discovery_mode, pkce_mode, response_mode, - additional_parameters as "additional_parameters: Json>" + additional_parameters as "additional_parameters: Json>", + forward_login_hint FROM upstream_oauth_providers WHERE disabled_at IS NULL ORDER BY ui_order ASC, upstream_oauth_provider_id ASC diff --git a/crates/storage/src/upstream_oauth2/provider.rs b/crates/storage/src/upstream_oauth2/provider.rs index 673050a8f..ac6553b88 100644 --- a/crates/storage/src/upstream_oauth2/provider.rs +++ b/crates/storage/src/upstream_oauth2/provider.rs @@ -96,6 +96,9 @@ pub struct UpstreamOAuthProviderParams { /// Additional parameters to include in the authorization request pub additional_authorization_parameters: Vec<(String, String)>, + /// Whether to forward the login hint to the upstream provider. + pub forward_login_hint: bool, + /// The position of the provider in the UI pub ui_order: i32, } diff --git a/crates/syn2mas/src/mas_writer/snapshots/syn2mas__mas_writer__test__write_user_with_upstream_provider_link.snap b/crates/syn2mas/src/mas_writer/snapshots/syn2mas__mas_writer__test__write_user_with_upstream_provider_link.snap index 1fbf6a100..a368aa9a5 100644 --- a/crates/syn2mas/src/mas_writer/snapshots/syn2mas__mas_writer__test__write_user_with_upstream_provider_link.snap +++ b/crates/syn2mas/src/mas_writer/snapshots/syn2mas__mas_writer__test__write_user_with_upstream_provider_link.snap @@ -20,6 +20,7 @@ upstream_oauth_providers: discovery_mode: oidc encrypted_client_secret: ~ fetch_userinfo: "false" + forward_login_hint: "false" human_name: ~ id_token_signed_response_alg: RS256 issuer: ~ diff --git a/crates/syn2mas/src/synapse_reader/config/oidc.rs b/crates/syn2mas/src/synapse_reader/config/oidc.rs index 5a9321ce2..9eea0a9be 100644 --- a/crates/syn2mas/src/synapse_reader/config/oidc.rs +++ b/crates/syn2mas/src/synapse_reader/config/oidc.rs @@ -175,6 +175,8 @@ pub struct OidcProvider { #[serde(default)] additional_authorization_parameters: BTreeMap, #[serde(default)] + forward_login_hint: bool, + #[serde(default)] user_mapping_provider: UserMappingProvider, } @@ -342,6 +344,7 @@ impl OidcProvider { response_mode, claims_imports, additional_authorization_parameters, + forward_login_hint: self.forward_login_hint, }) } } diff --git a/crates/templates/src/context.rs b/crates/templates/src/context.rs index 8c538a62a..54b2f193d 100644 --- a/crates/templates/src/context.rs +++ b/crates/templates/src/context.rs @@ -1485,6 +1485,7 @@ impl TemplateContext for UpstreamRegister { pkce_mode: UpstreamOAuthProviderPkceMode::Auto, response_mode: None, additional_authorization_parameters: Vec::new(), + forward_login_hint: false, created_at: now, disabled_at: None, }, diff --git a/docs/config.schema.json b/docs/config.schema.json index 818f592e7..06fa0e768 100644 --- a/docs/config.schema.json +++ b/docs/config.schema.json @@ -2118,6 +2118,11 @@ "additionalProperties": { "type": "string" } + }, + "forward_login_hint": { + "description": "Whether the `login_hint` should be forwarded to the provider in the authorization request.\n\nDefaults to `false`.", + "default": false, + "type": "boolean" } } }, diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index e6c504e0c..2303e889e 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -712,6 +712,10 @@ upstream_oauth2: # Additional parameters to include in the authorization request #additional_authorization_parameters: # foo: "bar" + + # Whether the `login_hint` should be forwarded to the provider in the + # authorization request. + #forward_login_hint: false # How user attributes should be mapped #