Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Check haveibeenpwned API during password reset and account creation #170

Open
wants to merge 2 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions includes/classes/Authentication/Passwords.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ class Passwords {

use Singleton;

/**
* Stores the Have I Been Pwned API URL
*/
const HIBP_API_URL = 'https://api.pwnedpasswords.com/range/';
const HIBP_CACHE_KEY = 'tenup_experience_hibp';

/**
* Setup hooks
*
Expand Down Expand Up @@ -307,6 +313,11 @@ public function validate_strong_password( $errors, $user_data ) {
return $errors;
}

// Validate the password against the Have I Been Pwned API.
if ( ! $this->is_password_secure( $password ) && is_wp_error( $errors ) ) {
$errors->add( 'password_reset_error', __( '<strong>ERROR:</strong> The password entered may have been included in a data breach and is not considered safe to use. Please choose another.', 'tenup' ) );
}

// Should a strong password be enforced for this user?
if ( $user_id ) {

Expand Down Expand Up @@ -374,4 +385,66 @@ public function enforce_for_user( $user_id ) {

return $enforce;
}

/**
* Check if password is secure by querying the Have I Been Pwned API.
*
* @param string $password Password to validate.
*
* @return bool True if password is ok, false if it shows up in a breach.
*/
protected function is_password_secure( $password ): bool {
// Default
$is_password_secure = true;

// Allow opt-out of Have I Been Pwned check through a constant or filter.
if (
( defined( 'TENUP_EXPERIENCE_DISABLE_HIBP' ) && TENUP_EXPERIENCE_DISABLE_HIBP ) ||
apply_filters( 'tenup_experience_disable_hibp', false, $password )
) {
return true;
}

$hash = strtoupper( sha1( $password ) );
$prefix = substr( $hash, 0, 5 );
$suffix = substr( $hash, 5 );

$cached_result = wp_cache_get( $prefix . $suffix, self::HIBP_CACHE_KEY );

if ( false !== $cached_result || false ) { // remove || false; only used for testing
return $cached_result;
}

$response = wp_remote_get( self::HIBP_API_URL . $prefix, [ 'user-agent' => '10up Experience WordPress Plugin' ] );

// Allow for a failed request to the HIPB API.
// Don't cache the result if the request failed.
if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) {
return true;
}

$body = wp_remote_retrieve_body( $response );

// Allow for a failed request to the HIPB API.
// Don't cache the result if the request failed.
if ( is_wp_error( $body ) ) {
return true;
}

$lines = explode( "\r\n", $body );

foreach ( $lines as $line ) {
$parts = explode( ':', $line );

// If the suffix is found in the response, the password may be in a breach.
if ( $parts[0] === $suffix ) {
$is_password_secure = false;
}
}

// Cache the result for 4 hours.
wp_cache_set( $prefix . $suffix, (int) $is_password_secure, self::HIBP_CACHE_KEY, 60 * 60 * 4 );

return $is_password_secure;
}
}
Loading