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 +4 -4
- data/Appraisals +4 -0
- data/README.md +9 -3
- data/config/secrets.yml +3 -0
- data/lib/google-authenticator-rails.rb +6 -3
- data/lib/google-authenticator-rails/active_record/acts_as_google_authenticated.rb +10 -0
- data/lib/google-authenticator-rails/active_record/helpers.rb +13 -2
- data/lib/google-authenticator-rails/version.rb +1 -1
- data/lib/tasks/google_authenticator.rake +23 -14
- data/spec/google_authenticator_spec.rb +229 -120
- data/spec/spec_helper.rb +21 -0
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 258692fbcd739848ad81f173e797cb59ba5d781e
|
4
|
+
data.tar.gz: f55e0f035fc69bbb9e9cb8b32b07dfe76a4c0314
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4e8b07d17935c0517c7b6cd2b988cc680299952950891a36758803b751f28d5602391f0c480cb3de7ff6b8d2cf5668a412bff25666bfec7c9e71c63ef11821b8
|
7
|
+
data.tar.gz: 3698e4f4ba7ab053dd21d85a0c318625e1bd47921e8c6e3d679babcea0c6045114758e7db80f3c9a97ff55b95a26ae5f983d6b22b315bbe78ab3f6397caeae4c
|
data/Appraisals
CHANGED
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
|
|
data/config/secrets.yml
ADDED
@@ -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,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 ?
|
6
|
-
|
7
|
-
|
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
|
-
|
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 :
|
19
|
-
task encrypt_secrets: :environment do
|
20
|
-
do_encrypt(false, '
|
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-
|
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 :
|
42
|
-
task decrypt_secrets: :environment do
|
43
|
-
do_encrypt(true, '
|
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
|
25
|
-
let(:
|
26
|
-
let(:
|
27
|
-
|
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
|
-
|
26
|
+
ROTP::Base32.stub!(:random_base32).and_return(secret)
|
47
27
|
end
|
48
28
|
|
49
|
-
|
50
|
-
|
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
|
-
|
71
|
-
|
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
|
-
|
76
|
-
|
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
|
-
|
82
|
-
user.
|
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
|
-
|
89
|
-
|
90
|
-
@user.set_google_secret
|
43
|
+
@user = user
|
44
|
+
user.google_secret = secret
|
91
45
|
end
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
98
|
-
|
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
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
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 '
|
135
|
-
|
136
|
-
|
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 '
|
140
|
-
|
141
|
-
|
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
|
-
|
145
|
-
|
146
|
-
|
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 '
|
150
|
-
|
151
|
-
|
152
|
-
it
|
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
|
156
|
-
|
157
|
-
|
158
|
-
|
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.
|
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-
|
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.
|
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.
|