Skip to content

Commit

Permalink
work on rule
Browse files Browse the repository at this point in the history
  • Loading branch information
taylorotwell authored and nunomaduro committed Apr 21, 2021
1 parent 8c08501 commit 4fb92f5
Show file tree
Hide file tree
Showing 5 changed files with 58 additions and 85 deletions.
Expand Up @@ -2,7 +2,7 @@

namespace Illuminate\Contracts\Validation;

interface NotCompromisedVerifier
interface UncompromisedVerifier
{
/**
* Verify that the given value has not been compromised in data leaks.
Expand Down
8 changes: 4 additions & 4 deletions src/Illuminate/Validation/NotPwnedVerifier.php
Expand Up @@ -3,11 +3,11 @@
namespace Illuminate\Validation;

use Exception;
use Illuminate\Contracts\Validation\NotCompromisedVerifier;
use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Http\Client\Factory;
use Illuminate\Support\Str;

class NotPwnedVerifier implements NotCompromisedVerifier
class NotPwnedVerifier implements UncompromisedVerifier
{
/**
* The http factory instance.
Expand Down Expand Up @@ -64,9 +64,9 @@ protected function getHash($value)
}

/**
* Search by the given hash prefix and returns all occurrences.
* Search by the given hash prefix and returns all occurrences of leaked passwords.
*
* @param string $hashPrefix
* @param string $hashPrefix
* @return \Illuminate\Support\Collection
*/
protected function search($hashPrefix)
Expand Down
58 changes: 32 additions & 26 deletions src/Illuminate/Validation/Rules/Password.php
Expand Up @@ -2,20 +2,15 @@

namespace Illuminate\Validation\Rules;

use Illuminate\Container\Container;
use Illuminate\Contracts\Validation\DataAwareRule;
use Illuminate\Contracts\Validation\Rule;
use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\Validator;

class Password implements Rule, DataAwareRule
{
/**
* If the password requires at least one uppercase and one lowercase letter.
*
* @var bool
*/
protected $caseDiff = false;

/**
* The data under validation.
*
Expand All @@ -24,25 +19,25 @@ class Password implements Rule, DataAwareRule
protected $data;

/**
* If the password requires at least one letter.
* The minimum size of the password.
*
* @var bool
* @var int
*/
protected $letters = false;
protected $min = 8;

/**
* The minimum size of the password.
* If the password requires at least one uppercase and one lowercase letter.
*
* @var int
* @var bool
*/
protected $min = 8;
protected $mixedCase = false;

/**
* If the password should has not been compromised in data leaks.
* If the password requires at least one letter.
*
* @var bool
*/
protected $notCompromised = false;
protected $letters = false;

/**
* If the password requires at least one number.
Expand All @@ -58,6 +53,13 @@ class Password implements Rule, DataAwareRule
*/
protected $symbols = false;

/**
* If the password should has not been compromised in data leaks.
*
* @var bool
*/
protected $uncompromised = false;

/**
* The failure messages, if any.
*
Expand Down Expand Up @@ -105,9 +107,9 @@ public static function min($size)
*
* @return $this
*/
public function ensureNotCompromised()
public function uncompromised()
{
$this->notCompromised = true;
$this->uncompromised = true;

return $this;
}
Expand All @@ -117,9 +119,9 @@ public function ensureNotCompromised()
*
* @return $this
*/
public function requireCaseDiff()
public function mixedCase()
{
$this->caseDiff = true;
$this->mixedCase = true;

return $this;
}
Expand All @@ -129,7 +131,7 @@ public function requireCaseDiff()
*
* @return $this
*/
public function requireLetters()
public function letters()
{
$this->letters = true;

Expand All @@ -141,7 +143,7 @@ public function requireLetters()
*
* @return $this
*/
public function requireNumbers()
public function numbers()
{
$this->numbers = true;

Expand All @@ -153,7 +155,7 @@ public function requireNumbers()
*
* @return $this
*/
public function requireSymbols()
public function symbols()
{
$this->symbols = true;

Expand All @@ -170,7 +172,7 @@ public function requireSymbols()
public function passes($attribute, $value)
{
$validator = Validator::make($this->data, [
$attribute => 'required|string|confirmed|min:'.$this->min,
$attribute => 'string|min:'.$this->min,
]);

if ($validator->fails()) {
Expand All @@ -179,7 +181,7 @@ public function passes($attribute, $value)

$value = (string) $value;

if ($this->caseDiff && ! preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/u', $value)) {
if ($this->mixedCase && ! preg_match('/(\p{Ll}+.*\p{Lu})|(\p{Lu}+.*\p{Ll})/u', $value)) {
$this->fail('The :attribute must contain at least one uppercase and one lowercase letter.');
}

Expand All @@ -199,7 +201,7 @@ public function passes($attribute, $value)
return false;
}

if ($this->notCompromised && ! app('validation.not_compromised')->verify($value)) {
if ($this->uncompromised && ! Container::getInstance()->make(UncompromisedVerifier::class)->verify($value)) {
return $this->fail(
'The given :attribute has appeared in a data leak. Please choose a different :attribute.'
);
Expand All @@ -226,7 +228,11 @@ public function message()
*/
protected function fail($messages)
{
$this->messages = array_merge($this->messages, Arr::wrap($messages));
$messages = collect(Arr::wrap($messages))->map(function ($message) {
return __($message);
})->all();

$this->messages = array_merge($this->messages, $messages);

return false;
}
Expand Down
15 changes: 6 additions & 9 deletions src/Illuminate/Validation/ValidationServiceProvider.php
Expand Up @@ -3,6 +3,7 @@
namespace Illuminate\Validation;

use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Http\Client\Factory as HttpFactory;
use Illuminate\Support\ServiceProvider;

Expand All @@ -16,9 +17,7 @@ class ValidationServiceProvider extends ServiceProvider implements DeferrablePro
public function register()
{
$this->registerPresenceVerifier();

$this->registerNotCompromisedVerifier();

$this->registerUncompromisedVerifier();
$this->registerValidationFactory();
}

Expand Down Expand Up @@ -56,16 +55,14 @@ protected function registerPresenceVerifier()
}

/**
* Register the not compromised verifier.
* Register the uncompromised password verifier.
*
* @return void
*/
protected function registerNotCompromisedVerifier()
protected function registerUncompromisedVerifier()
{
$this->app->singleton('validation.not_compromised', function ($app) {
return new NotPwnedVerifier(
$app[HttpFactory::class]
);
$this->app->singleton(UncompromisedVerifier::class, function ($app) {
return new NotPwnedVerifier($app[HttpFactory::class]);
});
}

Expand Down
60 changes: 15 additions & 45 deletions tests/Validation/ValidationPasswordRuleTest.php
Expand Up @@ -13,15 +13,6 @@

class ValidationPasswordRuleTest extends TestCase
{
public function testRequired()
{
$this->fails(Password::min(3), [null], [
'validation.required',
]);

$this->passes(Password::min(3), ['1234', 'abcd', 'a z s']);
}

public function testString()
{
$this->fails(Password::min(3), [['foo' => 'bar'], ['foo']], [
Expand All @@ -47,43 +38,43 @@ public function testMin()

public function testCaseDiff()
{
$this->fails(Password::min(2)->requireCaseDiff(), ['nn', 'MM'], [
$this->fails(Password::min(2)->mixedCase(), ['nn', 'MM'], [
'The my password must contain at least one uppercase and one lowercase letter.',
]);

$this->passes(Password::min(2)->requireCaseDiff(), ['Nn', 'Mn', 'âA']);
$this->passes(Password::min(2)->mixedCase(), ['Nn', 'Mn', 'âA']);
}

public function testLetters()
{
$this->fails(Password::min(2)->requireLetters(), ['11', '22', '^^', '``', '**'], [
$this->fails(Password::min(2)->letters(), ['11', '22', '^^', '``', '**'], [
'The my password must contain at least one letter.',
]);

$this->passes(Password::min(2)->requireLetters(), ['1a', 'b2', 'â1']);
$this->passes(Password::min(2)->letters(), ['1a', 'b2', 'â1']);
}

public function testNumbers()
{
$this->fails(Password::min(2)->requireNumbers(), ['aa', 'bb', ' a'], [
$this->fails(Password::min(2)->numbers(), ['aa', 'bb', ' a'], [
'The my password must contain at least one number.',
]);

$this->passes(Password::min(2)->requireNumbers(), ['1a', 'b2', '00']);
$this->passes(Password::min(2)->numbers(), ['1a', 'b2', '00']);
}

public function testSymbols()
{
$this->fails(Password::min(2)->requireSymbols(), ['ab', '1v'], [
$this->fails(Password::min(2)->symbols(), ['ab', '1v'], [
'The my password must contain at least one symbol.',
]);

$this->passes(Password::min(2)->requireSymbols(), ['n^d', 'd^!', 'âè']);
$this->passes(Password::min(2)->symbols(), ['n^d', 'd^!', 'âè']);
}

public function testNotCompromised()
{
$this->fails(Password::min(2)->ensureNotCompromised(), [
$this->fails(Password::min(2)->uncompromised(), [
'123456',
'password',
'welcome',
Expand All @@ -96,7 +87,7 @@ public function testNotCompromised()
'The given my password has appeared in a data leak. Please choose a different my password.',
]);

$this->passes(Password::min(2)->ensureNotCompromised(), [
$this->passes(Password::min(2)->uncompromised(), [
'!p8VrB',
'&xe6VeKWF#n4',
'%HurHUnw7zM!',
Expand All @@ -110,11 +101,11 @@ public function testMessages()
{
$makeRule = function () {
return Password::min(8)
->requireCaseDiff()
->requireLetters()
->requireNumbers()
->requireSymbols()
->ensureNotCompromised();
->mixedCase()
->letters()
->numbers()
->symbols()
->uncompromised();
};

$this->fails($makeRule(), ['foo', 'azdazd', '1231231'], [
Expand All @@ -134,27 +125,6 @@ public function testMessages()
]);
}

public function testConfirmed()
{
$v = new Validator(
resolve('translator'),
['my_password' => '1234', 'my_password_confirmation' => '5678'],
['my_password' => Password::min(3)]
);

$this->assertSame(false, $v->passes());
$this->assertSame(['my_password' => ['validation.confirmed']], $v->messages()->toArray());

$v = new Validator(
resolve('translator'),
['my_password' => '1234'],
['my_password' => Password::min(3)]
);

$this->assertSame(false, $v->passes());
$this->assertSame(['my_password' => ['validation.confirmed']], $v->messages()->toArray());
}

protected function passes($rule, $values)
{
$this->testRule($rule, $values, true, []);
Expand Down

0 comments on commit 4fb92f5

Please sign in to comment.