google-authenticator-rails 1.4.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1b0e27a878d00efaf2b62b132ee490bed3b51135
4
- data.tar.gz: 20d199184ac7af414fa73af13f2ca25f09df9818
3
+ metadata.gz: 258692fbcd739848ad81f173e797cb59ba5d781e
4
+ data.tar.gz: f55e0f035fc69bbb9e9cb8b32b07dfe76a4c0314
5
5
  SHA512:
6
- metadata.gz: 17402d2412a5d8eef21fc856f3db1c50df5258e141144b2680842836429e5ef69d286246519296d6a0112de3150c30e2203e1b6b543635632dcca4f756dc7f94
7
- data.tar.gz: 139193758c98d494c3b143edf136190b0d4f90ab6800af6d077464c8e2d0ed464b205d1c2089803e5296ea396c0045038e49fa686fbc74c3dc00329e623893df
6
+ metadata.gz: 4e8b07d17935c0517c7b6cd2b988cc680299952950891a36758803b751f28d5602391f0c480cb3de7ff6b8d2cf5668a412bff25666bfec7c9e71c63ef11821b8
7
+ data.tar.gz: 3698e4f4ba7ab053dd21d85a0c318625e1bd47921e8c6e3d679babcea0c6045114758e7db80f3c9a97ff55b95a26ae5f983d6b22b315bbe78ab3f6397caeae4c
data/Appraisals CHANGED
@@ -33,3 +33,7 @@ end
33
33
  appraise "rails5.0" do
34
34
  gem "activerecord", "~> 5.0.0"
35
35
  end
36
+
37
+ appraise "rails5.1" do
38
+ gem "activerecord", "~> 5.1.0"
39
+ end
data/README.md CHANGED
@@ -31,8 +31,11 @@ end
31
31
 
32
32
  @user = User.new
33
33
  @user.set_google_secret # => true
34
+ @user.google_secret_value # => 16-character plain-text secret, whatever the name of the secret column
34
35
  @user.google_qr_uri # => http://path.to.google/qr?with=params
35
36
  @user.google_authentic?(123456) # => true
37
+ @user.clear_google_secret! # => true
38
+ @user.google_secret_value # => nil
36
39
  ```
37
40
 
38
41
  ## Google Labels
@@ -327,13 +330,12 @@ If you want to manually destroy the MFA cookie (for example, when a user logs ou
327
330
  UserMfaSession::destroy
328
331
  ```
329
332
 
330
- ## Storing Secrets in Encrypted Form
333
+ ## Storing Secrets in Encrypted Form (Rails 4.1 and above)
331
334
 
332
335
  Normally, if an attacker gets access to the application database, they will be able to generate correct authentication codes,
333
336
  elmininating the security gains from two-factor authentication. If the application's ```secret_key_base``` is handled more securely
334
337
  than the database (by, for example, never putting it on the server filesystem), protection against database compromise can
335
- be gained by setting the ```:encrypt_secrets``` option to ```true```. Newly-created secrets will then be stored in encrypted
336
- form.
338
+ be gained by setting the ```:encrypt_secrets``` option to ```true```. Newly-created secrets will then be stored in encrypted form.
337
339
 
338
340
  Existing non-encrypted secrets for all models for which the ```:encrypt_secrets``` option has been set to ```true```
339
341
  can be encrypted by running
@@ -353,6 +355,10 @@ Then run
353
355
  ```
354
356
  to change all encrypted google secret fields to use the new key.
355
357
 
358
+ If the app is not running under Rails version 4.1 or above, encryption will be disabled, and a warning issued if ```:encrypt_secrets```
359
+ is enabled on a model.
360
+
361
+ If encryption is enabled for a model, the Google secret column of its table must be able to hold at least 138 characters, rather than just 16.
356
362
 
357
363
  ## Contributing
358
364
 
@@ -0,0 +1,3 @@
1
+ test:
2
+ secret_key_base: ea13d27b89aaa5004212cc78552e2a1f0ceaa21f797de0089277506a036b1d450f8508eb51b838c04af477c6d03d7c8b0f3258937dc291935eb49c2a8f3fa9d9
3
+ old_secret_key_base: c9e4baab86ab1d7373eafd7fbc467b227d44e23608834ea5bdfc502bd33211d205f245a06ced789af579e8d77aa4c1bfd605065190ee24d3709ada92366cadfb
@@ -1,6 +1,5 @@
1
1
  # Stuff the gem requires
2
2
  #
3
- require 'rails'
4
3
  require 'active_support'
5
4
  require 'active_record'
6
5
  require 'openssl'
@@ -24,11 +23,15 @@ GOOGLE_AUTHENTICATOR_RAILS_PATH = File.dirname(__FILE__) + "/google-authenticato
24
23
  # Sets up some basic accessors for use with the ROTP module
25
24
  #
26
25
  module GoogleAuthenticatorRails
26
+ def self.encryption_supported?
27
+ defined?(Rails) && (Rails::VERSION::MAJOR > 4 || Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR > 0)
28
+ end
29
+
27
30
  class Railtie < Rails::Railtie
28
31
  rake_tasks do
29
32
  load 'tasks/google_authenticator.rake'
30
33
  end
31
- end
34
+ end if encryption_supported? && !Rails.env.test? # Without this last condition tasks under test are run twice
32
35
 
33
36
  # Drift is set to 6 because ROTP drift is not inclusive. This allows a drift of 5 seconds.
34
37
  DRIFT = 6
@@ -41,7 +44,7 @@ module GoogleAuthenticatorRails
41
44
 
42
45
  # Additional configuration passed to a Session::Persistence cookie.
43
46
  @@cookie_options = { :httponly => true }
44
-
47
+
45
48
  def self.generate_password(secret, iteration)
46
49
  ROTP::HOTP.new(secret).at(iteration)
47
50
  end
@@ -97,6 +97,16 @@ module GoogleAuthenticatorRails # :nodoc:
97
97
  @google_issuer = options[:issuer]
98
98
  @google_qr_size = options[:qr_size] || '200x200'
99
99
  @google_secrets_encrypted = !!options[:encrypt_secrets]
100
+
101
+ if @google_secrets_encrypted && !GoogleAuthenticatorRails.encryption_supported?
102
+ msg = "Google secret encryption is only supported on Ruby on Rails 4.1 and above. Encryption has been disabled for #{name}."
103
+ if defined?(Rails) && !Rails.env.test?
104
+ Rails.logger.warn msg
105
+ else
106
+ puts msg
107
+ end
108
+ @google_secrets_encrypted = false
109
+ end
100
110
 
101
111
  puts ":skip_attr_accessible is no longer required. Called from #{Kernel.caller[0]}}" if options.has_key?(:skip_attr_accessible)
102
112
 
@@ -1,14 +1,19 @@
1
1
  module GoogleAuthenticatorRails # :nodoc:
2
2
  mattr_accessor :secret_encryptor
3
+
3
4
  module ActiveRecord # :nodoc:
4
5
  module Helpers
6
+
7
+ # Returns and memoizes the plain text google secret for this instance, irrespective of the
8
+ # name of the google secret storage column and whether secret encryption is enabled for this model.
9
+ #
5
10
  def google_secret_value
6
11
  if @google_secret_value_cached
7
12
  @google_secret_value
8
13
  else
9
14
  @google_secret_value_cached = true
10
15
  secret_in_db = google_secret_column_value
11
- @google_secret_value = secret_in_db && self.class.google_secrets_encrypted ? google_secret_encryptor.decrypt_and_verify(secret_in_db) : secret_in_db
16
+ @google_secret_value = secret_in_db.present? && self.class.google_secrets_encrypted ? google_secret_encryptor.decrypt_and_verify(secret_in_db) : secret_in_db
12
17
  end
13
18
  end
14
19
 
@@ -16,6 +21,12 @@ module GoogleAuthenticatorRails # :nodoc:
16
21
  change_google_secret_to!(GoogleAuthenticatorRails::generate_secret)
17
22
  end
18
23
 
24
+ # Sets and saves a nil google secret value for this instance.
25
+ #
26
+ def clear_google_secret!
27
+ change_google_secret_to!(nil)
28
+ end
29
+
19
30
  def google_authentic?(code)
20
31
  GoogleAuthenticatorRails.valid?(code, google_secret_value, self.class.google_drift)
21
32
  end
@@ -55,7 +66,7 @@ module GoogleAuthenticatorRails # :nodoc:
55
66
 
56
67
  def change_google_secret_to!(secret, encrypt = self.class.google_secrets_encrypted)
57
68
  @google_secret_value = secret
58
- self.__send__("#{self.class.google_secret_column}=", secret && encrypt ? google_secret_encryptor.encrypt_and_sign(secret) : secret)
69
+ self.__send__("#{self.class.google_secret_column}=", secret.present? && encrypt ? google_secret_encryptor.encrypt_and_sign(secret) : secret)
59
70
  @google_secret_value_cached = true
60
71
  save!
61
72
  end
@@ -1,7 +1,7 @@
1
1
  module Google
2
2
  module Authenticator
3
3
  module Rails
4
- VERSION = "1.4.0"
4
+ VERSION = "1.4.1"
5
5
  end
6
6
  end
7
7
  end
@@ -1,27 +1,36 @@
1
1
  namespace :google_authenticator do
2
2
 
3
- def do_encrypt(already_encrypted, op_name)
3
+ def do_encrypt(args, already_encrypted, op_name)
4
+ model_names = if args[:optional_model_list]
5
+ args.extras.unshift(args[:optional_model_list])
6
+ else
7
+ # Adapted from https://stackoverflow.com/a/8248849/7478194
8
+ Dir[Rails.root.join('app/models/*.rb').to_s].map { |filename| File.basename(filename, '.rb').camelize }
9
+ end
10
+
4
11
  ActiveRecord::Base.transaction do
5
- match_op = " #{already_encrypted ? '>' : '='} 16"
6
- # Adapted from https://stackoverflow.com/a/8248849/7478194
7
- Dir[Rails.root.join('app/models/*.rb').to_s].each do |filename|
8
- klass = File.basename(filename, '.rb').camelize.constantize
12
+ match_op = " = #{already_encrypted ? 138 : 16}"
13
+ model_names.each do |model_name|
14
+ klass = model_name.constantize
9
15
  next unless klass.ancestors.include?(ActiveRecord::Base) && klass.try(:google_secrets_encrypted)
10
- puts "#{op_name} model #{klass.name.inspect} (table #{klass.table_name.inspect})"
16
+ print "#{op_name}ing model #{klass.name.inspect} (table #{klass.table_name.inspect}): "
17
+ count = 0
11
18
  klass.where("LENGTH(#{klass.google_secret_column})#{match_op}").find_each do |record|
12
19
  yield record
20
+ count += 1
13
21
  end
22
+ puts "#{count} #{'secret'.pluralize(count)} #{op_name}ed"
14
23
  end
15
24
  end
16
25
  end
17
26
 
18
- desc 'Encrypt all secret columns (add the :encrypt_secret options *before* running)'
19
- task encrypt_secrets: :environment do
20
- do_encrypt(false, 'Encrypting') { |record| record.encrypt_google_secret! }
27
+ desc 'Encrypt all secret columns (add the :encrypt_secrets options *before* running)'
28
+ task :encrypt_secrets, [:optional_model_list] => :environment do |_t, args|
29
+ do_encrypt(args, false, 'Encrypt') { |record| record.encrypt_google_secret! }
21
30
  end
22
31
 
23
32
  desc 'Re-encrypt all secret columns from old_secret_key_base to secret_key_base'
24
- task reencrypt_secrets: :environment do
33
+ task :reencrypt_secrets, [:optional_model_list] => :environment do |_t, args|
25
34
  if Rails.application.secrets.old_secret_key_base.blank?
26
35
  puts 'old_secret_key_base is not set in config/secrets.yml'
27
36
  else
@@ -29,7 +38,7 @@ namespace :google_authenticator do
29
38
  Rails.application.secrets[:secret_key_base] = Rails.application.secrets.old_secret_key_base
30
39
  Rails.application.instance_eval { @caching_key_generator = nil }
31
40
  old_secret_encryptor = GoogleAuthenticatorRails::ActiveRecord::Helpers.get_google_secret_encryptor
32
- do_encrypt(true, 'Re-encrypting') do |record|
41
+ do_encrypt(args, true, 'Re-encrypt') do |record|
33
42
  GoogleAuthenticatorRails.secret_encryptor = old_secret_encryptor
34
43
  plain_secret = record.google_secret_value
35
44
  GoogleAuthenticatorRails.secret_encryptor = secret_encryptor
@@ -38,9 +47,9 @@ namespace :google_authenticator do
38
47
  end
39
48
  end
40
49
 
41
- desc 'Decrypt all secret columns (remove the :encrypt_secret options *after* running)'
42
- task decrypt_secrets: :environment do
43
- do_encrypt(true, 'Decrypting') { |record| record.send(:change_google_secret_to!, record.google_secret_value, false) }
50
+ desc 'Decrypt all secret columns (remove the :encrypt_secrets options *after* running)'
51
+ task :decrypt_secrets, [:optional_model_list] => :environment do |_t, args|
52
+ do_encrypt(args, true, 'Decrypt') { |record| record.send(:change_google_secret_to!, record.google_secret_value, false) }
44
53
  end
45
54
 
46
55
  end
@@ -1,11 +1,6 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe GoogleAuthenticatorRails do
4
- let(:random32) { "5qlcip7azyjuwm36" }
5
- before do
6
- ROTP::Base32.stub!(:random_base32).and_return(random32)
7
- end
8
-
9
4
  describe '#generate_password' do
10
5
  subject { GoogleAuthenticatorRails::generate_password("test", counter) }
11
6
 
@@ -21,144 +16,258 @@ describe GoogleAuthenticatorRails do
21
16
  end
22
17
 
23
18
  context 'time-based passwords' do
24
- let!(:time) { Time.parse("2012-08-07 11:11:11 AM +0700") }
25
- let(:secret) { "test" }
26
- let(:code) { 922511 }
27
- before { Time.stub!(:now).and_return(time) }
28
-
29
- specify { GoogleAuthenticatorRails::time_based_password(secret).should == code }
30
- specify { GoogleAuthenticatorRails::valid?(code, secret).should be true }
31
-
32
- specify { GoogleAuthenticatorRails::valid?(code * 2, secret).should be false }
33
- specify { GoogleAuthenticatorRails::valid?(code, secret * 2).should be false }
34
- end
35
-
36
- it 'can create a secret' do
37
- GoogleAuthenticatorRails::generate_secret.should == random32
38
- end
19
+ let(:secret) { '5qlcip7azyjuwm36' }
20
+ let(:original_time) { Time.parse("2012-08-07 11:11:00 AM +0700") }
21
+ let!(:time) { original_time }
22
+ let(:code) { 495502 }
39
23
 
40
- context 'integration with ActiveRecord' do
41
- let(:original_time) { Time.parse("2012-08-07 11:11:00 AM +0700") }
42
- let!(:time) { original_time }
43
- let(:user) { User.create(:email => "test@example.com", :user_name => "test_user") }
44
24
  before do
45
25
  Time.stub!(:now).and_return(time)
46
- user.google_secret = "test"
26
+ ROTP::Base32.stub!(:random_base32).and_return(secret)
47
27
  end
48
28
 
49
- context "custom drift" do
50
- # 30 seconds drift
51
- let(:user) { DriftUser.create(:email => "test@example.com", :user_name => "test_user") }
52
- subject { user.google_authentic?(922511) }
53
-
54
- context '6 seconds of drift' do
55
- let(:time) { original_time + 36.seconds }
56
- it { should be true }
57
- end
58
-
59
- context '30 seconds of drift' do
60
- let(:time) { original_time + 61.seconds }
61
- it { should be false }
62
- end
63
- end
64
-
65
- context 'code validation' do
66
- subject { user.google_authentic?(922511) }
67
-
68
- it { should be true }
29
+ specify { GoogleAuthenticatorRails::time_based_password(secret).should == code }
30
+ specify { GoogleAuthenticatorRails::valid?(code, secret).should be true }
69
31
 
70
- context 'within 5 seconds of drift' do
71
- let(:time) { original_time + 34.seconds }
72
- it { should be true }
73
- end
32
+ specify { GoogleAuthenticatorRails::valid?(code * 2, secret).should be false }
33
+ specify { GoogleAuthenticatorRails::valid?(code, secret * 2).should be false }
74
34
 
75
- context '6 seconds of drift' do
76
- let(:time) { original_time + 36.seconds }
77
- it { should be false }
78
- end
35
+ it 'can create a secret' do
36
+ GoogleAuthenticatorRails::generate_secret.should == secret
79
37
  end
80
38
 
81
- it 'creates a secret' do
82
- user.set_google_secret
83
- user.google_secret.should == random32
84
- end
39
+ context 'integration with ActiveRecord' do
40
+ let(:user) { UserFactory.create User }
85
41
 
86
- context 'secret column' do
87
42
  before do
88
- GoogleAuthenticatorRails.stub!(:generate_secret).and_return("test")
89
- @user = CustomUser.create(:email => "test@example.com", :user_name => "test_user")
90
- @user.set_google_secret
43
+ @user = user
44
+ user.google_secret = secret
91
45
  end
92
-
93
- it 'validates code' do
94
- @user.google_authentic?(922511).should be_true
46
+
47
+ context "custom drift" do
48
+ # 30 seconds drift
49
+ let(:user) { UserFactory.create DriftUser }
50
+ subject { user.google_authentic?(code) }
51
+
52
+ context '6 seconds of drift' do
53
+ let(:time) { original_time + 36.seconds }
54
+ it { should be true }
55
+ end
56
+
57
+ context '30 seconds of drift' do
58
+ let(:time) { original_time + 61.seconds }
59
+ it { should be false }
60
+ end
95
61
  end
96
-
97
- it 'generates a url for a qr code' do
98
- @user.google_qr_uri.should == "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3Dtest&chs=200x200"
62
+
63
+ context 'code validation' do
64
+ subject { user.google_authentic?(code) }
65
+
66
+ it { should be true }
67
+
68
+ context 'within 5 seconds of drift' do
69
+ let(:time) { original_time + 34.seconds }
70
+ it { should be true }
71
+ end
72
+
73
+ context '6 seconds of drift' do
74
+ let(:time) { original_time + 36.seconds }
75
+ it { should be false }
76
+ end
99
77
  end
100
- end
101
-
102
- context 'google label' do
103
- let(:user) { NilMethodUser.create(:email => "test@example.com", :user_name => "test_user") }
104
- subject { lambda { user.google_label } }
105
- it { should raise_error(NoMethodError) }
106
- end
107
-
108
- context "drift value" do
109
- it { DriftUser.google_drift.should == 31 }
110
-
111
- context "default value" do
112
- it { User.google_drift.should == 6 }
78
+
79
+ it 'creates a secret' do
80
+ user.set_google_secret
81
+ user.google_secret.should == secret
113
82
  end
114
- end
115
-
116
- context 'qr codes' do
117
- let(:options) { { :email => "test@example.com", :user_name => "test_user" } }
118
- let(:user) { User.create options }
119
- before { user.set_google_secret }
120
- subject { user.google_qr_uri }
121
-
122
- it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
123
-
124
- context 'custom column name' do
125
- let(:user) { ColumnNameUser.create options }
126
- it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest_user%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
83
+
84
+ shared_examples 'handles nil secrets' do
85
+ it 'clears a secret' do
86
+ @user.clear_google_secret!
87
+ @user.google_secret_value.should(be_nil) && @user.reload.google_secret_value.should(be_nil)
88
+ end
127
89
  end
128
90
 
129
- context 'custom proc' do
130
- let(:user) { ProcUser.create options }
131
- it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest_user%40futureadvisor-admin%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
91
+ it_behaves_like 'handles nil secrets'
92
+
93
+ context 'encrypted column' do
94
+ before do
95
+ @user = UserFactory.create EncryptedUser
96
+ @user.set_google_secret
97
+ end
98
+
99
+ it 'encrypts_the_secret' do
100
+ @user.google_secret.length.should == (GoogleAuthenticatorRails.encryption_supported? ? 138 : 16)
101
+ end
102
+
103
+ it 'decrypts_the_secret' do
104
+ @user.google_secret_value.should == secret
105
+ end
106
+
107
+ it 'validates code' do
108
+ @user.google_authentic?(code).should be_true
109
+ end
110
+
111
+ it_behaves_like 'handles nil secrets'
132
112
  end
133
-
134
- context 'method defined by symbol' do
135
- let(:user) { SymbolUser.create options }
136
- it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
113
+
114
+ context 'custom secret column' do
115
+ before do
116
+ @user = UserFactory.create CustomUser
117
+ @user.set_google_secret
118
+ end
119
+
120
+ it 'validates code' do
121
+ @user.google_authentic?(code).should be_true
122
+ end
123
+
124
+ it 'generates a url for a qr code' do
125
+ @user.google_qr_uri.should == "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D#{secret}&chs=200x200"
126
+ end
137
127
  end
138
-
139
- context 'method defined by string' do
140
- let(:user) { StringUser.create options }
141
- it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
128
+
129
+ context 'encrypted column with custom secret column' do
130
+ before do
131
+ @user = UserFactory.create EncryptedCustomUser
132
+ @user.set_google_secret
133
+ end
134
+
135
+ it 'encrypts the secret' do
136
+ @user.mfa_secret.length.should == (GoogleAuthenticatorRails.encryption_supported? ? 138 : 16)
137
+ end
138
+
139
+ it 'decrypts the secret' do
140
+ @user.google_secret_value.should == secret
141
+ end
142
+
143
+ it 'validates code' do
144
+ @user.google_authentic?(code).should be_true
145
+ end
142
146
  end
143
-
144
- context 'custom qr size' do
145
- let(:user) { QrCodeUser.create options }
146
- it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=300x300" }
147
+
148
+ if GoogleAuthenticatorRails.encryption_supported?
149
+ context 'encryption Rake tasks' do
150
+ before(:all) { Rails.application.load_tasks }
151
+
152
+ def set_and_run_task(type)
153
+ User.delete_all
154
+ EncryptedCustomUser.delete_all
155
+ @user = UserFactory.create User
156
+ @user.set_google_secret
157
+ @encrypted_user = UserFactory.create EncryptedCustomUser
158
+ @encrypted_user.set_google_secret
159
+ @non_encrypted_user = UserFactory.create EncryptedCustomUser
160
+ @non_encrypted_user.update_attribute(:mfa_secret, secret)
161
+ Rake.application.invoke_task("google_authenticator:#{type}_secrets[User,EncryptedCustomUser]")
162
+ end
163
+
164
+ def encryption_ok?(user, secret_should_be_encrypted)
165
+ secret_value = user.reload.send(:google_secret_column_value)
166
+ (secret_value.blank? || secret_value.length.should == (secret_should_be_encrypted ? 138 : 16)) &&
167
+ (user.class.google_secrets_encrypted ^ secret_should_be_encrypted || user.google_secret_value == secret)
168
+ end
169
+
170
+ shared_examples 'task tests' do |type|
171
+ it 'handles non-encrypted secrets' do
172
+ encryption_ok?(@non_encrypted_user, type == 'encrypt')
173
+ end
174
+
175
+ it 'handles encrypted secrets' do
176
+ encryption_ok?(@encrypted_user, type != 'decrypt')
177
+ end
178
+
179
+ it "doesn't #{type} non-encrypted models" do
180
+ encryption_ok?(@user, false)
181
+ end
182
+ end
183
+
184
+ context 'encrypt_secrets task' do
185
+ before(:all) { set_and_run_task('encrypt') }
186
+ it_behaves_like 'task tests', 'encrypt'
187
+ end
188
+
189
+ context 'decrypt_secrets task' do
190
+ before(:all) { set_and_run_task('decrypt') }
191
+ it_behaves_like 'task tests', 'decrypt'
192
+ end
193
+
194
+ context 'reencrypt_secrets task' do
195
+ before(:all) do
196
+ def reset_encryption(secret_key_base)
197
+ Rails.application.secrets[:secret_key_base] = secret_key_base
198
+ Rails.application.instance_eval { @caching_key_generator = nil }
199
+ GoogleAuthenticatorRails.secret_encryptor = nil
200
+ end
201
+
202
+ current_secret_key_base = Rails.application.secrets[:secret_key_base]
203
+ reset_encryption(Rails.application.secrets.old_secret_key_base)
204
+ set_and_run_task('reencrypt')
205
+ reset_encryption(current_secret_key_base)
206
+ end
207
+
208
+ it_behaves_like 'task tests', 'reencrypt'
209
+ end
210
+ end
147
211
  end
148
-
149
- context 'qr size passed to method' do
150
- subject { user.google_qr_uri('400x400') }
151
- let(:user) { StringUser.create options }
152
- it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=400x400" }
212
+
213
+ context 'google label' do
214
+ let(:user) { UserFactory.create NilMethodUser }
215
+ subject { lambda { user.google_label } }
216
+ it { should raise_error(NoMethodError) }
153
217
  end
154
-
155
- context 'qr size passed to method and size set on model' do
156
- let(:user) { QrCodeUser.create options }
157
- subject { user.google_qr_uri('400x400') }
158
- it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=400x400" }
218
+
219
+ context "drift value" do
220
+ it { DriftUser.google_drift.should == 31 }
221
+
222
+ context "default value" do
223
+ it { User.google_drift.should == 6 }
224
+ end
225
+ end
226
+
227
+ context 'qr codes' do
228
+ let(:user) { UserFactory.create User }
229
+ before { user.set_google_secret }
230
+ subject { user.google_qr_uri }
231
+
232
+ it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D#{secret}&chs=200x200" }
233
+
234
+ context 'custom column name' do
235
+ let(:user) { UserFactory.create ColumnNameUser }
236
+ it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest_user%3Fsecret%3D#{secret}&chs=200x200" }
237
+ end
238
+
239
+ context 'custom proc' do
240
+ let(:user) { UserFactory.create ProcUser }
241
+ it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest_user%40futureadvisor-admin%3Fsecret%3D#{secret}&chs=200x200" }
242
+ end
243
+
244
+ context 'method defined by symbol' do
245
+ let(:user) { UserFactory.create SymbolUser }
246
+ it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D#{secret}&chs=200x200" }
247
+ end
248
+
249
+ context 'method defined by string' do
250
+ let(:user) { UserFactory.create StringUser }
251
+ it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D#{secret}&chs=200x200" }
252
+ end
253
+
254
+ context 'custom qr size' do
255
+ let(:user) { UserFactory.create QrCodeUser }
256
+ it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D#{secret}&chs=300x300" }
257
+ end
258
+
259
+ context 'qr size passed to method' do
260
+ subject { user.google_qr_uri('400x400') }
261
+ let(:user) { UserFactory.create StringUser }
262
+ it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D#{secret}&chs=400x400" }
263
+ end
264
+
265
+ context 'qr size passed to method and size set on model' do
266
+ let(:user) { UserFactory.create QrCodeUser }
267
+ subject { user.google_qr_uri('400x400') }
268
+ it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D#{secret}&chs=400x400" }
269
+ end
159
270
  end
160
271
  end
161
-
162
272
  end
163
-
164
273
  end
data/spec/spec_helper.rb CHANGED
@@ -1,4 +1,7 @@
1
+ ENV['RAILS_ENV'] = 'test'
2
+
1
3
  require 'time'
4
+ require 'rails'
2
5
  require 'active_record'
3
6
  require 'action_controller'
4
7
  require 'rotp'
@@ -132,3 +135,21 @@ end
132
135
  class QrCodeUser < BaseUser
133
136
  acts_as_google_authenticated :qr_size => '300x300', :method => :email
134
137
  end
138
+
139
+ class EncryptedUser < BaseUser
140
+ acts_as_google_authenticated :encrypt_secrets => true
141
+ end
142
+
143
+ class EncryptedCustomUser < BaseUser
144
+ self.table_name = 'custom_users'
145
+ acts_as_google_authenticated :encrypt_secrets => true, :google_secret_column => :mfa_secret
146
+ end
147
+
148
+ class UserFactory
149
+ def self.create(klass)
150
+ klass.create(:email => 'test@example.com', :user_name => 'test_user')
151
+ end
152
+ end
153
+
154
+ class RailsApplication < Rails::Application
155
+ end if defined?(Rails)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: google-authenticator-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jared McFarland
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-10-31 00:00:00.000000000 Z
11
+ date: 2017-12-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rotp
@@ -153,6 +153,7 @@ files:
153
153
  - LICENSE
154
154
  - README.md
155
155
  - Rakefile
156
+ - config/secrets.yml
156
157
  - gemfiles/rails3.0.gemfile
157
158
  - gemfiles/rails3.1.gemfile
158
159
  - gemfiles/rails3.2.gemfile
@@ -198,7 +199,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
198
199
  version: '0'
199
200
  requirements: []
200
201
  rubyforge_project:
201
- rubygems_version: 2.6.14
202
+ rubygems_version: 2.5.1
202
203
  signing_key:
203
204
  specification_version: 4
204
205
  summary: Add the ability to use the Google Authenticator with ActiveRecord.