Skip to content

Commit 6efe8bf

Browse files
committed
Allow setting the response_mode on upstream OAuth 2.0 providers
1 parent ab4f438 commit 6efe8bf

22 files changed

+240
-53
lines changed

crates/cli/src/sync.rs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,15 @@ pub async fn config_sync(
232232
}
233233
};
234234

235+
let response_mode = match provider.response_mode {
236+
mas_config::UpstreamOAuth2ResponseMode::Query => {
237+
mas_data_model::UpstreamOAuthProviderResponseMode::Query
238+
}
239+
mas_config::UpstreamOAuth2ResponseMode::FormPost => {
240+
mas_data_model::UpstreamOAuthProviderResponseMode::FormPost
241+
}
242+
};
243+
235244
if discovery_mode.is_disabled() {
236245
if provider.authorization_endpoint.is_none() {
237246
error!("Provider has discovery disabled but no authorization endpoint set");
@@ -279,6 +288,7 @@ pub async fn config_sync(
279288
jwks_uri_override: provider.jwks_uri,
280289
discovery_mode,
281290
pkce_mode,
291+
response_mode,
282292
additional_authorization_parameters: provider
283293
.additional_authorization_parameters
284294
.into_iter()

crates/config/src/sections/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ pub use self::{
5151
ClaimsImports as UpstreamOAuth2ClaimsImports, DiscoveryMode as UpstreamOAuth2DiscoveryMode,
5252
EmailImportPreference as UpstreamOAuth2EmailImportPreference,
5353
ImportAction as UpstreamOAuth2ImportAction, PkceMethod as UpstreamOAuth2PkceMethod,
54+
ResponseMode as UpstreamOAuth2ResponseMode,
5455
SetEmailVerification as UpstreamOAuth2SetEmailVerification,
5556
TokenAuthMethod as UpstreamOAuth2TokenAuthMethod, UpstreamOAuth2Config,
5657
},

crates/config/src/sections/upstream_oauth2.rs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,29 @@ impl ConfigurationSection for UpstreamOAuth2Config {
105105
}
106106
}
107107

108+
/// The response mode we ask the provider to use for the callback
109+
#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default, JsonSchema)]
110+
#[serde(rename_all = "snake_case")]
111+
pub enum ResponseMode {
112+
/// `query`: The provider will send the response as a query string in the
113+
/// URL search parameters
114+
#[default]
115+
Query,
116+
117+
/// `form_post`: The provider will send the response as a POST request with
118+
/// the response parameters in the request body
119+
///
120+
/// <https://openid.net/specs/oauth-v2-form-post-response-mode-1_0.html>
121+
FormPost,
122+
}
123+
124+
impl ResponseMode {
125+
#[allow(clippy::trivially_copy_pass_by_ref)]
126+
const fn is_default(&self) -> bool {
127+
matches!(self, ResponseMode::Query)
128+
}
129+
}
130+
108131
/// Authentication methods used against the OAuth 2.0 provider
109132
#[derive(Debug, Clone, Copy, Serialize, Deserialize, JsonSchema)]
110133
#[serde(rename_all = "snake_case")]
@@ -460,6 +483,10 @@ pub struct Provider {
460483
#[serde(skip_serializing_if = "Option::is_none")]
461484
pub jwks_uri: Option<Url>,
462485

486+
/// The response mode we ask the provider to use for the callback
487+
#[serde(default, skip_serializing_if = "ResponseMode::is_default")]
488+
pub response_mode: ResponseMode,
489+
463490
/// How claims should be imported from the `id_token` provided by the
464491
/// provider
465492
#[serde(default, skip_serializing_if = "ClaimsImports::is_default")]

crates/data-model/src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,8 @@ pub use self::{
4141
UpstreamOAuthAuthorizationSessionState, UpstreamOAuthLink, UpstreamOAuthProvider,
4242
UpstreamOAuthProviderClaimsImports, UpstreamOAuthProviderDiscoveryMode,
4343
UpstreamOAuthProviderImportAction, UpstreamOAuthProviderImportPreference,
44-
UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderSubjectPreference,
45-
UpstreamOAuthProviderTokenAuthMethod,
44+
UpstreamOAuthProviderPkceMode, UpstreamOAuthProviderResponseMode,
45+
UpstreamOAuthProviderSubjectPreference, UpstreamOAuthProviderTokenAuthMethod,
4646
},
4747
user_agent::{DeviceType, UserAgent},
4848
users::{

crates/data-model/src/upstream_oauth2/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ pub use self::{
1616
ImportAction as UpstreamOAuthProviderImportAction,
1717
ImportPreference as UpstreamOAuthProviderImportPreference,
1818
PkceMode as UpstreamOAuthProviderPkceMode,
19+
ResponseMode as UpstreamOAuthProviderResponseMode,
1920
SetEmailVerification as UpsreamOAuthProviderSetEmailVerification,
2021
SubjectPreference as UpstreamOAuthProviderSubjectPreference,
2122
TokenAuthMethod as UpstreamOAuthProviderTokenAuthMethod, UpstreamOAuthProvider,

crates/data-model/src/upstream_oauth2/provider.rs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,55 @@ impl std::fmt::Display for PkceMode {
116116
}
117117
}
118118

119+
#[derive(Debug, Clone, Error)]
120+
#[error("Invalid response mode {0:?}")]
121+
pub struct InvalidResponseModeError(String);
122+
123+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
124+
#[serde(rename_all = "snake_case")]
125+
pub enum ResponseMode {
126+
#[default]
127+
Query,
128+
FormPost,
129+
}
130+
131+
impl From<ResponseMode> for oauth2_types::requests::ResponseMode {
132+
fn from(value: ResponseMode) -> Self {
133+
match value {
134+
ResponseMode::Query => oauth2_types::requests::ResponseMode::Query,
135+
ResponseMode::FormPost => oauth2_types::requests::ResponseMode::FormPost,
136+
}
137+
}
138+
}
139+
140+
impl ResponseMode {
141+
#[must_use]
142+
pub fn as_str(self) -> &'static str {
143+
match self {
144+
Self::Query => "query",
145+
Self::FormPost => "form_post",
146+
}
147+
}
148+
}
149+
150+
impl std::fmt::Display for ResponseMode {
151+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152+
f.write_str(self.as_str())
153+
}
154+
}
155+
156+
impl std::str::FromStr for ResponseMode {
157+
type Err = InvalidResponseModeError;
158+
159+
fn from_str(s: &str) -> Result<Self, Self::Err> {
160+
match s {
161+
"query" => Ok(ResponseMode::Query),
162+
"form_post" => Ok(ResponseMode::FormPost),
163+
s => Err(InvalidResponseModeError(s.to_owned())),
164+
}
165+
}
166+
}
167+
119168
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
120169
#[serde(rename_all = "snake_case")]
121170
pub enum TokenAuthMethod {
@@ -183,6 +232,7 @@ pub struct UpstreamOAuthProvider {
183232
pub encrypted_client_secret: Option<String>,
184233
pub token_endpoint_signing_alg: Option<JsonWebSignatureAlg>,
185234
pub token_endpoint_auth_method: TokenAuthMethod,
235+
pub response_mode: ResponseMode,
186236
pub created_at: DateTime<Utc>,
187237
pub disabled_at: Option<DateTime<Utc>>,
188238
pub claims_imports: ClaimsImports,

crates/handlers/src/upstream_oauth2/authorize.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ pub(crate) async fn get(
8787
provider.client_id.clone(),
8888
provider.scope.clone(),
8989
redirect_uri,
90-
);
90+
)
91+
.with_response_mode(provider.response_mode.into());
9192

9293
let data = if let Some(methods) = lazy_metadata.pkce_methods().await? {
9394
data.with_code_challenge_methods_supported(methods)

crates/handlers/src/upstream_oauth2/cache.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,12 +389,13 @@ mod tests {
389389
pkce_mode: UpstreamOAuthProviderPkceMode::Auto,
390390
jwks_uri_override: None,
391391
authorization_endpoint_override: None,
392-
token_endpoint_override: None,
393392
scope: Scope::from_iter([OPENID]),
393+
token_endpoint_override: None,
394394
client_id: "client_id".to_owned(),
395395
encrypted_client_secret: None,
396396
token_endpoint_signing_alg: None,
397397
token_endpoint_auth_method: UpstreamOAuthProviderTokenAuthMethod::None,
398+
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
398399
created_at: clock.now(),
399400
disabled_at: None,
400401
claims_imports: UpstreamOAuthProviderClaimsImports::default(),

crates/handlers/src/upstream_oauth2/link.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -917,6 +917,7 @@ mod tests {
917917
jwks_uri_override: None,
918918
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
919919
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
920+
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
920921
additional_authorization_parameters: Vec::new(),
921922
},
922923
)

crates/handlers/src/views/login.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -411,6 +411,7 @@ mod test {
411411
jwks_uri_override: None,
412412
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
413413
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
414+
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
414415
additional_authorization_parameters: Vec::new(),
415416
},
416417
)
@@ -446,6 +447,7 @@ mod test {
446447
jwks_uri_override: None,
447448
discovery_mode: mas_data_model::UpstreamOAuthProviderDiscoveryMode::Oidc,
448449
pkce_mode: mas_data_model::UpstreamOAuthProviderPkceMode::Auto,
450+
response_mode: mas_data_model::UpstreamOAuthProviderResponseMode::Query,
449451
additional_authorization_parameters: Vec::new(),
450452
},
451453
)

0 commit comments

Comments
 (0)