Allow response_mode to be null and if so do not add the query param (#3700)

This commit is contained in:
Mathieu Velten
2024-12-18 17:18:39 +00:00
committed by GitHub
parent 57fe6ca0c2
commit 33e1cdbf16
15 changed files with 56 additions and 48 deletions

View File

@@ -235,14 +235,16 @@ pub async fn config_sync(
}
};
let response_mode = match provider.response_mode {
mas_config::UpstreamOAuth2ResponseMode::Query => {
mas_data_model::UpstreamOAuthProviderResponseMode::Query
}
mas_config::UpstreamOAuth2ResponseMode::FormPost => {
mas_data_model::UpstreamOAuthProviderResponseMode::FormPost
}
};
let response_mode = provider
.response_mode
.map(|response_mode| match response_mode {
mas_config::UpstreamOAuth2ResponseMode::Query => {
mas_data_model::UpstreamOAuthProviderResponseMode::Query
}
mas_config::UpstreamOAuth2ResponseMode::FormPost => {
mas_data_model::UpstreamOAuthProviderResponseMode::FormPost
}
});
if discovery_mode.is_disabled() {
if provider.authorization_endpoint.is_none() {

View File

@@ -114,12 +114,11 @@ impl ConfigurationSection for UpstreamOAuth2Config {
}
/// The response mode we ask the provider to use for the callback
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, JsonSchema)]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ResponseMode {
/// `query`: The provider will send the response as a query string in the
/// URL search parameters
#[default]
Query,
/// `form_post`: The provider will send the response as a POST request with
@@ -129,13 +128,6 @@ pub enum ResponseMode {
FormPost,
}
impl ResponseMode {
#[allow(clippy::trivially_copy_pass_by_ref)]
const fn is_default(&self) -> bool {
matches!(self, ResponseMode::Query)
}
}
/// Authentication methods used against the OAuth 2.0 provider
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
@@ -561,8 +553,8 @@ pub struct Provider {
pub jwks_uri: Option<Url>,
/// The response mode we ask the provider to use for the callback
#[serde(default, skip_serializing_if = "ResponseMode::is_default")]
pub response_mode: ResponseMode,
#[serde(skip_serializing_if = "Option::is_none")]
pub response_mode: Option<ResponseMode>,
/// How claims should be imported from the `id_token` provided by the
/// provider

View File

@@ -236,7 +236,7 @@ pub struct UpstreamOAuthProvider {
pub token_endpoint_signing_alg: Option<JsonWebSignatureAlg>,
pub token_endpoint_auth_method: TokenAuthMethod,
pub id_token_signed_response_alg: JsonWebSignatureAlg,
pub response_mode: ResponseMode,
pub response_mode: Option<ResponseMode>,
pub created_at: DateTime<Utc>,
pub disabled_at: Option<DateTime<Utc>>,
pub claims_imports: ClaimsImports,

View File

@@ -83,12 +83,15 @@ pub(crate) async fn get(
let redirect_uri = url_builder.upstream_oauth_callback(provider.id);
let data = AuthorizationRequestData::new(
let mut data = AuthorizationRequestData::new(
provider.client_id.clone(),
provider.scope.clone(),
redirect_uri,
)
.with_response_mode(provider.response_mode.into());
);
if let Some(response_mode) = provider.response_mode {
data = data.with_response_mode(response_mode.into());
}
let data = if let Some(methods) = lazy_metadata.pkce_methods().await? {
data.with_code_challenge_methods_supported(methods)

View File

@@ -417,8 +417,8 @@ mod tests {
encrypted_client_secret: None,
token_endpoint_signing_alg: None,
token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None,
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
id_token_signed_response_alg: JsonWebSignatureAlg::Rs256,
response_mode: None,
created_at: clock.now(),
disabled_at: None,
claims_imports: UpstreamOAuthProviderClaimsImports::default(),

View File

@@ -109,7 +109,7 @@ pub(crate) enum RouteError {
MissingFormParams,
#[error("Invalid response mode, expected '{expected}'")]
InvalidParamsMode {
InvalidResponseMode {
expected: UpstreamOAuthProviderResponseMode,
},
@@ -185,8 +185,7 @@ pub(crate) async fn handler(
// the query parameters for GET requests. We need to then look at the method do
// make sure it matches the expected `response_mode`
match (provider.response_mode, method) {
(UpstreamOAuthProviderResponseMode::Query, Method::GET) => {}
(UpstreamOAuthProviderResponseMode::FormPost, Method::POST) => {
(Some(UpstreamOAuthProviderResponseMode::FormPost) | None, Method::POST) => {
// We set the cookies with a `Same-Site` policy set to `Lax`, so because this is
// usually a cross-site form POST, we need to render a form with the
// same values, which posts back to the same URL. However, there are
@@ -202,7 +201,8 @@ pub(crate) async fn handler(
return Ok(Html(html).into_response());
}
}
(expected, _) => return Err(RouteError::InvalidParamsMode { expected }),
(None, _) | (Some(UpstreamOAuthProviderResponseMode::Query), Method::GET) => {}
(Some(expected), _) => return Err(RouteError::InvalidResponseMode { expected }),
}
let (session_id, _post_auth_action) = sessions_cookie

View File

@@ -934,7 +934,7 @@ mod tests {
jwks_uri_override: None,
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
response_mode: None,
additional_authorization_parameters: Vec::new(),
},
)

View File

@@ -416,7 +416,7 @@ mod test {
jwks_uri_override: None,
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
response_mode: None,
additional_authorization_parameters: Vec::new(),
},
)
@@ -456,7 +456,7 @@ mod test {
jwks_uri_override: None,
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
response_mode: None,
additional_authorization_parameters: Vec::new(),
},
)

View File

@@ -146,7 +146,7 @@
true,
false,
false,
false,
true,
true
]
},

View File

@@ -144,7 +144,7 @@
true,
false,
false,
false,
true,
true
]
},

View File

@@ -0,0 +1,7 @@
-- Copyright 2024 New Vector Ltd.
--
-- SPDX-License-Identifier: AGPL-3.0-only
-- Please see LICENSE in the repository root for full details.
-- Drop not null requirement on response mode, so we can ignore this query parameter.
ALTER TABLE "upstream_oauth_providers" ALTER COLUMN "response_mode" DROP NOT NULL;

View File

@@ -74,7 +74,7 @@ mod tests {
jwks_uri_override: None,
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
response_mode: None,
additional_authorization_parameters: Vec::new(),
},
)
@@ -319,7 +319,7 @@ mod tests {
jwks_uri_override: None,
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
response_mode: None,
additional_authorization_parameters: Vec::new(),
},
)

View File

@@ -68,7 +68,7 @@ struct ProviderLookup {
userinfo_endpoint_override: Option<String>,
discovery_mode: String,
pkce_mode: String,
response_mode: String,
response_mode: Option<String>,
additional_parameters: Option<Json<Vec<(String, String)>>>,
}
@@ -177,12 +177,16 @@ impl TryFrom<ProviderLookup> for UpstreamOAuthProvider {
.source(e)
})?;
let response_mode = value.response_mode.parse().map_err(|e| {
DatabaseInconsistencyError::on("upstream_oauth_providers")
.column("response_mode")
.row(id)
.source(e)
})?;
let response_mode = value
.response_mode
.map(|x| x.parse())
.transpose()
.map_err(|e| {
DatabaseInconsistencyError::on("upstream_oauth_providers")
.column("response_mode")
.row(id)
.source(e)
})?;
let additional_authorization_parameters = value
.additional_parameters
@@ -370,7 +374,7 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> {
params.jwks_uri_override.as_ref().map(ToString::to_string),
params.discovery_mode.as_str(),
params.pkce_mode.as_str(),
params.response_mode.as_str(),
params.response_mode.as_ref().map(ToString::to_string),
created_at,
)
.traced()
@@ -576,7 +580,7 @@ impl UpstreamOAuthProviderRepository for PgUpstreamOAuthProviderRepository<'_> {
params.jwks_uri_override.as_ref().map(ToString::to_string),
params.discovery_mode.as_str(),
params.pkce_mode.as_str(),
params.response_mode.as_str(),
params.response_mode.as_ref().map(ToString::to_string),
Json(&params.additional_authorization_parameters) as _,
created_at,
)

View File

@@ -91,7 +91,7 @@ pub struct UpstreamOAuthProviderParams {
pub pkce_mode: UpstreamOAuthProviderPkceMode,
/// What response mode it should ask
pub response_mode: UpstreamOAuthProviderResponseMode,
pub response_mode: Option<UpstreamOAuthProviderResponseMode>,
/// Additional parameters to include in the authorization request
pub additional_authorization_parameters: Vec<(String, String)>,

View File

@@ -22,8 +22,8 @@ use mas_data_model::{
AuthorizationGrant, BrowserSession, Client, CompatSsoLogin, CompatSsoLoginState,
DeviceCodeGrant, UpstreamOAuthLink, UpstreamOAuthProvider, UpstreamOAuthProviderClaimsImports,
UpstreamOAuthProviderDiscoveryMode, UpstreamOAuthProviderPkceMode,
UpstreamOAuthProviderResponseMode, UpstreamOAuthProviderTokenAuthMethod, User, UserAgent,
UserEmail, UserEmailVerification, UserRecoverySession,
UpstreamOAuthProviderTokenAuthMethod, User, UserAgent, UserEmail, UserEmailVerification,
UserRecoverySession,
};
use mas_i18n::DataLocale;
use mas_iana::jose::JsonWebSignatureAlg;
@@ -1408,7 +1408,7 @@ impl TemplateContext for UpstreamRegister {
userinfo_signed_response_alg: None,
discovery_mode: UpstreamOAuthProviderDiscoveryMode::Oidc,
pkce_mode: UpstreamOAuthProviderPkceMode::Auto,
response_mode: UpstreamOAuthProviderResponseMode::Query,
response_mode: None,
additional_authorization_parameters: Vec::new(),
created_at: now,
disabled_at: None,