Compare commits

...

12 Commits

Author SHA1 Message Date
José Valim
3b598ec235 Release v3.2.1 2013-11-13 14:15:13 +01:00
José Valim
95ec62ea76 Ensure encryption on authentication 2013-11-13 13:45:34 +01:00
José Valim
9a412c139f Update CHANGELOG 2013-11-13 13:32:59 +01:00
José Valim
0582467032 Ensure we only store paths in store_location_for (thanks to @homakov for the tip) 2013-11-13 13:30:24 +01:00
José Valim
221be6d6ef Update bundled rails app 2013-11-13 13:29:25 +01:00
José Valim
ed86361b92 Merge pull request #2728 from edelpero/master
Adds yield around resource on devise controllers
2013-11-08 23:22:53 -08:00
José Valim
e303de9756 Merge pull request #2729 from matthewrudy/store-location-helper
Add store_location_for helper
2013-11-08 23:22:39 -08:00
Matthew Rudy Jacobs
268e486dbb Add store_location_for helper
This is used as a complement to `stored_location_for`.

Example:

Before authorizing with Omniauth;

  store_location_for(:user, dashboard_path)
  redirect_to user_omniauth_authorize_path(:facebook)

In our Omniauth callback

  sign_in(user)
  redirect_to stored_location_for(:user) || root_path
2013-11-09 00:59:00 +00:00
Ezequiel Delpero
989071144e Adds yield around resource on devise controllers
If you want to add a new behavior to your devise
controllers but you don't want to override devise's
default workflow, just pass a block around resource.

This would give you for example, the ability to
trigger background jobs after user signs in.
2013-11-08 20:43:08 -03:00
Rafael Mendonça França
25726becdd Merge pull request #2731 from plataformatec/lm-password-digest
Bring `password_digest` back.
2013-11-08 10:28:11 -08:00
Lucas Mazza
bf5bcd52cb Bring password_digest back.
This method is part of the protected API and is used by custom
encryption engines (like `devise-encryptable`) to hook the custom
encryption logic in the models.

Fixes #2730
2013-11-08 16:22:31 -02:00
José Valim
e26ea51fe5 Improve error message for wrongly nested omniauth callback 2013-11-07 14:30:32 +01:00
24 changed files with 168 additions and 60 deletions

View File

@@ -1,3 +1,15 @@
### 3.2.1
Security announcement: http://blog.plataformatec.com.br/2013/11/e-mail-enumeration-in-devise-in-paranoid-mode
* enhancements
* Add `store_location_for` helper and ensure it is safe (by @matthewrudy and @homakov)
* Add `yield` around resource methods in Devise controllers (by @edelpero)
* bug fix
* Bring `password_digest` back to fix compatibility with `devise-encryptable`
* Avoid e-mail enumeration on sign in when in paranoid mode
### 3.2.0
* enhancements

View File

@@ -12,7 +12,7 @@ GIT
PATH
remote: .
specs:
devise (3.2.0)
devise (3.2.1)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)

View File

@@ -7,6 +7,7 @@ class Devise::ConfirmationsController < DeviseController
# POST /resource/confirmation
def create
self.resource = resource_class.send_confirmation_instructions(resource_params)
yield resource if block_given?
if successfully_sent?(resource)
respond_with({}, :location => after_resending_confirmation_instructions_path_for(resource_name))
@@ -18,6 +19,7 @@ class Devise::ConfirmationsController < DeviseController
# GET /resource/confirmation?confirmation_token=abcdef
def show
self.resource = resource_class.confirm_by_token(params[:confirmation_token])
yield resource if block_given?
if resource.errors.empty?
set_flash_message(:notice, :confirmed) if is_flashing_format?

View File

@@ -11,6 +11,7 @@ class Devise::PasswordsController < DeviseController
# POST /resource/password
def create
self.resource = resource_class.send_reset_password_instructions(resource_params)
yield resource if block_given?
if successfully_sent?(resource)
respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name))
@@ -28,6 +29,7 @@ class Devise::PasswordsController < DeviseController
# PUT /resource/password
def update
self.resource = resource_class.reset_password_by_token(resource_params)
yield resource if block_given?
if resource.errors.empty?
resource.unlock_access! if unlockable?(resource)

View File

@@ -13,6 +13,7 @@ class Devise::RegistrationsController < DeviseController
build_resource(sign_up_params)
if resource.save
yield resource if block_given?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_up(resource_name, resource)
@@ -41,6 +42,7 @@ class Devise::RegistrationsController < DeviseController
prev_unconfirmed_email = resource.unconfirmed_email if resource.respond_to?(:unconfirmed_email)
if update_resource(resource, account_update_params)
yield resource if block_given?
if is_flashing_format?
flash_key = update_needs_confirmation?(resource, prev_unconfirmed_email) ?
:update_needs_confirmation : :updated
@@ -59,6 +61,7 @@ class Devise::RegistrationsController < DeviseController
resource.destroy
Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name)
set_flash_message :notice, :destroyed if is_flashing_format?
yield resource if block_given?
respond_with_navigational(resource){ redirect_to after_sign_out_path_for(resource_name) }
end

View File

@@ -15,6 +15,7 @@ class Devise::SessionsController < DeviseController
self.resource = warden.authenticate!(auth_options)
set_flash_message(:notice, :signed_in) if is_flashing_format?
sign_in(resource_name, resource)
yield resource if block_given?
respond_with resource, :location => after_sign_in_path_for(resource)
end
@@ -23,6 +24,7 @@ class Devise::SessionsController < DeviseController
redirect_path = after_sign_out_path_for(resource_name)
signed_out = (Devise.sign_out_all_scopes ? sign_out : sign_out(resource_name))
set_flash_message :notice, :signed_out if signed_out && is_flashing_format?
yield resource if block_given?
# We actually need to hardcode this as Rails default responder doesn't
# support returning empty response on GET request

View File

@@ -9,6 +9,7 @@ class Devise::UnlocksController < DeviseController
# POST /resource/unlock
def create
self.resource = resource_class.send_unlock_instructions(resource_params)
yield resource if block_given?
if successfully_sent?(resource)
respond_with({}, :location => after_sending_unlock_instructions_path_for(resource))
@@ -20,6 +21,7 @@ class Devise::UnlocksController < DeviseController
# GET /resource/unlock?unlock_token=abcdef
def show
self.resource = resource_class.unlock_access_by_token(params[:unlock_token])
yield resource if block_given?
if resource.errors.empty?
set_flash_message :notice, :unlocked if is_flashing_format?

View File

@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
devise (3.2.0)
devise (3.2.1)
bcrypt-ruby (~> 3.0)
orm_adapter (~> 0.1)
railties (>= 3.2.6, < 5)

View File

@@ -21,6 +21,7 @@ module Devise
autoload :Rememberable, 'devise/controllers/rememberable'
autoload :ScopedViews, 'devise/controllers/scoped_views'
autoload :SignInOut, 'devise/controllers/sign_in_out'
autoload :StoreLocation, 'devise/controllers/store_location'
autoload :UrlHelpers, 'devise/controllers/url_helpers'
end

View File

@@ -4,6 +4,7 @@ module Devise
module Helpers
extend ActiveSupport::Concern
include Devise::Controllers::SignInOut
include Devise::Controllers::StoreLocation
included do
helper_method :warden, :signed_in?, :devise_controller?
@@ -97,23 +98,6 @@ module Devise
request.env["devise.allow_params_authentication"] = true
end
# Returns and delete (if it's navigational format) the url stored in the session for
# the given scope. Useful for giving redirect backs after sign up:
#
# Example:
#
# redirect_to stored_location_for(:user) || root_path
#
def stored_location_for(resource_or_scope)
scope = Devise::Mapping.find_scope!(resource_or_scope)
if is_navigational_format?
session.delete("#{scope}_return_to")
else
session["#{scope}_return_to"]
end
end
# The scope root url to be used when he's signed in. By default, it first
# tries to find a resource_root_path, otherwise it uses the root_path.
def signed_in_root_path(resource_or_scope)

View File

@@ -0,0 +1,47 @@
require "uri"
module Devise
module Controllers
# Provide the ability to store a location.
# Used to redirect back to a desired path after sign in.
# Included by default in all controllers.
module StoreLocation
# Returns and delete (if it's navigational format) the url stored in the session for
# the given scope. Useful for giving redirect backs after sign up:
#
# Example:
#
# redirect_to stored_location_for(:user) || root_path
#
def stored_location_for(resource_or_scope)
session_key = stored_location_key_for(resource_or_scope)
if is_navigational_format?
session.delete(session_key)
else
session[session_key]
end
end
# Stores the provided location to redirect the user after signing in.
# Useful in combination with the `stored_location_for` helper.
#
# Example:
#
# store_location_for(:user, dashboard_path)
# redirect_to user_omniauth_authorize_path(:facebook)
#
def store_location_for(resource_or_scope, location)
session_key = stored_location_key_for(resource_or_scope)
session[session_key] = URI.parse(location).path if location
end
private
def stored_location_key_for(resource_or_scope)
scope = Devise::Mapping.find_scope!(resource_or_scope)
"#{scope}_return_to"
end
end
end
end

View File

@@ -13,6 +13,8 @@ module Devise
include Rails.application.routes.url_helpers
include Rails.application.routes.mounted_helpers
include Devise::Controllers::StoreLocation
delegate :flash, :to => :request
def self.call(env)
@@ -189,7 +191,7 @@ module Devise
# yet, but we still need to store the uri based on scope, so different scopes
# would never use the same uri to redirect.
def store_location!
session["#{scope}_return_to"] = attempted_path if request.get? && !http_auth?
store_location_for(scope, attempted_path) if request.get? && !http_auth?
end
def is_navigational_format?

View File

@@ -39,7 +39,7 @@ module Devise
# Generates password encryption based on the given value.
def password=(new_password)
@password = new_password
self.encrypted_password = Devise.bcrypt(self.class, @password) if @password.present?
self.encrypted_password = password_digest(@password) if @password.present?
end
# Verifies whether an password (ie from sign in) is the user password.
@@ -135,6 +135,15 @@ module Devise
protected
# Digests the password using bcrypt. Custom encryption should override
# this method to apply their own algorithm.
#
# See https://github.com/plataformatec/devise-encryptable for examples
# of other encryption engines.
def password_digest(password)
Devise.bcrypt(self.class, password)
end
module ClassMethods
Devise::Models.config(self, :pepper, :stretches)

View File

@@ -387,8 +387,23 @@ module ActionDispatch::Routing
def devise_omniauth_callback(mapping, controllers) #:nodoc:
if mapping.fullpath =~ /:[a-zA-Z_]/
raise "[DEVISE] Nesting omniauth callbacks under scopes with dynamic segments " \
"is not supported. Please, use Devise.omniauth_path_prefix instead."
raise <<-ERROR
Devise does not support scoping omniauth callbacks under a dynamic segment
and you have set #{mapping.fullpath.inspect}. You can work around by passing
`skip: :omniauth_callbacks` and manually defining the routes. Here is an example:
match "/users/auth/:provider",
:constraints => { :provider => /\Agoogle|facebook\z/ },
:to => "devise/omniauth_callbacks#passthru",
:as => :omniauth_authorize,
:via => [:get, :post]
match "/users/auth/:action/callback",
:constraints => { :action => /\Agoogle|facebook\z/ },
:to => "devise/omniauth_callbacks",
:as => :omniauth_callback,
:via => [:get, :post]
ERROR
end
path, @scope[:path] = @scope[:path], nil

View File

@@ -5,13 +5,16 @@ module Devise
# Default strategy for signing in a user, based on his email and password in the database.
class DatabaseAuthenticatable < Authenticatable
def authenticate!
resource = valid_password? && mapping.to.find_for_database_authentication(authentication_hash)
return fail(:not_found_in_database) unless resource
resource = valid_password? && mapping.to.find_for_database_authentication(authentication_hash)
encrypted = false
if validate(resource){ resource.valid_password?(password) }
if validate(resource){ encrypted = true; resource.valid_password?(password) }
resource.after_database_authentication
success!(resource)
end
mapping.to.new.password = password if !encrypted && Devise.paranoid
fail(:not_found_in_database) unless resource
end
end
end

View File

@@ -1,3 +1,3 @@
module Devise
VERSION = "3.2.0".freeze
VERSION = "3.2.1".freeze
end

View File

@@ -187,6 +187,23 @@ class ControllerAuthenticatableTest < ActionController::TestCase
assert_nil @controller.session[:"user_return_to"]
end
test 'store location for stores a location to redirect back to' do
assert_nil @controller.stored_location_for(:user)
@controller.store_location_for(:user, "/foo.bar")
assert_equal "/foo.bar", @controller.stored_location_for(:user)
end
test 'store location for accepts a resource as argument' do
@controller.store_location_for(User.new, "/foo.bar")
assert_equal "/foo.bar", @controller.stored_location_for(User.new)
end
test 'store location for stores only paths' do
assert_nil @controller.stored_location_for(:user)
@controller.store_location_for(:user, "//host/foo.bar")
assert_equal "/foo.bar", @controller.stored_location_for(:user)
end
test 'after sign in path defaults to root path if none by was specified for the given scope' do
assert_equal root_path, @controller.after_sign_in_path_for(:user)
end

View File

@@ -93,6 +93,11 @@ class DatabaseAuthenticatableTest < ActiveSupport::TestCase
assert_present user.encrypted_password
end
test 'should support custom encryption methods' do
user = UserWithCustomEncryption.new(:password => '654321')
assert_equal user.encrypted_password, '123456'
end
test 'allow authenticatable_salt to work even with nil encrypted password' do
user = User.new
user.encrypted_password = nil

View File

@@ -2,7 +2,13 @@ unless defined?(DEVISE_ORM)
DEVISE_ORM = (ENV["DEVISE_ORM"] || :active_record).to_sym
end
module Devise
# Detection for minor differences between Rails 3.2 and 4 in tests.
def self.rails4?
Rails.version.start_with? '4'
end
end
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__)
require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])

View File

@@ -22,10 +22,6 @@ RailsApp::Application.configure do
# Only use best-standards-support built into browsers.
config.action_dispatch.best_standards_support = :builtin
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL).
config.active_record.auto_explain_threshold_in_seconds = 0.5
# Raise an error on page load if there are pending migrations
config.active_record.migration_error = :page_load

View File

@@ -72,10 +72,6 @@ RailsApp::Application.configure do
# Send deprecation notices to registered listeners.
config.active_support.deprecation = :notify
# Log the query plan for queries taking more than this (works
# with SQLite, MySQL, and PostgreSQL).
# config.active_record.auto_explain_threshold_in_seconds = 0.5
# Disable automatic flushing of the log to improve performance.
# config.autoflush_log = false

View File

@@ -1,3 +1,4 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
@@ -8,40 +9,43 @@
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended to check this file into your version control system.
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(:version => 20100401102949) do
ActiveRecord::Schema.define(version: 20100401102949) do
create_table "admins", :force => true do |t|
create_table "admins", force: true do |t|
t.string "email"
t.string "encrypted_password", :limit => 128
t.string "password_salt"
t.string "remember_token"
t.datetime "remember_created_at"
t.string "encrypted_password"
t.string "reset_password_token"
t.integer "failed_attempts", :default => 0
t.string "unlock_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "unconfirmed_email"
t.datetime "locked_at"
t.boolean "active", default: false
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
create_table "users", force: true do |t|
t.string "username"
t.string "facebook_token"
t.string "email", :default => "", :null => false
t.string "encrypted_password", :limit => 128, :default => "", :null => false
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", :default => 0
t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.integer "failed_attempts", :default => 0
t.string "confirmation_token"
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
t.integer "failed_attempts", default: 0
t.string "unlock_token"
t.datetime "locked_at"
t.datetime "created_at"

View File

@@ -4,13 +4,6 @@ DEVISE_ORM = (ENV["DEVISE_ORM"] || :active_record).to_sym
$:.unshift File.dirname(__FILE__)
puts "\n==> Devise.orm = #{DEVISE_ORM.inspect}"
module Devise
# Detection for minor differences between Rails 3.2 and 4 in tests.
def self.rails4?
Rails.version.start_with? '4'
end
end
require "rails_app/config/environment"
require "rails/test_help"
require "orm/#{DEVISE_ORM}"

View File

@@ -12,6 +12,13 @@ class UserWithValidation < User
validates_presence_of :username
end
class UserWithCustomEncryption < User
protected
def password_digest(password)
password.reverse
end
end
class UserWithVirtualAttributes < User
devise :case_insensitive_keys => [ :email, :email_confirmation ]
validates :email, :presence => true, :confirmation => {:on => :create}