updated plugin ActivityPub version 0.14.3

This commit is contained in:
2022-12-19 23:08:11 +00:00
committed by Gitium
parent c5dce2cec6
commit 4657b9b202
24 changed files with 1059 additions and 143 deletions

View File

@ -14,6 +14,7 @@ class Admin {
\add_action( 'admin_menu', array( '\Activitypub\Admin', 'admin_menu' ) );
\add_action( 'admin_init', array( '\Activitypub\Admin', 'register_settings' ) );
\add_action( 'show_user_profile', array( '\Activitypub\Admin', 'add_fediverse_profile' ) );
\add_action( 'admin_enqueue_scripts', array( '\Activitypub\Admin', 'enqueue_scripts' ) );
}
/**
@ -21,7 +22,7 @@ class Admin {
*/
public static function admin_menu() {
$settings_page = \add_options_page(
'ActivityPub',
'Welcome',
'ActivityPub',
'manage_options',
'activitypub',
@ -39,7 +40,27 @@ class Admin {
* Load settings page
*/
public static function settings_page() {
\load_template( \dirname( __FILE__ ) . '/../templates/settings.php' );
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( empty( $_GET['tab'] ) ) {
$tab = 'welcome';
} else {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
$tab = sanitize_key( $_GET['tab'] );
}
switch ( $tab ) {
case 'settings':
\load_template( \dirname( __FILE__ ) . '/../templates/settings.php' );
break;
case 'welcome':
default:
wp_enqueue_script( 'plugin-install' );
add_thickbox();
wp_enqueue_script( 'updates' );
\load_template( \dirname( __FILE__ ) . '/../templates/welcome.php' );
break;
}
}
/**
@ -122,23 +143,7 @@ class Admin {
}
public static function add_settings_help_tab() {
\get_current_screen()->add_help_tab(
array(
'id' => 'overview',
'title' => \__( 'Overview', 'activitypub' ),
'content' =>
'<p>' . \__( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '</p>',
)
);
\get_current_screen()->set_help_sidebar(
'<p><strong>' . \__( 'For more information:', 'activitypub' ) . '</strong></p>' .
'<p>' . \__( '<a href="https://activitypub.rocks/">Test Suite</a>', 'activitypub' ) . '</p>' .
'<p>' . \__( '<a href="https://www.w3.org/TR/activitypub/">W3C Spec</a>', 'activitypub' ) . '</p>' .
'<p>' . \__( '<a href="https://github.com/pfefferle/wordpress-activitypub/issues">Give us feedback</a>', 'activitypub' ) . '</p>' .
'<hr />' .
'<p>' . \__( '<a href="https://notiz.blog/donate">Donate</a>', 'activitypub' ) . '</p>'
);
require_once \dirname( __FILE__ ) . '/help.php';
}
public static function add_followers_list_help_tab() {
@ -147,8 +152,15 @@ class Admin {
public static function add_fediverse_profile( $user ) {
?>
<h2><?php \esc_html_e( 'Fediverse', 'activitypub' ); ?></h2>
<h2 id="activitypub"><?php \esc_html_e( 'ActivityPub', 'activitypub' ); ?></h2>
<?php
\Activitypub\get_identifier_settings( $user->ID );
}
public static function enqueue_scripts( $hook_suffix ) {
if ( false !== strpos( $hook_suffix, 'activitypub' ) ) {
wp_enqueue_style( 'activitypub-admin-styles', plugins_url( 'assets/css/activitypub-admin.css', ACTIVITYPUB_PLUGIN_FILE ), array(), '1.0.0' );
wp_enqueue_script( 'activitypub-admin-styles', plugins_url( 'assets/js/activitypub-admin.js', ACTIVITYPUB_PLUGIN_FILE ), array( 'jquery' ), '1.0.0', false );
}
}
}

View File

@ -17,13 +17,16 @@ class Debug {
}
public static function log_remote_post_responses( $response, $url, $body, $user_id ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
\error_log( "Request to: {$url} with response: " . \print_r( $response, true ) );
}
public static function write_log( $log ) {
if ( \is_array( $log ) || \is_object( $log ) ) {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r
\error_log( \print_r( $log, true ) );
} else {
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
\error_log( $log );
}
}

View File

@ -15,6 +15,7 @@ class Health_Check {
*/
public static function init() {
\add_filter( 'site_status_tests', array( '\Activitypub\Health_Check', 'add_tests' ) );
\add_filter( 'debug_information', array( '\Activitypub\Health_Check', 'debug_information' ) );
}
public static function add_tests( $tests ) {
@ -198,58 +199,37 @@ class Health_Check {
* @return boolean|WP_Error
*/
public static function is_webfinger_endpoint_accessible() {
$user = \wp_get_current_user();
$webfinger = \Activitypub\get_webfinger_resource( $user->ID );
$user = \wp_get_current_user();
$account = \Activitypub\get_webfinger_resource( $user->ID );
$url = \wp_parse_url( \home_url(), \PHP_URL_SCHEME ) . '://' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
if ( \wp_parse_url( \home_url(), \PHP_URL_PORT ) ) {
$url .= ':' . \wp_parse_url( \home_url(), \PHP_URL_PORT );
}
$url = \trailingslashit( $url ) . '.well-known/webfinger';
$url = \add_query_arg( 'resource', 'acct:' . $webfinger, $url );
// try to access author URL
$response = \wp_remote_get(
$url,
array(
'headers' => array( 'Accept' => 'application/activity+json' ),
'redirection' => 0,
)
);
if ( \is_wp_error( $response ) ) {
return new \WP_Error(
'webfinger_url_not_accessible',
\sprintf(
$url = \Activitypub\Webfinger::resolve( $account );
if ( \is_wp_error( $url ) ) {
$health_messages = array(
'webfinger_url_not_accessible' => \sprintf(
// translators: %s: Author URL
\__(
'<p>Your WebFinger endpoint <code>%s</code> is not accessible. Please check your WordPress setup or permalink structure.</p>',
'activitypub'
),
$url
)
);
}
$response_code = \wp_remote_retrieve_response_code( $response );
// check if response is JSON
$body = \wp_remote_retrieve_body( $response );
if ( ! \is_string( $body ) || ! \is_array( \json_decode( $body, true ) ) ) {
return new \WP_Error(
'webfinger_url_not_accessible',
\sprintf(
$url->get_error_data()
),
'webfinger_url_invalid_response' => \sprintf(
// translators: %s: Author URL
\__(
'<p>Your WebFinger endpoint <code>%s</code> does not return valid JSON for <code>application/jrd+json</code>.</p>',
'activitypub'
),
$url
)
$url->get_error_data()
),
);
$message = null;
if ( isset( $health_messages[ $url->get_error_code() ] ) ) {
$message = $health_messages[ $url->get_error_code() ];
}
return new \WP_Error(
$url->get_error_code(),
$message,
$url->get_error_data()
);
}
@ -287,4 +267,30 @@ class Health_Check {
return $link;
}
/**
* Static function for generating site debug data when required.
*
* @param array $info The debug information to be added to the core information page.
* @return array The filtered informations
*/
public static function debug_information( $info ) {
$info['activitypub'] = array(
'label' => __( 'ActivityPub', 'activitypub' ),
'fields' => array(
'webfinger' => array(
'label' => __( 'WebFinger Resource', 'activitypub' ),
'value' => \Activitypub\Webfinger::get_user_resource( wp_get_current_user()->ID ),
'private' => true,
),
'author_url' => array(
'label' => __( 'Author URL', 'activitypub' ),
'value' => get_author_posts_url( wp_get_current_user()->ID ),
'private' => true,
),
),
);
return $info;
}
}

View File

@ -70,7 +70,7 @@ class Signature {
\update_user_meta( $user_id, 'magic_sig_public_key', $detail['key'] );
}
public static function generate_signature( $user_id, $url, $date, $digest = null ) {
public static function generate_signature( $user_id, $http_method, $url, $date, $digest = null ) {
$key = self::get_private_key( $user_id );
$url_parts = \wp_parse_url( $url );
@ -89,9 +89,9 @@ class Signature {
}
if ( ! empty( $digest ) ) {
$signed_string = "(request-target): post $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest";
$signed_string = "(request-target): $http_method $path\nhost: $host\ndate: $date\ndigest: SHA-256=$digest";
} else {
$signed_string = "(request-target): post $path\nhost: $host\ndate: $date";
$signed_string = "(request-target): $http_method $path\nhost: $host\ndate: $date";
}
$signature = null;

View File

@ -0,0 +1,69 @@
<?php
namespace Activitypub;
/**
* ActivityPub WebFinger Class
*
* @author Matthias Pfefferle
*
* @see https://webfinger.net/
*/
class Webfinger {
/**
* Returns a users WebFinger "resource"
*
* @param int $user_id
*
* @return string The user-resource
*/
public static function get_user_resource( $user_id ) {
// use WebFinger plugin if installed
if ( \function_exists( '\get_webfinger_resource' ) ) {
return \get_webfinger_resource( $user_id, false );
}
$user = \get_user_by( 'id', $user_id );
return $user->user_login . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
}
public static function resolve( $account ) {
if ( ! preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $account, $m ) ) {
return null;
}
$url = \add_query_arg( 'resource', 'acct:' . ltrim( $account, '@' ), 'https://' . $m[1] . '/.well-known/webfinger' );
if ( ! \wp_http_validate_url( $url ) ) {
return new \WP_Error( 'invalid_webfinger_url', null, $url );
}
// try to access author URL
$response = \wp_remote_get(
$url,
array(
'headers' => array( 'Accept' => 'application/activity+json' ),
'redirection' => 0,
)
);
if ( \is_wp_error( $response ) ) {
return new \WP_Error( 'webfinger_url_not_accessible', null, $url );
}
$response_code = \wp_remote_retrieve_response_code( $response );
$body = \wp_remote_retrieve_body( $response );
$body = \json_decode( $body, true );
if ( ! isset( $body['links'] ) ) {
return new \WP_Error( 'webfinger_url_invalid_response', null, $url );
}
foreach ( $body['links'] as $link ) {
if ( 'self' === $link['rel'] && 'application/activity+json' === $link['type'] ) {
return $link['href'];
}
}
return new \WP_Error( 'webfinger_url_no_activity_pub', null, $body );
}
}

View File

@ -35,7 +35,7 @@ function get_context() {
function safe_remote_post( $url, $body, $user_id ) {
$date = \gmdate( 'D, d M Y H:i:s T' );
$digest = \Activitypub\Signature::generate_digest( $body );
$signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date, $digest );
$signature = \Activitypub\Signature::generate_signature( $user_id, 'post', $url, $date, $digest );
$wp_version = \get_bloginfo( 'version' );
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
@ -63,7 +63,7 @@ function safe_remote_post( $url, $body, $user_id ) {
function safe_remote_get( $url, $user_id ) {
$date = \gmdate( 'D, d M Y H:i:s T' );
$signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date );
$signature = \Activitypub\Signature::generate_signature( $user_id, 'get', $url, $date );
$wp_version = \get_bloginfo( 'version' );
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
@ -95,24 +95,33 @@ function safe_remote_get( $url, $user_id ) {
* @return string The user-resource
*/
function get_webfinger_resource( $user_id ) {
// use WebFinger plugin if installed
if ( \function_exists( '\get_webfinger_resource' ) ) {
return \get_webfinger_resource( $user_id, false );
}
$user = \get_user_by( 'id', $user_id );
return $user->user_login . '@' . \wp_parse_url( \home_url(), \PHP_URL_HOST );
return \Activitypub\Webfinger::get_user_resource( $user_id );
}
/**
* [get_metadata_by_actor description]
*
* @param sting $actor
* @param string $actor
*
* @return array
*/
function get_remote_metadata_by_actor( $actor ) {
$pre = apply_filters( 'pre_get_remote_metadata_by_actor', false, $actor );
if ( $pre ) {
return $pre;
}
if ( preg_match( '/^@?[^@]+@((?:[a-z0-9-]+\.)+[a-z]+)$/i', $actor ) ) {
$actor = \Activitypub\Webfinger::resolve( $actor );
}
if ( ! $actor ) {
return null;
}
if ( is_wp_error( $actor ) ) {
return $actor;
}
$metadata = \get_transient( 'activitypub_' . $actor );
if ( $metadata ) {
@ -231,7 +240,7 @@ function get_identifier_settings( $user_id ) {
<td>
<p><code><?php echo \esc_html( \Activitypub\get_webfinger_resource( $user_id ) ); ?></code> or <code><?php echo \esc_url( \get_author_posts_url( $user_id ) ); ?></code></p>
<?php // translators: the webfinger resource ?>
<p class="description"><?php \printf( \esc_html__( 'Try to follow "@%s" in the Mastodon/Friendica search field.', 'activitypub' ), \esc_html( \Activitypub\get_webfinger_resource( $user_id ) ) ); ?></p>
<p class="description"><?php \printf( \esc_html__( 'Try to follow "@%s" by searching for it on Mastodon,Friendica & Co.', 'activitypub' ), \esc_html( \Activitypub\get_webfinger_resource( $user_id ) ) ); ?></p>
</td>
</tr>
</tbody>

View File

@ -0,0 +1,55 @@
<?php
\get_current_screen()->add_help_tab(
array(
'id' => 'fediverse',
'title' => \__( 'Fediverse', 'activitypub' ),
'content' =>
'<p><strong>' . \__( 'What is the Fediverse?', 'activitypub' ) . '</strong></p>' .
'<p>' . \__( 'The Fediverse is a new word made of two words: "federation" + "universe"', 'activitypub' ) . '</p>' .
'<p>' . \__( 'It is a federated social network running on free open software on a myriad of computers across the globe. Many independent servers are interconnected and allow people to interact with one another. There\'s no one central site: you choose a server to register. This ensures some decentralization and sovereignty of data. Fediverse (also called Fedi) has no built-in advertisements, no tricky algorithms, no one big corporation dictating the rules. Instead we have small cozy communities of like-minded people. Welcome!', 'activitypub' ) . '</p>' .
'<p>' . \__( 'For more informations please visit <a href="https://fediverse.party/" target="_blank">fediverse.party</a>', 'activitypub' ) . '</p>',
)
);
\get_current_screen()->add_help_tab(
array(
'id' => 'activitypub',
'title' => \__( 'ActivityPub', 'activitypub' ),
'content' =>
'<p><strong>' . \__( 'What is ActivityPub?', 'activitypub' ) . '</strong></p>' .
'<p>' . \__( 'ActivityPub is a decentralized social networking protocol based on the ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended standard published by the W3C Social Web Working Group. It provides a client to server API for creating, updating and deleting content, as well as a federated server to server API for delivering notifications and subscribing to content.', 'activitypub' ) . '</p>',
)
);
\get_current_screen()->add_help_tab(
array(
'id' => 'webfinger',
'title' => \__( 'WebFinger', 'activitypub' ),
'content' =>
'<p><strong>' . \__( 'What is WebFinger?', 'activitypub' ) . '</strong></p>' .
'<p>' . \__( 'WebFinger is used to discover information about people or other entities on the Internet that are identified by a URI using standard Hypertext Transfer Protocol (HTTP) methods over a secure transport. A WebFinger resource returns a JavaScript Object Notation (JSON) object describing the entity that is queried. The JSON object is referred to as the JSON Resource Descriptor (JRD).', 'activitypub' ) . '</p>' .
'<p>' . \__( 'For a person, the type of information that might be discoverable via WebFinger includes a personal profile address, identity service, telephone number, or preferred avatar. For other entities on the Internet, a WebFinger resource might return JRDs containing link relations that enable a client to discover, for example, that a printer can print in color on A4 paper, the physical location of a server, or other static information.', 'activitypub' ) . '</p>' .
'<p>' . \__( 'On Mastodon [and other Plattforms], user profiles can be hosted either locally on the same website as yours, or remotely on a completely different website. The same username may be used on a different domain. Therefore, a Mastodon user\'s full mention consists of both the username and the domain, in the form <code>@username@domain</code>. In practical terms, <code>@user@example.com</code> is not the same as <code>@user@example.org</code>. If the domain is not included, Mastodon will try to find a local user named <code>@username</code>. However, in order to deliver to someone over ActivityPub, the <code>@username@domain</code> mention is not enough mentions must be translated to an HTTPS URI first, so that the remote actor\'s inbox and outbox can be found. (This paragraph is copied from the <a href="https://docs.joinmastodon.org/spec/webfinger/" target="_blank">Mastodon Documentation</a>)', 'activitypub' ) . '</p>' .
'<p>' . \__( 'For more informations please visit <a href="https://webfinger.net/" target="_blank">webfinger.net</a>', 'activitypub' ) . '</p>',
)
);
\get_current_screen()->add_help_tab(
array(
'id' => 'nodeinfo',
'title' => \__( 'NodeInfo', 'activitypub' ),
'content' =>
'<p><strong>' . \__( 'What is NodeInfo?', 'activitypub' ) . '</strong></p>' .
'<p>' . \__( 'NodeInfo is an effort to create a standardized way of exposing metadata about a server running one of the distributed social networks. The two key goals are being able to get better insights into the user base of distributed social networking and the ability to build tools that allow users to choose the best fitting software and server for their needs.', 'activitypub' ) . '</p>' .
'<p>' . \__( 'For more informations please visit <a href="http://nodeinfo.diaspora.software/" target="_blank">nodeinfo.diaspora.software</a>', 'activitypub' ) . '</p>',
)
);
\get_current_screen()->set_help_sidebar(
'<p><strong>' . \__( 'For more information:', 'activitypub' ) . '</strong></p>' .
'<p>' . \__( '<a href="https://wordpress.org/support/plugin/activitypub/">Get support</a>', 'activitypub' ) . '</p>' .
'<p>' . \__( '<a href="https://github.com/pfefferle/wordpress-activitypub/issues">Report an issue</a>', 'activitypub' ) . '</p>' .
'<hr />' .
'<p>' . \__( '<a href="https://notiz.blog/donate">Donate</a>', 'activitypub' ) . '</p>'
);

View File

@ -75,7 +75,7 @@ class Activity {
}
public function to_array() {
$array = \get_object_vars( $this );
$array = array_filter( \get_object_vars( $this ) );
if ( $this->context ) {
$array = array( '@context' => $this->context ) + $array;

View File

@ -161,7 +161,6 @@ class Inbox {
public static function shared_inbox_post( $request ) {
$data = $request->get_params();
$type = $request->get_param( 'type' );
$users = self::extract_recipients( $data );
if ( ! $users ) {
@ -407,6 +406,10 @@ class Inbox {
public static function handle_create( $object, $user_id ) {
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
if ( ! isset( $object['object']['inReplyTo'] ) ) {
return;
}
$comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
// save only replys and reactions

View File

@ -95,10 +95,6 @@ class Nodeinfo {
'outbound' => array(),
);
$nodeinfo['metadata'] = array(
'email' => \get_option( 'admin_email' ),
);
return new \WP_REST_Response( $nodeinfo, 200 );
}
@ -120,13 +116,24 @@ class Nodeinfo {
'version' => \get_bloginfo( 'version' ),
);
$users = \count_users();
$users = \get_users(
array(
'capability__in' => array( 'publish_posts' ),
)
);
if ( is_array( $users ) ) {
$users = count( $users );
} else {
$users = 1;
}
$posts = \wp_count_posts();
$comments = \wp_count_comments();
$nodeinfo['usage'] = array(
'users' => array(
'total' => (int) $users['total_users'],
'total' => (int) $users,
),
'localPosts' => (int) $posts->publish,
'localComments' => (int) $comments->approved,
@ -140,10 +147,6 @@ class Nodeinfo {
'outbound' => array(),
);
$nodeinfo['metadata'] = array(
'email' => \get_option( 'admin_email' ),
);
return new \WP_REST_Response( $nodeinfo, 200 );
}

View File

@ -43,6 +43,7 @@ class Outbox {
public static function user_outbox_get( $request ) {
$user_id = $request->get_param( 'user_id' );
$author = \get_user_by( 'ID', $user_id );
$post_types = \get_option( 'activitypub_support_post_types', array( 'post', 'page' ) );
if ( ! $author ) {
return new \WP_Error(
@ -72,9 +73,15 @@ class Outbox {
$json->actor = \get_author_posts_url( $user_id );
$json->type = 'OrderedCollectionPage';
$json->partOf = \get_rest_url( null, "/activitypub/1.0/users/$user_id/outbox" ); // phpcs:ignore
$json->totalItems = 0; // phpcs:ignore
$count_posts = \wp_count_posts();
$json->totalItems = \intval( $count_posts->publish ); // phpcs:ignore
// phpcs:ignore
$json->totalItems = 0;
foreach ( $post_types as $post_type ) {
$count_posts = \wp_count_posts( $post_type );
$json->totalItems += \intval( $count_posts->publish ); // phpcs:ignore
}
$json->first = \add_query_arg( 'page', 1, $json->partOf ); // phpcs:ignore
$json->last = \add_query_arg( 'page', \ceil ( $json->totalItems / 10 ), $json->partOf ); // phpcs:ignore
@ -89,7 +96,7 @@ class Outbox {
'posts_per_page' => 10,
'author' => $user_id,
'offset' => ( $page - 1 ) * 10,
'post_type' => 'post',
'post_type' => $post_types,
)
);

View File

@ -44,9 +44,7 @@ class Webfinger {
public static function webfinger( $request ) {
$resource = $request->get_param( 'resource' );
$matched = \str_contains( $resource, '@' );
if ( ! $matched ) {
if ( \strpos( $resource, '@' ) === false ) {
return new \WP_Error( 'activitypub_unsupported_resource', \__( 'Resource is invalid', 'activitypub' ), array( 'status' => 400 ) );
}