Files
mahak-authenticate-bridge/test/integration-authentication.php
MeghdadFadaee 5f422ac0e4
Build plugin release / release (push) Successful in 5s
feat: add configurable role for Mahak-created users
2026-08-24 10:39:32 +03:30

404 lines
13 KiB
PHP

<?php
declare(strict_types=1);
const TEST_SITE_URL = 'http://127.0.0.1';
const TEST_PLUGIN = 'mahak-authenticate-bridge/dabestaniha-authenticate-bridge.php';
const TEST_SECRET = 'integration-test-secret';
const TEST_EMAIL = 'mahak-auth-user@example.test';
const TEST_NAME = 'Mahak Auth User';
const TEST_ROLE_EMAIL = 'mahak-role-user@example.test';
function fail(string $message): void
{
fwrite(STDERR, "[FAIL] $message\n");
exit(1);
}
function pass(string $message): void
{
echo "[OK] $message\n";
}
function assert_true(bool $condition, string $message): void
{
if (!$condition) {
fail($message);
}
}
function boot_wordpress(): void
{
$wpLoad = '/var/www/html/wp-load.php';
if (!is_file($wpLoad)) {
fail("WordPress bootstrap file was not found at $wpLoad.");
}
if (!defined('WP_INSTALLING')) {
define('WP_INSTALLING', true);
}
ob_start();
require_once $wpLoad;
ob_end_clean();
}
function install_wordpress(): void
{
require_once ABSPATH.'wp-admin/includes/upgrade.php';
if (!is_blog_installed()) {
wp_install(
'Mahak Authenticate Bridge Test',
'admin',
'admin@example.test',
true,
'',
'admin-password'
);
}
update_option('siteurl', TEST_SITE_URL);
update_option('home', TEST_SITE_URL);
pass('Fresh WordPress database installed');
}
function activate_and_configure_plugin(): void
{
require_once ABSPATH.'wp-admin/includes/plugin.php';
if (!is_plugin_active(TEST_PLUGIN)) {
ob_start();
$result = activate_plugin(TEST_PLUGIN);
$activationOutput = trim((string) ob_get_clean());
if (is_wp_error($result)) {
fail('Plugin activation failed: '.$result->get_error_message());
}
assert_true($activationOutput === '', 'Plugin activation produced unexpected output: '.$activationOutput);
}
assert_true(is_plugin_active(TEST_PLUGIN), 'Plugin was not activated.');
update_option('mahak_jwt_secret', TEST_SECRET);
update_option('mahak_user_identifier', 'email');
update_option('mahak_login_route', 'mahak/login');
update_option('mahak_after_login_route', 'wp-admin/profile.php');
update_option('default_role', 'subscriber');
update_option('mahak_new_user_role', 'wordpress_default');
pass('Plugin activated and configured');
}
function base64_url_encode(string $value): string
{
return rtrim(strtr(base64_encode($value), '+/', '-_'), '=');
}
function make_jwt(array $payload, string $secret): string
{
$header = base64_url_encode(json_encode(['typ' => 'JWT', 'alg' => 'HS256'], JSON_THROW_ON_ERROR));
$body = base64_url_encode(json_encode($payload, JSON_THROW_ON_ERROR));
$signature = base64_url_encode(hash_hmac('sha256', $header.'.'.$body, $secret, true));
return $header.'.'.$body.'.'.$signature;
}
function http_request(string $url, string $cookieHeader = ''): array
{
$headers = "Connection: close\r\n";
if ($cookieHeader !== '') {
$headers .= "Cookie: $cookieHeader\r\n";
}
$context = stream_context_create([
'http' => [
'ignore_errors' => true,
'timeout' => 20,
'follow_location' => 0,
'max_redirects' => 1,
'header' => $headers,
],
]);
$body = @file_get_contents($url, false, $context);
$responseHeaders = $http_response_header ?? [];
$status = 0;
if (isset($responseHeaders[0]) && preg_match('/^HTTP\/\S+\s+(\d+)/', $responseHeaders[0], $matches)) {
$status = (int) $matches[1];
}
return [
'status' => $status,
'headers' => $responseHeaders,
'body' => $body === false ? '' : $body,
];
}
function wait_for_http(): void
{
for ($attempt = 1; $attempt <= 60; $attempt++) {
$response = http_request(TEST_SITE_URL.'/wp-login.php');
if ($response['status'] > 0) {
pass('WordPress HTTP server is reachable');
return;
}
sleep(1);
}
fail('WordPress HTTP server did not become reachable.');
}
function header_contains(array $headers, string $needle): bool
{
foreach ($headers as $header) {
if (stripos($header, $needle) !== false) {
return true;
}
}
return false;
}
function cookie_header_from_response(array $headers): string
{
$cookies = [];
foreach ($headers as $header) {
if (stripos($header, 'Set-Cookie:') !== 0) {
continue;
}
$cookie = trim(substr($header, strlen('Set-Cookie:')));
$cookiePair = explode(';', $cookie, 2)[0] ?? '';
if ($cookiePair !== '') {
$cookies[] = $cookiePair;
}
}
return implode('; ', $cookies);
}
function test_valid_authentication(): void
{
$token = make_jwt([
'iat' => time() - 10,
'exp' => time() + 300,
'data' => [
'name' => TEST_NAME,
'email' => TEST_EMAIL,
'mobile' => '9123456789',
'username' => 'mahak-auth-user',
],
], TEST_SECRET);
$response = http_request(TEST_SITE_URL.'/mahak/login/?token='.rawurlencode($token));
assert_true($response['status'] === 302, 'Valid authentication request should return HTTP 302.');
assert_true(
header_contains($response['headers'], 'Set-Cookie: wordpress_logged_in_'),
'Valid authentication request did not issue a WordPress logged-in cookie.'
);
assert_true(
header_contains($response['headers'], 'Location: '.TEST_SITE_URL.'/wp-admin/profile.php'),
'Valid authentication request did not redirect to the configured route.'
);
$user = get_user_by('email', TEST_EMAIL);
assert_true($user !== false, 'Authenticated user was not created.');
assert_true($user->display_name === TEST_NAME, 'Authenticated user display name was not saved.');
assert_true(
$user->roles === ['subscriber'],
'New user did not receive the plugin-configured Subscriber role.'
);
$cookieHeader = cookie_header_from_response($response['headers']);
$profileResponse = http_request(TEST_SITE_URL.'/wp-admin/profile.php', $cookieHeader);
assert_true(
$profileResponse['status'] !== 302 || !header_contains($profileResponse['headers'], 'wp-login.php'),
'Issued cookies did not authenticate a follow-up WordPress admin request.'
);
pass('Valid JWT creates a Subscriber and issued cookies authenticate follow-up requests');
}
function test_configured_new_user_role_is_enforced(): void
{
$smsRoleOverride = function (int $userId): void {
(new WP_User($userId))->set_role('administrator');
};
add_action('user_register', $smsRoleOverride);
$controller = new Dabestaniha\AuthenticateBridge\App\Http\Controllers\AuthenticateController();
$userId = $controller->find_or_create_user('email', TEST_ROLE_EMAIL, 'Role Test User');
remove_action('user_register', $smsRoleOverride);
$user = get_userdata($userId);
assert_true(
$user !== false && $user->roles === ['subscriber'],
'Configured role was not enforced after another plugin changed the role during user_register.'
);
$user->set_role('author');
$existingUserId = $controller->find_or_create_user('email', TEST_ROLE_EMAIL, 'Role Test User');
$existingUser = get_userdata($existingUserId);
assert_true($existingUserId === $userId, 'Existing user was not reused.');
assert_true(
$existingUser !== false && $existingUser->roles === ['author'],
'Existing user role was changed during authentication.'
);
pass('Configured role overrides creation hooks without changing existing users');
}
function test_invalid_authentication(): void
{
$token = make_jwt([
'iat' => time() - 10,
'exp' => time() + 300,
'data' => [
'name' => 'Invalid User',
'email' => 'invalid-user@example.test',
],
], TEST_SECRET);
$response = http_request(TEST_SITE_URL.'/mahak/login/?token='.rawurlencode($token.'tampered'));
assert_true(
$response['status'] === 500,
'Invalid authentication request should return HTTP 500. Got HTTP '.$response['status'].' with body: '.substr($response['body'], 0, 200)
);
assert_true(
!header_contains($response['headers'], 'Set-Cookie: wordpress_logged_in_'),
'Invalid authentication request issued a logged-in cookie.'
);
assert_true(
strpos($response['body'], 'JWT signature is invalid.') === false,
'Debug details were exposed while debug mode was disabled.'
);
pass('Invalid JWT is rejected without login cookies or debug details');
}
function test_debug_error_details(): void
{
update_option('mahak_debug_mode', 1);
$futureIat = time() + 300;
$token = make_jwt([
'iat' => $futureIat,
'exp' => $futureIat + 600,
'data' => [
'name' => 'Future User',
'email' => 'future-user@example.test',
],
], TEST_SECRET);
$response = http_request(TEST_SITE_URL.'/mahak/login/?token='.rawurlencode($token));
assert_true($response['status'] === 500, 'Debug error response should return HTTP 500.');
assert_true(
strpos($response['body'], 'JWT cannot be used before iat.') !== false,
'Debug response did not contain the underlying exception message.'
);
assert_true(
strpos($response['body'], 'UnexpectedValueException') !== false,
'Debug response did not contain the exception class.'
);
assert_true(
strpos($response['body'], 'WordPress server diagnostics') !== false
&& strpos($response['body'], 'Unix timestamp') !== false
&& strpos($response['body'], 'WordPress timezone') !== false,
'Debug response did not contain WordPress server clock and timezone details.'
);
assert_true(
strpos($response['body'], 'JWT timing claims') !== false
&& strpos($response['body'], (string) $futureIat) !== false
&& strpos($response['body'], 'seconds vs server') !== false,
'Debug response did not contain safe JWT timing diagnostics.'
);
assert_true(
strpos($response['body'], 'Copy Markdown') !== false
&& strpos($response['body'], '# Mahak Authentication Error') !== false,
'Debug response did not contain a copyable Markdown report.'
);
assert_true(
strpos($response['body'], TEST_SECRET) === false && strpos($response['body'], $token) === false,
'Debug response exposed the JWT token or signing secret.'
);
assert_true(
strpos($response['body'], 'Sanitized stack trace') !== false,
'Debug response did not contain the sanitized stack trace.'
);
update_option('mahak_debug_mode', 0);
pass('Debug report includes clock diagnostics and Markdown without token or secret leakage');
}
function test_plugin_update_discovery(): void
{
$packageUrl = 'https://mahgit.ir/dabestaniha/mahak-authenticate-bridge/releases/download/v2.4.0/mahak-authenticate-bridge.zip';
$mockRelease = function ($response, array $request, string $url) use ($packageUrl) {
if ($url !== 'https://mahgit.ir/api/v1/repos/dabestaniha/mahak-authenticate-bridge/releases/latest') {
return $response;
}
return [
'headers' => [],
'body' => json_encode([
'tag_name' => 'v2.4.0',
'html_url' => 'https://mahgit.ir/dabestaniha/mahak-authenticate-bridge/releases/tag/v2.4.0',
'body' => 'Test release',
'draft' => false,
'prerelease' => false,
'assets' => [[
'name' => 'mahak-authenticate-bridge.zip',
'browser_download_url' => $packageUrl,
]],
], JSON_THROW_ON_ERROR),
'response' => ['code' => 200, 'message' => 'OK'],
'cookies' => [],
'filename' => null,
];
};
delete_site_transient('mahak_authenticate_bridge_gitea_release');
add_filter('pre_http_request', $mockRelease, 10, 3);
$updates = apply_filters('pre_set_site_transient_update_plugins', (object) [
'checked' => [TEST_PLUGIN => MAHAK_AUTHENTICATE_BRIDGE_VERSION],
'response' => [],
]);
remove_filter('pre_http_request', $mockRelease, 10);
assert_true(isset($updates->response[TEST_PLUGIN]), 'A newer Mahgit release was not offered as a WordPress update.');
assert_true($updates->response[TEST_PLUGIN]->new_version === '2.4.0', 'The offered plugin version was incorrect.');
assert_true($updates->response[TEST_PLUGIN]->package === $packageUrl, 'The release package URL was incorrect.');
pass('Published Mahgit releases are discovered by the WordPress updater');
}
boot_wordpress();
install_wordpress();
activate_and_configure_plugin();
wait_for_http();
test_valid_authentication();
test_configured_new_user_role_is_enforced();
test_invalid_authentication();
test_debug_error_details();
test_plugin_update_discovery();
echo "[OK] Authentication integration test passed\n";