clearance 0.10.5 → 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of clearance might be problematic. Click here for more details.
- data/CHANGELOG.md +8 -0
- data/README.md +5 -6
- data/VERSION +1 -1
- data/app/controllers/clearance/passwords_controller.rb +2 -3
- data/app/controllers/clearance/sessions_controller.rb +2 -3
- data/app/controllers/clearance/users_controller.rb +3 -3
- data/app/views/passwords/edit.html.erb +0 -4
- data/app/views/users/_form.html.erb +0 -4
- data/clearance.gemspec +2 -2
- data/features/engine/visitor_resets_password.feature +4 -14
- data/features/engine/visitor_signs_up.feature +9 -5
- data/features/step_definitions/engine/clearance_steps.rb +5 -7
- data/lib/clearance/authentication.rb +108 -111
- data/lib/clearance/engine.rb +1 -1
- data/lib/clearance/user.rb +113 -139
- data/spec/controllers/forgeries_controller_spec.rb +1 -1
- data/spec/controllers/passwords_controller_spec.rb +7 -14
- data/spec/controllers/sessions_controller_spec.rb +0 -1
- data/spec/factories.rb +2 -3
- data/spec/models/user_spec.rb +9 -41
- metadata +6 -7
- data/app/views/users/_inputs.html.erb +0 -6
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,11 @@
|
|
1
|
+
0.11.0
|
2
|
+
-------------------
|
3
|
+
|
4
|
+
* Removing password confirmation.
|
5
|
+
* Use ActiveSupport::Concern and ActiveSupport::SecureRandom to clean up code.
|
6
|
+
* New controller#authenticate(params) method. Redefine username & password or other styles of authentication.
|
7
|
+
* before_filter :authenticate API replaced with more aptly-named before_filter :authorize.
|
8
|
+
|
1
9
|
0.10.5
|
2
10
|
-------------------
|
3
11
|
|
data/README.md
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
Clearance
|
2
2
|
=========
|
3
3
|
|
4
|
-
Rails authentication with email & password.
|
4
|
+
Rails authentication & authorization with email & password.
|
5
5
|
|
6
6
|
[We have clearance, Clarence.](http://www.youtube.com/watch?v=fVq4_HhBK8Y)
|
7
7
|
|
@@ -11,9 +11,8 @@ Help
|
|
11
11
|
----
|
12
12
|
|
13
13
|
* [Documentation](http://rdoc.info/gems/clearance) at RDoc.info.
|
14
|
-
* [Patches
|
15
|
-
* [
|
16
|
-
* [Mailing list](http://groups.google.com/group/thoughtbot-clearance) on Google Groups.
|
14
|
+
* [Patches and bugs](http://github.com/thoughtbot/clearance/issues) at Github Issues.
|
15
|
+
* [Mailing list](http://groups.google.com/group/thoughtbot-clearance) at Google Groups.
|
17
16
|
|
18
17
|
Installation
|
19
18
|
------------
|
@@ -40,11 +39,11 @@ series of Clearance if you have a Rails 2 app.
|
|
40
39
|
Usage
|
41
40
|
-----
|
42
41
|
|
43
|
-
If you want to
|
42
|
+
If you want to authorize users for a controller action, use the authorize
|
44
43
|
method in a before_filter.
|
45
44
|
|
46
45
|
class WidgetsController < ApplicationController
|
47
|
-
before_filter :
|
46
|
+
before_filter :authorize
|
48
47
|
def index
|
49
48
|
@widgets = Widget.all
|
50
49
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.11.0
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Clearance::PasswordsController < ApplicationController
|
2
2
|
unloadable
|
3
3
|
|
4
|
-
skip_before_filter :
|
4
|
+
skip_before_filter :authorize, :only => [:new, :create, :edit, :update]
|
5
5
|
before_filter :forbid_missing_token, :only => [:edit, :update]
|
6
6
|
before_filter :forbid_non_existent_user, :only => [:edit, :update]
|
7
7
|
|
@@ -31,8 +31,7 @@ class Clearance::PasswordsController < ApplicationController
|
|
31
31
|
@user = ::User.find_by_id_and_confirmation_token(
|
32
32
|
params[:user_id], params[:token])
|
33
33
|
|
34
|
-
if @user.update_password(params[:user][:password]
|
35
|
-
params[:user][:password_confirmation])
|
34
|
+
if @user.update_password(params[:user][:password])
|
36
35
|
sign_in(@user)
|
37
36
|
flash_success_after_update
|
38
37
|
redirect_to(url_after_update)
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Clearance::SessionsController < ApplicationController
|
2
2
|
unloadable
|
3
3
|
|
4
|
-
skip_before_filter :
|
4
|
+
skip_before_filter :authorize, :only => [:new, :create, :destroy]
|
5
5
|
protect_from_forgery :except => :create
|
6
6
|
|
7
7
|
def new
|
@@ -9,8 +9,7 @@ class Clearance::SessionsController < ApplicationController
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def create
|
12
|
-
@user =
|
13
|
-
params[:session][:password])
|
12
|
+
@user = authenticate(params)
|
14
13
|
if @user.nil?
|
15
14
|
flash_failure_after_create
|
16
15
|
render :template => 'sessions/new', :status => :unauthorized
|
@@ -1,7 +1,7 @@
|
|
1
1
|
class Clearance::UsersController < ApplicationController
|
2
2
|
unloadable
|
3
3
|
|
4
|
-
skip_before_filter :
|
4
|
+
skip_before_filter :authorize, :only => [:new, :create]
|
5
5
|
before_filter :redirect_to_root, :only => [:new, :create], :if => :signed_in?
|
6
6
|
|
7
7
|
def new
|
@@ -10,7 +10,7 @@ class Clearance::UsersController < ApplicationController
|
|
10
10
|
end
|
11
11
|
|
12
12
|
def create
|
13
|
-
@user = ::User.new
|
13
|
+
@user = ::User.new(params[:user])
|
14
14
|
if @user.save
|
15
15
|
flash_notice_after_create
|
16
16
|
sign_in(@user)
|
@@ -23,7 +23,7 @@ class Clearance::UsersController < ApplicationController
|
|
23
23
|
private
|
24
24
|
|
25
25
|
def flash_notice_after_create
|
26
|
-
flash[:notice] = translate(:
|
26
|
+
flash[:notice] = translate(:signed_up,
|
27
27
|
:scope => [:clearance, :controllers, :users],
|
28
28
|
:default => "You are now signed up.")
|
29
29
|
end
|
@@ -12,10 +12,6 @@
|
|
12
12
|
<%= form.label :password, "Choose password" %>
|
13
13
|
<%= form.password_field :password %>
|
14
14
|
</div>
|
15
|
-
<div class="password_field">
|
16
|
-
<%= form.label :password_confirmation, "Confirm password" %>
|
17
|
-
<%= form.password_field :password_confirmation %>
|
18
|
-
</div>
|
19
15
|
<div class="submit_field">
|
20
16
|
<%= form.submit "Save this password" %>
|
21
17
|
</div>
|
data/clearance.gemspec
CHANGED
@@ -9,8 +9,8 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
10
10
|
s.authors = ["Dan Croak", "Mike Burns", "Jason Morrison", "Joe Ferris", "Eugene Bolshakov", "Nick Quaranto", "Josh Nichols", "Mike Breen", "Marcel G\303\266rner", "Bence Nagy", "Ben Mabey", "Eloy Duran", "Tim Pope", "Mihai Anca", "Mark Cornick", "Shay Arnett", "Jon Yurek", "Chad Pytel"]
|
11
11
|
s.date = Date.today.to_s
|
12
|
-
s.summary = %q{Rails authentication with email & password.}
|
13
|
-
s.description = %q{Rails authentication with email & password.}
|
12
|
+
s.summary = %q{Rails authentication & authorization with email & password.}
|
13
|
+
s.description = %q{Rails authentication & authorization with email & password.}
|
14
14
|
s.extra_rdoc_files = [
|
15
15
|
"LICENSE",
|
16
16
|
"README.md"
|
@@ -15,24 +15,14 @@ Feature: Password reset
|
|
15
15
|
Then I should see "instructions for changing your password"
|
16
16
|
And a password reset message should be sent to "email@example.com"
|
17
17
|
|
18
|
-
Scenario: User
|
18
|
+
Scenario: User tries to reset his password with a blank password
|
19
19
|
Given I signed up with "email@example.com/password"
|
20
20
|
And I go to the password reset request page
|
21
21
|
Then I should see an email field
|
22
22
|
And I fill in "Email address" with "email@example.com"
|
23
23
|
And I press "Reset password"
|
24
24
|
When I follow the password reset link sent to "email@example.com"
|
25
|
-
And I update my password with "
|
26
|
-
Then I should see an error message
|
27
|
-
And I should be signed out
|
28
|
-
|
29
|
-
Scenario: User is signed up updated his password and types wrong confirmation
|
30
|
-
Given I signed up with "email@example.com/password"
|
31
|
-
And I go to the password reset request page
|
32
|
-
And I fill in "Email address" with "email@example.com"
|
33
|
-
And I press "Reset password"
|
34
|
-
When I follow the password reset link sent to "email@example.com"
|
35
|
-
And I update my password with "newpassword/wrongconfirmation"
|
25
|
+
And I update my password with ""
|
36
26
|
Then I should see an error message
|
37
27
|
And I should be signed out
|
38
28
|
|
@@ -42,7 +32,7 @@ Feature: Password reset
|
|
42
32
|
And I fill in "Email address" with "email@example.com"
|
43
33
|
And I press "Reset password"
|
44
34
|
When I follow the password reset link sent to "email@example.com"
|
45
|
-
And I update my password with "newpassword
|
35
|
+
And I update my password with "newpassword"
|
46
36
|
Then I should be signed in
|
47
37
|
When I sign out
|
48
38
|
Then I should be signed out
|
@@ -55,6 +45,6 @@ Feature: Password reset
|
|
55
45
|
And I fill in "Email address" with "email@example.com"
|
56
46
|
And I press "Reset password"
|
57
47
|
When I follow the password reset link sent to "email@example.com"
|
58
|
-
And I update my password with "newpassword
|
48
|
+
And I update my password with "newpassword"
|
59
49
|
Then I should be signed in
|
60
50
|
|
@@ -8,16 +8,20 @@ Feature: Sign up
|
|
8
8
|
When I go to the sign up page
|
9
9
|
Then I should see an email field
|
10
10
|
|
11
|
-
Scenario: Visitor signs up with invalid
|
11
|
+
Scenario: Visitor signs up with invalid email
|
12
12
|
When I fill in "Email" with "invalidemail"
|
13
13
|
And I fill in "Password" with "password"
|
14
|
-
And I fill in "Confirm password" with ""
|
15
14
|
And I press "Sign up"
|
16
|
-
Then I should see
|
15
|
+
Then I should see "Email is invalid"
|
16
|
+
|
17
|
+
Scenario: Visitor signs up with blank password
|
18
|
+
When I fill in "Email" with "email@example.com"
|
19
|
+
And I fill in "Password" with ""
|
20
|
+
And I press "Sign up"
|
21
|
+
Then I should see "Password can't be blank"
|
17
22
|
|
18
23
|
Scenario: Visitor signs up with valid data
|
19
|
-
When I fill in "Email" with "email@
|
24
|
+
When I fill in "Email" with "email@example.com"
|
20
25
|
And I fill in "Password" with "password"
|
21
|
-
And I fill in "Confirm password" with "password"
|
22
26
|
And I press "Sign up"
|
23
27
|
Then I should see "signed up"
|
@@ -24,9 +24,8 @@ end
|
|
24
24
|
|
25
25
|
Given /^(?:I am|I have|I) signed up (?:as|with) "(.*)\/(.*)"$/ do |email, password|
|
26
26
|
Factory(:user,
|
27
|
-
:email
|
28
|
-
:password
|
29
|
-
:password_confirmation => password)
|
27
|
+
:email => email,
|
28
|
+
:password => password)
|
30
29
|
end
|
31
30
|
|
32
31
|
Given /^a user "([^"]*)" exists without a salt, remember token, or password$/ do |email|
|
@@ -71,9 +70,9 @@ Then /^a password reset message should be sent to "(.*)"$/ do |email|
|
|
71
70
|
assert !user.confirmation_token.blank?
|
72
71
|
assert !ActionMailer::Base.deliveries.empty?
|
73
72
|
result = ActionMailer::Base.deliveries.any? do |email|
|
74
|
-
email.to
|
73
|
+
email.to == [user.email] &&
|
75
74
|
email.subject =~ /password/i &&
|
76
|
-
email.body
|
75
|
+
email.body =~ /#{user.confirmation_token}/
|
77
76
|
end
|
78
77
|
assert result
|
79
78
|
end
|
@@ -111,9 +110,8 @@ When /^I request password reset link to be sent to "(.*)"$/ do |email|
|
|
111
110
|
And %{I press "Reset password"}
|
112
111
|
end
|
113
112
|
|
114
|
-
When /^I update my password with "(.*)
|
113
|
+
When /^I update my password with "(.*)"$/ do |password|
|
115
114
|
And %{I fill in "Choose password" with "#{password}"}
|
116
|
-
And %{I fill in "Confirm password" with "#{confirmation}"}
|
117
115
|
And %{I press "Save this password"}
|
118
116
|
end
|
119
117
|
|
@@ -1,138 +1,135 @@
|
|
1
1
|
module Clearance
|
2
2
|
module Authentication
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
helper_method :current_user, :signed_in?, :signed_out?
|
7
|
+
hide_action :current_user, :current_user=,
|
8
|
+
:signed_in?, :signed_out?,
|
9
|
+
:sign_in, :sign_out,
|
10
|
+
:authorize, :deny_access
|
7
11
|
end
|
8
12
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
:sign_in, :sign_out,
|
15
|
-
:authenticate, :deny_access
|
16
|
-
|
17
|
-
end
|
13
|
+
# User in the current cookie
|
14
|
+
#
|
15
|
+
# @return [User, nil]
|
16
|
+
def current_user
|
17
|
+
@_current_user ||= user_from_cookie
|
18
18
|
end
|
19
19
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
# Set the current user
|
29
|
-
#
|
30
|
-
# @param [User]
|
31
|
-
def current_user=(user)
|
32
|
-
@_current_user = user
|
33
|
-
end
|
34
|
-
|
35
|
-
# Is the current user signed in?
|
36
|
-
#
|
37
|
-
# @return [true, false]
|
38
|
-
def signed_in?
|
39
|
-
! current_user.nil?
|
40
|
-
end
|
20
|
+
# Set the current user
|
21
|
+
#
|
22
|
+
# @param [User]
|
23
|
+
def current_user=(user)
|
24
|
+
@_current_user = user
|
25
|
+
end
|
41
26
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
27
|
+
# Is the current user signed in?
|
28
|
+
#
|
29
|
+
# @return [true, false]
|
30
|
+
def signed_in?
|
31
|
+
! current_user.nil?
|
32
|
+
end
|
48
33
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
34
|
+
# Is the current user signed out?
|
35
|
+
#
|
36
|
+
# @return [true, false]
|
37
|
+
def signed_out?
|
38
|
+
current_user.nil?
|
39
|
+
end
|
56
40
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
end
|
41
|
+
# Sign user in to cookie.
|
42
|
+
#
|
43
|
+
# @param [User]
|
44
|
+
#
|
45
|
+
# @example
|
46
|
+
# sign_in(@user)
|
47
|
+
def sign_in(user)
|
48
|
+
if user
|
49
|
+
cookies[:remember_token] = {
|
50
|
+
:value => user.remember_token,
|
51
|
+
:expires => Clearance.configuration.cookie_expiration.call
|
52
|
+
}
|
53
|
+
self.current_user = user
|
71
54
|
end
|
55
|
+
end
|
72
56
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
57
|
+
# Sign user out of cookie.
|
58
|
+
#
|
59
|
+
# @example
|
60
|
+
# sign_out
|
61
|
+
def sign_out
|
62
|
+
current_user.reset_remember_token! if current_user
|
63
|
+
cookies.delete(:remember_token)
|
64
|
+
self.current_user = nil
|
65
|
+
end
|
82
66
|
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
67
|
+
# Find the user by the given params or return nil.
|
68
|
+
# By default, uses email and password.
|
69
|
+
# Redefine this method and User.authenticate for other mechanisms
|
70
|
+
# such as username and password.
|
71
|
+
#
|
72
|
+
# @example
|
73
|
+
# @user = authenticate(params)
|
74
|
+
def authenticate(params)
|
75
|
+
::User.authenticate(params[:session][:email],
|
76
|
+
params[:session][:password])
|
77
|
+
end
|
92
78
|
|
93
|
-
|
79
|
+
# Deny the user access if they are signed out.
|
80
|
+
#
|
81
|
+
# @example
|
82
|
+
# before_filter :authorize
|
83
|
+
def authorize
|
84
|
+
deny_access unless signed_in?
|
85
|
+
end
|
94
86
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
87
|
+
# Store the current location and redirect to sign in.
|
88
|
+
# Display a failure flash message if included.
|
89
|
+
#
|
90
|
+
# @param [String] optional flash message to display to denied user
|
91
|
+
def deny_access(flash_message = nil)
|
92
|
+
store_location
|
93
|
+
flash[:failure] = flash_message if flash_message
|
94
|
+
redirect_to(sign_in_url)
|
95
|
+
end
|
101
96
|
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
97
|
+
# CSRF protection in Rails >= 3.0.4
|
98
|
+
# http://weblog.rubyonrails.org/2011/2/8/csrf-protection-bypass-in-ruby-on-rails
|
99
|
+
def handle_unverified_request
|
100
|
+
super
|
101
|
+
sign_out
|
102
|
+
end
|
107
103
|
|
108
|
-
|
109
|
-
warn "[DEPRECATION] sign_user_in: unnecessary. use sign_in(user) instead."
|
110
|
-
sign_in(user)
|
111
|
-
end
|
104
|
+
protected
|
112
105
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
end
|
106
|
+
def user_from_cookie
|
107
|
+
if token = cookies[:remember_token]
|
108
|
+
::User.find_by_remember_token(token)
|
117
109
|
end
|
110
|
+
end
|
118
111
|
|
119
|
-
|
120
|
-
|
121
|
-
|
112
|
+
def store_location
|
113
|
+
if request.get?
|
114
|
+
session[:return_to] = request.fullpath
|
122
115
|
end
|
116
|
+
end
|
123
117
|
|
124
|
-
|
125
|
-
|
126
|
-
|
118
|
+
def redirect_back_or(default)
|
119
|
+
redirect_to(return_to || default)
|
120
|
+
clear_return_to
|
121
|
+
end
|
127
122
|
|
128
|
-
|
129
|
-
|
130
|
-
|
123
|
+
def return_to
|
124
|
+
session[:return_to] || params[:return_to]
|
125
|
+
end
|
131
126
|
|
132
|
-
|
133
|
-
|
134
|
-
end
|
127
|
+
def clear_return_to
|
128
|
+
session[:return_to] = nil
|
135
129
|
end
|
136
130
|
|
131
|
+
def redirect_to_root
|
132
|
+
redirect_to('/')
|
133
|
+
end
|
137
134
|
end
|
138
135
|
end
|
data/lib/clearance/engine.rb
CHANGED
data/lib/clearance/user.rb
CHANGED
@@ -2,6 +2,7 @@ require 'digest/sha1'
|
|
2
2
|
|
3
3
|
module Clearance
|
4
4
|
module User
|
5
|
+
extend ActiveSupport::Concern
|
5
6
|
|
6
7
|
# Hook for all Clearance::User modules.
|
7
8
|
#
|
@@ -9,192 +10,165 @@ module Clearance
|
|
9
10
|
# extend and include à la carte.
|
10
11
|
#
|
11
12
|
# @example
|
12
|
-
# extend ClassMethods
|
13
|
-
# include InstanceMethods
|
14
|
-
# include AttrAccessor
|
15
13
|
# include Callbacks
|
16
14
|
#
|
17
|
-
# @see ClassMethods
|
18
|
-
# @see InstanceMethods
|
19
|
-
# @see AttrAccessor
|
20
15
|
# @see Validations
|
21
16
|
# @see Callbacks
|
22
|
-
|
23
|
-
|
17
|
+
included do
|
18
|
+
attr_accessor :password, :password_changing
|
24
19
|
|
25
|
-
|
26
|
-
|
27
|
-
model.send(:include, Validations)
|
28
|
-
model.send(:include, Callbacks)
|
20
|
+
include Validations
|
21
|
+
include Callbacks
|
29
22
|
end
|
30
23
|
|
31
|
-
module
|
32
|
-
#
|
24
|
+
module ClassMethods
|
25
|
+
# Authenticate with email and password.
|
33
26
|
#
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
27
|
+
# @param [String, String] email and password
|
28
|
+
# @return [User, nil] authenticated user or nil
|
29
|
+
# @example
|
30
|
+
# User.authenticate("email@example.com", "password")
|
31
|
+
def authenticate(email, password)
|
32
|
+
return nil unless user = find_by_email(email.to_s.downcase)
|
33
|
+
return user if user.authenticated?(password)
|
41
34
|
end
|
42
35
|
end
|
43
36
|
|
44
37
|
module Validations
|
38
|
+
extend ActiveSupport::Concern
|
39
|
+
|
45
40
|
# Hook for validations.
|
46
41
|
#
|
47
42
|
# :email must be present, unique, formatted
|
48
43
|
#
|
49
44
|
# If password is required,
|
50
45
|
# :password must be present, confirmed
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
validates_presence_of :password, :unless => :password_optional?
|
58
|
-
validates_confirmation_of :password
|
59
|
-
end
|
46
|
+
included do
|
47
|
+
validates_presence_of :email, :unless => :email_optional?
|
48
|
+
validates_uniqueness_of :email, :case_sensitive => false, :allow_blank => true
|
49
|
+
validates_format_of :email, :with => %r{^[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+(?:\.[a-z0-9!#\$%&'*+\/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$}i, :allow_blank => true
|
50
|
+
|
51
|
+
validates_presence_of :password, :unless => :password_optional?
|
60
52
|
end
|
61
53
|
end
|
62
54
|
|
63
55
|
module Callbacks
|
56
|
+
extend ActiveSupport::Concern
|
57
|
+
|
64
58
|
# Hook for callbacks.
|
65
59
|
#
|
66
60
|
# salt, token, password encryption are handled before_save.
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
before_create :generate_remember_token
|
73
|
-
end
|
61
|
+
included do
|
62
|
+
before_validation :downcase_email
|
63
|
+
before_save :initialize_salt,
|
64
|
+
:encrypt_password
|
65
|
+
before_create :generate_remember_token
|
74
66
|
end
|
75
67
|
end
|
76
68
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
end
|
87
|
-
|
88
|
-
# Set the remember token.
|
89
|
-
#
|
90
|
-
# @deprecated Use {#reset_remember_token!} instead
|
91
|
-
def remember_me!
|
92
|
-
warn "[DEPRECATION] remember_me!: use reset_remember_token! instead"
|
93
|
-
reset_remember_token!
|
94
|
-
end
|
95
|
-
|
96
|
-
# Reset the remember token.
|
97
|
-
#
|
98
|
-
# @example
|
99
|
-
# user.reset_remember_token!
|
100
|
-
def reset_remember_token!
|
101
|
-
generate_remember_token
|
102
|
-
save(:validate => false)
|
103
|
-
end
|
69
|
+
# Am I authenticated with given password?
|
70
|
+
#
|
71
|
+
# @param [String] plain-text password
|
72
|
+
# @return [true, false]
|
73
|
+
# @example
|
74
|
+
# user.authenticated?('password')
|
75
|
+
def authenticated?(password)
|
76
|
+
encrypted_password == encrypt(password)
|
77
|
+
end
|
104
78
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
end
|
79
|
+
# Set the remember token.
|
80
|
+
#
|
81
|
+
# @deprecated Use {#reset_remember_token!} instead
|
82
|
+
def remember_me!
|
83
|
+
warn "[DEPRECATION] remember_me!: use reset_remember_token! instead"
|
84
|
+
reset_remember_token!
|
85
|
+
end
|
113
86
|
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
self.password = new_password
|
123
|
-
self.password_confirmation = new_password_confirmation
|
124
|
-
if valid?
|
125
|
-
self.confirmation_token = nil
|
126
|
-
generate_remember_token
|
127
|
-
end
|
128
|
-
save
|
129
|
-
end
|
87
|
+
# Reset the remember token.
|
88
|
+
#
|
89
|
+
# @example
|
90
|
+
# user.reset_remember_token!
|
91
|
+
def reset_remember_token!
|
92
|
+
generate_remember_token
|
93
|
+
save(:validate => false)
|
94
|
+
end
|
130
95
|
|
131
|
-
|
96
|
+
# Mark my account as forgotten password.
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# user.forgot_password!
|
100
|
+
def forgot_password!
|
101
|
+
generate_confirmation_token
|
102
|
+
save(:validate => false)
|
103
|
+
end
|
132
104
|
|
133
|
-
|
134
|
-
|
105
|
+
# Update my password.
|
106
|
+
#
|
107
|
+
# @return [true, false] password was updated or not
|
108
|
+
# @example
|
109
|
+
# user.update_password('new-password')
|
110
|
+
def update_password(new_password)
|
111
|
+
self.password_changing = true
|
112
|
+
self.password = new_password
|
113
|
+
if valid?
|
114
|
+
self.confirmation_token = nil
|
115
|
+
generate_remember_token
|
135
116
|
end
|
117
|
+
save
|
118
|
+
end
|
136
119
|
|
137
|
-
|
138
|
-
generate_hash("--#{salt}--#{string}--")
|
139
|
-
end
|
120
|
+
protected
|
140
121
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
end
|
145
|
-
end
|
122
|
+
def generate_hash(string)
|
123
|
+
Digest::SHA1.hexdigest(string)
|
124
|
+
end
|
146
125
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
end
|
151
|
-
end
|
126
|
+
def encrypt(string)
|
127
|
+
generate_hash("--#{salt}--#{string}--")
|
128
|
+
end
|
152
129
|
|
153
|
-
|
154
|
-
|
130
|
+
def initialize_salt
|
131
|
+
if salt.blank?
|
132
|
+
self.salt = ActiveSupport::SecureRandom.hex(20)
|
155
133
|
end
|
134
|
+
end
|
156
135
|
|
157
|
-
|
158
|
-
|
136
|
+
def encrypt_password
|
137
|
+
if password.present?
|
138
|
+
self.encrypted_password = encrypt(password)
|
159
139
|
end
|
140
|
+
end
|
160
141
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
def email_optional?
|
165
|
-
false
|
166
|
-
end
|
142
|
+
def generate_remember_token
|
143
|
+
self.remember_token = ActiveSupport::SecureRandom.hex(20)
|
144
|
+
end
|
167
145
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
# @return [Boolean] true if the password field can be left blank for this user
|
172
|
-
def password_optional?
|
173
|
-
encrypted_password.present? && password.blank? && password_changing.blank?
|
174
|
-
end
|
146
|
+
def generate_confirmation_token
|
147
|
+
self.confirmation_token = ActiveSupport::SecureRandom.hex(20)
|
148
|
+
end
|
175
149
|
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
150
|
+
# Always false. Override to allow other forms of authentication
|
151
|
+
# (username, facebook, etc).
|
152
|
+
# @return [Boolean] true if the email field be left blank for this user
|
153
|
+
def email_optional?
|
154
|
+
false
|
155
|
+
end
|
180
156
|
|
181
|
-
|
182
|
-
|
183
|
-
|
157
|
+
# True if the password has been set and the password is not being
|
158
|
+
# updated and we are not updating the password. Override to allow
|
159
|
+
# other forms of authentication (username, facebook, etc).
|
160
|
+
# @return [Boolean] true if the password field can be left blank for this user
|
161
|
+
def password_optional?
|
162
|
+
encrypted_password.present? && password.blank? && password_changing.blank?
|
184
163
|
end
|
185
164
|
|
186
|
-
|
187
|
-
#
|
188
|
-
|
189
|
-
# @param [String, String] email and password
|
190
|
-
# @return [User, nil] authenticated user or nil
|
191
|
-
# @example
|
192
|
-
# User.authenticate("email@example.com", "password")
|
193
|
-
def authenticate(email, password)
|
194
|
-
return nil unless user = find_by_email(email.to_s.downcase)
|
195
|
-
return user if user.authenticated?(password)
|
196
|
-
end
|
165
|
+
def password_required?
|
166
|
+
# warn "[DEPRECATION] password_required?: use !password_optional? instead"
|
167
|
+
!password_optional?
|
197
168
|
end
|
198
169
|
|
170
|
+
def downcase_email
|
171
|
+
self.email = email.to_s.downcase
|
172
|
+
end
|
199
173
|
end
|
200
174
|
end
|
@@ -3,7 +3,7 @@ require 'spec_helper'
|
|
3
3
|
class ForgeriesController < ActionController::Base
|
4
4
|
include Clearance::Authentication
|
5
5
|
protect_from_forgery
|
6
|
-
before_filter :
|
6
|
+
before_filter :authorize
|
7
7
|
|
8
8
|
# This is off in test by default, but we need it for this test
|
9
9
|
self.allow_forgery_protection = true
|
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Clearance::PasswordsController do
|
4
|
-
|
5
4
|
include Shoulda::ActionMailer::Matchers
|
6
5
|
|
7
6
|
it { should route(:get, '/users/1/password/edit').
|
@@ -101,18 +100,17 @@ describe Clearance::PasswordsController do
|
|
101
100
|
it { should render_template(:new) }
|
102
101
|
end
|
103
102
|
|
104
|
-
describe "on PUT to #update with
|
103
|
+
describe "on PUT to #update with password" do
|
105
104
|
before do
|
106
105
|
new_password = "new_password"
|
107
106
|
@encrypted_new_password = @user.send(:encrypt, new_password)
|
108
107
|
@user.encrypted_password.should_not == @encrypted_new_password
|
109
108
|
|
110
109
|
put(:update,
|
111
|
-
:user_id
|
112
|
-
:token
|
113
|
-
:user
|
114
|
-
:password
|
115
|
-
:password_confirmation => new_password
|
110
|
+
:user_id => @user,
|
111
|
+
:token => @user.confirmation_token,
|
112
|
+
:user => {
|
113
|
+
:password => new_password
|
116
114
|
})
|
117
115
|
@user.reload
|
118
116
|
end
|
@@ -133,17 +131,13 @@ describe Clearance::PasswordsController do
|
|
133
131
|
it { should redirect_to_url_after_update }
|
134
132
|
end
|
135
133
|
|
136
|
-
describe "on PUT to #update with
|
134
|
+
describe "on PUT to #update with blank password" do
|
137
135
|
before do
|
138
|
-
new_password = "new_password"
|
139
|
-
@encrypted_new_password = @user.send(:encrypt, new_password)
|
140
|
-
|
141
136
|
put(:update,
|
142
137
|
:user_id => @user.to_param,
|
143
138
|
:token => @user.confirmation_token,
|
144
139
|
:user => {
|
145
|
-
:password =>
|
146
|
-
:password_confirmation => ''
|
140
|
+
:password => ''
|
147
141
|
})
|
148
142
|
@user.reload
|
149
143
|
end
|
@@ -173,5 +167,4 @@ describe Clearance::PasswordsController do
|
|
173
167
|
sign_in_as @user_one
|
174
168
|
end
|
175
169
|
end
|
176
|
-
|
177
170
|
end
|
data/spec/factories.rb
CHANGED
@@ -3,9 +3,8 @@ Factory.sequence :email do |n|
|
|
3
3
|
end
|
4
4
|
|
5
5
|
Factory.define :user do |user|
|
6
|
-
user.email
|
7
|
-
user.password
|
8
|
-
user.password_confirmation { |instance| instance.password }
|
6
|
+
user.email { Factory.next :email }
|
7
|
+
user.password { "password" }
|
9
8
|
end
|
10
9
|
|
11
10
|
Factory.define :email_confirmed_user, :parent => :user do |user|
|
data/spec/models/user_spec.rb
CHANGED
@@ -20,20 +20,6 @@ describe User do
|
|
20
20
|
it { should_not allow_value("foo").for(:email) }
|
21
21
|
it { should_not allow_value("example.com").for(:email) }
|
22
22
|
|
23
|
-
it "should require password confirmation on create" do
|
24
|
-
user = Factory.build(:user, :password => 'blah',
|
25
|
-
:password_confirmation => 'boogidy')
|
26
|
-
(user.save).should_not be
|
27
|
-
user.errors[:password].should be_any
|
28
|
-
end
|
29
|
-
|
30
|
-
it "should require non blank password confirmation on create" do
|
31
|
-
user = Factory.build(:user, :password => 'blah',
|
32
|
-
:password_confirmation => '')
|
33
|
-
(user.save).should_not be
|
34
|
-
user.errors[:password].should be_any
|
35
|
-
end
|
36
|
-
|
37
23
|
it "should initialize salt" do
|
38
24
|
Factory(:user).salt.should_not be_nil
|
39
25
|
end
|
@@ -112,9 +98,9 @@ describe User do
|
|
112
98
|
@old_encrypted_password = @user.encrypted_password
|
113
99
|
end
|
114
100
|
|
115
|
-
describe "who updates password
|
101
|
+
describe "who updates password" do
|
116
102
|
before do
|
117
|
-
@user.update_password("new_password"
|
103
|
+
@user.update_password("new_password")
|
118
104
|
end
|
119
105
|
|
120
106
|
it "should change encrypted password" do
|
@@ -126,12 +112,8 @@ describe User do
|
|
126
112
|
it "should not generate the same remember token for users with the same password at the same time" do
|
127
113
|
Time.stubs(:now => Time.now)
|
128
114
|
password = 'secret'
|
129
|
-
first_user = Factory(:user,
|
130
|
-
|
131
|
-
:password_confirmation => password)
|
132
|
-
second_user = Factory(:user,
|
133
|
-
:password => password,
|
134
|
-
:password_confirmation => password)
|
115
|
+
first_user = Factory(:user, :password => password)
|
116
|
+
second_user = Factory(:user, :password => password)
|
135
117
|
|
136
118
|
second_user.remember_token.should_not == first_user.remember_token
|
137
119
|
end
|
@@ -155,9 +137,9 @@ describe User do
|
|
155
137
|
end
|
156
138
|
|
157
139
|
describe "and then updates password" do
|
158
|
-
describe 'with
|
140
|
+
describe 'with password' do
|
159
141
|
before do
|
160
|
-
@user.update_password("new_password"
|
142
|
+
@user.update_password("new_password")
|
161
143
|
end
|
162
144
|
|
163
145
|
it "should change encrypted password" do
|
@@ -169,23 +151,9 @@ describe User do
|
|
169
151
|
end
|
170
152
|
end
|
171
153
|
|
172
|
-
describe '
|
173
|
-
before do
|
174
|
-
@user.update_password("new_password", "")
|
175
|
-
end
|
176
|
-
|
177
|
-
it "should not change encrypted password" do
|
178
|
-
@old_encrypted_password.should == @user.encrypted_password
|
179
|
-
end
|
180
|
-
|
181
|
-
it "should not clear confirmation token" do
|
182
|
-
@user.confirmation_token.should_not be_nil
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
|
-
describe 'with blank password and confirmation' do
|
154
|
+
describe 'with blank password' do
|
187
155
|
before do
|
188
|
-
@user.update_password(""
|
156
|
+
@user.update_password("")
|
189
157
|
end
|
190
158
|
|
191
159
|
it "does not change encrypted password" do
|
@@ -251,7 +219,7 @@ describe User do
|
|
251
219
|
end
|
252
220
|
|
253
221
|
it "should initialize salt, generate remember token, and save encrypted password on update_password" do
|
254
|
-
@user.update_password('password'
|
222
|
+
@user.update_password('password')
|
255
223
|
@user.salt.should_not be_nil
|
256
224
|
@user.encrypted_password.should_not be_nil
|
257
225
|
@user.remember_token.should_not be_nil
|
metadata
CHANGED
@@ -4,9 +4,9 @@ version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease: false
|
5
5
|
segments:
|
6
6
|
- 0
|
7
|
-
-
|
8
|
-
-
|
9
|
-
version: 0.
|
7
|
+
- 11
|
8
|
+
- 0
|
9
|
+
version: 0.11.0
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Dan Croak
|
@@ -31,7 +31,7 @@ autorequire:
|
|
31
31
|
bindir: bin
|
32
32
|
cert_chain: []
|
33
33
|
|
34
|
-
date: 2011-04-
|
34
|
+
date: 2011-04-24 00:00:00 -04:00
|
35
35
|
default_executable:
|
36
36
|
dependencies:
|
37
37
|
- !ruby/object:Gem::Dependency
|
@@ -94,7 +94,7 @@ dependencies:
|
|
94
94
|
version: 0.10.0
|
95
95
|
type: :development
|
96
96
|
version_requirements: *id004
|
97
|
-
description: Rails authentication with email & password.
|
97
|
+
description: Rails authentication & authorization with email & password.
|
98
98
|
email: support@thoughtbot.com
|
99
99
|
executables: []
|
100
100
|
|
@@ -124,7 +124,6 @@ files:
|
|
124
124
|
- app/views/passwords/new.html.erb
|
125
125
|
- app/views/sessions/new.html.erb
|
126
126
|
- app/views/users/_form.html.erb
|
127
|
-
- app/views/users/_inputs.html.erb
|
128
127
|
- app/views/users/new.html.erb
|
129
128
|
- clearance.gemspec
|
130
129
|
- config/routes.rb
|
@@ -197,7 +196,7 @@ rubyforge_project:
|
|
197
196
|
rubygems_version: 1.3.7
|
198
197
|
signing_key:
|
199
198
|
specification_version: 3
|
200
|
-
summary: Rails authentication with email & password.
|
199
|
+
summary: Rails authentication & authorization with email & password.
|
201
200
|
test_files:
|
202
201
|
- features/engine/visitor_resets_password.feature
|
203
202
|
- features/engine/visitor_signs_in.feature
|