2019-09-10 04:03:33 +00:00
< ? php
namespace Zotlabs\Module ;
2019-12-03 02:42:59 +00:00
// ActivityPub delivery endpoint
2019-10-01 22:52:42 +00:00
use App ;
2019-09-10 04:03:33 +00:00
use Zotlabs\Web\HTTPSig ;
use Zotlabs\Lib\ActivityStreams ;
use Zotlabs\Lib\Activity ;
use Zotlabs\Web\Controller ;
use Zotlabs\Lib\Config ;
2019-12-03 02:42:59 +00:00
use Zotlabs\Lib\PConfig ;
2019-09-10 04:03:33 +00:00
class Inbox extends Controller {
function post () {
// This SHOULD be handled by the webserver, but in the RFC it is only indicated as
// a SHOULD and not a MUST, so some webservers fail to reject appropriately.
if (( array_key_exists ( 'HTTP_ACCEPT' , $_SERVER )) && ( $_SERVER [ 'HTTP_ACCEPT' ])
&& ( strpos ( $_SERVER [ 'HTTP_ACCEPT' ], '*' ) === false ) && ( ! ActivityStreams :: is_as_request ())) {
2019-12-03 02:42:59 +00:00
logger ( 'unhandled accept header: ' . $_SERVER [ 'HTTP_ACCEPT' ], LOGGER_DEBUG );
2019-09-10 04:03:33 +00:00
http_status_exit ( 406 , 'not acceptable' );
}
$sys_disabled = (( Config :: Get ( 'system' , 'disable_discover_tab' ) || Config :: Get ( 'system' , 'disable_activitypub_discover_tab' )) ? true : false );
2019-10-01 22:52:42 +00:00
logger ( 'inbox_args: ' . print_r ( App :: $argv , true ));
2019-09-10 04:03:33 +00:00
$is_public = false ;
if ( argc () == 1 || argv ( 1 ) === '[public]' ) {
$is_public = true ;
}
else {
2019-11-27 05:29:48 +00:00
$c = channelx_by_nick ( argv ( 1 ));
if ( ! $c ) {
2019-11-27 05:33:27 +00:00
http_status_exit ( 410 , 'Gone' );
2019-11-27 05:29:48 +00:00
}
$channels = [ $c ];
2019-09-10 04:03:33 +00:00
}
$data = file_get_contents ( 'php://input' );
if ( ! $data ) {
return ;
}
logger ( 'inbox_activity: ' . jindent ( $data ), LOGGER_DATA );
$hsig = HTTPSig :: verify ( $data );
$AS = new ActivityStreams ( $data );
2019-09-30 02:20:09 +00:00
if ( $AS -> is_valid () && $AS -> type === 'Announce' && is_array ( $AS -> obj )
&& array_key_exists ( 'object' , $AS -> obj ) && array_key_exists ( 'actor' , $AS -> obj )) {
// This is a relayed/forwarded Activity (as opposed to a shared/boosted object)
// Reparse the encapsulated Activity and use that instead
logger ( 'relayed activity' , LOGGER_DEBUG );
$AS = new ActivityStreams ( $AS -> obj );
}
2019-09-10 04:03:33 +00:00
//logger('debug: ' . $AS->debug());
if ( ! $AS -> is_valid ()) {
if ( $AS -> deleted ) {
// process mastodon user deletion activities, but only if we can validate the signature
if ( $hsig [ 'header_valid' ] && $hsig [ 'content_valid' ] && $hsig [ 'portable_id' ]) {
logger ( 'removing deleted actor' );
remove_all_xchan_resources ( $hsig [ 'portable_id' ]);
}
else {
logger ( 'ignoring deleted actor' , LOGGER_DEBUG , LOG_INFO );
}
}
return ;
}
2019-09-30 02:20:09 +00:00
2019-09-10 04:03:33 +00:00
// $observer_hash in this case is the sender
if ( $hsig [ 'header_valid' ] && $hsig [ 'content_valid' ] && $hsig [ 'portable_id' ]) {
$observer_hash = $hsig [ 'portable_id' ];
}
else {
$observer_hash = $AS -> actor [ 'id' ];
}
if ( ! $observer_hash ) {
return ;
}
$m = parse_url ( $observer_hash );
if ( $m && $m [ 'scheme' ] && $m [ 'host' ]) {
if ( ! check_siteallowed ( $m [ 'scheme' ] . '://' . $m [ 'host' ])) {
2019-11-27 05:29:48 +00:00
http_status_exit ( 403 , 'Permission denied' );
2019-09-10 04:03:33 +00:00
}
}
if ( is_array ( $AS -> actor ) && array_key_exists ( 'id' , $AS -> actor )) {
Activity :: actor_store ( $AS -> actor [ 'id' ], $AS -> actor );
}
if ( is_array ( $AS -> obj ) && ActivityStreams :: is_an_actor ( $AS -> obj [ 'type' ])) {
Activity :: actor_store ( $AS -> obj [ 'id' ], $AS -> obj );
}
if ( is_array ( $AS -> obj ) && is_array ( $AS -> obj [ 'actor' ]) && array_key_exists ( 'id' , $AS -> obj [ 'actor' ]) && $AS -> obj [ 'actor' ][ 'id' ] !== $AS -> actor [ 'id' ]) {
Activity :: actor_store ( $AS -> obj [ 'actor' ][ 'id' ], $AS -> obj [ 'actor' ]);
}
$test = q ( " update hubloc set hubloc_connected = '%s' where hubloc_hash = '%s' and hubloc_network = 'activitypub' " ,
dbesc ( datetime_convert ()),
dbesc ( $observer_hash )
);
if ( $is_public ) {
2020-03-02 23:42:50 +00:00
if ( $AS -> type === 'Follow' && is_array ( $AS -> obj ) && ActivityStreams :: is_an_actor ( $AS -> obj [ 'type' ])) {
2019-09-10 04:03:33 +00:00
$channels = q ( " SELECT * from channel where channel_address = '%s' and channel_removed = 0 " ,
dbesc ( basename ( $AS -> obj [ 'id' ]))
);
}
else {
// deliver to anybody following $AS->actor
$channels = q ( " SELECT * from channel where channel_id in ( SELECT abook_channel from abook left join xchan on abook_xchan = xchan_hash WHERE xchan_network = 'activitypub' and xchan_hash = '%s' ) and channel_removed = 0 " ,
dbesc ( $observer_hash )
);
if ( ! $channels ) {
$channels = [];
}
$parent = $AS -> parent_id ;
if ( $parent ) {
// this is a comment - deliver to everybody who owns the parent
$owners = q ( " SELECT * from channel where channel_id in ( SELECT uid from item where mid = '%s' ) " ,
dbesc ( $parent )
);
if ( $owners ) {
$channels = array_merge ( $channels , $owners );
}
}
}
if ( $channels === false ) {
$channels = [];
}
if ( in_array ( ACTIVITY_PUBLIC_INBOX , $AS -> recips )) {
// look for channels with send_stream = PERMS_PUBLIC
$r = q ( " select * from channel where channel_id in (select uid from pconfig where cat = 'perm_limits' and k = 'send_stream' and v = '1' ) and channel_removed = 0 " );
if ( $r ) {
$channels = array_merge ( $channels , $r );
}
2020-04-08 02:01:14 +00:00
// look for channels that are following hashtags. These will be checked in tgroup_check()
$r = q ( " select * from channel where channel_id in (select uid from pconfig where cat = 'system' and k = 'followed_tags' and v != '' ) and channel_removed = 0 " );
if ( $r ) {
$channels = array_merge ( $channels , $r );
}
2019-09-10 04:03:33 +00:00
if ( ! $sys_disabled ) {
$channels [] = get_sys_channel ();
}
}
}
if ( ! $channels ) {
logger ( 'no deliveries on this site' );
return ;
}
$saved_recips = [];
foreach ( [ 'to' , 'cc' , 'audience' ] as $x ) {
if ( array_key_exists ( $x , $AS -> data )) {
$saved_recips [ $x ] = $AS -> data [ $x ];
}
}
$AS -> set_recips ( $saved_recips );
foreach ( $channels as $channel ) {
2019-12-03 02:42:59 +00:00
if ( ! PConfig :: Get ( $channel [ 'channel_id' ], 'system' , 'activitypub' , true )) {
2019-09-24 23:11:28 +00:00
continue ;
}
2019-10-01 22:52:42 +00:00
logger ( 'inbox_channel: ' . $channel [ 'channel_address' ], LOGGER_DEBUG );
2019-09-24 23:11:28 +00:00
2019-09-10 04:03:33 +00:00
switch ( $AS -> type ) {
case 'Follow' :
2020-02-11 00:35:30 +00:00
if ( is_array ( $AS -> obj ) && array_key_exists ( 'type' , $AS -> obj ) && ActivityStreams :: is_an_actor ( $AS -> obj [ 'type' ])) {
2019-09-24 23:11:28 +00:00
// do follow activity
Activity :: follow ( $channel , $AS );
2019-09-10 04:03:33 +00:00
}
break ;
2019-12-03 02:42:59 +00:00
case 'Join' :
2020-02-11 00:35:30 +00:00
if ( is_array ( $AS -> obj ) && array_key_exists ( 'type' , $AS -> obj ) && $AS -> obj [ 'type' ] === 'Group' ) {
2019-12-03 02:42:59 +00:00
// do follow activity
Activity :: follow ( $channel , $AS );
}
break ;
2019-09-10 04:03:33 +00:00
case 'Accept' :
2020-02-11 00:35:30 +00:00
if ( is_array ( $AS -> obj ) && array_key_exists ( 'type' , $AS -> obj ) && $AS -> obj [ 'type' ] === 'Follow' ) {
2019-09-10 04:03:33 +00:00
// do follow activity
Activity :: follow ( $channel , $AS );
}
break ;
case 'Reject' :
default :
break ;
}
// These activities require permissions
$item = null ;
switch ( $AS -> type ) {
case 'Update' :
if ( is_array ( $AS -> obj ) && array_key_exists ( 'type' , $AS -> obj ) && ActivityStreams :: is_an_actor ( $AS -> obj [ 'type' ])) {
// pretend this is an old cache entry to force an update of all the actor details
$AS -> obj [ 'cached' ] = true ;
$AS -> obj [ 'updated' ] = datetime_convert ( 'UTC' , 'UTC' , '1980-01-01' , ATOM_TIME );
Activity :: actor_store ( $AS -> obj [ 'id' ], $AS -> obj );
break ;
}
case 'Create' :
case 'Like' :
case 'Dislike' :
case 'Announce' :
2019-09-18 04:25:26 +00:00
case 'Accept' :
case 'Reject' :
case 'TentativeAccept' :
case 'TentativeReject' :
2019-12-03 02:42:59 +00:00
case 'Add' :
case 'Arrive' :
case 'Block' :
case 'Flag' :
case 'Ignore' :
case 'Invite' :
case 'Listen' :
case 'Move' :
case 'Offer' :
case 'Question' :
case 'Read' :
case 'Travel' :
case 'View' :
2019-09-10 04:03:33 +00:00
case 'emojiReaction' :
2019-12-10 23:35:36 +00:00
case 'EmojiReaction' :
2020-02-04 23:44:26 +00:00
case 'EmojiReact' :
2019-09-10 04:03:33 +00:00
// These require a resolvable object structure
if ( is_array ( $AS -> obj )) {
2020-03-26 22:41:09 +00:00
// The boolean flag enables html cache of the item
$item = Activity :: decode_note ( $AS , true );
2019-09-10 04:03:33 +00:00
}
else {
logger ( 'unresolved object: ' . print_r ( $AS -> obj , true ));
}
break ;
case 'Undo' :
2020-02-11 00:35:30 +00:00
if ( $AS -> obj && array_key_exists ( 'type' , $AS -> obj ) && $AS -> obj [ 'type' ] === 'Follow' ) {
2019-09-10 04:03:33 +00:00
// do unfollow activity
Activity :: unfollow ( $channel , $AS );
break ;
}
2019-12-03 02:42:59 +00:00
case 'Leave' :
2020-02-11 00:35:30 +00:00
if ( $AS -> obj && array_key_exists ( 'type' , $AS -> obj ) && $AS -> obj [ 'type' ] === 'Group' ) {
2019-12-03 02:42:59 +00:00
// do unfollow activity
Activity :: unfollow ( $channel , $AS );
break ;
}
2019-11-08 01:25:19 +00:00
case 'Tombstone' :
2019-09-10 04:03:33 +00:00
case 'Delete' :
Activity :: drop ( $channel , $observer_hash , $AS );
break ;
2019-10-14 22:55:58 +00:00
case 'Move' :
2020-02-11 00:35:30 +00:00
if ( $observer_hash && $observer_hash === $AS -> actor
&& is_array ( $AS -> obj ) && array_key_exists ( 'type' , $AS -> obj ) && ActivityStream :: is_an_actor ( $AS -> obj [ 'type' ])
&& is_array ( $AS -> tgt ) && array_key_exists ( 'type' , $AS -> tgt ) && ActivityStream :: is_an_actor ( $AS -> tgt [ 'type' ])) {
2019-10-14 22:55:58 +00:00
ActivityPub :: move ( $AS -> obj , $AS -> tgt );
}
break ;
2019-09-10 04:03:33 +00:00
case 'Add' :
case 'Remove' :
default :
break ;
}
if ( $item ) {
logger ( 'parsed_item: ' . print_r ( $item , true ), LOGGER_DATA );
Activity :: store ( $channel , $observer_hash , $AS , $item );
}
}
http_status_exit ( 200 , 'OK' );
}
function get () {
}
}