Skip to content
Open
Show file tree
Hide file tree
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
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,12 @@
## https://docs.npmjs.com/misc/faq#should-i-check-my-node_modules-folder-into-git
node_modules

# Composer-generated lock file
# Composer-generated lock file and dependencies
composer.lock
vendor

# PHPUnit cache
.phpunit.result.cache

# Book build output
_book
Expand Down
1 change: 1 addition & 0 deletions inc/namespace.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ function bootstrap() {
add_filter( 'rest_authentication_errors', __NAMESPACE__ . '\\Authentication\\maybe_report_errors' );
add_filter( 'rest_index', __NAMESPACE__ . '\\register_in_index' );
add_action( 'rest_api_init', __NAMESPACE__ . '\\Endpoints\\register' );
add_action( 'parse_request', __NAMESPACE__ . '\\Well_Known\\maybe_serve_document' );

// Internal default hooks.
add_filter( 'oauth2.grant_types', __NAMESPACE__ . '\\register_grant_types', 0 );
Expand Down
112 changes: 112 additions & 0 deletions inc/well-known/namespace.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php
/**
*
* @package WordPress
* @subpackage JSON API
*/

namespace WP\OAuth2\Well_Known;

use WP\OAuth2;

const AUTHORIZATION_SERVER_DOCUMENT = 'oauth-authorization-server';
const AUTHORIZATION_SERVER_PATH = '/.well-known/' . AUTHORIZATION_SERVER_DOCUMENT;

/**
* Intercepts `.well-known/` requests before WordPress tries to match a
* post/page, and serves the matching discovery document.
*/
function maybe_serve_document() {
$document = match_well_known_path( $_SERVER['REQUEST_URI'] ?? '' ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput

if ( AUTHORIZATION_SERVER_DOCUMENT === $document ) {
serve_authorization_server_metadata();
}
}

/**
* Works out which discovery document, if any, a request URI is asking for.
*
* Tolerates a trailing slash: some hosts redirect extensionless GET paths to
* their trailing-slash form before WordPress runs, and clients following
* that redirect must still get the document.
*
* @param string $request_uri Raw request URI, as in `$_SERVER['REQUEST_URI']`.
* @return string|null `oauth-authorization-server`, or null.
*/
function match_well_known_path( $request_uri ) {
$path = untrailingslashit( (string) wp_parse_url( $request_uri, PHP_URL_PATH ) );

if ( AUTHORIZATION_SERVER_PATH === $path ) {
return AUTHORIZATION_SERVER_DOCUMENT;
}

return null;
}

/**
* Outputs the RFC 8414 authorization server metadata document and exits.
*/
function serve_authorization_server_metadata() {
$metadata = [
'issuer' => home_url(),
'authorization_endpoint' => OAuth2\get_authorization_url(),
'token_endpoint' => OAuth2\get_token_url(),
'grant_types_supported' => get_grant_types_supported(),
'response_types_supported' => get_response_types_supported(),
'token_endpoint_auth_methods_supported' => [ 'none', 'client_secret_post', 'client_secret_basic' ],
];

/**
* Filter the OAuth2 authorization server metadata returned at
* `/.well-known/oauth-authorization-server`.
*
* @param array $metadata RFC 8414 metadata document.
*/
$metadata = apply_filters( 'oauth2.well_known_authorization_server_metadata', $metadata );

send_json_document( $metadata );
}

/**
* Gets the grant types the token endpoint accepts.
*
* Combines the registered authorization grant type handlers with
* `client_credentials`, which the token endpoint supports directly rather
* than through the `oauth2.grant_types` filter.
*
* @return string[] Grant type identifiers.
*/
function get_grant_types_supported() {
$grant_types = array_keys( OAuth2\get_grant_types() );
$grant_types[] = 'client_credentials';

return array_values( array_unique( $grant_types ) );
}

/**
* Gets the response types advertised by the registered grant type handlers.
*
* @return string[] Response type codes, e.g. `code`, `token`.
*/
function get_response_types_supported() {
$response_types = [];

foreach ( OAuth2\get_grant_types() as $handler ) {
$response_types[] = $handler->get_response_type_code();
}

return array_values( array_unique( $response_types ) );
}

/**
* Sends a JSON discovery document and exits.
*
* @param array $document Data to encode as the response body.
*/
function send_json_document( $document ) {
header( 'Content-Type: application/json' );
header( 'Access-Control-Allow-Origin: *' );
echo wp_json_encode( $document );
exit;
}
1 change: 1 addition & 0 deletions plugin.php
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
require __DIR__ . '/inc/endpoints/namespace.php';
require __DIR__ . '/inc/endpoints/class-authorization.php';
require __DIR__ . '/inc/endpoints/class-token.php';
require __DIR__ . '/inc/well-known/namespace.php';
require __DIR__ . '/inc/tokens/namespace.php';
require __DIR__ . '/inc/tokens/class-token.php';
require __DIR__ . '/inc/tokens/class-access-token.php';
Expand Down
67 changes: 67 additions & 0 deletions tests/test-well-known.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
/**
* Tests for the WP\OAuth2\Well_Known namespace functions.
*
* @package WP\OAuth2\Tests
*/

namespace WP\OAuth2\Tests;

require_once __DIR__ . '/class-test-case.php';

use function WP\OAuth2\Well_Known\get_grant_types_supported;
use function WP\OAuth2\Well_Known\get_response_types_supported;
use function WP\OAuth2\Well_Known\match_well_known_path;

/**
* Test cases for the well-known discovery document functions.
*/
class Test_Well_Known extends Test_Case {

// -------------------------------------------------------------------------
// match_well_known_path
// -------------------------------------------------------------------------

public function test_match_well_known_path_matches_authorization_server() {
$this->assertEquals(
'oauth-authorization-server',
match_well_known_path( '/.well-known/oauth-authorization-server' )
);
}

public function test_match_well_known_path_tolerates_trailing_slash() {
$this->assertEquals(
'oauth-authorization-server',
match_well_known_path( '/.well-known/oauth-authorization-server/' )
);
}

public function test_match_well_known_path_ignores_query_string() {
$this->assertEquals(
'oauth-authorization-server',
match_well_known_path( '/.well-known/oauth-authorization-server?foo=bar' )
);
}

public function test_match_well_known_path_returns_null_for_unrelated_path() {
$this->assertNull( match_well_known_path( '/some-other-path' ) );
}

// -------------------------------------------------------------------------
// get_grant_types_supported / get_response_types_supported
// -------------------------------------------------------------------------

public function test_get_grant_types_supported_includes_authorization_code() {
$this->assertContains( 'authorization_code', get_grant_types_supported() );
}

public function test_get_grant_types_supported_includes_client_credentials() {
$this->assertContains( 'client_credentials', get_grant_types_supported() );
}

public function test_get_response_types_supported_includes_code_and_token() {
$response_types = get_response_types_supported();
$this->assertContains( 'code', $response_types );
$this->assertContains( 'token', $response_types );
}
}