devise_saml_authenticatable 1.3.2 → 1.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/.gitignore +0 -2
- data/.travis.yml +29 -22
- data/Gemfile +2 -2
- data/README.md +105 -32
- data/app/controllers/devise/saml_sessions_controller.rb +35 -7
- data/devise_saml_authenticatable.gemspec +2 -1
- data/lib/devise_saml_authenticatable.rb +27 -2
- data/lib/devise_saml_authenticatable/default_attribute_map_resolver.rb +26 -0
- data/lib/devise_saml_authenticatable/default_idp_entity_id_reader.rb +2 -0
- data/lib/devise_saml_authenticatable/exception.rb +1 -1
- data/lib/devise_saml_authenticatable/model.rb +16 -18
- data/lib/devise_saml_authenticatable/routes.rb +17 -6
- data/lib/devise_saml_authenticatable/saml_mapped_attributes.rb +15 -2
- data/lib/devise_saml_authenticatable/strategy.rb +1 -0
- data/lib/devise_saml_authenticatable/version.rb +1 -1
- data/spec/controllers/devise/saml_sessions_controller_spec.rb +118 -11
- data/spec/devise_saml_authenticatable/default_attribute_map_resolver_spec.rb +58 -0
- data/spec/devise_saml_authenticatable/model_spec.rb +68 -7
- data/spec/devise_saml_authenticatable/saml_mapped_attributes_spec.rb +50 -0
- data/spec/features/saml_authentication_spec.rb +45 -21
- data/spec/rails_helper.rb +6 -2
- data/spec/routes/routes_spec.rb +102 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/support/Gemfile.rails4 +23 -6
- data/spec/support/Gemfile.rails5 +13 -2
- data/spec/support/Gemfile.rails5.1 +25 -0
- data/spec/support/Gemfile.rails5.2 +25 -0
- data/spec/support/attribute-map.yml +12 -0
- data/spec/support/attribute_map_resolver.rb.erb +14 -0
- data/spec/support/idp_settings_adapter.rb.erb +5 -5
- data/spec/support/idp_template.rb +6 -2
- data/spec/support/rails_app.rb +75 -17
- data/spec/support/saml_idp_controller.rb.erb +13 -6
- data/spec/support/sp_template.rb +45 -21
- metadata +25 -13
- data/spec/support/Gemfile.ruby-saml-1.3 +0 -24
| @@ -0,0 +1,26 @@ | |
| 1 | 
            +
            module DeviseSamlAuthenticatable
         | 
| 2 | 
            +
              class DefaultAttributeMapResolver
         | 
| 3 | 
            +
                def initialize(saml_response)
         | 
| 4 | 
            +
                  @saml_response = saml_response
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def attribute_map
         | 
| 8 | 
            +
                  return {} unless File.exist?(attribute_map_path)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  attribute_map = YAML.load(File.read(attribute_map_path))
         | 
| 11 | 
            +
                  if attribute_map.key?(Rails.env)
         | 
| 12 | 
            +
                    attribute_map[Rails.env]
         | 
| 13 | 
            +
                  else
         | 
| 14 | 
            +
                    attribute_map
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                private
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                attr_reader :saml_response
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def attribute_map_path
         | 
| 23 | 
            +
                  Rails.root.join("config", "attribute-map.yml")
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
            end
         | 
| @@ -4,11 +4,13 @@ module DeviseSamlAuthenticatable | |
| 4 4 | 
             
                  if params[:SAMLRequest]
         | 
| 5 5 | 
             
                    OneLogin::RubySaml::SloLogoutrequest.new(
         | 
| 6 6 | 
             
                      params[:SAMLRequest],
         | 
| 7 | 
            +
                      settings: Devise.saml_config,
         | 
| 7 8 | 
             
                      allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
         | 
| 8 9 | 
             
                    ).issuer
         | 
| 9 10 | 
             
                  elsif params[:SAMLResponse]
         | 
| 10 11 | 
             
                    OneLogin::RubySaml::Response.new(
         | 
| 11 12 | 
             
                      params[:SAMLResponse],
         | 
| 13 | 
            +
                      settings: Devise.saml_config,
         | 
| 12 14 | 
             
                      allowed_clock_drift: Devise.allowed_clock_drift_in_seconds,
         | 
| 13 15 | 
             
                    ).issuers.first
         | 
| 14 16 | 
             
                  end
         | 
| @@ -33,9 +33,9 @@ module Devise | |
| 33 33 | 
             
                      key = Devise.saml_default_user_key
         | 
| 34 34 | 
             
                      decorated_response = ::SamlAuthenticatable::SamlResponse.new(
         | 
| 35 35 | 
             
                        saml_response,
         | 
| 36 | 
            -
                        attribute_map
         | 
| 36 | 
            +
                        attribute_map(saml_response),
         | 
| 37 37 | 
             
                      )
         | 
| 38 | 
            -
                      if  | 
| 38 | 
            +
                      if Devise.saml_use_subject
         | 
| 39 39 | 
             
                        auth_value = saml_response.name_id
         | 
| 40 40 | 
             
                      else
         | 
| 41 41 | 
             
                        auth_value = decorated_response.attribute_value_by_resource_key(key)
         | 
| @@ -44,8 +44,12 @@ module Devise | |
| 44 44 |  | 
| 45 45 | 
             
                      resource = Devise.saml_resource_locator.call(self, decorated_response, auth_value)
         | 
| 46 46 |  | 
| 47 | 
            -
                      if Devise.saml_resource_validator
         | 
| 48 | 
            -
             | 
| 47 | 
            +
                      raise "Only one validator configuration can be used at a time" if Devise.saml_resource_validator && Devise.saml_resource_validator_hook
         | 
| 48 | 
            +
                      if Devise.saml_resource_validator || Devise.saml_resource_validator_hook
         | 
| 49 | 
            +
                        valid = if Devise.saml_resource_validator then Devise.saml_resource_validator.new.validate(resource, saml_response)
         | 
| 50 | 
            +
                                else Devise.saml_resource_validator_hook.call(resource, decorated_response, auth_value)
         | 
| 51 | 
            +
                                end
         | 
| 52 | 
            +
                        if !valid
         | 
| 49 53 | 
             
                          logger.info("User(#{auth_value}) did not pass custom validation.")
         | 
| 50 54 | 
             
                          return nil
         | 
| 51 55 | 
             
                        end
         | 
| @@ -62,7 +66,12 @@ module Devise | |
| 62 66 | 
             
                      end
         | 
| 63 67 |  | 
| 64 68 | 
             
                      if Devise.saml_update_user || (resource.new_record? && Devise.saml_create_user)
         | 
| 65 | 
            -
                         | 
| 69 | 
            +
                        begin
         | 
| 70 | 
            +
                          Devise.saml_update_resource_hook.call(resource, decorated_response, auth_value)
         | 
| 71 | 
            +
                        rescue
         | 
| 72 | 
            +
                          logger.info("User(#{auth_value}) failed to create or update.")
         | 
| 73 | 
            +
                          return nil
         | 
| 74 | 
            +
                        end
         | 
| 66 75 | 
             
                      end
         | 
| 67 76 |  | 
| 68 77 | 
             
                      resource
         | 
| @@ -77,19 +86,8 @@ module Devise | |
| 77 86 | 
             
                      find_for_authentication(conditions)
         | 
| 78 87 | 
             
                    end
         | 
| 79 88 |  | 
| 80 | 
            -
                    def attribute_map
         | 
| 81 | 
            -
                       | 
| 82 | 
            -
                    end
         | 
| 83 | 
            -
             | 
| 84 | 
            -
                    private
         | 
| 85 | 
            -
             | 
| 86 | 
            -
                    def attribute_map_for_environment
         | 
| 87 | 
            -
                      attribute_map = YAML.load(File.read("#{Rails.root}/config/attribute-map.yml"))
         | 
| 88 | 
            -
                      if attribute_map.has_key?(Rails.env)
         | 
| 89 | 
            -
                        attribute_map[Rails.env]
         | 
| 90 | 
            -
                      else
         | 
| 91 | 
            -
                        attribute_map
         | 
| 92 | 
            -
                      end
         | 
| 89 | 
            +
                    def attribute_map(saml_response = nil)
         | 
| 90 | 
            +
                      Devise.saml_attribute_map_resolver.new(saml_response).attribute_map
         | 
| 93 91 | 
             
                    end
         | 
| 94 92 | 
             
                  end
         | 
| 95 93 | 
             
                end
         | 
| @@ -1,12 +1,23 @@ | |
| 1 1 | 
             
            ActionDispatch::Routing::Mapper.class_eval do
         | 
| 2 2 | 
             
              protected
         | 
| 3 3 | 
             
              def devise_saml_authenticatable(mapping, controllers)
         | 
| 4 | 
            -
                 | 
| 5 | 
            -
                   | 
| 6 | 
            -
                   | 
| 7 | 
            -
             | 
| 8 | 
            -
             | 
| 9 | 
            -
             | 
| 4 | 
            +
                if ::Devise.saml_route_helper_prefix
         | 
| 5 | 
            +
                  prefix = ::Devise.saml_route_helper_prefix
         | 
| 6 | 
            +
                  resource :session, only: [], controller: controllers[:saml_sessions], path: '' do
         | 
| 7 | 
            +
                    get :new, path: 'saml/sign_in', as: "new_#{prefix}"
         | 
| 8 | 
            +
                    post :create, path: 'saml/auth', as: prefix
         | 
| 9 | 
            +
                    match :destroy, path: mapping.path_names[:sign_out], as: "destroy_#{prefix}", via: mapping.sign_out_via
         | 
| 10 | 
            +
                    get :metadata, path: 'saml/metadata'
         | 
| 11 | 
            +
                    match :idp_sign_out, path: 'saml/idp_sign_out', as: "idp_destroy_#{prefix}", via: [:get, :post]
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                else
         | 
| 14 | 
            +
                  resource :session, only: [], controller: controllers[:saml_sessions], path: '' do
         | 
| 15 | 
            +
                    get :new, path: 'saml/sign_in', as: 'new'
         | 
| 16 | 
            +
                    post :create, path: 'saml/auth'
         | 
| 17 | 
            +
                    match :destroy, path: mapping.path_names[:sign_out], as: 'destroy', via: mapping.sign_out_via
         | 
| 18 | 
            +
                    get :metadata, path: 'saml/metadata'
         | 
| 19 | 
            +
                    match :idp_sign_out, path: 'saml/idp_sign_out', via: [:get, :post]
         | 
| 20 | 
            +
                  end
         | 
| 10 21 | 
             
                end
         | 
| 11 22 | 
             
              end
         | 
| 12 23 | 
             
            end
         | 
| @@ -3,7 +3,6 @@ module SamlAuthenticatable | |
| 3 3 | 
             
                def initialize(attributes, attribute_map)
         | 
| 4 4 | 
             
                  @attributes = attributes
         | 
| 5 5 | 
             
                  @attribute_map = attribute_map
         | 
| 6 | 
            -
                  @inverted_attribute_map = @attribute_map.invert
         | 
| 7 6 | 
             
                end
         | 
| 8 7 |  | 
| 9 8 | 
             
                def saml_attribute_keys
         | 
| @@ -15,7 +14,21 @@ module SamlAuthenticatable | |
| 15 14 | 
             
                end
         | 
| 16 15 |  | 
| 17 16 | 
             
                def value_by_resource_key(key)
         | 
| 18 | 
            -
                   | 
| 17 | 
            +
                  str_key = String(key)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                  # Find all of the SAML attributes that map to the resource key
         | 
| 20 | 
            +
                  attribute_map_for_key = @attribute_map.select { |_, resource_key| String(resource_key) == str_key }
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  saml_value = nil
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  # Find the first non-nil value
         | 
| 25 | 
            +
                  attribute_map_for_key.each_key do |saml_key|
         | 
| 26 | 
            +
                    saml_value = value_by_saml_attribute_key(saml_key)
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    break unless saml_value.nil?
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  saml_value
         | 
| 19 32 | 
             
                end
         | 
| 20 33 |  | 
| 21 34 | 
             
                def value_by_saml_attribute_key(key)
         | 
| @@ -1,31 +1,38 @@ | |
| 1 1 | 
             
            require 'rails_helper'
         | 
| 2 2 |  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 3 | 
            +
            # The important parts from devise
         | 
| 4 | 
            +
            class DeviseController < ApplicationController
         | 
| 5 | 
            +
              attr_accessor :current_user
         | 
| 6 | 
            +
             | 
| 5 7 | 
             
              def resource_class
         | 
| 6 8 | 
             
                User
         | 
| 7 9 | 
             
              end
         | 
| 8 10 |  | 
| 11 | 
            +
              def require_no_authentication
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
            class Devise::SessionsController < DeviseController
         | 
| 9 15 | 
             
              def destroy
         | 
| 10 16 | 
             
                sign_out
         | 
| 11 17 | 
             
                redirect_to after_sign_out_path_for(:user)
         | 
| 12 18 | 
             
              end
         | 
| 13 19 |  | 
| 14 | 
            -
              def  | 
| 20 | 
            +
              def verify_signed_out_user
         | 
| 21 | 
            +
                # no-op for these tests
         | 
| 15 22 | 
             
              end
         | 
| 16 23 | 
             
            end
         | 
| 17 24 |  | 
| 18 25 | 
             
            require_relative '../../../app/controllers/devise/saml_sessions_controller'
         | 
| 19 26 |  | 
| 20 27 | 
             
            describe Devise::SamlSessionsController, type: :controller do
         | 
| 21 | 
            -
              let(:saml_config) { Devise.saml_config }
         | 
| 22 28 | 
             
              let(:idp_providers_adapter) { spy("Stub IDPSettings Adaptor") }
         | 
| 23 29 |  | 
| 24 30 | 
             
              before do
         | 
| 31 | 
            +
                @request.env["devise.mapping"] = Devise.mappings[:user]
         | 
| 25 32 | 
             
                allow(idp_providers_adapter).to receive(:settings).and_return({
         | 
| 26 33 | 
             
                  assertion_consumer_service_url: "acs_url",
         | 
| 27 34 | 
             
                  assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
         | 
| 28 | 
            -
                  name_identifier_format: "urn:oasis:names:tc:SAML: | 
| 35 | 
            +
                  name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
         | 
| 29 36 | 
             
                  issuer: "sp_issuer",
         | 
| 30 37 | 
             
                  idp_entity_id: "http://www.example.com",
         | 
| 31 38 | 
             
                  authn_context: "",
         | 
| @@ -35,6 +42,20 @@ describe Devise::SamlSessionsController, type: :controller do | |
| 35 42 | 
             
                })
         | 
| 36 43 | 
             
              end
         | 
| 37 44 |  | 
| 45 | 
            +
              before do
         | 
| 46 | 
            +
                if Rails::VERSION::MAJOR < 5 && Gem::Version.new(RUBY_VERSION) > Gem::Version.new("2.6")
         | 
| 47 | 
            +
                  # we still want to support Rails 4
         | 
| 48 | 
            +
                  # patch tests using snippet from https://github.com/rails/rails/issues/34790#issuecomment-483607370
         | 
| 49 | 
            +
                  class ActionController::TestResponse < ActionDispatch::TestResponse
         | 
| 50 | 
            +
                    def recycle!
         | 
| 51 | 
            +
                      @mon_mutex_owner_object_id = nil
         | 
| 52 | 
            +
                      @mon_mutex = nil
         | 
| 53 | 
            +
                      initialize
         | 
| 54 | 
            +
                    end
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
             | 
| 38 59 | 
             
              describe '#new' do
         | 
| 39 60 | 
             
                let(:saml_response) { File.read(File.join(File.dirname(__FILE__), '../../support', 'response_encrypted_nameid.xml.base64')) }
         | 
| 40 61 |  | 
| @@ -66,6 +87,7 @@ describe Devise::SamlSessionsController, type: :controller do | |
| 66 87 | 
             
                  it "uses the DefaultIdpEntityIdReader" do
         | 
| 67 88 | 
             
                    expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
         | 
| 68 89 | 
             
                    do_get
         | 
| 90 | 
            +
                    expect(idp_providers_adapter).to have_received(:settings).with(nil)
         | 
| 69 91 | 
             
                  end
         | 
| 70 92 |  | 
| 71 93 | 
             
                  context "with a relay_state lambda defined" do
         | 
| @@ -104,6 +126,7 @@ describe Devise::SamlSessionsController, type: :controller do | |
| 104 126 |  | 
| 105 127 | 
             
                    it "redirects to the associated IdP SSO target url" do
         | 
| 106 128 | 
             
                      do_get
         | 
| 129 | 
            +
                      expect(idp_providers_adapter).to have_received(:settings).with("http://www.example.com")
         | 
| 107 130 | 
             
                      expect(response).to redirect_to(%r(\Ahttp://idp_sso_url\?SAMLRequest=))
         | 
| 108 131 | 
             
                    end
         | 
| 109 132 | 
             
                  end
         | 
| @@ -111,6 +134,8 @@ describe Devise::SamlSessionsController, type: :controller do | |
| 111 134 | 
             
              end
         | 
| 112 135 |  | 
| 113 136 | 
             
              describe '#metadata' do
         | 
| 137 | 
            +
                let(:saml_config) { Devise.saml_config.dup }
         | 
| 138 | 
            +
             | 
| 114 139 | 
             
                context "with the default configuration" do
         | 
| 115 140 | 
             
                  it 'generates metadata' do
         | 
| 116 141 | 
             
                    get :metadata
         | 
| @@ -130,7 +155,7 @@ describe Devise::SamlSessionsController, type: :controller do | |
| 130 155 | 
             
                    Devise.saml_configure do |settings|
         | 
| 131 156 | 
             
                      settings.assertion_consumer_service_url = "http://localhost:3000/users/saml/auth"
         | 
| 132 157 | 
             
                      settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
         | 
| 133 | 
            -
                      settings.name_identifier_format = "urn:oasis:names:tc:SAML: | 
| 158 | 
            +
                      settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
         | 
| 134 159 | 
             
                      settings.issuer = "http://localhost:3000"
         | 
| 135 160 | 
             
                    end
         | 
| 136 161 | 
             
                  end
         | 
| @@ -147,10 +172,81 @@ describe Devise::SamlSessionsController, type: :controller do | |
| 147 172 | 
             
              end
         | 
| 148 173 |  | 
| 149 174 | 
             
              describe '#destroy' do
         | 
| 150 | 
            -
                 | 
| 151 | 
            -
                   | 
| 152 | 
            -
             | 
| 153 | 
            -
             | 
| 175 | 
            +
                before do
         | 
| 176 | 
            +
                  allow(controller).to receive(:sign_out)
         | 
| 177 | 
            +
                end
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                context "when using the default saml config" do
         | 
| 180 | 
            +
                  it "signs out and redirects to the IdP" do
         | 
| 181 | 
            +
                    delete :destroy
         | 
| 182 | 
            +
                    expect(controller).to have_received(:sign_out)
         | 
| 183 | 
            +
                    expect(response).to redirect_to(%r(\Ahttp://localhost:8009/saml/logout\?SAMLRequest=))
         | 
| 184 | 
            +
                  end
         | 
| 185 | 
            +
                end
         | 
| 186 | 
            +
             | 
| 187 | 
            +
                context "when configured to use a non-transient name identifier" do
         | 
| 188 | 
            +
                  before do
         | 
| 189 | 
            +
                    allow(Devise.saml_config).to receive(:name_identifier_format).and_return("urn:oasis:names:tc:SAML:2.0:nameid-format:persistent")
         | 
| 190 | 
            +
                  end
         | 
| 191 | 
            +
             | 
| 192 | 
            +
                  it "includes a LogoutRequest with the name identifier and session index", :aggregate_failures do
         | 
| 193 | 
            +
                    controller.current_user = Struct.new(:email, :session_index).new("user@example.com", "sessionindex")
         | 
| 194 | 
            +
             | 
| 195 | 
            +
                    actual_settings = nil
         | 
| 196 | 
            +
                    expect_any_instance_of(OneLogin::RubySaml::Logoutrequest).to receive(:create) do |_, settings|
         | 
| 197 | 
            +
                      actual_settings = settings
         | 
| 198 | 
            +
                      "http://localhost:8009/saml/logout"
         | 
| 199 | 
            +
                    end
         | 
| 200 | 
            +
             | 
| 201 | 
            +
                    delete :destroy
         | 
| 202 | 
            +
                    expect(actual_settings.name_identifier_value).to eq("user@example.com")
         | 
| 203 | 
            +
                    expect(actual_settings.sessionindex).to eq("sessionindex")
         | 
| 204 | 
            +
                  end
         | 
| 205 | 
            +
                end
         | 
| 206 | 
            +
             | 
| 207 | 
            +
                context "with a specified idp" do
         | 
| 208 | 
            +
                  before do
         | 
| 209 | 
            +
                    Devise.idp_settings_adapter = idp_providers_adapter
         | 
| 210 | 
            +
                  end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  it "redirects to the associated IdP SSO target url" do
         | 
| 213 | 
            +
                    expect(DeviseSamlAuthenticatable::DefaultIdpEntityIdReader).to receive(:entity_id)
         | 
| 214 | 
            +
                    delete :destroy
         | 
| 215 | 
            +
                    expect(controller).to have_received(:sign_out)
         | 
| 216 | 
            +
                    expect(response).to redirect_to(%r(\Ahttp://idp_slo_url\?SAMLRequest=))
         | 
| 217 | 
            +
                  end
         | 
| 218 | 
            +
             | 
| 219 | 
            +
                  context "with a specified idp entity id reader" do
         | 
| 220 | 
            +
                    class OurIdpEntityIdReader
         | 
| 221 | 
            +
                      def self.entity_id(params)
         | 
| 222 | 
            +
                        params[:entity_id]
         | 
| 223 | 
            +
                      end
         | 
| 224 | 
            +
                    end
         | 
| 225 | 
            +
             | 
| 226 | 
            +
                    subject(:do_delete) {
         | 
| 227 | 
            +
                      if Rails::VERSION::MAJOR > 4
         | 
| 228 | 
            +
                        delete :destroy, params: {entity_id: "http://www.example.com"}
         | 
| 229 | 
            +
                      else
         | 
| 230 | 
            +
                        delete :destroy, entity_id: "http://www.example.com"
         | 
| 231 | 
            +
                      end
         | 
| 232 | 
            +
                    }
         | 
| 233 | 
            +
             | 
| 234 | 
            +
                    before do
         | 
| 235 | 
            +
                      @default_reader = Devise.idp_entity_id_reader
         | 
| 236 | 
            +
                      Devise.idp_entity_id_reader = OurIdpEntityIdReader # which will have some different behavior
         | 
| 237 | 
            +
                    end
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                    after do
         | 
| 240 | 
            +
                      Devise.idp_entity_id_reader = @default_reader
         | 
| 241 | 
            +
                    end
         | 
| 242 | 
            +
             | 
| 243 | 
            +
                    it "redirects to the associated IdP SLO target url" do
         | 
| 244 | 
            +
                      do_delete
         | 
| 245 | 
            +
                      expect(controller).to have_received(:sign_out)
         | 
| 246 | 
            +
                      expect(idp_providers_adapter).to have_received(:settings).with("http://www.example.com")
         | 
| 247 | 
            +
                      expect(response).to redirect_to(%r(\Ahttp://idp_slo_url\?SAMLRequest=))
         | 
| 248 | 
            +
                    end
         | 
| 249 | 
            +
                  end
         | 
| 154 250 | 
             
                end
         | 
| 155 251 | 
             
              end
         | 
| 156 252 |  | 
| @@ -214,12 +310,13 @@ describe Devise::SamlSessionsController, type: :controller do | |
| 214 310 | 
             
                  let(:name_id) { '12312312' }
         | 
| 215 311 | 
             
                  before do
         | 
| 216 312 | 
             
                    allow(OneLogin::RubySaml::SloLogoutrequest).to receive(:new).and_return(saml_request)
         | 
| 313 | 
            +
                    allow(User).to receive(:reset_session_key_for)
         | 
| 217 314 | 
             
                  end
         | 
| 218 315 |  | 
| 219 316 | 
             
                  it 'direct the resource to reset the session key' do
         | 
| 220 | 
            -
                    expect(User).to receive(:reset_session_key_for).with(name_id)
         | 
| 221 317 | 
             
                    do_post
         | 
| 222 318 | 
             
                    expect(response).to redirect_to response_url
         | 
| 319 | 
            +
                    expect(User).to have_received(:reset_session_key_for).with(name_id)
         | 
| 223 320 | 
             
                  end
         | 
| 224 321 |  | 
| 225 322 | 
             
                  context "with a specified idp" do
         | 
| @@ -236,6 +333,16 @@ describe Devise::SamlSessionsController, type: :controller do | |
| 236 333 | 
             
                    end
         | 
| 237 334 | 
             
                  end
         | 
| 238 335 |  | 
| 336 | 
            +
                  context "with a relay_state lambda defined" do
         | 
| 337 | 
            +
                    let(:relay_state) { ->(request) { "123" } }
         | 
| 338 | 
            +
             | 
| 339 | 
            +
                    it "includes the RelayState param in the request to the IdP" do
         | 
| 340 | 
            +
                      expect(Devise).to receive(:saml_relay_state).at_least(:once).and_return(relay_state)
         | 
| 341 | 
            +
                      do_post
         | 
| 342 | 
            +
                      expect(saml_response).to have_received(:create).with(Devise.saml_config, saml_request.id, nil, {RelayState: "123"})
         | 
| 343 | 
            +
                    end
         | 
| 344 | 
            +
                  end
         | 
| 345 | 
            +
             | 
| 239 346 | 
             
                  context 'when saml_session_index_key is not configured' do
         | 
| 240 347 | 
             
                    before do
         | 
| 241 348 | 
             
                      Devise.saml_session_index_key = nil
         | 
| @@ -0,0 +1,58 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
            require "devise_saml_authenticatable/default_attribute_map_resolver"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe DeviseSamlAuthenticatable::DefaultAttributeMapResolver do
         | 
| 5 | 
            +
              let!(:rails) { class_double("Rails", env: "test", logger: logger, root: rails_root).as_stubbed_const }
         | 
| 6 | 
            +
              let(:logger) { instance_double("Logger", info: nil) }
         | 
| 7 | 
            +
              let(:rails_root) { Pathname.new("tmp") }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
              let(:saml_response) { instance_double("OneLogin::RubySaml::Response") }
         | 
| 10 | 
            +
              let(:file_contents) {
         | 
| 11 | 
            +
                <<YAML
         | 
| 12 | 
            +
            ---
         | 
| 13 | 
            +
            firstname: first_name
         | 
| 14 | 
            +
            lastname: last_name
         | 
| 15 | 
            +
            YAML
         | 
| 16 | 
            +
              }
         | 
| 17 | 
            +
              before do
         | 
| 18 | 
            +
                allow(File).to receive(:exist?).and_return(true)
         | 
| 19 | 
            +
                allow(File).to receive(:read).and_return(file_contents)
         | 
| 20 | 
            +
              end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
              describe "#attribute_map" do
         | 
| 23 | 
            +
                it "reads the attribute map from the config file" do
         | 
| 24 | 
            +
                  expect(described_class.new(saml_response).attribute_map).to eq(
         | 
| 25 | 
            +
                    "firstname" => "first_name",
         | 
| 26 | 
            +
                    "lastname" => "last_name",
         | 
| 27 | 
            +
                  )
         | 
| 28 | 
            +
                  expect(File).to have_received(:read).with(Pathname.new("tmp").join("config", "attribute-map.yml"))
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                context "when the attribute map is broken down by environment" do
         | 
| 32 | 
            +
                  let(:file_contents) {
         | 
| 33 | 
            +
                    <<YAML
         | 
| 34 | 
            +
            ---
         | 
| 35 | 
            +
            test:
         | 
| 36 | 
            +
              first: first_name
         | 
| 37 | 
            +
              last: last_name
         | 
| 38 | 
            +
            YAML
         | 
| 39 | 
            +
                  }
         | 
| 40 | 
            +
                  it "reads the attribute map from the environment key" do
         | 
| 41 | 
            +
                    expect(described_class.new(saml_response).attribute_map).to eq(
         | 
| 42 | 
            +
                      "first" => "first_name",
         | 
| 43 | 
            +
                      "last" => "last_name",
         | 
| 44 | 
            +
                    )
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                context "when the config file does not exist" do
         | 
| 49 | 
            +
                  before do
         | 
| 50 | 
            +
                    allow(File).to receive(:exist?).and_return(false)
         | 
| 51 | 
            +
                  end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                  it "is an empty hash" do
         | 
| 54 | 
            +
                    expect(described_class.new(saml_response).attribute_map).to eq({})
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
              end
         | 
| 58 | 
            +
            end
         |