diff --git a/proto/src/oauth2.rs b/proto/src/oauth2.rs index 026a3c053..0e7229768 100644 --- a/proto/src/oauth2.rs +++ b/proto/src/oauth2.rs @@ -7,7 +7,8 @@ use serde::{Deserialize, Serialize}; use serde_with::base64::{Base64, UrlSafe}; use serde_with::formats::SpaceSeparator; use serde_with::{ - formats, serde_as, skip_serializing_none, NoneAsEmptyString, StringWithSeparator, + formats, rust::deserialize_ignore_any, serde_as, skip_serializing_none, NoneAsEmptyString, + StringWithSeparator, }; use url::Url; use uuid::Uuid; @@ -353,6 +354,9 @@ pub enum ResponseType { pub enum ResponseMode { Query, Fragment, + FormPost, + #[serde(other, deserialize_with = "deserialize_ignore_any")] + Invalid, } fn response_modes_supported_default() -> Vec<ResponseMode> { diff --git a/server/lib/src/idm/oauth2.rs b/server/lib/src/idm/oauth2.rs index a6cb8e618..9628e1ea4 100644 --- a/server/lib/src/idm/oauth2.rs +++ b/server/lib/src/idm/oauth2.rs @@ -122,6 +122,11 @@ impl std::fmt::Display for Oauth2Error { } // == internal state formats that we encrypt and send. +#[derive(Serialize, Deserialize, Debug, PartialEq)] +enum SupportedResponseMode { + Query, + Fragment, +} #[serde_as] #[derive(Serialize, Deserialize, Debug, PartialEq)] @@ -145,7 +150,7 @@ struct ConsentToken { // We stash some details here for oidc. pub nonce: Option<String>, /// The format the response should be returned to the application in. - pub response_mode: ResponseMode, + pub response_mode: SupportedResponseMode, } #[serde_as] @@ -239,7 +244,7 @@ pub struct AuthorisePermitSuccess { // The exchange code as a String pub code: String, /// The format the response should be returned to the application in. - pub response_mode: ResponseMode, + response_mode: SupportedResponseMode, } impl AuthorisePermitSuccess { @@ -252,7 +257,7 @@ impl AuthorisePermitSuccess { redirect_uri.set_fragment(None); match self.response_mode { - ResponseMode::Query => { + SupportedResponseMode::Query => { redirect_uri .query_pairs_mut() .append_pair("code", &self.code); @@ -261,7 +266,7 @@ impl AuthorisePermitSuccess { redirect_uri.query_pairs_mut().append_pair("state", state); }; } - ResponseMode::Fragment => { + SupportedResponseMode::Fragment => { redirect_uri.set_query(None); // Per [the RFC](https://www.rfc-editor.org/rfc/rfc6749#section-3.1.2), we can't set query pairs on fragment-containing redirects, only query ones. @@ -285,7 +290,7 @@ pub struct AuthoriseReject { // Where the client wants us to go back to. pub redirect_uri: Url, /// The format the response should be returned to the application in. - pub response_mode: ResponseMode, + response_mode: SupportedResponseMode, } impl AuthoriseReject { @@ -305,8 +310,8 @@ impl AuthoriseReject { .finish(); match self.response_mode { - ResponseMode::Query => redirect_uri.set_query(Some(&encoded)), - ResponseMode::Fragment => redirect_uri.set_fragment(Some(&encoded)), + SupportedResponseMode::Query => redirect_uri.set_query(Some(&encoded)), + SupportedResponseMode::Fragment => redirect_uri.set_fragment(Some(&encoded)), } redirect_uri @@ -1873,15 +1878,31 @@ impl IdmServerProxyReadTransaction<'_> { admin_warn!("Unsupported OAuth2 response_type (should be 'code')"); return Err(Oauth2Error::UnsupportedResponseType); } + let Some(response_mode) = auth_req.get_response_mode() else { - admin_warn!( + warn!( "Invalid response_mode {:?} for response_type {:?}", - auth_req.response_mode, - auth_req.response_type + auth_req.response_mode, auth_req.response_type ); return Err(Oauth2Error::InvalidRequest); }; + let response_mode = match response_mode { + ResponseMode::Query => SupportedResponseMode::Query, + ResponseMode::Fragment => SupportedResponseMode::Fragment, + ResponseMode::FormPost => { + warn!( + "Invalid response mode form_post requested - many clients request this incorrectly but proceed with response_mode=query. Remapping to query." + ); + warn!("This behaviour WILL BE REMOVED in a future release."); + SupportedResponseMode::Query + } + ResponseMode::Invalid => { + warn!("Invalid response mode requested, unable to proceed"); + return Err(Oauth2Error::InvalidRequest); + } + }; + /* * 4.1.2.1. Error Response *