From 27636b62d50c81d05105e343030daa01a0b18aa9 Mon Sep 17 00:00:00 2001 From: Django Doucet Date: Fri, 5 May 2023 12:02:12 -0600 Subject: [PATCH] Add Service actor for signing get requests --- includes/class-http.php | 4 +-- includes/class-signature.php | 39 +++++++++++++++++--------- includes/functions.php | 16 ++--------- includes/rest/class-server.php | 50 +++++++++++++++++++++++++++++++++- 4 files changed, 80 insertions(+), 29 deletions(-) diff --git a/includes/class-http.php b/includes/class-http.php index 798328bc..e6a9a79c 100644 --- a/includes/class-http.php +++ b/includes/class-http.php @@ -60,9 +60,9 @@ class Http { * * @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' ); - $signature = Signature::generate_signature( $user_id, 'get', $url, $date ); + $signature = Signature::generate_signature( -1, 'get', $url, $date ); $wp_version = \get_bloginfo( 'version' ); $user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) ); diff --git a/includes/class-signature.php b/includes/class-signature.php index 608bff34..dcf3d965 100644 --- a/includes/class-signature.php +++ b/includes/class-signature.php @@ -35,14 +35,15 @@ class Signature { * @return mixed */ public static function get_private_key( $user_id, $force = false ) { - $key = \get_user_meta( $user_id, 'magic_sig_private_key' ); - - if ( $key && ! $force ) { - return $key[0]; + if ( $force ) { + self::generate_key_pair( $user_id ); } - self::generate_key_pair( $user_id ); - $key = \get_user_meta( $user_id, 'magic_sig_private_key' ); + if ( -1 === $user_id ) { + $key = \get_option('activitypub_magic_sig_private_key' ); + } else { + $key = \get_user_meta( $user_id, 'magic_sig_private_key' ); + } return $key[0]; } @@ -63,14 +64,22 @@ class Signature { $priv_key = null; \openssl_pkey_export( $key, $priv_key ); - - // private key - \update_user_meta( $user_id, 'magic_sig_private_key', $priv_key ); - $detail = \openssl_pkey_get_details( $key ); - // public key - \update_user_meta( $user_id, 'magic_sig_public_key', $detail['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 + \update_user_meta( $user_id, 'magic_sig_private_key', $priv_key ); + + // public 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 ) { @@ -101,7 +110,11 @@ class Signature { \openssl_sign( $signed_string, $signature, $key, \OPENSSL_ALGO_SHA256 ); $signature = \base64_encode( $signature ); // phpcs:ignore - $key_id = \get_author_posts_url( $user_id ) . '#main-key'; + 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'; + } if ( ! empty( $digest ) ) { return \sprintf( 'keyId="%s",algorithm="rsa-sha256",headers="(request-target) host date digest",signature="%s"', $key_id, $signature ); diff --git a/includes/functions.php b/includes/functions.php index bd04e6c4..9743b442 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -36,8 +36,8 @@ function safe_remote_post( $url, $body, $user_id ) { return \Activitypub\Http::post( $url, $body, $user_id ); } -function safe_remote_get( $url, $user_id ) { - return \Activitypub\Http::get( $url, $user_id ); +function safe_remote_get( $url ) { + return \Activitypub\Http::get( $url ); } /** @@ -88,21 +88,11 @@ function get_remote_metadata_by_actor( $actor ) { 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() { return 3; }; 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 ); if ( \is_wp_error( $response ) ) { \set_transient( $transient_key, $response, HOUR_IN_SECONDS ); // Cache the error for a shorter period. diff --git a/includes/rest/class-server.php b/includes/rest/class-server.php index bf1cd5e3..5934bcf5 100644 --- a/includes/rest/class-server.php +++ b/includes/rest/class-server.php @@ -1,6 +1,7 @@ \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; } /**