devise-secure-password 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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