authlogic_motp 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/CHANGELOG.rdoc +3 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +20 -0
- data/Manifest.txt +12 -0
- data/README.rdoc +67 -0
- data/Rakefile +1 -0
- data/authlogic_motp.gemspec +27 -0
- data/init.rb +1 -0
- data/lib/authlogic_motp.rb +6 -0
- data/lib/authlogic_motp/acts_as_authentic.rb +18 -0
- data/lib/authlogic_motp/session.rb +138 -0
- data/lib/authlogic_motp/version.rb +3 -0
- data/rails/init.rb +1 -0
- metadata +72 -0
data/.gitignore
ADDED
data/CHANGELOG.rdoc
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
CHANGELOG.rdoc
|
2
|
+
MIT-LICENSE
|
3
|
+
Manifest.txt
|
4
|
+
README.rdoc
|
5
|
+
Rakefile
|
6
|
+
init.rb
|
7
|
+
lib/authlogic_motp.rb
|
8
|
+
lib/authlogic_motp/acts_as_authentic.rb
|
9
|
+
lib/authlogic_motp/session.rb
|
10
|
+
lib/authlogic_motp/version.rb
|
11
|
+
rails/init.rb
|
12
|
+
test/test_authlogic_motp.rb
|
data/README.rdoc
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
= Authlogic MOTP
|
2
|
+
|
3
|
+
Authlogic MOTP is an extension of the Authlogic library to add Mobile-OTP support.
|
4
|
+
|
5
|
+
== Helpful links
|
6
|
+
|
7
|
+
* <b>Mobile-OTP:</b> http://motp.sourceforge.net
|
8
|
+
* <b>Authlogic:</b> http://github.com/binarylogic/authlogic
|
9
|
+
|
10
|
+
== Requirements
|
11
|
+
|
12
|
+
authlogic_motp requires, of course, that authlogic is installed on your server.
|
13
|
+
It also assumes that registration of users (issuing/syncing secrets and PIN codes) will be handled by you.
|
14
|
+
|
15
|
+
== Install and use
|
16
|
+
|
17
|
+
=== 1. Install the Authlogic MOTP gem
|
18
|
+
|
19
|
+
$ sudo gem install authlogic_motp
|
20
|
+
|
21
|
+
Now add the gem dependency in your config:
|
22
|
+
|
23
|
+
Gemfile (Rails 3):
|
24
|
+
gem 'authlogic_motp'
|
25
|
+
|
26
|
+
config (Rails <3)
|
27
|
+
config.gem "authlogic_motp"
|
28
|
+
|
29
|
+
=== 2. Make some simple changes to your database:
|
30
|
+
|
31
|
+
class AddUsersMotpFields < ActiveRecord::Migration
|
32
|
+
def self.up
|
33
|
+
add_column :users, :motp_secret, :string
|
34
|
+
add_column :users, :motp_pin, :string
|
35
|
+
add_column :users, :motp_cache, :string
|
36
|
+
|
37
|
+
change_column :users, :crypted_password, :string, :default => nil, :null => true
|
38
|
+
change_column :users, :password_salt, :string, :default => nil, :null => true
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.down
|
42
|
+
remove_column :users, :motp_secret
|
43
|
+
remove_column :users, :motp_pin
|
44
|
+
remove_column :users, :motp_cache
|
45
|
+
|
46
|
+
[:crypted_password, :password_salt].each do |field|
|
47
|
+
User.all(:conditions => "#{field} is NULL").each { |user| user.update_attribute(field, "") if user.send(field).nil? }
|
48
|
+
change_column :users, field, :string, :default => "", :null => false
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
=== 2. Setup your views
|
54
|
+
|
55
|
+
authlogic-motp expects the login and password fields in your login form to be named "otp-login" and "otp-password".
|
56
|
+
The user should enter their usual login value, and then the OTP generated on their device for the password.
|
57
|
+
|
58
|
+
=== 3. Issue credentials
|
59
|
+
|
60
|
+
Each user will have to be issued a secret (in general a 16 character long HEX string), which they will use to initialize their account on the OTP device, and also a PIN (in general a 4 digit number) used to generate passwords. Some client programs allow the secret to be generated on the device. In this case the user will have to communicate both secret and pin to the administrator for registration.
|
61
|
+
|
62
|
+
These should be stored in :motp_secret and :motp_pin respectively.
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
Copyright (c) 2011 Martin Chandler, released under the MIT license
|
67
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "authlogic_motp/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "authlogic_motp"
|
7
|
+
s.version = AuthlogicMotp::VERSION
|
8
|
+
s.authors = ["Martin Chandler"]
|
9
|
+
s.email = ["browntigerz.lair@gmail.com"]
|
10
|
+
s.homepage = "http://github.com/browntiger/authlogic_motp"
|
11
|
+
s.summary = %q{Extension of the Authlogic library to add Mobile-OTP support.}
|
12
|
+
s.description = %q{Extension of the Authlogic library to add Mobile-OTP support.}
|
13
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
14
|
+
s.extra_rdoc_files = ["README.rdoc"]
|
15
|
+
|
16
|
+
# s.rubyforge_project = "authlogic_motp"
|
17
|
+
|
18
|
+
s.files = `git ls-files`.split("\n")
|
19
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
20
|
+
# s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
21
|
+
s.require_paths = ["lib"]
|
22
|
+
|
23
|
+
# specify any dependencies here; for example:
|
24
|
+
# s.add_development_dependency "rspec"
|
25
|
+
# s.add_runtime_dependency "rest-client"
|
26
|
+
s.add_runtime_dependency "authlogic"
|
27
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/rails/init.rb"
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module AuthlogicMotp
|
2
|
+
module ActsAsAuthentic
|
3
|
+
def self.included(klass)
|
4
|
+
klass.class_eval do
|
5
|
+
extend Config
|
6
|
+
add_acts_as_authentic_module(Methods, :prepend)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
module Config
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
module Methods
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
module AuthlogicMotp
|
3
|
+
module Session
|
4
|
+
def self.included(klass)
|
5
|
+
klass.class_eval do
|
6
|
+
extend Config
|
7
|
+
include Methods
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Config
|
12
|
+
def motp_maxperiod(value = nil)
|
13
|
+
rw_config(:motp_maxperiod_method, value, :motp_maxperiod)
|
14
|
+
end
|
15
|
+
alias_method :motp_maxperiod=, :motp_maxperiod
|
16
|
+
end
|
17
|
+
|
18
|
+
module Methods
|
19
|
+
def self.included(klass)
|
20
|
+
klass.class_eval do
|
21
|
+
attr_accessor :otp_login
|
22
|
+
attr_accessor :otp_password
|
23
|
+
|
24
|
+
validate :validate_by_otp, :if => :authenticating_with_otp?
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def credentials
|
29
|
+
if authenticating_with_otp?
|
30
|
+
details = {}
|
31
|
+
details[:otp_login] = send(login_field)
|
32
|
+
details[:otp_password] = '<protected>'
|
33
|
+
details
|
34
|
+
else
|
35
|
+
super
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def credentials=(value)
|
40
|
+
super
|
41
|
+
values = value.is_a?(Array) ? value : [value]
|
42
|
+
hash = values.first.is_a?(Hash) ? values.first.with_indifferent_access : nil
|
43
|
+
if !hash.nil?
|
44
|
+
self.otp_login = hash[:otp_login] if hash.key?(:otp_login)
|
45
|
+
self.otp_password = hash[:otp_password] if hash.key?(:otp_password)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
def authenticating_with_otp?
|
51
|
+
!otp_login.blank? || !otp_password.blank?
|
52
|
+
end
|
53
|
+
|
54
|
+
def motp_maxperiod
|
55
|
+
self.class.motp_maxperiod
|
56
|
+
end
|
57
|
+
|
58
|
+
def find_by_otp_login_method
|
59
|
+
self.class.find_by_login_method
|
60
|
+
end
|
61
|
+
|
62
|
+
def generalize_credentials_error_messages?
|
63
|
+
self.class.generalize_credentials_error_messages
|
64
|
+
end
|
65
|
+
|
66
|
+
def add_general_credentials_error
|
67
|
+
error_message =
|
68
|
+
if self.class.generalize_credentials_error_messages.is_a? String
|
69
|
+
self.class.generalize_credentials_error_messages
|
70
|
+
else
|
71
|
+
"Login credentials are not valid"
|
72
|
+
end
|
73
|
+
errors.add(:base, I18n.t('error_messages.general_credentials_error', :default => error_message))
|
74
|
+
end
|
75
|
+
|
76
|
+
def validate_by_otp
|
77
|
+
errors.add(:login, I18n.t('error_messages.login_blank', :default => 'cannot be blank')) if otp_login.blank?
|
78
|
+
errors.add(:password, I18n.t('error_messages.password_blank', :default => 'cannot be blank')) if otp_password.blank?
|
79
|
+
return if errors.count > 0
|
80
|
+
|
81
|
+
self.attempted_record = klass.send(find_by_otp_login_method, otp_login)
|
82
|
+
if attempted_record.blank?
|
83
|
+
generalize_credentials_error_messages? ?
|
84
|
+
add_general_credentials_error :
|
85
|
+
errors.add(:login, I18n.t('error_messages.login_not_found', :default => "does not exist"))
|
86
|
+
return
|
87
|
+
else
|
88
|
+
# don't allow otp that have been successfully used recently in the past
|
89
|
+
# even if it is within the legal time frame (otp does mean _one_ time password!)
|
90
|
+
old_otp = attempted_record.otp_cache.split(',') unless attempted_record.otp_cache.blank?
|
91
|
+
old_otp ||= []
|
92
|
+
otp_hash = Digest::MD5.hexdigest(otp_password)
|
93
|
+
if old_otp.include?(otp_hash)
|
94
|
+
generalize_credentials_error_messages? ?
|
95
|
+
add_general_credentials_error :
|
96
|
+
errors.add(:password, I18n.t('error_messages.password_invalid', :default => "is invalid"))
|
97
|
+
return
|
98
|
+
end
|
99
|
+
|
100
|
+
if CheckMOTP.validate(attempted_record.otp_secret, attempted_record.otp_pin, otp_password, motp_maxperiod)
|
101
|
+
# cache the otp so we can check against it next time
|
102
|
+
old_otp.pop if old_otp.length == 5
|
103
|
+
old_otp << otp_hash
|
104
|
+
attempted_record.update_attribute(:otp_cache, old_otp.join(","))
|
105
|
+
else
|
106
|
+
generalize_credentials_error_messages? ?
|
107
|
+
add_general_credentials_error :
|
108
|
+
errors.add(:password, I18n.t('error_messages.password_invalid', :default => "is invalid"))
|
109
|
+
return
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
class CheckMOTP
|
117
|
+
def initialize
|
118
|
+
@tmp_md5 = ''
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.validate(secret,pin,otp,period=3)
|
122
|
+
maxperiod = period * 60 # in seconds
|
123
|
+
time = Time.now.utc.to_i
|
124
|
+
((time - maxperiod)..(time + maxperiod)).each do |n|
|
125
|
+
md5 = generate_otp(n,secret,pin)
|
126
|
+
next if md5 == @tmp_md5
|
127
|
+
@tmp_md5 = md5
|
128
|
+
|
129
|
+
return true if md5.downcase == otp.chomp.downcase
|
130
|
+
end
|
131
|
+
false
|
132
|
+
end
|
133
|
+
|
134
|
+
def self.generate_otp(time,secret,pin)
|
135
|
+
Digest::MD5.hexdigest(time.to_s.chop << secret << pin.to_s)[0,6]
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'authlogic_motp'
|
metadata
ADDED
@@ -0,0 +1,72 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: authlogic_motp
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Martin Chandler
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: authlogic
|
16
|
+
requirement: &12154840 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *12154840
|
25
|
+
description: Extension of the Authlogic library to add Mobile-OTP support.
|
26
|
+
email:
|
27
|
+
- browntigerz.lair@gmail.com
|
28
|
+
executables: []
|
29
|
+
extensions: []
|
30
|
+
extra_rdoc_files:
|
31
|
+
- README.rdoc
|
32
|
+
files:
|
33
|
+
- .gitignore
|
34
|
+
- CHANGELOG.rdoc
|
35
|
+
- Gemfile
|
36
|
+
- MIT-LICENSE
|
37
|
+
- Manifest.txt
|
38
|
+
- README.rdoc
|
39
|
+
- Rakefile
|
40
|
+
- authlogic_motp.gemspec
|
41
|
+
- init.rb
|
42
|
+
- lib/authlogic_motp.rb
|
43
|
+
- lib/authlogic_motp/acts_as_authentic.rb
|
44
|
+
- lib/authlogic_motp/session.rb
|
45
|
+
- lib/authlogic_motp/version.rb
|
46
|
+
- rails/init.rb
|
47
|
+
homepage: http://github.com/browntiger/authlogic_motp
|
48
|
+
licenses: []
|
49
|
+
post_install_message:
|
50
|
+
rdoc_options:
|
51
|
+
- --charset=UTF-8
|
52
|
+
require_paths:
|
53
|
+
- lib
|
54
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ! '>='
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: '0'
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
requirements: []
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.8.12
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Extension of the Authlogic library to add Mobile-OTP support.
|
72
|
+
test_files: []
|