devise 1.0.8 → 1.0.9

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of devise might be problematic. Click here for more details.

@@ -1,3 +1,13 @@
1
+ == 1.0.9
2
+
3
+ * enhancements
4
+ * Extracted redirect path from Devise failure app to a new method, allowing override in custom failure apps
5
+ * Added sign_out_via
6
+
7
+ * bug fix
8
+ * Email is now case insensitive
9
+ * Avoid session fixation attacks
10
+
1
11
  == 1.0.8
2
12
 
3
13
  * enhancements
@@ -32,11 +32,11 @@ Devise is based on Warden (http://github.com/hassox/warden), a Rack Authenticati
32
32
 
33
33
  Install warden gem if you don't have it installed:
34
34
 
35
- sudo gem install warden
35
+ gem install warden
36
36
 
37
37
  Install devise gem:
38
38
 
39
- sudo gem install devise --version=1.0.7
39
+ gem install devise --version=1.0.8
40
40
 
41
41
  Configure warden and devise gems inside your app:
42
42
 
@@ -240,6 +240,16 @@ Devise supports both ActiveRecord (default) and MongoMapper, and has experimenta
240
240
 
241
241
  Please refer to TODO file.
242
242
 
243
+ == Security
244
+
245
+ Needless to say, security is extremely important to Devise. If you find yourself in a possible security issue with Devise, please go through the following steps, trying to reproduce the bug:
246
+
247
+ 1) Look at the source code a bit to find out whether your assumptions are correct;
248
+ 2) If possible, provide a way to reproduce the bug: a small app on Github or a step-by-step to reproduce;
249
+ 3) E-mail us or send a Github private message instead of using the normal issues;
250
+
251
+ Being able to reproduce the bug is the first step to fix it. Thanks for your understanding.
252
+
243
253
  == Maintainers
244
254
 
245
255
  * José Valim (http://github.com/josevalim)
data/Rakefile CHANGED
@@ -37,7 +37,7 @@ begin
37
37
  require 'jeweler'
38
38
  Jeweler::Tasks.new do |s|
39
39
  s.name = "devise"
40
- s.version = Devise::VERSION
40
+ s.version = Devise::VERSION.dup
41
41
  s.summary = "Flexible authentication solution for Rails with Warden"
42
42
  s.email = "contact@plataformatec.com.br"
43
43
  s.homepage = "http://github.com/plataformatec/devise"
@@ -183,7 +183,9 @@ module Devise
183
183
 
184
184
  # Configure default url options to be used within Devise and ActionController.
185
185
  def default_url_options(&block)
186
- Devise::Mapping.metaclass.send :define_method, :default_url_options, &block
186
+ who = Devise::Mapping.respond_to?(:singleton_class) ?
187
+ Devise::Mapping.singleton_class : Devise::Mapping.metaclass
188
+ who.send :define_method, :default_url_options, &block
187
189
  end
188
190
 
189
191
  # A method used internally to setup warden manager from the Rails initialize
@@ -66,6 +66,7 @@ module Devise
66
66
  scope = Devise::Mapping.find_scope!(resource_or_scope)
67
67
  resource ||= resource_or_scope
68
68
  warden.set_user(resource, :scope => scope)
69
+ @_session = request.session # Recalculate session
69
70
  end
70
71
 
71
72
  # Sign out a given user or scope. This helper is useful for signing out an user
@@ -92,7 +93,8 @@ module Devise
92
93
  #
93
94
  def stored_location_for(resource_or_scope)
94
95
  scope = Devise::Mapping.find_scope!(resource_or_scope)
95
- session.delete(:"#{scope}.return_to")
96
+ key = "#{scope}.return_to"
97
+ session.delete(key) || session.delete(key.to_sym)
96
98
  end
97
99
 
98
100
  # The default url to be used after signing in. This is used by all Devise
@@ -105,13 +107,13 @@ module Devise
105
107
  #
106
108
  # map.user_root '/users', :controller => 'users' # creates user_root_path
107
109
  #
108
- # map.resources :users do |users|
109
- # users.root # creates user_root_path
110
+ # map.namespace :user do |user|
111
+ # user.root :controller => 'users' # creates user_root_path
110
112
  # end
111
113
  #
112
114
  #
113
- # If none of these are defined, root_path is used. However, if this default
114
- # is not enough, you can customize it, for example:
115
+ # If the resource root path is not defined, root_path is used. However,
116
+ # if this default is not enough, you can customize it, for example:
115
117
  #
116
118
  # def after_sign_in_path_for(resource)
117
119
  # if resource.is_a?(User) && resource.can_publish?
@@ -123,7 +125,7 @@ module Devise
123
125
  #
124
126
  def after_sign_in_path_for(resource_or_scope)
125
127
  scope = Devise::Mapping.find_scope!(resource_or_scope)
126
- home_path = :"#{scope}_root_path"
128
+ home_path = "#{scope}_root_path"
127
129
  respond_to?(home_path, true) ? send(home_path) : root_path
128
130
  end
129
131
 
@@ -145,7 +147,11 @@ module Devise
145
147
  def sign_in_and_redirect(resource_or_scope, resource=nil, skip=false)
146
148
  scope = Devise::Mapping.find_scope!(resource_or_scope)
147
149
  resource ||= resource_or_scope
148
- sign_in(scope, resource) unless skip
150
+ if skip
151
+ @_session = request.session # Recalculate session
152
+ else
153
+ sign_in(scope, resource)
154
+ end
149
155
  redirect_to stored_location_for(scope) || after_sign_in_path_for(resource)
150
156
  end
151
157
 
@@ -173,7 +179,7 @@ module Devise
173
179
  # user_signed_in? # Checks whether there is an user signed in or not
174
180
  # admin_signed_in? # Checks whether there is an admin signed in or not
175
181
  # current_user # Current signed in user
176
- # current_admin # Currend signed in admin
182
+ # current_admin # Current signed in admin
177
183
  # user_session # Session data available only to the user scope
178
184
  # admin_session # Session data available only to the admin scope
179
185
  #
@@ -22,12 +22,8 @@ module Devise
22
22
  options = @env['warden.options']
23
23
  scope = options[:scope]
24
24
 
25
- redirect_path = if mapping = Devise.mappings[scope]
26
- "#{mapping.parsed_path}/#{mapping.path_names[:sign_in]}"
27
- else
28
- "/#{default_url}"
29
- end
30
- query_string = query_string_for(options)
25
+ redirect_path = redirect_path_for(scope)
26
+ query_string = query_string_for(options)
31
27
  store_location!(scope)
32
28
 
33
29
  headers = {}
@@ -54,6 +50,15 @@ module Devise
54
50
  Rack::Utils.build_query(params)
55
51
  end
56
52
 
53
+ # Build the path based on current scope.
54
+ def redirect_path_for(scope)
55
+ if mapping = Devise.mappings[scope]
56
+ "#{mapping.parsed_path}/#{mapping.path_names[:sign_in]}"
57
+ else
58
+ "/#{default_url}"
59
+ end
60
+ end
61
+
57
62
  # Stores requested uri to redirect the user after signing in. We cannot use
58
63
  # scoped session provided by warden here, since the user is not authenticated
59
64
  # yet, but we still need to store the uri based on scope, so different scopes
@@ -22,7 +22,7 @@ module Devise
22
22
  # # is the modules included in the class
23
23
  #
24
24
  class Mapping #:nodoc:
25
- attr_reader :name, :as, :path_names, :path_prefix, :route_options
25
+ attr_reader :name, :as, :path_names, :path_prefix, :route_options, :sign_out_via
26
26
 
27
27
  # Loop through all mappings looking for a map that matches with the requested
28
28
  # path (ie /users/sign_in). If a path prefix is given, it's taken into account.
@@ -64,6 +64,8 @@ module Devise
64
64
 
65
65
  @path_names = Hash.new { |h,k| h[k] = k.to_s }
66
66
  @path_names.merge!(options.delete(:path_names) || {})
67
+
68
+ @sign_out_via = (options.delete(:sign_out_via) || :get)
67
69
  end
68
70
 
69
71
  # Return modules for the mapping.
@@ -96,7 +98,7 @@ module Devise
96
98
 
97
99
  # Returns the parsed path taking into account the relative url root and raw path.
98
100
  def parsed_path
99
- returning (ActionController::Base.relative_url_root.to_s + raw_path) do |path|
101
+ (ActionController::Base.relative_url_root.to_s + raw_path).tap do |path|
100
102
  self.class.default_url_options.each do |key, value|
101
103
  path.gsub!(key.inspect, value.to_param)
102
104
  end
@@ -57,7 +57,7 @@ module Devise
57
57
 
58
58
  # Send confirmation instructions by email
59
59
  def send_confirmation_instructions
60
- generate_confirmation_token if self.confirmation_token.nil?
60
+ generate_confirmation_token! if self.confirmation_token.nil?
61
61
  ::DeviseMailer.deliver_confirmation_instructions(self)
62
62
  end
63
63
 
@@ -135,6 +135,10 @@ module Devise
135
135
  self.confirmation_sent_at = Time.now.utc
136
136
  end
137
137
 
138
+ def generate_confirmation_token!
139
+ generate_confirmation_token && save(false)
140
+ end
141
+
138
142
  module ClassMethods
139
143
  # Attempt to find a user by it's email. If a record is found, send new
140
144
  # confirmation instructions to it. If not user is found, returns a new user
@@ -15,7 +15,7 @@ module Devise
15
15
 
16
16
  base.class_eval do
17
17
  validates_presence_of :email
18
- validates_uniqueness_of :email, :scope => authentication_keys[1..-1], :allow_blank => true
18
+ validates_uniqueness_of :email, :scope => authentication_keys[1..-1], :case_sensitive => false, :allow_blank => true
19
19
  validates_format_of :email, :with => EMAIL_REGEX, :allow_blank => true
20
20
 
21
21
  with_options :if => :password_required? do |v|
@@ -66,6 +66,12 @@ module ActionController::Routing
66
66
  #
67
67
  # map.devise_for :users, :path_prefix => "/:locale"
68
68
  #
69
+ # * :sign_out_via => restirct the HTTP method(s) accepted for the :sign_out action (default: :get), possible values are :post, :get, :put, :delete and :any, e.g. if you wish to restrict this to accept only :delete requests you should do:
70
+ #
71
+ # map.devise_for :users, :sign_out_via => :delete
72
+ #
73
+ # You need to make sure that your sign_out controls trigger a request with a matching HTTP method.
74
+ #
69
75
  # Any other options will be passed to route definition. If you need conditions for your routes, just map:
70
76
  #
71
77
  # map.devise_for :users, :conditions => { :subdomain => /.+/ }
@@ -101,7 +107,9 @@ module ActionController::Routing
101
107
  routes.with_options(:controller => 'sessions', :name_prefix => nil) do |session|
102
108
  session.send(:"new_#{mapping.name}_session", mapping.path_names[:sign_in], :action => 'new', :conditions => { :method => :get })
103
109
  session.send(:"#{mapping.name}_session", mapping.path_names[:sign_in], :action => 'create', :conditions => { :method => :post })
104
- session.send(:"destroy_#{mapping.name}_session", mapping.path_names[:sign_out], :action => 'destroy', :conditions => { :method => :get })
110
+ destroy_options = { :action => 'destroy' }
111
+ destroy_options.merge! :conditions => { :method => mapping.sign_out_via } unless mapping.sign_out_via == :any
112
+ session.send(:"destroy_#{mapping.name}_session", mapping.path_names[:sign_out], destroy_options)
105
113
  end
106
114
  end
107
115
 
@@ -22,4 +22,39 @@ class Warden::SessionSerializer
22
22
  klass, id = keys
23
23
  klass.find(:first, :conditions => { :id => id })
24
24
  end
25
+ end
26
+
27
+ class ActionController::Request
28
+ def reset_session
29
+ session.destroy if session && session.respond_to?(:destroy)
30
+ self.session = {}
31
+ end
32
+ end
33
+
34
+ # Solve a bug in Rails where Set-Cookie is returning an array.
35
+ class Devise::CookieSanitizer
36
+ SET_COOKIE = "Set-Cookie".freeze
37
+
38
+ def initialize(app)
39
+ @app = app
40
+ end
41
+
42
+ def call(env)
43
+ response = @app.call(env)
44
+ headers = response[1]
45
+ headers[SET_COOKIE] = headers[SET_COOKIE].join("\n") if headers[SET_COOKIE].respond_to?(:join)
46
+ response
47
+ end
48
+ end
49
+
50
+ Rails.configuration.middleware.insert_after ActionController::Failsafe, Devise::CookieSanitizer
51
+
52
+ Warden::Manager.after_set_user :event => [:set_user, :authentication] do |record, warden, options|
53
+ if options[:scope] && warden.authenticated?(options[:scope])
54
+ request = warden.request
55
+ backup = request.session.to_hash
56
+ backup.delete(:session_id)
57
+ request.reset_session
58
+ request.session.update(backup)
59
+ end
25
60
  end
@@ -1,3 +1,3 @@
1
1
  module Devise
2
- VERSION = "1.0.8".freeze
2
+ VERSION = "1.0.9".freeze
3
3
  end
@@ -210,6 +210,17 @@ class AuthenticationTest < ActionController::IntegrationTest
210
210
  assert_equal "Cart", @controller.user_session[:cart]
211
211
  end
212
212
 
213
+ test 'session id is changed on sign in' do
214
+ get '/users'
215
+ session_id = request.session[:session_id]
216
+
217
+ get '/users'
218
+ assert_equal session_id, request.session[:session_id]
219
+
220
+ sign_in_as_user
221
+ assert_not_equal session_id, request.session[:session_id]
222
+ end
223
+
213
224
  test 'renders the scoped view if turned on and view is available' do
214
225
  swap Devise, :scoped_views => true do
215
226
  assert_raise Webrat::NotFoundError do
@@ -269,3 +280,53 @@ class AuthenticationTest < ActionController::IntegrationTest
269
280
  end
270
281
  end
271
282
  end
283
+
284
+ class AuthenticationSignOutViaTest < ActionController::IntegrationTest
285
+ def sign_in!(scope)
286
+ visit send("new_#{scope}_session_path")
287
+ sign_in_as_user(:visit => false)
288
+ assert warden.authenticated?(scope)
289
+ end
290
+
291
+ test 'allow sign out via delete when sign_out_via provides only delete' do
292
+ sign_in!(:sign_out_via_delete)
293
+ delete destroy_sign_out_via_delete_session_path
294
+ assert_not warden.authenticated?(:sign_out_via_delete)
295
+ end
296
+
297
+ test 'do not allow sign out via get when sign_out_via provides only delete' do
298
+ sign_in!(:sign_out_via_delete)
299
+ get destroy_sign_out_via_delete_session_path
300
+ assert warden.authenticated?(:sign_out_via_delete)
301
+ end
302
+
303
+ test 'allow sign out via post when sign_out_via provides only post' do
304
+ sign_in!(:sign_out_via_post)
305
+ post destroy_sign_out_via_post_session_path
306
+ assert_not warden.authenticated?(:sign_out_via_post)
307
+ end
308
+
309
+ test 'do not allow sign out via get when sign_out_via provides only post' do
310
+ sign_in!(:sign_out_via_post)
311
+ get destroy_sign_out_via_delete_session_path
312
+ assert warden.authenticated?(:sign_out_via_post)
313
+ end
314
+
315
+ test 'allow sign out via delete when sign_out_via provides any method' do
316
+ sign_in!(:sign_out_via_anymethod)
317
+ delete destroy_sign_out_via_anymethod_session_path
318
+ assert_not warden.authenticated?(:sign_out_via_anymethod)
319
+ end
320
+
321
+ test 'allow sign out via post when sign_out_via provides any method' do
322
+ sign_in!(:sign_out_via_anymethod)
323
+ post destroy_sign_out_via_anymethod_session_path
324
+ assert_not warden.authenticated?(:sign_out_via_anymethod)
325
+ end
326
+
327
+ test 'allow sign out via get when sign_out_via provides any method' do
328
+ sign_in!(:sign_out_via_anymethod)
329
+ get destroy_sign_out_via_anymethod_session_path
330
+ assert_not warden.authenticated?(:sign_out_via_anymethod)
331
+ end
332
+ end
@@ -132,6 +132,16 @@ class MappingTest < ActiveSupport::TestCase
132
132
  assert_equal({ :requirements => { :extra => 'value' } }, Devise.mappings[:manager].route_options)
133
133
  end
134
134
 
135
+ test 'sign_out_via defaults to :get' do
136
+ assert_equal :get, Devise.mappings[:user].sign_out_via
137
+ end
138
+
139
+ test 'allows custom sign_out_via to be given' do
140
+ assert_equal :delete, Devise.mappings[:sign_out_via_delete].sign_out_via
141
+ assert_equal :post, Devise.mappings[:sign_out_via_post].sign_out_via
142
+ assert_equal :any, Devise.mappings[:sign_out_via_anymethod].sign_out_via
143
+ end
144
+
135
145
  test 'magic predicates' do
136
146
  mapping = Devise.mappings[:user]
137
147
  assert mapping.authenticatable?
@@ -140,7 +140,7 @@ class ConfirmableTest < ActiveSupport::TestCase
140
140
  user.instance_eval { def confirmation_required?; false end }
141
141
  user.save
142
142
  user.send_confirmation_instructions
143
- assert_not_nil user.confirmation_token
143
+ assert_not_nil user.reload.confirmation_token
144
144
  end
145
145
 
146
146
  test 'should not resend email instructions if the user change his email' do
@@ -1,7 +1,7 @@
1
1
  require 'test/test_helper'
2
2
 
3
3
  class Configurable < User
4
- devise :authenticatable, :confirmable, :rememberable, :timeoutable, :lockable,
4
+ devise :database_authenticatable, :confirmable, :rememberable, :timeoutable, :lockable,
5
5
  :stretches => 15, :pepper => 'abcdef', :confirm_within => 5.days,
6
6
  :remember_for => 7.days, :timeout_in => 15.minutes, :unlock_in => 10.days
7
7
  end
@@ -8,7 +8,7 @@ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":me
8
8
  ActiveRecord::Schema.define(:version => 1) do
9
9
  [:users, :admins, :accounts].each do |table|
10
10
  create_table table do |t|
11
- t.authenticatable :null => table == :admins
11
+ t.database_authenticatable :null => table == :admins
12
12
 
13
13
  if table != :admin
14
14
  t.string :username
@@ -1,5 +1,5 @@
1
1
  class Admin < ActiveRecord::Base
2
- devise :authenticatable, :registerable, :timeoutable
2
+ devise :database_authenticatable, :registerable, :timeoutable
3
3
 
4
4
  def self.find_for_authentication(conditions)
5
5
  last(:conditions => conditions)
@@ -1,7 +1,7 @@
1
1
  class User < ActiveRecord::Base
2
- devise :authenticatable, :http_authenticatable, :confirmable, :lockable, :recoverable,
3
- :registerable, :rememberable, :timeoutable, :token_authenticatable,
4
- :trackable, :validatable
2
+ devise :database_authenticatable, :http_authenticatable, :confirmable,
3
+ :lockable, :recoverable, :registerable, :rememberable, :timeoutable,
4
+ :token_authenticatable, :trackable, :validatable
5
5
 
6
6
  attr_accessible :username, :email, :password, :password_confirmation
7
7
  end
@@ -1,7 +1,7 @@
1
1
  # Be sure to restart your server when you modify this file
2
2
 
3
3
  # Specifies gem version of Rails to use when vendor/rails is not present
4
- RAILS_GEM_VERSION = '2.3.5' unless defined? RAILS_GEM_VERSION
4
+ RAILS_GEM_VERSION = '2.3.10' unless defined? RAILS_GEM_VERSION
5
5
  DEVISE_ORM = :active_record unless defined? DEVISE_ORM
6
6
 
7
7
  # Bootstrap the Rails environment, frameworks, and default configuration
@@ -13,7 +13,7 @@ Rails::Initializer.run do |config|
13
13
  # -- all .rb files in that directory are automatically loaded.
14
14
 
15
15
  # Add additional load paths for your own custom dirs
16
- config.load_paths += [ "#{RAILS_ROOT}/app/#{DEVISE_ORM}/" ]
16
+ config.autoload_paths += [ "#{RAILS_ROOT}/app/#{DEVISE_ORM}/" ]
17
17
 
18
18
  # Specify gems that this application depends on and have them installed with rake gems:install
19
19
  # config.gem "bj"
@@ -12,6 +12,10 @@ ActionController::Routing::Routes.draw do |map|
12
12
  map.resources :admins, :only => :index
13
13
  map.root :controller => :home
14
14
 
15
+ map.devise_for :sign_out_via_deletes, :sign_out_via => :delete, :class_name => "User"
16
+ map.devise_for :sign_out_via_posts, :sign_out_via => :post, :class_name => "User"
17
+ map.devise_for :sign_out_via_anymethods, :sign_out_via => :any, :class_name => "User"
18
+
15
19
  map.connect '/admin_area/password/new', :controller => "passwords", :action => "new"
16
20
  map.admin_root '/admin_area/home', :controller => "admins", :action => "index"
17
21
 
@@ -107,4 +107,25 @@ class MapRoutingTest < ActionController::TestCase
107
107
  test 'map account with custom path name for registration' do
108
108
  assert_recognizes({:controller => 'registrations', :action => 'new', :locale => 'en', :extra => 'value'}, '/en/accounts/register')
109
109
  end
110
+
111
+ test 'map deletes with :sign_out_via option' do
112
+ assert_recognizes({:controller => 'sessions', :action => 'destroy'}, {:path => '/sign_out_via_deletes/sign_out', :method => :delete})
113
+ assert_raise ActionController::MethodNotAllowed do
114
+ assert_recognizes({:controller => 'sessions', :action => 'destroy'}, {:path => '/sign_out_via_deletes/sign_out', :method => :get})
115
+ end
116
+ end
117
+
118
+ test 'map posts with :sign_out_via option' do
119
+ assert_recognizes({:controller => 'sessions', :action => 'destroy'}, {:path => '/sign_out_via_posts/sign_out', :method => :post})
120
+ assert_raise ActionController::MethodNotAllowed do
121
+ assert_recognizes({:controller => 'sessions', :action => 'destroy'}, {:path => '/sign_out_via_posts/sign_out', :method => :get})
122
+ end
123
+ end
124
+
125
+ test 'map any methods with :sign_out_via option' do
126
+ assert_recognizes({:controller => 'sessions', :action => 'destroy'}, {:path => '/sign_out_via_anymethods/sign_out', :method => :get})
127
+ assert_recognizes({:controller => 'sessions', :action => 'destroy'}, {:path => '/sign_out_via_anymethods/sign_out', :method => :post})
128
+ assert_recognizes({:controller => 'sessions', :action => 'destroy'}, {:path => '/sign_out_via_anymethods/sign_out', :method => :delete})
129
+ assert_recognizes({:controller => 'sessions', :action => 'destroy'}, {:path => '/sign_out_via_anymethods/sign_out', :method => :put})
130
+ end
110
131
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise
3
3
  version: !ruby/object:Gem::Version
4
- hash: 7
4
+ hash: 5
5
5
  prerelease: false
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
- - 8
10
- version: 1.0.8
9
+ - 9
10
+ version: 1.0.9
11
11
  platform: ruby
12
12
  authors:
13
13
  - "Jos\xC3\xA9 Valim"
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2010-06-23 00:00:00 +02:00
19
+ date: 2010-11-26 00:00:00 +01:00
20
20
  default_executable:
21
21
  dependencies:
22
22
  - !ruby/object:Gem::Dependency