devise-otp 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +42 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +25 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +124 -0
  7. data/Rakefile +42 -0
  8. data/app/controllers/devise_otp/credentials_controller.rb +106 -0
  9. data/app/controllers/devise_otp/tokens_controller.rb +105 -0
  10. data/app/views/devise_otp/credentials/refresh.html.erb +20 -0
  11. data/app/views/devise_otp/credentials/show.html.erb +23 -0
  12. data/app/views/devise_otp/tokens/_token_secret.html.erb +17 -0
  13. data/app/views/devise_otp/tokens/recovery.html.erb +21 -0
  14. data/app/views/devise_otp/tokens/show.html.erb +31 -0
  15. data/config/locales/en.yml +66 -0
  16. data/devise-otp.gemspec +25 -0
  17. data/lib/devise-otp.rb +76 -0
  18. data/lib/devise-otp/version.rb +5 -0
  19. data/lib/devise_otp_authenticatable/controllers/helpers.rb +144 -0
  20. data/lib/devise_otp_authenticatable/controllers/url_helpers.rb +35 -0
  21. data/lib/devise_otp_authenticatable/engine.rb +23 -0
  22. data/lib/devise_otp_authenticatable/hooks.rb +13 -0
  23. data/lib/devise_otp_authenticatable/hooks/sessions.rb +57 -0
  24. data/lib/devise_otp_authenticatable/mapping.rb +19 -0
  25. data/lib/devise_otp_authenticatable/models/otp_authenticatable.rb +140 -0
  26. data/lib/devise_otp_authenticatable/routes.rb +30 -0
  27. data/lib/generators/active_record/devise_otp_generator.rb +13 -0
  28. data/lib/generators/active_record/templates/migration.rb +28 -0
  29. data/lib/generators/devise_otp/devise_otp_generator.rb +17 -0
  30. data/lib/generators/devise_otp/install_generator.rb +31 -0
  31. data/lib/generators/devise_otp/views_generator.rb +19 -0
  32. data/test/dummy/README.rdoc +261 -0
  33. data/test/dummy/Rakefile +7 -0
  34. data/test/dummy/app/assets/javascripts/application.js +13 -0
  35. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  36. data/test/dummy/app/controllers/application_controller.rb +4 -0
  37. data/test/dummy/app/controllers/posts_controller.rb +83 -0
  38. data/test/dummy/app/helpers/application_helper.rb +2 -0
  39. data/test/dummy/app/helpers/posts_helper.rb +2 -0
  40. data/test/dummy/app/mailers/.gitkeep +0 -0
  41. data/test/dummy/app/models/post.rb +2 -0
  42. data/test/dummy/app/models/user.rb +20 -0
  43. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  44. data/test/dummy/app/views/posts/_form.html.erb +25 -0
  45. data/test/dummy/app/views/posts/edit.html.erb +6 -0
  46. data/test/dummy/app/views/posts/index.html.erb +25 -0
  47. data/test/dummy/app/views/posts/new.html.erb +5 -0
  48. data/test/dummy/app/views/posts/show.html.erb +15 -0
  49. data/test/dummy/config.ru +4 -0
  50. data/test/dummy/config/application.rb +68 -0
  51. data/test/dummy/config/boot.rb +10 -0
  52. data/test/dummy/config/database.yml +25 -0
  53. data/test/dummy/config/environment.rb +5 -0
  54. data/test/dummy/config/environments/development.rb +37 -0
  55. data/test/dummy/config/environments/production.rb +73 -0
  56. data/test/dummy/config/environments/test.rb +36 -0
  57. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  58. data/test/dummy/config/initializers/devise.rb +253 -0
  59. data/test/dummy/config/initializers/inflections.rb +15 -0
  60. data/test/dummy/config/initializers/mime_types.rb +5 -0
  61. data/test/dummy/config/initializers/secret_token.rb +8 -0
  62. data/test/dummy/config/initializers/session_store.rb +8 -0
  63. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  64. data/test/dummy/config/locales/en.yml +5 -0
  65. data/test/dummy/config/routes.rb +6 -0
  66. data/test/dummy/db/migrate/20130125101430_create_users.rb +9 -0
  67. data/test/dummy/db/migrate/20130131092406_add_devise_to_users.rb +53 -0
  68. data/test/dummy/db/migrate/20130131142320_create_posts.rb +10 -0
  69. data/test/dummy/db/migrate/20130131160351_devise_otp_add_to_users.rb +28 -0
  70. data/test/dummy/lib/assets/.gitkeep +0 -0
  71. data/test/dummy/public/404.html +26 -0
  72. data/test/dummy/public/422.html +26 -0
  73. data/test/dummy/public/500.html +25 -0
  74. data/test/dummy/public/favicon.ico +0 -0
  75. data/test/dummy/script/rails +6 -0
  76. data/test/integration/refresh_test.rb +92 -0
  77. data/test/integration/sign_in_test.rb +77 -0
  78. data/test/integration_tests_helper.rb +48 -0
  79. data/test/model_tests_helper.rb +22 -0
  80. data/test/models/otp_authenticatable_test.rb +116 -0
  81. data/test/orm/active_record.rb +4 -0
  82. data/test/test_helper.rb +19 -0
  83. metadata +237 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: e3ae46900992f2693640f098161f369f5138706d
4
+ data.tar.gz: 9fe62fcb21f0faf7be427ee3e4f5e0c791fd2366
5
+ SHA512:
6
+ metadata.gz: a3115df085ed880dd71795041c2650f69e830a1626173a196ea200c08af0e03a2b4b44d703d164a05f27dea21ba5c9a4062152fd178f91a7859d659d4ba32850
7
+ data.tar.gz: bd1fa3d6ddbb5535d640ba6b561ad85e84dd429a232e9ed5752958a8bb9402313cff5137c42f659bfc3093b2470e244641db7e32f32522f378011123636ca074
@@ -0,0 +1,42 @@
1
+ ## RubyMine
2
+ .idea
3
+
4
+ ## MAC OS
5
+ .DS_Store
6
+
7
+ ## TEXTMATE
8
+ *.tmproj
9
+ tmtags
10
+
11
+ ## EMACS
12
+ *~
13
+ \#*
14
+ .\#*
15
+
16
+ ## VIM
17
+ *.swp
18
+
19
+ *.gem
20
+ *.rbc
21
+ .bundle
22
+ .config
23
+ .yardoc
24
+
25
+ ## PROJECT::GENERAL
26
+ _yardoc
27
+ doc/
28
+ coverage
29
+ rdoc
30
+ pkg
31
+ spec/reports
32
+ lib/bundler/man
33
+
34
+ ## PROJECT::SPECIFIC
35
+ test/dummy/log/**
36
+ test/dummy/tmp/**
37
+ test/dummy/db/*.sqlite3
38
+
39
+ Gemfile.lock
40
+
41
+ # Generated test files
42
+ tmp/*
@@ -0,0 +1,11 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - rbx-19mode
6
+ - rbx-20mode
7
+ script: rake test
8
+ env:
9
+ - DEVISE_ORM=active_record
10
+ matrix:
11
+ allow_failures:
data/Gemfile ADDED
@@ -0,0 +1,25 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in devise-otp.gemspec
4
+ gemspec
5
+
6
+ gem "rdoc"
7
+
8
+ group :test do
9
+ platforms :jruby do
10
+ gem 'activerecord-jdbcsqlite3-adapter'
11
+ end
12
+
13
+ platforms :ruby do
14
+ gem "sqlite3"
15
+ end
16
+
17
+ gem "rails", "~> 4.0.0"
18
+
19
+ gem "capybara"
20
+ gem 'shoulda'
21
+ gem 'selenium-webdriver'
22
+
23
+ #gem 'factory_girl_rails', '~> 1.2'
24
+ #gem 'rspec-rails', '~> 2.6.0'
25
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Lele Forzani
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,124 @@
1
+ # Devise::Otp
2
+ [![Build Status](https://travis-ci.org/wmlele/devise-otp.png?branch=master)](https://travis-ci.org/wmlele/devise-otp)
3
+
4
+ Devise OTP implements two-factors authentication for Devise, using an rfc6238 compatible Time-Based One-Time Password Algorithm.
5
+ It uses the [rotp library](https://github.com/mdp/rotp) for generation and verification of codes.
6
+
7
+ It currently has the following features:
8
+
9
+ * Url based provisioning of token devices, compatible with **Google Authenticator**.
10
+ * Two factors authentication can be **optional** at user discretion, **recommended** (it nags the user on every sign-in) or **mandatory** (users must enroll OTP after signing-in next time, before they can navigate the site). The settings is global, or per-user.
11
+ * Optionally, users can obtain a list of HOTP recovery tokens to be used for emergency log-in in case the token device is lost or unavailable.
12
+
13
+ Compatible token devices are:
14
+
15
+ * [Google Authenticator](https://code.google.com/p/google-authenticator/)
16
+ * ...
17
+
18
+ ## Quick overview of Two Factors Authentication, using OTPs.
19
+
20
+ * A shared secret is generated on the server, and stored both on the token device (ie: the phone) and the server itself.
21
+ * The secret is used to generate short numerical tokens that are either time or sequence based.
22
+ * Tokens can be generated on a phone without internet connectivity.
23
+ * The token provides an additional layer of security against password theft.
24
+ * OTP's should always be used as a second factor of authentication(if your phone is lost, you account is still secured with a password)
25
+ * Google Authenticator allows you to store multiple OTP secrets and provision those using a QR Code
26
+
27
+ Although there's an adjustable drift window, it is important that both the server and the token device (phone) have their clocks set (eg: using NTP).
28
+
29
+
30
+ ## Installation
31
+
32
+ Add this line to your application's Gemfile:
33
+
34
+ gem 'devise'
35
+ gem 'devise-otp'
36
+
37
+ And then execute:
38
+
39
+ $ bundle
40
+
41
+ Or install it yourself as:
42
+
43
+ $ gem install devise-otp
44
+
45
+
46
+ ### Devise Installation
47
+
48
+ To setup Devise, you need to do the following (but refer to https://github.com/plataformatec/devise for more information)
49
+
50
+ Install Devise:
51
+
52
+ rails g devise:install
53
+
54
+ Setup the User or Admin model
55
+
56
+ rails g devise MODEL
57
+
58
+ Configure your app for authorisation, edit your Controller and add this before_filter:
59
+
60
+ before_filter :authenticate_user!
61
+
62
+ Make sure your "root" route is configured in config/routes.rb
63
+
64
+ ### Automatic Installation
65
+
66
+ Run the following generator to add the necessary configuration options to Devise's config file:
67
+
68
+ rails g devise_otp:install
69
+
70
+ After you've created your Devise user models (which is usually done with a "rails g devise MODEL"), set up your Devise OTP additions:
71
+
72
+ rails g devise_otp MODEL
73
+
74
+ Don't forget to migrate:
75
+
76
+ rake db:migrate
77
+
78
+ ### Custom Views
79
+
80
+ If you want to customise your views (which you likely will want to), you can use the generator:
81
+
82
+ rails g devise_otp:views
83
+
84
+ ### I18n
85
+
86
+ The install generator also installs an english copy of a Devise OTP i18n file. This can be modified (or used to create other language versions) and is located at: _config/locales/devise.otp.en.yml_
87
+
88
+
89
+ ## Usage
90
+
91
+ With this extension enabled, the following is expected behaviour:
92
+
93
+ * Users may go to _/MODEL/otp/token_ and enable their OTP state, they might be asked to provide their password again (and OTP token, if it's enabled)
94
+ * Once enabled they're shown an alphanumeric code (for manual provisioning) and a QR code, for automatic provisioning of their authetication device (for instance, Google Authenticator)
95
+ * If config.otp_mandatory or model_instance.otp_mandatory, users will be required to enable, and provision, next time they successfully sign-in.
96
+
97
+
98
+ ### Configuration Options
99
+
100
+ The install generator adds some options to the end of your Devise config file (config/initializers/devise.rb)
101
+
102
+ * config.otp_mandatory - OTP is mandatory, users are going to be asked to enroll the next time they sign in, before they can successfully complete the session establishment.
103
+ * config.otp_authentication_timeout - how long the user has to authenticate with their token. (defaults to 3.minutes)s
104
+ * config.otp_drift_window - a window which provides allowance for drift between a user's token device clock (and therefore their OTP tokens) and the authentication server's clock. (default: 3)
105
+ * config.otp_credentials_refresh - Users that have logged in longer than this time ago, or haven't refreshed, are boing to be asked their password (and an OTP token, if enabled) before they can see or change their otp informations. (defaults to 15.minutes)
106
+ * config.recovery_tokens - Whether the users are given a list of one-time recovery tokens, for emergency access (default: true)
107
+ * config.otp_uri_application - The name of this application, to be added to the provisioning url as '<user_email>/application_name' (defaults to the Rails application class)
108
+
109
+ ## Contributing
110
+
111
+ 1. Fork it
112
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
113
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
114
+ 4. Push to the branch (`git push origin my-new-feature`)
115
+ 5. Create new Pull Request
116
+
117
+ ## Thanks
118
+
119
+ I started this extension by forking [devise_google_authenticator](https://github.com/AsteriskLabs/devise_google_authenticator), and this project still contains some chunk of code from it, esp. in the tests and generators.
120
+ At some point, my design goals were significantly diverging, so I refactored most of its code. Still, I want to thank the original author for his relevant contribution.
121
+
122
+ ## License
123
+
124
+ MIT Licensed
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'Foobar'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+ Bundler::GemHelper.install_tasks
25
+
26
+ require 'rake/testtask'
27
+ Rake::TestTask.new(:test) do |test|
28
+ test.libs << 'lib' << 'test'
29
+ test.pattern = 'test/**/*_test.rb'
30
+ test.verbose = true
31
+ end
32
+
33
+ desc 'Run Devise tests for all ORMs.'
34
+ task :tests do
35
+ Dir[File.join(File.dirname(__FILE__), 'test', 'orm', '*.rb')].each do |file|
36
+ orm = File.basename(file).split(".").first
37
+ system "rake test DEVISE_ORM=#{orm}"
38
+ end
39
+ end
40
+
41
+ desc 'Default: run tests for all ORMs.'
42
+ task :default => :tests
@@ -0,0 +1,106 @@
1
+ class DeviseOtp::CredentialsController < DeviseController
2
+ helper_method :new_session_path
3
+
4
+ prepend_before_filter :authenticate_scope!, :only => [:get_refresh, :set_refresh]
5
+ prepend_before_filter :require_no_authentication, :only => [ :show, :update ]
6
+
7
+ #
8
+ # show a request for the OTP token
9
+ #
10
+ def show
11
+ @challenge = params[:challenge]
12
+ @recovery = (params[:recovery] == 'true') && recovery_enabled?
13
+
14
+ if @challenge.nil?
15
+ redirect_to :root
16
+
17
+ else
18
+ self.resource = resource_class.find_valid_otp_challenge(@challenge)
19
+ if resource.nil?
20
+ redirect_to :root
21
+ elsif @recovery
22
+ @recovery_count = resource.otp_recovery_counter
23
+ render :show
24
+ else
25
+ render :show
26
+ end
27
+ end
28
+ end
29
+
30
+ #
31
+ # signs the resource in, if the OTP token is valid and the user has a valid challenge
32
+ #
33
+ def update
34
+
35
+ resource = resource_class.find_valid_otp_challenge(params[resource_name][:challenge])
36
+ recovery = (params[resource_name][:recovery] == 'true') && recovery_enabled?
37
+ token = params[resource_name][:token]
38
+
39
+ if token.blank?
40
+ otp_set_flash_message(:alert, :token_blank)
41
+ redirect_to otp_credential_path_for(resource_name, :challenge => params[resource_name][:challenge],
42
+ :recovery => recovery)
43
+ elsif resource.nil?
44
+ otp_set_flash_message(:alert, :otp_session_invalid)
45
+ redirect_to new_session_path(resource_name)
46
+ else
47
+ if resource.otp_challenge_valid? && resource.validate_otp_token(params[resource_name][:token], recovery)
48
+ set_flash_message(:success, :signed_in) if is_navigational_format?
49
+ sign_in(resource_name, resource)
50
+
51
+ otp_refresh_credentials_for(resource)
52
+ respond_with resource, :location => after_sign_in_path_for(resource)
53
+ else
54
+ otp_set_flash_message :alert, :token_invalid
55
+ redirect_to new_session_path(resource_name)
56
+ end
57
+ end
58
+ end
59
+
60
+
61
+ #
62
+ # displays the request for a credentials refresh
63
+ #
64
+ def get_refresh
65
+ ensure_resource!
66
+ render :refresh
67
+ end
68
+
69
+ #
70
+ # lets the user through is the refresh is valid
71
+ #
72
+ def set_refresh
73
+
74
+ ensure_resource!
75
+ # I am sure there's a much better way
76
+ if resource.valid_password?(params[resource_name][:refresh_password])
77
+ if resource.otp_enabled?
78
+ if resource.validate_otp_token(params[resource_name][:token].to_i)
79
+ done_valid_refresh
80
+ else
81
+ failed_refresh
82
+ end
83
+ else
84
+ done_valid_refresh
85
+ end
86
+ else
87
+ failed_refresh
88
+ end
89
+ end
90
+
91
+
92
+ private
93
+
94
+ def done_valid_refresh
95
+ otp_refresh_credentials_for(resource)
96
+ otp_set_flash_message :success, :valid_refresh if is_navigational_format?
97
+
98
+ respond_with resource, :location => otp_fetch_refresh_return_url
99
+ end
100
+
101
+ def failed_refresh
102
+ otp_set_flash_message :alert, :invalid_refresh
103
+ render :refresh
104
+ end
105
+
106
+ end
@@ -0,0 +1,105 @@
1
+ class DeviseOtp::TokensController < DeviseController
2
+ include Devise::Controllers::Helpers
3
+
4
+ prepend_before_filter :ensure_credentials_refresh
5
+ prepend_before_filter :authenticate_scope!
6
+
7
+ #protect_from_forgery :except => [:clear_persistence, :delete_persistence]
8
+
9
+ #
10
+ # Displays the status of OTP authentication
11
+ #
12
+ def show
13
+ if resource.nil?
14
+ redirect_to stored_location_for(scope) || :root
15
+ else
16
+ render :show
17
+ end
18
+ end
19
+
20
+ #
21
+ # Updates the status of OTP authentication
22
+ #
23
+ def update
24
+ #if resource.update_without_password(params[resource_name])
25
+ if resource.update_attribute(:otp_enabled, params[resource_name][:otp_enabled])
26
+
27
+ otp_set_flash_message :success, :successfully_updated
28
+ render :show
29
+ else
30
+ render :show
31
+ end
32
+ end
33
+
34
+ #
35
+ # Resets OTP authentication, generates new credentials, sets it to off
36
+ #
37
+ def destroy
38
+
39
+ if resource.reset_otp_credentials!
40
+ otp_set_flash_message :success, :successfully_reset_creds
41
+ end
42
+ render :show
43
+ end
44
+
45
+
46
+ #
47
+ # makes the current browser persistent
48
+ #
49
+ def get_persistence
50
+
51
+
52
+ if otp_set_trusted_device_for(resource)
53
+ otp_set_flash_message :success, :successfully_set_persistence
54
+ end
55
+ redirect_to :action => :show
56
+ end
57
+
58
+
59
+ #
60
+ # clears persistence for the current browser
61
+ #
62
+ def clear_persistence
63
+ if otp_clear_trusted_device_for(resource)
64
+ otp_set_flash_message :success, :successfully_cleared_persistence
65
+ end
66
+
67
+ redirect_to :action => :show
68
+ end
69
+
70
+
71
+ #
72
+ # rehash the persistence secret, thus, making all the persistence cookies invalid
73
+ #
74
+ def delete_persistence
75
+ if otp_reset_persistence_for(resource)
76
+ otp_set_flash_message :notice, :successfully_reset_persistence
77
+ end
78
+
79
+ redirect_to :action => :show
80
+ end
81
+
82
+ #
83
+ #
84
+ #
85
+ def recovery
86
+ render :recovery
87
+ end
88
+
89
+ private
90
+
91
+ def ensure_credentials_refresh
92
+
93
+ ensure_resource!
94
+ if needs_credentials_refresh?(resource)
95
+ otp_set_flash_message :notice, :need_to_refresh_credentials
96
+ redirect_to refresh_otp_credential_path_for(resource)
97
+ end
98
+ end
99
+
100
+ def scope
101
+ resource_name.to_sym
102
+ end
103
+
104
+
105
+ end