Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,16 @@ source 'https://rubygems.org'
gemspec

group :test do
gem 'activerecord', '~> 3.0'
gem "sqlite3", "~> 1.3.5"
gem "bson_ext", "~> 1.3"
gem "capybara", "~> 1.1.0"
gem 'shoulda', '~> 2.11.3'
gem 'mocha', '~> 0.13.0'
gem 'factory_girl_rails', '~> 1.2'
gem 'nokogiri', '< 1.6.0', :platforms => :ruby_18
gem 'activerecord', '~> 4.0'
gem "sqlite3"
gem "bson_ext"
gem "capybara"
gem 'shoulda'
gem 'mocha'
gem 'factory_girl_rails'
gem 'nokogiri', :platforms => :ruby_18
gem 'timecop'
gem 'railties'
gem 'actionmailer'
gem 'railties', '~> 4.0'
gem 'actionmailer', '~> 4.0'
# gem 'debugger'
end
8 changes: 6 additions & 2 deletions README.rdoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,9 @@ Install Devise:
Setup the User or Admin model
* rails g devise MODEL

Configure your app for authorisation, edit your Controller and add this before_filter:
Configure your app for authorisation, edit your Controller and add this before_action:

* before_filter :authenticate_user!
* before_action :authenticate_user!

Make sure your "root" route is configured in config/routes.rb

Expand Down Expand Up @@ -58,8 +58,12 @@ The install generator adds some options to the end of your Devise config file (c
* config.ga_timeout - how long should the user be able to authenticate with their Google Authenticator token
* config.ga_timedrift - a multiplier which provides for drift between a user's clock (and therefore their OTP) and the system clock. This should be fine at 3.
* config.ga_remembertime - how long to remember the token for before requiring another. By default this is 1 month. To disable this setting change it to nil.
* config.ga_remember_optional - true if the user is able to set whether or not the device should remember to not requiring another token, as long as the ga_remembertime is not expired.
* If this is enabled you can pass a boolean POST-parameter from the form called remember_me: <%= check_box_tag :remember_me %>
* config.ga_appname - If you want to set a custom application name instead of using the name of the Rails app.
* config.ga_bypass_signup - If you don't want to immediately forward newly registered or signed-up users to the Display QR page. If this is enabled, users will have to visit the /displayqr page to enable Google Authenticator.
* config.ga_skip_validation_if - If you want to skip Google Authenticator on specific conditions. Can be a boolean or a Proc with the parameters resource and request.
* Example to skip Google Authentication for requests from localhost: config.ga_skip_validation_if = ->(resource, request) { request.remote_ip == '127.0.0.1' }

== Custom Views

Expand Down
8 changes: 4 additions & 4 deletions app/controllers/devise/checkga_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
class Devise::CheckgaController < Devise::SessionsController
prepend_before_filter :devise_resource, :only => [:show]
prepend_before_filter :require_no_authentication, :only => [ :show, :update ]
prepend_before_action :devise_resource, :only => [:show]
prepend_before_action :require_no_authentication, :only => [ :show, :update ]

include Devise::Controllers::Helpers

Expand All @@ -24,7 +24,7 @@ def update
warden.manager._run_callbacks(:after_set_user, resource, warden, {:event => :authentication})
respond_with resource, :location => after_sign_in_path_for(resource)

if not resource.class.ga_remembertime.nil?
if !resource.class.ga_remembertime.nil? && (!resource.class.ga_remember_optional || Devise::TRUE_VALUES.include?(params['remember_me']))
cookies.signed[:gauth] = {
:value => resource.email << "," << Time.now.to_i.to_s,
:secure => !(Rails.env.test? || Rails.env.development?),
Expand All @@ -47,4 +47,4 @@ def update
def devise_resource
self.resource = resource_class.new
end
end
end
8 changes: 4 additions & 4 deletions app/controllers/devise/displayqr_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
class Devise::DisplayqrController < DeviseController
prepend_before_filter :authenticate_scope!, :only => [:show, :update, :refresh]
prepend_before_action :authenticate_scope!, :only => [:show, :update, :refresh]

include Devise::Controllers::Helpers

Expand Down Expand Up @@ -48,17 +48,17 @@ def scope
end

def authenticate_scope!
send(:"authenticate_#{resource_name}!")
send(:"authenticate_#{resource_name}!", :force => true)
self.resource = send("current_#{resource_name}")
end

# 7/2/15 - Unsure if this is used anymore - @xntrik
def resource_params
return params.require(resource_name.to_sym).permit(:gauth_enabled) if strong_parameters_enabled?
params
params[resource_name.to_sym]
end

def strong_parameters_enabled?
defined?(ActionController::StrongParameters)
end
end
end
2 changes: 1 addition & 1 deletion devise_google_authenticator.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Gem::Specification.new do |s|
# removed the following to try and get past this bundle update not finding compatible versions for gem issue
# 'actionmailer' => '>= 3.0',
#'actionmailer' => '~> 3.2',# '>= 3.2.12',
'devise' => '~> 3.2',
'devise' => '~> 4.3',
'rotp' => '~> 1.6'
}.each do |lib, version|
s.add_runtime_dependency(lib, *version)
Expand Down
2 changes: 1 addition & 1 deletion lib/devise_google_authenticatable/controllers/helpers.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module Controllers # :nodoc:
module Helpers # :nodoc:
def google_authenticator_qrcode(user, qualifier=nil, issuer=nil)
username = username_from_email(user.email)
app = user.class.ga_appname || Rails.application.class.parent_name
app = user.class.ga_appname || Rails.application.class.module_parent_name
data = "otpauth://totp/#{otpauth_user(username, app, qualifier)}?secret=#{user.gauth_secret}"
data << "&issuer=#{issuer}" if !issuer.nil?
data = Rack::Utils.escape(data)
Expand Down
23 changes: 20 additions & 3 deletions lib/devise_google_authenticatable/models/google_authenticatable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,11 @@ def get_qr

def set_gauth_enabled(param)
#self.update_without_password(params[gauth_enabled])
self.update_attributes(:gauth_enabled => param)
self.update(:gauth_enabled => param)
end

def assign_tmp
self.update_attributes(:gauth_tmp => ROTP::Base32.random_base32(32), :gauth_tmp_datetime => DateTime.now)
self.update(:gauth_tmp => ROTP::Base32.random_base32(32), :gauth_tmp_datetime => DateTime.now)
self.gauth_tmp
end

Expand Down Expand Up @@ -77,6 +77,23 @@ def require_token?(cookie)
return last_logged_in_email != self.email || (Time.now.to_i - last_logged_in_time) > self.class.ga_remembertime.to_i
end

def skip_validation? request
if self.class.ga_skip_validation_if.is_a? Proc
case self.class.ga_skip_validation_if.arity
when 0
self.class.ga_skip_validation_if.call
when 1
self.class.ga_skip_validation_if.call self
when 2
self.class.ga_skip_validation_if.call self, request
else
raise ArgumentError.new("too many required arguments for ga_skip_validation_if (#{self.class.ga_skip_validation.if.arity} instead of 0..2)")
end
else
self.class.ga_skip_validation_if
end
end

private

def assign_auth_secret
Expand All @@ -89,7 +106,7 @@ module ClassMethods # :nodoc:
def find_by_gauth_tmp(gauth_tmp)
where(gauth_tmp: gauth_tmp).first
end
::Devise::Models.config(self, :ga_timeout, :ga_timedrift, :ga_remembertime, :ga_appname, :ga_bypass_signup)
::Devise::Models.config(self, :ga_timeout, :ga_timedrift, :ga_remembertime, :ga_remember_optional, :ga_appname, :ga_bypass_signup, :ga_skip_validation_if)
end
end
end
Expand Down
5 changes: 3 additions & 2 deletions lib/devise_google_authenticatable/patches/check_ga.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ module CheckGA
end

define_method :create do

resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")

if resource.respond_to?(:get_qr) and resource.gauth_enabled? and resource.require_token?(cookies.signed[:gauth]) #Therefore we can quiz for a QR
if resource.respond_to?(:get_qr) and resource.gauth_enabled? and !resource.skip_validation?(request) and resource.require_token?(cookies.signed[:gauth]) #Therefore we can quiz for a QR
tmpid = resource.assign_tmp #assign a temporary key and fetch it
previous_location = stored_location_for resource_name
warden.logout #log the user out
store_location_for resource_name, previous_location # keep the location

#we head back into the checkga controller with the temporary id
#Because the model used for google auth may not always be the same, and may be a sub-model, the eval will evaluate the appropriate path name
Expand Down
1 change: 0 additions & 1 deletion lib/devise_google_authenticatable/patches/display_qr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ module DisplayQR
build_resource(sign_up_params)

if resource.save
yield resource if block_given?
if resource.active_for_authentication?
set_flash_message :notice, :signed_up if is_flashing_format?
sign_in(resource_name, resource)
Expand Down
11 changes: 8 additions & 3 deletions lib/devise_google_authenticatable/rails.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
module DeviseGoogleAuthenticator
class Engine < ::Rails::Engine # :nodoc:
ActionDispatch::Callbacks.to_prepare do
DeviseGoogleAuthenticator::Patches.apply
if defined? ActiveSupport::Reloader
ActiveSupport::Reloader.to_prepare do
DeviseGoogleAuthenticator::Patches.apply
end
else
ActionDispatch::Callbacks.to_prepare do
DeviseGoogleAuthenticator::Patches.apply
end
end

end
end
10 changes: 8 additions & 2 deletions lib/devise_google_authenticator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,17 @@ module Devise # :nodoc:
mattr_accessor :ga_remembertime
@@ga_remembertime = 1.month

mattr_accessor :ga_remember_optional
@@ga_remember_optional= false

mattr_accessor :ga_appname
@@ga_appname = Rails.application.class.parent_name
@@ga_appname = Rails.application.class.module_parent_name

mattr_accessor :ga_bypass_signup
@@ga_bypass_signup = false

mattr_accessor :ga_skip_validation_if
@@ga_skip_validation_if = false
end

# a security extension for devise
Expand All @@ -36,4 +42,4 @@ module DeviseGoogleAuthenticator
require 'devise_google_authenticatable/controllers/helpers'
ActionView::Base.send :include, DeviseGoogleAuthenticator::Controllers::Helpers

Devise.add_module :google_authenticatable, :controller => :google_authenticatable, :model => 'devise_google_authenticatable/models/google_authenticatable', :route => :displayqr
Devise.add_module :google_authenticatable, :controller => :google_authenticatable, :model => 'devise_google_authenticatable/models/google_authenticatable', :route => :displayqr
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@ def add_configs
" # Change setting to how long to remember device before requiring another token. Change to nil to turn feature off.\n" +
" # To change the default, uncomment and change the below:\n" +
" # config.ga_remembertime = 1.month\n\n" +
" # Change setting if the user is able to set whether or not the device should remember to not requiring another token, as long as the ga_remembertime is not expired.\n" +
" # If this is enabled you can pass a boolean POST-parameter from the form called remember_me\n" +
" # To change the default, uncomment and change the below:\n" +
" # config.ga_remember_optional = false\n\n" +
" # Change setting to assign the application name used by code generator. Defaults to Rails.application.class.parent_name.\n" +
" # To change the default, uncomment and change the below:\n" +
" # config.ga_appname = 'example.com'\n\n" +
" # Change setting to bypass the Display QR page immediately after a user sign's up\n" +
" # To change the default, uncomment and change the below. Defaults to false:\n" +
" # config.ga_bypass_signup = true\n\n" +
" # Change setting to skip Google Authentication if the condition is true. This can be a boolean or a Proc with the parameters resource and request.\n" +
" # To change the default, uncomment and change the below. This example will ignore Google Authentication if the request is from localhost. Default is false:\n" +
" # config.ga_skip_validation_if = ->(resource, request) { request.remote_ip == '127.0.0.1' }\n\n" +
"\n", :before => /end[ |\n|]+\Z/
end

Expand All @@ -29,4 +36,4 @@ def copy_locale
end
end
end
end
end
56 changes: 52 additions & 4 deletions test/integration/gauth_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ def teardown

test 'a new user should be able to sign in without using their token' do
create_full_user
User.find_by_email("fulluser@test.com").update_attributes(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition
User.find_by_email("fulluser@test.com").update(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition

visit new_user_session_path
fill_in 'user_email', :with => 'fulluser@test.com'
Expand All @@ -40,7 +40,7 @@ def teardown
test 'a new user should be able to sign in and change their qr code to enabled' do
# sign_in_as_user
create_full_user
User.find_by_email("fulluser@test.com").update_attributes(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition
User.find_by_email("fulluser@test.com").update(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition
visit new_user_session_path
fill_in 'user_email', :with => 'fulluser@test.com'
fill_in 'user_password', :with => '123456'
Expand All @@ -59,7 +59,7 @@ def teardown

test 'a new user should be able to sign in change their qr to enabled and be prompted for their token' do
create_full_user
User.find_by_email("fulluser@test.com").update_attributes(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition
User.find_by_email("fulluser@test.com").update(:gauth_enabled => 0) # force this off - unsure why sometimes it flicks on possible race condition
visit new_user_session_path
fill_in 'user_email', :with => 'fulluser@test.com'
fill_in 'user_password', :with => '123456'
Expand Down Expand Up @@ -179,4 +179,52 @@ def teardown

Timecop.return
end
end

test 'skip validation will not prompt the checkga page' do
default_value = User.ga_skip_validation_if

$skip_validation = false
User.ga_skip_validation_if = ->(user, request) { $skip_validation }

testuser = User.create!(
:username => 'skip_validation_usertest',
:email => 'skip_validation@test.com',
:password => '123456',
:password_confirmation => '123456'
)
testuser.gauth_enabled = 1
testuser.save!

Capybara.reset_sessions!

sign_in_as_user(testuser)
assert_equal user_checkga_path, current_path

$skip_validation = true

Capybara.reset_sessions!

sign_in_as_user(testuser)
assert_equal root_path, current_path

Capybara.reset_sessions!

# Skip if from localhost

User.ga_skip_validation_if = ->(user, request) { request.remote_ip == '127.0.0.1' }

sign_in_as_user(testuser)
assert_equal root_path, current_path

Capybara.reset_sessions!

# Skip if not from localhost

User.ga_skip_validation_if = ->(user, request) { request.remote_ip != '127.0.0.1' }

sign_in_as_user(testuser)
assert_equal user_checkga_path, current_path

User.ga_skip_validation_if = default_value
end
end
18 changes: 8 additions & 10 deletions test/integration_tests_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,18 @@ def warden
end

def create_full_user
@@user ||= begin
user = User.create!(
:username => 'usertest',
:email => 'fulluser@test.com',
:password => '123456',
:password_confirmation => '123456'
)
@@user = user
user
end
@@user ||= User.create!(
:username => 'usertest',
:email => 'fulluser@test.com',
:password => '123456',
:password_confirmation => '123456'
)
end

def create_and_signin_gauth_user
testuser = create_full_user
testuser.gauth_enabled = false
testuser.save!
sign_in_as_user(testuser)
visit user_displayqr_path
check 'user_gauth_enabled'
Expand Down
Loading