google-authenticator-rails 1.4.0 → 1.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.
|