Merge pull request #87 from pfefferle/blacklist

Blacklist
This commit is contained in:
Matthias Pfefferle 2020-02-22 13:13:23 +01:00 committed by GitHub
commit 60b5bb38d0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 239 additions and 155 deletions

View file

@ -66,6 +66,11 @@ function init() {
require_once \dirname( __FILE__ ) . '/includes/class-health-check.php'; require_once \dirname( __FILE__ ) . '/includes/class-health-check.php';
\Activitypub\Health_Check::init(); \Activitypub\Health_Check::init();
require_once \dirname( __FILE__ ) . '/includes/rest/class-server.php';
\add_filter( 'wp_rest_server_class', function() {
return '\Activitypub\Rest\Server';
} );
} }
add_action( 'plugins_loaded', '\Activitypub\init' ); add_action( 'plugins_loaded', '\Activitypub\init' );

View file

@ -106,6 +106,14 @@ class Admin {
'default' => array( 'post', 'pages' ), 'default' => array( 'post', 'pages' ),
) )
); );
\register_setting(
'activitypub', 'activitypub_blacklist', array(
'type' => 'string',
'description' => \esc_html__( 'Block fediverse instances', 'activitypub' ),
'show_in_rest' => true,
'default' => 'gab.com',
)
);
} }
public static function add_settings_help_tab() { public static function add_settings_help_tab() {

View file

@ -48,6 +48,32 @@ function safe_remote_post( $url, $body, $user_id ) {
return $response; return $response;
} }
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 );
$wp_version = \get_bloginfo( 'version' );
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
$args = array(
'timeout' => 100,
'limit_response_size' => 1048576,
'redirection' => 3,
'user-agent' => "$user_agent; ActivityPub",
'headers' => array(
'Accept' => 'application/activity+json',
'Content-Type' => 'application/activity+json',
'Signature' => $signature,
'Date' => $date,
),
);
$response = \wp_safe_remote_get( $url, $args );
\do_action( 'activitypub_safe_remote_get_response', $response, $url, $user_id );
return $response;
}
/** /**
* Returns a users WebFinger "resource" * Returns a users WebFinger "resource"
* *
@ -84,32 +110,16 @@ function get_remote_metadata_by_actor( $actor ) {
return new \WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), $actor ); return new \WP_Error( 'activitypub_no_valid_actor_url', \__( 'The "actor" is no valid URL', 'activitypub' ), $actor );
} }
// we just need any user to generate a request signature $user = \get_users( array (
$user_id = \reset( \get_users( array (
'number' => 1, 'number' => 1,
'who' => 'authors', 'who' => 'authors',
'fields' => 'ID' 'fields' => 'ID',
) ) ); ) );
$date = \gmdate( 'D, d M Y H:i:s T' ); // we just need any user to generate a request signature
$signature = \Activitypub\Signature::generate_signature( $user_id, $url, $date ); $user_id = \reset( $user );
$wp_version = \get_bloginfo( 'version' ); $response = \Activitypub\safe_remote_get( $actor, $user_id );
$user_agent = \apply_filters( 'http_headers_useragent', 'WordPress/' . $wp_version . '; ' . \get_bloginfo( 'url' ) );
$args = array(
'timeout' => 100,
'limit_response_size' => 1048576,
'redirection' => 3,
'user-agent' => "$user_agent; ActivityPub",
'headers' => array(
'accept' => 'application/activity+json, application/ld+json, application/ld+json; profile="https://www.w3.org/ns/activitystreams"',
'Signature' => $signature,
'Date' => $date,
),
);
$response = \wp_safe_remote_get( $actor, $args );
if ( \is_wp_error( $response ) ) { if ( \is_wp_error( $response ) ) {
return $response; return $response;
@ -277,3 +287,50 @@ function url_to_authorid( $url ) {
return 0; return 0;
} }
/**
* Get the blacklist from the WordPress options table
*
* @return array the list of blacklisted hosts
*
* @uses apply_filters() Calls 'activitypub_blacklist' filter
*/
function get_blacklist() {
$blacklist = \get_option( 'activitypub_blacklist' );
$blacklist_hosts = \explode( PHP_EOL, $blacklist );
// if no values have been set, revert to the defaults
if ( ! $blacklist || ! $blacklist_hosts || ! \is_array( $blacklist_hosts ) ) {
$blacklist_hosts = array(
'gab.com',
);
}
// clean out any blank values
foreach ( $blacklist_hosts as $key => $value ) {
if ( empty( $value ) ) {
unset( $blacklist_hosts[ $key ] );
} else {
$blacklist_hosts[ $key ] = \trim( $blacklist_hosts[ $key ] );
}
}
return \apply_filters( 'activitypub_blacklist', $blacklist_hosts );
}
/**
* Check if an URL is blacklisted
*
* @param string $url an URL to check
*
* @return boolean
*/
function is_blacklisted( $url ) {
foreach ( \ActivityPub\get_blacklist() as $blacklisted_host ) {
if ( \strpos( $url, $blacklisted_host ) !== false ) {
return true;
}
}
return false;
}

View file

@ -14,7 +14,7 @@ class Inbox {
*/ */
public static function init() { public static function init() {
\add_action( 'rest_api_init', array( '\Activitypub\Rest\Inbox', 'register_routes' ) ); \add_action( 'rest_api_init', array( '\Activitypub\Rest\Inbox', 'register_routes' ) );
//\add_filter( 'rest_pre_serve_request', array( '\Activitypub\Rest\Inbox', 'serve_request' ), 11, 4 ); \add_filter( 'rest_pre_serve_request', array( '\Activitypub\Rest\Inbox', 'serve_request' ), 11, 4 );
\add_action( 'activitypub_inbox_follow', array( '\Activitypub\Rest\Inbox', 'handle_follow' ), 10, 2 ); \add_action( 'activitypub_inbox_follow', array( '\Activitypub\Rest\Inbox', 'handle_follow' ), 10, 2 );
\add_action( 'activitypub_inbox_unfollow', array( '\Activitypub\Rest\Inbox', 'handle_unfollow' ), 10, 2 ); \add_action( 'activitypub_inbox_unfollow', array( '\Activitypub\Rest\Inbox', 'handle_unfollow' ), 10, 2 );
//\add_action( 'activitypub_inbox_like', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 ); //\add_action( 'activitypub_inbox_like', array( '\Activitypub\Rest\Inbox', 'handle_reaction' ), 10, 2 );
@ -36,7 +36,7 @@ class Inbox {
); );
\register_rest_route( \register_rest_route(
'activitypub/1.0', '/users/(?P<id>\d+)/inbox', array( 'activitypub/1.0', '/users/(?P<user_id>\d+)/inbox', array(
array( array(
'methods' => \WP_REST_Server::EDITABLE, 'methods' => \WP_REST_Server::EDITABLE,
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox' ), 'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox' ),
@ -61,10 +61,6 @@ class Inbox {
return $served; return $served;
} }
if ( 'POST' !== $request->get_method() ) {
return $served;
}
$signature = $request->get_header( 'signature' ); $signature = $request->get_header( 'signature' );
if ( ! $signature ) { if ( ! $signature ) {
@ -73,6 +69,7 @@ class Inbox {
$headers = $request->get_headers(); $headers = $request->get_headers();
// verify signature
//\Activitypub\Signature::verify_signature( $headers, $key ); //\Activitypub\Signature::verify_signature( $headers, $key );
return $served; return $served;
@ -86,21 +83,13 @@ class Inbox {
* @return WP_REST_Response * @return WP_REST_Response
*/ */
public static function user_inbox( $request ) { public static function user_inbox( $request ) {
$author_id = $request->get_param( 'id' ); $user_id = $request->get_param( 'user_id' );
$data = \json_decode( $request->get_body(), true ); $data = $request->get_params();
$type = $request->get_param( 'type' );
if ( ! \is_array( $data ) || ! \array_key_exists( 'type', $data ) ) { \do_action( 'activitypub_inbox', $data, $user_id, $type );
return new \WP_Error( 'rest_invalid_data', \__( 'Invalid payload', 'activitypub' ), array( 'status' => 422 ) ); \do_action( "activitypub_inbox_{$type}", $data, $user_id );
}
$type = 'create';
if ( ! empty( $data['type'] ) ) {
$type = \strtolower( $data['type'] );
}
\do_action( 'activitypub_inbox', $data, $author_id, $type );
\do_action( "activitypub_inbox_{$type}", $data, $author_id );
return new \WP_REST_Response( array(), 202 ); return new \WP_REST_Response( array(), 202 );
} }
@ -113,61 +102,7 @@ class Inbox {
* @return WP_Error not yet implemented * @return WP_Error not yet implemented
*/ */
public static function shared_inbox( $request ) { public static function shared_inbox( $request ) {
$data = \json_decode( $request->get_body(), true );
if ( empty( $data['to'] ) ) {
return new \WP_Error( 'rest_invalid_data', \__( 'No receiving actor set', 'activitypub' ), array( 'status' => 422 ) );
}
if ( \filter_var( $data['to'], \FILTER_VALIDATE_URL ) ) {
$author_id = \Activitypub\url_to_authorid( $data['to'] );
if ( ! $author_id ) {
return new \WP_Error( 'rest_invalid_data', \__( 'No matching user', 'activitypub' ), array( 'status' => 422 ) );
}
} else {
// get the identifier at the left of the '@'
$parts = \explode( '@', $data['to'] );
if ( 3 === \count( $parts ) ) {
$username = $parts[1];
$host = $parts[2];
} elseif ( 2 === \count( $parts ) ) {
$username = $parts[0];
$host = $parts[1];
}
if ( ! $username || ! $host ) {
return new \WP_Error( 'rest_invalid_data', \__( 'Invalid actor identifier', 'activitypub' ), array( 'status' => 422 ) );
}
// check domain
if ( ! \wp_parse_url( \home_url(), \PHP_URL_HOST ) !== $host ) {
return new \WP_Error( 'rest_invalid_data', \__( 'Invalid host', 'activitypub' ), array( 'status' => 422 ) );
}
$author = \get_user_by( 'login', $username );
if ( ! $author ) {
return new \WP_Error( 'rest_invalid_data', \__( 'No matching user', 'activitypub' ), array( 'status' => 422 ) );
}
$author_id = $author->ID;
}
if ( ! \is_array( $data ) || ! \array_key_exists( 'type', $data ) ) {
return new \WP_Error( 'rest_invalid_data', \__( 'Invalid payload', 'activitypub' ), array( 'status' => 422 ) );
}
$type = 'create';
if ( ! empty( $data['type'] ) ) {
$type = \strtolower( $data['type'] );
}
\do_action( 'activitypub_inbox', $data, $author_id, $type );
\do_action( "activitypub_inbox_{$type}", $data, $author_id );
return new \WP_REST_Response( array(), 202 );
} }
/** /**
@ -182,11 +117,54 @@ class Inbox {
'type' => 'integer', 'type' => 'integer',
); );
$params['id'] = array( $params['user_id'] = array(
'required' => true, 'required' => true,
'type' => 'integer', 'type' => 'integer',
); );
$params['id'] = array(
'required' => true,
'type' => 'string',
'validate_callback' => function( $param, $request, $key ) {
if ( ! \is_string( $param ) ) {
$param = $param['id'];
}
return ! \Activitypub\is_blacklisted( $param );
},
'sanitize_callback' => 'esc_url_raw',
);
$params['actor'] = array(
'required' => true,
'type' => array( 'object', 'string' ),
'validate_callback' => function( $param, $request, $key ) {
if ( ! \is_string( $param ) ) {
$param = $param['id'];
}
return ! \Activitypub\is_blacklisted( $param );
},
'sanitize_callback' => function( $param, $request, $key ) {
if ( ! \is_string( $param ) ) {
$param = $param['id'];
}
return \esc_url_raw( $param );
},
);
$params['type'] = array(
'required' => true,
'type' => 'enum',
'enum' => array( 'Create' ),
'sanitize_callback' => function( $param, $request, $key ) {
return \strtolower( $param );
},
);
$params['object'] = array(
'required' => true,
'type' => 'object',
);
return $params; return $params;
} }
@ -197,10 +175,6 @@ class Inbox {
* @param int $user_id The id of the local blog-user * @param int $user_id The id of the local blog-user
*/ */
public static function handle_follow( $object, $user_id ) { public static function handle_follow( $object, $user_id ) {
if ( ! \array_key_exists( 'actor', $object ) ) {
return new \WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ) );
}
// save follower // save follower
\Activitypub\Peer\Followers::add_follower( $object['actor'], $user_id ); \Activitypub\Peer\Followers::add_follower( $object['actor'], $user_id );
@ -226,10 +200,6 @@ class Inbox {
* @param int $user_id The id of the local blog-user * @param int $user_id The id of the local blog-user
*/ */
public static function handle_unfollow( $object, $user_id ) { public static function handle_unfollow( $object, $user_id ) {
if ( ! \array_key_exists( 'actor', $object ) ) {
return new \WP_Error( 'activitypub_no_actor', \__( 'No "Actor" found', 'activitypub' ) );
}
\Activitypub\Peer\Followers::remove_follower( $object['actor'], $user_id ); \Activitypub\Peer\Followers::remove_follower( $object['actor'], $user_id );
} }
@ -240,10 +210,6 @@ class Inbox {
* @param int $user_id The id of the local blog-user * @param int $user_id The id of the local blog-user
*/ */
public static function handle_reaction( $object, $user_id ) { public static function handle_reaction( $object, $user_id ) {
if ( ! \array_key_exists( 'actor', $object ) ) {
return new \WP_Error( 'activitypub_no_actor', \__( 'No "Actor" found', 'activitypub' ) );
}
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
$commentdata = array( $commentdata = array(
@ -277,10 +243,6 @@ class Inbox {
* @param int $user_id The id of the local blog-user * @param int $user_id The id of the local blog-user
*/ */
public static function handle_create( $object, $user_id ) { public static function handle_create( $object, $user_id ) {
if ( ! \array_key_exists( 'actor', $object ) ) {
return new \WP_Error( 'activitypub_no_actor', __( 'No "Actor" found', 'activitypub' ) );
}
$meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] ); $meta = \Activitypub\get_remote_metadata_by_actor( $object['actor'] );
$commentdata = array( $commentdata = array(

View file

@ -0,0 +1,31 @@
<?php
namespace Activitypub\Rest;
/**
* Custom (hopefully temporary) ActivityPub Rest Server
*
* @author Matthias Pfefferle
*/
class Server extends \WP_REST_Server {
/**
* Overwrite dispatch function to quick fix missing subtype featur
*
* @see https://core.trac.wordpress.org/ticket/49404
*
* @param WP_REST_Request $request Request to attempt dispatching.
* @return WP_REST_Response Response returned by the callback.
*/
public function dispatch( $request ) {
$content_type = $request->get_content_type();
// check for content-sub-types like 'application/ld+json; profile="https://www.w3.org/ns/activitystreams"'
if ( \preg_match( '/application\/([a-zA-Z+_-]+\+)json/', $content_type['value'] ) ) {
$request->set_header( 'Content-Type', 'application/json' );
}
// make request filterable
$request = apply_filters( 'activitypub_pre_dispatch_request', $request );
return parent::dispatch( $request );
}
}

View file

@ -5,7 +5,7 @@ msgstr ""
"Project-Id-Version: ActivityPub 0.9.1\n" "Project-Id-Version: ActivityPub 0.9.1\n"
"Report-Msgid-Bugs-To: " "Report-Msgid-Bugs-To: "
"https://wordpress.org/support/plugin/wordpress-activitypub\n" "https://wordpress.org/support/plugin/wordpress-activitypub\n"
"POT-Creation-Date: 2020-02-11 09:13:49+00:00\n" "POT-Creation-Date: 2020-02-21 10:08:48+00:00\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf-8\n" "Content-Type: text/plain; charset=utf-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
@ -48,11 +48,15 @@ msgstr ""
msgid "Enable ActivityPub support for post types" msgid "Enable ActivityPub support for post types"
msgstr "" msgstr ""
#: includes/class-admin.php:115 #: includes/class-admin.php:112
msgid "Block fediverse instances"
msgstr ""
#: includes/class-admin.php:123
msgid "Overview" msgid "Overview"
msgstr "" msgstr ""
#: includes/class-admin.php:117 #: includes/class-admin.php:125
msgid "" msgid ""
"ActivityPub is a decentralized social networking protocol based on the " "ActivityPub is a decentralized social networking protocol based on the "
"ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended " "ActivityStreams 2.0 data format. ActivityPub is an official W3C recommended "
@ -62,53 +66,53 @@ msgid ""
"subscribing to content." "subscribing to content."
msgstr "" msgstr ""
#: includes/class-admin.php:122 #: includes/class-admin.php:130
msgid "For more information:" msgid "For more information:"
msgstr "" msgstr ""
#: includes/class-admin.php:123 #: includes/class-admin.php:131
msgid "<a href=\"https://activitypub.rocks/\">Test Suite</a>" msgid "<a href=\"https://activitypub.rocks/\">Test Suite</a>"
msgstr "" msgstr ""
#: includes/class-admin.php:124 #: includes/class-admin.php:132
msgid "<a href=\"https://www.w3.org/TR/activitypub/\">W3C Spec</a>" msgid "<a href=\"https://www.w3.org/TR/activitypub/\">W3C Spec</a>"
msgstr "" msgstr ""
#: includes/class-admin.php:125 #: includes/class-admin.php:133
msgid "" msgid ""
"<a href=\"https://github.com/pfefferle/wordpress-activitypub/issues\">Give " "<a href=\"https://github.com/pfefferle/wordpress-activitypub/issues\">Give "
"us feedback</a>" "us feedback</a>"
msgstr "" msgstr ""
#: includes/class-admin.php:127 #: includes/class-admin.php:135
msgid "<a href=\"https://notiz.blog/donate\">Donate</a>" msgid "<a href=\"https://notiz.blog/donate\">Donate</a>"
msgstr "" msgstr ""
#: includes/class-admin.php:137 #: includes/class-admin.php:145
msgid "Fediverse" msgid "Fediverse"
msgstr "" msgstr ""
#: includes/functions.php:84 #: includes/functions.php:110
msgid "The \"actor\" is no valid URL" msgid "The \"actor\" is no valid URL"
msgstr "" msgstr ""
#: includes/functions.php:122 #: includes/functions.php:132
msgid "No valid JSON data" msgid "No valid JSON data"
msgstr "" msgstr ""
#: includes/functions.php:150 #: includes/functions.php:160
msgid "No \"Inbox\" found" msgid "No \"Inbox\" found"
msgstr "" msgstr ""
#: includes/functions.php:176 #: includes/functions.php:186
msgid "No \"Public-Key\" found" msgid "No \"Public-Key\" found"
msgstr "" msgstr ""
#: includes/functions.php:204 #: includes/functions.php:214
msgid "Profile identifier" msgid "Profile identifier"
msgstr "" msgstr ""
#: includes/functions.php:209 #: includes/functions.php:219
#. translators: the webfinger resource #. translators: the webfinger resource
msgid "Try to follow \"@%s\" in the Mastodon/Friendica search field." msgid "Try to follow \"@%s\" in the Mastodon/Friendica search field."
msgstr "" msgstr ""
@ -124,31 +128,6 @@ msgstr ""
msgid "User not found" msgid "User not found"
msgstr "" msgstr ""
#: includes/rest/class-inbox.php:94 includes/rest/class-inbox.php:159
msgid "Invalid payload"
msgstr ""
#: includes/rest/class-inbox.php:119
msgid "No receiving actor set"
msgstr ""
#: includes/rest/class-inbox.php:126 includes/rest/class-inbox.php:152
msgid "No matching user"
msgstr ""
#: includes/rest/class-inbox.php:141
msgid "Invalid actor identifier"
msgstr ""
#: includes/rest/class-inbox.php:146
msgid "Invalid host"
msgstr ""
#: includes/rest/class-inbox.php:201 includes/rest/class-inbox.php:230
#: includes/rest/class-inbox.php:244 includes/rest/class-inbox.php:281
msgid "No \"Actor\" found"
msgstr ""
#: includes/rest/class-webfinger.php:48 #: includes/rest/class-webfinger.php:48
msgid "Resource is invalid" msgid "Resource is invalid"
msgstr "" msgstr ""
@ -280,7 +259,26 @@ msgstr ""
msgid "Add all tags as hashtags to the end of each activity." msgid "Add all tags as hashtags to the end of each activity."
msgstr "" msgstr ""
#: templates/settings.php:99 #: templates/settings.php:93
msgid "Server"
msgstr ""
#: templates/settings.php:95
msgid "Server related settings."
msgstr ""
#: templates/settings.php:106
msgid "Blacklist"
msgstr ""
#: templates/settings.php:110
msgid ""
"A list of hosts, you want to block, one host per line. Please use only the "
"host/domain of the server you want to block, without <code>http://</code> "
"and without <code>www.</code>. For example <code>example.com</code>."
msgstr ""
#: templates/settings.php:124
msgid "" msgid ""
"If you like this plugin, what about a small <a " "If you like this plugin, what about a small <a "
"href=\"https://notiz.blog/donate\">donation</a>?" "href=\"https://notiz.blog/donate\">donation</a>?"

View file

@ -4,8 +4,6 @@
<file>./activitypub.php</file> <file>./activitypub.php</file>
<file>./includes/</file> <file>./includes/</file>
<config name="minimum_supported_wp_version" value="4.7"/> <config name="minimum_supported_wp_version" value="4.7"/>
<config name="installed_paths" value="vendor/phpcompatibility/php-compatibility" />
<config name="installed_paths" value="vendor/wp-coding-standards/wpcs" />
<exclude-pattern>*\.(inc|css|js|svg)</exclude-pattern> <exclude-pattern>*\.(inc|css|js|svg)</exclude-pattern>
<exclude-pattern>*/vendor/*</exclude-pattern> <exclude-pattern>*/vendor/*</exclude-pattern>
<exclude-pattern>*/node_modules/*</exclude-pattern> <exclude-pattern>*/node_modules/*</exclude-pattern>

View file

@ -90,6 +90,31 @@
<?php \do_settings_fields( 'activitypub', 'activity' ); ?> <?php \do_settings_fields( 'activitypub', 'activity' ); ?>
<h2><?php \esc_html_e( 'Server', 'activitypub' ); ?></h2>
<p><?php \esc_html_e( 'Server related settings.', 'activitypub' ); ?></p>
<?php
// load the existing blacklist from the WordPress options table
$activitypub_blacklist = \trim( \implode( PHP_EOL, \ActivityPub\get_blacklist() ), PHP_EOL );
?>
<table class="form-table">
<tbody>
<tr>
<th scope="row">
<?php \esc_html_e( 'Blacklist', 'activitypub' ); ?>
</th>
<td>
<textarea name="activitypub_blacklist" id="activitypub_blacklist" rows="10" cols="50" class="large-text"><?php echo $activitypub_blacklist; ?></textarea>
<p class="description"><?php \_e( 'A list of hosts, you want to block, one host per line. Please use only the host/domain of the server you want to block, without <code>http://</code> and without <code>www.</code>. For example <code>example.com</code>.', 'activitypub' ); ?></p>
</td>
</tr>
</tbody>
</table>
<?php \do_settings_fields( 'activitypub', 'server' ); ?>
<?php \do_settings_sections( 'activitypub' ); ?> <?php \do_settings_sections( 'activitypub' ); ?>
<?php \submit_button(); ?> <?php \submit_button(); ?>