Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ group :test do
gem 'timecop'
gem 'railties'
gem 'actionmailer'
# gem 'debugger'
gem 'test-unit'
end
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ task :tests do
orm = File.basename(file).split(".").first
system "rake test DEVISE_ORM=#{orm}"
end
end
end
47 changes: 21 additions & 26 deletions app/controllers/devise/checkga_controller.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
class Devise::CheckgaController < Devise::SessionsController
prepend_before_filter :devise_resource, :only => [:show]
prepend_before_filter :require_no_authentication, :only => [ :show, :update ]
prepend_before_filter :devise_resource, only: [:show]
prepend_before_filter :require_no_authentication, only: [:show, :update]

include Devise::Controllers::Helpers
class ErrorSigningIn < StandardError; end

def show
@tmpid = params[:id]
Expand All @@ -16,35 +17,29 @@ def show
def update
resource = resource_class.find_by_gauth_tmp(params[resource_name]['tmpid'])

if not resource.nil?

if resource.validate_token(params[resource_name]['gauth_token'].to_i)
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name,resource)
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?
cookies.signed[:gauth] = {
:value => resource.email << "," << Time.now.to_i.to_s,
:secure => !(Rails.env.test? || Rails.env.development?),
:expires => (resource.class.ga_remembertime + 1.days).from_now
}
end
else
set_flash_message(:error, :error)
redirect_to :root
end
fail ErrorSigningIn unless resource
fail ErrorSigningIn unless resource.validate_token(params[resource_name]['gauth_token'].to_i)

else
set_flash_message(:error, :error)
redirect_to :root
end
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name,resource)
warden.manager._run_callbacks(:after_set_user, resource, warden, event: :authentication)

gauth_cookie = {
value: [resource.email, Time.now.to_i.to_s].join(','),
secure: !(Rails.env.test? || Rails.env.development?),
expires: (resource.class.ga_remembertime + 1.day).from_now
}
cookies.signed[:gauth] = gauth_cookie if resource.class.ga_remembertime

respond_with resource, location: after_sign_in_path_for(resource)
rescue ErrorSigningIn
set_flash_message(:error, :error)
redirect_to :root
end

private

def devise_resource
self.resource = resource_class.new
end
end
end
48 changes: 25 additions & 23 deletions app/controllers/devise/displayqr_controller.rb
Original file line number Diff line number Diff line change
@@ -1,64 +1,66 @@
class Devise::DisplayqrController < DeviseController
prepend_before_filter :authenticate_scope!, :only => [:show, :update, :refresh]
prepend_before_filter :authenticate_scope!, only: [:show, :update, :refresh]

include Devise::Controllers::Helpers
class InvalidToken < StandardError; end

# GET /resource/displayqr
# GET /{resource}/displayqr
def show
if resource.nil? || resource.gauth_secret.nil?
sign_in resource_class.new, resource
redirect_to stored_location_for(scope) || :root
else
if resource && resource.gauth_secret
@tmpid = resource.assign_tmp
render :show
else
sign_in resource_class.new, resource
redirect_to stored_location_for(scope) || :root
end
end

def update
if resource.gauth_tmp != params[resource_name]['tmpid'] || !resource.validate_token(params[resource_name]['gauth_token'].to_i)
set_flash_message(:error, :invalid_token)
render :show
return
end
fail InvalidToken if resource.gauth_tmp != params[resource_name]['tmpid']
fail InvalidToken unless resource.validate_token(params[resource_name]['gauth_token'].to_i)

if resource.set_gauth_enabled(params[resource_name]['gauth_enabled'])
set_flash_message :notice, (resource.gauth_enabled? ? :enabled : :disabled)
sign_in scope, resource, :bypass => true
sign_in scope, resource, bypass: true
redirect_to stored_location_for(scope) || :root
else
render :show
end
rescue InvalidToken
set_flash_message(:error, :invalid_token)
render :show
end

def refresh
unless resource.nil?
if resource
resource.send(:assign_auth_secret)
resource.save
set_flash_message :notice, :newtoken
sign_in scope, resource, :bypass => true

set_flash_message(:notice, :newtoken)
sign_in(scope, resource, bypass: true)
redirect_to [resource_name, :displayqr]
else
redirect_to :root
end
end

private

def scope
resource_name.to_sym
end

def authenticate_scope!
send(:"authenticate_#{resource_name}!")
send("authenticate_#{resource_name}!")
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
end

def strong_parameters_enabled?
defined?(ActionController::StrongParameters)
if defined?(ActionController::StrongParameters)
params.require(resource_name.to_sym).permit(:gauth_enabled)
else
params
end
end
end
end
12 changes: 6 additions & 6 deletions app/views/devise/checkga/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<h2><%= I18n.t('submit_token_title', {:scope => 'devise'}) %></h2>
<h2><%= I18n.t('submit_token_title', scope: 'devise') %></h2>

<%= form_for(resource, :as => resource_name, :url => [resource_name, :checkga], :html => { :method => :put }) do |f| %>
<%= f.hidden_field :tmpid, {:value => @tmpid} %>
<%= f.text_field :gauth_token, :autocomplete => :off%>
<p><%= f.submit I18n.t('submit_token', {:scope => 'devise'}) %></p>
<% end %>
<%= form_for(resource, as: resource_name, url: [resource_name, :checkga], html: { method: :put }) do |f| %>
<%= f.hidden_field :tmpid, value: @tmpid %>
<%= f.text_field :gauth_token, autocomplete: :off %>
<p><%= f.submit I18n.t('submit_token', scope: 'devise') %></p>
<% end %>
24 changes: 12 additions & 12 deletions app/views/devise/displayqr/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
<h2><%= I18n.t('title', {:scope => 'devise.registration'}) %></h2>
<h2><%= I18n.t('title', scope: 'devise.registration') %></h2>

<%= google_authenticator_qrcode(resource) %>

<%= form_for(resource, :as => resource_name, :url => [:refresh, resource_name, :displayqr], :html => {:method => :post}) do |f|%>
<p><%= f.submit I18n.t('newtoken', {:scope => 'devise.registration'}) %></p>
<%= form_for(resource, as: resource_name, url: [:refresh, resource_name, :displayqr], html: { method: :post }) do |f|%>
<p><%= f.submit I18n.t('newtoken', scope: 'devise.registration') %></p>
<% end %>

<%= form_for(resource, :as => resource_name, :url => [resource_name, :displayqr], :html => { :method => :put }) do |f| %>
<%= devise_error_messages! %>
<h3><%= I18n.t('nice_request', {:scope => 'devise.registration'}) %></h3>
<p><%= f.label :gauth_enabled, I18n.t('qrstatus', {:scope => 'devise.registration'}) %><br />
<%= f.check_box :gauth_enabled %></p>
<%= f.hidden_field :tmpid, value: @tmpid %>
<p><%= f.label :gauth_token, I18n.t('enter_token', {:scope => 'devise.registration'}) %><br />
<%= f.number_field :gauth_token, :autocomplete => :off %>
<%= form_for(resource, as: resource_name, url: [resource_name, :displayqr], html: { method: :put }) do |f| %>
<%= devise_error_messages! %>
<h3><%= I18n.t('nice_request', scope: 'devise.registration') %></h3>
<p><%= f.label :gauth_enabled, I18n.t('qrstatus', scope: 'devise.registration') %><br />
<%= f.check_box :gauth_enabled %></p>
<%= f.hidden_field :tmpid, value: @tmpid %>
<p><%= f.label :gauth_token, I18n.t('enter_token', scope: 'devise.registration') %><br />
<%= f.number_field :gauth_token, autocomplete: :off %>

<p><%= f.submit I18n.t('submit', {:scope => 'devise.registration'}) %></p>
<p><%= f.submit I18n.t('submit', scope: 'devise.registration') %></p>
<% end %>

6 changes: 3 additions & 3 deletions devise_google_authenticator.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ 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',
'rotp' => '~> 1.6'
'devise' => '~> 3.2',
'rotp' => '~> 1.6',
'rqrcode' => '~> 0.10.1'
}.each do |lib, version|
s.add_runtime_dependency(lib, *version)
end

end
26 changes: 17 additions & 9 deletions lib/devise_google_authenticatable/controllers/helpers.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,32 @@
module DeviseGoogleAuthenticator
require 'rqrcode'
require 'base64'

module DeviseGoogleAuthenticator #:nodoc:
module Controllers # :nodoc:
module Helpers # :nodoc:
def google_authenticator_qrcode(user, qualifier=nil, issuer=nil)
username = username_from_email(user.email)
def google_authenticator_qrcode(user, qualifier = nil, issuer = nil)
username = nil
Devise.authentication_keys.any? {|k| username = user.public_send(k) rescue nil }
username ||= username_from_email(user.email)
app = user.class.ga_appname || Rails.application.class.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)
url = "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=#{data}"
return image_tag(url, :alt => 'Google Authenticator QRCode')
# data-uri is easier, so...
qrcode = RQRCode::QRCode.new(data, level: :m, mode: :byte_8bit)
png = qrcode.as_png(fill: 'white', color: 'black', border_modules: 1, module_px_size: 4)
url = "data:image/png;base64,#{Base64.encode64(png.to_s).strip}"
#url = "data:image/svg+xml;utf8,#{qrcode.as_svg}"
#url = "https://chart.googleapis.com/chart?chs=200x200&chld=M|0&cht=qr&chl=#{Rack::Utils.escape(data)}"
return image_tag(url, alt: 'Google Authenticator QRCode')
end

def otpauth_user(username, app, qualifier=nil)
def otpauth_user(username, app, qualifier = nil)
"#{username}@#{app}#{qualifier}"
end

def username_from_email(email)
(/^(.*)@/).match(email)[1]
((/^(.*)@/).match(email) || [])[1]
end

end
end
end
71 changes: 29 additions & 42 deletions lib/devise_google_authenticatable/models/google_authenticatable.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
require 'rotp'

module Devise # :nodoc:
module Models # :nodoc:

module GoogleAuthenticatable

def self.included(base) # :nodoc:
module Devise #:nodoc:
module Models #:nodoc:
module GoogleAuthenticatable #:nodoc:
def self.included(base) #:nodoc:
base.extend ClassMethods

base.class_eval do
Expand All @@ -14,66 +12,55 @@ def self.included(base) # :nodoc:
end
end

module InstanceMethods # :nodoc:
module InstanceMethods #:nodoc:
def get_qr
self.gauth_secret
end

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

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

def validate_token(token)
return false if self.gauth_tmp_datetime.nil?
if self.gauth_tmp_datetime < self.class.ga_timeout.ago
return false
else

valid_vals = []
valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now)
(1..self.class.ga_timedrift).each do |cc|
valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now.ago(30*cc))
valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now.in(30*cc))
end

if valid_vals.include?(token.to_i)
return true
else
return false
end
return false unless self.gauth_tmp_datetime
return false if self.gauth_tmp_datetime < self.class.ga_timeout.ago

valid_vals = []
valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now)
(1..self.class.ga_timedrift).each do |cc|
valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now.ago(30*cc))
valid_vals << ROTP::TOTP.new(self.get_qr).at(Time.now.in(30*cc))
end

return valid_vals.include?(token.to_i)
end

def gauth_enabled?
# Active_record seems to handle determining the status better this way
if self.gauth_enabled.respond_to?("to_i")
if self.gauth_enabled.to_i != 0
return true
else
return false
end
# Mongoid does NOT have a .to_i for the Boolean return value, hence, we can just return it
if self.gauth_enabled.respond_to?(:to_i)
# Active_record seems to handle determining the status better this way
return self.gauth_enabled.to_i != 0
else
# Mongoid does NOT have a .to_i for the Boolean return value, hence we can just return it
return self.gauth_enabled
end
end

def require_token?(cookie)
if self.class.ga_remembertime.nil? || cookie.blank?
return true
end
return true if self.class.ga_remembertime.nil? || cookie.blank?

array = cookie.to_s.split ','
if array.count != 2
return true
end

return true if array.count != 2

last_logged_in_email = array[0]
last_logged_in_time = array[1].to_i

return last_logged_in_email != self.email || (Time.now.to_i - last_logged_in_time) > self.class.ga_remembertime.to_i
end

Expand All @@ -82,13 +69,13 @@ def require_token?(cookie)
def assign_auth_secret
self.gauth_secret = ROTP::Base32.random_base32(64)
end

end

module ClassMethods # :nodoc:
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)
end
end
Expand Down
Loading