1
0
Fork 0
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:
José Valim 2010-03-29 20:52:34 +02:00
parent 1c5d4771ff
commit 65b8908960
18 changed files with 130 additions and 160 deletions

View file

@ -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

View file

@ -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

View 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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View 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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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"

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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"