-
-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #3 from kpumuk/dmytro/rails-validator
Added Rails validator
- Loading branch information
Showing
11 changed files
with
258 additions
and
24 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,21 @@ | ||
sudo: false | ||
language: ruby | ||
|
||
env: | ||
matrix: | ||
- RAILS_VERSION=4.2.0 | ||
- RAILS_VERSION=5.0.0 | ||
- RAILS_VERSION=5.1.0 | ||
|
||
rvm: | ||
- 2.5.0 | ||
- 2.4.0 | ||
- 2.3.0 | ||
- jruby | ||
- ruby-head | ||
|
||
before_install: gem install bundler -v 1.16.1 | ||
|
||
matrix: | ||
allow_failures: | ||
- rvm: ruby-head | ||
- rvm: ruby-head |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,17 @@ | ||
# Changelog for `Pwned` | ||
|
||
## Ongoing | ||
## Ongoing [☰](https://github.com/philnash/pwned/compare/v1.0.0...master) | ||
|
||
* 2 major updates | ||
* Major updates | ||
* Refactors exception handling with built in Ruby method ([PR #1](https://github.com/philnash/pwned/pull/1) thanks [@kpumuk](https://github.com/kpumuk)) | ||
* Passwords must be strings, the initializer will raise a `TypeError` unless `password.is_a? String`. ([dbf7697](https://github.com/philnash/pwned/commit/dbf7697e878d87ac74aed1e715cee19b73473369)) | ||
* Added Ruby on Rails validator ([PR #3](https://github.com/philnash/pwned/pull/3)) | ||
|
||
* Minor updates | ||
* SHA1 is only calculated once | ||
* Frozen string literal to make sure Ruby does not copy strings over and over again | ||
* Removal of `@match_data`, since we only use it to retrieve the counter. Caching the counter instead (all [PR #2](https://github.com/philnash/pwned/pull/2) thanks [@kpumuk](https://github.com/kpumuk)) | ||
|
||
## 1.0.0 / 2018-03-06 | ||
## 1.0.0 (March 6, 2018) [☰](https://github.com/philnash/pwned/commits/v1.0.0) | ||
|
||
Initial release. Includes basic features for checking passwords and their count from the Pwned Passwords API. Allows setting of request headers and other options for open-uri. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
en: | ||
errors: | ||
messages: | ||
pwned: has previously appeared in a data breach and should not be used | ||
pwned_error: could not be verified against the past data breaches |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# frozen_string_literal: true | ||
|
||
class PwnedValidator < ActiveModel::EachValidator | ||
# We do not want to break customer sign-up process when the service is down. | ||
DEFAULT_ON_ERROR = :valid | ||
|
||
def validate_each(record, attribute, value) | ||
begin | ||
pwned_check = Pwned::Password.new(value, request_options) | ||
if pwned_check.pwned? | ||
record.errors.add(attribute, :pwned, options.merge(count: pwned_check.pwned_count)) | ||
end | ||
rescue Pwned::Error => error | ||
case on_error | ||
when :invalid | ||
record.errors.add(attribute, :pwned_error, options.merge(message: options[:error_message])) | ||
when :valid | ||
# Do nothing, consider the record valid | ||
when Proc | ||
on_error.call(record, error) | ||
else | ||
raise | ||
end | ||
end | ||
end | ||
|
||
def on_error | ||
options[:on_error] || DEFAULT_ON_ERROR | ||
end | ||
|
||
def request_options | ||
options[:request_options] || {} | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
RSpec.describe PwnedValidator do | ||
class Model | ||
include ActiveModel::Validations | ||
|
||
attr_accessor :password | ||
end | ||
|
||
after(:example) do | ||
Model.clear_validators! | ||
end | ||
|
||
describe "when pwned", pwned_range: "5BAA6" do | ||
it "marks the model as invalid" do | ||
Model.validates :password, pwned: true | ||
model = create_model('password') | ||
|
||
expect(model).to_not be_valid | ||
expect(model.errors[:password].size).to eq(1) | ||
expect(model.errors[:password].first).to eq('has previously appeared in a data breach and should not be used') | ||
end | ||
|
||
it "allows to change the error message" do | ||
Model.validates :password, pwned: { message: "has been pwned %{count} times" } | ||
model = create_model('password') | ||
|
||
expect(model).to_not be_valid | ||
expect(model.errors[:password].size).to eq(1) | ||
expect(model.errors[:password].first).to eq('has been pwned 3303003 times') | ||
end | ||
|
||
it "allows the user agent to be set" do | ||
Model.validates :password, pwned: { | ||
request_options: { "User-Agent" => "Super fun user agent" } | ||
} | ||
model = create_model('password') | ||
|
||
expect(model).to_not be_valid | ||
expect(a_request(:get, "https://api.pwnedpasswords.com/range/5BAA6"). | ||
with(headers: { "User-Agent" => "Super fun user agent" })). | ||
to have_been_made.once | ||
end | ||
end | ||
|
||
describe "when not pwned", pwned_range: "37D5B" do | ||
it "reports the model as valid" do | ||
Model.validates :password, pwned: true | ||
model = create_model('t3hb3stpa55w0rd') | ||
|
||
expect(model).to be_valid | ||
end | ||
end | ||
|
||
describe "when the API times out" do | ||
before(:example) do | ||
@stub = stub_request(:get, "https://api.pwnedpasswords.com/range/5BAA6").to_timeout | ||
end | ||
|
||
it "marks the model as valid when not error handling configured" do | ||
Model.validates :password, pwned: true | ||
model = create_model('password') | ||
|
||
expect(model).to be_valid | ||
end | ||
|
||
it "raises a custom error when error handling configured to :raise_error" do | ||
Model.validates :password, pwned: { on_error: :raise_error } | ||
model = create_model('password') | ||
|
||
expect { model.valid? }.to raise_error(Pwned::TimeoutError, /execution expired/) | ||
end | ||
|
||
it "marks the model as invalid when error handling configured to :invalid" do | ||
Model.validates :password, pwned: { on_error: :invalid } | ||
model = create_model('password') | ||
|
||
expect(model).to_not be_valid | ||
expect(model.errors[:password].size).to eq(1) | ||
expect(model.errors[:password].first).to eq("could not be verified against the past data breaches") | ||
end | ||
|
||
it "marks the model as invalid with a custom error message when error handling configured to :invalid" do | ||
Model.validates :password, pwned: { on_error: :invalid, error_message: "might be pwned" } | ||
model = create_model('password') | ||
|
||
expect(model).to_not be_valid | ||
expect(model.errors[:password].size).to eq(1) | ||
expect(model.errors[:password].first).to eq("might be pwned") | ||
end | ||
|
||
it "marks the model as valid when error handling configured to :valid" do | ||
Model.validates :password, pwned: { on_error: :valid } | ||
model = create_model('password') | ||
|
||
expect(model).to be_valid | ||
end | ||
|
||
it "calls a proc configured for error handling" do | ||
Model.validates :password, pwned: { on_error: ->(record, error) { raise RuntimeError, "custom proc" } } | ||
model = create_model('password') | ||
|
||
expect { model.valid? }.to raise_error(RuntimeError, "custom proc") | ||
end | ||
end | ||
|
||
def create_model(password) | ||
Model.new.tap { |model| model.password = password } | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
RSpec.configure do |config| | ||
config.around :example, :pwned_range do |example| | ||
pwned_range = example.metadata[:pwned_range] | ||
File.open(File.expand_path("../fixtures/#{pwned_range}.txt", __dir__)) do |body| | ||
uri = "https://api.pwnedpasswords.com/range/#{pwned_range}" | ||
@stub = stub_request(:get, uri).to_return(body: body) | ||
example.run | ||
end | ||
end | ||
end |