├── .rspec ├── .gitignore ├── Gemfile ├── lib ├── warden │ ├── version.rb │ ├── test │ │ ├── warden_helpers.rb │ │ ├── helpers.rb │ │ └── mock.rb │ ├── session_serializer.rb │ ├── strategies.rb │ ├── mixins │ │ └── common.rb │ ├── errors.rb │ ├── config.rb │ ├── manager.rb │ ├── strategies │ │ └── base.rb │ ├── hooks.rb │ └── proxy.rb └── warden.rb ├── gemfiles ├── rack_2.gemfile └── rack_3.gemfile ├── Rakefile ├── spec ├── helpers │ ├── strategies │ │ ├── invalid.rb │ │ ├── pass.rb │ │ ├── failz.rb │ │ ├── fail_with_user.rb │ │ ├── single.rb │ │ ├── pass_with_message.rb │ │ └── password.rb │ └── request_helper.rb ├── warden │ ├── test │ │ ├── mock_spec.rb │ │ ├── test_mode_spec.rb │ │ └── helpers_spec.rb │ ├── errors_spec.rb │ ├── config_spec.rb │ ├── session_serializer_spec.rb │ ├── strategies_spec.rb │ ├── scoped_session_serializer.rb │ ├── authenticated_data_store_spec.rb │ ├── strategies │ │ └── base_spec.rb │ ├── manager_spec.rb │ ├── hooks_spec.rb │ └── proxy_spec.rb └── spec_helper.rb ├── README.md ├── .github └── workflows │ └── ruby.yml ├── warden.gemspec ├── LICENSE └── CHANGELOG.md /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation 3 | --color -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | pkg 3 | .*~ 4 | 5 | *.gem 6 | Gemfile.lock 7 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | 5 | gemspec 6 | 7 | gem 'pry' 8 | 9 | -------------------------------------------------------------------------------- /lib/warden/version.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | module Warden 4 | VERSION = "1.2.9" 5 | end 6 | -------------------------------------------------------------------------------- /gemfiles/rack_2.gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rack", "~> 2.2" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /gemfiles/rack_3.gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "rack", "~> 3.0" 6 | 7 | gemspec path: "../" 8 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # frozen_string_literal: true 3 | require "bundler/gem_tasks" 4 | require "rspec/core/rake_task" 5 | 6 | RSpec::Core::RakeTask.new(:spec) 7 | 8 | task :default => :spec 9 | -------------------------------------------------------------------------------- /spec/helpers/strategies/invalid.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | Warden::Strategies.add(:invalid) do 4 | def valid? 5 | false 6 | end 7 | 8 | def authenticate!; end 9 | end 10 | -------------------------------------------------------------------------------- /spec/helpers/strategies/pass.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | Warden::Strategies.add(:pass) do 4 | def authenticate! 5 | request.env['warden.spec.strategies'] ||= [] 6 | request.env['warden.spec.strategies'] << :pass 7 | success!("Valid User") unless scope == :failz 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/helpers/strategies/failz.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | Warden::Strategies.add(:failz) do 4 | def authenticate! 5 | request.env['warden.spec.strategies'] ||= [] 6 | request.env['warden.spec.strategies'] << :failz 7 | fail!("The Fails Strategy Has Failed You") 8 | throw :warden 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /spec/helpers/strategies/fail_with_user.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | Warden::Strategies.add(:fail_with_user) do 4 | def authenticate! 5 | request.env['warden.spec.strategies'] ||= [] 6 | request.env['warden.spec.strategies'] << :fail_with_user 7 | self.user = 'Valid User' 8 | fail! 9 | end 10 | end 11 | 12 | -------------------------------------------------------------------------------- /spec/helpers/strategies/single.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | Warden::Strategies.add(:single) do 4 | def authenticate! 5 | request.env['warden.spec.strategies'] ||= [] 6 | request.env['warden.spec.strategies'] << :single 7 | success!("Valid User") 8 | end 9 | 10 | def store? 11 | false 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /spec/helpers/strategies/pass_with_message.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | Warden::Strategies.add(:pass_with_message) do 4 | def authenticate! 5 | request.env['warden.spec.strategies'] ||= [] 6 | request.env['warden.spec.strategies'] << :pass_with_message 7 | success!("Valid User", "The Success Strategy Has Accepted You") unless scope == :failz 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /spec/warden/test/mock_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::Test::Mock do 4 | before{ $captures = [] } 5 | after{ Warden.test_reset! } 6 | 7 | it "should return a valid mocked warden" do 8 | user = "A User" 9 | login_as user 10 | 11 | expect(warden.class).to eq(Warden::Proxy) 12 | expect(warden.user).to eq(user) 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /spec/helpers/strategies/password.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | Warden::Strategies.add(:password) do 4 | def authenticate! 5 | request.env['warden.spec.strategies'] ||= [] 6 | request.env['warden.spec.strategies'] << :password 7 | if params["password"] || params["username"] 8 | params["password"] == "sekrit" && params["username"] == "fred" ? 9 | success!("Authenticated User") : fail!("Username or password is incorrect") 10 | else 11 | pass 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Warden 2 | 3 | [![Build Status](https://github.com/wardencommunity/warden/workflows/Tests/badge.svg?branch=master)](https://github.com/wardencommunity/warden/actions) 4 | 5 | ## Getting Started 6 | 7 | Please see the [Warden Wiki](https://github.com/wardencommunity/warden/wiki) for overview documentation. 8 | 9 | ## Maintainers 10 | 11 | * Daniel Neighman (hassox) 12 | * José Valim (josevalim) 13 | * Justin Smestad (jsmestad) 14 | * Whitney Smestad (whithub) 15 | 16 | [A list of all contributors is available on Github.](https://github.com/hassox/warden/contributors) 17 | 18 | ## LICENSE 19 | 20 | See `LICENSE` file. 21 | -------------------------------------------------------------------------------- /.github/workflows/ruby.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | runs-on: ubuntu-latest 7 | strategy: 8 | matrix: 9 | ruby: [ '2.5', '2.6', '2.7', '3.0', '3.1', '3.2', '3.3', '3.4' ] 10 | gemfile: 11 | - "rack_2" 12 | - "rack_3" 13 | env: 14 | BUNDLE_GEMFILE: ${{ github.workspace }}/gemfiles/${{ matrix.gemfile }}.gemfile 15 | name: Ruby ${{ matrix.ruby }} 16 | steps: 17 | - uses: actions/checkout@v5 18 | - name: Set up Ruby 19 | uses: ruby/setup-ruby@v1 20 | with: 21 | ruby-version: ${{ matrix.ruby }} 22 | bundler-cache: true 23 | - name: Run tests 24 | run: bundle exec rake 25 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | $TESTING=true 4 | 5 | $:.unshift File.join(File.dirname(__FILE__), '..', 'lib') 6 | $:.unshift File.expand_path(File.join(File.dirname(__FILE__))) 7 | require 'warden' 8 | 9 | require 'rubygems' 10 | require 'rack' 11 | 12 | Dir[File.join(File.dirname(__FILE__), "helpers", "**/*.rb")].each do |f| 13 | require f 14 | end 15 | 16 | RSpec.configure do |config| 17 | config.include(Warden::Spec::Helpers) 18 | config.include(Warden::Test::Helpers) 19 | config.include(Warden::Test::Mock) 20 | 21 | def load_strategies 22 | Dir[File.join(File.dirname(__FILE__), "helpers", "strategies", "**/*.rb")].each do |f| 23 | load f 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /warden.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # frozen_string_literal: true 3 | 4 | lib = File.expand_path("../lib", __FILE__) 5 | $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) 6 | require 'warden/version' 7 | 8 | Gem::Specification.new do |spec| 9 | spec.name = "warden" 10 | spec.version = Warden::VERSION 11 | spec.authors = ["Daniel Neighman", "Justin Smestad", "Whitney Smestad", "José Valim"] 12 | spec.email = %q{hasox.sox@gmail.com justin.smestad@gmail.com whitcolorado@gmail.com} 13 | spec.homepage = "https://github.com/hassox/warden" 14 | spec.summary = "An authentication library compatible with all Rack-based frameworks" 15 | spec.license = "MIT" 16 | spec.extra_rdoc_files = [ 17 | "LICENSE", 18 | "README.md" 19 | ] 20 | spec.files = `git ls-files -z`.split("\x0").reject do |f| 21 | f.match(%r{^(test|spec|features)/}) 22 | end 23 | spec.require_paths = ["lib"] 24 | spec.add_dependency "rack", ">= 2.2.3" 25 | 26 | spec.add_development_dependency "rake" 27 | spec.add_development_dependency "rspec", "~> 3" 28 | spec.add_development_dependency "rack-test" 29 | spec.add_development_dependency "rack-session" 30 | end 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2009-2017 Daniel Neighman 2 | Copyright (c) 2017-2020 Justin Smestad 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /lib/warden/test/warden_helpers.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | 4 | module Warden 5 | 6 | module Test 7 | module WardenHelpers 8 | # Returns list of regex objects that match paths expected to be an asset 9 | # @see Warden::Proxy#asset_request? 10 | # @api public 11 | def asset_paths 12 | @asset_paths ||= [/^\/assets\//] 13 | end 14 | 15 | # Sets list of regex objects that match paths expected to be an asset 16 | # @see Warden::Proxy#asset_request? 17 | # @api public 18 | def asset_paths=(*vals) 19 | @asset_paths = vals 20 | end 21 | 22 | # Adds a block to be executed on the next request when the stack reaches warden. 23 | # The warden proxy is yielded to the block 24 | # @api public 25 | def on_next_request(&blk) 26 | _on_next_request << blk 27 | end 28 | 29 | # resets wardens tests 30 | # any blocks queued to execute will be removed 31 | # @api public 32 | def test_reset! 33 | _on_next_request.clear 34 | end 35 | 36 | # A container for the on_next_request items. 37 | # @api private 38 | def _on_next_request 39 | @_on_next_request ||= [] 40 | @_on_next_request 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /lib/warden/test/helpers.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | 4 | module Warden 5 | module Test 6 | # A collection of test helpers for testing full stack rack applications using Warden 7 | # These provide the ability to login and logout on any given request 8 | # Note: During the teardown phase of your specs you should include: Warden.test_reset! 9 | module Helpers 10 | def self.included(_base) 11 | ::Warden.test_mode! 12 | end 13 | 14 | # A helper method that will perform a login of a user in warden for the next request. 15 | # Provide it the same options as you would to Warden::Proxy#set_user 16 | # @see Warden::Proxy#set_user 17 | # @api public 18 | def login_as(user, opts = {}) 19 | Warden.on_next_request do |proxy| 20 | opts[:event] ||= :authentication 21 | proxy.set_user(user, opts) 22 | end 23 | end 24 | 25 | # Logs out a user from the session. 26 | # Without arguments, all users will be logged out 27 | # Provide a list of scopes to only log out users with that scope. 28 | # @see Warden::Proxy#logout 29 | # @api public 30 | def logout(*scopes) 31 | Warden.on_next_request do |proxy| 32 | proxy.logout(*scopes) 33 | end 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/warden/session_serializer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | module Warden 4 | class SessionSerializer 5 | attr_reader :env 6 | 7 | def initialize(env) 8 | @env = env 9 | end 10 | 11 | def key_for(scope) 12 | "warden.user.#{scope}.key" 13 | end 14 | 15 | def serialize(user) 16 | user 17 | end 18 | 19 | def deserialize(key) 20 | key 21 | end 22 | 23 | def store(user, scope) 24 | return unless user 25 | method_name = "#{scope}_serialize" 26 | specialized = respond_to?(method_name) 27 | session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user) 28 | end 29 | 30 | def fetch(scope) 31 | key = session[key_for(scope)] 32 | return nil unless key 33 | 34 | method_name = "#{scope}_deserialize" 35 | user = respond_to?(method_name) ? send(method_name, key) : deserialize(key) 36 | delete(scope) unless user 37 | user 38 | end 39 | 40 | def stored?(scope) 41 | !!session[key_for(scope)] 42 | end 43 | 44 | def delete(scope, user=nil) 45 | session.delete(key_for(scope)) 46 | end 47 | 48 | # We can't cache this result because the session can be lazy loaded 49 | def session 50 | env["rack.session"] || {} 51 | end 52 | end # SessionSerializer 53 | end # Warden 54 | -------------------------------------------------------------------------------- /lib/warden/strategies.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | module Warden 4 | module Strategies 5 | class << self 6 | # Add a strategy and store it in a hash. 7 | def add(label, strategy = nil, &block) 8 | strategy ||= Class.new(Warden::Strategies::Base) 9 | strategy.class_eval(&block) if block_given? 10 | 11 | unless strategy.method_defined?(:authenticate!) 12 | raise NoMethodError, "authenticate! is not declared in the #{label.inspect} strategy" 13 | end 14 | 15 | base = Warden::Strategies::Base 16 | unless strategy.ancestors.include?(base) 17 | raise "#{label.inspect} is not a #{base}" 18 | end 19 | 20 | _strategies[label] = strategy 21 | end 22 | 23 | # Update a previously given strategy. 24 | def update(label, &block) 25 | strategy = _strategies[label] 26 | raise "Unknown strategy #{label.inspect}" unless strategy 27 | add(label, strategy, &block) 28 | end 29 | 30 | # Provides access to strategies by label 31 | # :api: public 32 | def [](label) 33 | _strategies[label] 34 | end 35 | 36 | # Clears all declared. 37 | # :api: public 38 | def clear! 39 | _strategies.clear 40 | end 41 | 42 | # :api: private 43 | def _strategies 44 | @strategies ||= {} 45 | end 46 | end # << self 47 | end # Strategies 48 | end # Warden 49 | -------------------------------------------------------------------------------- /spec/warden/errors_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::Proxy::Errors do 4 | 5 | before(:each) do 6 | @errors = Warden::Proxy::Errors.new 7 | end 8 | 9 | it "should report that it is empty on first creation" do 10 | expect(@errors).to be_empty 11 | end 12 | 13 | it "should continue to report that it is empty even after being checked" do 14 | @errors.on(:foo) 15 | expect(@errors).to be_empty 16 | end 17 | 18 | it "should add an error" do 19 | @errors.add(:login, "Login or password incorrect") 20 | expect(@errors[:login]).to eq(["Login or password incorrect"]) 21 | end 22 | 23 | it "should allow many errors to be added to the same field" do 24 | @errors.add(:login, "bad 1") 25 | @errors.add(:login, "bad 2") 26 | expect(@errors.on(:login)).to eq(["bad 1", "bad 2"]) 27 | end 28 | 29 | it "should give the full messages for an error" do 30 | @errors.add(:login, "login wrong") 31 | @errors.add(:password, "password wrong") 32 | ["password wrong", "login wrong"].each do |msg| 33 | expect(@errors.full_messages).to include(msg) 34 | end 35 | end 36 | 37 | it "should return the error for a specific field / label" do 38 | @errors.add(:login, "wrong") 39 | expect(@errors.on(:login)).to eq(["wrong"]) 40 | end 41 | 42 | it "should return nil for a specific field if it's not been set" do 43 | expect(@errors.on(:not_there)).to be_nil 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /lib/warden/mixins/common.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | module Warden 4 | module Mixins 5 | module Common 6 | 7 | # Convenience method to access the session 8 | # :api: public 9 | def session 10 | env['rack.session'] 11 | end # session 12 | 13 | # Alias :session to :raw_session since the former will be user API for storing scoped data. 14 | alias :raw_session :session 15 | 16 | # Convenience method to access the rack request. 17 | # :api: public 18 | def request 19 | @request ||= Rack::Request.new(@env) 20 | end # request 21 | 22 | # Provides a warden repository for cookies. Those are sent to the client 23 | # when the response is streamed back from the app. 24 | # :api: public 25 | def warden_cookies 26 | warn "warden_cookies was never functional and is going to be removed in next versions" 27 | env['warden.cookies'] ||= {} 28 | end # warden_cookies 29 | 30 | # Convenience method to access the rack request params 31 | # :api: public 32 | def params 33 | request.params 34 | end # params 35 | 36 | # Resets the session. By using this non-hash like sessions can 37 | # be cleared by overwriting this method in a plugin 38 | # @api overwritable 39 | def reset_session! 40 | raw_session.clear 41 | end # reset_session! 42 | 43 | end # Common 44 | end # Mixins 45 | end # Warden 46 | -------------------------------------------------------------------------------- /lib/warden.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | require 'forwardable' 4 | 5 | require 'warden/mixins/common' 6 | require 'warden/proxy' 7 | require 'warden/manager' 8 | require 'warden/errors' 9 | require 'warden/session_serializer' 10 | require 'warden/strategies' 11 | require 'warden/strategies/base' 12 | 13 | module Warden 14 | class NotAuthenticated < StandardError; end 15 | 16 | module Test 17 | autoload :WardenHelpers, 'warden/test/warden_helpers' 18 | autoload :Helpers, 'warden/test/helpers' 19 | autoload :Mock, 'warden/test/mock' 20 | end 21 | 22 | # Provides helper methods to warden for testing. 23 | # 24 | # To setup warden in test mode call the +test_mode!+ method on warden 25 | # 26 | # @example 27 | # Warden.test_mode! 28 | # 29 | # This will provide a number of methods. 30 | # Warden.on_next_request(&blk) - captures a block which is yielded the warden proxy on the next request 31 | # Warden.test_reset! - removes any captured blocks that would have been executed on the next request 32 | # 33 | # Warden.test_reset! should be called in after blocks for rspec, or teardown methods for Test::Unit 34 | def self.test_mode! 35 | unless Warden::Test::WardenHelpers === Warden 36 | Warden.extend Warden::Test::WardenHelpers 37 | Warden::Manager.on_request do |proxy| 38 | unless proxy.asset_request? 39 | while blk = Warden._on_next_request.shift 40 | blk.call(proxy) 41 | end 42 | end 43 | end 44 | end 45 | true 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/warden/config_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::Config do 4 | 5 | before(:each) do 6 | @config = Warden::Config.new 7 | end 8 | 9 | it "should behave like a hash" do 10 | @config[:foo] = :bar 11 | expect(@config[:foo]).to eq(:bar) 12 | end 13 | 14 | it "should provide hash accessors" do 15 | @config.failure_app = :foo 16 | expect(@config[:failure_app]).to eq(:foo) 17 | @config[:failure_app] = :bar 18 | expect(@config.failure_app).to eq(:bar) 19 | end 20 | 21 | it "should allow to read and set default strategies" do 22 | @config.default_strategies :foo, :bar 23 | expect(@config.default_strategies).to eq([:foo, :bar]) 24 | end 25 | 26 | it "should allow to silence missing strategies" do 27 | @config.silence_missing_strategies! 28 | expect(@config.silence_missing_strategies?).to eq(true) 29 | end 30 | 31 | it "should set the default_scope" do 32 | expect(@config.default_scope).to eq(:default) 33 | @config.default_scope = :foo 34 | expect(@config.default_scope).to eq(:foo) 35 | end 36 | 37 | it "should merge given options on initialization" do 38 | expect(Warden::Config.new(:foo => :bar)[:foo]).to eq(:bar) 39 | end 40 | 41 | it "should setup defaults with the scope_defaults method" do 42 | c = Warden::Config.new 43 | c.scope_defaults :foo, :strategies => [:foo, :bar], :store => false 44 | expect(c.default_strategies(:scope => :foo)).to eq([:foo, :bar]) 45 | expect(c.scope_defaults(:foo)).to eq(store: false) 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /spec/helpers/request_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | module Warden::Spec 4 | module Helpers 5 | FAILURE_APP = lambda{|_e|[401, {"Content-Type" => "text/plain"}, ["You Fail!"]] } 6 | 7 | def env_with_params(path = "/", params = {}, env = {}) 8 | method = params.delete(:method) || "GET" 9 | env = { 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => "#{method}" }.merge(env) 10 | Rack::MockRequest.env_for("#{path}?#{Rack::Utils.build_query(params)}", env) 11 | end 12 | 13 | def setup_rack(app = nil, opts = {}, &block) 14 | app ||= block if block_given? 15 | 16 | opts[:failure_app] ||= failure_app 17 | opts[:default_strategies] ||= [:password] 18 | opts[:default_serializers] ||= [:session] 19 | blk = opts[:configurator] || proc{} 20 | 21 | Rack::Builder.new do 22 | use opts[:session] || Warden::Spec::Helpers::Session unless opts[:nil_session] 23 | use Warden::Manager, opts, &blk 24 | run app 25 | end 26 | end 27 | 28 | def valid_response 29 | Rack::Response.new("OK").finish 30 | end 31 | 32 | def failure_app 33 | Warden::Spec::Helpers::FAILURE_APP 34 | end 35 | 36 | def success_app 37 | lambda{|e| [200, {"Content-Type" => "text/plain"}, ["You Win"]]} 38 | end 39 | 40 | class Session 41 | attr_accessor :app 42 | def initialize(app,configs = {}) 43 | @app = app 44 | end 45 | 46 | def call(e) 47 | e['rack.session'] ||= {} 48 | @app.call(e) 49 | end 50 | end # session 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /spec/warden/session_serializer_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::SessionSerializer do 4 | before(:each) do 5 | @env = env_with_params 6 | @env['rack.session'] ||= {} 7 | @session = Warden::SessionSerializer.new(@env) 8 | end 9 | 10 | it "should store data for the default scope" do 11 | @session.store("user", :default) 12 | expect(@env['rack.session']).to eq({ "warden.user.default.key"=>"user" }) 13 | end 14 | 15 | it "should check if a data is stored or not" do 16 | expect(@session).not_to be_stored(:default) 17 | @session.store("user", :default) 18 | expect(@session).to be_stored(:default) 19 | end 20 | 21 | it "should load an user from store" do 22 | expect(@session.fetch(:default)).to be_nil 23 | @session.store("user", :default) 24 | expect(@session.fetch(:default)).to eq("user") 25 | end 26 | 27 | it "should store data based on the scope" do 28 | @session.store("user", :default) 29 | expect(@session.fetch(:default)).to eq("user") 30 | expect(@session.fetch(:another)).to be_nil 31 | end 32 | 33 | it "should delete data from store" do 34 | @session.store("user", :default) 35 | expect(@session.fetch(:default)).to eq("user") 36 | @session.delete(:default) 37 | expect(@session.fetch(:default)).to be_nil 38 | end 39 | 40 | it "should delete information from store if user cannot be retrieved" do 41 | @session.store("user", :default) 42 | expect(@env['rack.session']).to have_key("warden.user.default.key") 43 | allow(@session).to receive(:deserialize).and_return(nil) 44 | @session.fetch(:default) 45 | expect(@env['rack.session']).not_to have_key("warden.user.default.key") 46 | end 47 | 48 | it "should support a nil session store" do 49 | @env['rack.session'] = nil 50 | expect(@session.fetch(:default)).to be_nil 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/warden/test/mock.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | 4 | require 'rack' 5 | 6 | module Warden 7 | module Test 8 | # A mock of an application to get a Warden object to test on 9 | # Note: During the teardown phase of your specs you should include: Warden.test_reset! 10 | module Mock 11 | def self.included(_base) 12 | ::Warden.test_mode! 13 | end 14 | 15 | # A helper method that provides the warden object by mocking the env variable. 16 | # @api public 17 | def warden 18 | @warden ||= begin 19 | env['warden'] 20 | end 21 | end 22 | 23 | private 24 | 25 | def env 26 | @env ||= begin 27 | request = Rack::MockRequest.env_for( 28 | "/?#{Rack::Utils.build_query({})}", 29 | { 'HTTP_VERSION' => '1.1', 'REQUEST_METHOD' => 'GET' } 30 | ) 31 | app.call(request) 32 | 33 | request 34 | end 35 | end 36 | 37 | def app 38 | @app ||= begin 39 | opts = { 40 | failure_app: lambda { |_e| 41 | [401, { 'Content-Type' => 'text/plain' }, ['You Fail!']] 42 | }, 43 | default_strategies: :password, 44 | default_serializers: :session 45 | } 46 | Rack::Builder.new do 47 | use Warden::Test::Mock::Session 48 | use Warden::Manager, opts, &proc {} 49 | run lambda { |_e| 50 | [200, { 'Content-Type' => 'text/plain' }, ['You Win']] 51 | } 52 | end 53 | end 54 | end 55 | 56 | class Session 57 | attr_accessor :app 58 | def initialize(app, _configs={}) 59 | @app = app 60 | end 61 | 62 | def call(e) 63 | e['rack.session'] ||= {} 64 | @app.call(e) 65 | end 66 | end # session 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /lib/warden/errors.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | module Warden 4 | class Proxy 5 | # Lifted from DataMapper's dm-validations plugin :) 6 | # @author Guy van den Berg 7 | # @since DM 0.9 8 | class Errors 9 | 10 | include Enumerable 11 | 12 | # Clear existing authentication errors. 13 | def clear! 14 | errors.clear 15 | end 16 | 17 | # Add a authentication error. Use the field_name :general if the errors does 18 | # not apply to a specific field of the Resource. 19 | # 20 | # @param field_name the name of the field that caused the error 21 | # @param message the message to add 22 | def add(field_name, message) 23 | (errors[field_name] ||= []) << message 24 | end 25 | 26 | # Collect all errors into a single list. 27 | def full_messages 28 | errors.inject([]) do |list,pair| 29 | list += pair.last 30 | end 31 | end 32 | 33 | # Return authentication errors for a particular field_name. 34 | # 35 | # @param field_name the name of the field you want an error for 36 | def on(field_name) 37 | errors_for_field = errors[field_name] 38 | blank?(errors_for_field) ? nil : errors_for_field 39 | end 40 | 41 | def each 42 | errors.map.each do |_k,v| 43 | next if blank?(v) 44 | yield(v) 45 | end 46 | end 47 | 48 | def empty? 49 | entries.empty? 50 | end 51 | 52 | def method_missing(meth, *args, &block) 53 | errors.send(meth, *args, &block) 54 | end 55 | 56 | private 57 | def errors 58 | @errors ||= {} 59 | end 60 | 61 | def blank?(thing) 62 | thing.nil? || thing == "" || (thing.respond_to?(:empty?) && thing.empty?) 63 | end 64 | 65 | end # class Errors 66 | end # Proxy 67 | end # Warden 68 | -------------------------------------------------------------------------------- /spec/warden/test/test_mode_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::Test::WardenHelpers do 4 | before :all do 5 | Warden.test_mode! 6 | end 7 | 8 | before do 9 | $captures = [] 10 | @app = lambda{|_e| valid_response } 11 | end 12 | 13 | after do 14 | Warden.test_reset! 15 | end 16 | 17 | it{ expect(Warden).to respond_to(:test_mode!) } 18 | it{ expect(Warden).to respond_to(:on_next_request) } 19 | it{ expect(Warden).to respond_to(:test_reset!) } 20 | 21 | it "should execute the on_next_request block on the next request" do 22 | Warden.on_next_request do |warden| 23 | $captures << warden 24 | end 25 | 26 | setup_rack(@app).call(env_with_params) 27 | expect($captures.length).to eq(1) 28 | expect($captures.first).to be_an_instance_of(Warden::Proxy) 29 | end 30 | 31 | it "should execute many on_next_request blocks on the next request" do 32 | Warden.on_next_request{|_w| $captures << :first } 33 | Warden.on_next_request{|_w| $captures << :second } 34 | setup_rack(@app).call(env_with_params) 35 | expect($captures).to eq([:first, :second]) 36 | end 37 | 38 | it "should not execute on_next_request blocks on subsequent requests" do 39 | app = setup_rack(@app) 40 | Warden.on_next_request{|_w| $captures << :first } 41 | app.call(env_with_params) 42 | expect($captures).to eq([:first]) 43 | $captures.clear 44 | app.call(env_with_params) 45 | expect($captures).to be_empty 46 | end 47 | 48 | it "should allow me to set new_on_next_request items to execute in the same test" do 49 | app = setup_rack(@app) 50 | Warden.on_next_request{|_w| $captures << :first } 51 | app.call(env_with_params) 52 | expect($captures).to eq([:first]) 53 | Warden.on_next_request{|_w| $captures << :second } 54 | app.call(env_with_params) 55 | expect($captures).to eq([:first, :second]) 56 | end 57 | 58 | it "should remove the on_next_request items when test is reset" do 59 | app = setup_rack(@app) 60 | Warden.on_next_request{|_w| $captures << :first } 61 | Warden.test_reset! 62 | app.call(env_with_params) 63 | expect($captures).to eq([]) 64 | end 65 | 66 | context "asset requests" do 67 | it "should not execute on_next_request blocks if this is an asset request" do 68 | app = setup_rack(@app) 69 | Warden.on_next_request{|_w| $captures << :first } 70 | app.call(env_with_params("/assets/fun.gif")) 71 | expect($captures).to eq([]) 72 | end 73 | end 74 | end 75 | -------------------------------------------------------------------------------- /spec/warden/test/helpers_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::Test::Helpers do 4 | before{ $captures = [] } 5 | after{ Warden.test_reset! } 6 | 7 | it "should log me in as a user" do 8 | user = "A User" 9 | login_as user 10 | app = lambda{|e| 11 | $captures << :run 12 | expect(e['warden']).to be_authenticated 13 | expect(e['warden'].user).to eq("A User") 14 | valid_response 15 | } 16 | setup_rack(app).call(env_with_params) 17 | expect($captures).to eq([:run]) 18 | end 19 | 20 | it "should log me in as a user of a given scope" do 21 | user = {:some => "user"} 22 | login_as user, :scope => :foo_scope 23 | app = lambda{|e| 24 | $captures << :run 25 | w = e['warden'] 26 | expect(w).to be_authenticated(:foo_scope) 27 | expect(w.user(:foo_scope)).to eq(some: "user") 28 | } 29 | setup_rack(app).call(env_with_params) 30 | expect($captures).to eq([:run]) 31 | end 32 | 33 | it "should login multiple users with different scopes" do 34 | user = "A user" 35 | foo_user = "A foo user" 36 | login_as user 37 | login_as foo_user, :scope => :foo 38 | app = lambda{|e| 39 | $captures << :run 40 | w = e['warden'] 41 | expect(w.user).to eq("A user") 42 | expect(w.user(:foo)).to eq("A foo user") 43 | expect(w).to be_authenticated 44 | expect(w).to be_authenticated(:foo) 45 | } 46 | setup_rack(app).call(env_with_params) 47 | expect($captures).to eq([:run]) 48 | end 49 | 50 | it "should log out all users" do 51 | user = "A user" 52 | foo = "Foo" 53 | login_as user 54 | login_as foo, :scope => :foo 55 | app = lambda{|e| 56 | $captures << :run 57 | w = e['warden'] 58 | expect(w.user).to eq("A user") 59 | expect(w.user(:foo)).to eq("Foo") 60 | w.logout 61 | expect(w.user).to be_nil 62 | expect(w.user(:foo)).to be_nil 63 | expect(w).not_to be_authenticated 64 | expect(w).not_to be_authenticated(:foo) 65 | } 66 | setup_rack(app).call(env_with_params) 67 | expect($captures).to eq([:run]) 68 | end 69 | 70 | it "should logout a specific user" do 71 | user = "A User" 72 | foo = "Foo" 73 | login_as user 74 | login_as foo, :scope => :foo 75 | app = lambda{|e| 76 | $captures << :run 77 | w = e['warden'] 78 | w.logout :foo 79 | expect(w.user).to eq("A User") 80 | expect(w.user(:foo)).to be_nil 81 | expect(w).not_to be_authenticated(:foo) 82 | } 83 | setup_rack(app).call(env_with_params) 84 | expect($captures).to eq([:run]) 85 | end 86 | 87 | describe "#asset_paths" do 88 | it "should default asset_paths to anything asset path regex" do 89 | expect(Warden.asset_paths).to eq([/^\/assets\//] ) 90 | end 91 | end 92 | end 93 | -------------------------------------------------------------------------------- /spec/warden/strategies_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::Strategies do 4 | it "should let me add a strategy via a block" do 5 | Warden::Strategies.add(:strategy1) do 6 | def authenticate! 7 | success("foo") 8 | end 9 | end 10 | expect(Warden::Strategies[:strategy1].ancestors).to include(Warden::Strategies::Base) 11 | end 12 | 13 | it "should raise an error if I add a strategy via a block, that does not have an authenticate! method" do 14 | expect { 15 | Warden::Strategies.add(:strategy2) do 16 | end 17 | }.to raise_error(NoMethodError) 18 | end 19 | 20 | it "should raise an error if I add a strategy that does not extend Warden::Strategies::Base" do 21 | non_base = Class.new do 22 | def authenticate! 23 | end 24 | end 25 | expect do 26 | Warden::Strategies.add(:strategy_non_base, non_base) 27 | end.to raise_error(/is not a Warden::Strategies::Base/) 28 | end 29 | 30 | it "should allow me to get access to a particular strategy" do 31 | Warden::Strategies.add(:strategy3) do 32 | def authenticate!; end 33 | end 34 | strategy = Warden::Strategies[:strategy3] 35 | expect(strategy).not_to be_nil 36 | expect(strategy.ancestors).to include(Warden::Strategies::Base) 37 | end 38 | 39 | it "should allow me to add a strategy with the required methods" do 40 | class MyStrategy < Warden::Strategies::Base 41 | def authenticate!; end 42 | end 43 | 44 | expect { 45 | Warden::Strategies.add(:strategy4, MyStrategy) 46 | }.not_to raise_error 47 | end 48 | 49 | it "should not allow a strategy that does not have an authenticate! method" do 50 | class MyOtherStrategy 51 | end 52 | expect { 53 | Warden::Strategies.add(:strategy5, MyOtherStrategy) 54 | }.to raise_error(NoMethodError) 55 | end 56 | 57 | it "should allow me to change a class when providing a block and class" do 58 | class MyStrategy < Warden::Strategies::Base 59 | end 60 | 61 | Warden::Strategies.add(:foo, MyStrategy) do 62 | def authenticate!; end 63 | end 64 | 65 | expect(Warden::Strategies[:foo].ancestors).to include(MyStrategy) 66 | end 67 | 68 | it "should allow me to update a previously given strategy" do 69 | class MyStrategy < Warden::Strategies::Base 70 | def authenticate!; end 71 | end 72 | 73 | Warden::Strategies.add(:strategy6, MyStrategy) 74 | 75 | new_module = Module.new 76 | Warden::Strategies.update(:strategy6) do 77 | include new_module 78 | end 79 | 80 | expect(Warden::Strategies[:strategy6].ancestors).to include(new_module) 81 | end 82 | 83 | it "should allow me to clear the strategies" do 84 | Warden::Strategies.add(:foobar) do 85 | def authenticate! 86 | :foo 87 | end 88 | end 89 | expect(Warden::Strategies[:foobar]).not_to be_nil 90 | Warden::Strategies.clear! 91 | expect(Warden::Strategies[:foobar]).to be_nil 92 | end 93 | end 94 | -------------------------------------------------------------------------------- /lib/warden/config.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | 4 | module Warden 5 | # This class is yielded inside Warden::Manager. If you have a plugin and want to 6 | # add more configuration to warden, you just need to extend this class. 7 | class Config < Hash 8 | # Creates an accessor that simply sets and reads a key in the hash: 9 | # 10 | # class Config < Hash 11 | # hash_accessor :failure_app 12 | # end 13 | # 14 | # config = Config.new 15 | # config.failure_app = Foo 16 | # config[:failure_app] #=> Foo 17 | # 18 | # config[:failure_app] = Bar 19 | # config.failure_app #=> Bar 20 | # 21 | def self.hash_accessor(*names) #:nodoc: 22 | names.each do |name| 23 | class_eval <<-METHOD, __FILE__, __LINE__ + 1 24 | def #{name} 25 | self[:#{name}] 26 | end 27 | 28 | def #{name}=(value) 29 | self[:#{name}] = value 30 | end 31 | METHOD 32 | end 33 | end 34 | 35 | hash_accessor :failure_app, :default_scope, :intercept_401 36 | 37 | def initialize(other={}) 38 | merge!(other) 39 | self[:default_scope] ||= :default 40 | self[:scope_defaults] ||= {} 41 | self[:default_strategies] ||= {} 42 | self[:intercept_401] = true unless key?(:intercept_401) 43 | end 44 | 45 | def initialize_copy(other) 46 | super 47 | deep_dup(:scope_defaults, other) 48 | deep_dup(:default_strategies, other) 49 | end 50 | 51 | # Do not raise an error if a missing strategy is given. 52 | # :api: plugin 53 | def silence_missing_strategies! 54 | self[:silence_missing_strategies] = true 55 | end 56 | 57 | def silence_missing_strategies? #:nodoc: 58 | !!self[:silence_missing_strategies] 59 | end 60 | 61 | # Set the default strategies to use. 62 | # :api: public 63 | def default_strategies(*strategies) 64 | opts = Hash === strategies.last ? strategies.pop : {} 65 | hash = self[:default_strategies] 66 | scope = opts[:scope] || :_all 67 | 68 | hash[scope] = strategies.flatten unless strategies.empty? 69 | hash[scope] || hash[:_all] || [] 70 | end 71 | 72 | # A short hand way to set up a particular scope 73 | # :api: public 74 | def scope_defaults(scope, opts = {}) 75 | if strategies = opts.delete(:strategies) 76 | default_strategies(strategies, :scope => scope) 77 | end 78 | 79 | if opts.empty? 80 | self[:scope_defaults][scope] || {} 81 | else 82 | self[:scope_defaults][scope] ||= {} 83 | self[:scope_defaults][scope].merge!(opts) 84 | end 85 | end 86 | 87 | # Quick accessor to strategies from manager 88 | # :api: public 89 | def strategies 90 | Warden::Strategies 91 | end 92 | 93 | # Hook from configuration to serialize_into_session. 94 | # :api: public 95 | def serialize_into_session(*args, &block) 96 | Warden::Manager.serialize_into_session(*args, &block) 97 | end 98 | 99 | # Hook from configuration to serialize_from_session. 100 | # :api: public 101 | def serialize_from_session(*args, &block) 102 | Warden::Manager.serialize_from_session(*args, &block) 103 | end 104 | 105 | protected 106 | 107 | def deep_dup(key, other) 108 | self[key] = hash = other[key].dup 109 | hash.each { |k, v| hash[k] = v.dup } 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /spec/warden/scoped_session_serializer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::Manager do 4 | before(:each) do 5 | @env = env_with_params 6 | @env['rack.session'] ||= {} 7 | Warden::Manager.serialize_from_session { |k| k } 8 | Warden::Manager.serialize_into_session { |u| u } 9 | begin 10 | Warden::SessionSerializer.send :remove_method, :admin_serialize 11 | rescue 12 | end 13 | begin 14 | Warden::SessionSerializer.send :remove_method, :admin_deserialize 15 | rescue 16 | end 17 | end 18 | 19 | after(:each) do 20 | Warden::Manager.serialize_from_session { |k| k } 21 | Warden::Manager.serialize_into_session { |u| u } 22 | begin 23 | Warden::SessionSerializer.send :remove_method, :admin_deserialize 24 | Warden::SessionSerializer.send :remove_method, :admin_serialize 25 | rescue 26 | end 27 | end 28 | 29 | def serializer_respond_to?(name) 30 | Warden::SessionSerializer.new(@env).respond_to? name 31 | end 32 | 33 | it "should respond to :serialize" do 34 | serializer_respond_to?(:serialize).should be_true 35 | end 36 | 37 | it "should respond to :deserialize" do 38 | serializer_respond_to?(:deserialize).should be_true 39 | end 40 | 41 | it "should respond to {scope}_deserialize if Manager.serialize_from_session is called with scope" do 42 | Rack::Builder.new do 43 | Warden::Manager.serialize_from_session(:admin) { |n| n } 44 | end 45 | serializer_respond_to?(:admin_deserialize).should be_true 46 | end 47 | 48 | it "should respond to {scope}_serialize if Manager.serialize_into_session is called with scope" do 49 | Rack::Builder.new do 50 | Warden::Manager.serialize_into_session(:admin) { |n| n } 51 | end 52 | serializer_respond_to?(:admin_serialize).should be_true 53 | end 54 | 55 | def initialize_with_scope(scope, &block) 56 | Rack::Builder.new do 57 | Warden::Manager.serialize_into_session(scope, &block) 58 | end 59 | end 60 | 61 | it "should execute serialize if no {scope}_serialize is present" do 62 | serialized_object = nil 63 | initialize_with_scope(nil) do |user| 64 | serialized_object = user 65 | user 66 | end 67 | serializer = Warden::SessionSerializer.new(@env) 68 | serializer.store("user", :admin) 69 | serialized_object.should eq("user") 70 | end 71 | 72 | it "should not have a {scope}_serialize by default" do 73 | serializer_respond_to?(:admin_serialize).should be_false 74 | end 75 | 76 | it "should execute {scope}_serialize when calling store with a scope" do 77 | serialized_object = nil 78 | initialize_with_scope(:admin) do |user| 79 | serialized_object = user 80 | user 81 | end 82 | 83 | serializer = Warden::SessionSerializer.new(@env) 84 | serializer.store("user", :admin) 85 | serialized_object.should eq("user") 86 | end 87 | 88 | 89 | it "should execute {scope}_deserialize when calling store with a scope" do 90 | serialized_object = nil 91 | 92 | Rack::Builder.new do 93 | Warden::Manager.serialize_from_session(:admin) do |key| 94 | serialized_object = key 95 | key 96 | end 97 | end 98 | 99 | serializer = Warden::SessionSerializer.new(@env) 100 | @env['rack.session'][serializer.key_for(:admin)] = "test" 101 | serializer.fetch(:admin) 102 | 103 | serialized_object.should eq("test") 104 | end 105 | 106 | it "should execute deserialize if {scope}_deserialize is not present" do 107 | serialized_object = nil 108 | 109 | Rack::Builder.new do 110 | Warden::Manager.serialize_from_session do |key| 111 | serialized_object = key 112 | key 113 | end 114 | end 115 | 116 | serializer = Warden::SessionSerializer.new(@env) 117 | @env['rack.session'][serializer.key_for(:admin)] = "test" 118 | serializer.fetch(:admin) 119 | 120 | serialized_object.should eq("test") 121 | end 122 | 123 | end 124 | -------------------------------------------------------------------------------- /spec/warden/authenticated_data_store_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe "authenticated data store" do 4 | 5 | before(:each) do 6 | @env = env_with_params 7 | @env['rack.session'] = { 8 | "warden.user.foo.key" => "foo user", 9 | "warden.user.default.key" => "default user", 10 | :foo => "bar" 11 | } 12 | end 13 | 14 | it "should store data for the default scope" do 15 | app = lambda do |e| 16 | e['warden'].authenticate(:pass) 17 | e['warden'].authenticate(:pass, :scope => :foo) 18 | expect(e['warden']).to be_authenticated 19 | expect(e['warden']).to be_authenticated(:foo) 20 | 21 | # Store the data for :default 22 | e['warden'].session[:key] = "value" 23 | valid_response 24 | end 25 | setup_rack(app).call(@env) 26 | expect(@env['rack.session']['warden.user.default.session']).to eq(key: "value") 27 | expect(@env['rack.session']['warden.user.foo.session']).to be_nil 28 | end 29 | 30 | it "should store data for the foo user" do 31 | app = lambda do |e| 32 | e['warden'].session(:foo)[:key] = "value" 33 | valid_response 34 | end 35 | setup_rack(app).call(@env) 36 | expect(@env['rack.session']['warden.user.foo.session']).to eq(key: "value") 37 | end 38 | 39 | it "should store the data separately" do 40 | app = lambda do |e| 41 | e['warden'].session[:key] = "value" 42 | e['warden'].session(:foo)[:key] = "another value" 43 | valid_response 44 | end 45 | setup_rack(app).call(@env) 46 | expect(@env['rack.session']['warden.user.default.session']).to eq(key: "value") 47 | expect(@env['rack.session']['warden.user.foo.session' ]).to eq(key: "another value") 48 | end 49 | 50 | it "should clear the foo scoped data when foo logs out" do 51 | app = lambda do |e| 52 | e['warden'].session[:key] = "value" 53 | e['warden'].session(:foo)[:key] = "another value" 54 | e['warden'].logout(:foo) 55 | valid_response 56 | end 57 | setup_rack(app).call(@env) 58 | expect(@env['rack.session']['warden.user.default.session']).to eq(key: "value") 59 | expect(@env['rack.session']['warden.user.foo.session' ]).to be_nil 60 | end 61 | 62 | it "should clear out the default data when :default logs out" do 63 | app = lambda do |e| 64 | e['warden'].session[:key] = "value" 65 | e['warden'].session(:foo)[:key] = "another value" 66 | e['warden'].logout(:default) 67 | valid_response 68 | end 69 | setup_rack(app).call(@env) 70 | expect(@env['rack.session']['warden.user.default.session']).to be_nil 71 | expect(@env['rack.session']['warden.user.foo.session' ]).to eq(key: "another value") 72 | end 73 | 74 | it "should clear out all data when a general logout is performed" do 75 | app = lambda do |e| 76 | e['warden'].session[:key] = "value" 77 | e['warden'].session(:foo)[:key] = "another value" 78 | e['warden'].logout 79 | valid_response 80 | end 81 | setup_rack(app).call(@env) 82 | expect(@env['rack.session']['warden.user.default.session']).to be_nil 83 | expect(@env['rack.session']['warden.user.foo.session' ]).to be_nil 84 | end 85 | 86 | it "should logout multiple persons at once" do 87 | @env['rack.session']['warden.user.bar.key'] = "bar user" 88 | 89 | app = lambda do |e| 90 | e['warden'].session[:key] = "value" 91 | e['warden'].session(:foo)[:key] = "another value" 92 | e['warden'].session(:bar)[:key] = "yet another" 93 | e['warden'].logout(:bar, :default) 94 | valid_response 95 | end 96 | setup_rack(app).call(@env) 97 | expect(@env['rack.session']['warden.user.default.session']).to be_nil 98 | expect(@env['rack.session']['warden.user.foo.session' ]).to eq(key: "another value") 99 | expect(@env['rack.session']['warden.user.bar.session' ]).to be_nil 100 | end 101 | 102 | it "should not store data for a user who is not logged in" do 103 | @env['rack.session'] 104 | app = lambda do |e| 105 | e['warden'].session(:not_here)[:key] = "value" 106 | valid_response 107 | end 108 | 109 | expect { 110 | setup_rack(app).call(@env) 111 | }.to raise_error(Warden::NotAuthenticated) 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /lib/warden/manager.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | require 'warden/hooks' 4 | require 'warden/config' 5 | 6 | module Warden 7 | # The middleware for Rack Authentication 8 | # The middleware requires that there is a session upstream 9 | # The middleware injects an authentication object into 10 | # the rack environment hash 11 | class Manager 12 | extend Warden::Hooks 13 | 14 | attr_accessor :config 15 | 16 | # Initialize the middleware. If a block is given, a Warden::Config is yielded so you can properly 17 | # configure the Warden::Manager. 18 | # :api: public 19 | def initialize(app, options={}) 20 | default_strategies = options.delete(:default_strategies) 21 | 22 | @app, @config = app, Warden::Config.new(options) 23 | @config.default_strategies(*default_strategies) if default_strategies 24 | yield @config if block_given? 25 | end 26 | 27 | # Invoke the application guarding for throw :warden. 28 | # If this is downstream from another warden instance, don't do anything. 29 | # :api: private 30 | def call(env) # :nodoc: 31 | return @app.call(env) if env['warden'] && env['warden'].manager != self 32 | 33 | env['warden'] = Proxy.new(env, self) 34 | result = catch(:warden) do 35 | env['warden'].on_request 36 | @app.call(env) 37 | end 38 | 39 | result ||= {} 40 | case result 41 | when Array 42 | handle_chain_result(result.first, result, env) 43 | when Hash 44 | process_unauthenticated(env, result) 45 | when Rack::Response 46 | handle_chain_result(result.status, result, env) 47 | end 48 | end 49 | 50 | # :api: private 51 | def _run_callbacks(*args) #:nodoc: 52 | self.class._run_callbacks(*args) 53 | end 54 | 55 | class << self 56 | # Prepares the user to serialize into the session. 57 | # Any object that can be serialized into the session in some way can be used as a "user" object 58 | # Generally however complex object should not be stored in the session. 59 | # If possible store only a "key" of the user object that will allow you to reconstitute it. 60 | # 61 | # You can supply different methods of serialization for different scopes by passing a scope symbol 62 | # 63 | # Example: 64 | # Warden::Manager.serialize_into_session{ |user| user.id } 65 | # # With Scope: 66 | # Warden::Manager.serialize_into_session(:admin) { |user| user.id } 67 | # 68 | # :api: public 69 | def serialize_into_session(scope = nil, &block) 70 | method_name = scope.nil? ? :serialize : "#{scope}_serialize" 71 | Warden::SessionSerializer.send :define_method, method_name, &block 72 | end 73 | 74 | # Reconstitutes the user from the session. 75 | # Use the results of user_session_key to reconstitute the user from the session on requests after the initial login 76 | # You can supply different methods of de-serialization for different scopes by passing a scope symbol 77 | # 78 | # Example: 79 | # Warden::Manager.serialize_from_session{ |id| User.get(id) } 80 | # # With Scope: 81 | # Warden::Manager.serialize_from_session(:admin) { |id| AdminUser.get(id) } 82 | # 83 | # :api: public 84 | def serialize_from_session(scope = nil, &block) 85 | method_name = scope.nil? ? :deserialize : "#{scope}_deserialize" 86 | 87 | if Warden::SessionSerializer.method_defined? method_name 88 | Warden::SessionSerializer.send :remove_method, method_name 89 | end 90 | 91 | Warden::SessionSerializer.send :define_method, method_name, &block 92 | end 93 | end 94 | 95 | private 96 | 97 | def handle_chain_result(status, result, env) 98 | if status == 401 && intercept_401?(env) 99 | process_unauthenticated(env) 100 | else 101 | result 102 | end 103 | end 104 | 105 | def intercept_401?(env) 106 | config[:intercept_401] && !env['warden'].custom_failure? 107 | end 108 | 109 | # When a request is unauthenticated, here's where the processing occurs. 110 | # It looks at the result of the proxy to see if it's been executed and what action to take. 111 | # :api: private 112 | def process_unauthenticated(env, options={}) 113 | options[:action] ||= begin 114 | opts = config[:scope_defaults][config.default_scope] || {} 115 | opts[:action] || 'unauthenticated' 116 | end 117 | 118 | proxy = env['warden'] 119 | result = options[:result] || proxy.result 120 | 121 | case result 122 | when :redirect 123 | body = proxy.message || "You are being redirected to #{proxy.headers['Location']}" 124 | [proxy.status, proxy.headers, [body]] 125 | when :custom 126 | proxy.custom_response 127 | else 128 | options[:message] ||= proxy.message 129 | call_failure_app(env, options) 130 | end 131 | end 132 | 133 | # Calls the failure app. 134 | # The before_failure hooks are run on each failure 135 | # :api: private 136 | def call_failure_app(env, options = {}) 137 | if config.failure_app 138 | options.merge!(:attempted_path => ::Rack::Request.new(env).fullpath) 139 | env["PATH_INFO"] = "/#{options[:action]}" 140 | env["warden.options"] = options 141 | 142 | _run_callbacks(:before_failure, env, options) 143 | config.failure_app.call(env).to_a 144 | else 145 | raise "No Failure App provided" 146 | end 147 | end # call_failure_app 148 | end 149 | end # Warden 150 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## Version 1.2.9 / 2020-08-31 4 | * Avoid warning on uninitialized instance variable (#188) 5 | * Bump rack to 2.2.3 (#190) 6 | * Remove Gemfile.lock 7 | * Resolve outstanding TODO entries (#179) 8 | * A bunch of gem structure cleanup (thanks @olleolleolle) 9 | * Set winning strategy when :warden is thrown (#174) 10 | * Bump rack dependency to >= 2.0.9 due to CVEs 11 | 12 | ## Version 1.2.8 / 2018-11-15 13 | 14 | * Bugfix: Flips two lines to allow scopes authenticating from another without stepping on each other's toes. (PR #144) 15 | * Update `rack` dependency to >= 2.0.6 due to security vulnerability 16 | * Internal: Add Rubocop Lint checking 17 | * Internal: Update RSpec to use `.rspec` file 18 | 19 | ## Version 1.2.7 / 2016-10-12 20 | 21 | * Added 'frozen_string_literal' comment, bump ruby to 2.3 22 | 23 | ## Version 1.2.6 / 2016-01-31 24 | 25 | * Separate test helpers to encapsulate Warden object mocking inside it's own class 26 | 27 | ## Version 1.2.5 / 2016-01-28 28 | 29 | * Expands on the test helpers available to make it easier for testing gems 30 | 31 | ## Version 1.2.3 / 2013-07-14 32 | 33 | * Fix an issue with lazy loaded sessions 34 | 35 | ## Version 1.2.2 / 2013-07-12 36 | 37 | * Support nil session stores on logout 38 | * Fix strategies blowing up with undefined method base 39 | 40 | ## Version 1.2.1 / 2012-06-16 41 | 42 | * Minor caching and speed improvements 43 | * Add support to #lock in the proxy 44 | * Add support to after_failed_fetch callback 45 | 46 | ## Version 1.2.0 / 2012-05-08 47 | 48 | * Deprecate warden_cookies since it was never functional 49 | * Add support to serialize_from_session and serialize_into_session per scope 50 | 51 | ## Version 1.1.1 / 2012-02-16 52 | 53 | * Allow run_callbacks as an option to set_user and user 54 | 55 | ## Version 1.1.0 / 2011-11-02 56 | 57 | * Use the default scopes action when using a bare throw(:warden) 58 | 59 | ## Version 1.0.6 60 | 61 | * Remove gem files from the packaged gem 62 | 63 | ## Version 1.0.3 64 | 65 | * Do not renew session on user fetch 66 | 67 | ## Version 1.0.2 68 | 69 | * Added :intercept_401 to Warden::Config 70 | 71 | ## Version 1.0.1 72 | 73 | * Bug fix on strategies errors handler 74 | 75 | ## Version 1.0.0 76 | 77 | * Bump! 78 | * Allow strategies to configure if user should be stored or not 79 | * Force session id renewal when user is set 80 | 81 | ## Version 0.10.7 82 | 83 | * Performance boost. config object to use raw accessors 84 | * Add per strategy storage option 85 | 86 | ## Version 0.10.6 / 0.10.7 / 2010-05-22 87 | 88 | * Bugfix set_user was not respecting logouts in hooks 89 | 90 | ## Version 0.10.4 / 0.10.5 / 2010-05-20 91 | * Add action specifying in scope_defaults 92 | 93 | ## Version 0.10.3 / 2010-03-01 94 | * Bugfix prevent halted winning strategy from being skipped in subsequent runs 95 | 96 | ## Version 0.10.2 / 2010-03-26 97 | * Halt on fail!. Add fail to allow cascading 98 | * cache the winning strategy 99 | * Make the config object Dupable 100 | 101 | ## Version 0.10.1 / 2010-03-23 102 | * Merge previous from master 103 | * tag 104 | 105 | ## Version 0.10.0 / 2010-03-22 106 | * Allow default strategies to be set on the proxy 107 | * Provide each scope with it's own default strategies 108 | * Provide each scope with default set_user opts 109 | * depricate the Proxy#default_strategies= method 110 | 111 | ## Version 0.9.5 / 2010-02-28 112 | 113 | * Add Warden.test_mode! 114 | * Add Warden.on_next_request 115 | * Add test helpers in Warden::Test::Helpers 116 | ** login_as 117 | ** logout 118 | 119 | ## Version 0.9.4 / 2010-02-23 120 | 121 | * Fix an issue where winning_strategy was not cleaned, allowing multiple scopes to sign in, even when the second one should not 122 | 123 | ## Version 0.9.3 / 2010-02-17 124 | 125 | * Add prepend_ to all hooks (josevalim) 126 | 127 | ## Version 0.9.2 / 2010-02-10 128 | 129 | * Ruby 1.9 compatibility changes (grimen) 130 | 131 | ## Version 0.9.1 / 2010-02-09 132 | 133 | * Support for passing a custom message with Warden::Strategy::Base#success! as second optional (grimen) 134 | 135 | ## Version 0.9.0 / 2010-01-21 136 | 137 | * Remove serializers and make strategies more powerful, including cache behavior (josevalim) 138 | 139 | ## Version 0.8.1 / 2010-01-06 140 | 141 | * Fix a bug when silence missing serializers is set (josevalim) 142 | 143 | ## Version 0.8.0 / 2010-01-06 144 | 145 | * enhancements 146 | * Add conditionals to callbacks (josevalim) 147 | * Extract Warden::Config from Warden::Manager (josevalim) 148 | 149 | ## Version 0.7.0 / 2010-01-04 150 | 151 | * enhancements 152 | * Expose config in warden proxy (hassox) 153 | 154 | ## Version 0.6.0 / 2009-11-16 155 | 156 | * enhancements 157 | * added serializers, including session serializer (set by default) and a cookie serializer (josevalim) 158 | 159 | * deprecation 160 | * serializer_into_session and serializer_from_session are deprecated, overwrite serialize and deserializer in Warden::Serializers::Session instead (josevalim) 161 | 162 | ## Version 0.5.3 / 2009-11-10 163 | 164 | * bug fixes 165 | * authenticated? and unauthenticated? should return true or false, not the user or false. (hassox) 166 | 167 | ## Version 0.5.2 / 2009-11-09 168 | 169 | * enhancements 170 | * authenticated? always try to serialize the user from session (josevalim) 171 | * stored_in_session? checks if user information is stored in session, without serializing (josevalim) 172 | * 401 behaves exactly like throw :warden (staugaard) 173 | 174 | ## Version 0.5.1 / 2009-10-25 175 | 176 | * enhancements 177 | * Adds yielding to authenticated? and unauthenticated? methods (hassox) 178 | * Adds an option to silence missing strategies (josevalim) 179 | * Add an option to authenticate(!) to prevent storage of a user into the session (hassox) 180 | * allow custom :action to be thrown (josevalim) 181 | 182 | ## Version 0.4.0 / 2009-10-12 183 | 184 | * enhancements 185 | * add Content-Type header to redirects (staugaard) 186 | * Make scope available to strategies (josevalim) 187 | 188 | * bug fixes 189 | * Do not consume opts twice, otherwise just the first will parse the scope (josevalim) 190 | 191 | ## Version 0.3.2 / 2009-09-15 192 | 193 | * enhancements 194 | * add a hook for plugins to specify how they can clear the whole section 195 | -------------------------------------------------------------------------------- /lib/warden/strategies/base.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | module Warden 4 | module Strategies 5 | # A strategy is a place where you can put logic related to authentication. Any strategy inherits 6 | # from Warden::Strategies::Base. 7 | # 8 | # The Warden::Strategies.add method is a simple way to provide custom strategies. 9 | # You _must_ declare an @authenticate!@ method. 10 | # You _may_ provide a @valid?@ method. 11 | # The valid method should return true or false depending on if the strategy is a valid one for the request. 12 | # 13 | # The parameters for Warden::Strategies.add method are: 14 | # The label is the name given to a strategy. Use the label to refer to the strategy when authenticating 15 | # The optional strategy argument if set _must_ be a class that inherits from Warden::Strategies::Base and _must_ 16 | # implement an @authenticate!@ method 17 | # The block acts as a convenient way to declare your strategy. Inside is the class definition of a strategy. 18 | # 19 | # Examples: 20 | # 21 | # Block Declared Strategy: 22 | # Warden::Strategies.add(:foo) do 23 | # def authenticate! 24 | # # authentication logic 25 | # end 26 | # end 27 | # 28 | # Class Declared Strategy: 29 | # Warden::Strategies.add(:foo, MyStrategy) 30 | # 31 | class Base 32 | # :api: public 33 | attr_accessor :user, :message 34 | 35 | # :api: private 36 | attr_accessor :result, :custom_response 37 | 38 | # :api: public 39 | attr_reader :env, :scope, :status 40 | 41 | include ::Warden::Mixins::Common 42 | 43 | # :api: private 44 | def initialize(env, scope=nil) # :nodoc: 45 | @env, @scope = env, scope 46 | @status, @headers = nil, {} 47 | @halted, @performed = false, false 48 | @result = nil 49 | end 50 | 51 | # The method that is called from above. This method calls the underlying authenticate! method 52 | # :api: private 53 | def _run! # :nodoc: 54 | @performed = true 55 | authenticate! 56 | self 57 | end 58 | 59 | # Returns if this strategy was already performed. 60 | # :api: private 61 | def performed? #:nodoc: 62 | @performed 63 | end 64 | 65 | # Marks this strategy as not performed. 66 | # :api: private 67 | def clear! 68 | @performed = false 69 | end 70 | 71 | # Acts as a guarding method for the strategy. 72 | # If #valid? responds false, the strategy will not be executed 73 | # Overwrite with your own logic 74 | # :api: overwritable 75 | def valid?; true; end 76 | 77 | # Provides access to the headers hash for setting custom headers 78 | # :api: public 79 | def headers(header = {}) 80 | @headers ||= {} 81 | @headers.merge! header 82 | @headers 83 | end 84 | 85 | # Access to the errors object. 86 | # :api: public 87 | def errors 88 | @env['warden'].errors 89 | end 90 | 91 | # Cause the processing of the strategies to stop and cascade no further 92 | # :api: public 93 | def halt! 94 | @halted = true 95 | end 96 | 97 | # Checks to see if a strategy was halted 98 | # :api: public 99 | def halted? 100 | !!@halted 101 | end 102 | 103 | # Checks to see if a strategy should result in a permanent login 104 | # :api: public 105 | def store? 106 | true 107 | end 108 | 109 | # A simple method to return from authenticate! if you want to ignore this strategy 110 | # :api: public 111 | def pass; end 112 | 113 | # Returns true only if the result is a success and a user was assigned. 114 | def successful? 115 | @result == :success && !user.nil? 116 | end 117 | 118 | # Whenever you want to provide a user object as "authenticated" use the +success!+ method. 119 | # This will halt the strategy, and set the user in the appropriate scope. 120 | # It is the "login" method 121 | # 122 | # Parameters: 123 | # user - The user object to login. This object can be anything you have setup to serialize in and out of the session 124 | # 125 | # :api: public 126 | def success!(user, message = nil) 127 | halt! 128 | @user = user 129 | @message = message 130 | @result = :success 131 | end 132 | 133 | # This causes the strategy to fail. It does not throw a :warden symbol to drop the request out to the failure application 134 | # You must throw a :warden symbol somewhere in the application to enforce this 135 | # Halts the strategies so that this is the last strategy checked 136 | # :api: public 137 | def fail!(message = "Failed to Login") 138 | halt! 139 | @message = message 140 | @result = :failure 141 | end 142 | 143 | # Causes the strategy to fail, but not halt. The strategies will cascade after this failure and warden will check the next strategy. The last strategy to fail will have it's message displayed. 144 | # :api: public 145 | def fail(message = "Failed to Login") 146 | @message = message 147 | @result = :failure 148 | end 149 | 150 | # Causes the authentication to redirect. A :warden symbol must be thrown to actually execute this redirect 151 | # 152 | # Parameters: 153 | # url - The string representing the URL to be redirected to 154 | # params - Any parameters to encode into the URL 155 | # opts - Any options to redirect with. 156 | # available options: permanent => (true || false) 157 | # 158 | # :api: public 159 | def redirect!(url, params = {}, opts = {}) 160 | halt! 161 | @status = opts[:permanent] ? 301 : 302 162 | headers["Location"] = url.dup 163 | headers["Location"] << "?" << Rack::Utils.build_query(params) unless params.empty? 164 | headers["Content-Type"] = opts[:content_type] || 'text/plain' 165 | 166 | @message = opts[:message] || "You are being redirected to #{headers["Location"]}" 167 | @result = :redirect 168 | 169 | headers["Location"] 170 | end 171 | 172 | # Return a custom rack array. You must throw an :warden symbol to activate this 173 | # :api: public 174 | def custom!(response) 175 | halt! 176 | @custom_response = response 177 | @result = :custom 178 | end 179 | 180 | end # Base 181 | end # Strategies 182 | end # Warden 183 | -------------------------------------------------------------------------------- /lib/warden/hooks.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | module Warden 4 | module Hooks 5 | 6 | # Hook to _run_callbacks asserting for conditions. 7 | def _run_callbacks(kind, *args) #:nodoc: 8 | options = args.last # Last callback arg MUST be a Hash 9 | 10 | send("_#{kind}").each do |callback, conditions| 11 | invalid = conditions.find do |key, value| 12 | value.is_a?(Array) ? !value.include?(options[key]) : (value != options[key]) 13 | end 14 | 15 | callback.call(*args) unless invalid 16 | end 17 | end 18 | 19 | # A callback hook set to run every time after a user is set. 20 | # This callback is triggered the first time one of those three events happens 21 | # during a request: :authentication, :fetch (from session) and :set_user (when manually set). 22 | # You can supply as many hooks as you like, and they will be run in order of declaration. 23 | # 24 | # If you want to run the callbacks for a given scope and/or event, you can specify them as options. 25 | # See parameters and example below. 26 | # 27 | # Parameters: 28 | # Some options which specify when the callback should be executed 29 | # scope - Executes the callback only if it matches the scope(s) given 30 | # only - Executes the callback only if it matches the event(s) given 31 | # except - Executes the callback except if it matches the event(s) given 32 | # A block where you can set arbitrary logic to run every time a user is set 33 | # Block Parameters: |user, auth, opts| 34 | # user - The user object that is being set 35 | # auth - The raw authentication proxy object. 36 | # opts - any options passed into the set_user call including :scope 37 | # 38 | # Example: 39 | # Warden::Manager.after_set_user do |user,auth,opts| 40 | # scope = opts[:scope] 41 | # if auth.session["#{scope}.last_access"].to_i > (Time.now - 5.minutes) 42 | # auth.logout(scope) 43 | # throw(:warden, :scope => scope, :reason => "Times Up") 44 | # end 45 | # auth.session["#{scope}.last_access"] = Time.now 46 | # end 47 | # 48 | # Warden::Manager.after_set_user :except => :fetch do |user,auth,opts| 49 | # user.login_count += 1 50 | # end 51 | # 52 | # :api: public 53 | def after_set_user(options = {}, method = :push, &block) 54 | raise BlockNotGiven unless block_given? 55 | 56 | if options.key?(:only) 57 | options[:event] = options.delete(:only) 58 | elsif options.key?(:except) 59 | options[:event] = [:set_user, :authentication, :fetch] - Array(options.delete(:except)) 60 | end 61 | 62 | _after_set_user.send(method, [block, options]) 63 | end 64 | 65 | # Provides access to the array of after_set_user blocks to run 66 | # :api: private 67 | def _after_set_user # :nodoc: 68 | @_after_set_user ||= [] 69 | end 70 | 71 | # after_authentication is just a wrapper to after_set_user, which is only invoked 72 | # when the user is set through the authentication path. The options and yielded arguments 73 | # are the same as in after_set_user. 74 | # 75 | # :api: public 76 | def after_authentication(options = {}, method = :push, &block) 77 | after_set_user(options.merge(:event => :authentication), method, &block) 78 | end 79 | 80 | # after_fetch is just a wrapper to after_set_user, which is only invoked 81 | # when the user is fetched from session. The options and yielded arguments 82 | # are the same as in after_set_user. 83 | # 84 | # :api: public 85 | def after_fetch(options = {}, method = :push, &block) 86 | after_set_user(options.merge(:event => :fetch), method, &block) 87 | end 88 | 89 | # A callback that runs just prior to the failure application being called. 90 | # This callback occurs after PATH_INFO has been modified for the failure (default /unauthenticated) 91 | # In this callback you can mutate the environment as required by the failure application 92 | # If a Rails controller were used for the failure_app for example, you would need to set request[:params][:action] = :unauthenticated 93 | # 94 | # Parameters: 95 | # Some options which specify when the callback should be executed 96 | # scope - Executes the callback only if it matches the scope(s) given 97 | # A block to contain logic for the callback 98 | # Block Parameters: |env, opts| 99 | # env - The rack env hash 100 | # opts - any options passed into the authenticate call including :scope 101 | # 102 | # Example: 103 | # Warden::Manager.before_failure do |env, opts| 104 | # params = Rack::Request.new(env).params 105 | # params[:action] = :unauthenticated 106 | # params[:warden_failure] = opts 107 | # end 108 | # 109 | # :api: public 110 | def before_failure(options = {}, method = :push, &block) 111 | raise BlockNotGiven unless block_given? 112 | _before_failure.send(method, [block, options]) 113 | end 114 | 115 | # Provides access to the callback array for before_failure 116 | # :api: private 117 | def _before_failure 118 | @_before_failure ||= [] 119 | end 120 | 121 | # A callback that runs if no user could be fetched, meaning there is now no user logged in. 122 | # 123 | # Parameters: 124 | # Some options which specify when the callback should be executed 125 | # scope - Executes the callback only if it matches the scope(s) given 126 | # A block to contain logic for the callback 127 | # Block Parameters: |user, auth, scope| 128 | # user - The authenticated user for the current scope 129 | # auth - The warden proxy object 130 | # opts - any options passed into the authenticate call including :scope 131 | # 132 | # Example: 133 | # Warden::Manager.after_failed_fetch do |user, auth, opts| 134 | # I18n.locale = :en 135 | # end 136 | # 137 | # :api: public 138 | def after_failed_fetch(options = {}, method = :push, &block) 139 | raise BlockNotGiven unless block_given? 140 | _after_failed_fetch.send(method, [block, options]) 141 | end 142 | 143 | # Provides access to the callback array for after_failed_fetch 144 | # :api: private 145 | def _after_failed_fetch 146 | @_after_failed_fetch ||= [] 147 | end 148 | 149 | # A callback that runs just prior to the logout of each scope. 150 | # 151 | # Parameters: 152 | # Some options which specify when the callback should be executed 153 | # scope - Executes the callback only if it matches the scope(s) given 154 | # A block to contain logic for the callback 155 | # Block Parameters: |user, auth, scope| 156 | # user - The authenticated user for the current scope 157 | # auth - The warden proxy object 158 | # opts - any options passed into the authenticate call including :scope 159 | # 160 | # Example: 161 | # Warden::Manager.before_logout do |user, auth, opts| 162 | # user.forget_me! 163 | # end 164 | # 165 | # :api: public 166 | def before_logout(options = {}, method = :push, &block) 167 | raise BlockNotGiven unless block_given? 168 | _before_logout.send(method, [block, options]) 169 | end 170 | 171 | # Provides access to the callback array for before_logout 172 | # :api: private 173 | def _before_logout 174 | @_before_logout ||= [] 175 | end 176 | 177 | # A callback that runs on each request, just after the proxy is initialized 178 | # 179 | # Parameters: 180 | # A block to contain logic for the callback 181 | # Block Parameters: |proxy| 182 | # proxy - The warden proxy object for the request 183 | # 184 | # Example: 185 | # user = "A User" 186 | # Warden::Manager.on_request do |proxy| 187 | # proxy.set_user = user 188 | # end 189 | # 190 | # :api: public 191 | def on_request(options = {}, method = :push, &block) 192 | raise BlockNotGiven unless block_given? 193 | _on_request.send(method, [block, options]) 194 | end 195 | 196 | # Provides access to the callback array for before_logout 197 | # :api: private 198 | def _on_request 199 | @_on_request ||= [] 200 | end 201 | 202 | # Add prepend filters version 203 | %w(after_set_user after_authentication after_fetch on_request 204 | before_failure before_logout).each do |filter| 205 | class_eval <<-METHOD, __FILE__, __LINE__ + 1 206 | def prepend_#{filter}(options={}, &block) 207 | #{filter}(options, :unshift, &block) 208 | end 209 | METHOD 210 | end 211 | end # Hooks 212 | end # Warden 213 | -------------------------------------------------------------------------------- /spec/warden/strategies/base_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | require 'spec_helper' 4 | 5 | describe Warden::Strategies::Base do 6 | 7 | before(:each) do 8 | RAS = Warden::Strategies unless defined?(RAS) 9 | Warden::Strategies.clear! 10 | end 11 | 12 | describe "headers" do 13 | it "should have headers" do 14 | Warden::Strategies.add(:foo) do 15 | def authenticate! 16 | headers("foo" => "bar") 17 | end 18 | end 19 | strategy = Warden::Strategies[:foo].new(env_with_params) 20 | strategy._run! 21 | expect(strategy.headers["foo"]).to eq("bar") 22 | end 23 | 24 | it "should allow us to clear the headers" do 25 | Warden::Strategies.add(:foo) do 26 | def authenticate! 27 | headers("foo" => "bar") 28 | end 29 | end 30 | strategy = Warden::Strategies[:foo].new(env_with_params) 31 | strategy._run! 32 | expect(strategy.headers["foo"]).to eq("bar") 33 | strategy.headers.clear 34 | expect(strategy.headers).to be_empty 35 | end 36 | end 37 | 38 | it "should have a user object" do 39 | RAS.add(:foobar) do 40 | def authenticate! 41 | success!("foo") 42 | end 43 | end 44 | strategy = RAS[:foobar].new(env_with_params) 45 | strategy._run! 46 | expect(strategy.user).to eq("foo") 47 | end 48 | 49 | it "should be performed after run" do 50 | RAS.add(:foobar) do 51 | def authenticate!; end 52 | end 53 | strategy = RAS[:foobar].new(env_with_params) 54 | expect(strategy).not_to be_performed 55 | strategy._run! 56 | expect(strategy).to be_performed 57 | strategy.clear! 58 | expect(strategy).not_to be_performed 59 | end 60 | 61 | it "should set the scope when initialized with one" do 62 | RAS.add(:foobar) do 63 | def authenticate!; end 64 | end 65 | strategy = RAS[:foobar].new(env_with_params, :user) 66 | expect(strategy.scope).to eq(:user) 67 | end 68 | 69 | it "should allow you to set a message" do 70 | RAS.add(:foobar) do 71 | def authenticate! 72 | self.message = "foo message" 73 | end 74 | end 75 | strategy = RAS[:foobar].new(env_with_params) 76 | strategy._run! 77 | expect(strategy.message).to eq("foo message") 78 | end 79 | 80 | it "should provide access to the errors" do 81 | RAS.add(:foobar) do 82 | def authenticate! 83 | errors.add(:foo, "foo has an error") 84 | end 85 | end 86 | env = env_with_params 87 | env['warden'] = Warden::Proxy.new(env, Warden::Manager.new({})) 88 | strategy = RAS[:foobar].new(env) 89 | strategy._run! 90 | expect(strategy.errors.on(:foo)).to eq(["foo has an error"]) 91 | end 92 | 93 | describe "halting" do 94 | it "should allow you to halt a strategy" do 95 | RAS.add(:foobar) do 96 | def authenticate! 97 | halt! 98 | end 99 | end 100 | str = RAS[:foobar].new(env_with_params) 101 | str._run! 102 | expect(str).to be_halted 103 | end 104 | 105 | it "should not be halted if halt was not called" do 106 | RAS.add(:foobar) do 107 | def authenticate! 108 | "foo" 109 | end 110 | end 111 | str = RAS[:foobar].new(env_with_params) 112 | str._run! 113 | expect(str).not_to be_halted 114 | end 115 | 116 | end 117 | 118 | describe "pass" do 119 | it "should allow you to pass" do 120 | RAS.add(:foobar) do 121 | def authenticate! 122 | pass 123 | end 124 | end 125 | str = RAS[:foobar].new(env_with_params) 126 | str._run! 127 | expect(str).not_to be_halted 128 | expect(str.user).to be_nil 129 | end 130 | end 131 | 132 | describe "redirect" do 133 | it "should allow you to set a redirection" do 134 | RAS.add(:foobar) do 135 | def authenticate! 136 | redirect!("/foo/bar") 137 | end 138 | end 139 | str = RAS[:foobar].new(env_with_params) 140 | str._run! 141 | expect(str.user).to be_nil 142 | end 143 | 144 | it "should mark the strategy as halted when redirecting" do 145 | RAS.add(:foobar) do 146 | def authenticate! 147 | redirect!("/foo/bar") 148 | end 149 | end 150 | str = RAS[:foobar].new(env_with_params) 151 | str._run! 152 | expect(str).to be_halted 153 | end 154 | 155 | it "should escape redirected url parameters" do 156 | RAS.add(:foobar) do 157 | def authenticate! 158 | redirect!("/foo/bar", :foo => "bar") 159 | end 160 | end 161 | str = RAS[:foobar].new(env_with_params) 162 | str._run! 163 | expect(str.headers["Location"]).to eq("/foo/bar?foo=bar") 164 | end 165 | 166 | it "should allow you to set a message" do 167 | RAS.add(:foobar) do 168 | def authenticate! 169 | redirect!("/foo/bar", {:foo => "bar"}, :message => "You are being redirected foo") 170 | end 171 | end 172 | str = RAS[:foobar].new(env_with_params) 173 | str._run! 174 | expect(str.headers["Location"]).to eq("/foo/bar?foo=bar") 175 | expect(str.message).to eq("You are being redirected foo") 176 | end 177 | 178 | it "should set the action as :redirect" do 179 | RAS.add(:foobar) do 180 | def authenticate! 181 | redirect!("/foo/bar", {:foo => "bar"}, :message => "foo") 182 | end 183 | end 184 | str = RAS[:foobar].new(env_with_params) 185 | str._run! 186 | expect(str.result).to be(:redirect) 187 | end 188 | end 189 | 190 | describe "failure" do 191 | 192 | before(:each) do 193 | RAS.add(:hard_fail) do 194 | def authenticate! 195 | fail!("You are not cool enough") 196 | end 197 | end 198 | 199 | RAS.add(:soft_fail) do 200 | def authenticate! 201 | fail("You are too soft") 202 | end 203 | end 204 | @hard = RAS[:hard_fail].new(env_with_params) 205 | @soft = RAS[:soft_fail].new(env_with_params) 206 | end 207 | 208 | it "should allow you to fail hard" do 209 | @hard._run! 210 | expect(@hard.user).to be_nil 211 | end 212 | 213 | it "should halt the strategies when failing hard" do 214 | @hard._run! 215 | expect(@hard).to be_halted 216 | end 217 | 218 | it "should allow you to set a message when failing hard" do 219 | @hard._run! 220 | expect(@hard.message).to eq("You are not cool enough") 221 | end 222 | 223 | it "should set the action as :failure when failing hard" do 224 | @hard._run! 225 | expect(@hard.result).to be(:failure) 226 | end 227 | 228 | it "should allow you to fail soft" do 229 | @soft._run! 230 | expect(@soft.user).to be_nil 231 | end 232 | 233 | it "should not halt the strategies when failing soft" do 234 | @soft._run! 235 | expect(@soft).not_to be_halted 236 | end 237 | 238 | it "should allow you to set a message when failing soft" do 239 | @soft._run! 240 | expect(@soft.message).to eq("You are too soft") 241 | end 242 | 243 | it "should set the action as :failure when failing soft" do 244 | @soft._run! 245 | expect(@soft.result).to be(:failure) 246 | end 247 | end 248 | 249 | describe "success" do 250 | before(:each) do 251 | RAS.add(:foobar) do 252 | def authenticate! 253 | success!("Foo User", "Welcome to the club!") 254 | end 255 | end 256 | @str = RAS[:foobar].new(env_with_params) 257 | end 258 | 259 | it "should allow you to succeed" do 260 | @str._run! 261 | end 262 | 263 | it "should be authenticated after success" do 264 | @str._run! 265 | expect(@str.user).not_to be_nil 266 | end 267 | 268 | it "should allow you to set a message when succeeding" do 269 | @str._run! 270 | expect(@str.message).to eq("Welcome to the club!") 271 | end 272 | 273 | it "should store the user" do 274 | @str._run! 275 | expect(@str.user).to eq("Foo User") 276 | end 277 | 278 | it "should set the action as :success" do 279 | @str._run! 280 | expect(@str.result).to be(:success) 281 | end 282 | end 283 | 284 | describe "custom response" do 285 | before(:each) do 286 | RAS.add(:foobar) do 287 | def authenticate! 288 | custom!([521, {"foo" => "bar"}, ["BAD"]]) 289 | end 290 | end 291 | @str = RAS[:foobar].new(env_with_params) 292 | @str._run! 293 | end 294 | 295 | it "should allow me to set a custom rack response" do 296 | expect(@str.user).to be_nil 297 | end 298 | 299 | it "should halt the strategy" do 300 | expect(@str).to be_halted 301 | end 302 | 303 | it "should provide access to the custom rack response" do 304 | expect(@str.custom_response).to eq([521, {"foo" => "bar"}, ["BAD"]]) 305 | end 306 | 307 | it "should set the action as :custom" do 308 | @str._run! 309 | expect(@str.result).to eq(:custom) 310 | end 311 | end 312 | 313 | end 314 | -------------------------------------------------------------------------------- /spec/warden/manager_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe Warden::Manager do 4 | it "should insert a Proxy object into the rack env" do 5 | env = env_with_params 6 | setup_rack(success_app).call(env) 7 | expect(env["warden"]).to be_an_instance_of(Warden::Proxy) 8 | end 9 | 10 | describe "thrown auth" do 11 | describe "Failure" do 12 | it "should respond with a 401 response if the strategy fails authentication" do 13 | env = env_with_params("/", :foo => "bar") 14 | app = lambda do |_env| 15 | _env['warden'].authenticate(:failz) 16 | throw(:warden, :action => :unauthenticated) 17 | end 18 | result = setup_rack(app).call(env) 19 | expect(result.first).to eq(401) 20 | end 21 | 22 | it "should use the failure message given to the failure method" do 23 | env = env_with_params("/", {}) 24 | app = lambda do |_env| 25 | _env['warden'].authenticate(:failz) 26 | throw(:warden) 27 | end 28 | fail_app = lambda do |_env| 29 | [401, {"Content-Type" => "text/plain"}, ["You Fail!"]] 30 | end 31 | result = setup_rack(app, :failure_app => fail_app).call(env) 32 | expect(result.last).to eq(["You Fail!"]) 33 | end 34 | 35 | it "should set the message from the winning strategy in warden.options hash" do 36 | env = env_with_params("/", {}) 37 | app = lambda do |_env| 38 | _env['warden'].authenticate(:failz) 39 | throw(:warden) 40 | end 41 | setup_rack(app).call(env) 42 | expect(env["warden.options"][:message]).to eq("The Fails Strategy Has Failed You") 43 | end 44 | 45 | it "should render the failure app when there's a failure" do 46 | app = lambda do |e| 47 | throw(:warden, :action => :unauthenticated) unless e['warden'].authenticated?(:failz) 48 | end 49 | fail_app = lambda do |e| 50 | [401, {"Content-Type" => "text/plain"}, ["Failure App"]] 51 | end 52 | result = setup_rack(app, :failure_app => fail_app).call(env_with_params) 53 | expect(result.last).to eq(["Failure App"]) 54 | end 55 | 56 | it "should call failure app if warden is thrown even after successful authentication" do 57 | env = env_with_params("/", {}) 58 | app = lambda do |_env| 59 | _env['warden'].authenticate(:pass) 60 | throw(:warden) 61 | end 62 | fail_app = lambda do |_env| 63 | [401, {"Content-Type" => "text/plain"}, ["You Fail!"]] 64 | end 65 | result = setup_rack(app, :failure_app => fail_app).call(env) 66 | expect(result.first).to eq(401) 67 | expect(result.last).to eq(["You Fail!"]) 68 | end 69 | 70 | it "should set the attempted url in warden.options hash" do 71 | env = env_with_params("/access/path", {}) 72 | app = lambda do |_env| 73 | _env['warden'].authenticate(:pass) 74 | throw(:warden) 75 | end 76 | result = setup_rack(app).call(env) 77 | expect(result.first).to eq(401) 78 | expect(env["warden.options"][:attempted_path]).to eq("/access/path") 79 | end 80 | 81 | it "should set action in warden.options if overridden" do 82 | env = env_with_params("/access/path", {}) 83 | app = lambda do |_env| 84 | _env['warden'].authenticate(:pass) 85 | throw(:warden, action: :different_action) 86 | end 87 | result = setup_rack(app).call(env) 88 | expect(result.first).to eq(401) 89 | expect(env["warden.options"][:action]).to eq(:different_action) 90 | end 91 | 92 | 93 | it "should catch a resubmitted request" do 94 | # this is a bit convoluted. but it's occurred in the field with Rack::OpenID 95 | $count = 0 96 | $throw_count = 0 97 | env = env_with_params("/foo") 98 | class ::ResubmittingMiddleware 99 | @@app = nil 100 | def initialize(app) 101 | @@app = app 102 | end 103 | 104 | def self.call(env) 105 | if $count > 1 106 | Rack::Response.new("Bad", 401) 107 | else 108 | $count += 1 109 | @@app.call(env) 110 | end 111 | end 112 | 113 | def call(env) 114 | $count += 1 115 | @@app.call(env) 116 | end 117 | 118 | end 119 | 120 | app = lambda do |e| 121 | $throw_count += 1 122 | throw(:warden) 123 | end 124 | 125 | builder = Rack::Builder.new do 126 | use ResubmittingMiddleware 127 | use Warden::Manager do |config| 128 | config.failure_app = ResubmittingMiddleware 129 | end 130 | run app 131 | end 132 | 133 | result = builder.to_app.call(env) 134 | expect(result[0]).to eq(401) 135 | expect(result[2]).to eq(["Bad"]) 136 | expect($throw_count).to eq(2) 137 | end 138 | 139 | it "should use the default scopes action when a bare throw is used" do 140 | env = env_with_params("/", :foo => "bar") 141 | action = nil 142 | 143 | failure = lambda do |_env| 144 | action = _env['PATH_INFO'] 145 | [401, {}, ['fail']] 146 | end 147 | 148 | app = lambda do |_env| 149 | throw(:warden) 150 | end 151 | result = setup_rack(app, 152 | :failure_app => failure, 153 | :configurator => lambda{ |c| c.scope_defaults(:default, :action => 'my_action', :strategies => [:password]) } 154 | ).call(env) 155 | 156 | expect(action).to eq("/my_action") 157 | expect(result.first).to eq(401) 158 | end 159 | end # failure 160 | end 161 | 162 | describe "integrated strategies" do 163 | before(:each) do 164 | RAS = Warden::Strategies unless defined?(RAS) 165 | Warden::Strategies.clear! 166 | @app = setup_rack do |env| 167 | env['warden'].authenticate!(:foobar) 168 | [200, {"Content-Type" => "text/plain"}, ["Foo Is A Winna"]] 169 | end 170 | end 171 | 172 | describe "redirecting" do 173 | 174 | it "should redirect with a message" do 175 | RAS.add(:foobar) do 176 | def authenticate! 177 | redirect!("/foo/bar", {:foo => "bar"}, :message => "custom redirection message") 178 | end 179 | end 180 | result = @app.call(env_with_params) 181 | expect(result[0]).to be(302) 182 | expect(result[1]["Location"]).to eq("/foo/bar?foo=bar") 183 | expect(result[2]).to eq(["custom redirection message"]) 184 | end 185 | 186 | it "should redirect with a default message" do 187 | RAS.add(:foobar) do 188 | def authenticate! 189 | redirect!("/foo/bar", {:foo => "bar"}) 190 | end 191 | end 192 | result = @app.call(env_with_params) 193 | expect(result[0]).to eq(302) 194 | expect(result[1]['Location']).to eq("/foo/bar?foo=bar") 195 | expect(result[2]).to eq(["You are being redirected to /foo/bar?foo=bar"]) 196 | end 197 | 198 | it "should redirect with a permanent redirect" do 199 | RAS.add(:foobar) do 200 | def authenticate! 201 | redirect!("/foo/bar", {}, :permanent => true) 202 | end 203 | end 204 | result = @app.call(env_with_params) 205 | expect(result[0]).to eq(301) 206 | end 207 | 208 | it "should redirect with a content type" do 209 | RAS.add(:foobar) do 210 | def authenticate! 211 | redirect!("/foo/bar", {:foo => "bar"}, :content_type => "text/xml") 212 | end 213 | end 214 | result = @app.call(env_with_params) 215 | expect(result[0]).to eq(302) 216 | expect(result[1]["Location"]).to eq("/foo/bar?foo=bar") 217 | expect(result[1]["Content-Type"]).to eq("text/xml") 218 | end 219 | 220 | it "should redirect with a default content type" do 221 | RAS.add(:foobar) do 222 | def authenticate! 223 | redirect!("/foo/bar", {:foo => "bar"}) 224 | end 225 | end 226 | result = @app.call(env_with_params) 227 | expect(result[0]).to eq(302) 228 | expect(result[1]["Location"]).to eq("/foo/bar?foo=bar") 229 | expect(result[1]["Content-Type"]).to eq("text/plain") 230 | end 231 | end 232 | 233 | describe "failing" do 234 | it "should fail according to the failure app" do 235 | RAS.add(:foobar) do 236 | def authenticate! 237 | fail! 238 | end 239 | end 240 | env = env_with_params 241 | result = @app.call(env) 242 | expect(result[0]).to eq(401) 243 | expect(result[2]).to eq(["You Fail!"]) 244 | expect(env['PATH_INFO']).to eq("/unauthenticated") 245 | end 246 | 247 | it "should allow you to customize the response" do 248 | app = lambda do |e| 249 | e['warden'].custom_failure! 250 | [401,{'Content-Type' => 'text/plain'},["Fail From The App"]] 251 | end 252 | env = env_with_params 253 | result = setup_rack(app).call(env) 254 | expect(result[0]).to eq(401) 255 | expect(result[2]).to eq(["Fail From The App"]) 256 | end 257 | 258 | it "should allow you to customize the response without the explicit call to custom_failure! if not intercepting 401" do 259 | app = lambda do |e| 260 | [401,{'Content-Type' => 'text/plain'},["Fail From The App"]] 261 | end 262 | env = env_with_params 263 | result = setup_rack(app, :intercept_401 => false).call(env) 264 | expect(result[0]).to eq(401) 265 | expect(result[2]).to eq(["Fail From The App"]) 266 | end 267 | 268 | it "should render the failure application for a 401 if no custom_failure flag is set" do 269 | app = lambda do |e| 270 | [401,{'Content-Type' => 'text/plain'},["Fail From The App"]] 271 | end 272 | result = setup_rack(app).call(env_with_params) 273 | expect(result[0]).to eq(401) 274 | expect(result[2]).to eq(["You Fail!"]) 275 | end 276 | 277 | end # failing 278 | 279 | describe "custom rack response" do 280 | it "should return a custom rack response" do 281 | RAS.add(:foobar) do 282 | def authenticate! 283 | custom!([523, {"Content-Type" => "text/plain", "Custom-Header" => "foo"}, ["Custom Stuff"]]) 284 | end 285 | end 286 | result = @app.call(env_with_params) 287 | expect(result[0]).to be(523) 288 | expect(result[1]["Custom-Header"]).to eq("foo") 289 | expect(result[2]).to eq(["Custom Stuff"]) 290 | end 291 | end 292 | 293 | describe "app returns Rack::Response" do 294 | it "should return it" do 295 | RAS.add(:foobar) do 296 | def authenticate! 297 | custom!(Rack::Response.new(['body'], 201, {"Content-Type" => "text/plain"})) 298 | end 299 | end 300 | result = @app.call(env_with_params) 301 | expect(result.status).to eq(201) 302 | expect(result.body).to eq(['body']) 303 | expect(result.headers['Content-Type']).to eq('text/plain') 304 | end 305 | end 306 | 307 | describe "success" do 308 | it "should pass through to the application when there is success" do 309 | RAS.add(:foobar) do 310 | def authenticate! 311 | success!("A User") 312 | end 313 | end 314 | env = env_with_params 315 | result = @app.call(env) 316 | expect(result[0]).to eq(200) 317 | expect(result[2]).to eq(["Foo Is A Winna"]) 318 | end 319 | end 320 | end # integrated strategies 321 | 322 | it "should allow me to set a different default scope for warden" do 323 | Rack::Builder.new do 324 | use Warden::Manager, :default_scope => :default do |manager| 325 | expect(manager.default_scope).to eq(:default) 326 | manager.default_scope = :other 327 | expect(manager.default_scope).to eq(:other) 328 | end 329 | end 330 | end 331 | 332 | it "should allow me to access strategies through manager" do 333 | Rack::Builder.new do 334 | use Warden::Manager do |manager| 335 | expect(manager.strategies).to eq(Warden::Strategies) 336 | end 337 | end 338 | end 339 | end 340 | -------------------------------------------------------------------------------- /lib/warden/proxy.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | 4 | module Warden 5 | class UserNotSet < RuntimeError; end 6 | 7 | class Proxy 8 | # An accessor to the winning strategy 9 | # :api: private 10 | attr_accessor :winning_strategy 11 | 12 | # An accessor to the rack env hash, the proxy owner and its config 13 | # :api: public 14 | attr_reader :env, :manager, :config, :winning_strategies 15 | 16 | extend ::Forwardable 17 | include ::Warden::Mixins::Common 18 | 19 | ENV_WARDEN_ERRORS = 'warden.errors'.freeze 20 | ENV_SESSION_OPTIONS = 'rack.session.options'.freeze 21 | 22 | # :api: private 23 | def_delegators :winning_strategy, :headers, :status, :custom_response 24 | 25 | # :api: public 26 | def_delegators :config, :default_strategies 27 | 28 | def initialize(env, manager) #:nodoc: 29 | @env, @users, @winning_strategies, @locked = env, {}, {}, false 30 | @manager, @config = manager, manager.config.dup 31 | @strategies = Hash.new { |h,k| h[k] = {} } 32 | end 33 | 34 | # Run the on_request callbacks 35 | # :api: private 36 | def on_request 37 | manager._run_callbacks(:on_request, self) 38 | end 39 | 40 | # Lazily initiate errors object in session. 41 | # :api: public 42 | def errors 43 | @env[ENV_WARDEN_ERRORS] ||= Errors.new 44 | end 45 | 46 | # Points to a SessionSerializer instance responsible for handling 47 | # everything related with storing, fetching and removing the user 48 | # session. 49 | # :api: public 50 | def session_serializer 51 | @session_serializer ||= Warden::SessionSerializer.new(@env) 52 | end 53 | 54 | # Clear the cache of performed strategies so far. Warden runs each 55 | # strategy just once during the request lifecycle. You can clear the 56 | # strategies cache if you want to allow a strategy to be run more than 57 | # once. 58 | # 59 | # This method has the same API as authenticate, allowing you to clear 60 | # specific strategies for given scope: 61 | # 62 | # Parameters: 63 | # args - a list of symbols (labels) that name the strategies to attempt 64 | # opts - an options hash that contains the :scope of the user to check 65 | # 66 | # Example: 67 | # # Clear all strategies for the configured default_scope 68 | # env['warden'].clear_strategies_cache! 69 | # 70 | # # Clear all strategies for the :admin scope 71 | # env['warden'].clear_strategies_cache!(:scope => :admin) 72 | # 73 | # # Clear password strategy for the :admin scope 74 | # env['warden'].clear_strategies_cache!(:password, :scope => :admin) 75 | # 76 | # :api: public 77 | def clear_strategies_cache!(*args) 78 | scope, _opts = _retrieve_scope_and_opts(args) 79 | 80 | @winning_strategies.delete(scope) 81 | @strategies[scope].each do |k, v| 82 | v.clear! if args.empty? || args.include?(k) 83 | end 84 | end 85 | 86 | # Locks the proxy so new users cannot authenticate during the 87 | # request lifecycle. This is useful when the request cannot 88 | # be verified (for example, using a CSRF verification token). 89 | # Notice that already authenticated users are kept as so. 90 | # 91 | # :api: public 92 | def lock! 93 | @locked = true 94 | end 95 | 96 | # Run the authentication strategies for the given strategies. 97 | # If there is already a user logged in for a given scope, the strategies are not run 98 | # This does not halt the flow of control and is a passive attempt to authenticate only 99 | # When scope is not specified, the default_scope is assumed. 100 | # 101 | # Parameters: 102 | # args - a list of symbols (labels) that name the strategies to attempt 103 | # opts - an options hash that contains the :scope of the user to check 104 | # 105 | # Example: 106 | # env['warden'].authenticate(:password, :basic, :scope => :sudo) 107 | # 108 | # :api: public 109 | def authenticate(*args) 110 | user, _opts = _perform_authentication(*args) 111 | user 112 | end 113 | 114 | # Same API as authenticate, but returns a boolean instead of a user. 115 | # The difference between this method (authenticate?) and authenticated? 116 | # is that the former will run strategies if the user has not yet been 117 | # authenticated, and the second relies on already performed ones. 118 | # :api: public 119 | def authenticate?(*args) 120 | result = !!authenticate(*args) 121 | yield if result && block_given? 122 | result 123 | end 124 | 125 | # The same as +authenticate+ except on failure it will throw a :warden symbol causing the request to be halted 126 | # and rendered through the +failure_app+ 127 | # 128 | # Example 129 | # env['warden'].authenticate!(:password, :scope => :publisher) # throws if it cannot authenticate 130 | # 131 | # :api: public 132 | def authenticate!(*args) 133 | user, opts = _perform_authentication(*args) 134 | throw(:warden, opts) unless user 135 | user 136 | end 137 | 138 | # Check to see if there is an authenticated user for the given scope. 139 | # This brings the user from the session, but does not run strategies before doing so. 140 | # If you want strategies to be run, please check authenticate?. 141 | # 142 | # Parameters: 143 | # scope - the scope to check for authentication. Defaults to default_scope 144 | # 145 | # Example: 146 | # env['warden'].authenticated?(:admin) 147 | # 148 | # :api: public 149 | def authenticated?(scope = @config.default_scope) 150 | result = !!user(scope) 151 | yield if block_given? && result 152 | result 153 | end 154 | 155 | # Same API as authenticated?, but returns false when authenticated. 156 | # :api: public 157 | def unauthenticated?(scope = @config.default_scope) 158 | result = !authenticated?(scope) 159 | yield if block_given? && result 160 | result 161 | end 162 | 163 | # Manually set the user into the session and auth proxy 164 | # 165 | # Parameters: 166 | # user - An object that has been setup to serialize into and out of the session. 167 | # opts - An options hash. Use the :scope option to set the scope of the user, set the :store option to false to skip serializing into the session, set the :run_callbacks to false to skip running the callbacks (the default is true). 168 | # 169 | # :api: public 170 | def set_user(user, opts = {}) 171 | scope = (opts[:scope] ||= @config.default_scope) 172 | 173 | # Get the default options from the master configuration for the given scope 174 | opts = (@config[:scope_defaults][scope] || {}).merge(opts) 175 | opts[:event] ||= :set_user 176 | @users[scope] = user 177 | 178 | if opts[:store] != false && opts[:event] != :fetch 179 | options = env[ENV_SESSION_OPTIONS] 180 | if options 181 | if options.frozen? 182 | env[ENV_SESSION_OPTIONS] = options.merge(:renew => true).freeze 183 | else 184 | options[:renew] = true 185 | end 186 | end 187 | session_serializer.store(user, scope) 188 | end 189 | 190 | run_callbacks = opts.fetch(:run_callbacks, true) 191 | manager._run_callbacks(:after_set_user, user, self, opts) if run_callbacks 192 | 193 | @users[scope] 194 | end 195 | 196 | # Provides access to the user object in a given scope for a request. 197 | # Will be nil if not logged in. Please notice that this method does not 198 | # perform strategies. 199 | # 200 | # Example: 201 | # # without scope (default user) 202 | # env['warden'].user 203 | # 204 | # # with scope 205 | # env['warden'].user(:admin) 206 | # 207 | # # as a Hash 208 | # env['warden'].user(:scope => :admin) 209 | # 210 | # # with default scope and run_callbacks option 211 | # env['warden'].user(:run_callbacks => false) 212 | # 213 | # # with a scope and run_callbacks option 214 | # env['warden'].user(:scope => :admin, :run_callbacks => true) 215 | # 216 | # :api: public 217 | def user(argument = {}) 218 | opts = argument.is_a?(Hash) ? argument : { :scope => argument } 219 | scope = (opts[:scope] ||= @config.default_scope) 220 | 221 | if @users.has_key?(scope) 222 | @users[scope] 223 | else 224 | unless user = session_serializer.fetch(scope) 225 | run_callbacks = opts.fetch(:run_callbacks, true) 226 | manager._run_callbacks(:after_failed_fetch, user, self, :scope => scope) if run_callbacks 227 | end 228 | 229 | @users[scope] = user ? set_user(user, opts.merge(:event => :fetch)) : nil 230 | end 231 | end 232 | 233 | # Provides a scoped session data for authenticated users. 234 | # Warden manages clearing out this data when a user logs out 235 | # 236 | # Example 237 | # # default scope 238 | # env['warden'].session[:foo] = "bar" 239 | # 240 | # # :sudo scope 241 | # env['warden'].session(:sudo)[:foo] = "bar" 242 | # 243 | # :api: public 244 | def session(scope = @config.default_scope) 245 | raise NotAuthenticated, "#{scope.inspect} user is not logged in" unless authenticated?(scope) 246 | raw_session["warden.user.#{scope}.session"] ||= {} 247 | end 248 | 249 | # Provides logout functionality. 250 | # The logout also manages any authenticated data storage and clears it when a user logs out. 251 | # 252 | # Parameters: 253 | # scopes - a list of scopes to logout 254 | # 255 | # Example: 256 | # # Logout everyone and clear the session 257 | # env['warden'].logout 258 | # 259 | # # Logout the default user but leave the rest of the session alone 260 | # env['warden'].logout(:default) 261 | # 262 | # # Logout the :publisher and :admin user 263 | # env['warden'].logout(:publisher, :admin) 264 | # 265 | # :api: public 266 | def logout(*scopes) 267 | if scopes.empty? 268 | scopes = @users.keys 269 | reset_session = true 270 | end 271 | 272 | scopes.each do |scope| 273 | user = @users.delete(scope) 274 | manager._run_callbacks(:before_logout, user, self, :scope => scope) 275 | 276 | raw_session.delete("warden.user.#{scope}.session") unless raw_session.nil? 277 | session_serializer.delete(scope, user) 278 | end 279 | 280 | reset_session! if reset_session 281 | end 282 | 283 | # proxy methods through to the winning strategy 284 | # :api: private 285 | def result # :nodoc: 286 | winning_strategy && winning_strategy.result 287 | end 288 | 289 | # Proxy through to the authentication strategy to find out the message that was generated. 290 | # :api: public 291 | def message 292 | winning_strategy && winning_strategy.message 293 | end 294 | 295 | # Provides a way to return a 401 without warden deferring to the failure app 296 | # The result is a direct passthrough of your own response 297 | # :api: public 298 | def custom_failure! 299 | @custom_failure = true 300 | end 301 | 302 | # Check to see if the custom failure flag has been set 303 | # :api: public 304 | def custom_failure? 305 | if instance_variable_defined?(:@custom_failure) 306 | !!@custom_failure 307 | else 308 | false 309 | end 310 | end 311 | 312 | # Check to see if this is an asset request 313 | # :api: public 314 | def asset_request? 315 | ::Warden::asset_paths.any? { |r| env['PATH_INFO'].to_s.match(r) } 316 | end 317 | 318 | def inspect(*args) 319 | "Warden::Proxy:#{object_id} @config=#{@config.inspect}" 320 | end 321 | 322 | def to_s(*args) 323 | inspect(*args) 324 | end 325 | 326 | private 327 | 328 | def _perform_authentication(*args) 329 | scope, opts = _retrieve_scope_and_opts(args) 330 | user = nil 331 | 332 | # Look for an existing user in the session for this scope. 333 | # If there was no user in the session, see if we can get one from the request. 334 | return user, opts if user = user(opts.merge(:scope => scope)) 335 | _run_strategies_for(scope, args) 336 | 337 | if winning_strategy && winning_strategy.successful? 338 | opts[:store] = opts.fetch(:store, winning_strategy.store?) 339 | set_user(winning_strategy.user, opts.merge!(:event => :authentication)) 340 | end 341 | 342 | [@users[scope], opts] 343 | end 344 | 345 | def _retrieve_scope_and_opts(args) #:nodoc: 346 | opts = args.last.is_a?(Hash) ? args.pop : {} 347 | scope = opts[:scope] || @config.default_scope 348 | opts = (@config[:scope_defaults][scope] || {}).merge(opts) 349 | [scope, opts] 350 | end 351 | 352 | # Run the strategies for a given scope 353 | def _run_strategies_for(scope, args) #:nodoc: 354 | self.winning_strategy = @winning_strategies[scope] 355 | return if winning_strategy && winning_strategy.halted? 356 | 357 | # Do not run any strategy if locked 358 | return if @locked 359 | 360 | if args.empty? 361 | defaults = @config[:default_strategies] 362 | strategies = defaults[scope] || defaults[:_all] 363 | end 364 | 365 | (strategies || args).each do |name| 366 | strategy = _fetch_strategy(name, scope) 367 | next unless strategy && !strategy.performed? && strategy.valid? 368 | catch(:warden) do 369 | _update_winning_strategy(strategy, scope) 370 | end 371 | 372 | strategy._run! 373 | _update_winning_strategy(strategy, scope) 374 | break if strategy.halted? 375 | end 376 | end 377 | 378 | # Fetches strategies and keep them in a hash cache. 379 | def _fetch_strategy(name, scope) 380 | @strategies[scope][name] ||= if klass = Warden::Strategies[name] 381 | klass.new(@env, scope) 382 | elsif @config.silence_missing_strategies? 383 | nil 384 | else 385 | raise "Invalid strategy #{name}" 386 | end 387 | end 388 | 389 | # Updates the winning strategy for a given scope 390 | def _update_winning_strategy(strategy, scope) 391 | self.winning_strategy = @winning_strategies[scope] = strategy 392 | end 393 | end # Proxy 394 | 395 | end # Warden 396 | -------------------------------------------------------------------------------- /spec/warden/hooks_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | RSpec.describe "standard authentication hooks" do 4 | describe "after_set_user" do 5 | before(:each) do 6 | RAM = Warden::Manager unless defined?(RAM) 7 | RAM._after_set_user.clear 8 | end 9 | 10 | after(:each) do 11 | RAM._after_set_user.clear 12 | end 13 | 14 | it "should allow me to add an after_set_user hook" do 15 | RAM.after_set_user do |user, auth, opts| 16 | "boo" 17 | end 18 | expect(RAM._after_set_user.length).to eq(1) 19 | end 20 | 21 | it "should allow me to add multiple after_set_user hooks" do 22 | RAM.after_set_user{|user, auth, opts| "foo"} 23 | RAM.after_set_user{|u,a| "bar"} 24 | expect(RAM._after_set_user.length).to eq(2) 25 | end 26 | 27 | it "should run each after_set_user hook after the user is set" do 28 | RAM.after_set_user{|u,a,o| a.env['warden.spec.hook.foo'] = "run foo"} 29 | RAM.after_set_user{|u,a,o| a.env['warden.spec.hook.bar'] = "run bar"} 30 | RAM.after_set_user{|u,a,o| a.logout} 31 | app = lambda do |e| 32 | e['warden'].set_user("foo") 33 | valid_response 34 | end 35 | env = env_with_params 36 | setup_rack(app).call(env) 37 | expect(env['warden'].user).to be_nil 38 | expect(env['warden.spec.hook.foo']).to eq("run foo") 39 | expect(env['warden.spec.hook.bar']).to eq("run bar") 40 | end 41 | 42 | it "should not run the event specified with except" do 43 | RAM.after_set_user(:except => :set_user){|u,a,o| fail} 44 | app = lambda do |e| 45 | e['warden'].set_user("foo") 46 | valid_response 47 | end 48 | env = env_with_params 49 | setup_rack(app).call(env) 50 | end 51 | 52 | it "should only run the event specified with only" do 53 | RAM.after_set_user(:only => :set_user){|u,a,o| fail} 54 | app = lambda do |e| 55 | e['warden'].authenticate(:pass) 56 | valid_response 57 | end 58 | env = env_with_params 59 | setup_rack(app).call(env) 60 | end 61 | 62 | it "should run filters in the given order" do 63 | RAM.after_set_user{|u,a,o| a.env['warden.spec.order'] << 2} 64 | RAM.after_set_user{|u,a,o| a.env['warden.spec.order'] << 3} 65 | RAM.prepend_after_set_user{|u,a,o| a.env['warden.spec.order'] << 1} 66 | app = lambda do |e| 67 | e['warden.spec.order'] = [] 68 | e['warden'].set_user("foo") 69 | valid_response 70 | end 71 | env = env_with_params 72 | setup_rack(app).call(env) 73 | expect(env['warden.spec.order']).to eq([1,2,3]) 74 | end 75 | 76 | context "after_authentication" do 77 | it "should be a wrapper to after_set_user behavior" do 78 | RAM.after_authentication{|u,a,o| a.env['warden.spec.hook.baz'] = "run baz"} 79 | RAM.after_authentication{|u,a,o| a.env['warden.spec.hook.paz'] = "run paz"} 80 | RAM.after_authentication{|u,a,o| expect(o[:event]).to eq(:authentication) } 81 | app = lambda do |e| 82 | e['warden'].authenticate(:pass) 83 | valid_response 84 | end 85 | env = env_with_params 86 | setup_rack(app).call(env) 87 | expect(env['warden.spec.hook.baz']).to eq('run baz') 88 | expect(env['warden.spec.hook.paz']).to eq('run paz') 89 | end 90 | 91 | it "should not be invoked on default after_set_user scenario" do 92 | RAM.after_authentication{|u,a,o| fail} 93 | app = lambda do |e| 94 | e['warden'].set_user("foo") 95 | valid_response 96 | end 97 | env = env_with_params 98 | setup_rack(app).call(env) 99 | end 100 | 101 | it "should run filters in the given order" do 102 | RAM.after_authentication{|u,a,o| a.env['warden.spec.order'] << 2} 103 | RAM.after_authentication{|u,a,o| a.env['warden.spec.order'] << 3} 104 | RAM.prepend_after_authentication{|u,a,o| a.env['warden.spec.order'] << 1} 105 | app = lambda do |e| 106 | e['warden.spec.order'] = [] 107 | e['warden'].authenticate(:pass) 108 | valid_response 109 | end 110 | env = env_with_params 111 | setup_rack(app).call(env) 112 | expect(env['warden.spec.order']).to eq([1,2,3]) 113 | end 114 | 115 | it "should allow me to log out a user in an after_set_user block" do 116 | RAM.after_set_user{|u,a,o| a.logout} 117 | 118 | app = lambda do |e| 119 | e['warden'].authenticate(:pass) 120 | valid_response 121 | end 122 | env = env_with_params 123 | setup_rack(app).call(env) 124 | expect(env['warden']).not_to be_authenticated 125 | end 126 | end 127 | 128 | context "after_fetch" do 129 | it "should be a wrapper to after_set_user behavior" do 130 | RAM.after_fetch{|u,a,o| a.env['warden.spec.hook.baz'] = "run baz"} 131 | RAM.after_fetch{|u,a,o| a.env['warden.spec.hook.paz'] = "run paz"} 132 | RAM.after_fetch{|u,a,o| expect(o[:event]).to eq(:fetch) } 133 | env = env_with_params 134 | setup_rack(lambda { |e| valid_response }).call(env) 135 | env['rack.session']['warden.user.default.key'] = "Foo" 136 | expect(env['warden'].user).to eq("Foo") 137 | expect(env['warden.spec.hook.baz']).to eq('run baz') 138 | expect(env['warden.spec.hook.paz']).to eq('run paz') 139 | end 140 | 141 | it "should not be invoked on default after_set_user scenario" do 142 | RAM.after_fetch{|u,a,o| fail} 143 | app = lambda do |e| 144 | e['warden'].set_user("foo") 145 | valid_response 146 | end 147 | env = env_with_params 148 | setup_rack(app).call(env) 149 | end 150 | 151 | it "should not be invoked if fetched user is nil" do 152 | RAM.after_fetch{|u,a,o| fail} 153 | env = env_with_params 154 | setup_rack(lambda { |e| valid_response }).call(env) 155 | env['rack.session']['warden.user.default.key'] = nil 156 | expect(env['warden'].user).to be_nil 157 | end 158 | 159 | it "should run filters in the given order" do 160 | RAM.after_fetch{|u,a,o| a.env['warden.spec.order'] << 2} 161 | RAM.after_fetch{|u,a,o| a.env['warden.spec.order'] << 3} 162 | RAM.prepend_after_fetch{|u,a,o| a.env['warden.spec.order'] << 1} 163 | app = lambda do |e| 164 | e['warden.spec.order'] = [] 165 | e['rack.session']['warden.user.default.key'] = "Foo" 166 | e['warden'].user 167 | valid_response 168 | end 169 | env = env_with_params 170 | setup_rack(app).call(env) 171 | expect(env['warden.spec.order']).to eq([1,2,3]) 172 | end 173 | end 174 | end 175 | 176 | 177 | describe "after_failed_fetch" do 178 | before(:each) do 179 | RAM = Warden::Manager unless defined?(RAM) 180 | RAM._after_failed_fetch.clear 181 | end 182 | 183 | after(:each) do 184 | RAM._after_failed_fetch.clear 185 | end 186 | 187 | it "should not be called when user is fetched" do 188 | RAM.after_failed_fetch{|u,a,o| fail } 189 | env = env_with_params 190 | setup_rack(lambda { |e| valid_response }).call(env) 191 | env['rack.session']['warden.user.default.key'] = "Foo" 192 | expect(env['warden'].user).to eq("Foo") 193 | end 194 | 195 | it "should be called if fetched user is nil" do 196 | calls = 0 197 | RAM.after_failed_fetch{|u,a,o| calls += 1 } 198 | env = env_with_params 199 | setup_rack(lambda { |e| valid_response }).call(env) 200 | expect(env['warden'].user).to be_nil 201 | expect(calls).to eq(1) 202 | end 203 | end 204 | 205 | describe "before_failure" do 206 | before(:each) do 207 | RAM = Warden::Manager unless defined?(RAM) 208 | RAM._before_failure.clear 209 | end 210 | 211 | after(:each) do 212 | RAM._before_failure.clear 213 | end 214 | 215 | it "should allow me to add a before_failure hook" do 216 | RAM.before_failure{|env, opts| "foo"} 217 | expect(RAM._before_failure.length).to eq(1) 218 | end 219 | 220 | it "should allow me to add multiple before_failure hooks" do 221 | RAM.before_failure{|env, opts| "foo"} 222 | RAM.before_failure{|env, opts| "bar"} 223 | expect(RAM._before_failure.length).to eq(2) 224 | end 225 | 226 | it "should run each before_failure hooks before failing" do 227 | RAM.before_failure{|e,o| e['warden.spec.before_failure.foo'] = "foo"} 228 | RAM.before_failure{|e,o| e['warden.spec.before_failure.bar'] = "bar"} 229 | app = lambda{|e| e['warden'].authenticate!(:failz); valid_response} 230 | env = env_with_params 231 | setup_rack(app).call(env) 232 | expect(env['warden.spec.before_failure.foo']).to eq("foo") 233 | expect(env['warden.spec.before_failure.bar']).to eq("bar") 234 | end 235 | 236 | it "should run filters in the given order" do 237 | RAM.before_failure{|e,o| e['warden.spec.order'] << 2} 238 | RAM.before_failure{|e,o| e['warden.spec.order'] << 3} 239 | RAM.prepend_before_failure{|e,o| e['warden.spec.order'] << 1} 240 | app = lambda do |e| 241 | e['warden.spec.order'] = [] 242 | e['warden'].authenticate!(:failz) 243 | valid_response 244 | end 245 | env = env_with_params 246 | setup_rack(app).call(env) 247 | expect(env['warden.spec.order']).to eq([1,2,3]) 248 | end 249 | end 250 | 251 | describe "before_logout" do 252 | before(:each) do 253 | RAM = Warden::Manager unless defined?(RAM) 254 | RAM._before_logout.clear 255 | end 256 | 257 | after(:each) do 258 | RAM._before_logout.clear 259 | end 260 | 261 | it "should allow me to add an before_logout hook" do 262 | RAM.before_logout{|user, auth, scopes| "foo"} 263 | expect(RAM._before_logout.length).to eq(1) 264 | end 265 | 266 | it "should allow me to add multiple after_authentication hooks" do 267 | RAM.before_logout{|u,a,o| "bar"} 268 | RAM.before_logout{|u,a,o| "baz"} 269 | expect(RAM._before_logout.length).to eq(2) 270 | end 271 | 272 | it "should run each before_logout hook before logout is run" do 273 | RAM.before_logout{|u,a,o| a.env['warden.spec.hook.lorem'] = "run lorem"} 274 | RAM.before_logout{|u,a,o| a.env['warden.spec.hook.ipsum'] = "run ipsum"} 275 | app = lambda{|e| e['warden'].authenticate(:pass); valid_response} 276 | env = env_with_params 277 | setup_rack(app).call(env) 278 | env['warden'].logout 279 | expect(env['warden.spec.hook.lorem']).to eq('run lorem') 280 | expect(env['warden.spec.hook.ipsum']).to eq('run ipsum') 281 | end 282 | 283 | it "should run before_logout hook for a specified scope" do 284 | RAM.before_logout(:scope => :scope1){|u,a,o| a.env["warden.spec.hook.a"] << :scope1 } 285 | RAM.before_logout(:scope => [:scope2]){|u,a,o| a.env["warden.spec.hook.b"] << :scope2 } 286 | 287 | app = lambda do |e| 288 | e['warden'].authenticate(:pass, :scope => :scope1) 289 | e['warden'].authenticate(:pass, :scope => :scope2) 290 | valid_response 291 | end 292 | env = env_with_params 293 | env["warden.spec.hook.a"] ||= [] 294 | env["warden.spec.hook.b"] ||= [] 295 | setup_rack(app).call(env) 296 | 297 | env['warden'].logout(:scope1) 298 | expect(env['warden.spec.hook.a']).to eq([:scope1]) 299 | expect(env['warden.spec.hook.b']).to eq([]) 300 | 301 | env['warden'].logout(:scope2) 302 | expect(env['warden.spec.hook.a']).to eq([:scope1]) 303 | expect(env['warden.spec.hook.b']).to eq([:scope2]) 304 | end 305 | 306 | it "should run filters in the given order" do 307 | RAM.before_logout{|u,a,o| a.env['warden.spec.order'] << 2} 308 | RAM.before_logout{|u,a,o| a.env['warden.spec.order'] << 3} 309 | RAM.prepend_before_logout{|u,a,o| a.env['warden.spec.order'] << 1} 310 | app = lambda do |e| 311 | e['warden.spec.order'] = [] 312 | e['warden'].authenticate(:pass) 313 | e['warden'].logout 314 | valid_response 315 | end 316 | env = env_with_params 317 | setup_rack(app).call(env) 318 | expect(env['warden.spec.order']).to eq([1,2,3]) 319 | end 320 | end 321 | 322 | describe "on_request" do 323 | before(:each) do 324 | RAM = Warden::Manager unless defined?(RAM) 325 | @old_on_request = RAM._on_request.dup 326 | RAM._on_request.clear 327 | end 328 | 329 | after(:each) do 330 | RAM._on_request.clear 331 | RAM._on_request.replace(@old_on_request) 332 | end 333 | 334 | it "should allow me to add an on_request hook" do 335 | RAM.on_request{|proxy| "foo"} 336 | expect(RAM._on_request.length).to eq(1) 337 | end 338 | 339 | it "should allow me to add multiple on_request hooks" do 340 | RAM.on_request{|proxy| "foo"} 341 | RAM.on_request{|proxy| "bar"} 342 | expect(RAM._on_request.length).to eq(2) 343 | end 344 | 345 | it "should run each on_request hooks when initializing" do 346 | RAM.on_request{|proxy| proxy.env['warden.spec.on_request.foo'] = "foo"} 347 | RAM.on_request{|proxy| proxy.env['warden.spec.on_request.bar'] = "bar"} 348 | app = lambda{|e| valid_response} 349 | env = env_with_params 350 | setup_rack(app).call(env) 351 | expect(env['warden.spec.on_request.foo']).to eq("foo") 352 | expect(env['warden.spec.on_request.bar']).to eq("bar") 353 | end 354 | 355 | it "should run filters in the given order" do 356 | RAM.on_request{|proxy| proxy.env['warden.spec.order'] << 2} 357 | RAM.on_request{|proxy| proxy.env['warden.spec.order'] << 3} 358 | RAM.prepend_on_request{|proxy| proxy.env['warden.spec.order'] << 1} 359 | app = lambda do |e| 360 | valid_response 361 | end 362 | env = Rack::MockRequest.env_for("/", "warden.spec.order" => []) 363 | setup_rack(app).call(env) 364 | expect(env['warden.spec.order']).to eq([1,2,3]) 365 | end 366 | 367 | it "should have the proxy on env in on_request" do 368 | warden = nil 369 | RAM.on_request{|proxy| warden = proxy.env['warden']} 370 | app = lambda{|e| valid_response} 371 | env = env_with_params 372 | setup_rack(app).call(env) 373 | expect(warden).to eq(env['warden']) 374 | end 375 | 376 | it "should be able to throw in on_request" do 377 | RAM.on_request{|proxy| throw :warden} 378 | app = lambda{|e| valid_response} 379 | env = env_with_params 380 | result = setup_rack(app).call(env) 381 | expect(result.first).to eq(401) 382 | end 383 | end 384 | end 385 | -------------------------------------------------------------------------------- /spec/warden/proxy_spec.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | # frozen_string_literal: true 3 | 4 | require 'rack/session' 5 | 6 | RSpec.describe Warden::Proxy do 7 | before(:all) do 8 | load_strategies 9 | end 10 | 11 | before(:each) do 12 | @basic_app = lambda{|_env| [200,{'Content-Type' => 'text/plain'},'OK']} 13 | @authd_app = lambda do |e| 14 | e['warden'].authenticate 15 | if e['warden'].authenticated? 16 | [200,{'Content-Type' => 'text/plain'},"OK"] 17 | else 18 | [401,{'Content-Type' => 'text/plain'},"You Fail"] 19 | end 20 | end 21 | @env = env_with_params("/") 22 | end # before(:each) 23 | 24 | describe "authentication" do 25 | 26 | it "should not check the authentication if it is not checked" do 27 | app = setup_rack(@basic_app) 28 | expect(app.call(@env).first).to eq(200) 29 | end 30 | 31 | it "should check the authentication if it is explicitly checked" do 32 | app = setup_rack(@authd_app) 33 | expect(app.call(@env).first).to eq(401) 34 | end 35 | 36 | it "should not allow the request if incorrect conditions are supplied" do 37 | env = env_with_params("/", :foo => "bar") 38 | app = setup_rack(@authd_app) 39 | response = app.call(env) 40 | expect(response.first).to eq(401) 41 | end 42 | 43 | it "should allow the request if the correct conditions are supplied" do 44 | env = env_with_params("/", :username => "fred", :password => "sekrit") 45 | app = setup_rack(@authd_app) 46 | resp = app.call(env) 47 | expect(resp.first).to eq(200) 48 | end 49 | 50 | it "should allow authentication in my application" do 51 | env = env_with_params('/', :username => "fred", :password => "sekrit") 52 | app = lambda do |env| 53 | env['warden'].authenticate 54 | expect(env['warden']).to be_authenticated 55 | expect(env['warden.spec.strategies']).to eq([:password]) 56 | valid_response 57 | end 58 | setup_rack(app).call(env) 59 | end 60 | 61 | it "should allow me to select which strategies I use in my application" do 62 | env = env_with_params("/", :foo => "bar") 63 | app = lambda do |env| 64 | env['warden'].authenticate(:failz) 65 | expect(env['warden']).not_to be_authenticated 66 | expect(env['warden.spec.strategies']).to eq([:failz]) 67 | valid_response 68 | end 69 | setup_rack(app).call(env) 70 | end 71 | 72 | it "should raise error on missing strategies" do 73 | app = lambda do |env| 74 | env['warden'].authenticate(:unknown) 75 | end 76 | expect { 77 | setup_rack(app).call(@env) 78 | }.to raise_error(RuntimeError, "Invalid strategy unknown") 79 | end 80 | 81 | it "should raise error if the strategy failed" do 82 | app = lambda do |env| 83 | env['warden'].authenticate(:fail_with_user) 84 | expect(env['warden'].user).to be_nil 85 | valid_response 86 | end 87 | setup_rack(app).call(@env) 88 | end 89 | 90 | it "should not raise error on missing strategies if silencing" do 91 | app = lambda do |env| 92 | env['warden'].authenticate 93 | valid_response 94 | end 95 | expect { 96 | setup_rack(app, :silence_missing_strategies => true, :default_strategies => [:unknown]).call(@env) 97 | }.not_to raise_error 98 | end 99 | 100 | it "should allow me to get access to the user at warden.user." do 101 | app = lambda do |env| 102 | env['warden'].authenticate(:pass) 103 | expect(env['warden']).to be_authenticated 104 | expect(env['warden.spec.strategies']).to eq([:pass]) 105 | valid_response 106 | end 107 | setup_rack(app).call(@env) 108 | end 109 | 110 | it "should run strategies when authenticate? is asked" do 111 | app = lambda do |env| 112 | expect(env['warden']).not_to be_authenticated 113 | env['warden'].authenticate?(:pass) 114 | expect(env['warden']).to be_authenticated 115 | expect(env['warden.spec.strategies']).to eq([:pass]) 116 | valid_response 117 | end 118 | setup_rack(app).call(@env) 119 | end 120 | 121 | it "should properly send the scope to the strategy" do 122 | app = lambda do |env| 123 | env['warden'].authenticate(:pass, :scope => :failz) 124 | expect(env['warden']).not_to be_authenticated 125 | expect(env['warden.spec.strategies']).to eq([:pass]) 126 | valid_response 127 | end 128 | setup_rack(app).call(@env) 129 | end 130 | 131 | it "should try multiple authentication strategies" do 132 | app = lambda do |env| 133 | env['warden'].authenticate(:password,:pass) 134 | expect(env['warden']).to be_authenticated 135 | expect(env['warden.spec.strategies']).to eq([:password, :pass]) 136 | valid_response 137 | end 138 | setup_rack(app).call(@env) 139 | end 140 | 141 | it "should look for an active user in the session with authenticate" do 142 | app = lambda do |env| 143 | env['rack.session']["warden.user.default.key"] = "foo as a user" 144 | env['warden'].authenticate(:pass) 145 | valid_response 146 | end 147 | setup_rack(app).call(@env) 148 | expect(@env['warden'].user).to eq("foo as a user") 149 | end 150 | 151 | it "should look for an active user in the session with authenticate?" do 152 | app = lambda do |env| 153 | env['rack.session']['warden.user.foo_scope.key'] = "a foo user" 154 | env['warden'].authenticate?(:pass, :scope => :foo_scope) 155 | valid_response 156 | end 157 | setup_rack(app).call(@env) 158 | expect(@env['warden'].user(:foo_scope)).to eq("a foo user") 159 | end 160 | 161 | it "should look for an active user in the session with authenticate!" do 162 | app = lambda do |env| 163 | env['rack.session']['warden.user.foo_scope.key'] = "a foo user" 164 | env['warden'].authenticate!(:pass, :scope => :foo_scope) 165 | valid_response 166 | end 167 | setup_rack(app).call(@env) 168 | expect(@env['warden'].user(:foo_scope)).to eq("a foo user") 169 | end 170 | 171 | it "should throw an error when authenticate!" do 172 | app = lambda do |env| 173 | env['warden'].authenticate!(:pass, :scope => :failz) 174 | raise "OMG" 175 | end 176 | setup_rack(app).call(@env) 177 | end 178 | 179 | it "should login 2 different users from the session" do 180 | app = lambda do |env| 181 | env['rack.session']['warden.user.foo.key'] = 'foo user' 182 | env['rack.session']['warden.user.bar.key'] = 'bar user' 183 | expect(env['warden']).to be_authenticated(:foo) 184 | expect(env['warden']).to be_authenticated(:bar) 185 | expect(env['warden']).not_to be_authenticated # default scope 186 | valid_response 187 | end 188 | setup_rack(app).call(@env) 189 | expect(@env['warden'].user(:foo)).to eq('foo user') 190 | expect(@env['warden'].user(:bar)).to eq('bar user') 191 | expect(@env['warden'].user).to be_nil 192 | end 193 | 194 | it "should not authenticate other scopes just because the first is authenticated" do 195 | app = lambda do |env| 196 | env['warden'].authenticate(:pass, :scope => :foo) 197 | env['warden'].authenticate(:invalid, :scope => :bar) 198 | expect(env['warden']).to be_authenticated(:foo) 199 | expect(env['warden']).not_to be_authenticated(:bar) 200 | valid_response 201 | end 202 | setup_rack(app).call(@env) 203 | end 204 | 205 | SID_REGEXP = /rack\.session=([^;]*);/ 206 | 207 | it "should renew session when user is set" do 208 | app = lambda do |env| 209 | env["rack.session"]["counter"] ||= 0 210 | env["rack.session"]["counter"] += 1 211 | if env["warden.on"] 212 | env["warden"].authenticate!(:pass) 213 | expect(env["warden"]).to be_authenticated 214 | end 215 | valid_response 216 | end 217 | 218 | # Setup a rack app with Pool session. 219 | app = setup_rack(app, :session => Rack::Session::Pool).to_app 220 | response = app.call(@env) 221 | expect(@env["rack.session"]["counter"]).to eq(1) 222 | 223 | # Ensure a cookie was given back 224 | cookie = response[1]["Set-Cookie"] 225 | expect(cookie).not_to be_nil 226 | 227 | # Ensure a session id was given 228 | sid = cookie.match(SID_REGEXP)[1] 229 | expect(sid).not_to be_nil 230 | 231 | # Do another request, giving a cookie but turning on warden authentication 232 | env = env_with_params("/", {}, 'rack.session' => @env['rack.session'], "HTTP_COOKIE" => cookie, "warden.on" => true) 233 | response = app.call(env) 234 | expect(env["rack.session"]["counter"]).to be(2) 235 | 236 | # Regardless of rack version, a cookie should be sent back 237 | new_cookie = response[1]["Set-Cookie"] 238 | expect(new_cookie).not_to be_nil 239 | 240 | # And the session id in this cookie should not be the same as the previous one 241 | new_sid = new_cookie.match(SID_REGEXP)[1] 242 | expect(new_sid).not_to be_nil 243 | expect(new_sid).not_to eq(sid) 244 | end 245 | 246 | it "should not renew session when user is fetch" do 247 | app = lambda do |env| 248 | env["rack.session"]["counter"] ||= 0 249 | env["rack.session"]["counter"] += 1 250 | env["warden"].authenticate!(:pass) 251 | expect(env["warden"]).to be_authenticated 252 | valid_response 253 | end 254 | 255 | # Setup a rack app with Pool session. 256 | app = setup_rack(app, :session => Rack::Session::Pool).to_app 257 | response = app.call(@env) 258 | expect(@env["rack.session"]["counter"]).to eq(1) 259 | 260 | # Ensure a cookie was given back 261 | cookie = response[1]["Set-Cookie"] 262 | expect(cookie).not_to be_nil 263 | 264 | # Ensure a session id was given 265 | sid = cookie.match(SID_REGEXP)[1] 266 | expect(sid).not_to be_nil 267 | 268 | # Do another request, passing the cookie. The user should be fetched from cookie. 269 | env = env_with_params("/", {}, "HTTP_COOKIE" => cookie) 270 | response = app.call(env) 271 | expect(env["rack.session"]["counter"]).to eq(2) 272 | 273 | # Depending on rack version, a cookie will be returned with the 274 | # same session id or no cookie is given back (becase it did not change). 275 | # If we don't get any of these two behaviors, raise an error. 276 | # Regardless of rack version, a cookie should be sent back 277 | new_cookie = response[1]["Set-Cookie"] 278 | if new_cookie && new_cookie.match(SID_REGEXP)[1] != sid 279 | raise "Expected a cookie to not be sent or session id to match" 280 | end 281 | end 282 | end 283 | 284 | describe "authentication cache" do 285 | it "should run strategies just once for a given scope" do 286 | app = lambda do |env| 287 | env['warden'].authenticate(:password, :pass, :scope => :failz) 288 | expect(env['warden']).not_to be_authenticated(:failz) 289 | env['warden'].authenticate(:password, :pass, :scope => :failz) 290 | expect(env['warden']).not_to be_authenticated(:failz) 291 | expect(env['warden.spec.strategies']).to eq([:password, :pass]) 292 | valid_response 293 | end 294 | setup_rack(app).call(@env) 295 | end 296 | 297 | it "should run strategies for a given scope several times if cache is cleaned" do 298 | app = lambda do |env| 299 | env['warden'].authenticate(:password, :pass, :scope => :failz) 300 | env['warden'].clear_strategies_cache!(:scope => :failz) 301 | env['warden'].authenticate(:password, :pass, :scope => :failz) 302 | expect(env['warden.spec.strategies']).to eq([:password, :pass, :password, :pass]) 303 | valid_response 304 | end 305 | setup_rack(app).call(@env) 306 | end 307 | 308 | it "should clear the cache for a specified strategy" do 309 | app = lambda do |env| 310 | env['warden'].authenticate(:password, :pass, :scope => :failz) 311 | env['warden'].clear_strategies_cache!(:password, :scope => :failz) 312 | env['warden'].authenticate(:password, :pass, :scope => :failz) 313 | expect(env['warden.spec.strategies']).to eq([:password, :pass, :password]) 314 | valid_response 315 | end 316 | setup_rack(app).call(@env) 317 | end 318 | 319 | it "should run the strategies several times for different scopes" do 320 | app = lambda do |env| 321 | env['warden'].authenticate(:password, :pass, :scope => :failz) 322 | expect(env['warden']).not_to be_authenticated(:failz) 323 | env['warden'].authenticate(:password, :pass) 324 | expect(env['warden']).to be_authenticated 325 | expect(env['warden.spec.strategies']).to eq([:password, :pass, :password, :pass]) 326 | valid_response 327 | end 328 | setup_rack(app).call(@env) 329 | end 330 | 331 | it "should not run strategies until cache is cleaned if latest winning strategy halted" do 332 | app = lambda do |env| 333 | env['warden'].authenticate(:failz) 334 | expect(env['warden']).not_to be_authenticated 335 | env['warden'].authenticate(:pass) 336 | expect(env['warden'].winning_strategy.message).to eq("The Fails Strategy Has Failed You") 337 | valid_response 338 | end 339 | setup_rack(app).call(@env) 340 | end 341 | 342 | it "should not store user if strategy isn't meant for permanent login" do 343 | session = Warden::SessionSerializer.new(@env) 344 | app = lambda do |env| 345 | env['warden'].authenticate(:single) 346 | expect(env['warden']).to be_authenticated 347 | expect(env['warden'].user).to eq("Valid User") 348 | expect(session).not_to be_stored(:default) 349 | valid_response 350 | end 351 | setup_rack(app).call(@env) 352 | end 353 | end 354 | 355 | describe "set user" do 356 | it "should store the user into the session" do 357 | app = lambda do |env| 358 | env['warden'].authenticate(:pass) 359 | expect(env['warden']).to be_authenticated 360 | expect(env['warden'].user).to eq("Valid User") 361 | expect(env['rack.session']["warden.user.default.key"]).to eq("Valid User") 362 | valid_response 363 | end 364 | setup_rack(app).call(@env) 365 | end 366 | 367 | it "should not store the user if the :store option is set to false" do 368 | app = lambda do |env| 369 | env['warden'].authenticate(:pass, :store => false) 370 | expect(env['warden']).to be_authenticated 371 | expect(env['warden'].user).to eq("Valid User") 372 | expect(env['rack.session']['warden.user.default.key']).to be_nil 373 | valid_response 374 | end 375 | setup_rack(app).call(@env) 376 | end 377 | 378 | it "should not throw error when no session is configured and store is false" do 379 | app = lambda do |env| 380 | env['rack.session'] = nil 381 | env['warden'].authenticate(:pass, :store => false) 382 | expect(env['warden']).to be_authenticated 383 | expect(env['warden'].user).to eq("Valid User") 384 | valid_response 385 | end 386 | setup_rack(app).call(@env) 387 | end 388 | 389 | it "should not run the callbacks when :run_callbacks is false" do 390 | app = lambda do |env| 391 | expect(env['warden'].manager).not_to receive(:_run_callbacks) 392 | env['warden'].authenticate(:run_callbacks => false, :scope => :pass) 393 | valid_response 394 | end 395 | setup_rack(app).call(@env) 396 | end 397 | 398 | it "should run the callbacks when :run_callbacks is true" do 399 | app = lambda do |env| 400 | expect(env['warden'].manager).to receive(:_run_callbacks).at_least(:once) 401 | env['warden'].authenticate(:pass) 402 | valid_response 403 | end 404 | setup_rack(app).call(@env) 405 | end 406 | 407 | it "should run the callbacks by default" do 408 | app = lambda do |env| 409 | expect(env['warden'].manager).to receive(:_run_callbacks).at_least(:once) 410 | env['warden'].authenticate(:pass) 411 | valid_response 412 | end 413 | setup_rack(app).call(@env) 414 | end 415 | 416 | it "should set renew on rack.session.options" do 417 | app = lambda do |env| 418 | env['warden'].authenticate(:pass) 419 | valid_response 420 | end 421 | 422 | @env[Warden::Proxy::ENV_SESSION_OPTIONS] = {} 423 | 424 | setup_rack(app).call(@env) 425 | 426 | expect(@env[Warden::Proxy::ENV_SESSION_OPTIONS]).to include(renew: true) 427 | expect(@env[Warden::Proxy::ENV_SESSION_OPTIONS]).to_not be_frozen 428 | end 429 | 430 | it "should not modify attempt to modify a frozen rack.session.options" do 431 | app = lambda do |env| 432 | env['warden'].authenticate(:pass) 433 | valid_response 434 | end 435 | 436 | original_options = {}.freeze 437 | @env[Warden::Proxy::ENV_SESSION_OPTIONS] = original_options 438 | 439 | setup_rack(app).call(@env) 440 | 441 | expect(original_options).to be_empty 442 | expect(@env[Warden::Proxy::ENV_SESSION_OPTIONS]).to include(renew: true) 443 | expect(@env[Warden::Proxy::ENV_SESSION_OPTIONS]).to be_frozen 444 | end 445 | end 446 | 447 | describe "lock" do 448 | it "should not run any strategy" do 449 | app = lambda do |env| 450 | env['warden'].lock! 451 | env['warden'].authenticate(:pass) 452 | expect(env['warden'].user).to be_nil 453 | valid_response 454 | end 455 | 456 | setup_rack(app).call(@env) 457 | end 458 | 459 | it "should keep already authenticated users" do 460 | app = lambda do |env| 461 | env['warden'].authenticate(:pass) 462 | env['warden'].lock! 463 | expect(env['warden'].user).not_to be_nil 464 | valid_response 465 | end 466 | 467 | setup_rack(app).call(@env) 468 | end 469 | end 470 | 471 | describe "get user" do 472 | before(:each) do 473 | @env['rack.session'] ||= {} 474 | @env['rack.session'].delete("warden.user.default.key") 475 | end 476 | 477 | it "should return nil when not logged in" do 478 | app = lambda do |env| 479 | expect(env['warden'].user).to be_nil 480 | valid_response 481 | end 482 | setup_rack(app).call(@env) 483 | end 484 | 485 | it "should not run strategies when not logged in" do 486 | app = lambda do |env| 487 | expect(env['warden'].user).to be_nil 488 | expect(env['warden.spec.strategies']).to be_nil 489 | valid_response 490 | end 491 | setup_rack(app).call(@env) 492 | end 493 | 494 | it "should cache unfound user" do 495 | expect_any_instance_of(Warden::SessionSerializer).to receive(:fetch).once 496 | app = lambda do |env| 497 | expect(env['warden'].user).to be_nil 498 | expect(env['warden'].user).to be_nil 499 | valid_response 500 | end 501 | setup_rack(app).call(@env) 502 | end 503 | 504 | describe "previously logged in" do 505 | before(:each) do 506 | @env['rack.session']['warden.user.default.key'] = "A Previous User" 507 | @env['warden.spec.strategies'] = [] 508 | end 509 | 510 | it "should take the user from the session when logged in" do 511 | app = lambda do |env| 512 | expect(env['warden'].user).to eq("A Previous User") 513 | valid_response 514 | end 515 | setup_rack(app).call(@env) 516 | end 517 | 518 | it "should cache found user" do 519 | expect_any_instance_of(Warden::SessionSerializer).to receive(:fetch).once.and_return "A Previous User" 520 | app = lambda do |env| 521 | expect(env['warden'].user).to eq("A Previous User") 522 | expect(env['warden'].user).to eq("A Previous User") 523 | valid_response 524 | end 525 | setup_rack(app).call(@env) 526 | end 527 | 528 | it "should not run strategies when the user exists in the session" do 529 | app = lambda do |env| 530 | env['warden'].authenticate!(:pass) 531 | valid_response 532 | end 533 | setup_rack(app).call(@env) 534 | expect(@env['warden.spec.strategies']).not_to include(:pass) 535 | end 536 | 537 | describe "run callback option" do 538 | it "should not call run_callbacks when we pass a :run_callback => false" do 539 | app = lambda do |env| 540 | expect(env['warden'].manager).not_to receive(:_run_callbacks) 541 | env['warden'].user(:run_callbacks => false) 542 | valid_response 543 | end 544 | setup_rack(app).call(@env) 545 | end 546 | 547 | it "should call run_callbacks when we pass a :run_callback => true" do 548 | app = lambda do |env| 549 | expect(env['warden'].manager).to receive(:_run_callbacks).at_least(:once) 550 | env['warden'].user(:run_callbacks => true) 551 | valid_response 552 | end 553 | setup_rack(app).call(@env) 554 | end 555 | 556 | it "should call run_callbacks by default" do 557 | app = lambda do |env| 558 | expect(env['warden'].manager).to receive(:_run_callbacks).at_least(:once) 559 | env['warden'].user 560 | valid_response 561 | end 562 | setup_rack(app).call(@env) 563 | end 564 | end 565 | end 566 | end 567 | 568 | describe "logout" do 569 | before(:each) do 570 | @env['rack.session'] = {"warden.user.default.key" => "default key", "warden.user.foo.key" => "foo key", :foo => "bar"} 571 | @app = lambda do |e| 572 | e['warden'].logout(e['warden.spec.which_logout']) 573 | valid_response 574 | end 575 | end 576 | 577 | it "should logout only the scoped foo user" do 578 | @app = setup_rack(@app) 579 | @env['warden.spec.which_logout'] = :foo 580 | @app.call(@env) 581 | expect(@env['rack.session']['warden.user.default.key']).to eq("default key") 582 | expect(@env['rack.session']['warden.user.foo.key']).to be_nil 583 | expect(@env['rack.session'][:foo]).to eq("bar") 584 | end 585 | 586 | it "should logout only the scoped default user" do 587 | @app = setup_rack(@app) 588 | @env['warden.spec.which_logout'] = :default 589 | @app.call(@env) 590 | expect(@env['rack.session']['warden.user.default.key']).to be_nil 591 | expect(@env['rack.session']['warden.user.foo.key']).to eq("foo key") 592 | expect(@env['rack.session'][:foo]).to eq("bar") 593 | end 594 | 595 | it "should clear the session when no argument is given to logout" do 596 | expect(@env['rack.session']).not_to be_nil 597 | app = lambda do |e| 598 | e['warden'].logout 599 | valid_response 600 | end 601 | setup_rack(app).call(@env) 602 | expect(@env['rack.session']).to be_empty 603 | end 604 | 605 | it "should not raise exception if raw_session is nil" do 606 | @app = setup_rack(@app, { nil_session: true }) 607 | @env['rack.session'] = nil 608 | @env['warden.spec.which_logout'] = :foo 609 | expect { @app.call(@env) }.to_not raise_error 610 | end 611 | 612 | it "should clear the user when logging out" do 613 | expect(@env['rack.session']).not_to be_nil 614 | app = lambda do |e| 615 | expect(e['warden'].user).not_to be_nil 616 | e['warden'].logout 617 | expect(e['warden']).not_to be_authenticated 618 | expect(e['warden'].user).to be_nil 619 | valid_response 620 | end 621 | setup_rack(app).call(@env) 622 | expect(@env['warden'].user).to be_nil 623 | end 624 | 625 | it "should clear the session data when logging out" do 626 | expect(@env['rack.session']).not_to be_nil 627 | app = lambda do |e| 628 | expect(e['warden'].user).not_to be_nil 629 | e['warden'].session[:foo] = :bar 630 | e['warden'].logout 631 | valid_response 632 | end 633 | setup_rack(app).call(@env) 634 | end 635 | 636 | it "should clear out the session by calling reset_session! so that plugins can setup their own session clearing" do 637 | expect(@env['rack.session']).not_to be_nil 638 | app = lambda do |e| 639 | expect(e['warden'].user).not_to be_nil 640 | expect(e['warden']).to receive(:reset_session!) 641 | e['warden'].logout 642 | valid_response 643 | end 644 | setup_rack(app).call(@env) 645 | end 646 | end 647 | 648 | describe "messages" do 649 | it "should allow access to the failure message" do 650 | failure = lambda do |e| 651 | [401, {"Content-Type" => "text/plain"}, [e['warden'].message]] 652 | end 653 | app = lambda do |e| 654 | e['warden'].authenticate! :failz 655 | end 656 | result = setup_rack(app, :failure_app => failure).call(@env) 657 | expect(result.last).to eq(["The Fails Strategy Has Failed You"]) 658 | end 659 | 660 | it "should allow access to the success message" do 661 | success = lambda do |e| 662 | [200, {"Content-Type" => "text/plain"}, [e['warden'].message]] 663 | end 664 | app = lambda do |e| 665 | e['warden'].authenticate! :pass_with_message 666 | success.call(e) 667 | end 668 | result = setup_rack(app).call(@env) 669 | expect(result.last).to eq(["The Success Strategy Has Accepted You"]) 670 | end 671 | 672 | it "should not die when accessing a message from a source where no authentication has occurred" do 673 | app = lambda do |e| 674 | [200, {"Content-Type" => "text/plain"}, [e['warden'].message]] 675 | end 676 | result = setup_rack(app).call(@env) 677 | expect(result[2]).to eq([nil]) 678 | end 679 | end 680 | 681 | describe "when all strategies are not valid?" do 682 | it "should return false for authenticated? when there are no valid? strategies" do 683 | @env['rack.session'] = {} 684 | app = lambda do |e| 685 | expect(e['warden'].authenticate(:invalid)).to be_nil 686 | expect(e['warden']).not_to be_authenticated 687 | end 688 | setup_rack(app).call(@env) 689 | end 690 | 691 | it "should return nil for authenticate when there are no valid strategies" do 692 | @env['rack.session'] = {} 693 | app = lambda do |e| 694 | expect(e['warden'].authenticate(:invalid)).to be_nil 695 | end 696 | setup_rack(app).call(@env) 697 | end 698 | 699 | it "should return false for authenticate? when there are no valid strategies" do 700 | @env['rack.session'] = {} 701 | app = lambda do |e| 702 | expect(e['warden'].authenticate?(:invalid)).to eq(false) 703 | end 704 | setup_rack(app).call(@env) 705 | end 706 | 707 | it "should respond with a 401 when authenticate! cannot find any valid strategies" do 708 | @env['rack.session'] = {} 709 | app = lambda do |e| 710 | e['warden'].authenticate!(:invalid) 711 | end 712 | result = setup_rack(app).call(@env) 713 | expect(result.first).to eq(401) 714 | end 715 | end 716 | 717 | describe "authenticated?" do 718 | describe "positive authentication" do 719 | before do 720 | @env['rack.session'] = {'warden.user.default.key' => 'defult_key'} 721 | $captures = [] 722 | end 723 | 724 | it "should return true when authenticated in the session" do 725 | app = lambda do |e| 726 | expect(e['warden']).to be_authenticated 727 | end 728 | setup_rack(app).call(@env) 729 | end 730 | 731 | it "should yield to a block when the block is passed and authenticated" do 732 | app = lambda do |e| 733 | e['warden'].authenticated? do 734 | $captures << :in_the_block 735 | end 736 | end 737 | setup_rack(app).call(@env) 738 | expect($captures).to eq([:in_the_block]) 739 | end 740 | 741 | it "should authenticate for a user in a different scope" do 742 | @env['rack.session'] = {'warden.user.foo.key' => 'foo_key'} 743 | app = lambda do |e| 744 | e['warden'].authenticated?(:foo) do 745 | $captures << :in_the_foo_block 746 | end 747 | end 748 | setup_rack(app).call(@env) 749 | expect($captures).to eq([:in_the_foo_block]) 750 | end 751 | end 752 | 753 | describe "negative authentication" do 754 | before do 755 | @env['rack.session'] = {'warden.foo.default.key' => 'foo_key'} 756 | $captures = [] 757 | end 758 | 759 | it "should return false when authenticated in the session" do 760 | app = lambda do |e| 761 | expect(e['warden']).not_to be_authenticated 762 | end 763 | setup_rack(app).call(@env) 764 | end 765 | 766 | it "should return false if scope cannot be retrieved from session" do 767 | begin 768 | Warden::Manager.serialize_from_session { |_k| nil } 769 | app = lambda do |env| 770 | env['rack.session']['warden.user.foo_scope.key'] = "a foo user" 771 | env['warden'].authenticated?(:foo_scope) 772 | valid_response 773 | end 774 | setup_rack(app).call(@env) 775 | expect(@env['warden'].user(:foo_scope)).to be_nil 776 | ensure 777 | Warden::Manager.serialize_from_session { |k| k } 778 | end 779 | end 780 | 781 | it "should not yield to a block when the block is passed and authenticated" do 782 | app = lambda do |e| 783 | e['warden'].authenticated? do 784 | $captures << :in_the_block 785 | end 786 | end 787 | setup_rack(app).call(@env) 788 | expect($captures).to eq([]) 789 | end 790 | 791 | it "should not yield for a user in a different scope" do 792 | app = lambda do |e| 793 | e['warden'].authenticated?(:bar) do 794 | $captures << :in_the_bar_block 795 | end 796 | end 797 | setup_rack(app).call(@env) 798 | expect($captures).to eq([]) 799 | end 800 | end 801 | end 802 | 803 | describe "unauthenticated?" do 804 | describe "negative unauthentication" do 805 | before do 806 | @env['rack.session'] = {'warden.user.default.key' => 'defult_key'} 807 | $captures = [] 808 | end 809 | 810 | it "should return false when authenticated in the session" do 811 | app = lambda do |e| 812 | expect(e['warden']).not_to be_unauthenticated 813 | end 814 | _result = setup_rack(app).call(@env) 815 | end 816 | 817 | it "should not yield to a block when the block is passed and authenticated" do 818 | app = lambda do |e| 819 | e['warden'].unauthenticated? do 820 | $captures << :in_the_block 821 | end 822 | end 823 | setup_rack(app).call(@env) 824 | expect($captures).to eq([]) 825 | end 826 | 827 | it "should not yield to the block for a user in a different scope" do 828 | @env['rack.session'] = {'warden.user.foo.key' => 'foo_key'} 829 | app = lambda do |e| 830 | e['warden'].unauthenticated?(:foo) do 831 | $captures << :in_the_foo_block 832 | end 833 | end 834 | setup_rack(app).call(@env) 835 | expect($captures).to eq([]) 836 | end 837 | end 838 | 839 | describe "positive unauthentication" do 840 | before do 841 | @env['rack.session'] = {'warden.foo.default.key' => 'foo_key'} 842 | $captures = [] 843 | end 844 | 845 | it "should return false when unauthenticated in the session" do 846 | app = lambda do |e| 847 | expect(e['warden']).to be_unauthenticated 848 | end 849 | setup_rack(app).call(@env) 850 | end 851 | 852 | it "should yield to a block when the block is passed and authenticated" do 853 | app = lambda do |e| 854 | e['warden'].unauthenticated? do 855 | $captures << :in_the_block 856 | end 857 | end 858 | setup_rack(app).call(@env) 859 | expect($captures).to eq([:in_the_block]) 860 | end 861 | 862 | it "should yield for a user in a different scope" do 863 | app = lambda do |e| 864 | e['warden'].unauthenticated?(:bar) do 865 | $captures << :in_the_bar_block 866 | end 867 | end 868 | setup_rack(app).call(@env) 869 | expect($captures).to eq([:in_the_bar_block]) 870 | end 871 | end 872 | end 873 | 874 | describe "attributes" do 875 | def def_app(&blk) 876 | @app = setup_rack(blk) 877 | end 878 | 879 | it "should have a config attribute" do 880 | app = def_app do |e| 881 | expect(e['warden'].config).to be_a_kind_of(Hash) 882 | valid_response 883 | end 884 | app.call(@env) 885 | end 886 | end 887 | end 888 | 889 | describe "dynamic default_strategies" do 890 | before(:all) do 891 | class ::DynamicDefaultStrategies 892 | def initialize(app, &blk) 893 | @app, @blk = app, blk 894 | end 895 | 896 | def call(env) 897 | @blk.call(env) 898 | @app.call(env) 899 | end 900 | end 901 | 902 | Warden::Strategies.add(:one) do 903 | def authenticate!; $captures << :one; success!("User") end 904 | end 905 | 906 | Warden::Strategies.add(:two) do 907 | def authenticate!; $captures << :two; fail("User not found") end 908 | end 909 | end 910 | 911 | before(:each) do 912 | @app = lambda{|e| e['warden'].authenticate! } 913 | @env = env_with_params("/") 914 | $captures = [] 915 | end 916 | 917 | def wrap_app(app, &blk) 918 | builder = Rack::Builder.new do 919 | use DynamicDefaultStrategies, &blk 920 | run app 921 | end 922 | builder.to_app 923 | end 924 | 925 | it "should allow me to change the default strategies on the fly" do 926 | app = wrap_app(@app) do |e| 927 | expect(e['warden'].default_strategies).to eq([:password]) 928 | expect(e['warden'].config.default_strategies).to eq([:password]) 929 | e['warden'].default_strategies :one 930 | e['warden'].authenticate! 931 | Rack::Response.new("OK").finish 932 | end 933 | setup_rack(app).call(@env) 934 | 935 | expect($captures).to eq([:one]) 936 | end 937 | 938 | it "should allow me to append to the default strategies on the fly" do 939 | app = wrap_app(@app) do |e| 940 | e['warden'].default_strategies << :one 941 | expect(e['warden'].default_strategies).to eq([:password, :one]) 942 | e['warden'].authenticate! 943 | Rack::Response.new("OK").finish 944 | end 945 | setup_rack(app).call(@env) 946 | 947 | expect($captures).to eq([:one]) 948 | end 949 | 950 | it "should allow me to set the default strategies on a per scope basis" do 951 | app = wrap_app(@app) do |e| 952 | w = e['warden'] 953 | w.default_strategies(:two, :one, :scope => :foo) 954 | w.default_strategies(:two, :scope => :default) 955 | expect(w.default_strategies(:scope => :foo)).to eq([:two, :one]) 956 | w.authenticate(:scope => :foo) 957 | expect($captures).to eq([:two, :one]) 958 | $captures.clear 959 | w.authenticate 960 | expect($captures).to eq([:two]) 961 | end 962 | setup_rack(app).call(@env) 963 | expect($captures).to eq([:two]) 964 | end 965 | 966 | it "should allow me to setup default strategies for each scope on the manager" do 967 | builder = Rack::Builder.new do 968 | use Warden::Spec::Helpers::Session 969 | use Warden::Manager do |config| 970 | config.default_strategies :one 971 | config.default_strategies :two, :one, :scope => :foo 972 | config.failure_app = Warden::Spec::Helpers::FAILURE_APP 973 | end 974 | run(lambda do |e| 975 | w = e['warden'] 976 | w.authenticate 977 | w.authenticate(:scope => :foo) 978 | $captures << :complete 979 | end) 980 | end 981 | builder.to_app.call(@env) 982 | expect($captures).to eq([:one, :two, :one, :complete]) 983 | end 984 | 985 | it "should not change the master configurations strategies when I change them" do 986 | app = wrap_app(@app) do |e| 987 | e['warden'].default_strategies << :one 988 | expect(e['warden'].default_strategies).to eq([:password, :one]) 989 | expect(e['warden'].manager.config.default_strategies).to eq([:password]) 990 | e['warden'].authenticate! 991 | Rack::Response.new("OK").finish 992 | end 993 | setup_rack(app).call(@env) 994 | 995 | expect($captures).to eq([:one]) 996 | end 997 | 998 | describe "default scope options" do 999 | 1000 | it "should allow me to set a default action for a given scope" do 1001 | $captures = [] 1002 | builder = Rack::Builder.new do 1003 | use Warden::Manager do |config| 1004 | config.scope_defaults :foo, :strategies => [:two], :action => "some_bad_action" 1005 | config.failure_app = Warden::Spec::Helpers::FAILURE_APP 1006 | end 1007 | 1008 | run(lambda do |e| 1009 | e['warden'].authenticate!(:scope => :foo) 1010 | end) 1011 | end 1012 | 1013 | env = env_with_params("/foo") 1014 | env["rack.session"] = {} 1015 | builder.to_app.call(env) 1016 | request = Rack::Request.new(env) 1017 | expect(request.path).to eq("/some_bad_action") 1018 | end 1019 | 1020 | it "should allow me to set store, false on a given scope" do 1021 | $captures = [] 1022 | warden = [] 1023 | builder = Rack::Builder.new do 1024 | use Warden::Manager do |config| 1025 | config.default_strategies :one 1026 | config.default_strategies :two, :one, :scope => :foo 1027 | config.default_strategies :two, :one, :scope => :bar 1028 | 1029 | config.scope_defaults :bar, :store => false 1030 | config.scope_defaults :baz, :store => false 1031 | config.failure_app = Warden::Spec::Helpers::FAILURE_APP 1032 | end 1033 | run(lambda do |e| 1034 | w = e['warden'] 1035 | w.authenticate 1036 | w.authenticate(:scope => :foo) 1037 | w.authenticate(:one, :scope => :bar) 1038 | w.authenticate(:one, :scope => :baz, :store => true) 1039 | warden << w 1040 | $captures << :complete 1041 | Rack::Response.new("OK").finish 1042 | end) 1043 | end 1044 | session = @env["rack.session"] = {} 1045 | builder.to_app.call(@env) 1046 | expect($captures).to include(:complete) 1047 | w = warden.first 1048 | expect(w.user).to eq("User") 1049 | expect(w.user(:foo)).to eq("User") 1050 | expect(w.user(:bar)).to eq("User") 1051 | expect(w.user(:baz)).to eq("User") 1052 | expect(session['warden.user.default.key']).to eq("User") 1053 | expect(session['warden.user.foo.key']).to eq("User") 1054 | expect(session.key?('warden.user.bar.key')).to eq(false) 1055 | expect(session['warden.user.bar.key']).to be_nil 1056 | expect(session['warden.user.baz.key']).to eq("User") 1057 | end 1058 | end 1059 | 1060 | describe "#asset_request?" do 1061 | before(:each) do 1062 | @asset_regex = /^\/assets\// 1063 | ::Warden.asset_paths = @asset_regex 1064 | end 1065 | 1066 | it "should return true if PATH_INFO is in asset list" do 1067 | env = env_with_params('/assets/fun.gif') 1068 | setup_rack(success_app).call(env) 1069 | proxy = env["warden"] 1070 | 1071 | expect(proxy.env['PATH_INFO']).to match(@asset_regex) 1072 | expect(proxy).to be_asset_request 1073 | end 1074 | 1075 | it "should return false if PATH_INFO is not in asset list" do 1076 | env = env_with_params('/home') 1077 | setup_rack(success_app).call(env) 1078 | proxy = env["warden"] 1079 | 1080 | expect(proxy.env['PATH_INFO']).not_to match(@asset_regex) 1081 | expect(proxy).not_to be_asset_request 1082 | end 1083 | end 1084 | end 1085 | --------------------------------------------------------------------------------