updated plugin ActivityPub version 9.1.0
This commit is contained in:
@ -31,7 +31,7 @@ class Server {
|
||||
|
||||
// Schedule cleanup cron.
|
||||
if ( ! \wp_next_scheduled( 'activitypub_oauth_cleanup' ) ) {
|
||||
\wp_schedule_event( time(), 'daily', 'activitypub_oauth_cleanup' );
|
||||
\wp_schedule_event( \time(), 'daily', 'activitypub_oauth_cleanup' );
|
||||
}
|
||||
\add_action( 'activitypub_oauth_cleanup', array( self::class, 'cleanup' ) );
|
||||
}
|
||||
@ -56,6 +56,22 @@ class Server {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Only honor OAuth bearer tokens on the plugin's own REST API.
|
||||
*
|
||||
* These are scoped ActivityPub C2S grants. Honoring them on core routes
|
||||
* (e.g. POST /wp/v2/posts) would establish a full-capability session that
|
||||
* ignores the token's granted scope, since scope is only enforced where
|
||||
* the plugin opts in via check_oauth_permission() (CWE-863). Every C2S
|
||||
* endpoint lives under ACTIVITYPUB_REST_NAMESPACE, so restricting here
|
||||
* does not affect legitimate clients. Direct callers (outbox permalinks,
|
||||
* SSE query-param auth) invoke this method outside REST dispatch and are
|
||||
* trusted to have scoped the context themselves.
|
||||
*/
|
||||
if ( ! self::is_authenticatable_request() ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$validated = Token::validate( $token );
|
||||
|
||||
if ( \is_wp_error( $validated ) ) {
|
||||
@ -68,6 +84,63 @@ class Server {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current request may be authenticated with an OAuth bearer token.
|
||||
*
|
||||
* Bearer tokens are scoped ActivityPub C2S grants and must only authenticate
|
||||
* the plugin's own REST API. During REST dispatch we therefore require the
|
||||
* requested route to live under {@see ACTIVITYPUB_REST_NAMESPACE}; a token
|
||||
* presented to a core route such as `/wp/v2/posts` is ignored so its granted
|
||||
* scope cannot be bypassed. Outside REST dispatch (e.g. outbox permalinks
|
||||
* that authenticate explicitly) there is no route to scope against, so the
|
||||
* request is allowed through to the caller's own checks.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return bool True if the request may be OAuth-authenticated.
|
||||
*/
|
||||
private static function is_authenticatable_request() {
|
||||
global $wp;
|
||||
|
||||
$route = isset( $wp->query_vars['rest_route'] ) ? (string) $wp->query_vars['rest_route'] : '';
|
||||
|
||||
if ( '' === $route ) {
|
||||
// No REST route in context: only trust non-REST (direct/permalink) callers.
|
||||
return ! \wp_is_serving_rest_request();
|
||||
}
|
||||
|
||||
$route = '/' . \ltrim( $route, '/' );
|
||||
$namespace = '/' . \trim( ACTIVITYPUB_REST_NAMESPACE, '/' );
|
||||
|
||||
return $route === $namespace || 0 === \strpos( $route, $namespace . '/' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject requests that authenticated with an OAuth bearer token.
|
||||
*
|
||||
* OAuth bearer tokens are scoped ActivityPub C2S grants and must not reach
|
||||
* the plugin's admin and management endpoints, which authorize by WordPress
|
||||
* capability rather than by OAuth scope. Without this guard a token consented
|
||||
* to for a narrow scope (e.g. read) could drive privileged, capability-gated
|
||||
* actions such as moderation. Cookie-authenticated admin-UI requests do not
|
||||
* establish an OAuth session and are unaffected.
|
||||
*
|
||||
* @since 9.1.0
|
||||
*
|
||||
* @return \WP_Error|null WP_Error when the request is OAuth-authenticated, null otherwise.
|
||||
*/
|
||||
public static function deny_if_oauth() {
|
||||
if ( ! self::is_oauth_request() ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new \WP_Error(
|
||||
'activitypub_oauth_not_allowed',
|
||||
\__( 'OAuth authentication is not allowed for this endpoint.', 'activitypub' ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current OAuth token from the request.
|
||||
*
|
||||
@ -113,11 +186,11 @@ class Server {
|
||||
}
|
||||
|
||||
// Check for Bearer token.
|
||||
if ( 0 !== strpos( $auth_header, 'Bearer ' ) ) {
|
||||
if ( 0 !== \strpos( $auth_header, 'Bearer ' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return substr( $auth_header, 7 );
|
||||
return \substr( $auth_header, 7 );
|
||||
}
|
||||
|
||||
/**
|
||||
@ -142,14 +215,14 @@ class Server {
|
||||
// phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
|
||||
|
||||
// Fallback: read from Apache's own header API (case-insensitive).
|
||||
if ( ! function_exists( 'apache_request_headers' ) ) {
|
||||
if ( ! \function_exists( 'apache_request_headers' ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$headers = apache_request_headers();
|
||||
$headers = \apache_request_headers();
|
||||
|
||||
foreach ( $headers as $key => $value ) {
|
||||
if ( 'authorization' === strtolower( $key ) ) {
|
||||
if ( 'authorization' === \strtolower( $key ) ) {
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
@ -214,7 +287,7 @@ class Server {
|
||||
return new \WP_Error(
|
||||
'activitypub_insufficient_scope',
|
||||
/* translators: %s: The required scope */
|
||||
sprintf( \__( 'This action requires the "%s" scope.', 'activitypub' ), $scope ),
|
||||
\sprintf( \__( 'This action requires the "%s" scope.', 'activitypub' ), $scope ),
|
||||
array( 'status' => 403 )
|
||||
);
|
||||
}
|
||||
@ -248,7 +321,7 @@ class Server {
|
||||
'revocation_endpoint' => $base_url . 'oauth/revoke',
|
||||
'introspection_endpoint' => $base_url . 'oauth/introspect',
|
||||
'registration_endpoint' => $base_url . 'oauth/clients',
|
||||
'scopes_supported' => Scope::ALL,
|
||||
'scopes_supported' => Scope::supported(),
|
||||
'response_types_supported' => array( 'code' ),
|
||||
'response_modes_supported' => array( 'query' ),
|
||||
'grant_types_supported' => array( 'authorization_code', 'refresh_token' ),
|
||||
@ -337,7 +410,7 @@ class Server {
|
||||
// Build form action URL.
|
||||
// phpcs:ignore VariableAnalysis.CodeAnalysis.VariableAnalysis.UnusedVariable
|
||||
$form_url = \add_query_arg(
|
||||
array_merge( array( 'action' => 'activitypub_authorize' ), $authorize_params ),
|
||||
\array_merge( array( 'action' => 'activitypub_authorize' ), $authorize_params ),
|
||||
\wp_login_url()
|
||||
);
|
||||
|
||||
@ -448,7 +521,7 @@ class Server {
|
||||
$url = Sanitize::redirect_uri( \add_query_arg( $params, $redirect_uri ) );
|
||||
|
||||
\nocache_headers();
|
||||
header( 'Location: ' . $url, true, 303 );
|
||||
\header( 'Location: ' . $url, true, 303 );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user