diff --git a/lib/omniauth/strategies/saml.rb b/lib/omniauth/strategies/saml.rb index 0819559..f1dd1c6 100644 --- a/lib/omniauth/strategies/saml.rb +++ b/lib/omniauth/strategies/saml.rb @@ -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' }, @@ -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 diff --git a/spec/omniauth/strategies/saml_spec.rb b/spec/omniauth/strategies/saml_spec.rb index 789b65e..68f2b5a 100644 --- a/spec/omniauth/strategies/saml_spec.rb +++ b/spec/omniauth/strategies/saml_spec.rb @@ -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