clearance 1.0.0.rc4 → 1.0.0.rc6

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.

Files changed (58) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +14 -3
  3. data/Appraisals +7 -1
  4. data/Gemfile.lock +33 -26
  5. data/LICENSE +1 -1
  6. data/NEWS.md +13 -10
  7. data/README.md +44 -37
  8. data/Rakefile +3 -0
  9. data/app/controllers/clearance/passwords_controller.rb +6 -2
  10. data/app/views/clearance_mailer/change_password.html.erb +2 -2
  11. data/app/views/passwords/create.html.erb +3 -1
  12. data/app/views/passwords/edit.html.erb +15 -13
  13. data/app/views/passwords/new.html.erb +13 -11
  14. data/app/views/sessions/_form.html.erb +8 -3
  15. data/app/views/sessions/new.html.erb +4 -11
  16. data/app/views/users/_form.html.erb +2 -2
  17. data/app/views/users/new.html.erb +14 -5
  18. data/clearance.gemspec +5 -3
  19. data/config/locales/clearance.en.yml +53 -23
  20. data/config/routes.rb +3 -3
  21. data/gemfiles/{3.0.17.gemfile → 3.0.20.gemfile} +1 -1
  22. data/gemfiles/{3.0.17.gemfile.lock → 3.0.20.gemfile.lock} +62 -57
  23. data/gemfiles/{3.2.8.gemfile → 3.1.11.gemfile} +1 -1
  24. data/gemfiles/{3.1.8.gemfile.lock → 3.1.11.gemfile.lock} +70 -65
  25. data/gemfiles/{3.1.8.gemfile → 3.2.12.gemfile} +1 -1
  26. data/gemfiles/{3.2.8.gemfile.lock → 3.2.12.gemfile.lock} +74 -68
  27. data/gemfiles/3.2.13.rc2.gemfile +7 -0
  28. data/gemfiles/3.2.13.rc2.gemfile.lock +182 -0
  29. data/lib/clearance.rb +2 -1
  30. data/lib/clearance/authentication.rb +8 -53
  31. data/lib/clearance/authorization.rb +62 -0
  32. data/lib/clearance/back_door.rb +42 -0
  33. data/lib/clearance/controller.rb +11 -0
  34. data/lib/clearance/password_strategies/bcrypt.rb +13 -1
  35. data/lib/clearance/password_strategies/bcrypt_migration_from_sha1.rb +1 -0
  36. data/lib/clearance/password_strategies/blowfish.rb +5 -1
  37. data/lib/clearance/password_strategies/sha1.rb +5 -1
  38. data/lib/clearance/testing.rb +1 -1
  39. data/lib/clearance/testing/app/controllers/application_controller.rb +1 -1
  40. data/lib/clearance/user.rb +23 -10
  41. data/lib/clearance/version.rb +1 -1
  42. data/lib/generators/clearance/install/install_generator.rb +1 -1
  43. data/lib/generators/clearance/specs/templates/support/integration.rb +2 -0
  44. data/spec/clearance/back_door_spec.rb +39 -0
  45. data/spec/controllers/denies_controller_spec.rb +3 -2
  46. data/spec/controllers/flashes_controller_spec.rb +3 -3
  47. data/spec/controllers/forgeries_controller_spec.rb +3 -2
  48. data/spec/controllers/passwords_controller_spec.rb +14 -0
  49. data/spec/mailers/clearance_mailer_spec.rb +9 -1
  50. data/spec/models/bcrypt_migration_from_sha1_spec.rb +10 -9
  51. data/spec/models/bcrypt_spec.rb +21 -7
  52. data/spec/models/blowfish_spec.rb +1 -6
  53. data/spec/models/password_strategies_spec.rb +9 -3
  54. data/spec/models/sha1_spec.rb +1 -6
  55. data/spec/models/user_spec.rb +19 -9
  56. data/spec/support/clearance.rb +1 -1
  57. data/spec/support/fake_model_with_password_strategy.rb +14 -0
  58. metadata +54 -47
@@ -30,9 +30,13 @@ module Clearance
30
30
 
31
31
  def initialize_salt_if_necessary
32
32
  if salt.blank?
33
- self.salt = generate_random_code
33
+ self.salt = generate_salt
34
34
  end
35
35
  end
36
+
37
+ def generate_salt
38
+ SecureRandom.hex(20).encode('UTF-8')
39
+ end
36
40
  end
37
41
  end
38
42
  end
@@ -17,6 +17,6 @@ end
17
17
  if defined?(RSpec) && RSpec.respond_to?(:configure)
18
18
  RSpec.configure do |config|
19
19
  config.include Clearance::Testing::Matchers
20
- config.include Clearance::Testing::Helpers
20
+ config.include Clearance::Testing::Helpers, :type => :controller
21
21
  end
22
22
  end
@@ -1,5 +1,5 @@
1
1
  class ApplicationController < ActionController::Base
2
- include Clearance::Authentication
2
+ include Clearance::Controller
3
3
 
4
4
  def show
5
5
  render :text => '', :layout => 'application'
@@ -1,4 +1,5 @@
1
1
  require 'digest/sha1'
2
+ require 'email_validator'
2
3
 
3
4
  module Clearance
4
5
  module User
@@ -10,29 +11,41 @@ module Clearance
10
11
 
11
12
  include Validations
12
13
  include Callbacks
13
- include (Clearance.configuration.password_strategy ||
14
- Clearance::PasswordStrategies::BCrypt)
14
+ include(
15
+ Clearance.configuration.password_strategy ||
16
+ Clearance::PasswordStrategies::BCrypt
17
+ )
15
18
  end
16
19
 
17
20
  module ClassMethods
18
21
  def authenticate(email, password)
19
- if user = find_by_email(email.to_s.downcase)
22
+ if user = find_by_normalized_email(email)
20
23
  if user.authenticated? password
21
24
  return user
22
25
  end
23
26
  end
24
27
  end
28
+
29
+ def find_by_normalized_email(email)
30
+ find_by_email normalize_email(email)
31
+ end
32
+
33
+ def normalize_email(email)
34
+ email.to_s.downcase.gsub(/\s+/, "")
35
+ end
25
36
  end
26
37
 
27
38
  module Validations
28
39
  extend ActiveSupport::Concern
29
40
 
30
41
  included do
31
- validates_presence_of :email, :unless => :email_optional?
32
- validates_uniqueness_of :email, :allow_blank => true
33
- 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
42
+ validates :email,
43
+ email: true,
44
+ presence: true,
45
+ uniqueness: { allow_blank: true },
46
+ unless: :email_optional?
34
47
 
35
- validates_presence_of :password, :unless => :password_optional?
48
+ validates :password, presence: true, unless: :password_optional?
36
49
  end
37
50
  end
38
51
 
@@ -40,7 +53,7 @@ module Clearance
40
53
  extend ActiveSupport::Concern
41
54
 
42
55
  included do
43
- before_validation :downcase_email
56
+ before_validation :normalize_email
44
57
  before_create :generate_remember_token
45
58
  end
46
59
  end
@@ -69,8 +82,8 @@ module Clearance
69
82
 
70
83
  private
71
84
 
72
- def downcase_email
73
- self.email = email.to_s.downcase
85
+ def normalize_email
86
+ self.email = self.class.normalize_email(email)
74
87
  end
75
88
 
76
89
  def email_optional?
@@ -1,3 +1,3 @@
1
1
  module Clearance
2
- VERSION = '1.0.0.rc4'
2
+ VERSION = '1.0.0.rc6'
3
3
  end
@@ -15,7 +15,7 @@ module Clearance
15
15
  inject_into(
16
16
  ApplicationController,
17
17
  'app/controllers/application_controller.rb',
18
- 'include Clearance::Authentication'
18
+ 'include Clearance::Controller'
19
19
  )
20
20
  end
21
21
 
@@ -1,3 +1,5 @@
1
+ Dir[Rails.root.join("spec/support/integration/*.rb")].each { |f| require f }
2
+
1
3
  RSpec.configure do |config|
2
4
  config.include Integration::ClearanceHelpers, :type => :request
3
5
  config.include Integration::ActionMailerHelpers, :type => :request
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe Clearance::BackDoor do
4
+ it 'signs in as a given user' do
5
+ user_id = '123'
6
+ user = stub('user')
7
+ User.stubs(:find).with(user_id).returns(user)
8
+ env = env_for_user_id(user_id)
9
+ back_door = Clearance::BackDoor.new(mock_app)
10
+
11
+ result = back_door.call(env)
12
+
13
+ env[:clearance].should have_received(:sign_in).with(user)
14
+ result.should eq mock_app.call(env)
15
+ end
16
+
17
+ it 'delegates directly without a user' do
18
+ env = env_without_user_id
19
+ back_door = Clearance::BackDoor.new(mock_app)
20
+
21
+ result = back_door.call(env)
22
+
23
+ env[:clearance].should have_received(:sign_in).never
24
+ result.should eq mock_app.call(env)
25
+ end
26
+
27
+ def env_without_user_id
28
+ env_for_user_id('')
29
+ end
30
+
31
+ def env_for_user_id(user_id)
32
+ clearance = stub('clearance', sign_in: true)
33
+ Rack::MockRequest.env_for("/?as=#{user_id}").merge(clearance: clearance)
34
+ end
35
+
36
+ def mock_app
37
+ lambda { |env| [200, {}, ['okay']] }
38
+ end
39
+ end
@@ -1,7 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class DeniesController < ActionController::Base
4
- include Clearance::Authentication
4
+ include Clearance::Controller
5
+
5
6
  before_filter :authorize, :only => :show
6
7
 
7
8
  def new
@@ -23,7 +24,7 @@ describe DeniesController do
23
24
  before do
24
25
  Rails.application.routes.draw do
25
26
  resource :deny, :only => [:new, :show]
26
- match 'sign_in' => 'clearance/sessions#new', :as => 'sign_in'
27
+ get '/sign_in' => 'clearance/sessions#new', :as => 'sign_in'
27
28
  end
28
29
  end
29
30
 
@@ -1,7 +1,7 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class FlashesController < ActionController::Base
4
- include Clearance::Authentication
4
+ include Clearance::Controller
5
5
 
6
6
  def set_flash
7
7
  flash[:notice] = params[:message]
@@ -16,8 +16,8 @@ end
16
16
  describe FlashesController do
17
17
  before do
18
18
  Rails.application.routes.draw do
19
- match 'set_flash' => 'flashes#set_flash'
20
- match 'view_flash' => 'flashes#view_flash'
19
+ get '/set_flash' => 'flashes#set_flash'
20
+ get '/view_flash' => 'flashes#view_flash'
21
21
  end
22
22
  end
23
23
 
@@ -1,7 +1,8 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  class ForgeriesController < ActionController::Base
4
- include Clearance::Authentication
4
+ include Clearance::Controller
5
+
5
6
  protect_from_forgery
6
7
  before_filter :authorize
7
8
 
@@ -18,7 +19,7 @@ describe ForgeriesController do
18
19
  before do
19
20
  Rails.application.routes.draw do
20
21
  resources :forgeries
21
- match 'sign_in' => 'clearance/sessions#new', :as => 'sign_in'
22
+ get '/sign_in' => 'clearance/sessions#new', :as => 'sign_in'
22
23
  end
23
24
 
24
25
  @user = create(:user)
@@ -35,6 +35,20 @@ describe Clearance::PasswordsController do
35
35
  it { should respond_with(:success) }
36
36
  end
37
37
 
38
+ describe 'with correct email address capitalized differently' do
39
+ before do
40
+ ActionMailer::Base.deliveries.clear
41
+ post :create, :password => { :email => @user.email.upcase }
42
+ end
43
+
44
+ it 'should generate a token for the change your password email' do
45
+ @user.reload.confirmation_token.should_not be_nil
46
+ end
47
+
48
+ it { should have_sent_email.with_subject(/change your password/i) }
49
+ it { should respond_with(:success) }
50
+ end
51
+
38
52
  describe 'with incorrect email address' do
39
53
  before do
40
54
  email = 'user1@example.com'
@@ -21,7 +21,15 @@ describe ClearanceMailer do
21
21
  @email.body.to_s.should =~ regexp
22
22
  end
23
23
 
24
- it 'should set its subject' do
24
+ it 'sets its subject' do
25
25
  @email.subject.should =~ /Change your password/
26
26
  end
27
+
28
+ it 'contains opening text in the body' do
29
+ @email.body.should =~ /a link to change your password/
30
+ end
31
+
32
+ it 'contains closing text in the body' do
33
+ @email.body.should =~ /Your password has not been changed/
34
+ end
27
35
  end
@@ -2,12 +2,9 @@ require 'spec_helper'
2
2
 
3
3
  describe Clearance::PasswordStrategies::BCryptMigrationFromSHA1 do
4
4
  subject do
5
- Class.new do
6
- attr_reader :password
7
- attr_accessor :encrypted_password
8
- attr_accessor :salt
9
- include Clearance::PasswordStrategies::BCryptMigrationFromSHA1
10
- end.new
5
+ fake_model_with_password_strategy(
6
+ Clearance::PasswordStrategies::BCryptMigrationFromSHA1
7
+ )
11
8
  end
12
9
 
13
10
  describe '#password=' do
@@ -27,13 +24,12 @@ describe Clearance::PasswordStrategies::BCryptMigrationFromSHA1 do
27
24
  end
28
25
 
29
26
  it 'encrypts with BCrypt' do
30
- BCrypt::Password.should have_received(:create).with(password)
27
+ BCrypt::Password.should have_received(:create).with(password, anything)
31
28
  end
32
29
 
33
30
  it 'sets the pasword on the subject' do
34
31
  subject.password.should be_present
35
32
  end
36
-
37
33
  end
38
34
 
39
35
  describe '#authenticated?' do
@@ -45,6 +41,7 @@ describe Clearance::PasswordStrategies::BCryptMigrationFromSHA1 do
45
41
  before do
46
42
  subject.salt = salt
47
43
  subject.encrypted_password = sha1_hash
44
+ subject.stubs :save => true
48
45
  end
49
46
 
50
47
  it 'is authenticated' do
@@ -61,6 +58,11 @@ describe Clearance::PasswordStrategies::BCryptMigrationFromSHA1 do
61
58
  subject.authenticated? 'bad' + password
62
59
  }.should_not raise_error(BCrypt::Errors::InvalidHash)
63
60
  end
61
+
62
+ it 'saves the subject to database' do
63
+ subject.authenticated? password
64
+ subject.should have_received(:save)
65
+ end
64
66
  end
65
67
 
66
68
  context 'with a BCrypt-encrypted password' do
@@ -80,5 +82,4 @@ describe Clearance::PasswordStrategies::BCryptMigrationFromSHA1 do
80
82
  end
81
83
  end
82
84
  end
83
-
84
85
  end
@@ -2,10 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Clearance::PasswordStrategies::BCrypt do
4
4
  subject do
5
- Class.new do
6
- attr_accessor :encrypted_password
7
- include Clearance::PasswordStrategies::BCrypt
8
- end.new
5
+ fake_model_with_password_strategy(Clearance::PasswordStrategies::BCrypt)
9
6
  end
10
7
 
11
8
  describe '#password=' do
@@ -14,15 +11,32 @@ describe Clearance::PasswordStrategies::BCrypt do
14
11
 
15
12
  before do
16
13
  BCrypt::Password.stubs :create => encrypted_password
17
- subject.password = password
18
14
  end
19
15
 
20
16
  it 'encrypts the password into encrypted_password' do
17
+ subject.password = password
18
+
21
19
  subject.encrypted_password.should == encrypted_password
22
20
  end
23
21
 
24
- it 'encrypts with BCrypt' do
25
- BCrypt::Password.should have_received(:create).with(password)
22
+ it 'encrypts with BCrypt using default cost in non test environments' do
23
+ Rails.stubs :env => ActiveSupport::StringInquirer.new("production")
24
+
25
+ subject.password = password
26
+
27
+ BCrypt::Password.should have_received(:create).with(
28
+ password,
29
+ :cost => ::BCrypt::Engine::DEFAULT_COST
30
+ )
31
+ end
32
+
33
+ it 'encrypts with BCrypt using minimum cost in test environment' do
34
+ subject.password = password
35
+
36
+ BCrypt::Password.should have_received(:create).with(
37
+ password,
38
+ :cost => ::BCrypt::Engine::MIN_COST
39
+ )
26
40
  end
27
41
  end
28
42
 
@@ -2,12 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Clearance::PasswordStrategies::Blowfish do
4
4
  subject do
5
- Class.new do
6
- attr_accessor :salt, :encrypted_password
7
- include Clearance::PasswordStrategies::Blowfish
8
-
9
- def generate_random_code; 'code'; end
10
- end.new
5
+ fake_model_with_password_strategy(Clearance::PasswordStrategies::Blowfish)
11
6
  end
12
7
 
13
8
  describe '#password=' do
@@ -2,10 +2,16 @@ require 'spec_helper'
2
2
 
3
3
  describe Clearance::User do
4
4
  subject do
5
+ class UniquenessValidator < ActiveModel::Validator
6
+ def validate(record)
7
+ end
8
+ end
9
+
5
10
  Class.new do
6
- def self.validates_presence_of(*args); end
7
- def self.validates_uniqueness_of(*args); end
8
- def self.validates_format_of(*args); end
11
+ include ActiveModel::Validations
12
+
13
+ validates_with UniquenessValidator
14
+
9
15
  def self.before_validation(*args); end
10
16
  def self.before_create(*args); end
11
17
 
@@ -2,12 +2,7 @@ require 'spec_helper'
2
2
 
3
3
  describe Clearance::PasswordStrategies::SHA1 do
4
4
  subject do
5
- Class.new do
6
- attr_accessor :salt, :encrypted_password
7
- include Clearance::PasswordStrategies::SHA1
8
-
9
- def generate_random_code; "code"; end
10
- end.new
5
+ fake_model_with_password_strategy(Clearance::PasswordStrategies::SHA1)
11
6
  end
12
7
 
13
8
  describe '#password=' do
@@ -15,8 +15,8 @@ describe User do
15
15
  it { should_not allow_value('foo').for(:email) }
16
16
  it { should_not allow_value('example.com').for(:email) }
17
17
 
18
- it 'stores email in down case' do
19
- user = create(:user, :email => 'John.Doe@example.com')
18
+ it 'stores email in down case and removes whitespace' do
19
+ user = create(:user, :email => 'Jo hn.Do e @exa mp le.c om')
20
20
  user.email.should == 'john.doe@example.com'
21
21
  end
22
22
  end
@@ -33,27 +33,28 @@ describe User do
33
33
  end
34
34
 
35
35
  it 'is authenticated with correct email and password' do
36
- (Clearance.configuration.user_model.authenticate(@user.email, @password)).
37
- should be
36
+ User.authenticate(@user.email, @password).should eq(@user)
38
37
  @user.should be_authenticated(@password)
39
38
  end
40
39
 
41
40
  it 'is authenticated with correct uppercased email and correct password' do
42
- (Clearance.configuration.user_model.authenticate(@user.email.upcase, @password)).
43
- should be
41
+ User.authenticate(@user.email.upcase, @password).should eq(@user)
44
42
  @user.should be_authenticated(@password)
45
43
  end
46
44
 
47
45
  it 'is authenticated with incorrect credentials' do
48
- (Clearance.configuration.user_model.authenticate(@user.email, 'bad_password')).
49
- should_not be
46
+ User.authenticate(@user.email, 'bad_password').should be_nil
50
47
  @user.should_not be_authenticated('bad password')
51
48
  end
49
+
50
+ it 'is retrieved via a case-insensitive search' do
51
+ User.find_by_normalized_email(@user.email.upcase).should eq(@user)
52
+ end
52
53
  end
53
54
 
54
55
  describe 'when resetting authentication with reset_remember_token!' do
55
56
  before do
56
- @user = create(:user)
57
+ @user = create(:user)
57
58
  @user.remember_token = 'old-token'
58
59
  @user.reset_remember_token!
59
60
  end
@@ -139,6 +140,7 @@ describe User do
139
140
  describe 'a user with an optional email' do
140
141
  before do
141
142
  @user = User.new
143
+
142
144
  class << @user
143
145
  def email_optional?
144
146
  true
@@ -175,6 +177,14 @@ describe User do
175
177
  end
176
178
  end
177
179
 
180
+ describe 'email address normalization' do
181
+ let(:email) { 'Jo hn.Do e @exa mp le.c om' }
182
+
183
+ it 'downcases the address and strips spaces' do
184
+ User.normalize_email(email).should eq 'john.doe@example.com'
185
+ end
186
+ end
187
+
178
188
  describe 'the password setter on a User' do
179
189
  let(:password) { 'a-password' }
180
190
  before { subject.send(:password=, password) }