Dmitrii Golub
13 years ago
commit
e4c812ad6d
19 changed files with 320 additions and 0 deletions
@ -0,0 +1,20 @@
|
||||
*.gem |
||||
.bundle |
||||
Gemfile.lock |
||||
pkg/* |
||||
|
||||
# Temporary files of every sort |
||||
.DS_Store |
||||
.idea |
||||
.rvmrc |
||||
.stgit* |
||||
*.swap |
||||
*.swo |
||||
*.swp |
||||
*~ |
||||
bin/* |
||||
nbproject |
||||
patches-* |
||||
capybara-*.html |
||||
dump.rdb |
||||
*.ids |
@ -0,0 +1,4 @@
|
||||
source "http://rubygems.org" |
||||
|
||||
# Specify your gem's dependencies in devise_ip_filter.gemspec |
||||
gemspec |
@ -0,0 +1,19 @@
|
||||
Copyright (C) 2012 Dmitrii Golub |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of |
||||
this software and associated documentation files (the "Software"), to deal in |
||||
the Software without restriction, including without limitation the rights to |
||||
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies |
||||
of the Software, and to permit persons to whom the Software is furnished to do |
||||
so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,48 @@
|
||||
## Two factor authentication for Devise |
||||
|
||||
## Features |
||||
|
||||
* control sms code pattern |
||||
* configure max login attempts |
||||
* per user level control if he really need two factor authentication |
||||
* your own sms logic |
||||
|
||||
## Configuration |
||||
|
||||
To enable two factor authentication for User model, you should add two_factor_authentication to your devise line, like: |
||||
|
||||
```ruby |
||||
devise :database_authenticatable, :registerable, |
||||
:recoverable, :rememberable, :trackable, :validatable, :two_factor_authenticatable |
||||
``` |
||||
|
||||
Two default parameters |
||||
|
||||
```ruby |
||||
config.login_code_random_pattern = /\w+/ |
||||
config.max_login_attempts = 3 |
||||
``` |
||||
|
||||
Possible random patterns |
||||
```ruby |
||||
/\d{5}/ |
||||
/\w{4,8}/ |
||||
``` |
||||
|
||||
see more https://github.com/benburkert/randexp |
||||
|
||||
By default second factor authentication enabled for each user, you can change it with this method in your User mdoel: |
||||
```ruby |
||||
def need_two_factor_authentication?(request) |
||||
request.ip != '127.0.0.1' |
||||
end |
||||
``` |
||||
this will disable two factor authentication for local users |
||||
|
||||
Your send sms logic should be in this method in your User model: |
||||
```ruby |
||||
def send_two_factor_authentication_code(code) |
||||
puts code |
||||
end |
||||
``` |
||||
This example just puts code in logs |
@ -0,0 +1,43 @@
|
||||
class Devise::TwoFactorAuthenticationController < DeviseController |
||||
prepend_before_filter :authenticate_scope! |
||||
before_filter :prepare_and_validate, :handle_two_factor_authentication |
||||
|
||||
def show |
||||
end |
||||
|
||||
def update |
||||
render :show and return if params[:code].nil? |
||||
md5 = Digest::MD5.hexdigest(params[:code]) |
||||
if md5.eql?(resource.second_factor_pass_code) |
||||
warden.session(resource_name)[:need_two_factor_authentication] = false |
||||
sign_in resource_name, resource, :bypass => true |
||||
redirect_to stored_location_for(resource_name) || :root |
||||
resource.update_attribute(:second_factor_attempts_count, 0) |
||||
else |
||||
resource.second_factor_attempts_count += 1 |
||||
resource.save |
||||
set_flash_message :notice, :attempt_failed |
||||
if resource.max_login_attempts? |
||||
sign_out(resource) |
||||
render :template => 'devise/two_factor_authentication/max_login_attempts_reached' and return |
||||
else |
||||
render :show |
||||
end |
||||
end |
||||
end |
||||
|
||||
private |
||||
|
||||
def authenticate_scope! |
||||
self.resource = send("current_#{resource_name}") |
||||
end |
||||
|
||||
def prepare_and_validate |
||||
redirect_to :root and return if resource.nil? |
||||
@limit = resource.class.max_login_attempts |
||||
if resource.max_login_attempts? |
||||
sign_out(resource) |
||||
render :template => 'devise/two_factor_authentication/max_login_attempts_reached' and return |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,3 @@
|
||||
<h2>Access completly denied as you have reached your attempts limit = <%= @limit %></h2> |
||||
<p>Please contact your system administrator</p> |
||||
|
@ -0,0 +1,10 @@
|
||||
<h2>Enter your personal code</h2> |
||||
|
||||
<p><%= flash[:notice] %></p> |
||||
|
||||
<%= form_tag([resource_name, :two_factor_authentication], :method => :put) do %> |
||||
<%= text_field_tag :code %> |
||||
<%= submit_tag "Submit" %> |
||||
<% end %> |
||||
|
||||
<%= link_to "Sign out", destroy_user_session_path, :method => :delete %> |
@ -0,0 +1,4 @@
|
||||
en: |
||||
devise: |
||||
two_factor_authentication: |
||||
attempt_failed: "Attemp failed" |
@ -0,0 +1,27 @@
|
||||
require 'two_factor_authentication/version' |
||||
require 'randexp' |
||||
require 'devise' |
||||
require 'digest' |
||||
require 'active_support/concern' |
||||
|
||||
module Devise |
||||
mattr_accessor :login_code_random_pattern |
||||
@@login_code_random_pattern = /\w+/ |
||||
|
||||
mattr_accessor :max_login_attempts |
||||
@@max_login_attempts = 3 |
||||
end |
||||
|
||||
module TwoFactorAuthentication |
||||
autoload :Schema, 'two_factor_authentication/schema' |
||||
module Controllers |
||||
autoload :Helpers, 'two_factor_authentication/controllers/helpers' |
||||
end |
||||
end |
||||
|
||||
Devise.add_module :two_factor_authenticatable, :model => 'two_factor_authentication/models/two_factor_authenticatable', :controller => :two_factor_authentication, :route => :two_factor_authentication |
||||
|
||||
require 'two_factor_authentication/orm/active_record' |
||||
require 'two_factor_authentication/routes' |
||||
require 'two_factor_authentication/models/two_factor_authenticatable' |
||||
require 'two_factor_authentication/rails' |
@ -0,0 +1,34 @@
|
||||
module TwoFactorAuthentication |
||||
module Controllers |
||||
module Helpers |
||||
extend ActiveSupport::Concern |
||||
|
||||
included do |
||||
before_filter :handle_two_factor_authentication |
||||
end |
||||
|
||||
module InstanceMethods |
||||
private |
||||
|
||||
def handle_two_factor_authentication |
||||
if not request.format.nil? and request.format.html? and not devise_controller? |
||||
Devise.mappings.keys.flatten.any? do |scope| |
||||
if signed_in?(scope) and warden.session(scope)[:need_two_factor_authentication] |
||||
session["#{scope}_return_tor"] = request.path if request.get? |
||||
redirect_to two_factor_authentication_path_for(scope) |
||||
return |
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
def two_factor_authentication_path_for(resource_or_scope = nil) |
||||
scope = Devise::Mapping.find_scope!(resource_or_scope) |
||||
change_path = "#{scope}_two_factor_authentication_path" |
||||
send(change_path) |
||||
end |
||||
|
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,10 @@
|
||||
Warden::Manager.after_authentication do |user, auth, options| |
||||
if user.respond_to?(:need_two_factor_authentication?) |
||||
if auth.session(options[:scope])[:need_two_factor_authentication] = user.need_two_factor_authentication?(auth.request) |
||||
code = user.generate_two_factor_code |
||||
user.second_factor_pass_code = Digest::MD5.hexdigest(code) |
||||
user.save |
||||
user.send_two_factor_authentication_code(code) |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,30 @@
|
||||
require 'two_factor_authentication/hooks/two_factor_authenticatable' |
||||
module Devise |
||||
module Models |
||||
module TwoFactorAuthenticatable |
||||
extend ActiveSupport::Concern |
||||
|
||||
module ClassMethods |
||||
::Devise::Models.config(self, :login_code_random_pattern, :max_login_attempts) |
||||
end |
||||
|
||||
module InstanceMethods |
||||
def need_two_factor_authentication? |
||||
true |
||||
end |
||||
|
||||
def generate_two_factor_code |
||||
self.class.login_code_random_pattern.gen |
||||
end |
||||
|
||||
def send_two_factor_authentication_code(code) |
||||
p "Code is #{code}" |
||||
end |
||||
|
||||
def max_login_attempts? |
||||
second_factor_attempts_count >= self.class.max_login_attempts |
||||
end |
||||
end |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,14 @@
|
||||
module TwoFactorAuthentication |
||||
module Orm |
||||
|
||||
module ActiveRecord |
||||
module Schema |
||||
include TwoFactorAuthentication::Schema |
||||
|
||||
end |
||||
end |
||||
end |
||||
end |
||||
|
||||
ActiveRecord::ConnectionAdapters::Table.send :include, TwoFactorAuthentication::Orm::ActiveRecord::Schema |
||||
ActiveRecord::ConnectionAdapters::TableDefinition.send :include, TwoFactorAuthentication::Orm::ActiveRecord::Schema |
@ -0,0 +1,7 @@
|
||||
module TwoFactorAuthentication |
||||
class Engine < ::Rails::Engine |
||||
ActiveSupport.on_load(:action_controller) do |
||||
include TwoFactorAuthentication::Controllers::Helpers |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,9 @@
|
||||
module ActionDispatch::Routing |
||||
class Mapper |
||||
protected |
||||
|
||||
def devise_two_factor_authentication(mapping, controllers) |
||||
resource :two_factor_authentication, :only => [:show, :update], :path => mapping.path_names[:two_factor_authentication], :controller => controllers[:two_factor_authentication] |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,8 @@
|
||||
module TwoFactorAuthentication |
||||
module Schema |
||||
def two_factor_authenticatable |
||||
apply_devise_schema :second_factor_pass_code, String, :limit => 32 |
||||
apply_devise_schema :second_factor_attempts_count, Integer, :default => 0 |
||||
end |
||||
end |
||||
end |
@ -0,0 +1,3 @@
|
||||
module TwoFactorAuthentication |
||||
VERSION = "0.0.1" |
||||
end |
@ -0,0 +1,26 @@
|
||||
# -*- encoding: utf-8 -*- |
||||
$:.push File.expand_path("../lib", __FILE__) |
||||
require "two_factor_authentication/version" |
||||
|
||||
Gem::Specification.new do |s| |
||||
s.name = "two_factor_authentication" |
||||
s.version = TwoFactorAuthentication::VERSION |
||||
s.authors = ["Dmitrii Golub"] |
||||
s.email = ["dmitrii.golub@gmail.com"] |
||||
s.homepage = "" |
||||
s.summary = %q{Two factor authentication plugin for devise} |
||||
s.description = s.summary |
||||
|
||||
s.rubyforge_project = "two_factor_authentication" |
||||
|
||||
s.files = `git ls-files`.split("\n") |
||||
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") |
||||
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } |
||||
s.require_paths = ["lib"] |
||||
|
||||
s.add_runtime_dependency 'rails', '>= 3.1.1' |
||||
s.add_runtime_dependency 'devise' |
||||
s.add_runtime_dependency 'randexp' |
||||
|
||||
s.add_development_dependency 'bundler' |
||||
end |
Loading…
Reference in new issue