Skip to content

Commit 7d71ac0

Browse files
authored
Merge pull request #231 from pfefferle/security_privacy
Security & privacy related fixes
2 parents 7d5b8e7 + 195727b commit 7d71ac0

15 files changed

+195
-20
lines changed

Dockerfile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
FROM php:7.4-alpine3.13
2+
3+
RUN mkdir /app
4+
5+
WORKDIR /app
6+
7+
# Install Git, NPM & needed libraries
8+
RUN apk update \
9+
&& apk add bash git nodejs npm gettext subversion mysql mysql-client zip \
10+
&& rm -f /var/cache/apk/*
11+
12+
RUN docker-php-ext-install mysqli
13+
14+
# Install Composer
15+
RUN EXPECTED_CHECKSUM=$(curl -s https://composer.github.io/installer.sig) \
16+
&& curl https://getcomposer.org/installer -o composer-setup.php \
17+
&& ACTUAL_CHECKSUM="$(php -r "echo hash_file('sha384', 'composer-setup.php');")" \
18+
&& if [ "$EXPECTED_CHECKSUM" != "$ACTUAL_CHECKSUM" ]; then >&2 echo 'ERROR: Invalid installer checksum'; rm composer-setup.php; exit 1; fi \
19+
&& php composer-setup.php --quiet \
20+
&& php -r "unlink('composer-setup.php');" \
21+
&& mv composer.phar /usr/local/bin/composer
22+
23+
RUN curl -O https://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar && \
24+
chmod +x wp-cli.phar && \
25+
mv wp-cli.phar /usr/local/bin/wp
26+
27+
RUN chmod +x -R ./

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
**Tags:** OStatus, fediverse, activitypub, activitystream
55
**Requires at least:** 4.7
66
**Tested up to:** 6.1
7-
**Stable tag:** 0.14.3
7+
**Stable tag:** 0.15.0
88
**Requires PHP:** 5.6
99
**License:** MIT
1010
**License URI:** http://opensource.org/licenses/MIT
@@ -88,6 +88,12 @@ Where 'blog' is the path to the subdirectory at which your blog resides.
8888

8989
Project maintained on GitHub at [pfefferle/wordpress-activitypub](https://github.com/pfefferle/wordpress-activitypub).
9090

91+
### 0.15.0 ###
92+
93+
* Enable ActivityPub only for users that can `publish_posts`
94+
* Persist only public Activities
95+
* Fix remote-delete
96+
9197
### 0.14.3 ###
9298

9399
* Better error handling. props [@akirk](https://github.com/akirk)

activitypub.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
* Plugin Name: ActivityPub
44
* Plugin URI: https://github.com/pfefferle/wordpress-activitypub/
55
* Description: The ActivityPub protocol is a decentralized social networking protocol based upon the ActivityStreams 2.0 data format.
6-
* Version: 0.14.3
6+
* Version: 0.15.0
77
* Author: Matthias Pfefferle
88
* Author URI: https://notiz.blog/
99
* License: MIT

composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
"scripts": {
3535
"test": [
3636
"composer install",
37-
"bin/install-wp-tests.sh wordpress wordpress wordpress",
37+
"bin/install-wp-tests.sh activitypub-test root activitypub-test test-db latest true",
3838
"vendor/bin/phpunit"
3939
]
4040
}

docker-compose-test.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
version: '2'
2+
services:
3+
test-db:
4+
image: mysql:5.7
5+
environment:
6+
MYSQL_DATABASE: activitypub-test
7+
MYSQL_ROOT_PASSWORD: activitypub-test
8+
9+
test-php:
10+
build:
11+
context: .
12+
dockerfile: Dockerfile
13+
links:
14+
- test-db
15+
volumes:
16+
- .:/app
17+
command: ["composer", "run-script", "test"]

includes/class-activitypub.php

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ public static function init() {
2424
}
2525

2626
\add_action( 'transition_post_status', array( '\Activitypub\Activitypub', 'schedule_post_activity' ), 10, 3 );
27+
\add_action( 'wp_trash_post', array( '\Activitypub\Activitypub', 'trash_post' ), 1 );
28+
\add_action( 'untrash_post', array( '\Activitypub\Activitypub', 'untrash_post' ), 1 );
2729
}
2830

2931
/**
@@ -38,6 +40,11 @@ public static function render_json_template( $template ) {
3840
return $template;
3941
}
4042

43+
// check if user can publish posts
44+
if ( \is_author() && ! user_can( \get_the_author_meta( 'ID' ), 'publish_posts' ) ) {
45+
return $template;
46+
}
47+
4148
if ( \is_author() ) {
4249
$json_template = \dirname( __FILE__ ) . '/../templates/author-json.php';
4350
} elseif ( \is_singular() ) {
@@ -180,4 +187,26 @@ public static function get_avatar_url( $comment ) {
180187
}
181188
return \get_comment_meta( $comment->comment_ID, 'avatar_url', true );
182189
}
190+
191+
/**
192+
* Store permalink in meta, to send delete Activity
193+
*
194+
* @param string $post_id The Post ID
195+
*
196+
* @return void
197+
*/
198+
public static function trash_post( $post_id ) {
199+
\add_post_meta( $post_id, 'activitypub_canonical_url', \get_permalink( $post_id ), true );
200+
}
201+
202+
/**
203+
* Delete permalink from meta
204+
*
205+
* @param string $post_id The Post ID
206+
*
207+
* @return void
208+
*/
209+
public static function untrash_post( $post_id ) {
210+
\delete_post_meta( $post_id, 'activitypub_canonical_url' );
211+
}
183212
}

includes/model/class-post.php

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,11 +68,15 @@ public function to_json() {
6868
}
6969

7070
public function generate_id() {
71-
$post = $this->post;
72-
$permalink = \get_permalink( $post );
71+
$post = $this->post;
72+
73+
if ( 'trash' === get_post_status( $post ) ) {
74+
$permalink = \get_post_meta( $post->ID, 'activitypub_canonical_url', true );
75+
} else {
76+
$permalink = \get_permalink( $post );
77+
}
7378

74-
// replace 'trashed' for delete activity
75-
return \str_replace( '__trashed', '', $permalink );
79+
return $permalink;
7680
}
7781

7882
public function generate_attachments() {

includes/rest/class-followers.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,9 @@ public static function request_parameters() {
101101
$params['user_id'] = array(
102102
'required' => true,
103103
'type' => 'integer',
104+
'validate_callback' => function( $param, $request, $key ) {
105+
return user_can( $param, 'publish_posts' );
106+
},
104107
);
105108

106109
return $params;

includes/rest/class-following.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ public static function request_parameters() {
9999
$params['user_id'] = array(
100100
'required' => true,
101101
'type' => 'integer',
102+
'validate_callback' => function( $param, $request, $key ) {
103+
return user_can( $param, 'publish_posts' );
104+
},
102105
);
103106

104107
return $params;

includes/rest/class-inbox.php

Lines changed: 86 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ public static function register_routes() {
3333
array(
3434
'methods' => \WP_REST_Server::EDITABLE,
3535
'callback' => array( '\Activitypub\Rest\Inbox', 'shared_inbox_post' ),
36-
'args' => self::shared_inbox_request_parameters(),
36+
'args' => self::shared_inbox_post_parameters(),
3737
'permission_callback' => '__return_true',
3838
),
3939
)
@@ -46,12 +46,13 @@ public static function register_routes() {
4646
array(
4747
'methods' => \WP_REST_Server::EDITABLE,
4848
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_post' ),
49-
'args' => self::user_inbox_request_parameters(),
49+
'args' => self::user_inbox_post_parameters(),
5050
'permission_callback' => '__return_true',
5151
),
5252
array(
5353
'methods' => \WP_REST_Server::READABLE,
5454
'callback' => array( '\Activitypub\Rest\Inbox', 'user_inbox_get' ),
55+
'args' => self::user_inbox_get_parameters(),
5556
'permission_callback' => '__return_true',
5657
),
5758
)
@@ -195,7 +196,7 @@ public static function shared_inbox_post( $request ) {
195196
*
196197
* @return array list of parameters
197198
*/
198-
public static function user_inbox_request_parameters() {
199+
public static function user_inbox_get_parameters() {
199200
$params = array();
200201

201202
$params['page'] = array(
@@ -205,6 +206,32 @@ public static function user_inbox_request_parameters() {
205206
$params['user_id'] = array(
206207
'required' => true,
207208
'type' => 'integer',
209+
'validate_callback' => function( $param, $request, $key ) {
210+
return user_can( $param, 'publish_posts' );
211+
},
212+
);
213+
214+
return $params;
215+
}
216+
217+
/**
218+
* The supported parameters
219+
*
220+
* @return array list of parameters
221+
*/
222+
public static function user_inbox_post_parameters() {
223+
$params = array();
224+
225+
$params['page'] = array(
226+
'type' => 'integer',
227+
);
228+
229+
$params['user_id'] = array(
230+
'required' => true,
231+
'type' => 'integer',
232+
'validate_callback' => function( $param, $request, $key ) {
233+
return user_can( $param, 'publish_posts' );
234+
},
208235
);
209236

210237
$params['id'] = array(
@@ -243,7 +270,7 @@ public static function user_inbox_request_parameters() {
243270
*
244271
* @return array list of parameters
245272
*/
246-
public static function shared_inbox_request_parameters() {
273+
public static function shared_inbox_post_parameters() {
247274
$params = array();
248275

249276
$params['page'] = array(
@@ -410,6 +437,12 @@ public static function handle_create( $object, $user_id ) {
410437
return;
411438
}
412439

440+
// check if Activity is public or not
441+
if ( ! self::is_activity_public( $object ) ) {
442+
// @todo maybe send email
443+
return;
444+
}
445+
413446
$comment_post_id = \url_to_postid( $object['object']['inReplyTo'] );
414447

415448
// save only replys and reactions
@@ -446,21 +479,53 @@ public static function handle_create( $object, $user_id ) {
446479
\add_action( 'check_comment_flood', 'check_comment_flood_db', 10, 4 );
447480
}
448481

482+
/**
483+
* Extract recipient URLs from Activity object
484+
*
485+
* @param array $data
486+
*
487+
* @return array The list of user URLs
488+
*/
449489
public static function extract_recipients( $data ) {
450-
$recipients = array();
451-
$users = array();
490+
$recipient_items = array();
452491

453492
foreach ( array( 'to', 'bto', 'cc', 'bcc', 'audience' ) as $i ) {
454493
if ( array_key_exists( $i, $data ) ) {
455-
$recipients = array_merge( $recipients, $data[ $i ] );
494+
$recipient_items = array_merge( $recipient_items, $data[ $i ] );
456495
}
457496

458497
if ( array_key_exists( $i, $data['object'] ) ) {
459-
$recipients = array_merge( $recipients, $data[ $i ] );
498+
$recipient_items = array_merge( $recipient_items, $data[ $i ] );
499+
}
500+
}
501+
502+
$recipients = array();
503+
504+
// flatten array
505+
foreach ( $recipient_items as $recipient ) {
506+
if ( is_array( $recipient ) ) {
507+
// check if recipient is an object
508+
if ( array_key_exists( 'id', $recipient ) ) {
509+
$recipients[] = $recipient['id'];
510+
}
511+
} else {
512+
$recipients[] = $recipient;
460513
}
461514
}
462515

463-
$recipients = array_unique( $recipients );
516+
return array_unique( $recipients );
517+
}
518+
519+
/**
520+
* Get local user recipients
521+
*
522+
* @param array $data
523+
*
524+
* @return array The list of local users
525+
*/
526+
public static function get_recipients( $data ) {
527+
$recipients = self::extract_recipients( $data );
528+
$users = array();
464529

465530
foreach ( $recipients as $recipient ) {
466531
$user_id = \Activitypub\url_to_authorid( $recipient );
@@ -474,4 +539,16 @@ public static function extract_recipients( $data ) {
474539

475540
return $users;
476541
}
542+
543+
/**
544+
* Check if passed Activity is Public
545+
*
546+
* @param array $data
547+
* @return boolean
548+
*/
549+
public static function is_activity_public( $data ) {
550+
$recipients = self::extract_recipients( $data );
551+
552+
return in_array( 'https://www.w3.org/ns/activitystreams#Public', $recipients, true );
553+
}
477554
}

0 commit comments

Comments
 (0)