google-authenticator-rails 0.0.2 → 0.0.3
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.
- data/CONTRIBUTE.rb +7 -0
- data/README.md +71 -5
- data/google-authenticator.gemspec +1 -1
- data/lib/google-authenticator-rails.rb +34 -3
- data/lib/google-authenticator-rails/action_controller.rb +1 -0
- data/lib/google-authenticator-rails/action_controller/rails_adapter.rb +36 -0
- data/lib/google-authenticator-rails/active_record.rb +1 -65
- data/lib/google-authenticator-rails/active_record/acts_as_google_authenticated.rb +88 -29
- data/lib/google-authenticator-rails/active_record/helpers.rb +51 -0
- data/lib/google-authenticator-rails/session.rb +10 -0
- data/lib/google-authenticator-rails/session/activation.rb +51 -0
- data/lib/google-authenticator-rails/session/base.rb +32 -0
- data/lib/google-authenticator-rails/session/persistence.rb +75 -0
- data/lib/google-authenticator-rails/version.rb +1 -1
- data/spec/action_controller/integration_spec.rb +29 -0
- data/spec/action_controller/rails_adapter_spec.rb +11 -0
- data/spec/google_authenticator_spec.rb +75 -86
- data/spec/session/activation_spec.rb +51 -0
- data/spec/session/persistance_spec.rb +58 -0
- data/spec/spec_helper.rb +83 -2
- metadata +22 -10
- data/lib/google-authenticator-rails/active_record/google_authentication.rb +0 -34
- data/lib/google-authenticator-rails/google.rb +0 -1
- data/lib/google-authenticator-rails/google/rails.rb +0 -1
- data/lib/google-authenticator-rails/google/rails/rotp_integration.rb +0 -26
data/CONTRIBUTE.rb
ADDED
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
|
1
|
+
# GoogleAuthenticatorRails
|
2
2
|
|
3
3
|
[](http://travis-ci.org/jaredonline/google-authenticator)
|
4
4
|
|
5
|
-
Rails (ActiveRecord) integration with the Google Authenticator apps for Android and the iPhone.
|
5
|
+
Rails (ActiveRecord) integration with the Google Authenticator apps for Android and the iPhone. Uses the Authlogic style for cookie management.
|
6
6
|
|
7
7
|
## Installation
|
8
8
|
|
@@ -28,9 +28,9 @@ acts_as_google_authenticated
|
|
28
28
|
end
|
29
29
|
|
30
30
|
@user = User.new
|
31
|
-
@user.set_google_secret
|
31
|
+
@user.set_google_secret # => true
|
32
32
|
@user.google_qr_uri # => http://path.to.google/qr?with=params
|
33
|
-
@user.
|
33
|
+
@user.google_authentic?(123456) # => true
|
34
34
|
```
|
35
35
|
|
36
36
|
Google Labels
|
@@ -86,10 +86,76 @@ class User
|
|
86
86
|
end
|
87
87
|
|
88
88
|
@user = User.new
|
89
|
-
@user.set_google_secret
|
89
|
+
@user.set_google_secret
|
90
90
|
@user.mfa_secret # => "56ahi483"
|
91
91
|
```
|
92
92
|
|
93
|
+
## Sample Rails Setup
|
94
|
+
|
95
|
+
This is a very rough outline of how GoogleAuthenticatorRails is meant to manage the sessions and cookies for a Rails app.
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
Gemfile
|
99
|
+
|
100
|
+
gem 'rails'
|
101
|
+
gem 'google-authenticator-rails'
|
102
|
+
```
|
103
|
+
|
104
|
+
```ruby
|
105
|
+
app/models/users.rb
|
106
|
+
|
107
|
+
class User < ActiveRecord::Base
|
108
|
+
acts_as_google_authenticated
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
If you want to authenticate based on a model called `User`, then you should name your session object `UserMfaSession`.
|
113
|
+
|
114
|
+
```ruby
|
115
|
+
app/models/user_mfa_session.rb
|
116
|
+
|
117
|
+
class UserMfaSession < GoogleAuthenticator::Session::Base
|
118
|
+
# no real code needed here
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
```ruby
|
123
|
+
app/controllers/user_mfa_session_controller.rb
|
124
|
+
|
125
|
+
class UserMfaSessionController < ApplicationController
|
126
|
+
|
127
|
+
def new
|
128
|
+
# load your view
|
129
|
+
end
|
130
|
+
|
131
|
+
def create
|
132
|
+
user = current_user # grab your currently logged in user
|
133
|
+
if user.google_authentic?(params[:mfa_code])
|
134
|
+
UserMfaSession.create(user)
|
135
|
+
redirect_to root_path
|
136
|
+
else
|
137
|
+
flash[:error] = "Wrong code"
|
138
|
+
render :new
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
```
|
144
|
+
|
145
|
+
```ruby
|
146
|
+
app/controllers/application_controller.rb
|
147
|
+
|
148
|
+
class ApplicationController < ActionController::Base
|
149
|
+
before_filter :check_mfa
|
150
|
+
|
151
|
+
private
|
152
|
+
def check_mfa
|
153
|
+
if !(user_mfa_session = UserMfaSession.find) && user_mfa_session.record == current_user
|
154
|
+
redirect_to new_user_mfa_session_path
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
```
|
93
159
|
|
94
160
|
## Contributing
|
95
161
|
|
@@ -15,10 +15,10 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = Google::Authenticator::Rails::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency "activesupport"
|
19
18
|
gem.add_dependency "rotp"
|
20
19
|
gem.add_dependency "activerecord"
|
21
20
|
gem.add_dependency "google-qr"
|
21
|
+
gem.add_dependency "actionpack"
|
22
22
|
|
23
23
|
gem.add_development_dependency "rspec", "~> 2.8.0"
|
24
24
|
gem.add_development_dependency "sqlite3"
|
@@ -7,7 +7,38 @@ require 'rotp'
|
|
7
7
|
require 'google-qr'
|
8
8
|
|
9
9
|
# Stuff the gem is
|
10
|
+
#
|
11
|
+
GOOGLE_AUTHENTICATOR_RAILS_PATH = File.dirname(__FILE__) + "/google-authenticator-rails/"
|
12
|
+
|
13
|
+
[
|
14
|
+
"version",
|
15
|
+
|
16
|
+
"action_controller",
|
17
|
+
"active_record",
|
18
|
+
"session"
|
19
|
+
].each do |library|
|
20
|
+
require GOOGLE_AUTHENTICATOR_RAILS_PATH + library
|
21
|
+
end
|
22
|
+
|
23
|
+
# Sets up some basic accessors for use with the ROTP module
|
10
24
|
#
|
11
|
-
|
12
|
-
|
13
|
-
|
25
|
+
module GoogleAuthenticatorRails
|
26
|
+
# Drift is set to 6 because ROTP drift is not inclusive. This allows a drift of 5 seconds.
|
27
|
+
DRIFT = 6
|
28
|
+
|
29
|
+
def self.generate_password(secret, iteration)
|
30
|
+
ROTP::HOTP.new(secret).at(iteration)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.time_based_password(secret)
|
34
|
+
ROTP::TOTP.new(secret).now
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.valid?(code, secret)
|
38
|
+
ROTP::TOTP.new(secret).verify_with_drift(code, DRIFT)
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.generate_secret
|
42
|
+
ROTP::Base32.random_base32
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'google-authenticator-rails/action_controller/rails_adapter'
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module GoogleAuthenticatorRails
|
2
|
+
module ActionController
|
3
|
+
class RailsAdapter
|
4
|
+
class LoadedTooLateError < StandardError
|
5
|
+
def initialize
|
6
|
+
super("GoogleAuthenticatorRails is trying to prepend a before_filter in ActionController::Base. Because you've already defined" +
|
7
|
+
" ApplicationController, your controllers will not get this before_filter. Please load GoogleAuthenticatorRails before defining" +
|
8
|
+
" ApplicationController.")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(controller)
|
13
|
+
@controller = controller
|
14
|
+
end
|
15
|
+
|
16
|
+
def cookies
|
17
|
+
@controller.send(:cookies)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Integration
|
22
|
+
def self.included(klass)
|
23
|
+
raise RailsAdapter::LoadedTooLateError.new if defined?(::ApplicationController)
|
24
|
+
|
25
|
+
klass.prepend_before_filter(:activate_google_authenticator_rails)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
def activate_google_authenticator_rails
|
30
|
+
GoogleAuthenticatorRails::Session::Base.controller = RailsAdapter.new(self)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
ActionController::Base.send(:include, GoogleAuthenticatorRails::ActionController::Integration)
|
@@ -1,66 +1,2 @@
|
|
1
|
-
require 'google-authenticator-rails/active_record/google_authentication'
|
2
1
|
require 'google-authenticator-rails/active_record/acts_as_google_authenticated'
|
3
|
-
|
4
|
-
class ActiveRecord::Base # :nodoc:
|
5
|
-
|
6
|
-
# This is the single integration point. Monkey patch ActiveRecord::Base
|
7
|
-
# to include the ActsAsGoogleAuthenticated module, which allows a user
|
8
|
-
# to call User.acts_as_google_authenticated.
|
9
|
-
#
|
10
|
-
# The model being used must have a string column named "google_secret", or an explicitly
|
11
|
-
# named column.
|
12
|
-
#
|
13
|
-
# Example:
|
14
|
-
#
|
15
|
-
# class User
|
16
|
-
# acts_as_google_authenticated
|
17
|
-
# end
|
18
|
-
#
|
19
|
-
# @user = user.new
|
20
|
-
# @user.set_google_secret! # => true
|
21
|
-
# @user.google_qr_uri # => http://path.to.google/qr?with=params
|
22
|
-
# @user.google_authenticate(123456) # => true
|
23
|
-
#
|
24
|
-
# Google Labels
|
25
|
-
# When setting up an account with the GoogleAuthenticator you need to provide
|
26
|
-
# a label for that account (to distinguish it from other accounts).
|
27
|
-
#
|
28
|
-
# GoogleAuthenticatorRails allows you to customize how the record will create
|
29
|
-
# that label. There are three options:
|
30
|
-
# - The default just uses the column "email" on the model
|
31
|
-
# - You can specify a custom column with the :column_name option
|
32
|
-
# - You can specify a custom method via a symbol or a proc
|
33
|
-
#
|
34
|
-
# Examples:
|
35
|
-
#
|
36
|
-
# class User
|
37
|
-
# acts_as_google_authenticated :column => :user_name
|
38
|
-
# end
|
39
|
-
#
|
40
|
-
# @user = User.new(:user_name => "ted")
|
41
|
-
# @user.google_label # => "ted"
|
42
|
-
#
|
43
|
-
# class User
|
44
|
-
# acts_as_google_authenticated :method => :user_name_with_label
|
45
|
-
#
|
46
|
-
# def user_name_with_label
|
47
|
-
# "#{user_name}@mysweetservice.com"
|
48
|
-
# end
|
49
|
-
# end
|
50
|
-
#
|
51
|
-
# @user = User.new(:user_name => "ted")
|
52
|
-
# @user.google_label # => "ted@mysweetservice.com"
|
53
|
-
#
|
54
|
-
# class User
|
55
|
-
# acts_as_google_authenticated :method => Proc.new { |user| user.user_name_with_label.upcase }
|
56
|
-
#
|
57
|
-
# def user_name_with_label
|
58
|
-
# "#{user_name}@mysweetservice.com"
|
59
|
-
# end
|
60
|
-
# end
|
61
|
-
#
|
62
|
-
# @user = User.new(:user_name => "ted")
|
63
|
-
# @user.google_label # => "TED@MYSWEETSERVICE.COM"
|
64
|
-
#
|
65
|
-
include ActiveRecord::ActsAsGoogleAuthenticated
|
66
|
-
end
|
2
|
+
require 'google-authenticator-rails/active_record/helpers'
|
@@ -1,37 +1,96 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
base
|
5
|
-
|
1
|
+
module GoogleAuthenticatorRails # :nodoc:
|
2
|
+
module ActiveRecord # :nodoc:
|
3
|
+
module ActsAsGoogleAuthenticated # :nodoc:
|
4
|
+
def self.included(base)
|
5
|
+
base.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
# This is the single integration point. Monkey patch ActiveRecord::Base
|
9
|
+
# to include the ActsAsGoogleAuthenticated module, which allows a user
|
10
|
+
# to call User.acts_as_google_authenticated.
|
11
|
+
#
|
12
|
+
# The model being used must have a string column named "google_secret", or an explicitly
|
13
|
+
# named column.
|
14
|
+
#
|
15
|
+
# Example:
|
16
|
+
#
|
17
|
+
# class User
|
18
|
+
# acts_as_google_authenticated
|
19
|
+
# end
|
20
|
+
#
|
21
|
+
# @user = user.new
|
22
|
+
# @user.set_google_secret # => true
|
23
|
+
# @user.google_qr_uri # => http://path.to.google/qr?with=params
|
24
|
+
# @user.google_authentic?(123456) # => true
|
25
|
+
#
|
26
|
+
# Google Labels
|
27
|
+
# When setting up an account with the GoogleAuthenticator you need to provide
|
28
|
+
# a label for that account (to distinguish it from other accounts).
|
29
|
+
#
|
30
|
+
# GoogleAuthenticatorRails allows you to customize how the record will create
|
31
|
+
# that label. There are three options:
|
32
|
+
# - The default just uses the column "email" on the model
|
33
|
+
# - You can specify a custom column with the :column_name option
|
34
|
+
# - You can specify a custom method via a symbol or a proc
|
35
|
+
#
|
36
|
+
# Examples:
|
37
|
+
#
|
38
|
+
# class User
|
39
|
+
# acts_as_google_authenticated :column => :user_name
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# @user = User.new(:user_name => "ted")
|
43
|
+
# @user.google_label # => "ted"
|
44
|
+
#
|
45
|
+
# class User
|
46
|
+
# acts_as_google_authenticated :method => :user_name_with_label
|
47
|
+
#
|
48
|
+
# def user_name_with_label
|
49
|
+
# "#{user_name}@example.com"
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# @user = User.new(:user_name => "ted")
|
54
|
+
# @user.google_label # => "ted@example.com"
|
55
|
+
#
|
56
|
+
# class User
|
57
|
+
# acts_as_google_authenticated :method => Proc.new { |user| user.user_name_with_label.upcase }
|
58
|
+
#
|
59
|
+
# def user_name_with_label
|
60
|
+
# "#{user_name}@example.com"
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# @user = User.new(:user_name => "ted")
|
65
|
+
# @user.google_label # => "TED@EXAMPLE.COM"
|
66
|
+
#
|
67
|
+
module ClassMethods # :nodoc
|
6
68
|
|
7
|
-
|
69
|
+
# Initializes the class attributes with the specified options and includes the
|
70
|
+
# respective ActiveRecord helper methods
|
71
|
+
#
|
72
|
+
# Options:
|
73
|
+
# [:column_name] the name of the column used to create the google_label
|
74
|
+
# [:method] name of the method to call to create the google_label
|
75
|
+
# it supercedes :column_name
|
76
|
+
# [:google_secret_column] the column the secret will be stored in, defaults
|
77
|
+
# to "google_secret"
|
78
|
+
def acts_as_google_authenticated(options = {})
|
79
|
+
@google_label_column = options[:column_name] || :email
|
80
|
+
@google_label_method = options[:method] || :default_google_label_method
|
81
|
+
@google_secret_column = options[:google_secret_column] || :google_secret
|
8
82
|
|
9
|
-
|
10
|
-
# GoogleAuthentication module
|
11
|
-
#
|
12
|
-
# Options:
|
13
|
-
# [:column_name] the name of the column used to create the google_label
|
14
|
-
# [:method] name of the method to call to created the google_label
|
15
|
-
# it supercedes :column_name
|
16
|
-
# [:google_secret_column] the column the secret will be stored in, defaults
|
17
|
-
# to "google_secret"
|
18
|
-
# [:skip_attr_accessible] defaults to false, if set to true will no call
|
19
|
-
# attr_accessible on the google_secret_column
|
20
|
-
def acts_as_google_authenticated(options = {})
|
21
|
-
@google_label_column = options[:column_name] || :email
|
22
|
-
@google_label_method = options[:method] || :default_google_label_method
|
23
|
-
@google_secret_column = options[:google_secret_column] || :google_secret
|
24
|
-
|
25
|
-
attr_accessible @google_secret_column unless options[:skip_attr_accessible] == true
|
83
|
+
puts ":skip_attr_accessible is no longer required. Called from #{Kernel.caller[0]}}" if options.has_key?(:skip_attr_accessible)
|
26
84
|
|
27
|
-
|
28
|
-
|
29
|
-
instance_variable_get("@#{cattr}")
|
85
|
+
[:google_label_column, :google_label_method, :google_secret_column].each do |cattr|
|
86
|
+
self.singleton_class.class_eval { attr_reader cattr }
|
30
87
|
end
|
31
|
-
end
|
32
88
|
|
33
|
-
|
89
|
+
include GoogleAuthenticatorRails::ActiveRecord::Helpers
|
90
|
+
end
|
34
91
|
end
|
35
92
|
end
|
36
93
|
end
|
37
|
-
end
|
94
|
+
end
|
95
|
+
|
96
|
+
ActiveRecord::Base.send(:include, GoogleAuthenticatorRails::ActiveRecord::ActsAsGoogleAuthenticated)
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module GoogleAuthenticatorRails # :nodoc:
|
2
|
+
module ActiveRecord # :nodoc:
|
3
|
+
module Helpers
|
4
|
+
def set_google_secret
|
5
|
+
self.__send__("#{self.class.google_secret_column}=", GoogleAuthenticatorRails::generate_secret)
|
6
|
+
save
|
7
|
+
end
|
8
|
+
|
9
|
+
# TODO: Remove this method in version 0.0.4
|
10
|
+
def set_google_secret!
|
11
|
+
put "DEPRECATION WARNING: #set_google_secret! is no longer being used, use #set_google_secret instead. #set_google_secret! will be removed in 0.0.4. Called from #{Kernel.caller[0]}"
|
12
|
+
set_google_secret
|
13
|
+
end
|
14
|
+
|
15
|
+
def google_authentic?(code)
|
16
|
+
GoogleAuthenticatorRails.valid?(code, google_secret_value)
|
17
|
+
end
|
18
|
+
|
19
|
+
# TODO: Remove this method in version 0.0.4
|
20
|
+
def google_authenticate(code)
|
21
|
+
put "DEPRECATION WARNING: #google_authenticate is no longer being used, use #google_authentic? instead. #google_authenticate will be removed in 0.0.4. Called from #{Kernel.caller[0]}"
|
22
|
+
google_authentic?(code)
|
23
|
+
end
|
24
|
+
|
25
|
+
def google_qr_uri
|
26
|
+
GoogleQR.new(:data => ROTP::TOTP.new(google_secret_value).provisioning_uri(google_label), :size => "200x200").to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def google_label
|
30
|
+
method = self.class.google_label_method
|
31
|
+
case method
|
32
|
+
when Proc
|
33
|
+
method.call(self)
|
34
|
+
when Symbol, String
|
35
|
+
self.__send__(method)
|
36
|
+
else
|
37
|
+
raise NoMethodError.new("the method used to generate the google_label was never defined")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def default_google_label_method
|
43
|
+
self.__send__(self.class.google_label_column)
|
44
|
+
end
|
45
|
+
|
46
|
+
def google_secret_value
|
47
|
+
self.__send__(self.class.google_secret_column)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module GoogleAuthenticatorRails
|
2
|
+
module Session
|
3
|
+
module Activation
|
4
|
+
class ControllerMissingError < StandardError; end
|
5
|
+
|
6
|
+
def self.included(klass)
|
7
|
+
klass.class_eval do
|
8
|
+
extend ClassMethods
|
9
|
+
include InstanceMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods
|
16
|
+
# Every thread in Passenger handles only a single request at a time, but there can be many threads running.
|
17
|
+
# This ensures that when setting the current active controller
|
18
|
+
# it only gets set for the current active thread (and doesn't mess up any other threads).
|
19
|
+
#
|
20
|
+
def controller=(controller)
|
21
|
+
Thread.current[:google_authenticator_rails_controller] = controller
|
22
|
+
end
|
23
|
+
|
24
|
+
def controller
|
25
|
+
Thread.current[:google_authenticator_rails_controller]
|
26
|
+
end
|
27
|
+
|
28
|
+
# If the controller isn't set, we can't use the Sessions. They rely on the session information passed
|
29
|
+
# in from ActionController to access the cookies.
|
30
|
+
#
|
31
|
+
def activated?
|
32
|
+
!controller.nil?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
module InstanceMethods
|
37
|
+
attr_reader :record
|
38
|
+
|
39
|
+
def initialize(record)
|
40
|
+
raise Activation::ControllerMissingError unless self.class.activated?
|
41
|
+
|
42
|
+
@record = record
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def controller
|
47
|
+
self.class.controller
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module GoogleAuthenticatorRails
|
2
|
+
module Session
|
3
|
+
# This is where the heart of the session control logic works.
|
4
|
+
# GoogleAuthenticatorRails works in the same way as Authlogic. It assumes that you've created a class based on
|
5
|
+
# GoogleAuthenticatorRails::Session::Base with the name of the model you want to authenticate + "MfaSession". So if you had
|
6
|
+
#
|
7
|
+
# class User < ActiveRecord::Base
|
8
|
+
# end
|
9
|
+
#
|
10
|
+
# Your Session management class would look like
|
11
|
+
#
|
12
|
+
# class UserMfaSession < GoogleAuthenticatorRails::Session::Base
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# The Session class gets the name of the record to lookup from the name of the class.
|
16
|
+
#
|
17
|
+
# To create a new session based off our User class, you just call
|
18
|
+
#
|
19
|
+
# UserMfaSession.create(@user) # => <# UserMfaSession @record="<# User >">
|
20
|
+
#
|
21
|
+
# Then, in your controller, you can lookup that session by calling
|
22
|
+
#
|
23
|
+
# UserMfaSession.find
|
24
|
+
#
|
25
|
+
# You don't have to pass any arguments because only one session can be active at a time.
|
26
|
+
#
|
27
|
+
class Base
|
28
|
+
include Activation
|
29
|
+
include Persistence
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module GoogleAuthenticatorRails
|
2
|
+
module Session
|
3
|
+
module Persistence
|
4
|
+
class TokenNotFound < StandardError; end
|
5
|
+
|
6
|
+
def self.included(klass)
|
7
|
+
klass.class_eval do
|
8
|
+
extend ClassMethods
|
9
|
+
include InstanceMethods
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
def find
|
16
|
+
cookie = controller.cookies[cookie_key]
|
17
|
+
if cookie
|
18
|
+
token, user_id = parse_cookie(cookie).values_at(:token, :user_id)
|
19
|
+
conditions = { :persistence_token => token, :id => user_id }
|
20
|
+
record = __send__(finder, conditions).first
|
21
|
+
session = new(record)
|
22
|
+
session.valid? ? session : nil
|
23
|
+
else
|
24
|
+
nil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def create(user)
|
29
|
+
raise GoogleAuthenticatorRails::Session::Persistence::TokenNotFound if !user.respond_to?(:persistence_token) || user.persistence_token.blank?
|
30
|
+
controller.cookies[cookie_key] = create_cookie(user.persistence_token, user.id)
|
31
|
+
new(user)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
def finder
|
36
|
+
@_finder ||= klass.public_methods.include?(:where) ? :rails_3_finder : :rails_2_finder
|
37
|
+
end
|
38
|
+
|
39
|
+
def rails_3_finder(conditions)
|
40
|
+
klass.where(conditions)
|
41
|
+
end
|
42
|
+
|
43
|
+
def rails_2_finder(conditions)
|
44
|
+
klass.scoped(:conditions => conditions)
|
45
|
+
end
|
46
|
+
|
47
|
+
def klass
|
48
|
+
@_klass ||= "#{self.to_s.sub("MfaSession", "")}".constantize
|
49
|
+
end
|
50
|
+
|
51
|
+
def parse_cookie(cookie)
|
52
|
+
token, user_id = cookie.split('::')
|
53
|
+
{ :token => token, :user_id => user_id }
|
54
|
+
end
|
55
|
+
|
56
|
+
def create_cookie(token, user_id)
|
57
|
+
value = [token, user_id].join('::')
|
58
|
+
{
|
59
|
+
:value => value,
|
60
|
+
:expires => 24.hours.from_now
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
def cookie_key
|
65
|
+
"#{klass.to_s.downcase}_mfa_credentials"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module InstanceMethods
|
70
|
+
def valid?
|
71
|
+
!record.nil?
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleAuthenticatorRails::ActionController::Integration do
|
4
|
+
describe '::included' do
|
5
|
+
context 'ApplicationController already defined' do
|
6
|
+
before { class ApplicationController < MockController; end }
|
7
|
+
after { Object.send(:remove_const, :ApplicationController) }
|
8
|
+
subject { lambda { MockController.send(:include, GoogleAuthenticatorRails::ActionController::Integration) } }
|
9
|
+
|
10
|
+
it { should raise_error(GoogleAuthenticatorRails::ActionController::RailsAdapter::LoadedTooLateError) }
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should add the before filter' do
|
14
|
+
MockController.should_receive(:prepend_before_filter).with(:activate_google_authenticator_rails)
|
15
|
+
MockController.send(:include, GoogleAuthenticatorRails::ActionController::Integration)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '::activate_google_authenticator_rails' do
|
20
|
+
let(:controller) { MockController.new }
|
21
|
+
|
22
|
+
before do
|
23
|
+
MockController.send(:include, GoogleAuthenticatorRails::ActionController::Integration)
|
24
|
+
controller.send(:activate_google_authenticator_rails)
|
25
|
+
end
|
26
|
+
|
27
|
+
specify { GoogleAuthenticatorRails::Session::Base.controller.should be_a GoogleAuthenticatorRails::ActionController::RailsAdapter }
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleAuthenticatorRails::ActionController::RailsAdapter do
|
4
|
+
describe '#cookies' do
|
5
|
+
let(:controller) { MockController.new }
|
6
|
+
let(:adapter) { GoogleAuthenticatorRails::ActionController::RailsAdapter.new(controller) }
|
7
|
+
|
8
|
+
after { adapter.cookies }
|
9
|
+
specify { controller.should_receive(:cookies) }
|
10
|
+
end
|
11
|
+
end
|
@@ -1,94 +1,81 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
acts_as_google_authenticated
|
7
|
-
end
|
8
|
-
|
9
|
-
class CustomUser < ActiveRecord::Base
|
10
|
-
attr_accessible :email, :user_name
|
11
|
-
|
12
|
-
acts_as_google_authenticated :google_secret_column => :mfa_secret
|
13
|
-
end
|
14
|
-
|
15
|
-
describe Google::Authenticator::Rails do
|
3
|
+
describe GoogleAuthenticatorRails do
|
4
|
+
let(:random32) { "5qlcip7azyjuwm36" }
|
16
5
|
before do
|
17
|
-
ROTP::Base32.stub!(:random_base32).and_return(
|
18
|
-
end
|
19
|
-
|
20
|
-
it 'implements counter based passwords' do
|
21
|
-
Google::Authenticator::Rails::generate_password("test", 1).should == 812658
|
22
|
-
Google::Authenticator::Rails::generate_password("test", 2).should == 73348
|
6
|
+
ROTP::Base32.stub!(:random_base32).and_return(random32)
|
23
7
|
end
|
24
8
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
9
|
+
describe '#generate_password' do
|
10
|
+
subject { GoogleAuthenticatorRails::generate_password("test", counter) }
|
11
|
+
|
12
|
+
context 'counter = 1' do
|
13
|
+
let(:counter) { 1 }
|
14
|
+
it { should == 812658 }
|
15
|
+
end
|
16
|
+
|
17
|
+
context 'counter = 2' do
|
18
|
+
let(:counter) { 2 }
|
19
|
+
it { should == 73348 }
|
20
|
+
end
|
29
21
|
end
|
30
22
|
|
31
|
-
|
32
|
-
time
|
33
|
-
|
34
|
-
|
23
|
+
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) { 472374 }
|
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 }
|
35
34
|
end
|
36
35
|
|
37
36
|
it 'can create a secret' do
|
38
|
-
|
37
|
+
GoogleAuthenticatorRails::generate_secret.should == random32
|
39
38
|
end
|
40
39
|
|
41
40
|
context 'integration with ActiveRecord' do
|
42
|
-
|
41
|
+
let(:original_time) { Time.parse("2012-08-07 11:11:00 AM +0700") }
|
42
|
+
let(:time) { original_time }
|
43
43
|
before do
|
44
|
-
time = Time.parse("2012-08-07 11:11:00 AM +0700")
|
45
44
|
Time.stub!(:now).and_return(time)
|
46
45
|
@user = User.create(:email => "test@example.com", :user_name => "test_user")
|
47
46
|
@user.google_secret = "test"
|
48
47
|
end
|
49
48
|
|
50
|
-
|
51
|
-
@user.
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
end
|
59
|
-
|
60
|
-
it 'does not validate with 6 seconds of drift' do
|
61
|
-
time = Time.parse("2012-08-07 11:11:36 AM +0700")
|
62
|
-
Time.stub!(:now).and_return(time)
|
63
|
-
@user.google_authenticate(472374).should be_false
|
64
|
-
end
|
65
|
-
|
66
|
-
it 'creates a secret' do
|
67
|
-
@user.set_google_secret!
|
68
|
-
@user.google_secret.should == "5qlcip7azyjuwm36"
|
69
|
-
end
|
70
|
-
|
71
|
-
context 'skip_attr_accessible' do
|
72
|
-
it 'respects the :skip_attr_accessible flag' do
|
73
|
-
User.should_not_receive(:attr_accessible).with(:google_secret)
|
74
|
-
User.acts_as_google_authenticated :skip_attr_accessible => true
|
49
|
+
context 'code validation' do
|
50
|
+
subject { @user.google_authentic?(472374) }
|
51
|
+
|
52
|
+
it { should be true }
|
53
|
+
|
54
|
+
context 'within 5 seconds of drift' do
|
55
|
+
let(:time) { original_time + 34.seconds }
|
56
|
+
it { should be true }
|
75
57
|
end
|
76
58
|
|
77
|
-
|
78
|
-
|
79
|
-
|
59
|
+
context '6 seconds of drift' do
|
60
|
+
let(:time) { original_time + 36.seconds }
|
61
|
+
it { should be false }
|
80
62
|
end
|
81
63
|
end
|
64
|
+
|
65
|
+
it 'creates a secret' do
|
66
|
+
@user.set_google_secret
|
67
|
+
@user.google_secret.should == random32
|
68
|
+
end
|
82
69
|
|
83
70
|
context 'secret column' do
|
84
71
|
before do
|
85
|
-
|
72
|
+
GoogleAuthenticatorRails.stub!(:generate_secret).and_return("test")
|
86
73
|
@user = CustomUser.create(:email => "test@example.com", :user_name => "test_user")
|
87
|
-
@user.set_google_secret
|
74
|
+
@user.set_google_secret
|
88
75
|
end
|
89
76
|
|
90
77
|
it 'validates code' do
|
91
|
-
@user.
|
78
|
+
@user.google_authentic?(472374).should be_true
|
92
79
|
end
|
93
80
|
|
94
81
|
it 'generates a url for a qr code' do
|
@@ -96,37 +83,39 @@ describe Google::Authenticator::Rails do
|
|
96
83
|
end
|
97
84
|
end
|
98
85
|
|
86
|
+
context 'google label' do
|
87
|
+
let(:user) { NilMethodUser.create(:email => "test@example.com", :user_name => "test_user") }
|
88
|
+
subject { lambda { user.google_label } }
|
89
|
+
it { should raise_error(NoMethodError) }
|
90
|
+
end
|
91
|
+
|
99
92
|
context 'qr codes' do
|
93
|
+
let(:options) { { :email => "test@example.com", :user_name => "test_user" } }
|
94
|
+
let(:user) { User.create options }
|
95
|
+
before { user.set_google_secret }
|
96
|
+
subject { user.google_qr_uri }
|
97
|
+
|
98
|
+
it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
|
100
99
|
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
|
106
|
-
it 'can generate off any column' do
|
107
|
-
@user.class.acts_as_google_authenticated :column_name => :user_name
|
108
|
-
@user.set_google_secret!
|
109
|
-
@user.google_qr_uri.should == "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest_user%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200"
|
110
|
-
end
|
111
|
-
|
112
|
-
it 'can generate with a custom proc' do
|
113
|
-
@user.class.acts_as_google_authenticated :method => Proc.new { |user| "#{user.user_name}@futureadvisor-admin" }
|
114
|
-
@user.set_google_secret!
|
115
|
-
@user.google_qr_uri.should == "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest_user%40futureadvisor-admin%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200"
|
100
|
+
context 'custom column name' do
|
101
|
+
let(:user) { ColumnNameUser.create options }
|
102
|
+
it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest_user%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
|
116
103
|
end
|
117
104
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
@user.google_qr_uri.should == "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200"
|
105
|
+
context 'custom proc' do
|
106
|
+
let(:user) { ProcUser.create options }
|
107
|
+
it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest_user%40futureadvisor-admin%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
|
122
108
|
end
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
@user.google_qr_uri.should == "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200"
|
109
|
+
|
110
|
+
context 'method defined by symbol' do
|
111
|
+
let(:user) { SymbolUser.create options }
|
112
|
+
it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
|
128
113
|
end
|
129
|
-
|
114
|
+
|
115
|
+
context 'method defined by string' do
|
116
|
+
let(:user) { StringUser.create options }
|
117
|
+
it { should eq "https://chart.googleapis.com/chart?cht=qr&chl=otpauth%3A%2F%2Ftotp%2Ftest%40example.com%3Fsecret%3D5qlcip7azyjuwm36&chs=200x200" }
|
118
|
+
end
|
130
119
|
end
|
131
120
|
|
132
121
|
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleAuthenticatorRails::Session::Base do
|
4
|
+
describe 'ClassMethods' do
|
5
|
+
context 'thread safety' do
|
6
|
+
let(:thread_count) { 100 }
|
7
|
+
let(:controllers) { thread_count.times.map { MockController.new } }
|
8
|
+
let(:threads) do
|
9
|
+
controllers.map do |controller|
|
10
|
+
Thread.new do
|
11
|
+
GoogleAuthenticatorRails::Session::Base.controller = controller
|
12
|
+
Thread.current[:test_case_controller] = GoogleAuthenticatorRails::Session::Base.controller
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
GoogleAuthenticatorRails::Session::Base.controller = nil
|
19
|
+
sleep(0.01) while threads.any?(&:status)
|
20
|
+
end
|
21
|
+
|
22
|
+
specify { GoogleAuthenticatorRails::Session::Base.controller.should be_nil }
|
23
|
+
specify { threads.map { |thread| thread[:test_case_controller].object_id }.should eq controllers.map(&:object_id) }
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '::activated?' do
|
27
|
+
subject { GoogleAuthenticatorRails::Session::Base.activated? }
|
28
|
+
before { GoogleAuthenticatorRails::Session::Base.controller = controller }
|
29
|
+
|
30
|
+
context 'controller present' do
|
31
|
+
let(:controller) { MockController.new }
|
32
|
+
it { should be true }
|
33
|
+
end
|
34
|
+
|
35
|
+
context 'controller missing' do
|
36
|
+
let(:controller) { nil }
|
37
|
+
it { should be false }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'InstanceMethods' do
|
43
|
+
describe '#initialize' do
|
44
|
+
context 'controller missing' do
|
45
|
+
before { GoogleAuthenticatorRails::Session::Base.controller = nil }
|
46
|
+
subject { lambda { GoogleAuthenticatorRails::Session::Base.new(nil) } }
|
47
|
+
it { should raise_error(GoogleAuthenticatorRails::Session::Activation::ControllerMissingError) }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleAuthenticatorRails::Session::Base do
|
4
|
+
let(:controller) { MockController.new }
|
5
|
+
let(:user) { User.create(:password => "password", :email => "email@example.com") }
|
6
|
+
|
7
|
+
# Instantiate the controller so it activates UserSession
|
8
|
+
before { controller.send(:activate_google_authenticator_rails) }
|
9
|
+
|
10
|
+
describe 'ClassMethods' do
|
11
|
+
describe '::find' do
|
12
|
+
subject { UserMfaSession.find }
|
13
|
+
|
14
|
+
context 'no session' do
|
15
|
+
it { should be nil }
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'session' do
|
19
|
+
before { set_cookie_for(user) }
|
20
|
+
after { clear_cookie }
|
21
|
+
|
22
|
+
it { should be_a UserMfaSession }
|
23
|
+
its(:record) { should eq user }
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '::create' do
|
28
|
+
after { clear_cookie }
|
29
|
+
subject { UserMfaSession.create(user) }
|
30
|
+
|
31
|
+
it { should be_a UserMfaSession }
|
32
|
+
its(:record) { should eq user }
|
33
|
+
|
34
|
+
context 'nil user' do
|
35
|
+
let(:user) { nil }
|
36
|
+
subject { lambda { UserMfaSession.create(user) } }
|
37
|
+
it { should raise_error(GoogleAuthenticatorRails::Session::Persistence::TokenNotFound) }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe 'InstanceMethods' do
|
43
|
+
describe '#valid?' do
|
44
|
+
subject { UserMfaSession.create(user) }
|
45
|
+
context 'user object' do
|
46
|
+
it { should be_valid }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def set_cookie_for(user)
|
53
|
+
controller.cookies[UserMfaSession.__send__(:cookie_key)] = { :value => [user.persistence_token, user.id].join('::'), :expires => nil }
|
54
|
+
end
|
55
|
+
|
56
|
+
def clear_cookie
|
57
|
+
controller.cookies[UserMfaSession.__send__(:cookie_key)] = nil
|
58
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,8 +1,46 @@
|
|
1
|
-
require 'google-authenticator-rails'
|
2
1
|
require 'time'
|
3
2
|
require 'active_record'
|
3
|
+
require 'action_controller'
|
4
4
|
require 'rotp'
|
5
5
|
|
6
|
+
require 'google-authenticator-rails'
|
7
|
+
|
8
|
+
class MockController
|
9
|
+
class << self
|
10
|
+
attr_accessor :callbacks
|
11
|
+
|
12
|
+
def prepend_before_filter(filter)
|
13
|
+
self.callbacks ||= []
|
14
|
+
self.callbacks = [filter] + self.callbacks
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
include GoogleAuthenticatorRails::ActionController::Integration
|
19
|
+
|
20
|
+
attr_accessor :cookies
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@cookies = MockCookieJar.new
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class MockCookieJar < Hash
|
28
|
+
def [](key)
|
29
|
+
hash = super
|
30
|
+
hash && hash[:value]
|
31
|
+
end
|
32
|
+
|
33
|
+
def cookie_domain
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
|
37
|
+
def delete(key, options = {})
|
38
|
+
super(key)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
class UserMfaSession < GoogleAuthenticatorRails::Session::Base; end
|
43
|
+
|
6
44
|
ActiveRecord::Base.establish_connection(
|
7
45
|
:adapter => 'sqlite3',
|
8
46
|
:database => ':memory:'
|
@@ -15,6 +53,9 @@ ActiveRecord::Schema.define do
|
|
15
53
|
t.string :google_secret
|
16
54
|
t.string :email
|
17
55
|
t.string :user_name
|
56
|
+
t.string :password
|
57
|
+
t.string :persistence_token
|
58
|
+
|
18
59
|
t.timestamps
|
19
60
|
end
|
20
61
|
|
@@ -22,6 +63,46 @@ ActiveRecord::Schema.define do
|
|
22
63
|
t.string :mfa_secret
|
23
64
|
t.string :email
|
24
65
|
t.string :user_name
|
66
|
+
t.string :persistence_token
|
67
|
+
|
25
68
|
t.timestamps
|
26
69
|
end
|
27
|
-
end
|
70
|
+
end
|
71
|
+
|
72
|
+
class BaseUser < ActiveRecord::Base
|
73
|
+
attr_accessible :email, :user_name
|
74
|
+
self.table_name = "users"
|
75
|
+
|
76
|
+
before_save do |user|
|
77
|
+
user.persistence_token ||= "token"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class User < BaseUser
|
82
|
+
acts_as_google_authenticated
|
83
|
+
end
|
84
|
+
|
85
|
+
class CustomUser < BaseUser
|
86
|
+
self.table_name = "custom_users"
|
87
|
+
acts_as_google_authenticated :google_secret_column => :mfa_secret
|
88
|
+
end
|
89
|
+
|
90
|
+
class NilMethodUser < BaseUser
|
91
|
+
acts_as_google_authenticated :method => true
|
92
|
+
end
|
93
|
+
|
94
|
+
class ColumnNameUser < BaseUser
|
95
|
+
acts_as_google_authenticated :column_name => :user_name
|
96
|
+
end
|
97
|
+
|
98
|
+
class ProcUser < BaseUser
|
99
|
+
acts_as_google_authenticated :method => Proc.new { |user| "#{user.user_name}@futureadvisor-admin" }
|
100
|
+
end
|
101
|
+
|
102
|
+
class SymbolUser < BaseUser
|
103
|
+
acts_as_google_authenticated :method => :email
|
104
|
+
end
|
105
|
+
|
106
|
+
class StringUser < BaseUser
|
107
|
+
acts_as_google_authenticated :method => "email"
|
108
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: google-authenticator-rails
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,10 +9,10 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-09-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
15
|
+
name: rotp
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
@@ -28,7 +28,7 @@ dependencies:
|
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
version: '0'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
|
-
name:
|
31
|
+
name: activerecord
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
33
33
|
none: false
|
34
34
|
requirements:
|
@@ -44,7 +44,7 @@ dependencies:
|
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: '0'
|
46
46
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
47
|
+
name: google-qr
|
48
48
|
requirement: !ruby/object:Gem::Requirement
|
49
49
|
none: false
|
50
50
|
requirements:
|
@@ -60,7 +60,7 @@ dependencies:
|
|
60
60
|
- !ruby/object:Gem::Version
|
61
61
|
version: '0'
|
62
62
|
- !ruby/object:Gem::Dependency
|
63
|
-
name:
|
63
|
+
name: actionpack
|
64
64
|
requirement: !ruby/object:Gem::Requirement
|
65
65
|
none: false
|
66
66
|
requirements:
|
@@ -117,20 +117,28 @@ files:
|
|
117
117
|
- .gitignore
|
118
118
|
- .rspec
|
119
119
|
- .travis.yml
|
120
|
+
- CONTRIBUTE.rb
|
120
121
|
- Gemfile
|
121
122
|
- LICENSE
|
122
123
|
- README.md
|
123
124
|
- Rakefile
|
124
125
|
- google-authenticator.gemspec
|
125
126
|
- lib/google-authenticator-rails.rb
|
127
|
+
- lib/google-authenticator-rails/action_controller.rb
|
128
|
+
- lib/google-authenticator-rails/action_controller/rails_adapter.rb
|
126
129
|
- lib/google-authenticator-rails/active_record.rb
|
127
130
|
- lib/google-authenticator-rails/active_record/acts_as_google_authenticated.rb
|
128
|
-
- lib/google-authenticator-rails/active_record/
|
129
|
-
- lib/google-authenticator-rails/
|
130
|
-
- lib/google-authenticator-rails/
|
131
|
-
- lib/google-authenticator-rails/
|
131
|
+
- lib/google-authenticator-rails/active_record/helpers.rb
|
132
|
+
- lib/google-authenticator-rails/session.rb
|
133
|
+
- lib/google-authenticator-rails/session/activation.rb
|
134
|
+
- lib/google-authenticator-rails/session/base.rb
|
135
|
+
- lib/google-authenticator-rails/session/persistence.rb
|
132
136
|
- lib/google-authenticator-rails/version.rb
|
137
|
+
- spec/action_controller/integration_spec.rb
|
138
|
+
- spec/action_controller/rails_adapter_spec.rb
|
133
139
|
- spec/google_authenticator_spec.rb
|
140
|
+
- spec/session/activation_spec.rb
|
141
|
+
- spec/session/persistance_spec.rb
|
134
142
|
- spec/spec_helper.rb
|
135
143
|
homepage: http://github.com/jaredonline/google-authenticator
|
136
144
|
licenses: []
|
@@ -157,5 +165,9 @@ signing_key:
|
|
157
165
|
specification_version: 3
|
158
166
|
summary: Add the ability to use the Google Authenticator with ActiveRecord.
|
159
167
|
test_files:
|
168
|
+
- spec/action_controller/integration_spec.rb
|
169
|
+
- spec/action_controller/rails_adapter_spec.rb
|
160
170
|
- spec/google_authenticator_spec.rb
|
171
|
+
- spec/session/activation_spec.rb
|
172
|
+
- spec/session/persistance_spec.rb
|
161
173
|
- spec/spec_helper.rb
|
@@ -1,34 +0,0 @@
|
|
1
|
-
module ActiveRecord # :nodoc:
|
2
|
-
module GoogleAuthentication # :nodoc:
|
3
|
-
def set_google_secret!
|
4
|
-
update_attributes("#{self.class.google_secret_column}" => Google::Authenticator::Rails::generate_secret)
|
5
|
-
end
|
6
|
-
|
7
|
-
def google_authenticate(code)
|
8
|
-
Google::Authenticator::Rails.valid?(code, google_secret_value)
|
9
|
-
end
|
10
|
-
|
11
|
-
def google_qr_uri
|
12
|
-
GoogleQR.new(:data => ROTP::TOTP.new(google_secret_value).provisioning_uri(google_label), :size => "200x200").to_s
|
13
|
-
end
|
14
|
-
|
15
|
-
def google_label
|
16
|
-
method = self.class.google_label_method
|
17
|
-
case method
|
18
|
-
when Proc
|
19
|
-
method.call(self)
|
20
|
-
when Symbol, String
|
21
|
-
self.__send__(method)
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
private
|
26
|
-
def default_google_label_method
|
27
|
-
self.__send__(self.class.google_label_column)
|
28
|
-
end
|
29
|
-
|
30
|
-
def google_secret_value
|
31
|
-
self.__send__(self.class.google_secret_column)
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1 +0,0 @@
|
|
1
|
-
require 'google-authenticator-rails/google/rails'
|
@@ -1 +0,0 @@
|
|
1
|
-
require 'google-authenticator-rails/google/rails/rotp_integration'
|
@@ -1,26 +0,0 @@
|
|
1
|
-
# Sets up some basic accessors for use with the ROTP module
|
2
|
-
#
|
3
|
-
module Google
|
4
|
-
module Authenticator # :nodoc:
|
5
|
-
module Rails # :nodoc:
|
6
|
-
# Drift is set to 6 because ROTP drift is not inclusive. This allows a drift of 5 seconds.
|
7
|
-
DRIFT = 6
|
8
|
-
|
9
|
-
def self.generate_password(secret, iteration)
|
10
|
-
ROTP::HOTP.new(secret).at(iteration)
|
11
|
-
end
|
12
|
-
|
13
|
-
def self.time_based_password(secret)
|
14
|
-
ROTP::TOTP.new(secret).now
|
15
|
-
end
|
16
|
-
|
17
|
-
def self.valid?(code, secret)
|
18
|
-
ROTP::TOTP.new(secret).verify_with_drift(code, DRIFT)
|
19
|
-
end
|
20
|
-
|
21
|
-
def self.generate_secret
|
22
|
-
ROTP::Base32.random_base32
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|