Implement Undo activity

This commit is contained in:
Jeremy Dormitzer 2018-09-25 23:46:31 -04:00
parent 024e1557b7
commit fd3afcbb16
9 changed files with 335 additions and 14 deletions

201
inc/activities/undo.php Normal file
View File

@ -0,0 +1,201 @@
<?php
namespace activities\undo;
require_once plugin_dir_path( __FILE__ ) . '/../util.php';
require_once plugin_dir_path( __FILE__ ) . '/../activities.php';
require_once plugin_dir_path( __FILE__ ) . '/../actors.php';
require_once plugin_dir_path( __FILE__ ) . '/../objects.php';
require_once plugin_dir_path( __FILE__ ) . '/../likes.php';
require_once plugin_dir_path( __FILE__ ) . '/../following.php';
require_once plugin_dir_path( __FILE__ ) . '/../followers.php';
function handle_outbox( $actor_slug, $activity ) {
$object = validate_undo( $activity );
if ( is_wp_error( $object ) ) {
return $object;
}
$actor_id = \actors\get_actor_id( $actor_slug );
if ( !$actor_id ) {
return new \WP_Error(
'not_found',
__( 'Actor not found', 'pterotype' ),
array( 'status' => 404 )
);
}
switch ( $object['type'] ) {
case 'Like':
if ( !array_key_exists( 'object', $object ) ) {
return new \WP_Error(
'invalid_activity',
__( 'Expected an "object" field', 'pterotype' ),
array( 'status' => 400 )
);
}
$liked_object_url = \util\get_id( $object['object'] );
if ( !$liked_object_url ) {
break;
}
$liked_object_id = \objects\get_object_id( $liked_object_url );
if ( !$liked_object_id ) {
break;
}
\likes\delete_local_actor_like( $actor_id, $liked_object_id );
$like_id = \activities\get_activity_id( $object['id'] );
if ( !$like_id ) {
break;
}
\likes\delete_object_like( $liked_object_id, $like_id );
break;
case 'Block':
if ( !array_key_exists( 'object', $object ) ) {
break;
}
$blocked_object_url = \util\get_id( $object['object'] );
if ( !$blocked_object_url ) {
break;
}
$res = \blocks\delete_block( $actor_id, $blocked_object_url );
if ( is_wp_error( $res ) ) {
return $res;
}
break;
case 'Follow':
if ( !array_key_exists( 'object', $object ) ) {
break;
}
$follow_object_url = \util\get_id( $object['object'] );
if ( !$follow_object_url ) {
break;
}
$follow_object_id = \objects\get_object_id( $follow_object_url );
if ( !$follow_object_id ) {
break;
}
\following\reject_follow( $actor_id, $follow_object_id );
break;
// TODO I should support Undoing these as well
case 'Add':
case 'Remove':
case 'Accept':
break;
default:
break;
}
return $activity;
}
function handle_inbox( $actor_slug, $activity ) {
$object = validate_undo( $activity );
if ( is_wp_error( $object ) ) {
return $object;
}
$actor_id = \actors\get_actor_id( $actor_slug );
if ( !$actor_id ) {
return new \WP_Error(
'not_found',
__( 'Actor not found', 'pterotype' ),
array( 'status' => 404 )
);
}
switch( $object['type'] ) {
case 'Like':
if ( !array_key_exists( 'object', $object ) ) {
break;
}
if ( \objects\is_local_object( $object['object'] ) ) {
$object_url = \objects\get_object_id( $object['object'] );
if ( !$object_url ) {
break;
}
$object_id = \objects\get_object_id( $object_url );
$like_id = \activities\get_activity_id( $object['id'] );
if ( !$like_id ) {
break;
}
\likes\delete_object_like( $object_id, $like_id );
}
break;
case 'Follow':
if ( !array_key_exists( 'actor', $object ) ) {
break;
}
$follower = $object['actor'];
\followers\remove_follower( $actor_slug, $follower );
break;
case 'Accept':
if ( !array_key_exists( 'object', $object ) ) {
break;
}
$accept_object = \util\dereference_object( $object['object'] );
if ( is_wp_error( $object ) ) {
break;
}
if ( array_key_exists( 'type', $accept_object ) && $accept_object['type'] === 'Follow' ) {
if ( !array_key_exists( 'object', $accept_object ) ) {
break;
}
$followed_object_url = \util\get_id( $accept_object['object'] );
$followed_object_id = \objects\get_object_id( $followed_object_url );
if ( !$followed_object_id ) {
break;
}
// Put the follow request back into the PENDING state
\following\request_follow( $actor_id, $followed_object_id );
}
break;
default:
break;
}
return $activity;
}
function validate_undo( $activity ) {
if ( !array_key_exists( 'actor', $activity ) ) {
return new \WP_Error(
'invalid_activity',
__( 'Expected an "actor" field', 'pterotype' ),
array( 'status' => 400 )
);
}
if ( !array_key_exists( 'object', $activity ) ) {
return new \WP_Error(
'invalid_activity',
__( 'Expected an "object" field', 'pterotype' ),
array( 'status' => 400 )
);
}
$object = \util\dereference_object( $activity['object'] );
if ( is_wp_error( $object ) ) {
return $object;
}
if ( !array_key_exists( 'actor', $object ) ) {
return new \WP_Error(
'invalid_activity',
__( 'Expected a "actor" field', 'pterotype' ),
array( 'status' => 400 )
);
}
if ( !array_key_exists( 'id', $object ) ) {
return new \WP_Error(
'invalid_activity',
__( 'Expected an "id" field', 'pterotype' ),
array( 'status' => 400 )
);
}
if ( !\util\is_same_object( $activity['actor'], $object['actor'] ) ) {
return new \WP_Error(
'unauthorized',
__( 'Unauthorzed Undo activity', 'pterotype' ),
array( 'status' => 403 )
);
}
if ( !array_key_exists( 'type', $object ) ) {
return new \WP_Error(
'invalid_activity',
__( 'Expected a "type" field', 'pterotype' ),
array( 'status' => 400 )
);
}
return $object;
}
?>

View File

@ -17,4 +17,15 @@ function create_block( $actor_id, $blocked_actor_url ) {
return new \WP_Error( 'db_error', __( 'Error inserting block row', 'pterotype' ) );
}
}
function delete_block( $actor_id, $blocked_actor_url ) {
global $wpdb;
$res = $wpdb->delete(
'pterotype_blocks',
array( 'actor_id' => $actor_id, 'blocked_actor_url' => $blocked_actor_url )
);
if ( !$res ) {
return new \WP_Error( 'db_error', __( 'Error inserting block row', 'pterotype' ) );
}
}
?>

View File

@ -35,6 +35,40 @@ function add_follower( $actor_slug, $follower ) {
);
}
function remove_follower( $actor_slug, $follower ) {
global $wpdb;
$actor_id = \actors\get_actor_id( $actor_slug );
if ( !$actor_id ) {
return new \WP_Error(
'not_found',
__( 'Actor not found', 'pterotype' ),
array( 'status' => 404 )
);
}
if ( !array_key_exists( 'id', $follower ) ) {
return new \WP_Error(
'invalid_object',
__( 'Object must have an "id" field', 'pterotype' ),
array( 'status' => 400 )
);
}
$object_id = \objects\get_object_id( $follower['id'] );
if ( !$object_id ) {
return new \WP_Error(
'not_found',
__( 'Object not found', 'pterotype' ),
array( 'status' => 404 )
);
}
$wpdb->delete(
'pterotype_followers',
array(
'actor_id' => $actor_id,
'object_id' = $object_id,
);
);
}
function get_followers_collection( $actor_slug ) {
global $wpdb;
$actor_id = \actors\get_actor_id( $actor_slug );

View File

@ -8,11 +8,12 @@ define( 'PTEROTYPE_FOLLOW_FOLLOWING', 'FOLLOWING' );
function request_follow( $actor_id, $object_id ) {
global $wpdb;
return $wpdb->insert(
return $wpdb->replace(
'pterotype_following',
array( 'actor_id' => $actor_id,
'object_id' => $object_id,
'state' => PTEROTYPE_FOLLOW_PENDING
array(
'actor_id' => $actor_id,
'object_id' => $object_id,
'state' => PTEROTYPE_FOLLOW_PENDING
)
);
}

View File

@ -19,6 +19,7 @@ require_once plugin_dir_path( __FILE__ ) . '/activities/follow.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/accept.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/reject.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/announce.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/undo.php';
function handle_activity( $actor_slug, $activity ) {
if ( !array_key_exists( 'type', $activity ) ) {
@ -52,17 +53,11 @@ function handle_activity( $actor_slug, $activity ) {
case 'Reject':
$activity = \activities\reject\handle_inbox( $actor_slug, $activity );
break;
case 'Add':
// TODO not yet implemented
break;
case 'Remove':
// TODO not yet implemented
break;
case 'Announce':
$activity = \activities\announce\handle_inbox( $actor_slug, $activity );
break;
case 'Undo':
// TODO
$activity = \activities\undo\handle_inbox( $actor_slug, $activity );
break;
}
if ( is_wp_error( $activity ) ) {

View File

@ -12,6 +12,15 @@ function create_local_actor_like( $actor_id, $object_id ) {
);
}
function delete_local_actor_like( $actor_id, $object_id ) {
global $wpdb;
return $wpdb->delete(
'pterotype_actor_likes',
array( 'actor_id' => $actor_id, 'object_id' => $object_id ),
'%d'
);
}
function record_like ( $object_id, $like_id ) {
global $wpdb;
return $wpdb->insert(
@ -24,6 +33,18 @@ function record_like ( $object_id, $like_id ) {
);
}
function delete_object_like( $object_id, $like_id ) {
global $wpdb;
return $wpdb->delete(
'pterotype_object_likes',
array(
'object_id' => $object_id,
'like_id' => $like_id
),
'%d'
);
}
function get_likes_collection( $object_id ) {
global $wpdb;
$likes = $wpdb->get_results(

View File

@ -240,8 +240,18 @@ function make_tombstone( $object ) {
}
function is_local_object( $object ) {
if ( array_key_exists( 'id', $object ) ) {
$parsed = parse_url( $object['id'] );
if ( is_array( $object ) ) {
if ( array_key_exists( 'id', $object ) ) {
$parsed = parse_url( $object['id'] );
if ( $parsed ) {
$site_host = parse_url( get_site_url() )['host'];
return $parsed['host'] === $site_host;
}
} else {
return false;
}
} else {
$parsed = parse_url( $object );
if ( $parsed ) {
$site_host = parse_url( get_site_url() )['host'];
return $parsed['host'] === $site_host;

View File

@ -21,6 +21,7 @@ require_once plugin_dir_path( __FILE__ ) . '/activities/delete.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/like.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/follow.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/block.php';
require_once plugin_dir_path( __FILE__ ) . '/activities/undo.php';
function handle_activity( $actor_slug, $activity ) {
// TODO handle authentication/authorization
@ -69,7 +70,7 @@ function handle_activity( $actor_slug, $activity ) {
$activity = \activities\block\handle_outbox( $actor_slug, $activity );
break;
case 'Undo':
// TODO
$activity = \activities\undo\handle_outbox( $actor_slug, $activity );
break;
case 'Accept':
$activity = \activities\accept\handle_inbox( $actor_slug, $activity );

47
inc/util.php Normal file
View File

@ -0,0 +1,47 @@
<?php
namespace util;
// TODO audit places throughout the repo where I need to dereference objects
// (this is anywhere I access a field on an object, basically)
function dereference_object( $object ) {
if ( is_array( $object ) ) {
return $object;
} else if ( filter_var( $object, FILTER_VALIDATE_URL ) ) {
$response = wp_remote_get( $object );
if ( is_wp_error( $response ) ) {
return $response;
}
$body = wp_remote_retrieve_body( $response );
if ( empty( $body ) ) {
return new \WP_Error(
'not_found',
__( 'The object did not dereference to a valid object', 'pterotype' ),
array( 'status' => 404 )
);
}
$body_array = json_decode( $body, true );
return $body_array;
} else {
return new \WP_Error(
'invalid_object',
__( 'Not a valid ActivityPub object or reference', 'pterotype' ),
array( 'status' => 400 )
);
}
}
function is_same_object( $object1, $object2 ) {
return get_id( $object1 ) === get_id( $object2 );
}
function get_id( $object ) {
if ( is_array( $object ) ) {
return array_key_exists( 'id', $object ) ?
$object['id'] :
null;
} else {
return $object;
}
}
?>