Add a configuration for forwarding the login hint to the upstream provider.

This commit is contained in:
Doug
2025-05-06 17:17:58 +01:00
parent 830b1690cc
commit 7f91c8948b
21 changed files with 135 additions and 60 deletions

View File

@@ -304,6 +304,7 @@ pub async fn config_sync(
.additional_authorization_parameters
.into_iter()
.collect(),
forward_login_hint: provider.forward_login_hint,
ui_order,
},
)

View File

@@ -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<String, String>,
/// Whether the login_hint should be forwarded to the provider in the
/// authorization request.
///
/// Defaults to `false`.
#[serde(default)]
pub forward_login_hint: bool,
}

View File

@@ -241,6 +241,7 @@ pub struct UpstreamOAuthProvider {
pub disabled_at: Option<DateTime<Utc>>,
pub claims_imports: ClaimsImports,
pub additional_authorization_parameters: Vec<(String, String)>,
pub forward_login_hint: bool,
}
impl PartialOrd for UpstreamOAuthProvider {

View File

@@ -94,9 +94,16 @@ pub(crate) async fn get(
// Forward the raw login hint upstream for the provider to handle however it
// sees fit
if let Some(PostAuthAction::ContinueAuthorizationGrant { id }) = &query.post_auth_action {
if let Some(grant) = repo.oauth2_authorization_grant().lookup(*id).await? {
data.login_hint = grant.login_hint;
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);
}
}
}

View File

@@ -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

View File

@@ -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,
},
)

View File

@@ -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,
},
)

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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"
}

View File

@@ -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<UpstreamOAuthProviderClaimsImports>\",\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<Vec<(String, String)>>\"\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<UpstreamOAuthProviderClaimsImports>\",\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<Vec<(String, String)>>\",\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<Vec<(String, String)>>",
"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"
}

View File

@@ -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<UpstreamOAuthProviderClaimsImports>\",\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<Vec<(String, String)>>\"\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<UpstreamOAuthProviderClaimsImports>\",\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<Vec<(String, String)>>\",\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<Vec<(String, String)>>",
"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"
}

View File

@@ -23,7 +23,7 @@
"Left": []
},
"nullable": [
true,
false,
true,
null
]

View File

@@ -0,0 +1,8 @@
-- Copyright 2024 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;

View File

@@ -119,6 +119,7 @@ pub enum UpstreamOAuthProviders {
PkceMode,
ResponseMode,
AdditionalParameters,
ForwardLoginHint,
JwksUriOverride,
TokenEndpointOverride,
AuthorizationEndpointOverride,

View File

@@ -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,
},
)

View File

@@ -70,6 +70,7 @@ struct ProviderLookup {
pkce_mode: String,
response_mode: Option<String>,
additional_parameters: Option<Json<Vec<(String, String)>>>,
forward_login_hint: bool,
}
impl TryFrom<ProviderLookup> for UpstreamOAuthProvider {
@@ -217,6 +218,7 @@ impl TryFrom<ProviderLookup> 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<Vec<(String, String)>>"
additional_parameters as "additional_parameters: Json<Vec<(String, String)>>",
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(&params.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<Vec<(String, String)>>"
additional_parameters as "additional_parameters: Json<Vec<(String, String)>>",
forward_login_hint
FROM upstream_oauth_providers
WHERE disabled_at IS NULL
ORDER BY ui_order ASC, upstream_oauth_provider_id ASC

View File

@@ -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,
}

View File

@@ -175,6 +175,8 @@ pub struct OidcProvider {
#[serde(default)]
additional_authorization_parameters: BTreeMap<String, String>,
#[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,
})
}
}

View File

@@ -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,
},

View File

@@ -2118,6 +2118,11 @@
"additionalProperties": {
"type": "string"
}
},
"forward_login_mode": {
"description": " Whether the login_hint should be forwarded to the provider in the authorization request.",
"default": false,
"type": "boolean"
}
}
},