google-authenticator-rails 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://secure.travis-ci.org/jaredonline/google-authenticator.png)](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
|