Skip to content

Commit

Permalink
[Blueprints] Reload after autologin to set login cookies during boot (#…
Browse files Browse the repository at this point in the history
…1914)

Co-authored-by: Alex Kirk <akirk@users.noreply.github.com>
  • Loading branch information
bgrgicak and akirk authored Oct 22, 2024
1 parent b0d3f41 commit 97f5811
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 45 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import { loadNodeRuntime } from '@php-wasm/node';
import { readFileSync } from 'fs';
import { join } from 'path';
import { login } from './login';
import { PHPRequest, PHPRequestHandler } from '@php-wasm/universal';

describe('Blueprint step enableMultisite', () => {
let handler: PHPRequestHandler;
async function doBootWordPress(options: { absoluteUrl: string }) {
const requestHandler = await bootWordPress({
handler = await bootWordPress({
createPhpRuntime: async () =>
await loadNodeRuntime(RecommendedPHPVersion),
siteUrl: options.absoluteUrl,
Expand All @@ -26,11 +28,21 @@ describe('Blueprint step enableMultisite', () => {
),
},
});
const php = await requestHandler.getPrimaryPhp();
const php = await handler.getPrimaryPhp();

return { php, requestHandler };
return { php, handler };
}

const requestFollowRedirects = async (request: PHPRequest) => {
let response = await handler.request(request);
while (response.httpStatusCode === 302) {
response = await handler.request({
url: response.headers['location'][0],
});
}
return response;
};

[
{
absoluteUrl: 'http://playground-domain/scope:987987/',
Expand All @@ -44,7 +56,7 @@ describe('Blueprint step enableMultisite', () => {
it(`should set the WP_ALLOW_MULTISITE and SUBDOMAIN_INSTALL constants on a ${
scoped ? 'scoped' : 'scopeless'
} URL`, async () => {
const { php, requestHandler } = await doBootWordPress({
const { php } = await doBootWordPress({
absoluteUrl,
});
await enableMultisite(php, {});
Expand All @@ -69,7 +81,7 @@ describe('Blueprint step enableMultisite', () => {
* the admin bar includes the multisite menu.
*/
await login(php, {});
const response = await requestHandler.request({
const response = await requestFollowRedirects({
url: '/',
});
expect(response.httpStatusCode).toEqual(200);
Expand Down
65 changes: 37 additions & 28 deletions packages/playground/blueprints/src/lib/steps/login.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { PHP } from '@php-wasm/universal';
import { PHP, PHPRequest } from '@php-wasm/universal';
import { RecommendedPHPVersion } from '@wp-playground/common';
import {
getSqliteDatabaseModule,
Expand All @@ -9,6 +9,7 @@ import { PHPRequestHandler } from '@php-wasm/universal';
import { bootWordPress } from '@wp-playground/wordpress';
import { loadNodeRuntime } from '@php-wasm/node';
import { defineWpConfigConsts } from './define-wp-config-consts';
import { joinPaths, phpVar } from '@php-wasm/util';

describe('Blueprint step login', () => {
let php: PHP;
Expand All @@ -25,9 +26,19 @@ describe('Blueprint step login', () => {
php = await handler.getPrimaryPhp();
});

const requestFollowRedirects = async (request: PHPRequest) => {
let response = await handler.request(request);
while (response.httpStatusCode === 302) {
response = await handler.request({
url: response.headers['location'][0],
});
}
return response;
};

it('should log the user in', async () => {
await login(php, {});
const response = await handler.request({
const response = await requestFollowRedirects({
url: '/',
});
expect(response.httpStatusCode).toBe(200);
Expand All @@ -36,28 +47,11 @@ describe('Blueprint step login', () => {

it('should log the user into wp-admin', async () => {
await login(php, {});
const initialResponse = await handler.request({
const response = await requestFollowRedirects({
url: '/wp-admin/',
});
expect(initialResponse.httpStatusCode).toBe(302);
expect(initialResponse.headers['location']).toHaveLength(1);
const loginRedirectUrl = new URL(
initialResponse.headers['location'][0]
);
expect(loginRedirectUrl.pathname).toBe('/wp-login.php');

const loginResponse = await handler.request({
url: loginRedirectUrl.toString(),
});
expect(loginResponse.httpStatusCode).toBe(302);
const adminRedirectUrl = new URL(loginResponse.headers['location'][0]);
expect(adminRedirectUrl.pathname).toBe('/wp-admin/');

const adminResponse = await handler.request({
url: adminRedirectUrl.toString(),
});
expect(adminResponse.httpStatusCode).toBe(200);
expect(adminResponse.text).toContain('Dashboard');
expect(response.httpStatusCode).toBe(200);
expect(response.text).toContain('Dashboard');
});

it('should log the user in if the playground_force_auto_login_as_user query parameter is set', async () => {
Expand All @@ -66,15 +60,30 @@ describe('Blueprint step login', () => {
PLAYGROUND_FORCE_AUTO_LOGIN_ENABLED: true,
},
});
const initialResponse = await handler.request({
const response = await requestFollowRedirects({
url: '/?playground_force_auto_login_as_user=admin',
});
expect(initialResponse.httpStatusCode).toBe(200);
expect(response.httpStatusCode).toBe(200);
expect(response.text).toContain('Dashboard');
});

const adminResponse = await handler.request({
url: '/wp-admin/',
it('should set WordPress login cookie after login', async () => {
await login(php, {});
await php.writeFile(
'/wordpress/nonce-test.php',
`<?php
require_once ${phpVar(joinPaths(handler.documentRoot, 'wp-load.php'))};
if (!empty($_COOKIE) && array_filter(array_keys($_COOKIE), function($key) {
return strpos($key, 'wordpress_logged_in_') === 0;
})
) {
echo '1';
}
`
);
const response = await requestFollowRedirects({
url: '/nonce-test.php',
});
expect(adminResponse.httpStatusCode).toBe(200);
expect(adminResponse.text).toContain('Dashboard');
expect(response.text).toBe('1');
});
});
13 changes: 13 additions & 0 deletions packages/playground/remote/src/lib/worker-thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,19 @@ export class PlaygroundWorkerEndpoint extends PHPWorker {
const primaryPhp = await requestHandler.getPrimaryPhp();
await this.setPrimaryPHP(primaryPhp);

await primaryPhp.writeFile(
'/wordpress/nonce-test.php',
`<?php
require_once '/wordpress/wp-load.php';
error_log(print_r($_COOKIE, true));
if (!empty($_COOKIE)) {
echo array_filter(array_keys($_COOKIE), function($key) {
return strpos($key, 'wordpress_logged_in_') === 0;
}) ? '1' : '0';
}
`
);

// NOTE: We need to derive the loaded WP version or we might assume WP loaded
// from browser storage is the default version when it is actually something else.
// Assuming an incorrect WP version would break remote asset retrieval for minified
Expand Down
5 changes: 4 additions & 1 deletion packages/playground/website-deployment/custom-redirects.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
<?php

/**
* Initialize the Playground request handler
* on the Playground.WordPress.net server.
*/
if ( 'cli' !== php_sapi_name() ) {
define('PLAYGROUND_DEBUG', false);
require_once __DIR__ . '/custom-redirects-lib.php';
Expand Down
53 changes: 42 additions & 11 deletions packages/playground/wordpress/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,10 +113,26 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) {
}
return false;
}
/**
* Logs the user in on their first visit if the Playground runtime told us to.
*/
function playground_auto_login() {
/**
* The redirect should only run if the current PHP request is
* a HTTP request. If it's a PHP CLI run, we can't login the user
* because logins require cookies which aren't available in the CLI.
*
* Currently all Playground requests use the "cli" SAPI name
* to ensure support for WP-CLI, so the best way to distinguish
* between a CLI run and an HTTP request is by checking if the
* $_SERVER['REQUEST_URI'] global is set.
*
* If $_SERVER['REQUEST_URI'] is not set, we assume it's a CLI run.
*/
if (empty($_SERVER['REQUEST_URI'])) {
return;
}
$user_name = playground_get_username_for_auto_login();
if ( false === $user_name ) {
return;
Expand All @@ -131,27 +147,42 @@ export async function setupPlatformLevelMuPlugins(php: UniversalPHP) {
if (!$user) {
return;
}
/**
* This approach is described in a comment on
* https://developer.wordpress.org/reference/functions/wp_set_current_user/
*/
wp_set_current_user( $user->ID, $user->user_login );
wp_set_auth_cookie( $user->ID );
do_action( 'wp_login', $user->user_login, $user );
setcookie('playground_auto_login_already_happened', '1');
}
/**
* Reload page to ensure the user is logged in correctly.
* WordPress uses cookies to determine if the user is logged in,
* so we need to reload the page to ensure the cookies are set.
*
* Both WordPress home url and REQUEST_URI may include the site scope
* subdirectory.
* To prevent the redirect from including two scopes, we need to
* remove the scope from the REQUEST_URI if it's present.
*/
$redirect_url = $_SERVER['REQUEST_URI'];
if (strpos($redirect_url, '/scope:') === 0) {
$parts = explode('/', $redirect_url);
$redirect_url = '/' . implode('/', array_slice($parts, 2));
}
wp_redirect(
home_url($redirect_url),
302
);
exit;
}
/**
* Autologin users from the wp-login.php page.
*
* The wp hook isn't triggered on
**/
add_action('init', function() {
playground_auto_login();
/**
* Check if the request is for the login page.
*/
if (is_login() && is_user_logged_in() && !empty($_GET['redirect_to'])) {
wp_redirect($_GET['redirect_to']);
exit;
}
}, 1);
add_action('init', 'playground_auto_login', 1);
/**
* Disable the Site Admin Email Verification Screen for any session started
Expand Down

0 comments on commit 97f5811

Please sign in to comment.