From f4f46fc08493b6ab071f8a65a77404b528cd4256 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 23 Jul 2021 15:46:28 +0200 Subject: [PATCH] added health checks --- README.md | 9 +- activitypub.php | 6 +- includes/class-health-check.php | 218 ++++++++++++++++++++++++++++---- includes/debug.php | 16 +++ includes/functions.php | 4 +- readme.txt | 9 +- 6 files changed, 233 insertions(+), 29 deletions(-) create mode 100644 includes/debug.php diff --git a/README.md b/README.md index c1d41f4..ededaea 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,8 @@ **Donate link:** https://notiz.blog/donate/ **Tags:** OStatus, fediverse, activitypub, activitystream **Requires at least:** 4.7 -**Tested up to:** 5.6 -**Stable tag:** 0.12.0 +**Tested up to:** 5.8 +**Stable tag:** 0.13.0 **Requires PHP:** 5.6 **License:** MIT **License URI:** http://opensource.org/licenses/MIT @@ -88,6 +88,11 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub). +### 0.13.0 ### + +* add Autor URL and WebFinger health checks +* fix NodeInfo endpoint + ### 0.12.0 ### * use "pre_option_require_name_email" filter instead of "check_comment_flood". props [@akirk](https://github.com/akirk) diff --git a/activitypub.php b/activitypub.php index c268088..c98b50c 100644 --- a/activitypub.php +++ b/activitypub.php @@ -3,7 +3,7 @@ * Plugin Name: ActivityPub * Plugin URI: https://github.com/pfefferle/wordpress-activitypub/ * Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format. - * Version: 0.12.0 + * Version: 0.13.0 * Author: Matthias Pfefferle * Author URI: https://notiz.blog/ * License: MIT @@ -75,6 +75,10 @@ function init() { \add_filter( 'wp_rest_server_class', function() { return '\Activitypub\Rest\Server'; } ); + + if ( \WP_DEBUG ) { + require_once \dirname( __FILE__ ) . '/includes/debug.php'; + } } \add_action( 'plugins_loaded', '\Activitypub\init' ); diff --git a/includes/class-health-check.php b/includes/class-health-check.php index 4a19875..12f8d71 100644 --- a/includes/class-health-check.php +++ b/includes/class-health-check.php @@ -7,25 +7,36 @@ namespace Activitypub; * @author Matthias Pfefferle */ class Health_Check { + + /** + * Initialize health checks + * + * @return void + */ public static function init() { \add_filter( 'site_status_tests', array( '\Activitypub\Health_Check', 'add_tests' ) ); } public static function add_tests( $tests ) { - $tests['direct']['activitypub_test_profile_url'] = array( - 'label' => \__( 'Profile URL test', 'activitypub' ), - 'test' => array( '\Activitypub\Health_Check', 'test_profile_url' ), + $tests['direct']['activitypub_test_author_url'] = array( + 'label' => \__( 'Author URL test', 'activitypub' ), + 'test' => array( '\Activitypub\Health_Check', 'test_author_url' ), ); - //$tests['direct']['activitypub_test_profile_url2'] = array( - // 'label' => __( 'Profile URL Test', 'activitypub' ), - // 'test' => array( '\Activitypub\Health_Check', 'test_profile_url' ), - //); + $tests['direct']['activitypub_test_webfinger'] = array( + 'label' => __( 'WebFinger Test', 'activitypub' ), + 'test' => array( '\Activitypub\Health_Check', 'test_webfinger' ), + ); return $tests; } - public static function test_profile_url() { + /** + * Author URL tests + * + * @return void + */ + public static function test_author_url() { $result = array( 'label' => \__( 'Author URL accessible', 'activitypub' ), 'status' => 'good', @@ -38,45 +49,208 @@ class Health_Check { \__( 'Your author URL is accessible and supports the required "Accept" header.', 'activitypub' ) ), 'actions' => '', - 'test' => 'test_profile_url', + 'test' => 'test_author_url', ); - $enum = self::is_profile_url_accessible(); + $check = self::is_author_url_accessible(); - if ( true !== $enum ) { - $result['status'] = 'critical'; - $result['label'] = \__( 'Profile URL is not accessible', 'activitypub' ); - $result['description'] = \sprintf( - '

%s

', - \__( 'Your author URL is not accessible and/or does not return valid JSON. Please check if the author URL is accessible and does not redirect to another page (often done by SEO plugins).', 'activitypub' ) - ); + if ( true === $check ) { + return $result; } + $result['status'] = 'critical'; + $result['label'] = \__( 'Author URL is not accessible', 'activitypub' ); + $result['badge']['color'] = 'red'; + $result['description'] = \sprintf( + '

%s

', + $check->get_error_message() + ); + return $result; } - public static function is_profile_url_accessible() { + /** + * WebFinger tests + * + * @return void + */ + public static function test_webfinger() { + $result = array( + 'label' => \__( 'WebFinger endpoint', 'activitypub' ), + 'status' => 'good', + 'badge' => array( + 'label' => \__( 'ActivityPub', 'activitypub' ), + 'color' => 'green', + ), + 'description' => \sprintf( + '

%s

', + \__( 'Your WebFinger endpoint is accessible and returns the correct informations.', 'activitypub' ) + ), + 'actions' => '', + 'test' => 'test_webfinger', + ); + + $check = self::is_webfinger_endpoint_accessible(); + + if ( true === $check ) { + return $result; + } + + $result['status'] = 'critical'; + $result['label'] = \__( 'WebFinger endpoint is not accessible', 'activitypub' ); + $result['badge']['color'] = 'red'; + $result['description'] = \sprintf( + '

%s

', + $check->get_error_message() + ); + + return $result; + } + + /** + * Check if `author_posts_url` is accessible and that requerst returns correct JSON + * + * @return boolean|WP_Error + */ + public static function is_author_url_accessible() { $user = \wp_get_current_user(); $author_url = \get_author_posts_url( $user->ID ); $reference_author_url = self::get_author_posts_url( $user->ID, $user->user_nicename ); // check for "author" in URL if ( $author_url !== $reference_author_url ) { - return false; + return new \WP_Error( + 'author_url_not_accessible', + \sprintf( + // translators: %s: Author URL + \__( + '

Your author URL %s was replaced, this is often done by plugins.

', + 'activitypub' + ), + $author_url + ) + ); } // try to access author URL - $response = \wp_remote_get( $author_url, array( 'headers' => array( 'Accept' => 'application/activity+json' ) ) ); + $response = \wp_remote_get( + $author_url, + array( + 'headers' => array( 'Accept' => 'application/activity+json' ), + 'redirection' => 0, + ) + ); if ( \is_wp_error( $response ) ) { - return false; + return new \WP_Error( + 'author_url_not_accessible', + \sprintf( + // translators: %s: Author URL + \__( + '

Your author URL %s is not accessible. Please check your WordPress setup or permalink structure. If the setup seems fine, maybe check if a plugin might restrict the access.

', + 'activitypub' + ), + $author_url + ) + ); + } + + $response_code = \wp_remote_retrieve_response_code( $response ); + + // check for redirects + if ( \in_array( $response_code, array( 301, 302, 307, 308 ), true ) ) { + return new \WP_Error( + 'author_url_not_accessible', + \sprintf( + // translators: %s: Author URL + \__( + '

Your author URL %s is redirecting to another page, this is often done by SEO plugins like "Yoast SEO".

', + 'activitypub' + ), + $author_url + ) + ); } // check if response is JSON $body = \wp_remote_retrieve_body( $response ); if ( ! \is_string( $body ) || ! \is_array( \json_decode( $body, true ) ) ) { - return false; + return new \WP_Error( + 'author_url_not_accessible', + \sprintf( + // translators: %s: Author URL + \__( + '

Your author URL %s does not return valid JSON for application/activity+json. Please check if your hosting supports alternate Accept headers.

', + 'activitypub' + ), + $author_url + ) + ); + } + + return true; + } + + /** + * Check if WebFinger endoint is accessible and profile requerst returns correct JSON + * + * @return boolean|WP_Error + */ + public static function is_webfinger_endpoint_accessible() { + $user = \wp_get_current_user(); + $webfinger = \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( + // translators: %s: Author URL + \__( + '

Your WebFinger endpoint %s is not accessible. Please check your WordPress setup or permalink structure.

', + '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( + // translators: %s: Author URL + \__( + '

Your WebFinger endpoint %s does not return valid JSON for application/jrd+json.

', + 'activitypub' + ), + $url + ) + ); } return true; diff --git a/includes/debug.php b/includes/debug.php new file mode 100644 index 0000000..a5683f4 --- /dev/null +++ b/includes/debug.php @@ -0,0 +1,16 @@ + 'as:Hashtag', 'featured' => array( '@id' => 'toot:featured', - '@type' => '@id' + '@type' => '@id', ), 'featuredTags' => array( '@id' => 'toot:featuredTags', - '@type' => '@id' + '@type' => '@id', ), ), ); diff --git a/readme.txt b/readme.txt index 93cb976..c816df4 100644 --- a/readme.txt +++ b/readme.txt @@ -3,8 +3,8 @@ Contributors: pfefferle, mediaformat Donate link: https://notiz.blog/donate/ Tags: OStatus, fediverse, activitypub, activitystream Requires at least: 4.7 -Tested up to: 5.6 -Stable tag: 0.12.0 +Tested up to: 5.8 +Stable tag: 0.13.0 Requires PHP: 5.6 License: MIT License URI: http://opensource.org/licenses/MIT @@ -88,6 +88,11 @@ Where 'blog' is the path to the subdirectory at which your blog resides. Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub). += 0.13.0 = + +* add Autor URL and WebFinger health checks +* fix NodeInfo endpoint + = 0.12.0 = * use "pre_option_require_name_email" filter instead of "check_comment_flood". props [@akirk](https://github.com/akirk)