Skip to content

Commit 429b734

Browse files
authored
Added some rules for hyperf/validation. (#6749)
1 parent c72dca5 commit 429b734

16 files changed

+1949
-0
lines changed

src/ConditionalRules.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Validation;
14+
15+
use Closure;
16+
use Hyperf\Support\Fluent;
17+
use Hyperf\Validation\Contract\Rule as RuleContract;
18+
19+
use function Hyperf\Support\value;
20+
21+
class ConditionalRules
22+
{
23+
public function __construct(
24+
protected bool|Closure $condition,
25+
protected array|Closure|RuleContract|string $rules,
26+
protected array|Closure|RuleContract|string $defaultRules,
27+
) {
28+
}
29+
30+
/**
31+
* Determine if the conditional rules should be added.
32+
*/
33+
public function passes(array $data = []): bool
34+
{
35+
return is_callable($this->condition)
36+
? call_user_func($this->condition, new Fluent($data))
37+
: $this->condition;
38+
}
39+
40+
/**
41+
* Get the rules.
42+
*/
43+
public function rules(array $data = [])
44+
{
45+
return is_string($this->rules)
46+
? explode('|', $this->rules)
47+
: value($this->rules, new Fluent($data));
48+
}
49+
50+
/**
51+
* Get the default rules.
52+
*
53+
* @return array
54+
*/
55+
public function defaultRules(array $data = [])
56+
{
57+
return is_string($this->defaultRules)
58+
? explode('|', $this->defaultRules)
59+
: value($this->defaultRules, new Fluent($data));
60+
}
61+
}

src/ConfigProvider.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
namespace Hyperf\Validation;
1414

1515
use Hyperf\Validation\Contract\PresenceVerifierInterface;
16+
use Hyperf\Validation\Contract\UncompromisedVerifier;
1617
use Hyperf\Validation\Contract\ValidatorFactoryInterface as FactoryInterface;
1718

1819
class ConfigProvider
@@ -30,6 +31,7 @@ public function __invoke(): array
3031
'dependencies' => [
3132
PresenceVerifierInterface::class => DatabasePresenceVerifierFactory::class,
3233
FactoryInterface::class => ValidatorFactoryFactory::class,
34+
UncompromisedVerifier::class => NotPwnedVerifier::class,
3335
],
3436
'publish' => [
3537
[
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Validation\Contract;
14+
15+
interface UncompromisedVerifier
16+
{
17+
/**
18+
* Verify that the given data has not been compromised in data leaks.
19+
*/
20+
public function verify(array $data): bool;
21+
}

src/NotPwnedVerifier.php

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Validation;
14+
15+
use Hyperf\Collection\Collection;
16+
use Hyperf\Guzzle\ClientFactory;
17+
use Hyperf\Stringable\Str;
18+
use Hyperf\Validation\Contract\UncompromisedVerifier;
19+
20+
class NotPwnedVerifier implements UncompromisedVerifier
21+
{
22+
/**
23+
* Create a new uncompromised verifier.
24+
* @param ClientFactory $factory the factory to create the HTTP client
25+
* @param int $timeout the number of seconds the request can run before timing out
26+
*/
27+
public function __construct(
28+
protected ClientFactory $factory,
29+
protected int $timeout = 30,
30+
) {
31+
}
32+
33+
public function verify(array $data): bool
34+
{
35+
$value = $data['value'];
36+
$threshold = $data['threshold'];
37+
38+
if (empty($value = (string) $value)) {
39+
return false;
40+
}
41+
42+
[$hash,$hashPrefix] = $this->getHash($value);
43+
return ! $this->search($hashPrefix)
44+
->contains(static function ($line) use ($hash, $hashPrefix, $threshold) {
45+
[$hashSuffix, $count] = explode(':', $line);
46+
47+
return $hashPrefix . $hashSuffix === $hash && $count > $threshold;
48+
});
49+
}
50+
51+
/**
52+
* Get the hash and its first 5 chars.
53+
*/
54+
protected function getHash(string $value): array
55+
{
56+
$hash = strtoupper(sha1($value));
57+
58+
$hashPrefix = substr($hash, 0, 5);
59+
60+
return [$hash, $hashPrefix];
61+
}
62+
63+
/**
64+
* Search by the given hash prefix and returns all occurrences of leaked passwords.
65+
*/
66+
protected function search(string $hashPrefix): Collection
67+
{
68+
$client = $this->factory->create([
69+
'timeout' => $this->timeout,
70+
]);
71+
$response = $client->get(
72+
'https://api.pwnedpasswords.com/range/' . $hashPrefix,
73+
[
74+
'headers' => [
75+
'Add-Padding' => true,
76+
],
77+
]
78+
);
79+
80+
$body = ($response->getStatusCode() === 200)
81+
? $response->getBody()->getContents()
82+
: '';
83+
84+
return Str::of($body)->trim()->explode("\n")->filter(function ($line) {
85+
return str_contains($line, ':');
86+
});
87+
}
88+
}

src/Rule.php

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,15 @@
1212

1313
namespace Hyperf\Validation;
1414

15+
use Closure;
1516
use Hyperf\Contract\Arrayable;
1617
use Hyperf\Macroable\Macroable;
18+
use Hyperf\Validation\Contract\Rule as RuleContract;
19+
use Hyperf\Validation\Rules\ArrayRule;
20+
use Hyperf\Validation\Rules\Enum;
1721
use Hyperf\Validation\Rules\ExcludeIf;
22+
use Hyperf\Validation\Rules\File;
23+
use Hyperf\Validation\Rules\ImageFile;
1824
use Hyperf\Validation\Rules\ProhibitedIf;
1925

2026
class Rule
@@ -86,4 +92,59 @@ public static function excludeIf($callback): ExcludeIf
8692
{
8793
return new ExcludeIf($callback);
8894
}
95+
96+
/**
97+
* Apply the given rules if the given condition is truthy.
98+
*/
99+
public static function when(
100+
bool|Closure $condition,
101+
array|Closure|RuleContract|string $rules,
102+
array|Closure|RuleContract|string $defaultRules = []
103+
): ConditionalRules {
104+
return new ConditionalRules($condition, $rules, $defaultRules);
105+
}
106+
107+
/**
108+
* Apply the given rules if the given condition is falsy.
109+
*/
110+
public static function unless(
111+
bool|Closure $condition,
112+
array|Closure|RuleContract|string $rules,
113+
array|Closure|RuleContract|string $defaultRules = []
114+
): ConditionalRules {
115+
return new ConditionalRules($condition, $defaultRules, $rules);
116+
}
117+
118+
/**
119+
* Get an array rule builder instance.
120+
* @param null|mixed $keys
121+
*/
122+
public static function array($keys = null): ArrayRule
123+
{
124+
return new ArrayRule(...func_get_args());
125+
}
126+
127+
/**
128+
* Get an enum rule builder instance.
129+
*/
130+
public static function enum(string $type): Enum
131+
{
132+
return new Enum($type);
133+
}
134+
135+
/**
136+
* Get a file rule builder instance.
137+
*/
138+
public static function file(): File
139+
{
140+
return new File();
141+
}
142+
143+
/**
144+
* Get an image file rule builder instance.
145+
*/
146+
public static function imageFile(): File
147+
{
148+
return new ImageFile();
149+
}
89150
}

src/Rules/ArrayRule.php

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
/**
5+
* This file is part of Hyperf.
6+
*
7+
* @link https://www.hyperf.io
8+
* @document https://hyperf.wiki
9+
* @contact group@hyperf.io
10+
* @license https://github.com/hyperf/hyperf/blob/master/LICENSE
11+
*/
12+
13+
namespace Hyperf\Validation\Rules;
14+
15+
use BackedEnum;
16+
use Hyperf\Contract\Arrayable;
17+
use Stringable;
18+
use UnitEnum;
19+
20+
class ArrayRule implements Stringable
21+
{
22+
protected $keys;
23+
24+
public function __construct(
25+
$keys = null,
26+
) {
27+
if ($keys instanceof Arrayable) {
28+
$keys = $keys->toArray();
29+
}
30+
$this->keys = is_array($keys) ? $keys : func_get_args();
31+
}
32+
33+
public function __toString()
34+
{
35+
if (empty($this->keys)) {
36+
return 'array';
37+
}
38+
39+
$keys = array_map(
40+
static fn ($key) => match (true) {
41+
$key instanceof BackedEnum => $key->value,
42+
$key instanceof UnitEnum => $key->name,
43+
default => $key,
44+
},
45+
$this->keys,
46+
);
47+
48+
return 'array:' . implode(',', $keys);
49+
}
50+
}

0 commit comments

Comments
 (0)