mirror of
https://github.com/heartcombo/devise.git
synced 2022-11-09 12:18:31 -05:00
Initial work on making the authentication stack more flexible.
This commit is contained in:
parent
604b7ef61c
commit
1c5d4771ff
26 changed files with 109 additions and 99 deletions
|
@ -25,8 +25,8 @@ module Devise
|
|||
# Constants which holds devise configuration for extensions. Those should
|
||||
# not be modified by the "end user".
|
||||
ALL = []
|
||||
CONTROLLERS = {}
|
||||
ROUTES = []
|
||||
CONTROLLERS = ActiveSupport::OrderedHash.new
|
||||
ROUTES = ActiveSupport::OrderedHash.new
|
||||
STRATEGIES = ActiveSupport::OrderedHash.new
|
||||
FLASH_MESSAGES = [:unauthenticated]
|
||||
|
||||
|
|
|
@ -150,9 +150,9 @@ module Devise
|
|||
# access that specific controller/action.
|
||||
# Example:
|
||||
#
|
||||
# Maps:
|
||||
# User => :authenticatable
|
||||
# Admin => :authenticatable
|
||||
# Roles:
|
||||
# User
|
||||
# Admin
|
||||
#
|
||||
# Generated methods:
|
||||
# authenticate_user! # Signs user in or redirect
|
||||
|
|
|
@ -19,7 +19,7 @@ module Devise
|
|||
# Those helpers are added to your ApplicationController.
|
||||
module UrlHelpers
|
||||
|
||||
Devise::ROUTES.each do |module_name|
|
||||
Devise::ROUTES.values.uniq.each do |module_name|
|
||||
[:path, :url].each do |path_or_url|
|
||||
actions = [ nil, :new_ ]
|
||||
actions << :edit_ if [:password, :registration].include?(module_name)
|
||||
|
|
|
@ -3,13 +3,6 @@ Warden::Manager.after_set_user do |record, warden, options|
|
|||
if record && record.respond_to?(:active?) && !record.active?
|
||||
scope = options[:scope]
|
||||
warden.logout(scope)
|
||||
|
||||
# If winning strategy was set, this is being called after authenticate and
|
||||
# there is no need to force a redirect.
|
||||
if warden.winning_strategy
|
||||
warden.winning_strategy.fail!(record.inactive_message)
|
||||
else
|
||||
throw :warden, :scope => scope, :message => record.inactive_message
|
||||
end
|
||||
throw :warden, :scope => scope, :message => record.inactive_message
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,7 +78,11 @@ module Devise
|
|||
end
|
||||
|
||||
def strategies
|
||||
@strategies ||= STRATEGIES.values_at(*self.modules).compact.reverse
|
||||
@strategies ||= STRATEGIES.values_at(*self.modules).compact.uniq.reverse
|
||||
end
|
||||
|
||||
def routes
|
||||
@routes ||= ROUTES.values_at(*self.modules).compact.uniq
|
||||
end
|
||||
|
||||
# Keep a list of allowed controllers for this mapping. It's useful to ensure
|
||||
|
@ -101,6 +105,10 @@ module Devise
|
|||
path_prefix + as.to_s
|
||||
end
|
||||
|
||||
def authenticatable?
|
||||
@authenticatable ||= self.modules.any? { |m| m.to_s =~ /authenticatable/ }
|
||||
end
|
||||
|
||||
# Create magic predicates for verifying what module is activated by this map.
|
||||
# Example:
|
||||
#
|
||||
|
|
|
@ -38,7 +38,7 @@ module Devise
|
|||
|
||||
# Include the chosen devise modules in your model:
|
||||
#
|
||||
# devise :authenticatable, :confirmable, :recoverable
|
||||
# devise :database_authenticatable, :confirmable, :recoverable
|
||||
#
|
||||
# You can also give any of the devise configuration values in form of a hash,
|
||||
# with specific values for this model. Please check your Devise initializer
|
||||
|
@ -46,12 +46,17 @@ module Devise
|
|||
#
|
||||
def devise(*modules)
|
||||
raise "You need to give at least one Devise module" if modules.empty?
|
||||
|
||||
options = modules.extract_options!
|
||||
|
||||
if modules.delete(:authenticatable)
|
||||
ActiveSupport::Deprecation.warn ":authenticatable as module is deprecated. Please give :database_authenticatable instead.", caller
|
||||
modules << :database_authenticatable
|
||||
end
|
||||
|
||||
@devise_modules = Devise::ALL & modules.map(&:to_sym).uniq
|
||||
|
||||
devise_modules_hook! do
|
||||
devise_modules.each { |m| include Devise::Models.const_get(m.to_s.classify) }
|
||||
@devise_modules.each { |m| include Devise::Models.const_get(m.to_s.classify) }
|
||||
options.each { |key, value| send(:"#{key}=", value) }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -127,7 +127,7 @@ module Devise
|
|||
# this token is being generated
|
||||
def generate_confirmation_token
|
||||
self.confirmed_at = nil
|
||||
self.confirmation_token = Devise.friendly_token
|
||||
self.confirmation_token = self.class.confirmation_token
|
||||
self.confirmation_sent_at = Time.now.utc
|
||||
end
|
||||
|
||||
|
@ -152,6 +152,10 @@ module Devise
|
|||
confirmable
|
||||
end
|
||||
|
||||
def confirmation_token
|
||||
Devise.friendly_token
|
||||
end
|
||||
|
||||
Devise::Models.config(self, :confirm_within)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
require 'devise/strategies/authenticatable'
|
||||
require 'devise/strategies/database_authenticatable'
|
||||
|
||||
module Devise
|
||||
module Models
|
||||
|
@ -26,7 +26,7 @@ module Devise
|
|||
# User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
|
||||
# User.find(1).valid_password?('password123') # returns true/false
|
||||
#
|
||||
module Authenticatable
|
||||
module DatabaseAuthenticatable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
|
@ -81,11 +81,6 @@ module Devise
|
|||
result
|
||||
end
|
||||
|
||||
# Allows you to overwrite mail messages headers for a given mail.
|
||||
def headers_for(action)
|
||||
{}
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Digests the password using the configured encryptor.
|
|
@ -89,7 +89,7 @@ module Devise
|
|||
|
||||
# Generates unlock token
|
||||
def generate_unlock_token
|
||||
self.unlock_token = Devise.friendly_token
|
||||
self.unlock_token = self.class.unlock_token
|
||||
end
|
||||
|
||||
# Tells if the lock is expired if :time unlock strategy is active
|
||||
|
@ -138,6 +138,10 @@ module Devise
|
|||
[:both, strategy].include?(self.unlock_strategy)
|
||||
end
|
||||
|
||||
def unlock_token
|
||||
Devise.friendly_token
|
||||
end
|
||||
|
||||
Devise::Models.config(self, :maximum_attempts, :unlock_strategy, :unlock_in)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -45,43 +45,35 @@ module Devise
|
|||
self.reset_authentication_token! if self.authentication_token.blank?
|
||||
end
|
||||
|
||||
# Verifies whether an +incoming_authentication_token+ (i.e. from single access URL)
|
||||
# is the user authentication token.
|
||||
def valid_authentication_token?(incoming_auth_token)
|
||||
incoming_auth_token.present? && incoming_auth_token == self.authentication_token
|
||||
end
|
||||
|
||||
module ClassMethods
|
||||
::Devise::Models.config(self, :token_authentication_key)
|
||||
|
||||
# Authenticate a user based on authentication token.
|
||||
def authenticate_with_token(attributes)
|
||||
token = attributes[self.token_authentication_key]
|
||||
resource = self.find_for_token_authentication(token)
|
||||
resource if resource.try(:valid_authentication_token?, token)
|
||||
self.find_for_token_authentication(token)
|
||||
end
|
||||
|
||||
def authentication_token
|
||||
::Devise.friendly_token
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
# Find first record based on conditions given (ie by the sign in form).
|
||||
# Overwrite to add customized conditions, create a join, or maybe use a
|
||||
# namedscope to filter records while authenticating.
|
||||
#
|
||||
# == Example:
|
||||
#
|
||||
# def self.find_for_token_authentication(token, conditions = {})
|
||||
# conditions = {:active => true}
|
||||
# self.find_by_authentication_token(token, :conditions => conditions)
|
||||
# end
|
||||
#
|
||||
def find_for_token_authentication(token)
|
||||
self.find(:first, :conditions => { :authentication_token => token})
|
||||
end
|
||||
protected
|
||||
|
||||
# Find first record based on conditions given (ie by the sign in form).
|
||||
# Overwrite to add customized conditions, create a join, or maybe use a
|
||||
# namedscope to filter records while authenticating.
|
||||
#
|
||||
# == Example:
|
||||
#
|
||||
# def self.find_for_token_authentication(token, conditions = {})
|
||||
# conditions = {:active => true}
|
||||
# self.find_by_authentication_token(token, :conditions => conditions)
|
||||
# end
|
||||
#
|
||||
def find_for_token_authentication(token)
|
||||
self.find(:first, :conditions => { :authentication_token => token})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,9 +3,9 @@ require 'active_support/core_ext/object/with_options'
|
|||
Devise.with_options :model => true do |d|
|
||||
# Strategies first
|
||||
d.with_options :strategy => true do |s|
|
||||
s.add_module :authenticatable, :controller => :sessions, :flash => :invalid, :route => :session
|
||||
s.add_module :database_authenticatable, :controller => :sessions, :flash => :invalid, :route => :session
|
||||
s.add_module :http_authenticatable
|
||||
s.add_module :token_authenticatable, :controller => :sessions, :flash => :invalid_token, :route => :session
|
||||
s.add_module :token_authenticatable, :controller => :sessions, :flash => :invalid_token, :route => :session
|
||||
s.add_module :rememberable
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ module Devise
|
|||
# This module contains some helpers and handle schema (migrations):
|
||||
#
|
||||
# create_table :accounts do |t|
|
||||
# t.authenticatable
|
||||
# t.database_authenticatable
|
||||
# t.confirmable
|
||||
# t.recoverable
|
||||
# t.rememberable
|
||||
|
|
|
@ -83,9 +83,9 @@ module ActionDispatch::Routing
|
|||
#
|
||||
# devise_for :users, :controllers => { :sessions => "users/sessions" }
|
||||
#
|
||||
# * :skip => tell which modules you want to skip routes from being created:
|
||||
# * :skip => tell which controller you want to skip routes from being created:
|
||||
#
|
||||
# devise_for :users, :skip => :authenticatable
|
||||
# devise_for :users, :skip => :sessions
|
||||
#
|
||||
def devise_for(*resources)
|
||||
options = resources.extract_options!
|
||||
|
@ -100,16 +100,18 @@ module ActionDispatch::Routing
|
|||
"inside 'config/initializers/devise.rb' or before your application definition in 'config/application.rb'"
|
||||
end
|
||||
|
||||
routes_modules = mapping.modules - Array(options.delete(:skip))
|
||||
routes_modules.each do |mod|
|
||||
send(mod, mapping, mapping.controllers) if self.respond_to?(mod, true)
|
||||
routes = mapping.routes
|
||||
routes -= Array(options.delete(:skip)).map { |s| s.to_s.singularize.to_sym }
|
||||
|
||||
routes.each do |mod|
|
||||
send(:"devise_#{mod}", mapping, mapping.controllers)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def authenticatable(mapping, controllers)
|
||||
def devise_session(mapping, controllers)
|
||||
scope mapping.path do
|
||||
get mapping.path_names[:sign_in], :to => "#{controllers[:sessions]}#new", :as => :"new_#{mapping.name}_session"
|
||||
post mapping.path_names[:sign_in], :to => "#{controllers[:sessions]}#create", :as => :"#{mapping.name}_session"
|
||||
|
@ -117,25 +119,25 @@ module ActionDispatch::Routing
|
|||
end
|
||||
end
|
||||
|
||||
def recoverable(mapping, controllers)
|
||||
def devise_password(mapping, controllers)
|
||||
scope mapping.path, :name_prefix => mapping.name do
|
||||
resource :password, :only => [:new, :create, :edit, :update], :as => mapping.path_names[:password], :controller => controllers[:passwords]
|
||||
end
|
||||
end
|
||||
|
||||
def confirmable(mapping, controllers)
|
||||
def devise_confirmation(mapping, controllers)
|
||||
scope mapping.path, :name_prefix => mapping.name do
|
||||
resource :confirmation, :only => [:new, :create, :show], :as => mapping.path_names[:confirmation], :controller => controllers[:confirmations]
|
||||
end
|
||||
end
|
||||
|
||||
def lockable(mapping, controllers)
|
||||
def devise_unlock(mapping, controllers)
|
||||
scope mapping.path, :name_prefix => mapping.name do
|
||||
resource :unlock, :only => [:new, :create, :show], :as => mapping.path_names[:unlock], :controller => controllers[:unlocks]
|
||||
end
|
||||
end
|
||||
|
||||
def registerable(mapping, controllers)
|
||||
def devise_registration(mapping, controllers)
|
||||
scope mapping.path[1..-1], :name_prefix => "#{mapping.name}_registration" do
|
||||
resource :registration, :only => [:new, :create, :edit, :update, :destroy], :as => "",
|
||||
:path_names => { :new => mapping.path_names[:sign_up] }, :controller => controllers[:registrations]
|
||||
|
|
|
@ -3,12 +3,17 @@ module Devise
|
|||
# and overwrite the apply_schema method.
|
||||
module Schema
|
||||
|
||||
def authenticatable(*args)
|
||||
ActiveSupport::Deprecation.warn "t.authenticatable in migrations is deprecated. Please use t.database_authenticatable instead.", caller
|
||||
database_authenticatable(*args)
|
||||
end
|
||||
|
||||
# Creates email, encrypted_password and password_salt.
|
||||
#
|
||||
# == Options
|
||||
# * :null - When true, allow columns to be null.
|
||||
# * :encryptor - The encryptor going to be used, necessary for setting the proper encrypter password length.
|
||||
def authenticatable(options={})
|
||||
def database_authenticatable(options={})
|
||||
null = options[:null] || false
|
||||
default = options[:default]
|
||||
encryptor = options[:encryptor] || (respond_to?(:encryptor) ? self.encryptor : :sha1)
|
||||
|
@ -16,7 +21,7 @@ module Devise
|
|||
apply_schema :email, String, :null => null, :default => default
|
||||
apply_schema :encrypted_password, String, :null => null, :default => default, :limit => Devise::ENCRYPTORS_LENGTH[encryptor]
|
||||
apply_schema :password_salt, String, :null => null, :default => default
|
||||
end
|
||||
end
|
||||
|
||||
# Creates authentication_token.
|
||||
def token_authenticatable
|
||||
|
|
|
@ -11,6 +11,14 @@ module Devise
|
|||
mapping
|
||||
end
|
||||
end
|
||||
|
||||
def success!(record)
|
||||
if record.respond_to?(:active?) && !record.active?
|
||||
fail!(record.inactive_message)
|
||||
else
|
||||
super
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,7 +4,7 @@ module Devise
|
|||
module Strategies
|
||||
# Default strategy for signing in a user, based on his email and password.
|
||||
# Redirects to sign_in page if it's not authenticated
|
||||
class Authenticatable < Base
|
||||
class DatabaseAuthenticatable < Base
|
||||
def valid?
|
||||
valid_controller? && valid_params?
|
||||
end
|
||||
|
@ -33,4 +33,4 @@ module Devise
|
|||
end
|
||||
end
|
||||
|
||||
Warden::Strategies.add(:authenticatable, Devise::Strategies::Authenticatable)
|
||||
Warden::Strategies.add(:database_authenticatable, Devise::Strategies::DatabaseAuthenticatable)
|
|
@ -37,9 +37,9 @@ class DeviseGenerator < Rails::Generators::NamedBase
|
|||
def inject_devise_config_into_model
|
||||
inject_into_class model_path, class_name, <<-CONTENT
|
||||
# Include default devise modules. Others available are:
|
||||
# :http_authenticatable, :token_authenticatable, :lockable, :timeoutable and :activatable
|
||||
devise :registerable, :authenticatable, :confirmable, :recoverable,
|
||||
:rememberable, :trackable, :validatable
|
||||
# :token_authenticatable, :lockable, :timeoutable and :activatable
|
||||
devise :database_authenticatable, :registerable, :confirmable,
|
||||
:recoverable, :rememberable, :trackable, :validatable
|
||||
|
||||
# Setup accessible (or protected) attributes for your model
|
||||
attr_accessible :email, :password, :password_confirmation
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class DeviseCreate<%= table_name.camelize %> < ActiveRecord::Migration
|
||||
def self.up
|
||||
create_table(:<%= table_name %>) do |t|
|
||||
t.authenticatable :encryptor => :<%= Devise.encryptor %>, :null => false
|
||||
t.database_authenticatable :encryptor => :<%= Devise.encryptor %>, :null => false
|
||||
t.confirmable
|
||||
t.recoverable
|
||||
t.rememberable
|
||||
|
|
|
@ -4,7 +4,18 @@ Devise.setup do |config|
|
|||
# Configure the e-mail address which will be shown in DeviseMailer.
|
||||
config.mailer_sender = "please-change-me@config-initializers-devise.com"
|
||||
|
||||
# ==> Configuration for :authenticatable
|
||||
# ==> Configuration for any authentication mechanism
|
||||
# Configure which keys are used when authenticating an user. By default is
|
||||
# just :email. You can configure it to use [:username, :subdomain], so for
|
||||
# authenticating an user, both parameters are required. Remember that those
|
||||
# parameters are used only when authenticating and not when retrieving from
|
||||
# session. If you need permissions, you should implement that in a before filter.
|
||||
# config.authentication_keys = [ :email ]
|
||||
|
||||
# The realm used in Http Basic Authentication
|
||||
# config.http_authentication_realm = "Application"
|
||||
|
||||
# ==> Configuration for :database_authenticatable
|
||||
# Invoke `rake secret` and use the printed value to setup a pepper to generate
|
||||
# the encrypted password. By default no pepper is used.
|
||||
# config.pepper = "rake secret output"
|
||||
|
@ -19,16 +30,6 @@ Devise.setup do |config|
|
|||
# (then you should set stretches to 10, and copy REST_AUTH_SITE_KEY to pepper)
|
||||
# config.encryptor = :sha1
|
||||
|
||||
# Configure which keys are used when authenticating an user. By default is
|
||||
# just :email. You can configure it to use [:username, :subdomain], so for
|
||||
# authenticating an user, both parameters are required. Remember that those
|
||||
# parameters are used only when authenticating and not when retrieving from
|
||||
# session. If you need permissions, you should implement that in a before filter.
|
||||
# config.authentication_keys = [ :email ]
|
||||
|
||||
# The realm used in Http Basic Authentication
|
||||
# config.http_authentication_realm = "Application"
|
||||
|
||||
# ==> Configuration for :confirmable
|
||||
# The time you want give to your user to confirm his account. During this time
|
||||
# he will be able to access your application without confirming. Default is nil.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
require 'test_helper'
|
||||
|
||||
class AuthenticationSanityTest < ActionController::IntegrationTest
|
||||
class DatabaseAuthenticationSanityTest < ActionController::IntegrationTest
|
||||
test 'home should be accessible without sign in' do
|
||||
visit '/'
|
||||
assert_response :success
|
|
@ -31,8 +31,8 @@ class MappingTest < ActiveSupport::TestCase
|
|||
|
||||
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
|
||||
:http_authenticatable, :database_authenticatable], Devise.mappings[:user].strategies
|
||||
assert_equal [:database_authenticatable], Devise.mappings[:admin].strategies
|
||||
end
|
||||
|
||||
test 'find mapping by path' do
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
require 'test_helper'
|
||||
require 'digest/sha1'
|
||||
|
||||
class AuthenticatableTest < ActiveSupport::TestCase
|
||||
class DatabaseAuthenticatableTest < ActiveSupport::TestCase
|
||||
|
||||
def encrypt_password(user, pepper=User.pepper, stretches=User.stretches, encryptor=::Devise::Encryptors::Sha1)
|
||||
encryptor.digest('123456', stretches, user.password_salt, pepper)
|
|
@ -25,13 +25,6 @@ class TokenAuthenticatableTest < ActiveSupport::TestCase
|
|||
assert_equal previous_token, user.authentication_token
|
||||
end
|
||||
|
||||
test 'should test for a valid authentication token' do
|
||||
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||
user = create_user
|
||||
assert user.valid_authentication_token?(VALID_AUTHENTICATION_TOKEN)
|
||||
assert_not user.valid_authentication_token?(VALID_AUTHENTICATION_TOKEN.reverse)
|
||||
end
|
||||
|
||||
test 'should authenticate a valid user with authentication token and return it' do
|
||||
User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
|
||||
user = create_user
|
||||
|
|
|
@ -23,12 +23,12 @@ class ActiveRecordTest < ActiveSupport::TestCase
|
|||
end
|
||||
|
||||
test 'add modules cherry pick' do
|
||||
assert_include_modules Admin, :authenticatable, :registerable, :timeoutable, :recoverable
|
||||
assert_include_modules Admin, :database_authenticatable, :registerable, :timeoutable, :recoverable
|
||||
end
|
||||
|
||||
test 'order of module inclusion' do
|
||||
correct_module_order = [:authenticatable, :recoverable, :registerable, :timeoutable]
|
||||
incorrect_module_order = [:authenticatable, :timeoutable, :registerable, :recoverable]
|
||||
correct_module_order = [:database_authenticatable, :recoverable, :registerable, :timeoutable]
|
||||
incorrect_module_order = [:database_authenticatable, :timeoutable, :registerable, :recoverable]
|
||||
|
||||
assert_include_modules Admin, *incorrect_module_order
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ Rails::Application.routes.draw do
|
|||
resources :admins, :only => [:index]
|
||||
|
||||
devise_for :users
|
||||
devise_for :admin, :as => "admin_area", :controllers => { :sessions => "sessions" }, :skip => :recoverable
|
||||
devise_for :admin, :as => "admin_area", :controllers => { :sessions => "sessions" }, :skip => :passwords
|
||||
devise_for :accounts, :scope => "manager", :path_prefix => ":locale",
|
||||
:class_name => "User", :path_names => {
|
||||
:sign_in => "login", :sign_out => "logout",
|
||||
|
|
|
@ -82,7 +82,7 @@ class MapRoutingTest < ActionController::TestCase
|
|||
assert_recognizes({:controller => 'sessions', :action => 'new'}, {:path => 'admin_area/sign_in', :method => :get})
|
||||
end
|
||||
|
||||
test 'does not map admin confirmation' do
|
||||
test 'does not map admin password' do
|
||||
assert_raise ActionController::RoutingError do
|
||||
assert_recognizes({:controller => 'devise/passwords', :action => 'new'}, 'admin_area/password/new')
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue