Merge pull request #231 from pfefferle/security_privacy
Security & privacy related fixes
This commit is contained in:
commit
7d71ac07e1
15 changed files with 195 additions and 20 deletions
27
Dockerfile
Normal file
27
Dockerfile
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
FROM php:7.4-alpine3.13
|
||||||
|
|
||||||
|
RUN mkdir /app
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# Install Git, NPM & needed libraries
|
||||||
|
RUN apk update \
|
||||||
|
&& apk add bash git nodejs npm gettext subversion mysql mysql-client zip \
|
||||||
|
&& rm -f /var/cache/apk/*
|
||||||
|
|
||||||
|
RUN docker-php-ext-install mysqli
|
||||||
|
|
||||||
|
# Install Composer
|
||||||
|
RUN EXPECTED_CHECKSUM=$(curl -s https://composer.github.io/installer.sig) \
|
||||||
|
&& curl https://getcomposer.org/installer -o composer-setup.php \
|
||||||
|
&& ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" \
|
||||||
|
&& if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then >&2 echo 'ERROR: Invalid installer checksum'; rm composer-setup.php; exit 1; fi \
|
||||||
|
&& php composer-setup.php --quiet \
|
||||||
|
&& php -r "unlink('composer-setup.php');" \
|
||||||
|
&& mv composer.phar /usr/local/bin/composer
|
||||||
|
|
||||||
|
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
|
||||||
|
chmod +x wp-cli.phar && \
|
||||||
|
mv wp-cli.phar /usr/local/bin/wp
|
||||||
|
|
||||||
|
RUN chmod +x -R ./
|
|
@ -4,7 +4,7 @@
|
||||||
**Tags:** OStatus, fediverse, activitypub, activitystream
|
**Tags:** OStatus, fediverse, activitypub, activitystream
|
||||||
**Requires at least:** 4.7
|
**Requires at least:** 4.7
|
||||||
**Tested up to:** 6.1
|
**Tested up to:** 6.1
|
||||||
**Stable tag:** 0.14.3
|
**Stable tag:** 0.15.0
|
||||||
**Requires PHP:** 5.6
|
**Requires PHP:** 5.6
|
||||||
**License:** MIT
|
**License:** MIT
|
||||||
**License URI:** http://opensource.org/licenses/MIT
|
**License URI:** http://opensource.org/licenses/MIT
|
||||||
|
@ -88,6 +88,12 @@ 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).
|
Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
|
||||||
|
|
||||||
|
### 0.15.0 ###
|
||||||
|
|
||||||
|
* Enable ActivityPub only for users that can `publish_posts`
|
||||||
|
* Persist only public Activities
|
||||||
|
* Fix remote-delete
|
||||||
|
|
||||||
### 0.14.3 ###
|
### 0.14.3 ###
|
||||||
|
|
||||||
* Better error handling. props [@akirk](https://github.com/akirk)
|
* Better error handling. props [@akirk](https://github.com/akirk)
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* Plugin Name: ActivityPub
|
* Plugin Name: ActivityPub
|
||||||
* Plugin URI: https://github.com/pfefferle/wordpress-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.
|
* Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
|
||||||
* Version: 0.14.3
|
* Version: 0.15.0
|
||||||
* Author: Matthias Pfefferle
|
* Author: Matthias Pfefferle
|
||||||
* Author URI: https://notiz.blog/
|
* Author URI: https://notiz.blog/
|
||||||
* License: MIT
|
* License: MIT
|
||||||
|
|
|
@ -34,7 +34,7 @@
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": [
|
"test": [
|
||||||
"composer install",
|
"composer install",
|
||||||
"bin/install-wp-tests.sh wordpress wordpress wordpress",
|
"bin/install-wp-tests.sh activitypub-test root activitypub-test test-db latest true",
|
||||||
"vendor/bin/phpunit"
|
"vendor/bin/phpunit"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
17
docker-compose-test.yml
Normal file
17
docker-compose-test.yml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
version: '2'
|
||||||
|
services:
|
||||||
|
test-db:
|
||||||
|
image: mysql:5.7
|
||||||
|
environment:
|
||||||
|
MYSQL_DATABASE: activitypub-test
|
||||||
|
MYSQL_ROOT_PASSWORD: activitypub-test
|
||||||
|
|
||||||
|
test-php:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
links:
|
||||||
|
- test-db
|
||||||
|
volumes:
|
||||||
|
- .:/app
|
||||||
|
command: ["composer", "run-script", "test"]
|
|
@ -24,6 +24,8 @@ class Activitypub {
|
||||||
}
|
}
|
||||||
|
|
||||||
\add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 10, 3 );
|
\add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 10, 3 );
|
||||||
|
\add_action( 'wp_trash_post', array( '\Activitypub\Activitypub', 'trash_post' ), 1 );
|
||||||
|
\add_action( 'untrash_post', array( '\Activitypub\Activitypub', 'untrash_post' ), 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,6 +40,11 @@ class Activitypub {
|
||||||
return $template;
|
return $template;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if user can publish posts
|
||||||
|
if ( \is_author() && ! user_can( \get_the_author_meta( 'ID' ), 'publish_posts' ) ) {
|
||||||
|
return $template;
|
||||||
|
}
|
||||||
|
|
||||||
if ( \is_author() ) {
|
if ( \is_author() ) {
|
||||||
$json_template = \dirname( __FILE__ ) . '/../templates/author-json.php';
|
$json_template = \dirname( __FILE__ ) . '/../templates/author-json.php';
|
||||||
} elseif ( \is_singular() ) {
|
} elseif ( \is_singular() ) {
|
||||||
|
@ -180,4 +187,26 @@ class Activitypub {
|
||||||
}
|
}
|
||||||
return \get_comment_meta( $comment->comment_ID, 'avatar_url', true );
|
return \get_comment_meta( $comment->comment_ID, 'avatar_url', true );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Store permalink in meta, to send delete Activity
|
||||||
|
*
|
||||||
|
* @param string $post_id The Post ID
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function trash_post( $post_id ) {
|
||||||
|
\add_post_meta( $post_id, 'activitypub_canonical_url', \get_permalink( $post_id ), true );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete permalink from meta
|
||||||
|
*
|
||||||
|
* @param string $post_id The Post ID
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function untrash_post( $post_id ) {
|
||||||
|
\delete_post_meta( $post_id, 'activitypub_canonical_url' );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,11 +68,15 @@ class Post {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generate_id() {
|
public function generate_id() {
|
||||||
$post = $this->post;
|
$post = $this->post;
|
||||||
$permalink = \get_permalink( $post );
|
|
||||||
|
|
||||||
// replace 'trashed' for delete activity
|
if ( 'trash' === get_post_status( $post ) ) {
|
||||||
return \str_replace( '__trashed', '', $permalink );
|
$permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
|
||||||
|
} else {
|
||||||
|
$permalink = \get_permalink( $post );
|
||||||
|
}
|
||||||
|
|
||||||
|
return $permalink;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function generate_attachments() {
|
public function generate_attachments() {
|
||||||
|
|
|
@ -101,6 +101,9 @@ class Followers {
|
||||||
$params['user_id'] = array(
|
$params['user_id'] = array(
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
|
'validate_callback' => function( $param, $request, $key ) {
|
||||||
|
return user_can( $param, 'publish_posts' );
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
|
|
|
@ -99,6 +99,9 @@ class Following {
|
||||||
$params['user_id'] = array(
|
$params['user_id'] = array(
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
|
'validate_callback' => function( $param, $request, $key ) {
|
||||||
|
return user_can( $param, 'publish_posts' );
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Inbox {
|
||||||
array(
|
array(
|
||||||
'methods' => \WP_REST_Server::EDITABLE,
|
'methods' => \WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox_post' ),
|
'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox_post' ),
|
||||||
'args' => self::shared_inbox_request_parameters(),
|
'args' => self::shared_inbox_post_parameters(),
|
||||||
'permission_callback' => '__return_true',
|
'permission_callback' => '__return_true',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -46,12 +46,13 @@ class Inbox {
|
||||||
array(
|
array(
|
||||||
'methods' => \WP_REST_Server::EDITABLE,
|
'methods' => \WP_REST_Server::EDITABLE,
|
||||||
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_post' ),
|
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_post' ),
|
||||||
'args' => self::user_inbox_request_parameters(),
|
'args' => self::user_inbox_post_parameters(),
|
||||||
'permission_callback' => '__return_true',
|
'permission_callback' => '__return_true',
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'methods' => \WP_REST_Server::READABLE,
|
'methods' => \WP_REST_Server::READABLE,
|
||||||
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_get' ),
|
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_get' ),
|
||||||
|
'args' => self::user_inbox_get_parameters(),
|
||||||
'permission_callback' => '__return_true',
|
'permission_callback' => '__return_true',
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -195,7 +196,7 @@ class Inbox {
|
||||||
*
|
*
|
||||||
* @return array list of parameters
|
* @return array list of parameters
|
||||||
*/
|
*/
|
||||||
public static function user_inbox_request_parameters() {
|
public static function user_inbox_get_parameters() {
|
||||||
$params = array();
|
$params = array();
|
||||||
|
|
||||||
$params['page'] = array(
|
$params['page'] = array(
|
||||||
|
@ -205,6 +206,32 @@ class Inbox {
|
||||||
$params['user_id'] = array(
|
$params['user_id'] = array(
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
|
'validate_callback' => function( $param, $request, $key ) {
|
||||||
|
return user_can( $param, 'publish_posts' );
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return $params;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The supported parameters
|
||||||
|
*
|
||||||
|
* @return array list of parameters
|
||||||
|
*/
|
||||||
|
public static function user_inbox_post_parameters() {
|
||||||
|
$params = array();
|
||||||
|
|
||||||
|
$params['page'] = array(
|
||||||
|
'type' => 'integer',
|
||||||
|
);
|
||||||
|
|
||||||
|
$params['user_id'] = array(
|
||||||
|
'required' => true,
|
||||||
|
'type' => 'integer',
|
||||||
|
'validate_callback' => function( $param, $request, $key ) {
|
||||||
|
return user_can( $param, 'publish_posts' );
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
$params['id'] = array(
|
$params['id'] = array(
|
||||||
|
@ -243,7 +270,7 @@ class Inbox {
|
||||||
*
|
*
|
||||||
* @return array list of parameters
|
* @return array list of parameters
|
||||||
*/
|
*/
|
||||||
public static function shared_inbox_request_parameters() {
|
public static function shared_inbox_post_parameters() {
|
||||||
$params = array();
|
$params = array();
|
||||||
|
|
||||||
$params['page'] = array(
|
$params['page'] = array(
|
||||||
|
@ -410,6 +437,12 @@ class Inbox {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// check if Activity is public or not
|
||||||
|
if ( ! self::is_activity_public( $object ) ) {
|
||||||
|
// @todo maybe send email
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
|
$comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
|
||||||
|
|
||||||
// save only replys and reactions
|
// save only replys and reactions
|
||||||
|
@ -446,21 +479,53 @@ class Inbox {
|
||||||
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract recipient URLs from Activity object
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array The list of user URLs
|
||||||
|
*/
|
||||||
public static function extract_recipients( $data ) {
|
public static function extract_recipients( $data ) {
|
||||||
$recipients = array();
|
$recipient_items = array();
|
||||||
$users = array();
|
|
||||||
|
|
||||||
foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
|
foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
|
||||||
if ( array_key_exists( $i, $data ) ) {
|
if ( array_key_exists( $i, $data ) ) {
|
||||||
$recipients = array_merge( $recipients, $data[ $i ] );
|
$recipient_items = array_merge( $recipient_items, $data[ $i ] );
|
||||||
}
|
}
|
||||||
|
|
||||||
if ( array_key_exists( $i, $data['object'] ) ) {
|
if ( array_key_exists( $i, $data['object'] ) ) {
|
||||||
$recipients = array_merge( $recipients, $data[ $i ] );
|
$recipient_items = array_merge( $recipient_items, $data[ $i ] );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$recipients = array_unique( $recipients );
|
$recipients = array();
|
||||||
|
|
||||||
|
// flatten array
|
||||||
|
foreach ( $recipient_items as $recipient ) {
|
||||||
|
if ( is_array( $recipient ) ) {
|
||||||
|
// check if recipient is an object
|
||||||
|
if ( array_key_exists( 'id', $recipient ) ) {
|
||||||
|
$recipients[] = $recipient['id'];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$recipients[] = $recipient;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_unique( $recipients );
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get local user recipients
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
*
|
||||||
|
* @return array The list of local users
|
||||||
|
*/
|
||||||
|
public static function get_recipients( $data ) {
|
||||||
|
$recipients = self::extract_recipients( $data );
|
||||||
|
$users = array();
|
||||||
|
|
||||||
foreach ( $recipients as $recipient ) {
|
foreach ( $recipients as $recipient ) {
|
||||||
$user_id = \Activitypub\url_to_authorid( $recipient );
|
$user_id = \Activitypub\url_to_authorid( $recipient );
|
||||||
|
@ -474,4 +539,16 @@ class Inbox {
|
||||||
|
|
||||||
return $users;
|
return $users;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if passed Activity is Public
|
||||||
|
*
|
||||||
|
* @param array $data
|
||||||
|
* @return boolean
|
||||||
|
*/
|
||||||
|
public static function is_activity_public( $data ) {
|
||||||
|
$recipients = self::extract_recipients( $data );
|
||||||
|
|
||||||
|
return in_array( 'https://www.w3.org/ns/activitystreams#Public', $recipients, true );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,9 @@ class Outbox {
|
||||||
$params['user_id'] = array(
|
$params['user_id'] = array(
|
||||||
'required' => true,
|
'required' => true,
|
||||||
'type' => 'integer',
|
'type' => 'integer',
|
||||||
|
'validate_callback' => function( $param, $request, $key ) {
|
||||||
|
return user_can( $param, 'publish_posts' );
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return $params;
|
return $params;
|
||||||
|
|
|
@ -59,7 +59,7 @@ class Webfinger {
|
||||||
|
|
||||||
$user = \get_user_by( 'login', \esc_sql( $resource_identifier ) );
|
$user = \get_user_by( 'login', \esc_sql( $resource_identifier ) );
|
||||||
|
|
||||||
if ( ! $user ) {
|
if ( ! $user || ! user_can( $user, 'publish_posts' ) ) {
|
||||||
return new \WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) );
|
return new \WP_Error( 'activitypub_user_not_found', \__( 'User not found', 'activitypub' ), array( 'status' => 404 ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
convertWarningsToExceptions="true"
|
convertWarningsToExceptions="true"
|
||||||
>
|
>
|
||||||
<testsuites>
|
<testsuites>
|
||||||
<testsuite>
|
<testsuite name="ActivityPub">
|
||||||
<directory prefix="test-" suffix=".php">./tests/</directory>
|
<directory prefix="test-" suffix=".php">./tests/</directory>
|
||||||
</testsuite>
|
</testsuite>
|
||||||
</testsuites>
|
</testsuites>
|
||||||
|
|
|
@ -4,7 +4,7 @@ Donate link: https://notiz.blog/donate/
|
||||||
Tags: OStatus, fediverse, activitypub, activitystream
|
Tags: OStatus, fediverse, activitypub, activitystream
|
||||||
Requires at least: 4.7
|
Requires at least: 4.7
|
||||||
Tested up to: 6.1
|
Tested up to: 6.1
|
||||||
Stable tag: 0.14.3
|
Stable tag: 0.15.0
|
||||||
Requires PHP: 5.6
|
Requires PHP: 5.6
|
||||||
License: MIT
|
License: MIT
|
||||||
License URI: http://opensource.org/licenses/MIT
|
License URI: http://opensource.org/licenses/MIT
|
||||||
|
@ -88,6 +88,12 @@ 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).
|
Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
|
||||||
|
|
||||||
|
= 0.15.0 =
|
||||||
|
|
||||||
|
* Enable ActivityPub only for users that can `publish_posts`
|
||||||
|
* Persist only public Activities
|
||||||
|
* Fix remote-delete
|
||||||
|
|
||||||
= 0.14.3 =
|
= 0.14.3 =
|
||||||
|
|
||||||
* Better error handling. props [@akirk](https://github.com/akirk)
|
* Better error handling. props [@akirk](https://github.com/akirk)
|
||||||
|
|
|
@ -38,7 +38,7 @@ $json->manuallyApprovesFollowers = \apply_filters( 'activitypub_json_manually_ap
|
||||||
$json->publicKey = array(
|
$json->publicKey = array(
|
||||||
'id' => \get_home_url( '/' ) . '#main-key',
|
'id' => \get_home_url( '/' ) . '#main-key',
|
||||||
'owner' => \get_home_url( '/' ),
|
'owner' => \get_home_url( '/' ),
|
||||||
'publicKeyPem' => \trim(),
|
'publicKeyPem' => '',
|
||||||
);
|
);
|
||||||
|
|
||||||
$json->tag = array();
|
$json->tag = array();
|
||||||
|
|
Loading…
Reference in a new issue