diff --git a/includes/class-spacefast-oauth.php b/includes/class-spacefast-oauth.php index 01badd3..749d7f7 100644 --- a/includes/class-spacefast-oauth.php +++ b/includes/class-spacefast-oauth.php @@ -94,7 +94,11 @@ public function begin( string $mode ): array { 'code_challenge_method' => 'S256', 'resource' => $resource, ); - return array( 'ok' => true, 'message' => '', 'url' => add_query_arg( $query, $api_url . '/v1/auth/oauth2/authorize' ) ); + // add_query_arg() deliberately leaves values unencoded. The callback URL + // has its own query string, so using it here would turn the callback's + // second parameter into a top-level authorization parameter. + $url = $api_url . '/v1/auth/oauth2/authorize?' . http_build_query( $query, '', '&', PHP_QUERY_RFC3986 ); + return array( 'ok' => true, 'message' => '', 'url' => $url ); } /** @return array{ok:bool,message:string} */ diff --git a/readme.txt b/readme.txt index 5a3ee72..e7b81e0 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: spacefast Tags: static site, headless cms, simply static, deployment Requires at least: 6.5 Requires PHP: 8.1 -Stable tag: 0.3.1 +Stable tag: 0.3.2 License: GPLv2 or later Publish a Simply Static export to Spacefast, or rebuild a headless site when WordPress content changes. @@ -31,6 +31,9 @@ OAuth access is limited to the Team you authorize and the mode you choose. WordP == Changelog == += 0.3.2 = +* Preserve the complete WordPress admin callback URL during Spacefast authorization. + = 0.3.1 = * Debounce automatic headless builds until public content has been quiet for 60 seconds. * Keep manual builds immediate and avoid duplicate scheduled delivery afterward. diff --git a/spacefast-wordpress.php b/spacefast-wordpress.php index 17076b6..f390b85 100644 --- a/spacefast-wordpress.php +++ b/spacefast-wordpress.php @@ -3,7 +3,7 @@ * Plugin Name: Spacefast * Plugin URI: https://spacefast.com/ * Description: Publishes static WordPress exports or rebuilds a headless Spacefast site. - * Version: 0.3.1 + * Version: 0.3.2 * Update URI: https://github.com/spacefast/wordpress * Requires at least: 6.5 * Requires PHP: 8.1 @@ -14,7 +14,7 @@ defined( 'ABSPATH' ) || exit; -define( 'SPACEFAST_WORDPRESS_VERSION', '0.3.1' ); +define( 'SPACEFAST_WORDPRESS_VERSION', '0.3.2' ); define( 'SPACEFAST_WORDPRESS_FILE', __FILE__ ); require_once __DIR__ . '/includes/class-spacefast-settings.php'; diff --git a/tests/acceptance/wordpress.php b/tests/acceptance/wordpress.php index d8504df..dd8a169 100644 --- a/tests/acceptance/wordpress.php +++ b/tests/acceptance/wordpress.php @@ -71,7 +71,7 @@ function spacefast_accept( bool $condition, string $message ): void { ); $plugin = get_plugin_data( WP_PLUGIN_DIR . '/spacefast-wordpress/spacefast-wordpress.php', false, false ); -spacefast_accept( '0.3.1' === $plugin['Version'], 'Unexpected plugin version.' ); +spacefast_accept( '0.3.2' === $plugin['Version'], 'Unexpected plugin version.' ); spacefast_accept( 'https://github.com/spacefast/wordpress' === $plugin['UpdateURI'], 'Update URI is missing.' ); Spacefast_Settings::disconnect(); diff --git a/tests/behavior.php b/tests/behavior.php index 65835ce..fe6b0b9 100644 --- a/tests/behavior.php +++ b/tests/behavior.php @@ -85,7 +85,13 @@ function admin_url( string $path = '' ): string { return 'https://wp.example.test/wp-admin/' . ltrim( $path, '/' ); } function add_query_arg( array $args, string $url ): string { - return $url . ( str_contains( $url, '?' ) ? '&' : '?' ) . http_build_query( $args, '', '&', PHP_QUERY_RFC3986 ); + // WordPress build_query() leaves values unencoded. This matters when a + // query argument is itself a URL with its own query string. + $pairs = array(); + foreach ( $args as $key => $value ) { + $pairs[] = $key . '=' . $value; + } + return $url . ( str_contains( $url, '?' ) ? '&' : '?' ) . implode( '&', $pairs ); } function home_url(): string { return 'https://wp.example.test'; @@ -249,6 +255,12 @@ function raw_response( int $status, array $data = array() ): array { 'binds tokens to the Spacefast API resource' ); check( str_contains( (string) $authorization['url'], 'code_challenge_method=S256' ), 'authorization uses PKCE S256' ); +$authorization_query = array(); +parse_str( (string) parse_url( (string) $authorization['url'], PHP_URL_QUERY ), $authorization_query ); +check( + Spacefast_OAuth::callback_url() === ( $authorization_query['redirect_uri'] ?? '' ), + 'authorization preserves the complete query-bearing WordPress callback URI' +); $pending = get_option( Spacefast_OAuth::PENDING_OPTION ); $finished = $oauth->finish( 'authorization-code', (string) $pending['state'] ); check( true === $finished['ok'], 'valid callback exchanges its code and loads Team-scoped choices' );