Skip to content

Instantly share code, notes, and snippets.

@carlalexander
Created February 6, 2020 04:16
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Embed
What would you like to do?
Chain-of-responsibility pattern
<?php
if (!class_exists('PasswordHash')) {
require_once ABSPATH . WPINC . '/class-phpass.php';
}
$password_hasher = new PasswordHasherChain([
new NativePasswordHasher(),
new WordPressPasswordHasher(new \PasswordHash(8, true)),
]);
<?php
/**
* Password hasher that uses the native PHP password hashing functions.
*/
class NativePasswordHasher implements PasswordHasherInterface
{
/**
* Algorithm used when hashing a password.
*
* @var int
*/
private $algorithm;
/**
* Constructor.
*/
public function __construct()
{
$this->algorithm = PASSWORD_DEFAULT;
if (defined('PASSWORD_ARGON2ID')) {
$this->algorithm = PASSWORD_ARGON2ID;
} elseif (defined('PASSWORD_ARGON2I')) {
$this->algorithm = PASSWORD_ARGON2I;
}
}
/**
* {@inheritdoc}
*/
public function hash_password($password)
{
return password_hash($password, $this->algorithm);
}
/**
* {@inheritdoc}
*/
public function is_hash_supported($hash)
{
if (0 === strpos($hash, '$argon2id$')) {
return defined('PASSWORD_ARGON2ID');
} elseif (0 === strpos($hash, '$argon2i$')) {
return defined('PASSWORD_ARGON2I');
}
return 0 === strpos($hash, '$2');
}
/**
* {@inheritdoc}
*/
public function is_hash_valid($hash)
{
return !password_needs_rehash($hash, $this->algorithm);
}
/**
* {@inheritdoc}
*/
public function is_password_valid($password, $hash)
{
return password_verify($password, $hash);
}
}
<?php
/**
* Manages a chain of password hashers.
*/
class PasswordHasherChain implements PasswordHasherInterface
{
/**
* The password hashers that the chain handles.
*
* @var PasswordHasherInterface[]
*/
private $password_hashers;
/**
* Constructor.
*
* @param PasswordHasherInterface[] $password_hashers
*/
public function __construct(array $password_hashers = [])
{
$this->password_hashers = array_filter($password_hashers, function ($password_hasher) {
return $password_hasher instanceof PasswordHasherInterface;
});
}
/**
* {@inheritdoc}
*/
public function hash_password($password)
{
$hash = array_reduce($this->password_hashers, function ($hash, PasswordHasherInterface $password_hasher) use ($password) {
if (empty($hash)) {
$hash = $password_hasher->hash_password($password);
}
return $hash;
});
if (empty($hash)) {
throw new \RuntimeException('Could not create a hash for the given password.');
}
return $hash;
}
/**
* {@inheritdoc}
*/
public function is_hash_supported($hash)
{
return $this->get_password_hasher($hash) instanceof PasswordHasherInterface;
}
/**
* {@inheritdoc}
*/
public function is_hash_valid($hash)
{
$password_hasher = $this->get_password_hasher($hash);
if (!$password_hasher instanceof PasswordHasherInterface) {
return false;
}
return $password_hasher->is_hash_valid($hash);
}
/**
* {@inheritdoc}
*/
public function is_password_valid($password, $hash)
{
return array_reduce($this->password_hashers, function ($check, PasswordHasherInterface $password_hasher) use ($password, $hash) {
if (true !== $check) {
$check = $password_hasher->is_password_valid($password, $hash);
}
return $check;
}, false);
}
/**
* Get the password hasher that supports the given hash.
*
* @param string $hash
*
* @return PasswordHasherInterface|null
*/
private function get_password_hasher($hash)
{
return array_reduce($this->password_hashers, function ($found, PasswordHasherInterface $password_hasher) use ($hash) {
if (!$found instanceof PasswordHasherInterface && $password_hasher->is_hash_supported($hash)) {
$found = $password_hasher;
}
return $found;
});
}
}
<?php
/**
* A password hasher hashes a password using a hashing algorithm.
*/
interface PasswordHasherInterface
{
/**
* Hashes the given password. Returns null if unable to hash password.
*
* @param string $password
*
* @return string|null
*/
public function hash_password($password);
/**
* Checks if the password hasher supports the given hash for verification.
*
* @param string $hash
*
* @return bool
*/
public function is_hash_supported($hash);
/**
* Checks if the given hash is valid. If a hash is invalid, we need to rehash it.
*
* @param string $hash
*
* @return bool
*/
public function is_hash_valid($hash);
/**
* Validates that the given password matches the given hash.
*
* @param string $password
* @param string $hash
*
* @return bool
*/
public function is_password_valid($password, $hash);
}
<?php
/**
* Password hasher that uses the builtin WordPress password hasher.
*/
class WordPressPasswordHasher implements PasswordHasherInterface
{
/**
* WordPress password hasher.
*
* @var \PasswordHash
*/
private $wordpress_hasher;
/**
* Constructor.
*
* @param \PasswordHash $wordpress_hasher
*/
public function __construct(\PasswordHash $wordpress_hasher)
{
$this->wordpress_hasher = $wordpress_hasher;
}
/**
* {@inheritdoc}
*/
public function hash_password($password)
{
return $this->wordpress_hasher->HashPassword($password);
}
/**
* {@inheritdoc}
*/
public function is_hash_supported($hash)
{
return 0 === strpos($hash, '$P$');
}
/**
* {@inheritdoc}
*/
public function is_hash_valid($hash)
{
return false;
}
/**
* {@inheritdoc}
*/
public function is_password_valid($password, $hash)
{
return $this->wordpress_hasher->CheckPassword($password, $hash);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment