gravis-clearance 0.3.7
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +21 -0
- data/README.textile +165 -0
- data/Rakefile +46 -0
- data/TODO.textile +22 -0
- data/generators/clearance/USAGE +1 -0
- data/generators/clearance/clearance_generator.rb +73 -0
- data/generators/clearance/templates/app/controllers/application.rb +5 -0
- data/generators/clearance/templates/app/controllers/confirmations_controller.rb +3 -0
- data/generators/clearance/templates/app/controllers/passwords_controller.rb +3 -0
- data/generators/clearance/templates/app/controllers/sessions_controller.rb +3 -0
- data/generators/clearance/templates/app/controllers/users_controller.rb +3 -0
- data/generators/clearance/templates/app/models/clearance_mailer.rb +5 -0
- data/generators/clearance/templates/app/models/user.rb +3 -0
- data/generators/clearance/templates/app/views/clearance_mailer/change_password.html.erb +6 -0
- data/generators/clearance/templates/app/views/clearance_mailer/confirmation.html.erb +1 -0
- data/generators/clearance/templates/app/views/confirmations/new.html.erb +6 -0
- data/generators/clearance/templates/app/views/passwords/edit.html.erb +23 -0
- data/generators/clearance/templates/app/views/passwords/new.html.erb +15 -0
- data/generators/clearance/templates/app/views/sessions/new.html.erb +26 -0
- data/generators/clearance/templates/app/views/users/_form.html.erb +13 -0
- data/generators/clearance/templates/app/views/users/edit.html.erb +4 -0
- data/generators/clearance/templates/app/views/users/new.html.erb +4 -0
- data/generators/clearance/templates/test/factories.rb +9 -0
- data/generators/clearance/templates/test/functional/confirmations_controller_test.rb +5 -0
- data/generators/clearance/templates/test/functional/passwords_controller_test.rb +5 -0
- data/generators/clearance/templates/test/functional/sessions_controller_test.rb +5 -0
- data/generators/clearance/templates/test/functional/users_controller_test.rb +5 -0
- data/generators/clearance/templates/test/unit/clearance_mailer_test.rb +6 -0
- data/generators/clearance/templates/test/unit/user_test.rb +5 -0
- data/lib/clearance.rb +15 -0
- data/lib/clearance/app/controllers/application_controller.rb +84 -0
- data/lib/clearance/app/controllers/confirmations_controller.rb +46 -0
- data/lib/clearance/app/controllers/passwords_controller.rb +67 -0
- data/lib/clearance/app/controllers/sessions_controller.rb +79 -0
- data/lib/clearance/app/controllers/users_controller.rb +47 -0
- data/lib/clearance/app/models/clearance_mailer.rb +33 -0
- data/lib/clearance/app/models/user.rb +93 -0
- data/lib/clearance/test/functional/confirmations_controller_test.rb +85 -0
- data/lib/clearance/test/functional/passwords_controller_test.rb +188 -0
- data/lib/clearance/test/functional/sessions_controller_test.rb +148 -0
- data/lib/clearance/test/functional/users_controller_test.rb +67 -0
- data/lib/clearance/test/test_helper.rb +94 -0
- data/lib/clearance/test/unit/clearance_mailer_test.rb +63 -0
- data/lib/clearance/test/unit/user_test.rb +222 -0
- data/lib/clearance/version.rb +7 -0
- metadata +120 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
require 'digest/sha1'
|
2
|
+
|
3
|
+
module Clearance
|
4
|
+
module App
|
5
|
+
module Models
|
6
|
+
module User
|
7
|
+
|
8
|
+
def self.included(base)
|
9
|
+
base.class_eval do
|
10
|
+
|
11
|
+
attr_accessible :email, :password, :password_confirmation
|
12
|
+
attr_accessor :password, :password_confirmation
|
13
|
+
|
14
|
+
validates_presence_of :email
|
15
|
+
validates_presence_of :password, :if => :password_required?
|
16
|
+
validates_confirmation_of :password, :if => :password_required?
|
17
|
+
validates_uniqueness_of :email
|
18
|
+
validates_format_of :email, :with => %r{.+@.+\..+}
|
19
|
+
|
20
|
+
before_save :initialize_salt, :encrypt_password, :downcase_email
|
21
|
+
|
22
|
+
extend ClassMethods
|
23
|
+
include InstanceMethods
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
include ProtectedInstanceMethods
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
def authenticate(email, password)
|
34
|
+
user = find_by_email email.downcase
|
35
|
+
user && user.authenticated?(password) ? user : nil
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module InstanceMethods
|
40
|
+
def authenticated?(password)
|
41
|
+
crypted_password == encrypt(password)
|
42
|
+
end
|
43
|
+
|
44
|
+
def encrypt(password)
|
45
|
+
Digest::SHA1.hexdigest "--#{salt}--#{password}--"
|
46
|
+
end
|
47
|
+
|
48
|
+
def remember_token?
|
49
|
+
remember_token_expires_at && Time.now.utc < remember_token_expires_at
|
50
|
+
end
|
51
|
+
|
52
|
+
def remember_me!
|
53
|
+
remember_me_until 2.weeks.from_now.utc
|
54
|
+
end
|
55
|
+
|
56
|
+
def remember_me_until(time)
|
57
|
+
self.update_attribute :remember_token_expires_at, time
|
58
|
+
self.update_attribute :remember_token, encrypt("#{email}--#{remember_token_expires_at}")
|
59
|
+
end
|
60
|
+
|
61
|
+
def forget_me!
|
62
|
+
self.update_attribute :remember_token_expires_at, nil
|
63
|
+
self.update_attribute :remember_token, nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def confirm!
|
67
|
+
self.update_attribute :confirmed, true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
module ProtectedInstanceMethods
|
72
|
+
def initialize_salt
|
73
|
+
self.salt = Digest::SHA1.hexdigest("--#{Time.now.to_s}--#{email}--") if new_record?
|
74
|
+
end
|
75
|
+
|
76
|
+
def encrypt_password
|
77
|
+
return if password.blank?
|
78
|
+
self.crypted_password = encrypt(password)
|
79
|
+
end
|
80
|
+
|
81
|
+
def password_required?
|
82
|
+
crypted_password.blank? || !password.blank?
|
83
|
+
end
|
84
|
+
|
85
|
+
def downcase_email
|
86
|
+
self.email.downcase!
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
module Clearance
|
2
|
+
module Test
|
3
|
+
module Functional
|
4
|
+
module ConfirmationsControllerTest
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
|
9
|
+
context 'A GET to #new' do
|
10
|
+
context "with the User with the given id's salt" do
|
11
|
+
setup do
|
12
|
+
@user = Factory :user
|
13
|
+
get :new, :user_id => @user.to_param, :salt => @user.salt
|
14
|
+
end
|
15
|
+
|
16
|
+
should 'find the User record with the given id and salt' do
|
17
|
+
assert_equal @user, assigns(:user)
|
18
|
+
end
|
19
|
+
|
20
|
+
should_respond_with :success
|
21
|
+
should_render_template :new
|
22
|
+
end
|
23
|
+
|
24
|
+
context "without the User with the given id's salt" do
|
25
|
+
setup do
|
26
|
+
user = Factory :user
|
27
|
+
salt = ''
|
28
|
+
assert_not_equal salt, user.salt
|
29
|
+
|
30
|
+
get :new, :user_id => user.to_param, :salt => ''
|
31
|
+
end
|
32
|
+
|
33
|
+
should_respond_with :not_found
|
34
|
+
|
35
|
+
should 'render nothing' do
|
36
|
+
assert @response.body.blank?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'A POST to #create' do
|
42
|
+
context "with the User with the given id's salt" do
|
43
|
+
setup do
|
44
|
+
@user = Factory :user
|
45
|
+
assert ! @user.confirmed?
|
46
|
+
|
47
|
+
post :create, :user_id => @user, :salt => @user.salt
|
48
|
+
@user.reload
|
49
|
+
end
|
50
|
+
|
51
|
+
should 'confirm the User record with the given id' do
|
52
|
+
assert @user.confirmed?
|
53
|
+
end
|
54
|
+
|
55
|
+
should 'log the User in' do
|
56
|
+
assert_equal @user.id, session[:user_id]
|
57
|
+
end
|
58
|
+
|
59
|
+
should_redirect_to "@controller.send(:url_after_create)"
|
60
|
+
end
|
61
|
+
|
62
|
+
context "without the User with the given id's salt" do
|
63
|
+
setup do
|
64
|
+
user = Factory :user
|
65
|
+
salt = ''
|
66
|
+
assert_not_equal salt, user.salt
|
67
|
+
|
68
|
+
post :create, :user_id => user.id, :salt => salt
|
69
|
+
end
|
70
|
+
|
71
|
+
should_respond_with :not_found
|
72
|
+
|
73
|
+
should 'render nothing' do
|
74
|
+
assert @response.body.blank?
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,188 @@
|
|
1
|
+
module Clearance
|
2
|
+
module Test
|
3
|
+
module Functional
|
4
|
+
module PasswordsControllerTest
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
|
9
|
+
should_route :get, '/users/1/password/edit', :action => 'edit', :user_id => '1'
|
10
|
+
|
11
|
+
context 'with a user' do
|
12
|
+
setup { @user = Factory :user }
|
13
|
+
|
14
|
+
context 'A GET to #new' do
|
15
|
+
setup { get :new, :user_id => @user.to_param }
|
16
|
+
|
17
|
+
should_respond_with :success
|
18
|
+
should_render_template 'new'
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'A POST to #create' do
|
22
|
+
context "with an existing user's email address" do
|
23
|
+
setup do
|
24
|
+
ActionMailer::Base.deliveries.clear
|
25
|
+
|
26
|
+
post :create, :password => { :email => @user.email }
|
27
|
+
@email = ActionMailer::Base.deliveries[0]
|
28
|
+
end
|
29
|
+
|
30
|
+
should 'send an email to the user to edit their password' do
|
31
|
+
assert @email.subject =~ /change your password/i
|
32
|
+
end
|
33
|
+
|
34
|
+
should_redirect_to "@controller.send(:url_after_create)"
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with a non-existing email address' do
|
38
|
+
setup do
|
39
|
+
email = 'user1@example.com'
|
40
|
+
assert ! User.exists?(['email = ?', email])
|
41
|
+
ActionMailer::Base.deliveries.clear
|
42
|
+
|
43
|
+
post :create, :password => { :email => email }
|
44
|
+
end
|
45
|
+
|
46
|
+
should 'not send a password reminder email' do
|
47
|
+
assert ActionMailer::Base.deliveries.empty?
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'set a :warning flash' do
|
51
|
+
assert_not_nil flash.now[:warning]
|
52
|
+
end
|
53
|
+
|
54
|
+
should_render_template "new"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'A GET to #edit' do
|
59
|
+
context "with an existing user's id and password" do
|
60
|
+
setup do
|
61
|
+
get :edit,
|
62
|
+
:user_id => @user.to_param,
|
63
|
+
:password => @user.crypted_password,
|
64
|
+
:email => @user.email
|
65
|
+
end
|
66
|
+
|
67
|
+
should 'find the user with the given id and password' do
|
68
|
+
assert_equal @user, assigns(:user)
|
69
|
+
end
|
70
|
+
|
71
|
+
should_respond_with :success
|
72
|
+
should_render_template "edit"
|
73
|
+
|
74
|
+
should "have a form for the user's email, password, and password confirm" do
|
75
|
+
update_path = ERB::Util.h(user_password_path(@user,
|
76
|
+
:password => @user.crypted_password,
|
77
|
+
:email => @user.email))
|
78
|
+
|
79
|
+
assert_select 'form[action=?]', update_path do
|
80
|
+
assert_select 'input[name=_method][value=?]', 'put'
|
81
|
+
assert_select 'input[name=?]', 'user[password]'
|
82
|
+
assert_select 'input[name=?]', 'user[password_confirmation]'
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "with an existing user's id but not password" do
|
88
|
+
setup do
|
89
|
+
get :edit, :user_id => @user.to_param, :password => ''
|
90
|
+
end
|
91
|
+
|
92
|
+
should_respond_with :not_found
|
93
|
+
|
94
|
+
should 'render an empty response' do
|
95
|
+
assert @response.body.blank?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'A PUT to #update' do
|
101
|
+
context "with an existing user's id but not password" do
|
102
|
+
setup do
|
103
|
+
put :update, :user_id => @user.to_param, :password => ''
|
104
|
+
end
|
105
|
+
|
106
|
+
should "not update the user's password" do
|
107
|
+
assert_not_equal @encrypted_new_password, @user.crypted_password
|
108
|
+
end
|
109
|
+
|
110
|
+
should 'not log the user in' do
|
111
|
+
assert_nil session[:user_id]
|
112
|
+
end
|
113
|
+
|
114
|
+
should_respond_with :not_found
|
115
|
+
|
116
|
+
should 'render an empty response' do
|
117
|
+
assert @response.body.blank?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'with a matching password and password confirmation' do
|
122
|
+
setup do
|
123
|
+
new_password = 'new_password'
|
124
|
+
encryption_format = "--#{@user.salt}--#{new_password}--"
|
125
|
+
@encrypted_new_password = Digest::SHA1.hexdigest encryption_format
|
126
|
+
assert_not_equal @encrypted_new_password, @user.crypted_password
|
127
|
+
|
128
|
+
put(:update,
|
129
|
+
:user_id => @user,
|
130
|
+
:email => @user.email,
|
131
|
+
:password => @user.crypted_password,
|
132
|
+
:user => {
|
133
|
+
:password => new_password,
|
134
|
+
:password_confirmation => new_password
|
135
|
+
})
|
136
|
+
@user.reload
|
137
|
+
end
|
138
|
+
|
139
|
+
should "update the user's password" do
|
140
|
+
assert_equal @encrypted_new_password, @user.crypted_password
|
141
|
+
end
|
142
|
+
|
143
|
+
should 'log the user in' do
|
144
|
+
assert_equal session[:user_id], @user.id
|
145
|
+
end
|
146
|
+
|
147
|
+
should_redirect_to "user_path(@user)"
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'with password but blank password confirmation' do
|
151
|
+
setup do
|
152
|
+
new_password = 'new_password'
|
153
|
+
encryption_format = "--#{@user.salt}--#{new_password}--"
|
154
|
+
@encrypted_new_password = Digest::SHA1.hexdigest encryption_format
|
155
|
+
|
156
|
+
put(:update,
|
157
|
+
:user_id => @user.to_param,
|
158
|
+
:password => @user.crypted_password,
|
159
|
+
:user => {
|
160
|
+
:password => new_password,
|
161
|
+
:password_confirmation => ''
|
162
|
+
})
|
163
|
+
@user.reload
|
164
|
+
end
|
165
|
+
|
166
|
+
should "not update the user's password" do
|
167
|
+
assert_not_equal @encrypted_new_password, @user.crypted_password
|
168
|
+
end
|
169
|
+
|
170
|
+
should 'not log the user in' do
|
171
|
+
assert_nil session[:user_id]
|
172
|
+
end
|
173
|
+
|
174
|
+
should_respond_with :not_found
|
175
|
+
|
176
|
+
should 'render an empty response' do
|
177
|
+
assert @response.body.blank?
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Clearance
|
2
|
+
module Test
|
3
|
+
module Functional
|
4
|
+
module SessionsControllerTest
|
5
|
+
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
should_filter_params :password
|
9
|
+
|
10
|
+
context "on GET to /sessions/new" do
|
11
|
+
setup { get :new }
|
12
|
+
|
13
|
+
should_respond_with :success
|
14
|
+
should_render_template :new
|
15
|
+
should_not_set_the_flash
|
16
|
+
should_have_form :action => "session_path",
|
17
|
+
:fields => { "session[email]" => :text,
|
18
|
+
"session[password]" => :password,
|
19
|
+
"session[remember_me]" => :checkbox }
|
20
|
+
end
|
21
|
+
|
22
|
+
context "Given an unconfirmed user" do
|
23
|
+
setup do
|
24
|
+
@user = Factory(:user, :confirmed => false)
|
25
|
+
end
|
26
|
+
|
27
|
+
context "a POST to #create with good credentials" do
|
28
|
+
setup do
|
29
|
+
ActionMailer::Base.deliveries.clear
|
30
|
+
post :create, :session => {
|
31
|
+
:email => @user.email,
|
32
|
+
:password => @user.password
|
33
|
+
}
|
34
|
+
end
|
35
|
+
|
36
|
+
should_deny_access(:flash => /confirm/i)
|
37
|
+
|
38
|
+
should "send the confirmation email" do
|
39
|
+
assert_not_nil email = ActionMailer::Base.deliveries[0]
|
40
|
+
assert_match /account confirmation/i, email.subject
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "Given a confirmed user" do
|
46
|
+
setup { @user = Factory(:user, :confirmed => true) }
|
47
|
+
|
48
|
+
context "a POST to #create with good credentials" do
|
49
|
+
setup do
|
50
|
+
post :create, :session => { :email => @user.email,
|
51
|
+
:password => @user.password }
|
52
|
+
end
|
53
|
+
|
54
|
+
should_set_the_flash_to /success/i
|
55
|
+
should_redirect_to '@controller.send(:url_after_create)'
|
56
|
+
should_return_from_session :user_id, "@user.id"
|
57
|
+
end
|
58
|
+
|
59
|
+
context "a POST to #create with bad credentials" do
|
60
|
+
setup do
|
61
|
+
post :create, :session => { :email => @user.email,
|
62
|
+
:password => "bad value" }
|
63
|
+
end
|
64
|
+
|
65
|
+
should_set_the_flash_to /bad/i
|
66
|
+
should_render_template :new
|
67
|
+
should_return_from_session :user_id, "nil"
|
68
|
+
end
|
69
|
+
|
70
|
+
context "a POST to #create with good credentials and remember me" do
|
71
|
+
setup do
|
72
|
+
post :create, :session => { :email => @user.email,
|
73
|
+
:password => @user.password, :remember_me => '1' }
|
74
|
+
end
|
75
|
+
|
76
|
+
should_set_the_flash_to /success/i
|
77
|
+
should_redirect_to "@controller.send(:url_after_create)"
|
78
|
+
should_return_from_session :user_id, "@user.id"
|
79
|
+
|
80
|
+
should 'set the cookie' do
|
81
|
+
assert ! cookies['auth_token'].empty?
|
82
|
+
end
|
83
|
+
|
84
|
+
should 'set the remember me token in users table' do
|
85
|
+
assert_not_nil @user.reload.remember_token
|
86
|
+
assert_not_nil @user.reload.remember_token_expires_at
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "a POST to #create with bad credentials and remember me" do
|
91
|
+
setup do
|
92
|
+
post :create, :session => { :email => @user.email,
|
93
|
+
:password => "bad value", :remember_me => '1' }
|
94
|
+
end
|
95
|
+
|
96
|
+
should_set_the_flash_to /bad/i
|
97
|
+
should_render_template :new
|
98
|
+
should_return_from_session :user_id, "nil"
|
99
|
+
|
100
|
+
should 'not create the cookie' do
|
101
|
+
assert_nil cookies['auth_token']
|
102
|
+
end
|
103
|
+
|
104
|
+
should 'not set the remember me token in users table' do
|
105
|
+
assert_nil @user.reload.remember_token
|
106
|
+
assert_nil @user.reload.remember_token_expires_at
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
public_context do
|
112
|
+
context "logging out again" do
|
113
|
+
setup { delete :destroy }
|
114
|
+
should_redirect_to '@controller.send(:url_after_destroy)'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
logged_in_user_context do
|
119
|
+
context "a DELETE to #destroy without a cookie" do
|
120
|
+
setup { delete :destroy }
|
121
|
+
|
122
|
+
should_set_the_flash_to(/logged out/i)
|
123
|
+
should_redirect_to '@controller.send(:url_after_destroy)'
|
124
|
+
end
|
125
|
+
|
126
|
+
context 'a DELETE to #destroy with a cookie' do
|
127
|
+
setup do
|
128
|
+
cookies['auth_token'] = CGI::Cookie.new 'token', 'value'
|
129
|
+
delete :destroy
|
130
|
+
end
|
131
|
+
|
132
|
+
should 'delete the cookie' do
|
133
|
+
assert cookies['auth_token'].empty?
|
134
|
+
end
|
135
|
+
|
136
|
+
should 'delete the remember me token in users table' do
|
137
|
+
assert_nil @user.reload.remember_token
|
138
|
+
assert_nil @user.reload.remember_token_expires_at
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|