devise 0.9.1 → 0.9.2

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
+ == 0.9.2
2
+
3
+ * bug fix
4
+ * Ensure inactive user cannot sign in
5
+ * Ensure redirect to proper url after sign up
6
+
7
+ * enhancements
8
+ * Added gemspec to repo
9
+ * Added token authenticatable (by github.com/grimen)
10
+
1
11
  == 0.9.1
2
12
 
3
13
  * bug fix
@@ -18,7 +18,7 @@ class SessionsController < ApplicationController
18
18
  set_flash_message :notice, :signed_in
19
19
  sign_in_and_redirect(resource_name, resource, true)
20
20
  else
21
- set_now_flash_message :alert, warden.message || :invalid
21
+ set_now_flash_message :alert, (warden.message || :invalid)
22
22
  build_resource
23
23
  render_with_scope :new
24
24
  end
@@ -53,6 +53,10 @@ Devise.setup do |config|
53
53
  # Time interval to unlock the account if :time is enabled as unlock_strategy.
54
54
  # config.unlock_in = 1.hour
55
55
 
56
+ # ==> Configuration for :token_authenticatable
57
+ # Defines name of the authentication token params key
58
+ # config.token_authentication_key = :auth_token
59
+
56
60
  # ==> General configuration
57
61
  # Load and configure the ORM. Supports :active_record (default), :mongo_mapper
58
62
  # (requires mongo_ext installed) and :data_mapper (experimental).
@@ -26,22 +26,34 @@ module Devise
26
26
  autoload :MongoMapper, 'devise/orm/mongo_mapper'
27
27
  end
28
28
 
29
- ALL = [:authenticatable, :activatable, :confirmable, :recoverable,
30
- :rememberable, :validatable, :trackable, :timeoutable, :lockable]
29
+ ALL = []
30
+
31
+ # Authentication ones first
32
+ ALL.push :authenticatable, :token_authenticatable, :rememberable
33
+
34
+ # Misc after
35
+ ALL.push :recoverable, :validatable
36
+
37
+ # The ones which can sign out after
38
+ ALL.push :activatable, :confirmable, :lockable, :timeoutable
39
+
40
+ # Stats for last, so we make sure the user is really signed in
41
+ ALL.push :trackable
31
42
 
32
43
  # Maps controller names to devise modules
33
44
  CONTROLLERS = {
34
- :sessions => [:authenticatable],
45
+ :sessions => [:authenticatable, :token_authenticatable],
35
46
  :passwords => [:recoverable],
36
47
  :confirmations => [:confirmable],
37
48
  :unlocks => [:lockable]
38
49
  }
39
50
 
40
- STRATEGIES = [:rememberable, :authenticatable]
51
+ STRATEGIES = [:rememberable, :token_authenticatable, :authenticatable]
52
+
41
53
  TRUE_VALUES = [true, 1, '1', 't', 'T', 'true', 'TRUE']
42
54
 
43
55
  # Maps the messages types that are used in flash message.
44
- FLASH_MESSAGES = [ :unauthenticated, :unconfirmed, :invalid, :timeout, :inactive, :locked ]
56
+ FLASH_MESSAGES = [ :unauthenticated, :unconfirmed, :invalid, :invalid_token, :timeout, :inactive, :locked ]
45
57
 
46
58
  # Declare encryptors length which are used in migrations.
47
59
  ENCRYPTORS_LENGTH = {
@@ -53,8 +65,8 @@ module Devise
53
65
  :bcrypt => 60
54
66
  }
55
67
 
56
- # Email regex used to validate email formats. Retrieved from authlogic.
57
- EMAIL_REGEX = /\A[\w\.%\+\-]+@(?:[A-Z0-9\-]+\.)+(?:[A-Z]{2,4}|museum|travel)\z/i
68
+ # Email regex used to validate email formats. Adapted from authlogic.
69
+ EMAIL_REGEX = /^([\w\.%\+\-]+)@([\w\-]+\.)+([\w]{2,})$/i
58
70
 
59
71
  # Used to encrypt password. Please generate one with rake secret.
60
72
  mattr_accessor :pepper
@@ -131,6 +143,10 @@ module Devise
131
143
  mattr_accessor :mailer_sender
132
144
  @@mailer_sender
133
145
 
146
+ # Authentication token params key name of choice. E.g. /users/sign_in?some_key=...
147
+ mattr_accessor :token_authentication_key
148
+ @@token_authentication_key = :auth_token
149
+
134
150
  class << self
135
151
  # Default way to setup Devise. Run script/generate devise_install to create
136
152
  # a fresh initializer with all configuration values.
@@ -59,7 +59,7 @@ module Devise
59
59
  # yet, but we still need to store the uri based on scope, so different scopes
60
60
  # would never use the same uri to redirect.
61
61
  def store_location!(scope)
62
- session[:"#{scope}.return_to"] ||= request.request_uri if request && request.get?
62
+ session[:"#{scope}.return_to"] = request.request_uri if request && request.get?
63
63
  end
64
64
  end
65
65
  end
@@ -8,6 +8,7 @@ en:
8
8
  unconfirmed: 'You have to confirm your account before continuing.'
9
9
  locked: 'Your account is locked.'
10
10
  invalid: 'Invalid email or password.'
11
+ invalid_token: 'Invalid authentication token.'
11
12
  timeout: 'Your session expired, please sign in again to continue.'
12
13
  inactive: 'Your account was not activated yet.'
13
14
  passwords:
@@ -36,7 +36,8 @@ module Devise
36
36
  end
37
37
  end
38
38
 
39
- # Regenerates password salt and encrypted password each time password is set.
39
+ # Regenerates password salt and encrypted password each time password is set,
40
+ # and then trigger any "after_changed_password"-callbacks.
40
41
  def password=(new_password)
41
42
  @password = new_password
42
43
 
@@ -48,7 +49,13 @@ module Devise
48
49
 
49
50
  # Verifies whether an incoming_password (ie from sign in) is the user password.
50
51
  def valid_password?(incoming_password)
51
- password_digest(incoming_password) == encrypted_password
52
+ password_digest(incoming_password) == self.encrypted_password
53
+ end
54
+
55
+ # Verifies whether an +incoming_authentication_token+ (i.e. from single access URL)
56
+ # is the user authentication token.
57
+ def valid_authentication_token?(incoming_auth_token)
58
+ incoming_auth_token == self.authentication_token
52
59
  end
53
60
 
54
61
  # Checks if a resource is valid upon authentication.
@@ -75,6 +82,9 @@ module Devise
75
82
  end
76
83
 
77
84
  module ClassMethods
85
+
86
+ Devise::Models.config(self, :pepper, :stretches, :encryptor, :authentication_keys)
87
+
78
88
  # Authenticate a user based on configured attribute keys. Returns the
79
89
  # authenticated user if it's valid or nil. Attributes are by default
80
90
  # :email and :password, but the latter is always required.
@@ -106,7 +116,6 @@ module Devise
106
116
  find(:first, :conditions => conditions)
107
117
  end
108
118
 
109
- Devise::Models.config(self, :pepper, :stretches, :encryptor, :authentication_keys)
110
119
  end
111
120
  end
112
121
  end
@@ -0,0 +1,89 @@
1
+ require 'devise/strategies/token_authenticatable'
2
+
3
+ module Devise
4
+ module Models
5
+ # Token Authenticatable Module, responsible for generate authentication token and validating
6
+ # authenticity of a user while signing in using a authentication token (say follows an URL).
7
+ #
8
+ # == Configuration:
9
+ #
10
+ # You can overwrite configuration values by setting in globally in Devise (+Devise.setup+),
11
+ # using devise method, or overwriting the respective instance method.
12
+ #
13
+ # +token_authentication_key+ - Defines name of the authentication token params key. E.g. /users/sign_in?some_key=...
14
+ #
15
+ # == Examples:
16
+ #
17
+ # User.authenticate_with_token(:auth_token => '123456789') # returns authenticated user or nil
18
+ # User.find(1).valid_authentication_token?('rI1t6PKQ8yP7VetgwdybB') # returns true/false
19
+ #
20
+ module TokenAuthenticatable
21
+ def self.included(base)
22
+ base.class_eval do
23
+ extend ClassMethods
24
+ before_save :ensure_authentication_token
25
+ end
26
+ end
27
+
28
+ # Generate new authentication token (a.k.a. "single access token").
29
+ def reset_authentication_token
30
+ self.authentication_token = self.class.authentication_token
31
+ end
32
+
33
+ # Generate new authentication token and save the record.
34
+ def reset_authentication_token!
35
+ reset_authentication_token
36
+ self.save
37
+ end
38
+
39
+ # Generate authentication token unless already exists.
40
+ def ensure_authentication_token
41
+ self.reset_authentication_token if self.authentication_token.blank?
42
+ end
43
+
44
+ # Generate authentication token unless already exists and save the record.
45
+ def ensure_authentication_token!
46
+ self.reset_authentication_token! if self.authentication_token.blank?
47
+ end
48
+
49
+ # Verifies whether an +incoming_authentication_token+ (i.e. from single access URL)
50
+ # is the user authentication token.
51
+ def valid_authentication_token?(incoming_auth_token)
52
+ incoming_auth_token.present? && incoming_auth_token == self.authentication_token
53
+ end
54
+
55
+ module ClassMethods
56
+ ::Devise::Models.config(self, :token_authentication_key)
57
+
58
+ # Authenticate a user based on authentication token.
59
+ def authenticate_with_token(attributes)
60
+ token = attributes[self.token_authentication_key]
61
+ resource = self.find_for_token_authentication(token)
62
+ resource if resource.try(:valid_authentication_token?, token)
63
+ end
64
+
65
+ def authentication_token
66
+ ::Devise.friendly_token
67
+ end
68
+
69
+ protected
70
+
71
+ # Find first record based on conditions given (ie by the sign in form).
72
+ # Overwrite to add customized conditions, create a join, or maybe use a
73
+ # namedscope to filter records while authenticating.
74
+ #
75
+ # == Example:
76
+ #
77
+ # def self.find_for_token_authentication(token, conditions = {})
78
+ # conditions = {:active => true}
79
+ # self.find_by_authentication_token(token, :conditions => conditions)
80
+ # end
81
+ #
82
+ def find_for_token_authentication(token)
83
+ self.find(:first, :conditions => { :authentication_token => token})
84
+ end
85
+
86
+ end
87
+ end
88
+ end
89
+ end
@@ -20,7 +20,19 @@ module Devise
20
20
  klass.send(mod) if klass.respond_to?(mod)
21
21
  end
22
22
  end
23
-
23
+
24
+ def find(*args)
25
+ options = args.extract_options!
26
+ case args.first
27
+ when :first
28
+ first(options)
29
+ when :all
30
+ all(options)
31
+ else
32
+ super
33
+ end
34
+ end
35
+
24
36
  include Devise::Schema
25
37
 
26
38
  # Tell how to apply schema methods. This automatically converts DateTime
@@ -17,6 +17,11 @@ module Devise
17
17
  apply_schema :password_salt, String, :null => null
18
18
  end
19
19
 
20
+ # Creates authentication_token.
21
+ def token_authenticatable
22
+ apply_schema :authentication_token, String, :limit => 20
23
+ end
24
+
20
25
  # Creates confirmation_token, confirmed_at and confirmation_sent_at.
21
26
  def confirmable
22
27
  apply_schema :confirmation_token, String, :limit => 20
@@ -0,0 +1,38 @@
1
+ require 'devise/strategies/base'
2
+
3
+ module Devise
4
+ module Strategies
5
+ # Strategy for signing in a user, based on a authenticatable token.
6
+ # Redirects to sign_in page if it's not authenticated.
7
+ class TokenAuthenticatable < Base
8
+ def valid?
9
+ super && authentication_token(scope).present?
10
+ end
11
+
12
+ # Authenticate a user based on authenticatable token params, returning to warden
13
+ # success and the authenticated user if everything is okay. Otherwise redirect
14
+ # to sign in page.
15
+ def authenticate!
16
+ if resource = mapping.to.authenticate_with_token(params[scope] || params)
17
+ success!(resource)
18
+ else
19
+ fail!(:invalid_token)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ # Detect authentication token in params: scoped or not.
26
+ def authentication_token(scope)
27
+ if params[scope]
28
+ params[scope][mapping.to.token_authentication_key]
29
+ else
30
+ params[mapping.to.token_authentication_key]
31
+ end
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
38
+ Warden::Strategies.add(:token_authenticatable, Devise::Strategies::TokenAuthenticatable)
@@ -1,3 +1,3 @@
1
1
  module Devise
2
- VERSION = "0.9.1".freeze
2
+ VERSION = "0.9.2".freeze
3
3
  end
@@ -25,7 +25,7 @@ class DeviseTest < ActiveSupport::TestCase
25
25
  Devise.configure_warden(config)
26
26
 
27
27
  assert_equal Devise::FailureApp, config.failure_app
28
- assert_equal [:rememberable, :authenticatable], config.default_strategies
28
+ assert_equal [:rememberable, :token_authenticatable, :authenticatable], config.default_strategies
29
29
  assert_equal :user, config.default_scope
30
30
  assert config.silence_missing_strategies?
31
31
  end
@@ -154,25 +154,42 @@ class AuthenticationTest < ActionController::IntegrationTest
154
154
  assert_contain 'You need to sign in or sign up before continuing.'
155
155
  end
156
156
 
157
- test 'return to default url if no other was requested' do
157
+ test 'redirect to default url if no other was configured' do
158
158
  sign_in_as_user
159
159
 
160
160
  assert_template 'home/index'
161
- assert_nil session[:return_to]
161
+ assert_nil session[:"user.return_to"]
162
162
  end
163
163
 
164
- test 'return to given url after sign in' do
164
+ test 'redirect to requested url after sign in' do
165
165
  get users_path
166
166
  assert_redirected_to new_user_session_path(:unauthenticated => true)
167
167
  assert_equal users_path, session[:"user.return_to"]
168
+
168
169
  follow_redirect!
170
+ sign_in_as_user :visit => false
171
+
172
+ assert_template 'users/index'
173
+ assert_nil session[:"user.return_to"]
174
+ end
169
175
 
176
+ test 'redirect to last requested url overwriting the stored return_to option' do
177
+ get expire_user_path(create_user)
178
+ assert_redirected_to new_user_session_path(:unauthenticated => true)
179
+ assert_equal expire_user_path(create_user), session[:"user.return_to"]
180
+
181
+ get users_path
182
+ assert_redirected_to new_user_session_path(:unauthenticated => true)
183
+ assert_equal users_path, session[:"user.return_to"]
184
+
185
+ follow_redirect!
170
186
  sign_in_as_user :visit => false
187
+
171
188
  assert_template 'users/index'
172
189
  assert_nil session[:"user.return_to"]
173
190
  end
174
191
 
175
- test 'return to configured home path after sign in' do
192
+ test 'redirect to configured home path for a given scope after sign in' do
176
193
  sign_in_as_admin
177
194
  assert_equal "/admin_area/home", @request.path
178
195
  end
@@ -128,4 +128,14 @@ class PasswordTest < ActionController::IntegrationTest
128
128
 
129
129
  assert warden.authenticated?(:user)
130
130
  end
131
+
132
+ test 'does not sign in user automatically after changing it\'s password if it\'s not active' do
133
+ user = create_user(:confirm => false)
134
+ request_forgot_password
135
+ reset_password :reset_password_token => user.reload.reset_password_token
136
+
137
+ assert_redirected_to new_user_session_path(:unconfirmed => true)
138
+ assert !warden.authenticated?(:user)
139
+ end
140
+
131
141
  end
@@ -0,0 +1,55 @@
1
+ require 'test/test_helper'
2
+
3
+ class TokenAuthenticationTest < ActionController::IntegrationTest
4
+
5
+ test 'sign in user should authenticate with valid authentication token and proper authentication token key' do
6
+ swap Devise, :token_authentication_key => :secret_token do
7
+ sign_in_as_new_user_with_token(:auth_token_key => :secret_token)
8
+
9
+ assert_response :success
10
+ assert_template 'users/index'
11
+ assert_contain 'Welcome'
12
+ assert warden.authenticated?(:user)
13
+ end
14
+ end
15
+
16
+ test 'user signing in with valid authentication token - but improper authentication token key - return to sign in form with error message' do
17
+ swap Devise, :token_authentication_key => :donald_duck_token do
18
+ sign_in_as_new_user_with_token(:auth_token_key => :secret_token)
19
+ assert_redirected_to new_user_session_path(:unauthenticated => true)
20
+ follow_redirect!
21
+
22
+ assert_contain 'You need to sign in or sign up before continuing'
23
+ assert_contain 'Sign in'
24
+ assert_not warden.authenticated?(:user)
25
+ end
26
+ end
27
+
28
+ test 'user signing in with invalid authentication token should return to sign in form with error message' do
29
+ store_translations :en, :devise => {:sessions => {:invalid_token => 'LOL, that was not a single character correct.'}} do
30
+ sign_in_as_new_user_with_token(:auth_token => '*** INVALID TOKEN ***')
31
+ assert_redirected_to new_user_session_path(:invalid_token => true)
32
+ follow_redirect!
33
+
34
+ assert_response :success
35
+ assert_contain 'LOL, that was not a single character correct.'
36
+ assert_contain 'Sign in'
37
+ assert_not warden.authenticated?(:user)
38
+ end
39
+ end
40
+
41
+ private
42
+
43
+ def sign_in_as_new_user_with_token(options = {}, &block)
44
+ options[:auth_token_key] ||= Devise.token_authentication_key
45
+ options[:auth_token] ||= VALID_AUTHENTICATION_TOKEN
46
+
47
+ user = create_user(options)
48
+ user.authentication_token = VALID_AUTHENTICATION_TOKEN
49
+ user.save
50
+
51
+ visit users_path(options[:auth_token_key].to_sym => options[:auth_token])
52
+ user
53
+ end
54
+
55
+ end
@@ -0,0 +1,51 @@
1
+ require 'test/test_helper'
2
+
3
+ class TokenAuthenticatableTest < ActiveSupport::TestCase
4
+
5
+ test 'should generate friendly authentication token on create' do
6
+ User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
7
+ user = create_user
8
+ assert_present user.authentication_token
9
+ assert_equal VALID_AUTHENTICATION_TOKEN, user.authentication_token
10
+ end
11
+
12
+ test 'should reset authentication token' do
13
+ user = new_user
14
+ user.reset_authentication_token
15
+ previous_token = user.authentication_token
16
+ user.reset_authentication_token
17
+ assert_not_equal previous_token, user.authentication_token
18
+ end
19
+
20
+ test 'should ensure authentication token' do
21
+ user = new_user
22
+ user.ensure_authentication_token
23
+ previous_token = user.authentication_token
24
+ user.ensure_authentication_token
25
+ assert_equal previous_token, user.authentication_token
26
+ end
27
+
28
+ test 'should test for a valid authentication token' do
29
+ User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
30
+ user = create_user
31
+ assert user.valid_authentication_token?(VALID_AUTHENTICATION_TOKEN)
32
+ assert_not user.valid_authentication_token?(VALID_AUTHENTICATION_TOKEN.reverse)
33
+ end
34
+
35
+ test 'should authenticate a valid user with authentication token and return it' do
36
+ User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
37
+ user = create_user
38
+ user.confirm!
39
+ authenticated_user = User.authenticate_with_token(:auth_token => user.authentication_token)
40
+ assert_equal authenticated_user, user
41
+ end
42
+
43
+ test 'should return nil when authenticating an invalid user by authentication token' do
44
+ User.expects(:authentication_token).returns(VALID_AUTHENTICATION_TOKEN)
45
+ user = create_user
46
+ user.confirm!
47
+ authenticated_user = User.authenticate_with_token(:auth_token => user.authentication_token.reverse)
48
+ assert_nil authenticated_user
49
+ end
50
+
51
+ end
@@ -17,6 +17,7 @@ ActiveRecord::Schema.define(:version => 1) do
17
17
  t.rememberable
18
18
  t.trackable
19
19
  t.lockable
20
+ t.token_authenticatable
20
21
  end
21
22
 
22
23
  t.timestamps
@@ -1,5 +1,5 @@
1
1
  class User < ActiveRecord::Base
2
2
  devise :authenticatable, :confirmable, :recoverable, :rememberable, :trackable,
3
- :validatable, :timeoutable, :lockable
3
+ :validatable, :timeoutable, :lockable, :token_authenticatable
4
4
  attr_accessible :username, :email, :password, :password_confirmation
5
5
  end
@@ -2,6 +2,6 @@ class User
2
2
  include MongoMapper::Document
3
3
  key :created_at, DateTime
4
4
  devise :authenticatable, :confirmable, :recoverable, :rememberable, :trackable,
5
- :validatable, :timeoutable, :lockable
5
+ :validatable, :timeoutable, :lockable, :token_authenticatable
6
6
  # attr_accessible :username, :email, :password, :password_confirmation
7
7
  end
@@ -0,0 +1,5 @@
1
+ class ActiveSupport::TestCase
2
+
3
+ VALID_AUTHENTICATION_TOKEN = 'AbCdEfGhIjKlMnOpQrSt'.freeze
4
+
5
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devise
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.1
4
+ version: 0.9.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - "Jos\xC3\xA9 Valim"
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2010-01-25 00:00:00 +01:00
13
+ date: 2010-02-05 00:00:00 +01:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -91,6 +91,7 @@ files:
91
91
  - lib/devise/models/recoverable.rb
92
92
  - lib/devise/models/rememberable.rb
93
93
  - lib/devise/models/timeoutable.rb
94
+ - lib/devise/models/token_authenticatable.rb
94
95
  - lib/devise/models/trackable.rb
95
96
  - lib/devise/models/validatable.rb
96
97
  - lib/devise/orm/active_record.rb
@@ -103,6 +104,7 @@ files:
103
104
  - lib/devise/strategies/authenticatable.rb
104
105
  - lib/devise/strategies/base.rb
105
106
  - lib/devise/strategies/rememberable.rb
107
+ - lib/devise/strategies/token_authenticatable.rb
106
108
  - lib/devise/test_helpers.rb
107
109
  - lib/devise/version.rb
108
110
  has_rdoc: true
@@ -146,6 +148,7 @@ test_files:
146
148
  - test/integration/recoverable_test.rb
147
149
  - test/integration/rememberable_test.rb
148
150
  - test/integration/timeoutable_test.rb
151
+ - test/integration/token_authenticatable_test.rb
149
152
  - test/integration/trackable_test.rb
150
153
  - test/mailers/confirmation_instructions_test.rb
151
154
  - test/mailers/reset_password_instructions_test.rb
@@ -157,6 +160,7 @@ test_files:
157
160
  - test/models/recoverable_test.rb
158
161
  - test/models/rememberable_test.rb
159
162
  - test/models/timeoutable_test.rb
163
+ - test/models/token_authenticatable_test.rb
160
164
  - test/models/trackable_test.rb
161
165
  - test/models/validatable_test.rb
162
166
  - test/models_test.rb
@@ -186,5 +190,6 @@ test_files:
186
190
  - test/support/integration_tests_helper.rb
187
191
  - test/support/model_tests_helper.rb
188
192
  - test/support/test_silencer.rb
193
+ - test/support/tests_helper.rb
189
194
  - test/test_helper.rb
190
195
  - test/test_helpers_test.rb