diff --git a/lib/devise.rb b/lib/devise.rb index 8cdb9f9b..02fa34ea 100644 --- a/lib/devise.rb +++ b/lib/devise.rb @@ -27,7 +27,7 @@ module Devise ALL = [] CONTROLLERS = {} ROUTES = [] - STRATEGIES = [] + STRATEGIES = ActiveSupport::OrderedHash.new FLASH_MESSAGES = [:unauthenticated] # True values used to check params @@ -122,49 +122,73 @@ module Devise mattr_accessor :token_authentication_key @@token_authentication_key = :auth_token - # The realm used in Http Basic Authentication + # The realm used in Http Basic Authentication. mattr_accessor :http_authentication_realm @@http_authentication_realm = "Application" + # Private methods to interface with Warden. + mattr_reader :warden_config + @@warden_config = nil + @@warden_config_block = nil + # Default way to setup Devise. Run rails generate devise_install to create # a fresh initializer with all configuration values. def self.setup yield self end + # Register a model in Devise. You can call this manually if you don't want + # to use devise routes. Check devise_for in routes to know which options + # are available. + def self.register(resource, options) + mapping = Devise::Mapping.new(resource, options) + self.mappings[mapping.name] = mapping + self.default_scope ||= mapping.name + + warden_config.default_scope ||= mapping.name + warden_config.scope_defaults mapping.name, :strategies => mapping.strategies + mapping + end + # Make Devise aware of an 3rd party Devise-module. For convenience. # # == Options: # - # +strategy+ - Boolean value representing if this module got a custom *strategy*. - # Default is +false+. Note: Devise will auto-detect this in such case if this is true. - # +model+ - String representing the load path to a custom *model* for this module (to autoload.) - # Default is +nil+ (i.e. +false+). - # +controller+ - Symbol representing the name of an exisiting or custom *controller* for this module. - # Default is +nil+ (i.e. +false+). - # +route+ - Symbol representing the named *router* helper for this module. - # Default is +nil+ (i.e. +false+). - # +flash+ - Symbol representing the *flash messages* used by this helper. - # Default is +nil+ (i.e. +false+). + # +model+ - String representing the load path to a custom *model* for this module (to autoload.) + # +controller+ - Symbol representing the name of an exisiting or custom *controller* for this module. + # +route+ - Symbol representing the named *route* helper for this module. + # +flash+ - Symbol representing the *flash messages* used by this helper. + # +strategy+ - Symbol representing if this module got a custom *strategy*. + # + # All values, except :model, accept also a boolean and will have the same name as the given module + # name. # # == Examples: # # Devise.add_module(:party_module) # Devise.add_module(:party_module, :strategy => true, :controller => :sessions) - # Devise.add_module(:party_module, :autoload => 'party_module/model') + # Devise.add_module(:party_module, :model => 'party_module/model') # def self.add_module(module_name, options = {}) ALL << module_name - options.assert_valid_keys(:strategy, :model, :controller, :route, :flash) + options.assert_valid_keys(:strategy, :model, :controller, :route, :flash, :passive_strategy) - { :strategy => STRATEGIES, :flash => FLASH_MESSAGES, :route => ROUTES }.each do |key, value| + config = { + :strategy => STRATEGIES, + :flash => FLASH_MESSAGES, + :route => ROUTES, + :controller => CONTROLLERS + } + + config.each do |key, value| next unless options[key] name = (options[key] == true ? module_name : options[key]) - value.unshift(name) unless value.include?(name) - end - if options[:controller] - Devise::CONTROLLERS[module_name] = options[:controller].to_sym + if value.is_a?(Hash) + value[module_name] = name + else + value << name unless value.include?(name) + end end if options[:model] @@ -172,7 +196,7 @@ module Devise Devise::Models.send(:autoload, module_name.to_s.camelize.to_sym, model_path) end - Devise::Mapping.register module_name + Devise::Mapping.add_module module_name end # Sets warden configuration using a block that will be invoked on warden @@ -187,19 +211,17 @@ module Devise # end # end def self.warden(&block) - @warden_config = block + @@warden_config_block = block end # A method used internally to setup warden manager from the Rails initialize # block. def self.configure_warden(config) #:nodoc: - config.default_strategies *Devise::STRATEGIES - config.failure_app = Devise::FailureApp - config.silence_missing_strategies! + config.failure_app = Devise::FailureApp config.default_scope = Devise.default_scope - # If the user provided a warden hook, call it now. - @warden_config.try :call, config + @@warden_config = config + @@warden_config_block.try :call, config end # Generate a friendly string randomically to be used as token. diff --git a/lib/devise/mapping.rb b/lib/devise/mapping.rb index d4d149a2..7544e308 100644 --- a/lib/devise/mapping.rb +++ b/lib/devise/mapping.rb @@ -77,6 +77,10 @@ module Devise klass end + def strategies + @strategies ||= STRATEGIES.values_at(*self.modules).compact.reverse + end + # Keep a list of allowed controllers for this mapping. It's useful to ensure # that an Admin cannot access the registrations controller unless it has # :registerable in the model. @@ -104,7 +108,7 @@ module Devise # self.modules.include?(:confirmable) # end # - def self.register(m) + def self.add_module(m) class_eval <<-METHOD, __FILE__, __LINE__ + 1 def #{m}? self.modules.include?(:#{m}) diff --git a/lib/devise/rails/routes.rb b/lib/devise/rails/routes.rb index 7108e02f..b36ef48c 100644 --- a/lib/devise/rails/routes.rb +++ b/lib/devise/rails/routes.rb @@ -92,17 +92,14 @@ module ActionDispatch::Routing resources.map!(&:to_sym) resources.each do |resource| - mapping = Devise::Mapping.new(resource, options) + mapping = Devise.register(resource, options) unless mapping.to.respond_to?(:devise) raise "#{mapping.to.name} does not respond to 'devise' method. This usually means you haven't " << - "loaded your ORM file or it's being loaded to late. To fix it, be sure to require 'devise/orm/YOUR_ORM' " << + "loaded your ORM file or it's being loaded too late. To fix it, be sure to require 'devise/orm/YOUR_ORM' " << "inside 'config/initializers/devise.rb' or before your application definition in 'config/application.rb'" end - Devise.default_scope ||= mapping.name - Devise.mappings[mapping.name] = mapping - routes_modules = mapping.modules - Array(options.delete(:skip)) routes_modules.each do |mod| send(mod, mapping, mapping.controllers) if self.respond_to?(mod, true) diff --git a/lib/devise/strategies/authenticatable.rb b/lib/devise/strategies/authenticatable.rb index 6a0857b7..22199c10 100644 --- a/lib/devise/strategies/authenticatable.rb +++ b/lib/devise/strategies/authenticatable.rb @@ -6,7 +6,7 @@ module Devise # Redirects to sign_in page if it's not authenticated class Authenticatable < Base def valid? - valid_controller? && valid_params? && mapping.to.respond_to?(:authenticate) + valid_controller? && valid_params? end # Authenticate a user based on email and password params, returning to warden diff --git a/lib/devise/strategies/http_authenticatable.rb b/lib/devise/strategies/http_authenticatable.rb index 1cba9297..a7df1b6f 100644 --- a/lib/devise/strategies/http_authenticatable.rb +++ b/lib/devise/strategies/http_authenticatable.rb @@ -5,7 +5,7 @@ module Devise # Sign in an user using HTTP authentication. class HttpAuthenticatable < Base def valid? - request.authorization && mapping.to.respond_to?(:authenticate_with_http) + request.authorization end def authenticate! diff --git a/lib/devise/strategies/rememberable.rb b/lib/devise/strategies/rememberable.rb index 9b3a5382..e01a8b26 100644 --- a/lib/devise/strategies/rememberable.rb +++ b/lib/devise/strategies/rememberable.rb @@ -10,7 +10,7 @@ module Devise # A valid strategy for rememberable needs a remember token in the cookies. def valid? - remember_me_cookie.present? && mapping.to.respond_to?(:serialize_from_cookie) + remember_me_cookie.present? end # To authenticate a user we deserialize the cookie and attempt finding diff --git a/lib/devise/strategies/token_authenticatable.rb b/lib/devise/strategies/token_authenticatable.rb index d8baf545..b52c0eb3 100644 --- a/lib/devise/strategies/token_authenticatable.rb +++ b/lib/devise/strategies/token_authenticatable.rb @@ -6,7 +6,7 @@ module Devise # Redirects to sign_in page if it's not authenticated. class TokenAuthenticatable < Base def valid? - mapping.to.respond_to?(:authenticate_with_token) && authentication_token(scope).present? + authentication_token(scope).present? end # Authenticate a user based on authenticatable token params, returning to warden diff --git a/lib/devise/test_helpers.rb b/lib/devise/test_helpers.rb index b2227c3c..cd7171df 100644 --- a/lib/devise/test_helpers.rb +++ b/lib/devise/test_helpers.rb @@ -15,7 +15,7 @@ module Devise def initialize(controller) @controller = controller manager = Warden::Manager.new(nil) do |config| - Devise.configure_warden(config) + config.merge! Devise.warden_config end super(controller.request.env, manager) end diff --git a/test/devise_test.rb b/test/devise_test.rb index c651bf74..0a474a4c 100644 --- a/test/devise_test.rb +++ b/test/devise_test.rb @@ -1,8 +1,11 @@ require 'test_helper' module Devise - def self.clean_warden_config! - @warden_config = nil + def self.yield_and_restore + c, b = @@warden_config, @@warden_config_block + yield + ensure + @@warden_config, @@warden_config_block = c, b end end @@ -21,17 +24,17 @@ class DeviseTest < ActiveSupport::TestCase end test 'warden manager configuration' do - config = Warden::Config.new - Devise.configure_warden(config) + Devise.yield_and_restore do + config = Warden::Config.new + Devise.configure_warden(config) - assert_equal Devise::FailureApp, config.failure_app - assert_equal [:rememberable, :token_authenticatable, :http_authenticatable, :authenticatable], config.default_strategies - assert_equal :user, config.default_scope - assert config.silence_missing_strategies? + assert_equal Devise::FailureApp, config.failure_app + assert_equal :user, config.default_scope + end end test 'warden manager user configuration through a block' do - begin + Devise.yield_and_restore do @executed = false Devise.warden do |config| @executed = true @@ -40,8 +43,6 @@ class DeviseTest < ActiveSupport::TestCase Devise.configure_warden(Warden::Config.new) assert @executed - ensure - Devise.clean_warden_config! end end @@ -52,8 +53,8 @@ class DeviseTest < ActiveSupport::TestCase assert_not defined?(Devise::Models::Coconut) Devise::ALL.delete(:coconut) - assert_nothing_raised(Exception) { Devise.add_module(:banana, :strategy => true) } - assert_equal 1, Devise::STRATEGIES.select { |v| v == :banana }.size + assert_nothing_raised(Exception) { Devise.add_module(:banana, :strategy => :fruits) } + assert_equal :fruits, Devise::STRATEGIES[:banana] Devise::ALL.delete(:banana) Devise::STRATEGIES.delete(:banana) diff --git a/test/mapping_test.rb b/test/mapping_test.rb index 5d1cf746..4c24a044 100644 --- a/test/mapping_test.rb +++ b/test/mapping_test.rb @@ -13,7 +13,7 @@ class MappingTest < ActiveSupport::TestCase assert_equal :admin_area, Devise.mappings[:admin].as end - test 'allow custom scope to be given' do + test 'allows custom scope to be given' do assert_equal :accounts, Devise.mappings[:manager].as end @@ -29,6 +29,12 @@ class MappingTest < ActiveSupport::TestCase assert_not allowed.include?("devise/unlocks") end + test 'has strategies depending on the model declaration' do + assert_equal [:rememberable, :token_authenticatable, + :http_authenticatable, :authenticatable], Devise.mappings[:user].strategies + assert_equal [:authenticatable], Devise.mappings[:admin].strategies + end + test 'find mapping by path' do assert_nil Devise::Mapping.find_by_path("/foo/bar") assert_equal Devise.mappings[:user], Devise::Mapping.find_by_path("/users/session")