Skip to content

Instantly share code, notes, and snippets.

@tjblackheart
Last active March 9, 2018 13:12
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tjblackheart/d59d57c397295f81b7f15087836cc74d to your computer and use it in GitHub Desktop.
Save tjblackheart/d59d57c397295f81b7f15087836cc74d to your computer and use it in GitHub Desktop.
A Symfony form validator which validates against the https://haveibeenpwned.com/passwords Range API. You'll need https://github.com/php-http/HttplugBundle.
<?php
namespace App\Utils;
use Http\Client\Common\HttpMethodsClient;
use Http\Client\Exception as HttpClientException;
use Http\Discovery\HttpClientDiscovery;
use Http\Discovery\MessageFactoryDiscovery;
class PwnedApi
{
public const API = 'https://api.pwnedpasswords.com/range';
public static function searchByRange(string $hash) : array
{
$list = [];
$client = new HttpMethodsClient(
HttpClientDiscovery::find(),
MessageFactoryDiscovery::find()
);
try {
$response = $client->get(sprintf("%s/%s", self::API, $hash));
foreach (preg_split("/((\r?\n)|(\r\n?))/", (string) $response->getBody()) as $line) {
$result = explode(':', $line);
$list[$result[0]] = $result[1];
}
} catch (HttpClientException $ex) {
// fail silently ... or do whatever.
}
return $list;
}
}
<?php
namespace App\Validator\Constraints;
use Symfony\Component\Validator\Constraint;
/**
* @Annotation
*/
class PwnedPasswordConstraint extends Constraint
{
public $message = "This password has previously appeared in a data breach
and was used {{ count }} times before.
If you've ever used it anywhere before, change it!
For more info visit: https://haveibeenpwned.com/passwords";
}
<?php
namespace App\Validator\Constraints;
use App\Utils\PwnedApi;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidator;
class PwnedPasswordConstraintValidator extends ConstraintValidator
{
public function validate($value, Constraint $constraint)
{
if (empty($value)) {
return;
}
$sha1 = strtoupper(hash('sha1', $value));
$prefix = substr($sha1, 0, 5);
$suffix = substr($sha1, 5, strlen($sha1));
$list = PwnedApi::searchByRange($prefix);
if (!empty($list[$suffix])) {
$this->context->buildViolation($constraint->message)
->setParameter('{{ count }}', $list[$suffix])
->addViolation();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment