clearance 1.0.0.rc7 → 1.0.0.rc8
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.
- checksums.yaml +6 -14
- data/.gitignore +3 -3
- data/.travis.yml +0 -17
- data/Appraisals +2 -6
- data/Gemfile +16 -1
- data/Gemfile.lock +116 -107
- data/NEWS.md +7 -3
- data/README.md +195 -129
- data/Rakefile +5 -9
- data/app/views/layouts/application.html.erb +1 -1
- data/clearance.gemspec +20 -17
- data/config/locales/clearance.en.yml +2 -1
- data/config/routes.rb +2 -2
- data/features/add_migrations_to_project.feature +7 -37
- data/features/integration_with_rspec.feature +5 -4
- data/features/integration_with_test_unit.feature +11 -38
- data/features/step_definitions/configuration_steps.rb +94 -8
- data/features/step_definitions/gem_file_steps.rb +8 -0
- data/features/support/env.rb +7 -0
- data/lib/clearance/authorization.rb +16 -3
- data/lib/clearance/configuration.rb +2 -0
- data/lib/clearance/engine.rb +1 -1
- data/lib/clearance/session.rb +12 -11
- data/lib/clearance/testing.rb +0 -4
- data/lib/clearance/testing/application.rb +23 -24
- data/lib/clearance/testing/helpers.rb +1 -1
- data/lib/clearance/user.rb +17 -10
- data/lib/clearance/version.rb +1 -1
- data/lib/generators/clearance/specs/templates/{integration → features}/clearance/user_signs_out_spec.rb +0 -0
- data/lib/generators/clearance/specs/templates/{integration → features}/clearance/visitor_resets_password_spec.rb +24 -0
- data/lib/generators/clearance/specs/templates/{integration → features}/clearance/visitor_signs_in_spec.rb +0 -0
- data/lib/generators/clearance/specs/templates/{integration → features}/clearance/visitor_signs_up_spec.rb +8 -0
- data/lib/generators/clearance/specs/templates/{integration → features}/clearance/visitor_updates_password_spec.rb +0 -0
- data/lib/generators/clearance/specs/templates/support/features.rb +5 -0
- data/lib/generators/clearance/specs/templates/support/{integration → features}/clearance_helpers.rb +1 -1
- data/spec/clearance/session_spec.rb +27 -0
- data/spec/controllers/apis_controller_spec.rb +36 -0
- data/spec/controllers/passwords_controller_spec.rb +13 -11
- data/spec/controllers/sessions_controller_spec.rb +13 -27
- data/spec/controllers/users_controller_spec.rb +6 -2
- data/spec/factories.rb +5 -0
- data/spec/models/user_spec.rb +13 -19
- data/spec/support/clearance.rb +9 -0
- metadata +36 -218
- data/gemfiles/3.0.20.gemfile +0 -7
- data/gemfiles/3.0.20.gemfile.lock +0 -173
- data/gemfiles/3.1.11.gemfile +0 -7
- data/gemfiles/3.1.11.gemfile.lock +0 -183
- data/gemfiles/3.2.12.gemfile +0 -7
- data/gemfiles/3.2.12.gemfile.lock +0 -182
- data/gemfiles/3.2.13.rc2.gemfile +0 -7
- data/gemfiles/3.2.13.rc2.gemfile.lock +0 -182
- data/lib/clearance/password_strategies/fake.rb +0 -23
- data/lib/generators/clearance/specs/templates/support/integration.rb +0 -6
- data/lib/generators/clearance/specs/templates/support/integration/action_mailer_helpers.rb +0 -19
@@ -2,6 +2,7 @@ module Clearance
|
|
2
2
|
class Configuration
|
3
3
|
attr_accessor \
|
4
4
|
:cookie_expiration,
|
5
|
+
:httponly,
|
5
6
|
:mailer_sender,
|
6
7
|
:password_strategy,
|
7
8
|
:redirect_url,
|
@@ -10,6 +11,7 @@ module Clearance
|
|
10
11
|
|
11
12
|
def initialize
|
12
13
|
@cookie_expiration = lambda { 1.year.from_now.utc }
|
14
|
+
@httponly = false
|
13
15
|
@mailer_sender = 'reply@example.com'
|
14
16
|
@secure_cookie = false
|
15
17
|
@redirect_url = '/'
|
data/lib/clearance/engine.rb
CHANGED
@@ -7,6 +7,6 @@ module Clearance
|
|
7
7
|
app.config.filter_parameters += [:password, :token]
|
8
8
|
end
|
9
9
|
|
10
|
-
config.app_middleware.insert_after ActionDispatch::
|
10
|
+
config.app_middleware.insert_after ActionDispatch::ParamsParser, Clearance::RackSession
|
11
11
|
end
|
12
12
|
end
|
data/lib/clearance/session.rb
CHANGED
@@ -7,15 +7,15 @@ module Clearance
|
|
7
7
|
end
|
8
8
|
|
9
9
|
def add_cookie_to_headers(headers)
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
10
|
+
Rack::Utils.set_cookie_header!(
|
11
|
+
headers,
|
12
|
+
REMEMBER_TOKEN_COOKIE,
|
13
|
+
:value => remember_token,
|
14
|
+
:expires => Clearance.configuration.cookie_expiration.call,
|
15
|
+
:secure => Clearance.configuration.secure_cookie,
|
16
|
+
:httponly => Clearance.configuration.httponly,
|
17
|
+
:path => '/'
|
18
|
+
)
|
19
19
|
end
|
20
20
|
|
21
21
|
def current_user
|
@@ -26,6 +26,7 @@ module Clearance
|
|
26
26
|
|
27
27
|
def sign_in(user)
|
28
28
|
@current_user = user
|
29
|
+
cookies[REMEMBER_TOKEN_COOKIE] = @current_user.remember_token
|
29
30
|
end
|
30
31
|
|
31
32
|
def sign_out
|
@@ -56,8 +57,8 @@ module Clearance
|
|
56
57
|
end
|
57
58
|
|
58
59
|
def with_remember_token
|
59
|
-
if
|
60
|
-
yield
|
60
|
+
if remember_token
|
61
|
+
yield remember_token
|
61
62
|
end
|
62
63
|
end
|
63
64
|
end
|
data/lib/clearance/testing.rb
CHANGED
@@ -2,10 +2,6 @@ require 'clearance/testing/assertion_error'
|
|
2
2
|
require 'clearance/testing/deny_access_matcher'
|
3
3
|
require 'clearance/testing/helpers'
|
4
4
|
|
5
|
-
Clearance.configure do |config|
|
6
|
-
config.password_strategy = Clearance::PasswordStrategies::Fake
|
7
|
-
end
|
8
|
-
|
9
5
|
if defined?(ActionController::TestCase)
|
10
6
|
ActionController::TestCase.extend Clearance::Testing::Matchers
|
11
7
|
|
@@ -4,41 +4,40 @@ module Clearance
|
|
4
4
|
module Testing
|
5
5
|
APP_ROOT = File.expand_path('..', __FILE__).freeze
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
if Rails::VERSION::MAJOR >= 3 && Rails::VERSION::MINOR >= 1
|
12
|
-
config.paths['config/database'] = "#{APP_ROOT}/config/database.yml"
|
13
|
-
config.paths['config/routes'] << "#{APP_ROOT}/config/routes.rb"
|
14
|
-
config.paths['app/controllers'] << "#{APP_ROOT}/app/controllers"
|
15
|
-
config.paths['app/views'] << "#{APP_ROOT}/app/views"
|
16
|
-
config.paths['log'] = "tmp/log/development.log"
|
17
|
-
config.assets.enabled = true
|
18
|
-
else
|
19
|
-
config.paths.config.database = "#{APP_ROOT}/config/database.yml"
|
20
|
-
config.paths.config.routes << "#{APP_ROOT}/config/routes.rb"
|
21
|
-
config.paths.app.controllers << "#{APP_ROOT}/app/controllers"
|
22
|
-
config.paths.app.views << "#{APP_ROOT}/app/views"
|
23
|
-
config.paths.log = "tmp/log"
|
24
|
-
end
|
7
|
+
def self.rails4?
|
8
|
+
Rails::VERSION::MAJOR == 4
|
9
|
+
end
|
25
10
|
|
26
|
-
|
27
|
-
config.
|
28
|
-
config.consider_all_requests_local = true
|
11
|
+
class Application < Rails::Application
|
12
|
+
config.action_controller.allow_forgery_protection = false
|
29
13
|
config.action_controller.perform_caching = false
|
30
14
|
config.action_dispatch.show_exceptions = false
|
31
|
-
config.
|
15
|
+
config.action_mailer.default_url_options = { :host => 'localhost' }
|
32
16
|
config.action_mailer.delivery_method = :test
|
33
17
|
config.active_support.deprecation = :stderr
|
34
|
-
config.
|
18
|
+
config.assets.enabled = true
|
19
|
+
config.cache_classes = true
|
20
|
+
config.consider_all_requests_local = true
|
21
|
+
config.eager_load = false
|
22
|
+
config.encoding = 'utf-8'
|
23
|
+
config.paths['app/controllers'] << "#{APP_ROOT}/app/controllers"
|
24
|
+
config.paths['app/views'] << "#{APP_ROOT}/app/views"
|
25
|
+
config.paths['config/database'] = "#{APP_ROOT}/config/database.yml"
|
26
|
+
config.paths['log'] = 'tmp/log/development.log'
|
27
|
+
config.secret_token = 'SECRET_TOKEN_IS_MIN_30_CHARS_LONG'
|
28
|
+
|
29
|
+
if Clearance::Testing.rails4?
|
30
|
+
config.paths.add 'config/routes.rb', with: "#{APP_ROOT}/config/routes.rb"
|
31
|
+
else
|
32
|
+
config.paths.add 'config/routes', with: "#{APP_ROOT}/config/routes.rb"
|
33
|
+
end
|
35
34
|
|
36
35
|
def require_environment!
|
37
36
|
initialize!
|
38
37
|
end
|
39
38
|
|
40
39
|
def initialize!
|
41
|
-
FileUtils.mkdir_p(Rails.root.join(
|
40
|
+
FileUtils.mkdir_p(Rails.root.join('db').to_s)
|
42
41
|
super unless @initialized
|
43
42
|
end
|
44
43
|
end
|
data/lib/clearance/user.rb
CHANGED
@@ -11,16 +11,13 @@ module Clearance
|
|
11
11
|
|
12
12
|
include Validations
|
13
13
|
include Callbacks
|
14
|
-
include
|
15
|
-
Clearance.configuration.password_strategy ||
|
16
|
-
Clearance::PasswordStrategies::BCrypt
|
17
|
-
)
|
14
|
+
include password_strategy
|
18
15
|
end
|
19
16
|
|
20
17
|
module ClassMethods
|
21
18
|
def authenticate(email, password)
|
22
19
|
if user = find_by_normalized_email(email)
|
23
|
-
if user.authenticated?
|
20
|
+
if password.present? && user.authenticated?(password)
|
24
21
|
return user
|
25
22
|
end
|
26
23
|
end
|
@@ -33,6 +30,12 @@ module Clearance
|
|
33
30
|
def normalize_email(email)
|
34
31
|
email.to_s.downcase.gsub(/\s+/, "")
|
35
32
|
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def password_strategy
|
37
|
+
Clearance.configuration.password_strategy || PasswordStrategies::BCrypt
|
38
|
+
end
|
36
39
|
end
|
37
40
|
|
38
41
|
module Validations
|
@@ -45,7 +48,7 @@ module Clearance
|
|
45
48
|
uniqueness: { allow_blank: true },
|
46
49
|
unless: :email_optional?
|
47
50
|
|
48
|
-
validates :password, presence: true, unless: :
|
51
|
+
validates :password, presence: true, unless: :skip_password_validation?
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
@@ -90,6 +93,14 @@ module Clearance
|
|
90
93
|
false
|
91
94
|
end
|
92
95
|
|
96
|
+
def password_optional?
|
97
|
+
false
|
98
|
+
end
|
99
|
+
|
100
|
+
def skip_password_validation?
|
101
|
+
password_optional? || (encrypted_password.present? && !password_changing)
|
102
|
+
end
|
103
|
+
|
93
104
|
def generate_confirmation_token
|
94
105
|
self.confirmation_token = SecureRandom.hex(20).encode('UTF-8')
|
95
106
|
end
|
@@ -97,9 +108,5 @@ module Clearance
|
|
97
108
|
def generate_remember_token
|
98
109
|
self.remember_token = SecureRandom.hex(20).encode('UTF-8')
|
99
110
|
end
|
100
|
-
|
101
|
-
def password_optional?
|
102
|
-
encrypted_password.present? && password.blank? && password_changing.blank?
|
103
|
-
end
|
104
111
|
end
|
105
112
|
end
|
data/lib/clearance/version.rb
CHANGED
File without changes
|
@@ -1,6 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
feature 'Visitor resets password' do
|
4
|
+
scenario 'by navigating to the page' do
|
5
|
+
visit sign_in_path
|
6
|
+
|
7
|
+
click_link I18n.t('sessions.form.forgot_password')
|
8
|
+
|
9
|
+
current_path.should eq new_password_path
|
10
|
+
end
|
11
|
+
|
4
12
|
scenario 'with valid email' do
|
5
13
|
user = user_with_reset_password
|
6
14
|
|
@@ -25,4 +33,20 @@ feature 'Visitor resets password' do
|
|
25
33
|
def page_should_display_change_password_message
|
26
34
|
page.should have_content I18n.t('passwords.create.description')
|
27
35
|
end
|
36
|
+
|
37
|
+
def mailer_should_have_delivery(recipient, subject, body)
|
38
|
+
ActionMailer::Base.deliveries.should_not be_empty
|
39
|
+
|
40
|
+
message = ActionMailer::Base.deliveries.any? do |email|
|
41
|
+
email.to == [recipient] &&
|
42
|
+
email.subject =~ /#{subject}/i &&
|
43
|
+
email.body =~ /#{body}/
|
44
|
+
end
|
45
|
+
|
46
|
+
message.should be
|
47
|
+
end
|
48
|
+
|
49
|
+
def mailer_should_have_no_deliveries
|
50
|
+
ActionMailer::Base.deliveries.should be_empty
|
51
|
+
end
|
28
52
|
end
|
File without changes
|
@@ -1,6 +1,14 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
feature 'Visitor signs up' do
|
4
|
+
scenario 'by navigating to the page' do
|
5
|
+
visit sign_in_path
|
6
|
+
|
7
|
+
click_link I18n.t('sessions.form.sign_up')
|
8
|
+
|
9
|
+
current_path.should eq sign_up_path
|
10
|
+
end
|
11
|
+
|
4
12
|
scenario 'with valid email and password' do
|
5
13
|
sign_up_with 'valid@example.com', 'password'
|
6
14
|
|
File without changes
|
@@ -38,6 +38,33 @@ describe Clearance::Session do
|
|
38
38
|
session.current_user.should == user
|
39
39
|
end
|
40
40
|
|
41
|
+
context 'if httponly is set' do
|
42
|
+
before do
|
43
|
+
Clearance.configuration.httponly = true
|
44
|
+
session.sign_in(user)
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'sets a httponly cookie' do
|
48
|
+
session.add_cookie_to_headers(headers)
|
49
|
+
|
50
|
+
headers['Set-Cookie'].should =~ /remember_token=.+; HttpOnly/
|
51
|
+
end
|
52
|
+
|
53
|
+
after { restore_default_config }
|
54
|
+
end
|
55
|
+
|
56
|
+
context 'if httponly is not set' do
|
57
|
+
before do
|
58
|
+
session.sign_in(user)
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'sets a standard cookie' do
|
62
|
+
session.add_cookie_to_headers(headers)
|
63
|
+
|
64
|
+
headers['Set-Cookie'].should_not =~ /remember_token=.+; HttpOnly/
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
41
68
|
it 'sets a remember token cookie with a default expiration of 1 year from now' do
|
42
69
|
user = create(:user)
|
43
70
|
headers = {}
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class ApisController < ActionController::Base
|
4
|
+
include Clearance::Controller
|
5
|
+
|
6
|
+
before_filter :authorize
|
7
|
+
|
8
|
+
respond_to :js
|
9
|
+
|
10
|
+
def show
|
11
|
+
render text: 'response'
|
12
|
+
end
|
13
|
+
|
14
|
+
protected
|
15
|
+
|
16
|
+
def authorize
|
17
|
+
deny_access 'Access denied.'
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe ApisController do
|
22
|
+
before do
|
23
|
+
Rails.application.routes.draw do
|
24
|
+
resource :api, only: [:show]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
after do
|
29
|
+
Rails.application.reload_routes!
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'responds with HTTP status code 401 when denied' do
|
33
|
+
get :show, format: :js
|
34
|
+
subject.should respond_with(:unauthorized)
|
35
|
+
end
|
36
|
+
end
|
@@ -1,13 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Clearance::PasswordsController do
|
4
|
-
include Shoulda::Matchers::ActionMailer
|
5
|
-
|
6
|
-
it {
|
7
|
-
should route(:get, '/users/1/password/edit').
|
8
|
-
to(:controller => 'clearance/passwords', :action => 'edit', :user_id => '1')
|
9
|
-
}
|
10
|
-
|
11
4
|
describe 'a signed up user' do
|
12
5
|
before do
|
13
6
|
@user = create(:user)
|
@@ -31,7 +24,11 @@ describe Clearance::PasswordsController do
|
|
31
24
|
@user.reload.confirmation_token.should_not be_nil
|
32
25
|
end
|
33
26
|
|
34
|
-
it
|
27
|
+
it 'sends an email with relevant subject' do
|
28
|
+
email = ActionMailer::Base.deliveries.last
|
29
|
+
email.subject.should match(/change your password/i)
|
30
|
+
end
|
31
|
+
|
35
32
|
it { should respond_with(:success) }
|
36
33
|
end
|
37
34
|
|
@@ -45,7 +42,11 @@ describe Clearance::PasswordsController do
|
|
45
42
|
@user.reload.confirmation_token.should_not be_nil
|
46
43
|
end
|
47
44
|
|
48
|
-
it
|
45
|
+
it 'sends an email with relevant subject' do
|
46
|
+
email = ActionMailer::Base.deliveries.last
|
47
|
+
email.subject.should match(/change your password/i)
|
48
|
+
end
|
49
|
+
|
49
50
|
it { should respond_with(:success) }
|
50
51
|
end
|
51
52
|
|
@@ -113,14 +114,15 @@ describe Clearance::PasswordsController do
|
|
113
114
|
describe 'on PUT to #update with password' do
|
114
115
|
before do
|
115
116
|
@new_password = 'new_password'
|
116
|
-
@user.encrypted_password
|
117
|
+
@old_encrypted_password = @user.encrypted_password
|
118
|
+
|
117
119
|
put :update, :user_id => @user, :token => @user.confirmation_token,
|
118
120
|
:password_reset => { :password => @new_password }
|
119
121
|
@user.reload
|
120
122
|
end
|
121
123
|
|
122
124
|
it 'should update password' do
|
123
|
-
@user.encrypted_password.
|
125
|
+
@user.encrypted_password.to_s.should_not eq @old_encrypted_password
|
124
126
|
end
|
125
127
|
|
126
128
|
it 'should clear confirmation token' do
|
@@ -9,6 +9,19 @@ describe Clearance::SessionsController do
|
|
9
9
|
it { should_not set_the_flash }
|
10
10
|
end
|
11
11
|
|
12
|
+
context 'when password is optional' do
|
13
|
+
describe 'POST create' do
|
14
|
+
it 'renders the page with error' do
|
15
|
+
user = create(:user_with_optional_password)
|
16
|
+
|
17
|
+
post :create, session: { email: user.email, password: user.password }
|
18
|
+
|
19
|
+
expect(response).to render_template(:new)
|
20
|
+
expect(flash[:notice]).to match /^Bad email or password/
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
12
25
|
describe 'on POST to #create with good credentials' do
|
13
26
|
before do
|
14
27
|
@user = create(:user)
|
@@ -40,33 +53,6 @@ describe Clearance::SessionsController do
|
|
40
53
|
end
|
41
54
|
end
|
42
55
|
|
43
|
-
describe 'on POST to #create with good credentials and a request return url' do
|
44
|
-
before do
|
45
|
-
@user = create(:user)
|
46
|
-
@return_url = '/url_in_the_request'
|
47
|
-
post :create, :session => { :email => @user.email, :password => @user.password },
|
48
|
-
:return_to => @return_url
|
49
|
-
end
|
50
|
-
|
51
|
-
it 'redirects to the return URL' do
|
52
|
-
should redirect_to(@return_url)
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
describe 'on POST to #create with good credentials and a session return url and request return url' do
|
57
|
-
before do
|
58
|
-
@user = create(:user)
|
59
|
-
@return_url = '/url_in_the_session'
|
60
|
-
@request.session[:return_to] = @return_url
|
61
|
-
post :create, :session => { :email => @user.email, :password => @user.password },
|
62
|
-
:return_to => '/url_in_the_request'
|
63
|
-
end
|
64
|
-
|
65
|
-
it 'redirects to the return url' do
|
66
|
-
should redirect_to(@return_url)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
56
|
describe 'on DELETE to #destroy given a signed out user' do
|
71
57
|
before do
|
72
58
|
sign_out
|