NotPwnedVerifier.php
2.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
<?php
namespace Illuminate\Validation;
use Exception;
use Illuminate\Contracts\Validation\UncompromisedVerifier;
use Illuminate\Support\Str;
class NotPwnedVerifier implements UncompromisedVerifier
{
/**
* The HTTP factory instance.
*
* @var \Illuminate\Http\Client\Factory
*/
protected $factory;
/**
* The number of seconds the request can run before timing out.
*
* @var int
*/
protected $timeout;
/**
* Create a new uncompromised verifier.
*
* @param \Illuminate\Http\Client\Factory $factory
* @param int|null $timeout
* @return void
*/
public function __construct($factory, $timeout = null)
{
$this->factory = $factory;
$this->timeout = $timeout ?? 30;
}
/**
* Verify that the given data has not been compromised in public breaches.
*
* @param array $data
* @return bool
*/
public function verify($data)
{
$value = $data['value'];
$threshold = $data['threshold'];
if (empty($value = (string) $value)) {
return false;
}
[$hash, $hashPrefix] = $this->getHash($value);
return ! $this->search($hashPrefix)
->contains(function ($line) use ($hash, $hashPrefix, $threshold) {
[$hashSuffix, $count] = explode(':', $line);
return $hashPrefix.$hashSuffix == $hash && $count > $threshold;
});
}
/**
* Get the hash and its first 5 chars.
*
* @param string $value
* @return array
*/
protected function getHash($value)
{
$hash = strtoupper(sha1((string) $value));
$hashPrefix = substr($hash, 0, 5);
return [$hash, $hashPrefix];
}
/**
* Search by the given hash prefix and returns all occurrences of leaked passwords.
*
* @param string $hashPrefix
* @return \Illuminate\Support\Collection
*/
protected function search($hashPrefix)
{
try {
$response = $this->factory->withHeaders([
'Add-Padding' => true,
])->timeout($this->timeout)->get(
'https://api.pwnedpasswords.com/range/'.$hashPrefix
);
} catch (Exception $e) {
report($e);
}
$body = (isset($response) && $response->successful())
? $response->body()
: '';
return Str::of($body)->trim()->explode("\n")->filter(function ($line) {
return Str::contains($line, ':');
});
}
}