Add Service actor for signing get requests

This commit is contained in:
Django Doucet 2023-05-05 12:02:12 -06:00
parent 3a0fef27e0
commit 27636b62d5
4 changed files with 80 additions and 29 deletions

View file

@ -60,9 +60,9 @@ class Http {
* *
* @return array|WP_Error The GET Response or an WP_ERROR * @return array|WP_Error The GET Response or an WP_ERROR
*/ */
public static function get( $url, $user_id ) { public static function get( $url ) {
$date = \gmdate( 'D, d M Y H:i:s T' ); $date = \gmdate( 'D, d M Y H:i:s T' );
$signature = Signature::generate_signature( $user_id, 'get', $url, $date ); $signature = Signature::generate_signature( -1, 'get', $url, $date );
$wp_version = \get_bloginfo( 'version' ); $wp_version = \get_bloginfo( 'version' );
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) ); $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );

View file

@ -35,14 +35,15 @@ class Signature {
* @return mixed * @return mixed
*/ */
public static function get_private_key( $user_id, $force = false ) { public static function get_private_key( $user_id, $force = false ) {
$key = \get_user_meta( $user_id, 'magic_sig_private_key' ); if ( $force ) {
self::generate_key_pair( $user_id );
if ( $key && ! $force ) {
return $key[0];
} }
self::generate_key_pair( $user_id ); if ( -1 === $user_id ) {
$key = \get_option('activitypub_magic_sig_private_key' );
} else {
$key = \get_user_meta( $user_id, 'magic_sig_private_key' ); $key = \get_user_meta( $user_id, 'magic_sig_private_key' );
}
return $key[0]; return $key[0];
} }
@ -63,15 +64,23 @@ class Signature {
$priv_key = null; $priv_key = null;
\openssl_pkey_export( $key, $priv_key ); \openssl_pkey_export( $key, $priv_key );
$detail = \openssl_pkey_get_details( $key );
if ( -1 === $user_id ) {
// private key
\add_option('activitypub_magic_sig_private_key', $priv_key );
// public key
\add_option('activitypub_magic_sig_public_key', $detail['key'] );
} else {
// private key // private key
\update_user_meta( $user_id, 'magic_sig_private_key', $priv_key ); \update_user_meta( $user_id, 'magic_sig_private_key', $priv_key );
$detail = \openssl_pkey_get_details( $key );
// public key // public key
\update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] ); \update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] );
} }
}
public static function generate_signature( $user_id, $http_method, $url, $date, $digest = null ) { public static function generate_signature( $user_id, $http_method, $url, $date, $digest = null ) {
$key = self::get_private_key( $user_id ); $key = self::get_private_key( $user_id );
@ -101,7 +110,11 @@ class Signature {
\openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 ); \openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 );
$signature = \base64_encode( $signature ); // phpcs:ignore $signature = \base64_encode( $signature ); // phpcs:ignore
if ( -1 === $user_id ) {
$key_id = \get_rest_url( null, 'activitypub/1.0/service#main-key' );
} else {
$key_id = \get_author_posts_url( $user_id ) . '#main-key'; $key_id = \get_author_posts_url( $user_id ) . '#main-key';
}
if ( ! empty( $digest ) ) { if ( ! empty( $digest ) ) {
return \sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="%s"', $key_id, $signature ); return \sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="%s"', $key_id, $signature );

View file

@ -36,8 +36,8 @@ function safe_remote_post( $url, $body, $user_id ) {
return \Activitypub\Http::post( $url, $body, $user_id ); return \Activitypub\Http::post( $url, $body, $user_id );
} }
function safe_remote_get( $url, $user_id ) { function safe_remote_get( $url ) {
return \Activitypub\Http::get( $url, $user_id ); return \Activitypub\Http::get( $url );
} }
/** /**
@ -88,21 +88,11 @@ function get_remote_metadata_by_actor( $actor ) {
return $metadata; return $metadata;
} }
$user = \get_users(
array(
'number' => 1,
'capability__in' => array( 'publish_posts' ),
'fields' => 'ID',
)
);
// we just need any user to generate a request signature
$user_id = \reset( $user );
$short_timeout = function() { $short_timeout = function() {
return 3; return 3;
}; };
add_filter( 'activitypub_remote_get_timeout', $short_timeout ); add_filter( 'activitypub_remote_get_timeout', $short_timeout );
$response = Http::get( $actor, $user_id ); $response = Http::get( $actor );
remove_filter( 'activitypub_remote_get_timeout', $short_timeout ); remove_filter( 'activitypub_remote_get_timeout', $short_timeout );
if ( \is_wp_error( $response ) ) { if ( \is_wp_error( $response ) ) {
\set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period. \set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period.

View file

@ -1,6 +1,7 @@
<?php <?php
namespace Activitypub\Rest; namespace Activitypub\Rest;
use WP_REST_Response;
use Activitypub\Signature; use Activitypub\Signature;
/** /**
@ -16,7 +17,54 @@ class Server {
* Initialize the class, registering WordPress hooks * Initialize the class, registering WordPress hooks
*/ */
public static function init() { public static function init() {
\add_filter( 'rest_request_before_callbacks', array( '\Activitypub\Rest\Server', 'authorize_activitypub_requests' ), 10, 3 ); \add_filter( 'rest_request_before_callbacks', array( self::class, 'authorize_activitypub_requests' ), 10, 3 );
\add_action( 'rest_api_init', array( self::class, 'register_routes' ) );
}
/**
* Register routes
*/
public static function register_routes() {
\register_rest_route(
'activitypub/1.0',
'/service',
array(
array(
'methods' => \WP_REST_Server::READABLE,
'callback' => array( self::class, 'service_actor' ),
'permission_callback' => '__return_true',
),
)
);
}
/**
* Render Service actor profile
*
* @return WP_REST_Response
*/
public static function service_actor() {
$json = new \stdClass();
$json->{'@context'} = \Activitypub\get_context();
$json->id = \get_rest_url( null, 'activitypub/1.0/service' );
$json->type = 'Application';
$json->preferredUsername = parse_url( get_site_url(), PHP_URL_HOST );
$json->name = get_bloginfo( 'name' );
$json->summary = "ActivityPub service actor";
$json->manuallyApprovesFollowers = TRUE;
$json->icon = [ get_site_icon_url() ];
$json->publicKey = (object) array(
'id' => \get_rest_url( null, 'activitypub/1.0/service#main-key' ),
'owner' => \get_rest_url( null, 'activitypub/1.0/service' ),
'publicKeyPem' => Signature::get_public_key( -1 ),
);
$response = new WP_REST_Response( $json, 200 );
$response->header( 'Content-Type', 'application/activity+json' );
return $response;
} }
/** /**