Skip to content

Commit

Permalink
Support WP application passwords
Browse files Browse the repository at this point in the history
Fixes #22

Handles API requests and updates application passwords to bcrypt when
applicable.
  • Loading branch information
swalkinshaw committed Oct 30, 2021
1 parent e7bad0d commit 0136937
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 12 deletions.
6 changes: 4 additions & 2 deletions phpcs.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
<arg value="sp"/>

<!-- PHP compatibility takes precedent over PSR12 -->
<rule ref="PHPCompatibility"/>
<rule ref="PHPCompatibility">
<exclude-pattern>tests/*</exclude-pattern>
</rule>
<rule ref="PSR12"/>

<!-- Support for PHP 5.6+ -->
Expand All @@ -24,4 +26,4 @@
<rule ref="PSR1.Methods.CamelCapsMethodName.NotCamelCaps">
<exclude-pattern>tests/*</exclude-pattern>
</rule>
</ruleset>
</ruleset>
2 changes: 0 additions & 2 deletions tests/Constants.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

namespace Roots\PasswordBcrypt\Tests;

// phpcs:disable PHPCompatibility.Classes.NewConstVisibility.Found

class Constants
{
/**
Expand Down
2 changes: 0 additions & 2 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
use Brain\Monkey;
use Mockery\Adapter\Phpunit\MockeryTestCase;

// phpcs:disable PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.voidFound

class TestCase extends MockeryTestCase
{
use MocksWpdb;
Expand Down
73 changes: 73 additions & 0 deletions tests/Unit/ApplicationPasswordTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
<?php

namespace Roots\PasswordBcrypt\Tests\Unit;

use Roots\PasswordBcrypt\Tests\TestCase;
use Roots\PasswordBcrypt\Tests\Constants;
use Mockery;

use function Brain\Monkey\Functions\expect;
use function Brain\Monkey\Filters\expectApplied;

class ApplicationPasswordTest extends TestCase
{

/** @test */
public function phpass_application_passwords_should_be_verified_and_converted_to_bcrypt()
{
require_once __DIR__ . '/../WPApplicationPasswords.php';

expectApplied('application_password_is_api_request')
->andReturn(true);

$this
->wpHasher()
->shouldReceive('CheckPassword')
->times(3)
->andReturnValues([true, true, false]);

expect('update_user_meta')
->once()
->withArgs(function (...$args) {
[$userId, $metaKey, $passwords] = $args;

if ($userId != Constants::USER_ID) {
return false;
}

if ($metaKey != \WP_Application_Passwords::USERMETA_KEY_APPLICATION_PASSWORDS) {
return false;
}

if (count($passwords) != 3) {
return false;
}

if (!key_exists(0, $passwords)) {
return false;
}

$passwords = array_map((function ($item) {
return $item['password'];
}), $passwords);

[$pw1, $pw2, $pw3] = $passwords;

if (!password_verify(Constants::PASSWORD, $pw1)) {
return false;
}

if (!password_verify(Constants::PASSWORD, $pw2)) {
return false;
}

if (password_verify(Constants::PASSWORD, $pw3)) {
return false;
}

return true;
});

$hash = wp_set_password(Constants::PASSWORD, Constants::USER_ID);
}
}
25 changes: 25 additions & 0 deletions tests/WPApplicationPasswords.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<?php

use Roots\PasswordBcrypt\Tests\Constants;

// phpcs:disable PSR1.Classes.ClassDeclaration.MissingNamespace
// phpcs:disable Squiz.Classes.ValidClassName.NotCamelCaps
class WP_Application_Passwords
{
public const USERMETA_KEY_APPLICATION_PASSWORDS = '_application_passwords';

public static function get_user_application_passwords($userId)
{
return [
[
'password' => Constants::PHPPASS_HASH
],
[
'password' => Constants::BCRYPT_HASH
],
[
'password' => Constants::INVALID_HASH
]
];
}
}
50 changes: 44 additions & 6 deletions wp-password-bcrypt.php
Original file line number Diff line number Diff line change
Expand Up @@ -86,14 +86,52 @@ function wp_hash_password($password)
function wp_set_password($password, $user_id)
{
$hash = wp_hash_password($password);
global $wpdb;
$is_api_request = apply_filters(
'application_password_is_api_request',
(defined('XMLRPC_REQUEST') && XMLRPC_REQUEST) ||
(defined('REST_REQUEST') && REST_REQUEST)
);

if (! $is_api_request) {
global $wpdb;

$wpdb->update($wpdb->users, [
'user_pass' => $hash,
'user_activation_key' => ''
], ['ID' => $user_id]);

clean_user_cache($user_id);

return $hash;
}

if (
! class_exists('WP_Application_Passwords') ||
empty($passwords = WP_Application_Passwords::get_user_application_passwords($user_id))
) {
return;
}

global $wp_hasher;

$wpdb->update($wpdb->users, [
'user_pass' => $hash,
'user_activation_key' => ''
], ['ID' => $user_id]);
if (empty($wp_hasher)) {
require_once ABSPATH . WPINC . '/class-phpass.php';
$wp_hasher = new PasswordHash(8, true);
}

clean_user_cache($user_id);
foreach ($passwords as $key => $value) {
if (! $wp_hasher->CheckPassword($password, $value['password'])) {
continue;
}

$passwords[$key]['password'] = $hash;
}

update_user_meta(
$user_id,
WP_Application_Passwords::USERMETA_KEY_APPLICATION_PASSWORDS,
$passwords
);

return $hash;
}

0 comments on commit 0136937

Please sign in to comment.