From 4e1733dae49a206bd4382239728f8b034f92ae45 Mon Sep 17 00:00:00 2001 From: Gerardo Navarro Date: Fri, 7 Nov 2025 14:16:37 +0100 Subject: [PATCH 1/2] Gate SLO endpoints behind slo_enabled option --- README.md | 8 ++++++-- lib/omniauth/strategies/saml.rb | 9 +++++++++ spec/omniauth/strategies/saml_spec.rb | 9 ++++++++- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 9cefeaf..7520049 100644 --- a/README.md +++ b/README.md @@ -103,6 +103,8 @@ Note that when [integrating with Devise](#devise-integration), the URL path will instance will be passed to this callable if it has an arity of 1. If the value is a string, the string will be returned, when the `RelayState` is called. Optional. +* `:slo_enabled` - Enables or disables Single Logout (SLO). Set to `false` to disable SLO. Defaults to `true`. Optional. + * `:idp_sso_service_url_runtime_params` - A dynamic mapping of request params that exist during the request phase of OmniAuth that should to be sent to the IdP after a specific mapping. So for example, a param `original_request_param` with value `original_param_value`, @@ -112,7 +114,7 @@ Note that when [integrating with Devise](#devise-integration), the URL path will * `:idp_cert` - The identity provider's certificate in PEM format. Takes precedence over the fingerprint option below. This option or `:idp_cert_multi` or `:idp_cert_fingerprint` must be present. - + * `:idp_cert_multi` - Multiple identity provider certificates in PEM format. Takes precedence over the fingerprint option below. This option `:idp_cert` or `:idp_cert_fingerprint` must be present. @@ -192,7 +194,9 @@ Single Logout can be Service Provider initiated or Identity Provider initiated. For SP initiated logout, the `idp_slo_service_url` option must be set to the logout url on the IdP, and users directed to `user_saml_omniauth_authorize_path + '/spslo'` after logging out locally. For IdP initiated logout, logout requests from the IdP should go to `/auth/saml/slo` (this can be -advertised in metadata by setting the `single_logout_service_url` config option). +advertised in metadata by setting the `single_logout_service_url` config option). If you wish to +disable Single Logout entirely (both SP and IdP initiated), set `:slo_enabled => false`; the `/auth/saml/slo` +and `/auth/saml/spslo` endpoints will then respond with HTTP 501 Not Implemented. When using Devise as an authentication solution, the SP initiated flow can be integrated in the `SessionsController#destroy` action. diff --git a/lib/omniauth/strategies/saml.rb b/lib/omniauth/strategies/saml.rb index 014a607..83bc966 100644 --- a/lib/omniauth/strategies/saml.rb +++ b/lib/omniauth/strategies/saml.rb @@ -28,6 +28,7 @@ def self.inherited(subclass) last_name: ["last_name", "lastname", "lastName"] } option :slo_default_relay_state + option :slo_enabled, true option :uid_attribute option :idp_slo_session_destroy, proc { |_env, session| session.clear } @@ -259,6 +260,14 @@ def other_phase_for_spslo end end + def slo_enabled? + !!options[:slo_enabled] + end + + def slo_disabled_response + Rack::Response.new("Not Implemented", 501, { "Content-Type" => "text/html" }).finish + end + def add_request_attributes_to(settings) settings.attribute_consuming_service.service_name options.attribute_service_name settings.sp_entity_id = options.sp_entity_id diff --git a/spec/omniauth/strategies/saml_spec.rb b/spec/omniauth/strategies/saml_spec.rb index c807da0..d63793b 100644 --- a/spec/omniauth/strategies/saml_spec.rb +++ b/spec/omniauth/strategies/saml_spec.rb @@ -268,7 +268,6 @@ def post_xml(xml = :example_response, opts = {}) expect(last_request.env['omniauth.error'].message).to eq("SAML response missing 'missing_attribute' attribute") end end - end describe 'POST /auth/saml/slo' do @@ -277,6 +276,10 @@ def post_xml(xml = :example_response, opts = {}) end context "when response is a logout response" do + before do + saml_options[:slo_enabled] = true + end + before :each do post "/auth/saml/slo", { SAMLResponse: load_xml(:example_logout_response), @@ -336,6 +339,10 @@ def post_xml(xml = :example_response, opts = {}) end describe 'POST /auth/saml/spslo' do + before do + saml_options[:slo_enabled] = true + end + def test_default_relay_state(static_default_relay_state = nil, &block_default_relay_state) saml_options["slo_default_relay_state"] = static_default_relay_state || block_default_relay_state post "/auth/saml/spslo" From d0f418d1227c5bf939ab35845bc9aff0ca8a937c Mon Sep 17 00:00:00 2001 From: Gerardo Navarro Date: Fri, 7 Nov 2025 15:12:24 +0100 Subject: [PATCH 2/2] Reorder SLO disabled specs --- lib/omniauth/strategies/saml.rb | 4 ++++ spec/omniauth/strategies/saml_spec.rb | 32 ++++++++++++++++++++------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/omniauth/strategies/saml.rb b/lib/omniauth/strategies/saml.rb index 83bc966..ceeb292 100644 --- a/lib/omniauth/strategies/saml.rb +++ b/lib/omniauth/strategies/saml.rb @@ -74,8 +74,12 @@ def other_phase if on_subpath?(:metadata) other_phase_for_metadata elsif on_subpath?(:slo) + return slo_disabled_response unless slo_enabled? + other_phase_for_slo elsif on_subpath?(:spslo) + return slo_disabled_response unless slo_enabled? + other_phase_for_spslo else call_app! diff --git a/spec/omniauth/strategies/saml_spec.rb b/spec/omniauth/strategies/saml_spec.rb index d63793b..a662a84 100644 --- a/spec/omniauth/strategies/saml_spec.rb +++ b/spec/omniauth/strategies/saml_spec.rb @@ -276,10 +276,6 @@ def post_xml(xml = :example_response, opts = {}) end context "when response is a logout response" do - before do - saml_options[:slo_enabled] = true - end - before :each do post "/auth/saml/slo", { SAMLResponse: load_xml(:example_logout_response), @@ -336,13 +332,21 @@ def post_xml(xml = :example_response, opts = {}) end end end - end - describe 'POST /auth/saml/spslo' do - before do - saml_options[:slo_enabled] = true + context "when SLO is disabled" do + before do + saml_options[:slo_enabled] = false + post "/auth/saml/slo" + end + + it "should return not implemented" do + expect(last_response.status).to eq 501 + expect(last_response.body).to eq "Not Implemented" + end end + end + describe 'POST /auth/saml/spslo' do def test_default_relay_state(static_default_relay_state = nil, &block_default_relay_state) saml_options["slo_default_relay_state"] = static_default_relay_state || block_default_relay_state post "/auth/saml/spslo" @@ -375,6 +379,18 @@ def test_default_relay_state(static_default_relay_state = nil, &block_default_re expect(last_response.status).to eq 501 expect(last_response.body).to match /Not Implemented/ end + + context "when SLO is disabled" do + before do + saml_options[:slo_enabled] = false + post "/auth/saml/spslo" + end + + it "should return not implemented" do + expect(last_response.status).to eq 501 + expect(last_response.body).to eq "Not Implemented" + end + end end describe 'POST /auth/saml/metadata' do