[ActivityPub] Fix issues concerning Activity URIs

And some other minor bugs.
This commit is contained in:
Diogo Cordeiro 2020-08-28 01:12:40 +01:00
parent 11ebb98919
commit c75bf1a19d
13 changed files with 169 additions and 67 deletions

View File

@ -180,6 +180,14 @@ class ActivityPubPlugin extends Plugin
$acceptHeaders
);
// v3
$m->connect(
'activity/:id',
['action' => 'apNotice'],
['id' => '[0-9]+'],
);
// v2
$m->connect(
'notice/:id',
['action' => 'apNotice'],

View File

@ -53,11 +53,12 @@ class apNoticeAction extends ManagedAction
try {
$notice = Notice::getByID($this->trimmed('id'));
} catch (Exception $e) {
ActivityPubReturn::error('Invalid Notice URI.', 404);
ActivityPubReturn::error('Invalid Activity URI.', 404);
}
if (!$notice->isLocal()) {
ActivityPubReturn::error("This is not a local notice.", 403);
// We have no authority on the requested activity.
ActivityPubReturn::error("This is not a local activity.", 403);
}
$res = Activitypub_notice::notice_to_array($notice);

View File

@ -54,7 +54,7 @@ class ActivityPubQueueHandler extends QueueHandler
public function handle($notice): bool
{
if (!($notice instanceof Notice)) {
common_log(LOG_ERR, "Got a bogus notice, not distributing");
common_log(LOG_ERR, 'Got a bogus notice, not distributing');
return true;
}
@ -74,7 +74,7 @@ class ActivityPubQueueHandler extends QueueHandler
);
// Handling a Create?
if (ActivityUtils::compareVerbs($notice->verb, [ActivityVerb::POST])) {
if (ActivityUtils::compareVerbs($notice->verb, [ActivityVerb::POST, ActivityVerb::SHARE])) {
return $this->handle_create($profile, $notice, $other);
}
@ -143,7 +143,7 @@ class ActivityPubQueueHandler extends QueueHandler
// That was it
$postman = new Activitypub_postman($profile, $other);
$postman->announce($repeated_notice);
$postman->announce($notice, $repeated_notice);
}
// either made the announce or found nothing to repeat
@ -160,7 +160,7 @@ class ActivityPubQueueHandler extends QueueHandler
* Notify remote users when their notices get favourited.
*
* @param Profile $profile of local user doing the faving
* @param Notice $notice Notice being favored
* @param Notice $notice_liked Notice being favored
* @return bool return value
* @throws HTTP_Request2_Exception
* @throws InvalidUrlException
@ -168,10 +168,10 @@ class ActivityPubQueueHandler extends QueueHandler
*/
public function onEndFavorNotice(Profile $profile, Notice $notice, $other)
{
$notice = $notice->getParent();
if ($notice->reply_to) {
$notice_liked = $notice->getParent();
if ($notice_liked->reply_to) {
try {
$parent_notice = $notice->getParent();
$parent_notice = $notice_liked->getParent();
try {
$other[] = Activitypub_profile::from_profile($parent_notice->getProfile());

View File

@ -331,7 +331,7 @@ class Activitypub_inbox_handler
private function handle_like()
{
$notice = ActivityPubPlugin::grab_notice_from_url($this->object);
Fave::addNew($this->actor, $notice);
Activitypub_like::addNew($this->activity['id'], $this->actor, $notice);
}
/**
@ -390,7 +390,7 @@ class Activitypub_inbox_handler
*/
private function handle_undo_like()
{
$notice = ActivityPubPlugin::grab_notice_from_url($this->object['object']);
$notice = ActivityPubPlugin::grab_notice_from_url($this->activity['id']);
Fave::removeEntry($this->actor, $notice);
}
@ -402,7 +402,7 @@ class Activitypub_inbox_handler
*/
private function handle_announce()
{
$object_notice = ActivityPubPlugin::grab_notice_from_url($this->object);
$object_notice->repeat($this->actor, 'ActivityPub');
$notice = ActivityPubPlugin::grab_notice_from_url($this->object);
Activitypub_announce::repeat($this->activity['id'], $this->actor, $notice);
}
}

View File

@ -41,13 +41,16 @@ class Activitypub_announce
*
* @param Profile $actor
* @param Notice $notice
* @param Notice $repeat_of
* @return array pretty array to be used in a response
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public static function announce_to_array(Profile $actor, Notice $notice): array
{
public static function announce_to_array(
Profile $actor,
Notice $notice,
Notice $repeat_of
): array {
$actor_uri = $actor->getUri();
$notice_url = Activitypub_notice::getUrl($notice);
$to = [common_local_url('apActorFollowers', ['id' => $actor->getID()])];
foreach ($notice->getAttentionProfiles() as $to_profile) {
@ -58,13 +61,48 @@ class Activitypub_announce
$res = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => common_root_url().'share_from_'.urlencode($actor_uri).'_to_'.urlencode($notice_url),
"type" => "Announce",
"actor" => $actor_uri,
"object" => $notice_url,
"to" => $to,
"cc" => $cc
'id' => Activitypub_notice::getUri($notice),
'type' => 'Announce',
'actor' => $actor_uri,
'object' => Activitypub_notice::getUri($repeat_of),
'to' => $to,
'cc' => $cc,
];
return $res;
}
/**
* Convenience function for posting a repeat of an existing message.
*
* @param string $uri
* @param Profile $actor Profile which is doing the repeat
* @param Notice $target
* @return Notice
*/
public static function repeat(string $uri, Profile $actor, Notice $target): Notice
{
// TRANS: Message used to repeat a notice. RT is the abbreviation of 'retweet'.
// TRANS: %1$s is the repeated user's name, %2$s is the repeated notice.
$content = sprintf(
_('RT @%1$s %2$s'),
$actor->getNickname(),
$target->getContent()
);
$options = [
'source' => 'ActivityPub',
'uri' => $uri,
'is_local' => ($actor->isLocal() ? Notice::LOCAL_PUBLIC : Notice::REMOTE),
'repeat_of' => $target->getParent()->getID(),
'scope' => $target->getScope(),
];
// Scope is same as this one's
return Notice::saveNew(
$actor->getID(),
$content,
'ActivityPub',
$options
);
}
}

View File

@ -49,7 +49,7 @@ class Activitypub_create
{
$res = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $object['id'] . '/create',
'id' => $object['id'] . '#create',
'type' => 'Create',
'directMessage' => $directMessage,
'to' => $object['to'],

View File

@ -48,7 +48,7 @@ class Activitypub_delete
{
$res = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $object.'/delete',
'id' => $object . '#delete',
'type' => 'Delete',
'to' => ['https://www.w3.org/ns/activitystreams#Public'],
'actor' => $actor,

View File

@ -39,20 +39,73 @@ class Activitypub_like
/**
* Generates an ActivityPub representation of a Like
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param string $actor Actor URI
* @param string $object Notice URI
* @param Notice $notice Notice URI
* @return array pretty array to be used in a response
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public static function like_to_array($actor, $object)
public static function like_to_array(string $actor, Notice $notice): array
{
$res = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => common_root_url().'like_from_'.urlencode($actor).'_to_'.urlencode($object),
"type" => "Like",
"actor" => $actor,
"object" => $object
'id' => Activitypub_notice::getUri($notice),
'type' => 'Like',
'actor' => $actor,
'object' => Activitypub_notice::getUri($notice->getParent()),
];
return $res;
}
/**
* Save a favorite record.
*
* @param string $uri
* @param Profile $actor the local or remote Profile who favorites
* @param Notice $target the notice that is favorited
* @return Notice record on success
* @throws AlreadyFulfilledException
* @throws ClientException
* @throws NoticeSaveException
* @throws ServerException
*/
public static function addNew(string $uri, Profile $actor, Notice $target): Notice
{
if (Fave::existsForProfile($target, $actor)) {
// TRANS: Client error displayed when trying to mark a notice as favorite that already is a favorite.
throw new AlreadyFulfilledException(_m('You have already favorited this!'));
}
$act = new Activity();
$act->type = ActivityObject::ACTIVITY;
$act->verb = ActivityVerb::FAVORITE;
$act->time = time();
$act->id = $uri;
$act->title = _m('Favor');
// TRANS: Message that is the "content" of a favorite (%1$s is the actor's nickname, %2$ is the favorited
// notice's nickname and %3$s is the content of the favorited notice.)
$act->content = sprintf(
_m('%1$s favorited something by %2$s: %3$s'),
$actor->getNickname(),
$target->getProfile()->getNickname(),
$target->getRendered()
);
$act->actor = $actor->asActivityObject();
$act->target = $target->asActivityObject();
$act->objects = [clone($act->target)];
$url = common_local_url('AtomPubShowFavorite', ['profile'=>$actor->id, 'notice'=>$target->id]);
$act->selfLink = $url;
$act->editLink = $url;
$options = [
'source' => 'ActivityPub',
'uri' => $act->id,
'url' => $url,
'is_local' => ($actor->isLocal() ? Notice::LOCAL_PUBLIC : Notice::REMOTE),
'scope' => $target->getScope(),
];
// saveActivity will in turn also call Fave::saveActivityObject
return Notice::saveActivity($act, $actor, $options);
}
}

View File

@ -47,7 +47,7 @@ class Activitypub_notice
* @throws Exception
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public static function notice_to_array($notice)
public static function notice_to_array(Notice $notice): array
{
$profile = $notice->getProfile();
$attachments = [];
@ -81,10 +81,10 @@ class Activitypub_notice
$item = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => self::getUrl($notice),
'id' => self::getUri($notice),
'type' => 'Note',
'published' => str_replace(' ', 'T', $notice->getCreated()) . 'Z',
'url' => self::getUrl($notice),
'url' => $notice->getUrl(),
'attributedTo' => $profile->getUri(),
'to' => $to,
'cc' => $cc,
@ -97,7 +97,7 @@ class Activitypub_notice
// Is this a reply?
if (!empty($notice->reply_to)) {
$item['inReplyTo'] = self::getUrl(Notice::getById($notice->reply_to));
$item['inReplyTo'] = self::getUri(Notice::getById($notice->reply_to));
}
// Do we have a location for this notice?
@ -253,7 +253,7 @@ class Activitypub_notice
* @throws Exception if invalid ActivityPub object
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public static function validate_note($object)
public static function validate_note(array $object): bool
{
if (!isset($object['id'])) {
common_debug('ActivityPub Notice Validator: Rejected because Object ID was not specified.');
@ -290,7 +290,7 @@ class Activitypub_notice
* @throws Exception
* @author Bruno Casteleiro <brunoccast@fc.up.pt>
*/
public static function getUrl(Notice $notice): string
public static function getUri(Notice $notice): string
{
if ($notice->isLocal()) {
return common_local_url('apNotice', ['id' => $notice->getID()]);

View File

@ -43,7 +43,7 @@ class Activitypub_reject
* @param array $object
* @return array pretty array to be used in a response
*/
public static function reject_to_array($object)
public static function reject_to_array(array $object): array
{
$res = [
'@context' => 'https://www.w3.org/ns/activitystreams',

View File

@ -40,15 +40,15 @@ class Activitypub_tag
* Generates a pretty tag from a Tag object
*
* @author Diogo Cordeiro <diogo@fc.up.pt>
* @param array Tag $tag
* @param string $tag
* @return array pretty array to be used in a response
*/
public static function tag_to_array($tag)
public static function tag_to_array(string $tag): array
{
$res = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'name' => $tag,
'url' => common_local_url('tag', ['tag' => $tag])
'url' => common_local_url('tag', ['tag' => $tag]),
];
return $res;
}

View File

@ -43,11 +43,11 @@ class Activitypub_undo
* @param array $object
* @return array pretty array to be used in a response
*/
public static function undo_to_array($object)
public static function undo_to_array(array $object): array
{
$res = [
'@context' => 'https://www.w3.org/ns/activitystreams',
'id' => $object['id'].'/undo',
'id' => $object['id'] . '#undo',
'type' => 'Undo',
'actor' => $object['actor'],
'object' => $object
@ -63,7 +63,7 @@ class Activitypub_undo
* @throws Exception
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public static function validate_object($object)
public static function validate_object(array $object): bool
{
if (!is_array($object)) {
throw new Exception('Invalid Object Format for Undo Activity.');

View File

@ -199,18 +199,18 @@ class Activitypub_postman
* @throws Exception
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function like($notice)
public function like(Notice $notice): void
{
$data = Activitypub_like::like_to_array(
$this->actor_uri,
Activitypub_notice::getUrl($notice)
$notice
);
$data = json_encode($data, JSON_UNESCAPED_SLASHES);
foreach ($this->to_inbox() as $inbox) {
$res = $this->send($data, $inbox);
// accummulate errors for later use, if needed
// accumulate errors for later use, if needed
if (!($res->getStatus() == 200 || $res->getStatus() == 202 || $res->getStatus() == 409)) {
$res_body = json_decode($res->getBody(), true);
$errors[] = isset($res_body['error']) ?
@ -237,7 +237,7 @@ class Activitypub_postman
$data = Activitypub_undo::undo_to_array(
Activitypub_like::like_to_array(
$this->actor_uri,
Activitypub_notice::getUrl($notice)
$notice
)
);
$data = json_encode($data, JSON_UNESCAPED_SLASHES);
@ -327,14 +327,16 @@ class Activitypub_postman
* Send a Announce notification to remote instances
*
* @param Notice $notice
* @param Notice $repeat_of
* @throws HTTP_Request2_Exception
* @throws Exception
* @author Diogo Cordeiro <diogo@fc.up.pt>
*/
public function announce($notice)
public function announce(Notice $notice, Notice $repeat_of): void
{
$data = json_encode(Activitypub_announce::announce_to_array($this->actor, $notice),
JSON_UNESCAPED_SLASHES);
$data = json_encode(
Activitypub_announce::announce_to_array($this->actor, $notice, $repeat_of),
JSON_UNESCAPED_SLASHES
);
foreach ($this->to_inbox() as $inbox) {
$res = $this->send($data, $inbox);
@ -365,7 +367,7 @@ class Activitypub_postman
{
$data = Activitypub_delete::delete_to_array(
$notice->getProfile()->getUri(),
Activitypub_notice::getUrl($notice)
Activitypub_notice::getUri($notice)
);
$errors = [];
$data = json_encode($data, JSON_UNESCAPED_SLASHES);