devise-secure-password 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/CODE_OF_CONDUCT.md +74 -0
  3. data/Changelog.md +98 -0
  4. data/Dockerfile +44 -0
  5. data/Dockerfile.prev +44 -0
  6. data/Gemfile +13 -0
  7. data/Gemfile.lock +272 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +382 -0
  10. data/Rakefile +11 -0
  11. data/app/controllers/devise/passwords_with_policy_controller.rb +93 -0
  12. data/app/views/devise/passwords_with_policy/edit.html.erb +20 -0
  13. data/bin/console +14 -0
  14. data/bin/setup +6 -0
  15. data/config/locales/en.yml +84 -0
  16. data/devise-secure-password.gemspec +57 -0
  17. data/docker-entrypoint.sh +6 -0
  18. data/gemfiles/rails_5_1.gemfile +21 -0
  19. data/gemfiles/rails_5_2.gemfile +22 -0
  20. data/lib/devise/secure_password.rb +71 -0
  21. data/lib/devise/secure_password/controllers/devise_helpers.rb +23 -0
  22. data/lib/devise/secure_password/controllers/helpers.rb +58 -0
  23. data/lib/devise/secure_password/grammar.rb +13 -0
  24. data/lib/devise/secure_password/models/password_disallows_frequent_changes.rb +62 -0
  25. data/lib/devise/secure_password/models/password_disallows_frequent_reuse.rb +73 -0
  26. data/lib/devise/secure_password/models/password_has_required_content.rb +170 -0
  27. data/lib/devise/secure_password/models/password_requires_regular_updates.rb +54 -0
  28. data/lib/devise/secure_password/models/previous_password.rb +20 -0
  29. data/lib/devise/secure_password/routes.rb +11 -0
  30. data/lib/devise/secure_password/version.rb +5 -0
  31. data/lib/generators/devise/secure_password/install_generator.rb +30 -0
  32. data/lib/generators/devise/templates/README.txt +21 -0
  33. data/lib/generators/devise/templates/secure_password.rb +43 -0
  34. data/lib/support/string/character_counter.rb +55 -0
  35. data/pkg/devise-secure-password-1.1.1.gem +0 -0
  36. metadata +429 -0
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017-2018, Valimail, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,382 @@
1
+ # Devise Secure Password Extension
2
+
3
+ [![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](#license)
4
+
5
+ The __Devise Secure Password Extension__ is a user account password policy enforcement gem that can be
6
+ added to a Rails project to enforce password policies. The gem is implemented as an extension to the Rails
7
+ [devise](https://github.com/plataformatec/devise) authentication solution gem and requires that __devise__ is installed
8
+ as well.
9
+
10
+ ## Build Status
11
+
12
+ | Service | rails 5.2 |
13
+ |:-----------|:-----------:|
14
+ | Circle CI | [![Circle CI](https://circleci.com/gh/ValiMail/devise-secure_password/tree/master.svg?style=shield&circle-token=cd173d5f9d2944a9b14737c2d4339b20b08565cf)]() |
15
+
16
+ ## Overview
17
+
18
+ The __Devise Secure Password Extension__ is composed of the following modules:
19
+
20
+ - __password_has_required_content__: require that passwords consist of a specific number (configurable) of letters,
21
+ numbers, and special characters (symbols)
22
+ - __password_disallows_frequent_reuse__: prevent the reuse of a number (configurable) of previous passwords when a user
23
+ changes their password
24
+ - __password_disallows_frequent_changes__: prevent the user from changing their password more than once within a time
25
+ duration (configurable)
26
+ - __password_requires_regular_updates__: require that a user change their password following a time duration
27
+ (configurable)
28
+
29
+ ## Compatibility
30
+
31
+ The goal of this project is to provide compatibility for officially supported stable releases of [Ruby](https://www.ruby-lang.org/en/downloads/)
32
+ and [Ruby on Rails](http://guides.rubyonrails.org/maintenance_policy.html). More specifically, the following releases
33
+ are currently supported by the __Devise Secure Password Extension__:
34
+
35
+ - Ruby on Rails: __5.2.Z__, __5.1.Z__ (current and previous stable release)
36
+ - Ruby: __2.5.1__, __2.4.4__ (current and previous stable release)
37
+
38
+ ## Installation
39
+
40
+ Add this line to your application's Gemfile:
41
+
42
+ ```ruby
43
+ gem 'devise', '~> 4.2'
44
+ gem 'devise-secure_password', '~> 1.0.5'
45
+ ```
46
+
47
+ And then execute:
48
+
49
+ ```shell
50
+ prompt> bundle
51
+ ```
52
+
53
+ Or install it yourself as:
54
+
55
+ ```shell
56
+ prompt> gem install devise-secure_password
57
+ ```
58
+
59
+ Finally, run the generator:
60
+
61
+ ```shell
62
+ prompt> rails generate devise:secure_password:install
63
+ ```
64
+
65
+ ## Usage
66
+
67
+ ### Configuration
68
+
69
+ The __Devise Secure Password Extension__ exposes configuration parameters as outlined below. Commented out configuration
70
+ parameters reflect the default settings.
71
+
72
+ ```ruby
73
+ Devise.setup do |config|
74
+ # ==> Configuration for the Devise Secure Password extension
75
+ # Module: password_has_required_content
76
+ #
77
+ # Configure password content requirements including the number of uppercase,
78
+ # lowercase, number, and special characters that are required. To configure the
79
+ # minimum and maximum length refer to the Devise config.password_length
80
+ # standard configuration parameter.
81
+
82
+ # The number of uppercase letters (latin A-Z) required in a password:
83
+ # config.password_required_uppercase_count = 1
84
+
85
+ # The number of lowercase letters (latin A-Z) required in a password:
86
+ # config.password_required_lowercase_count = 1
87
+
88
+ # The number of numbers (0-9) required in a password:
89
+ # config.password_required_number_count = 1
90
+
91
+ # The number of special characters (!@#$%^&*()_+-=[]{}|') required in a password:
92
+ # config.password_required_special_character_count = 1
93
+
94
+ # ==> Configuration for the Devise Secure Password extension
95
+ # Module: password_disallows_frequent_reuse
96
+ #
97
+ # The number of previously used passwords that can not be reused:
98
+ # config.password_previously_used_count = 8
99
+
100
+ # ==> Configuration for the Devise Secure Password extension
101
+ # Module: password_disallows_frequent_changes
102
+ # *Requires* password_disallows_frequent_reuse
103
+ #
104
+ # The minimum time that must pass between password changes:
105
+ # config.password_minimum_age = 1.days
106
+
107
+ # ==> Configuration for the Devise Secure Password extension
108
+ # Module: password_requires_regular_updates
109
+ # *Requires* password_disallows_frequent_reuse
110
+ #
111
+ # The maximum allowed age of a password:
112
+ # config.password_maximum_age = 180.days
113
+ end
114
+ ```
115
+
116
+ >NOTE: Password policy defaults have been selected as a middle-of-the-road combination based on published
117
+ recommendations by [Microsoft](https://technet.microsoft.com/en-us/library/ff741764.aspx) and
118
+ [Carnegie Mellon University](https://www.cmu.edu/iso/governance/guidelines/password-management.html). It is up to
119
+ __YOU__ to verify the default settings and make adjustments where necessary.
120
+
121
+ Enable the __Devise Secure Password Extension__ enforcement in your Devise model(s):
122
+
123
+ ```ruby
124
+ devise :password_has_required_content, :password_disallows_frequent_reuse,
125
+ :password_disallows_frequent_changes, :password_requires_regular_updates
126
+ ```
127
+
128
+ Usually, you would append these after your selection of Devise modules. So your configuration will more likely look like
129
+ the following:
130
+
131
+ ```ruby
132
+ class User < ApplicationRecord
133
+ # Include default devise modules. Others available are:
134
+ # :confirmable, :lockable, :timeoutable and :omniauthable
135
+ devise :database_authenticatable, :registerable,
136
+ :recoverable, :rememberable, :trackable, :validatable,
137
+ :password_has_required_content, :password_disallows_frequent_reuse,
138
+ :password_disallows_frequent_changes, :password_requires_regular_updates
139
+ ...
140
+ <YOUR USER MODEL CONTENT FOLLOWS>
141
+ end
142
+ ```
143
+
144
+ >NOTE: Both `:password_disallows_frequent_changes` and `:password_requires_regular_updates` are dependent upon the
145
+ previous passwords memorization implemented by the `:password_disallows_frequent_reuse` module.
146
+
147
+ ### Database migration
148
+
149
+ The following database migration needs to be applied:
150
+
151
+ ```shell
152
+ prompt> rails generate migration create_previous_passwords salt:string encrypted_password:string user:references
153
+ ```
154
+
155
+ Edit the resulting file to disallow null values for the hash,add indexes for both hash and user_id fields, and to also
156
+ add the timestamp (created_at, updated_at) fields:
157
+
158
+ ```ruby
159
+ class CreatePreviousPasswords < ActiveRecord::Migration[5.1]
160
+ def change
161
+ create_table :previous_passwords do |t|
162
+ t.string :salt, null: false
163
+ t.string :encrypted_password, null: false
164
+ t.references :user, foreign_key: true
165
+
166
+ t.timestamps
167
+ end
168
+
169
+ add_index :previous_passwords, :encrypted_password
170
+ add_index :previous_passwords, [:user_id, :created_at]
171
+ end
172
+ end
173
+
174
+ ```
175
+
176
+ And then:
177
+
178
+ ```shell
179
+ prompt> bundle exec rake db:migrate
180
+ ```
181
+
182
+ ### Displaying errors
183
+
184
+ You will likely want to display errors, produced as a result of secure password enforcement violations, to your users.
185
+ Errors are available via the `User.errors` array and via the `devise_error_messages!` method. An example usage follows
186
+ and is taken from the default password `edit.html.erb` page:
187
+
188
+ ```erb
189
+ <%= form_for(resource, as: resource_name, url: [resource_name, :password_with_policy], html: { method: :put }) do |f| %>
190
+ <% if resource.errors.full_messages.count.positive? %>
191
+ <%= devise_error_messages! %>
192
+ <% end %>
193
+
194
+ <p><%= f.label :current_password, 'Current password' %><br />
195
+ <%= f.password_field :current_password %></p>
196
+
197
+ <p><%= f.label :password, 'New password' %><br />
198
+ <%= f.password_field :password %></p>
199
+
200
+ <p><%= f.label :password_confirmation, 'Password confirmation' %><br />
201
+ <%= f.password_field :password_confirmation %></p>
202
+
203
+ <p><%= f.submit 'Update' %></p>
204
+ <% end %>
205
+ ```
206
+
207
+ <a name="running-tests"></a>
208
+
209
+ ## Running Tests
210
+
211
+ This document assumes that you already have a [functioning ruby install](https://rvm.io/).
212
+
213
+ ### Default Rails target
214
+
215
+ The __Devise Secure Password Extension__ provides compatibility for officially supported stable releases of Ruby on
216
+ Rails. To configure and test the default target (the most-recent supported Rails release):
217
+
218
+ ```bash
219
+ prompt> bundle
220
+ prompt> bundle exec rake
221
+ ```
222
+
223
+ ### Selecting an alternate Rails target
224
+
225
+ To determine the Ruby on Rails versions supported by this release, run the following commands:
226
+
227
+ ```bash
228
+ prompt> gem install flay ruby2ruby rubocop rspec
229
+ prompt> rake test:spec:targets
230
+
231
+ Available Rails targets: 5.1, 5.2
232
+ ```
233
+
234
+ Reconfigure the project by specifying the correct Gemfile when running bundler, followed by running tests:
235
+
236
+ ```bash
237
+ prompt> BUNDLE_GEMFILE=gemfiles/rails_5_2.gemfile bundle
238
+ prompt> BUNDLE_GEMFILE=gemfiles/rails_5_2.gemfile bundle exec rake
239
+ ```
240
+
241
+ The only time you need to define the `BUNDLE_GEMFILE` environment variable is when testing a non-default target.
242
+
243
+ ### Testing with code coverage (SimpleCov)
244
+
245
+ SimpleCov tests are enabled by defining the `test:spec:coverage` rake task:
246
+
247
+ ```bash
248
+ prompt> bundle exec rake test:spec:coverage
249
+ ```
250
+
251
+ A brief summary will be output at the end of the run but a more extensive eport will be saved in the `coverage`
252
+ directory (under the top-level project directory).
253
+
254
+ ### Testing with headless Chrome
255
+
256
+ You will need to install the [ChromeDriver >= v2.3.4](https://sites.google.com/a/chromium.org/chromedriver/downloads)
257
+ for testing.
258
+
259
+ ```bash
260
+ prompt> brew install chromedriver
261
+ ```
262
+
263
+ >NOTE: __ChromeDriver__ < 2.33 has a bug for testing clickable targets; therefore, install >= 2.3.4.
264
+
265
+ You can always install [ChromeDriver](https://sites.google.com/a/chromium.org/chromedriver/) by downloading and then
266
+ unpacking into the `/usr/local/bin` directory.
267
+
268
+ #### Automated screenshots on failure
269
+
270
+ The [capybara-screenshot gem](https://github.com/mattheworiordan/capybara-screenshot) supports automated screenshot
271
+ captures on failing tests but this will only take place for tests that have JavaScript enabled. You can temporarily
272
+ modify an example by setting `js: true` as in the following example:
273
+
274
+ ```ruby
275
+ context 'when minimum age enforcement is enabled', js: true do
276
+ ...
277
+ end
278
+ ```
279
+
280
+ Do not submit pull requests with this setting enabled where it wasn't enabled previously.
281
+
282
+ ### Testing inside the spec/rails-app-X_y_z
283
+
284
+ To debug from inside of the dummy rails-app you will need to first install the rails bin stubs and then perform a db
285
+ migration:
286
+
287
+ ```bash
288
+ prompt> cd spec/rails-app-X_y_z
289
+ prompt> rake app:update:bin
290
+ prompt> RAILS_ENV=development bundle exec rake db:migrate
291
+ ```
292
+
293
+ Remember, the dummy app is not meant to be a full featured rails app: there is just enough functionality to test the
294
+ gem feature set.
295
+
296
+ ### Running benchmarks
297
+
298
+ Available benchmarks can be run as follows:
299
+
300
+ ```bash
301
+ prompt> bundle exec rake test:benchmark
302
+ ```
303
+
304
+ Benchmarks are run within an RSpec context but are not run along with other tests as benchmarks merely seek to measure
305
+ performance and not enforce set performance targets.
306
+
307
+ ### Screenshots
308
+
309
+ Failing tests that invoke the JavaScript driver will result in both the failing html along with a screenshot of the
310
+ page output to be saved in the `spec/rails-app-X_y_z/tmp/capybara` snapshot directory.
311
+
312
+ >NOTE: On __circleci__ the snapshots will be captured as artifacts.
313
+
314
+ The snapshot directory will be pruned automatically between runs.
315
+
316
+ ## Docker
317
+
318
+ This repository includes a [Dockerfile](https://docs.docker.com/engine/reference/builder/) to facilitate testing in and
319
+ using [Docker](https://www.docker.com/).
320
+
321
+ To start the container simply build and launch the image:
322
+
323
+ ```bash
324
+ prompt> docker build -t secure-password-dev .
325
+ prompt> docker run -it --rm secure-password-dev /bin/bash
326
+ ```
327
+
328
+ The above `docker run` command will start the container, connect you to the command line within the project home
329
+ directory where you can issue the tests as documented in the [Running Tests](#running-tests) section above. When you exit
330
+ the shell, the container will be removed.
331
+
332
+ ### Running tests in a Docker container
333
+
334
+ The Docker container is derived from the latest [circleci/ruby](https://hub.docker.com/r/circleci/ruby/) image. It is
335
+ critical that you update the bundler inside of the Docker image as the `circleci` user (i.e. the default user) before
336
+ initiating any development work including tests.
337
+
338
+ ```bash
339
+ prompt> gem update bundler
340
+ ```
341
+
342
+ #### Updating test.sqlite3.db
343
+
344
+ To update or generate a `db/test/sqlite3.db` database file:
345
+
346
+ ```bash
347
+ prompt> cd spec/rails-app-X_y_z
348
+ prompt> bundle install
349
+ prompt> rake app:update:bin
350
+ prompt> RAILS_ENV=test bundle exec rake db:migrate
351
+ ```
352
+
353
+ ## Contributing
354
+
355
+ Bug reports and pull requests are welcome on GitHub at https://github.com/valimail/devise-secure_password. This project
356
+ is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the
357
+ [Contributor Covenant](http://contributor-covenant.org) code of conduct.
358
+
359
+ ### Basic guidelines for contributors
360
+
361
+ 1 Fork it
362
+
363
+ 2 Create your feature branch (`git checkout -b my-new-feature`)
364
+
365
+ 3 Commit your changes (`git commit -am 'Add some feature'`)
366
+
367
+ 4 Push to the branch (`git push origin my-new-feature`)
368
+
369
+ 5 Create new Pull Request
370
+
371
+ >NOTE: Contributions should always be based on the `master` branch. You may be asked to [rebase](https://git-scm.com/docs/git-rebase)
372
+ your contributions on the tip of the `master` branch, this is normal and is to be expected if the `master` branch has
373
+ moved ahead since your pull request was opened, discussed, and accepted.
374
+
375
+ ## License
376
+
377
+ The __Devise Secure Password Extension__ gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
378
+
379
+ ## Code of Conduct
380
+
381
+ Everyone interacting in the __Devise Secure Password Extension__ project’s codebases and issue trackers is expected to
382
+ follow the [code of conduct](https://github.com/valimail/devise-secure_password/blob/master/CODE_OF_CONDUCT.md).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+ require 'rake'
4
+
5
+ # load all rake tasks
6
+ Dir.glob(File.expand_path('lib/tasks/*.rake', __dir__)).each do |file|
7
+ import file
8
+ end
9
+
10
+ # require 'yard'
11
+ # YARD::Rake::YardocTask.new
@@ -0,0 +1,93 @@
1
+ module Devise
2
+ class PasswordsWithPolicyController < Devise::RegistrationsController
3
+ before_action :configure_permitted_parameters
4
+
5
+ def edit
6
+ self.resource = resource_class.new
7
+ resource.errors.add(:base, "#{error_string_for_password_expired}.")
8
+ render :edit
9
+ end
10
+
11
+ def update
12
+ if update_resource(resource, account_update_params)
13
+ prepare_for_redirect
14
+ redirect_to stored_location_for(scope_name) || :root
15
+ else
16
+ render :edit
17
+ end
18
+ end
19
+
20
+ def devise_parameter_sanitizer
21
+ @devise_parameter_sanitizer ||= Devise::ParameterSanitizer.new(resource_class, resource_name, params)
22
+ end
23
+
24
+ protected
25
+
26
+ def account_update_params
27
+ devise_parameter_sanitizer.sanitize(:account_update)
28
+ end
29
+
30
+ def authenticate_scope!
31
+ send(:"authenticate_#{scope_name}!")
32
+ self.resource = send("current_#{scope_name}")
33
+ end
34
+
35
+ def alert_string_for_password_updated
36
+ I18n.t('secure_password.password_requires_regular_updates.alerts.messages.password_updated')
37
+ end
38
+
39
+ def configure_permitted_parameters
40
+ devise_parameter_sanitizer.permit(:account_update, keys: [:update_action])
41
+ end
42
+
43
+ def error_string_for_password_expired
44
+ return 'password expired' unless warden.user.class.respond_to?(:password_maximum_age)
45
+
46
+ I18n.t(
47
+ 'secure_password.password_requires_regular_updates.errors.messages.password_expired',
48
+ timeframe: precise_distance_of_time_in_words(warden.user.class.password_maximum_age)
49
+ )
50
+ end
51
+
52
+ def prepare_for_redirect
53
+ unset_devise_secure_password_expired!
54
+ flash[:notice] = alert_string_for_password_updated
55
+ bypass_sign_in resource, scope: scope_name
56
+ end
57
+
58
+ def update_resource(resource, params)
59
+ update_action = (params[:update_action] ? params.delete(:update_action) : nil)
60
+ return false unless update_action == 'change_password'
61
+
62
+ update_password(resource, params)
63
+
64
+ # do what devise would do under normal circumstances but also be aware of
65
+ # secure_password or other validators that would be ignored by devise.
66
+ result = if resource.errors.count.zero?
67
+ resource.update(params)
68
+ else
69
+ false
70
+ end
71
+
72
+ resource.clean_up_passwords
73
+ result
74
+ end
75
+
76
+ def update_password(resource, params)
77
+ #
78
+ # order of operations that follow is absolutely critical
79
+ #
80
+ current_password = params.delete(:current_password)
81
+ current_password_valid = resource.valid_password?(current_password)
82
+
83
+ # let our installed validator chain handle the validation work
84
+ resource.assign_attributes(params)
85
+ resource.valid?
86
+ unless current_password_valid
87
+ resource.errors.add(:current_password, current_password.blank? ? :blank : :invalid)
88
+ end
89
+
90
+ resource
91
+ end
92
+ end
93
+ end