Skip to content

Commit

Permalink
feat: Support RelayState binding by default during SSO
Browse files Browse the repository at this point in the history
Per [OASIS SAML 2.0 standard](https://docs.oasis-open.org/security/saml/v2.0/saml-bindings-2.0-os.pdf):

> Some bindings define a "RelayState" mechanism for preserving and conveying state information. When
> such a mechanism is used in conveying a request message as the initial step of a SAML protocol, it
> places requirements on the selection and use of the binding subsequently used to convey the response.
> Namely, if a SAML request message is accompanied by RelayState data, then the SAML responder
> MUST return its SAML protocol response using a binding that also supports a RelayState mechanism, and
> it MUST place the exact RelayState data it received with the request into the corresponding RelayState
> parameter in the response.

In order to make standards-compliant usage of `RelayState` easier for implementing developers, this PR makes two changes:

1. It adds a default `RelayState` param mapping to the gem's `:idp_sso_service_url_runtime_params` config.
2. It enables the use of `RelayState` when `OmniAuth.config.test_mode` is enabled. - It does this by extending `OmniAuth::Strategy#mock_request_call` to add any POST `RelayState` params to the query string that will be used in the callback URL.

Tests have been added for both of these new behaviors.
  • Loading branch information
smudge committed Jul 25, 2023
1 parent 3463fdd commit 6b3582a
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 1 deletion.
18 changes: 17 additions & 1 deletion lib/omniauth/strategies/saml.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def self.inherited(subclass)
RUBYSAML_RESPONSE_OPTIONS = OneLogin::RubySaml::Response::AVAILABLE_OPTIONS

option :name_identifier_format, nil
option :idp_sso_service_url_runtime_params, {}
option :idp_sso_service_url_runtime_params, { RelayState: 'RelayState' }
option :request_attributes, [
{ :name => 'email', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Email address' },
{ :name => 'name', :name_format => 'urn:oasis:names:tc:SAML:2.0:attrname-format:basic', :friendly_name => 'Full name' },
Expand Down Expand Up @@ -118,6 +118,22 @@ def find_attribute_by(keys)
nil
end

def mock_request_call
# Per SAML 2.0, if a RelayState param is passed, IDPs "MUST place the exact RelayState
# data it received with the request into the corresponding RelayState parameter in the response."
#
# By default, the "mock" `OmniAuth::Strategy` implementation will forward along any URL params,
# so we can in turn take any POSTed RelayState params and put them in the GET query string:
query_hash = request.GET.merge!(additional_params_for_authn_request.slice('RelayState'))
query_string = Rack::Utils.build_query(query_hash)

request.set_header(Rack::QUERY_STRING, query_string)
request.set_header(Rack::RACK_REQUEST_QUERY_STRING, query_string)
request.set_header(Rack::RACK_REQUEST_QUERY_HASH, query_hash)

super
end

private

def request_path_pattern
Expand Down
27 changes: 27 additions & 0 deletions spec/omniauth/strategies/saml_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,33 @@ def post_xml(xml = :example_response, opts = {})
end
end

context 'with RelayState param' do
before do
post '/auth/saml', 'RelayState' => 'RELAY_STATE_VALUE'
end

it 'should get authentication page' do
expect(last_response).to be_redirect
expect(last_response.location).to match(
/\Ahttps:\/\/idp.sso.example.com\/signon\/29490\?SAMLRequest=.*&RelayState=RELAY_STATE_VALUE\z/,
)
end

context 'when test_mode is enabled' do
around do |example|
OmniAuth.config.test_mode = true
example.run
ensure
OmniAuth.config.test_mode = false
end

it 'should redirect to local saml callback page' do
expect(last_response).to be_redirect
expect(last_response.location).to eq('http://example.org/auth/saml/callback?RelayState=RELAY_STATE_VALUE')
end
end
end

context "when the assertion_consumer_service_url is the default" do
before :each do
saml_options[:compress_request] = false
Expand Down

0 comments on commit 6b3582a

Please sign in to comment.