mirror of
https://github.com/heartcombo/devise.git
synced 2022-11-09 12:18:31 -05:00
Create authenticatable base model and strategy.
This commit is contained in:
parent
1c5d4771ff
commit
65b8908960
18 changed files with 130 additions and 160 deletions
|
@ -54,15 +54,23 @@ module Devise
|
||||||
# Keys used when authenticating an user.
|
# Keys used when authenticating an user.
|
||||||
mattr_accessor :authentication_keys
|
mattr_accessor :authentication_keys
|
||||||
@@authentication_keys = [ :email ]
|
@@authentication_keys = [ :email ]
|
||||||
|
|
||||||
# Range validation for password length
|
# If http authentication is enabled by default.
|
||||||
mattr_accessor :password_length
|
mattr_accessor :http_authenticatable
|
||||||
@@password_length = 6..20
|
@@http_authenticatable = true
|
||||||
|
|
||||||
|
# The realm used in Http Basic Authentication.
|
||||||
|
mattr_accessor :http_authentication_realm
|
||||||
|
@@http_authentication_realm = "Application"
|
||||||
|
|
||||||
# Email regex used to validate email formats. Adapted from authlogic.
|
# Email regex used to validate email formats. Adapted from authlogic.
|
||||||
mattr_accessor :email_regexp
|
mattr_accessor :email_regexp
|
||||||
@@email_regexp = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
|
@@email_regexp = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
|
||||||
|
|
||||||
|
# Range validation for password length
|
||||||
|
mattr_accessor :password_length
|
||||||
|
@@password_length = 6..20
|
||||||
|
|
||||||
# Time interval where the remember me token is valid.
|
# Time interval where the remember me token is valid.
|
||||||
mattr_accessor :remember_for
|
mattr_accessor :remember_for
|
||||||
@@remember_for = 2.weeks
|
@@remember_for = 2.weeks
|
||||||
|
@ -122,10 +130,6 @@ module Devise
|
||||||
mattr_accessor :token_authentication_key
|
mattr_accessor :token_authentication_key
|
||||||
@@token_authentication_key = :auth_token
|
@@token_authentication_key = :auth_token
|
||||||
|
|
||||||
# The realm used in Http Basic Authentication.
|
|
||||||
mattr_accessor :http_authentication_realm
|
|
||||||
@@http_authentication_realm = "Application"
|
|
||||||
|
|
||||||
# Private methods to interface with Warden.
|
# Private methods to interface with Warden.
|
||||||
mattr_reader :warden_config
|
mattr_reader :warden_config
|
||||||
@@warden_config = nil
|
@@warden_config = nil
|
||||||
|
|
|
@ -53,6 +53,10 @@ module Devise
|
||||||
modules << :database_authenticatable
|
modules << :database_authenticatable
|
||||||
end
|
end
|
||||||
|
|
||||||
|
if modules.delete(:http_authenticatable)
|
||||||
|
ActiveSupport::Deprecation.warn ":http_authenticatable as module is deprecated and is on by default. Revert by setting :http_authenticatable => false.", caller
|
||||||
|
end
|
||||||
|
|
||||||
@devise_modules = Devise::ALL & modules.map(&:to_sym).uniq
|
@devise_modules = Devise::ALL & modules.map(&:to_sym).uniq
|
||||||
|
|
||||||
devise_modules_hook! do
|
devise_modules_hook! do
|
||||||
|
|
38
lib/devise/models/authenticatable.rb
Normal file
38
lib/devise/models/authenticatable.rb
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
module Devise
|
||||||
|
module Models
|
||||||
|
# Authenticable module. Holds common settings for authentication.
|
||||||
|
#
|
||||||
|
# Configuration:
|
||||||
|
#
|
||||||
|
# You can overwrite configuration values by setting in globally in Devise,
|
||||||
|
# using devise method or overwriting the respective instance method.
|
||||||
|
#
|
||||||
|
# authentication_keys: parameters used for authentication. By default [:email].
|
||||||
|
#
|
||||||
|
# http_authenticatable: if this model allows http authentication. By default true.
|
||||||
|
#
|
||||||
|
module Authenticatable
|
||||||
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
|
module ClassMethods
|
||||||
|
Devise::Models.config(self, :authentication_keys, :http_authenticatable)
|
||||||
|
|
||||||
|
alias :http_authenticatable? :http_authenticatable
|
||||||
|
|
||||||
|
# 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_authentication(conditions={})
|
||||||
|
# conditions[:active] = true
|
||||||
|
# super
|
||||||
|
# end
|
||||||
|
#
|
||||||
|
def find_for_authentication(conditions)
|
||||||
|
find(:first, :conditions => conditions)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -1,3 +1,4 @@
|
||||||
|
require 'devise/models/authenticatable'
|
||||||
require 'devise/strategies/database_authenticatable'
|
require 'devise/strategies/database_authenticatable'
|
||||||
|
|
||||||
module Devise
|
module Devise
|
||||||
|
@ -19,8 +20,6 @@ module Devise
|
||||||
#
|
#
|
||||||
# encryptor: the encryptor going to be used. By default :sha1.
|
# encryptor: the encryptor going to be used. By default :sha1.
|
||||||
#
|
#
|
||||||
# authentication_keys: parameters used for authentication. By default [:email]
|
|
||||||
#
|
|
||||||
# Examples:
|
# Examples:
|
||||||
#
|
#
|
||||||
# User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
|
# User.authenticate('email@test.com', 'password123') # returns authenticated user or nil
|
||||||
|
@ -30,6 +29,8 @@ module Devise
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
included do
|
included do
|
||||||
|
include Devise::Models::Authenticatable
|
||||||
|
|
||||||
attr_reader :password, :current_password
|
attr_reader :password, :current_password
|
||||||
attr_accessor :password_confirmation
|
attr_accessor :password_confirmation
|
||||||
end
|
end
|
||||||
|
@ -89,15 +90,13 @@ module Devise
|
||||||
end
|
end
|
||||||
|
|
||||||
module ClassMethods
|
module ClassMethods
|
||||||
Devise::Models.config(self, :pepper, :stretches, :encryptor, :authentication_keys)
|
Devise::Models.config(self, :pepper, :stretches, :encryptor)
|
||||||
|
|
||||||
# Authenticate a user based on configured attribute keys. Returns the
|
# Authenticate a user based on configured attribute keys. Returns the
|
||||||
# authenticated user if it's valid or nil.
|
# authenticated user if it's valid or nil.
|
||||||
def authenticate(attributes={})
|
def authenticate(conditions)
|
||||||
return unless authentication_keys.all? { |k| attributes[k].present? }
|
resource = find_for_database_authentication(conditions.except(:password))
|
||||||
conditions = attributes.slice(*authentication_keys)
|
resource if resource.try(:valid_for_authentication?, conditions)
|
||||||
resource = find_for_authentication(conditions)
|
|
||||||
resource if resource.try(:valid_for_authentication?, attributes)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the class for the configured encryptor.
|
# Returns the class for the configured encryptor.
|
||||||
|
@ -105,20 +104,8 @@ module Devise
|
||||||
@encryptor_class ||= ::Devise::Encryptors.const_get(encryptor.to_s.classify)
|
@encryptor_class ||= ::Devise::Encryptors.const_get(encryptor.to_s.classify)
|
||||||
end
|
end
|
||||||
|
|
||||||
protected
|
def find_for_database_authentication(*args)
|
||||||
|
find_for_authentication(*args)
|
||||||
# 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_authentication(conditions={})
|
|
||||||
# conditions[:active] = true
|
|
||||||
# find(:first, :conditions => conditions)
|
|
||||||
# end
|
|
||||||
#
|
|
||||||
def find_for_authentication(conditions)
|
|
||||||
find(:first, :conditions => conditions)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
require 'devise/strategies/http_authenticatable'
|
|
||||||
|
|
||||||
module Devise
|
|
||||||
module Models
|
|
||||||
# Adds HttpAuthenticatable behavior to your model. It expects that your
|
|
||||||
# model class responds to authenticate and authentication_keys methods
|
|
||||||
# (which for example are defined in authenticatable).
|
|
||||||
module HttpAuthenticatable
|
|
||||||
extend ActiveSupport::Concern
|
|
||||||
|
|
||||||
module ClassMethods
|
|
||||||
# Authenticate an user using http.
|
|
||||||
def authenticate_with_http(username, password)
|
|
||||||
authenticate(authentication_keys.first => username, :password => password)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -68,7 +68,7 @@ module Devise
|
||||||
#
|
#
|
||||||
# def self.find_for_token_authentication(token, conditions = {})
|
# def self.find_for_token_authentication(token, conditions = {})
|
||||||
# conditions = {:active => true}
|
# conditions = {:active => true}
|
||||||
# self.find_by_authentication_token(token, :conditions => conditions)
|
# super
|
||||||
# end
|
# end
|
||||||
#
|
#
|
||||||
def find_for_token_authentication(token)
|
def find_for_token_authentication(token)
|
||||||
|
|
|
@ -2,9 +2,8 @@ require 'active_support/core_ext/object/with_options'
|
||||||
|
|
||||||
Devise.with_options :model => true do |d|
|
Devise.with_options :model => true do |d|
|
||||||
# Strategies first
|
# Strategies first
|
||||||
d.with_options :strategy => true do |s|
|
d.with_options :strategy => true do |s|
|
||||||
s.add_module :database_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
|
s.add_module :rememberable
|
||||||
end
|
end
|
||||||
|
|
55
lib/devise/strategies/authenticatable.rb
Normal file
55
lib/devise/strategies/authenticatable.rb
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
require 'devise/strategies/base'
|
||||||
|
|
||||||
|
module Devise
|
||||||
|
module Strategies
|
||||||
|
class Authenticatable < Base
|
||||||
|
attr_accessor :authentication_hash, :password
|
||||||
|
|
||||||
|
def valid?
|
||||||
|
valid_for_http_auth? || valid_for_params_auth?
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def valid_for_http_auth?
|
||||||
|
mapping.to.http_authenticatable? && request.authorization && set_http_auth_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_for_params_auth?
|
||||||
|
valid_controller? && valid_params? && set_params_auth_hash
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_controller?
|
||||||
|
mapping.controllers[:sessions] == params[:controller]
|
||||||
|
end
|
||||||
|
|
||||||
|
def valid_params?
|
||||||
|
params[scope].is_a?(Hash)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_http_auth_hash
|
||||||
|
keys = [authentication_keys.first, :password]
|
||||||
|
with_authentication_hash Hash[*keys.zip(decode_credentials).flatten]
|
||||||
|
end
|
||||||
|
|
||||||
|
def decode_credentials
|
||||||
|
username_and_password = request.authorization.split(' ', 2).last || ''
|
||||||
|
ActiveSupport::Base64.decode64(username_and_password).split(/:/, 2)
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_params_auth_hash
|
||||||
|
with_authentication_hash params[scope]
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_authentication_hash(hash)
|
||||||
|
self.authentication_hash = hash.slice(*authentication_keys)
|
||||||
|
self.password = hash[:password]
|
||||||
|
authentication_keys.all?{ |k| authentication_hash[k].present? } && password.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def authentication_keys
|
||||||
|
@authentication_keys ||= mapping.to.authentication_keys
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,6 +12,7 @@ module Devise
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO Move to a module
|
||||||
def success!(record)
|
def success!(record)
|
||||||
if record.respond_to?(:active?) && !record.active?
|
if record.respond_to?(:active?) && !record.active?
|
||||||
fail!(record.inactive_message)
|
fail!(record.inactive_message)
|
||||||
|
|
|
@ -1,19 +1,15 @@
|
||||||
require 'devise/strategies/base'
|
require 'devise/strategies/authenticatable'
|
||||||
|
|
||||||
module Devise
|
module Devise
|
||||||
module Strategies
|
module Strategies
|
||||||
# Default strategy for signing in a user, based on his email and password.
|
# Default strategy for signing in a user, based on his email and password.
|
||||||
# Redirects to sign_in page if it's not authenticated
|
# Redirects to sign_in page if it's not authenticated
|
||||||
class DatabaseAuthenticatable < Base
|
class DatabaseAuthenticatable < Authenticatable
|
||||||
def valid?
|
|
||||||
valid_controller? && valid_params?
|
|
||||||
end
|
|
||||||
|
|
||||||
# Authenticate a user based on email and password params, returning to warden
|
# Authenticate a user based on email and password params, returning to warden
|
||||||
# success and the authenticated user if everything is okay. Otherwise redirect
|
# success and the authenticated user if everything is okay. Otherwise redirect
|
||||||
# to sign in page.
|
# to sign in page.
|
||||||
def authenticate!
|
def authenticate!
|
||||||
if resource = mapping.to.authenticate(params[scope])
|
if resource = mapping.to.authenticate(authentication_hash.merge(:password => password))
|
||||||
success!(resource)
|
success!(resource)
|
||||||
else
|
else
|
||||||
fail(:invalid)
|
fail(:invalid)
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
require 'devise/strategies/base'
|
|
||||||
|
|
||||||
module Devise
|
|
||||||
module Strategies
|
|
||||||
# Sign in an user using HTTP authentication.
|
|
||||||
class HttpAuthenticatable < Base
|
|
||||||
def valid?
|
|
||||||
request.authorization
|
|
||||||
end
|
|
||||||
|
|
||||||
def authenticate!
|
|
||||||
username, password = username_and_password
|
|
||||||
|
|
||||||
if resource = mapping.to.authenticate_with_http(username, password)
|
|
||||||
success!(resource)
|
|
||||||
else
|
|
||||||
fail!(:invalid)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def username_and_password
|
|
||||||
decode_credentials(request).split(/:/, 2)
|
|
||||||
end
|
|
||||||
|
|
||||||
def decode_credentials(request)
|
|
||||||
ActiveSupport::Base64.decode64(request.authorization.split(' ', 2).last || '')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
Warden::Strategies.add(:http_authenticatable, Devise::Strategies::HttpAuthenticatable)
|
|
|
@ -12,6 +12,9 @@ Devise.setup do |config|
|
||||||
# session. If you need permissions, you should implement that in a before filter.
|
# session. If you need permissions, you should implement that in a before filter.
|
||||||
# config.authentication_keys = [ :email ]
|
# config.authentication_keys = [ :email ]
|
||||||
|
|
||||||
|
# Tell if authentication for http is enabled. True by default.
|
||||||
|
# config.http_authenticatable = true
|
||||||
|
|
||||||
# The realm used in Http Basic Authentication
|
# The realm used in Http Basic Authentication
|
||||||
# config.http_authentication_realm = "Application"
|
# config.http_authentication_realm = "Application"
|
||||||
|
|
||||||
|
|
|
@ -30,8 +30,7 @@ class MappingTest < ActiveSupport::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'has strategies depending on the model declaration' do
|
test 'has strategies depending on the model declaration' do
|
||||||
assert_equal [:rememberable, :token_authenticatable,
|
assert_equal [:rememberable, :token_authenticatable, :database_authenticatable], Devise.mappings[:user].strategies
|
||||||
:http_authenticatable, :database_authenticatable], Devise.mappings[:user].strategies
|
|
||||||
assert_equal [:database_authenticatable], Devise.mappings[:admin].strategies
|
assert_equal [:database_authenticatable], Devise.mappings[:admin].strategies
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -98,38 +98,6 @@ class DatabaseAuthenticatableTest < ActiveSupport::TestCase
|
||||||
assert_not user.valid_password?('654321')
|
assert_not user.valid_password?('654321')
|
||||||
end
|
end
|
||||||
|
|
||||||
test 'should authenticate a valid user with email and password and return it' do
|
|
||||||
user = create_user
|
|
||||||
user.confirm!
|
|
||||||
authenticated_user = User.authenticate(:email => user.email, :password => user.password)
|
|
||||||
assert_equal authenticated_user, user
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'should return nil when authenticating an invalid user by email' do
|
|
||||||
user = create_user
|
|
||||||
authenticated_user = User.authenticate(:email => 'another.email@email.com', :password => user.password)
|
|
||||||
assert_nil authenticated_user
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'should return nil when authenticating an invalid user by password' do
|
|
||||||
user = create_user
|
|
||||||
authenticated_user = User.authenticate(:email => user.email, :password => 'another_password')
|
|
||||||
assert_nil authenticated_user
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'should use authentication keys to retrieve users' do
|
|
||||||
swap Devise, :authentication_keys => [:username] do
|
|
||||||
user = create_user
|
|
||||||
assert_nil User.authenticate(:email => user.email, :password => user.password)
|
|
||||||
assert_not_nil User.authenticate(:username => user.username, :password => user.password)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'should allow overwriting find for authentication conditions' do
|
|
||||||
admin = Admin.create!(valid_attributes)
|
|
||||||
assert_not_nil Admin.authenticate(:email => admin.email, :password => admin.password)
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'should respond to current password' do
|
test 'should respond to current password' do
|
||||||
assert new_user.respond_to?(:current_password)
|
assert new_user.respond_to?(:current_password)
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
require 'test_helper'
|
|
||||||
|
|
||||||
class HttpAuthenticatableTest < ActiveSupport::TestCase
|
|
||||||
test 'should authenticate a valid user with email and password and return it' do
|
|
||||||
user = create_user
|
|
||||||
user.confirm!
|
|
||||||
|
|
||||||
authenticated_user = User.authenticate_with_http(user.email, user.password)
|
|
||||||
assert_equal authenticated_user, user
|
|
||||||
end
|
|
||||||
|
|
||||||
test 'should return nil when authenticating an invalid user by email' do
|
|
||||||
user = create_user
|
|
||||||
user.confirm!
|
|
||||||
|
|
||||||
authenticated_user = User.authenticate_with_http('another.email@email.com', user.password)
|
|
||||||
assert_nil authenticated_user
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -1,7 +1,3 @@
|
||||||
class Admin < ActiveRecord::Base
|
class Admin < ActiveRecord::Base
|
||||||
devise :authenticatable, :registerable, :timeoutable, :recoverable
|
devise :authenticatable, :registerable, :timeoutable, :recoverable
|
||||||
|
|
||||||
def self.find_for_authentication(conditions)
|
|
||||||
last(:conditions => conditions)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,10 +5,6 @@ class Admin
|
||||||
property :username, String
|
property :username, String
|
||||||
|
|
||||||
devise :authenticatable, :registerable, :timeoutable, :recoverable
|
devise :authenticatable, :registerable, :timeoutable, :recoverable
|
||||||
|
|
||||||
def self.find_for_authentication(conditions)
|
|
||||||
last(conditions)
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.create!(*args)
|
def self.create!(*args)
|
||||||
create(*args)
|
create(*args)
|
||||||
|
|
|
@ -2,10 +2,6 @@ class Admin
|
||||||
include Mongoid::Document
|
include Mongoid::Document
|
||||||
|
|
||||||
devise :authenticatable, :timeoutable, :registerable, :recoverable
|
devise :authenticatable, :timeoutable, :registerable, :recoverable
|
||||||
|
|
||||||
def self.find_for_authentication(conditions)
|
|
||||||
last(:conditions => conditions, :sort => [[:email, :asc]])
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.last(options={})
|
def self.last(options={})
|
||||||
options.delete(:order) if options[:order] == "id"
|
options.delete(:order) if options[:order] == "id"
|
||||||
|
|
Loading…
Reference in a new issue