[PLUGIN][ActivityPub] Improve flexibility of Type layer, accomodate more elaborate understanding of Group Announces after FEP-2100 development
This commit is contained in:
parent
7305a725cb
commit
be33c20614
|
@ -324,12 +324,16 @@ class ActivityPub extends Plugin
|
|||
array &$retry_args,
|
||||
): bool {
|
||||
try {
|
||||
$data = Model::toJson($activity);
|
||||
if ($sender->isGroup()) {
|
||||
// When the sender is a group, we have to wrap it in an Announce activity
|
||||
$data = Type::create('Announce', ['object' => $data])->toJson();
|
||||
$data = Model::toType($activity);
|
||||
if ($sender->isGroup() && ($activity->getVerb() !== 'subscribe' || !($activity->getVerb() === 'undo' && $data->get('object')->get('type') === 'Follow'))) {
|
||||
// When the sender is a group, we have to wrap it in a transient Announce activity
|
||||
$data = Type::create('Announce', [
|
||||
'@context' => 'https:\/\/www.w3.org\/ns\/activitystreams',
|
||||
'actor' => $sender->getUri(type: Router::ABSOLUTE_URL),
|
||||
'object' => $data,
|
||||
]);
|
||||
}
|
||||
$res = self::postman($sender, $data, $inbox);
|
||||
$res = self::postman($sender, $data->toJson(), $inbox);
|
||||
|
||||
// accumulate errors for later use, if needed
|
||||
$status_code = $res->getStatusCode();
|
||||
|
@ -377,6 +381,7 @@ class ActivityPub extends Plugin
|
|||
// the actor, that could for example mean that OStatus handled this actor while we were deactivated
|
||||
// On next interaction this should be resolved, for now continue
|
||||
if (\is_null($ap_target = DB::findOneBy(ActivitypubActor::class, ['actor_id' => $actor->getId()], return_null: true))) {
|
||||
Log::info('FreeNetwork wrongly told ActivityPub that it can handle actor id: ' . $actor->getId() . ' you might want to keep an eye on it.');
|
||||
continue;
|
||||
}
|
||||
$to_addr[$ap_target->getInboxSharedUri() ?? $ap_target->getInboxUri()][] = $actor;
|
||||
|
|
|
@ -98,7 +98,7 @@ class Inbox extends Controller
|
|||
try {
|
||||
$resource_parts = parse_url($type->get('actor'));
|
||||
if ($resource_parts['host'] !== Common::config('site', 'server')) {
|
||||
$actor = DB::wrapInTransaction(fn () => Explorer::getOneFromUri($type->get('actor')));
|
||||
$actor = DB::wrapInTransaction(fn () => Explorer::getOneFromUri($type->get('actor')));
|
||||
$ap_actor = DB::findOneBy(ActivitypubActor::class, ['actor_id' => $actor->getId()]);
|
||||
} else {
|
||||
throw new Exception('Only remote actors can use this endpoint.');
|
||||
|
|
|
@ -114,24 +114,36 @@ abstract class Model
|
|||
*/
|
||||
abstract public static function fromJson(string|Type\AbstractObject $json, array $options = []): Entity;
|
||||
|
||||
/**
|
||||
* Get a Type
|
||||
*
|
||||
* @throws \App\Util\Exception\ServerException
|
||||
* @throws ClientException
|
||||
*/
|
||||
public static function toType(mixed $object): Type\AbstractObject
|
||||
{
|
||||
switch ($object::class) {
|
||||
case \App\Entity\Activity::class:
|
||||
return Activity::toType($object);
|
||||
case \App\Entity\Note::class:
|
||||
return Note::toType($object);
|
||||
default:
|
||||
$type = self::jsonToType($object);
|
||||
Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]);
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a JSON
|
||||
*
|
||||
* @param ?int $options PHP JSON options
|
||||
* @param int $options PHP JSON options
|
||||
*
|
||||
* @throws \App\Util\Exception\ServerException
|
||||
* @throws ClientException
|
||||
*/
|
||||
public static function toJson(mixed $object, int $options = \JSON_UNESCAPED_SLASHES): string
|
||||
{
|
||||
switch ($object::class) {
|
||||
case \App\Entity\Activity::class:
|
||||
return Activity::toJson($object, $options);
|
||||
case \App\Entity\Note::class:
|
||||
return Note::toJson($object, $options);
|
||||
default:
|
||||
$type = self::jsonToType($object);
|
||||
Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]);
|
||||
return $type->toJson($options);
|
||||
}
|
||||
return self::toType($object)->toJson($options);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ use App\Core\Event;
|
|||
use App\Core\Log;
|
||||
use App\Core\Router\Router;
|
||||
use App\Entity\Activity as GSActivity;
|
||||
use App\Util\Common;
|
||||
use App\Util\Exception\ClientException;
|
||||
use App\Util\Exception\NoSuchActorException;
|
||||
use App\Util\Exception\NotFoundException;
|
||||
|
@ -46,7 +47,6 @@ use App\Util\Exception\NotImplementedException;
|
|||
use DateTimeInterface;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use const JSON_UNESCAPED_SLASHES;
|
||||
use Plugin\ActivityPub\ActivityPub;
|
||||
use Plugin\ActivityPub\Entity\ActivitypubActivity;
|
||||
use Plugin\ActivityPub\Util\Explorer;
|
||||
|
@ -90,9 +90,14 @@ class Activity extends Model
|
|||
// Find Actor and Object
|
||||
$actor = Explorer::getOneFromUri($type_activity->get('actor'));
|
||||
$type_object = $type_activity->get('object');
|
||||
if (\is_string($type_object)) { // Retrieve it
|
||||
$type_object = ActivityPub::getObjectByUri($type_object, try_online: true);
|
||||
} else { // Encapsulated, if we have it locally, prefer it
|
||||
if (\is_string($type_object)) {
|
||||
if (Common::isValidHttpUrl($type_object)) { // Retrieve it
|
||||
$type_object = ActivityPub::getObjectByUri($type_object, try_online: true);
|
||||
} else {
|
||||
$type_object = Type::fromJson($type_object);
|
||||
}
|
||||
}
|
||||
if ($type_object instanceof AbstractObject) { // Encapsulated, if we have it locally, prefer it
|
||||
// TODO: Test authority of activity over object
|
||||
try {
|
||||
$type_object = ActivityPub::getObjectByUri($type_object->get('id'), try_online: false);
|
||||
|
@ -153,7 +158,7 @@ class Activity extends Model
|
|||
*
|
||||
* @throws ClientException
|
||||
*/
|
||||
public static function toJson(mixed $object, int $options = JSON_UNESCAPED_SLASHES): string
|
||||
public static function toType(mixed $object): AbstractObject
|
||||
{
|
||||
if ($object::class !== GSActivity::class) {
|
||||
throw new InvalidArgumentException('First argument type must be an Activity.');
|
||||
|
@ -186,7 +191,8 @@ class Activity extends Model
|
|||
// Get object or Tombstone
|
||||
try {
|
||||
$child = $object->getObject(); // Throws NotFoundException
|
||||
$attr['object'] = ($attr['type'] === 'Create') ? self::jsonToType(Model::toJson($child)) : ActivityPub::getUriByObject($child);
|
||||
$prefer_embed = ['Create', 'Undo'];
|
||||
$attr['object'] = \in_array($attr['type'], $prefer_embed) ? self::jsonToType(Model::toJson($child)) : ActivityPub::getUriByObject($child);
|
||||
} catch (NotFoundException) {
|
||||
// It seems this object was deleted, refer to it as a Tombstone
|
||||
$uri = match ($object->getObjectType()) {
|
||||
|
@ -203,6 +209,6 @@ class Activity extends Model
|
|||
}
|
||||
$type = self::jsonToType($attr);
|
||||
Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]);
|
||||
return $type->toJson($options);
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,8 @@ declare(strict_types = 1);
|
|||
namespace Plugin\ActivityPub\Util\Model;
|
||||
|
||||
use ActivityPhp\Type\AbstractObject;
|
||||
use Exception;
|
||||
use InvalidArgumentException;
|
||||
use Plugin\ActivityPub\Entity\ActivitypubActivity;
|
||||
|
||||
/**
|
||||
|
@ -45,27 +47,15 @@ class ActivityAnnounce extends Activity
|
|||
{
|
||||
protected static function handle_core_activity(\App\Entity\Actor $actor, AbstractObject $type_activity, mixed $type_object, ?ActivitypubActivity &$ap_act): ActivitypubActivity
|
||||
{
|
||||
// The only core Announce we recognise is for (transitive) activities coming from Group actors
|
||||
// The only core Announce we recognise is for (transient) activities coming from Group actors
|
||||
if ($actor->isGroup()) {
|
||||
if ($type_object instanceof AbstractObject) {
|
||||
$actual_to = array_flip(\is_string($type_object->get('to')) ? [$type_object->get('to')] : $type_object->get('to'));
|
||||
$actual_cc = array_flip(\is_string($type_object->get('cc')) ? [$type_object->get('cc')] : $type_object->get('cc'));
|
||||
$actual_cc[$type_activity->get('actor')] = true; // Add group to targets
|
||||
foreach (\is_string($type_activity->get('to')) ? [$type_activity->get('to')] : $type_activity->get('to') as $to) {
|
||||
if ($to !== 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
$actual_to[$to] = true;
|
||||
}
|
||||
}
|
||||
foreach (\is_string($type_activity->get('cc')) ? [$type_activity->get('cc')] : $type_activity->get('cc') as $cc) {
|
||||
if ($cc !== 'https://www.w3.org/ns/activitystreams#Public') {
|
||||
$actual_cc[$cc] = true;
|
||||
}
|
||||
}
|
||||
$type_object->set('to', array_keys($actual_to));
|
||||
$type_object->set('cc', array_keys($actual_cc));
|
||||
$ap_act = self::fromJson($type_object);
|
||||
return $ap_act = Activity::fromJson($type_object);
|
||||
} else {
|
||||
throw new Exception('Already handled.');
|
||||
}
|
||||
} else {
|
||||
throw new InvalidArgumentException('Unsupported Announce Activity.');
|
||||
}
|
||||
return $ap_act ?? ($ap_act = $type_object);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ use App\Core\DB\DB;
|
|||
use App\Entity\Activity as GSActivity;
|
||||
use App\Util\Exception\ClientException;
|
||||
use Component\Subscription\Subscription;
|
||||
use Component\Subscription\Subscription as SubscriptionComponent;
|
||||
use DateTime;
|
||||
use InvalidArgumentException;
|
||||
use Plugin\ActivityPub\Entity\ActivitypubActivity;
|
||||
|
@ -63,6 +64,8 @@ class ActivityFollow extends Activity
|
|||
if (\is_null($act)) {
|
||||
throw new ClientException('You are already subscribed to this actor.');
|
||||
}
|
||||
SubscriptionComponent::refreshSubscriptionCount($actor, $subscribed);
|
||||
|
||||
// Store ActivityPub Activity
|
||||
$ap_act = ActivitypubActivity::create([
|
||||
'activity_id' => $act->getId(),
|
||||
|
|
|
@ -166,6 +166,7 @@ class Note extends Model
|
|||
'reply_to' => $reply_to = $handleInReplyTo($type_note),
|
||||
'modified' => new DateTime(),
|
||||
'type' => match ($type_note->get('type')) {
|
||||
'Article' => 'article',
|
||||
'Page' => 'page',
|
||||
default => 'note'
|
||||
},
|
||||
|
@ -361,7 +362,7 @@ class Note extends Model
|
|||
* @throws InvalidArgumentException
|
||||
* @throws ServerException
|
||||
*/
|
||||
public static function toJson(mixed $object, int $options = \JSON_UNESCAPED_SLASHES): string
|
||||
public static function toType(mixed $object): AbstractObject
|
||||
{
|
||||
if ($object::class !== GSNote::class) {
|
||||
throw new InvalidArgumentException('First argument type must be a Note.');
|
||||
|
@ -469,6 +470,6 @@ class Note extends Model
|
|||
|
||||
$type = self::jsonToType($attr);
|
||||
Event::handle('ActivityPubAddActivityStreamsTwoData', [$type->get('type'), &$type]);
|
||||
return $type->toJson($options);
|
||||
return $type;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -263,9 +263,9 @@ abstract class Common
|
|||
public static function getPreferredPhpUploadLimit(): int
|
||||
{
|
||||
return min(
|
||||
self::sizeStrToInt(ini_get('post_max_size')),
|
||||
self::sizeStrToInt(ini_get('upload_max_filesize')),
|
||||
self::sizeStrToInt(ini_get('memory_limit')),
|
||||
self::sizeStrToInt(\ini_get('post_max_size')),
|
||||
self::sizeStrToInt(\ini_get('upload_max_filesize')),
|
||||
self::sizeStrToInt(\ini_get('memory_limit')),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -295,10 +295,6 @@ abstract class Common
|
|||
*/
|
||||
public static function isValidHttpUrl(string $url, bool $ensure_secure = false)
|
||||
{
|
||||
if (empty($url)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// (if false, we use '?' in 'https?' to say the 's' is optional)
|
||||
$regex = $ensure_secure ? '/^https$/' : '/^https?$/';
|
||||
return filter_var($url, \FILTER_VALIDATE_URL) !== false
|
||||
|
|
Loading…
Reference in New Issue
Block a user