Skip to content

Commit

Permalink
Added some rules for hyperf/validation. (#6749)
Browse files Browse the repository at this point in the history
  • Loading branch information
zds-s authored May 15, 2024
1 parent c72dca5 commit 429b734
Show file tree
Hide file tree
Showing 16 changed files with 1,949 additions and 0 deletions.
61 changes: 61 additions & 0 deletions src/ConditionalRules.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact [email protected]
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/

namespace Hyperf\Validation;

use Closure;
use Hyperf\Support\Fluent;
use Hyperf\Validation\Contract\Rule as RuleContract;

use function Hyperf\Support\value;

class ConditionalRules
{
public function __construct(
protected bool|Closure $condition,
protected array|Closure|RuleContract|string $rules,
protected array|Closure|RuleContract|string $defaultRules,
) {
}

/**
* Determine if the conditional rules should be added.
*/
public function passes(array $data = []): bool
{
return is_callable($this->condition)
? call_user_func($this->condition, new Fluent($data))
: $this->condition;
}

/**
* Get the rules.
*/
public function rules(array $data = [])
{
return is_string($this->rules)
? explode('|', $this->rules)
: value($this->rules, new Fluent($data));
}

/**
* Get the default rules.
*
* @return array
*/
public function defaultRules(array $data = [])
{
return is_string($this->defaultRules)
? explode('|', $this->defaultRules)
: value($this->defaultRules, new Fluent($data));
}
}
2 changes: 2 additions & 0 deletions src/ConfigProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
namespace Hyperf\Validation;

use Hyperf\Validation\Contract\PresenceVerifierInterface;
use Hyperf\Validation\Contract\UncompromisedVerifier;
use Hyperf\Validation\Contract\ValidatorFactoryInterface as FactoryInterface;

class ConfigProvider
Expand All @@ -30,6 +31,7 @@ public function __invoke(): array
'dependencies' => [
PresenceVerifierInterface::class => DatabasePresenceVerifierFactory::class,
FactoryInterface::class => ValidatorFactoryFactory::class,
UncompromisedVerifier::class => NotPwnedVerifier::class,
],
'publish' => [
[
Expand Down
21 changes: 21 additions & 0 deletions src/Contract/UncompromisedVerifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact [email protected]
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/

namespace Hyperf\Validation\Contract;

interface UncompromisedVerifier
{
/**
* Verify that the given data has not been compromised in data leaks.
*/
public function verify(array $data): bool;
}
88 changes: 88 additions & 0 deletions src/NotPwnedVerifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
<?php

declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact [email protected]
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/

namespace Hyperf\Validation;

use Hyperf\Collection\Collection;
use Hyperf\Guzzle\ClientFactory;
use Hyperf\Stringable\Str;
use Hyperf\Validation\Contract\UncompromisedVerifier;

class NotPwnedVerifier implements UncompromisedVerifier
{
/**
* Create a new uncompromised verifier.
* @param ClientFactory $factory the factory to create the HTTP client
* @param int $timeout the number of seconds the request can run before timing out
*/
public function __construct(
protected ClientFactory $factory,
protected int $timeout = 30,
) {
}

public function verify(array $data): bool
{
$value = $data['value'];
$threshold = $data['threshold'];

if (empty($value = (string) $value)) {
return false;
}

[$hash,$hashPrefix] = $this->getHash($value);
return ! $this->search($hashPrefix)
->contains(static function ($line) use ($hash, $hashPrefix, $threshold) {
[$hashSuffix, $count] = explode(':', $line);

return $hashPrefix . $hashSuffix === $hash && $count > $threshold;
});
}

/**
* Get the hash and its first 5 chars.
*/
protected function getHash(string $value): array
{
$hash = strtoupper(sha1($value));

$hashPrefix = substr($hash, 0, 5);

return [$hash, $hashPrefix];
}

/**
* Search by the given hash prefix and returns all occurrences of leaked passwords.
*/
protected function search(string $hashPrefix): Collection
{
$client = $this->factory->create([
'timeout' => $this->timeout,
]);
$response = $client->get(
'https://api.pwnedpasswords.com/range/' . $hashPrefix,
[
'headers' => [
'Add-Padding' => true,
],
]
);

$body = ($response->getStatusCode() === 200)
? $response->getBody()->getContents()
: '';

return Str::of($body)->trim()->explode("\n")->filter(function ($line) {
return str_contains($line, ':');
});
}
}
61 changes: 61 additions & 0 deletions src/Rule.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,15 @@

namespace Hyperf\Validation;

use Closure;
use Hyperf\Contract\Arrayable;
use Hyperf\Macroable\Macroable;
use Hyperf\Validation\Contract\Rule as RuleContract;
use Hyperf\Validation\Rules\ArrayRule;
use Hyperf\Validation\Rules\Enum;
use Hyperf\Validation\Rules\ExcludeIf;
use Hyperf\Validation\Rules\File;
use Hyperf\Validation\Rules\ImageFile;
use Hyperf\Validation\Rules\ProhibitedIf;

class Rule
Expand Down Expand Up @@ -86,4 +92,59 @@ public static function excludeIf($callback): ExcludeIf
{
return new ExcludeIf($callback);
}

/**
* Apply the given rules if the given condition is truthy.
*/
public static function when(
bool|Closure $condition,
array|Closure|RuleContract|string $rules,
array|Closure|RuleContract|string $defaultRules = []
): ConditionalRules {
return new ConditionalRules($condition, $rules, $defaultRules);
}

/**
* Apply the given rules if the given condition is falsy.
*/
public static function unless(
bool|Closure $condition,
array|Closure|RuleContract|string $rules,
array|Closure|RuleContract|string $defaultRules = []
): ConditionalRules {
return new ConditionalRules($condition, $defaultRules, $rules);
}

/**
* Get an array rule builder instance.
* @param null|mixed $keys
*/
public static function array($keys = null): ArrayRule
{
return new ArrayRule(...func_get_args());
}

/**
* Get an enum rule builder instance.
*/
public static function enum(string $type): Enum
{
return new Enum($type);
}

/**
* Get a file rule builder instance.
*/
public static function file(): File
{
return new File();
}

/**
* Get an image file rule builder instance.
*/
public static function imageFile(): File
{
return new ImageFile();
}
}
50 changes: 50 additions & 0 deletions src/Rules/ArrayRule.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);
/**
* This file is part of Hyperf.
*
* @link https://www.hyperf.io
* @document https://hyperf.wiki
* @contact [email protected]
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
*/

namespace Hyperf\Validation\Rules;

use BackedEnum;
use Hyperf\Contract\Arrayable;
use Stringable;
use UnitEnum;

class ArrayRule implements Stringable
{
protected $keys;

public function __construct(
$keys = null,
) {
if ($keys instanceof Arrayable) {
$keys = $keys->toArray();
}
$this->keys = is_array($keys) ? $keys : func_get_args();
}

public function __toString()
{
if (empty($this->keys)) {
return 'array';
}

$keys = array_map(
static fn ($key) => match (true) {
$key instanceof BackedEnum => $key->value,
$key instanceof UnitEnum => $key->name,
default => $key,
},
$this->keys,
);

return 'array:' . implode(',', $keys);
}
}
Loading

0 comments on commit 429b734

Please sign in to comment.