From 8e05c282b197904cc5949014f82db2b912f286a9 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 29 Oct 2023 08:49:24 +0000 Subject: [PATCH] Post permissions are now copied from the thread parent --- mod/item.php | 4 ++ src/Content/Item.php | 28 ++++++-- src/Model/Circle.php | 2 +- src/Model/Item.php | 7 ++ src/Model/Tag.php | 2 - src/Module/Api/Mastodon/Statuses.php | 16 ++++- src/Module/PermissionTooltip.php | 22 ++++-- src/Protocol/ActivityPub/Transmitter.php | 90 ++++++++++++++++++++---- src/Util/HTTPSignature.php | 1 + src/Worker/Notifier.php | 2 +- 10 files changed, 141 insertions(+), 33 deletions(-) diff --git a/mod/item.php b/mod/item.php index 72c2ed8c5d..70c5c9a8b6 100644 --- a/mod/item.php +++ b/mod/item.php @@ -221,6 +221,10 @@ function item_insert(int $uid, array $request, bool $preview, string $return_pat DI::contentItem()->postProcessPost($post, $recipients); + if (($post['private'] == Item::PRIVATE) && ($post['thr-parent-id'] != $post['uri-id'])) { + DI::contentItem()->copyPermissions($post['thr-parent-id'], $post['uri-id']); + } + Logger::debug('post_complete'); item_post_return(DI::baseUrl(), $return_path); diff --git a/src/Content/Item.php b/src/Content/Item.php index 48dcb80d32..e2942e27e6 100644 --- a/src/Content/Item.php +++ b/src/Content/Item.php @@ -48,6 +48,7 @@ use Friendica\Model\User; use Friendica\Network\HTTPException; use Friendica\Object\EMail\ItemCCEMail; use Friendica\Protocol\Activity; +use Friendica\Protocol\ActivityPub; use Friendica\Util\ACLFormatter; use Friendica\Util\DateTimeFormat; use Friendica\Util\Emailer; @@ -991,12 +992,14 @@ class Item $post['deny_gid'] = $owner['deny_gid']; } - if ($post['allow_gid'] || $post['allow_cid'] || $post['deny_gid'] || $post['deny_cid']) { - $post['private'] = ItemModel::PRIVATE; - } elseif ($this->pConfig->get($post['uid'], 'system', 'unlisted')) { - $post['private'] = ItemModel::UNLISTED; - } else { - $post['private'] = ItemModel::PUBLIC; + if (!isset($post['private'])) { + if ($post['allow_gid'] || $post['allow_cid'] || $post['deny_gid'] || $post['deny_cid']) { + $post['private'] = ItemModel::PRIVATE; + } elseif ($this->pConfig->get($post['uid'], 'system', 'unlisted')) { + $post['private'] = ItemModel::UNLISTED; + } else { + $post['private'] = ItemModel::PUBLIC; + } } if (empty($post['contact-id'])) { @@ -1046,6 +1049,8 @@ class Item Tag::createImplicitMentions($post['uri-id'], $post['thr-parent-id']); } + ActivityPub\Transmitter::storeReceiversForItem($post); + Hook::callAll('post_local_end', $post); $author = DBA::selectFirst('contact', ['thumb'], ['uid' => $post['uid'], 'self' => true]); @@ -1066,4 +1071,15 @@ class Item )); } } + + public function copyPermissions(int $fromUriId, int $toUriId) + { + $existing = array_column(Tag::getByURIId($toUriId, [Tag::TO, Tag::CC, Tag::BCC]), 'url'); + foreach (Tag::getByURIId($fromUriId, [Tag::TO, Tag::CC, Tag::BCC]) as $receiver) { + if (in_array($receiver['url'], $existing)) { + continue; + } + Tag::store($toUriId, $receiver['type'], $receiver['name'], $receiver['url']); + } + } } diff --git a/src/Model/Circle.php b/src/Model/Circle.php index 92e3ea6560..bfa02efd82 100644 --- a/src/Model/Circle.php +++ b/src/Model/Circle.php @@ -428,7 +428,7 @@ class Circle 'uid' => $uid, 'rel' => [Contact::FOLLOWER, Contact::FRIEND], 'network' => $networks, - 'contact-type' => [Contact::TYPE_UNKNOWN, Contact::TYPE_PERSON], + 'contact-type' => [Contact::TYPE_UNKNOWN, Contact::TYPE_PERSON, Contact::TYPE_NEWS, Contact::TYPE_ORGANISATION], 'archive' => false, 'pending' => false, 'blocked' => false, diff --git a/src/Model/Item.php b/src/Model/Item.php index 0e8a04b3ef..d498b978f2 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -880,6 +880,10 @@ class Item if (is_int($notify) && in_array($notify, Worker::PRIORITIES)) { $priority = $notify; } + + // Mastodon style API visibility + $copy_permissions = ($item['visibility'] ?? 'private') == 'private'; + unset($item['visibility']); } else { $item['network'] = trim(($item['network'] ?? '') ?: Protocol::PHANTOM); } @@ -1359,6 +1363,9 @@ class Item if ($notify) { DI::contentItem()->postProcessPost($posted_item); + if ($copy_permissions && ($posted_item['thr-parent-id'] != $posted_item['uri-id']) && ($posted_item['private'] == self::PRIVATE)) { + DI::contentItem()->copyPermissions($posted_item['thr-parent-id'], $posted_item['uri-id']); + } } else { Hook::callAll('post_remote_end', $posted_item); } diff --git a/src/Model/Tag.php b/src/Model/Tag.php index 3758e065d3..1792e29b59 100644 --- a/src/Model/Tag.php +++ b/src/Model/Tag.php @@ -25,13 +25,11 @@ use Friendica\Content\Text\BBCode; use Friendica\Core\Cache\Enum\Duration; use Friendica\Core\Logger; use Friendica\Core\Protocol; -use Friendica\Core\System; use Friendica\Database\Database; use Friendica\Database\DBA; use Friendica\DI; use Friendica\Protocol\ActivityPub; use Friendica\Util\DateTimeFormat; -use Friendica\Util\Network; use Friendica\Util\Strings; /** diff --git a/src/Module/Api/Mastodon/Statuses.php b/src/Module/Api/Mastodon/Statuses.php index dfd81c9d45..f6cddb32d9 100644 --- a/src/Module/Api/Mastodon/Statuses.php +++ b/src/Module/Api/Mastodon/Statuses.php @@ -25,7 +25,6 @@ use Friendica\Content\PageInfo; use Friendica\Content\Text\BBCode; use Friendica\Content\Text\Markdown; use Friendica\Core\Protocol; -use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; @@ -192,6 +191,7 @@ class Statuses extends BaseApi $item['title'] = ''; $item['body'] = $this->formatStatus($request['status'], $uid); $item['app'] = $this->getApp(); + $item['visibility'] = $request['visibility']; switch ($request['visibility']) { case 'public': @@ -209,6 +209,18 @@ class Statuses extends BaseApi $item['private'] = Item::UNLISTED; break; case 'private': + if ($request['in_reply_to_id']) { + $parent_item = Post::selectFirst(Item::ITEM_FIELDLIST, ['uri-id' => $request['in_reply_to_id'], 'uid' => $uid, 'private' => Item::PRIVATE]); + if (!empty($parent_item)) { + $item['allow_cid'] = $parent_item['allow_cid']; + $item['allow_gid'] = $parent_item['allow_gid']; + $item['deny_cid'] = $parent_item['deny_cid']; + $item['deny_gid'] = $parent_item['deny_gid']; + $item['private'] = $parent_item['private']; + break; + } + } + if (!empty($owner['allow_cid'] . $owner['allow_gid'] . $owner['deny_cid'] . $owner['deny_gid'])) { $item['allow_cid'] = $owner['allow_cid']; $item['allow_gid'] = $owner['allow_gid']; @@ -287,7 +299,7 @@ class Statuses extends BaseApi } $item = DI::contentItem()->expandTags($item, $request['visibility'] == 'direct'); - + if (!empty($request['media_ids'])) { $item = $this->storeMediaIds($request['media_ids'], $item); } diff --git a/src/Module/PermissionTooltip.php b/src/Module/PermissionTooltip.php index 9db90538c5..91f5b7006c 100644 --- a/src/Module/PermissionTooltip.php +++ b/src/Module/PermissionTooltip.php @@ -113,12 +113,26 @@ class PermissionTooltip extends \Friendica\BaseModule exit; } + if (!empty($model['allow_cid']) || !empty($model['allow_gid']) || !empty($model['deny_cid']) || !empty($model['deny_gid'])) { + $receivers = $this->fetchReceiversFromACL($model); + } + + $this->httpExit(DI::l10n()->t('Visible to:') . '
' . $receivers); + } + + /** + * Fetch a list of receivers based on the ACL data + * + * @param array $model + * @return string + */ + private function fetchReceiversFromACL(array $model) + { $allowed_users = $model['allow_cid']; $allowed_circles = $model['allow_gid']; $deny_users = $model['deny_cid']; $deny_circles = $model['deny_gid']; - $o = DI::l10n()->t('Visible to:') . '
'; $l = []; if (count($allowed_circles)) { @@ -165,11 +179,7 @@ class PermissionTooltip extends \Friendica\BaseModule $l[] = '' . $contact['name'] . ''; } - if (!empty($l)) { - $this->httpExit($o . implode(', ', $l)); - } else { - $this->httpExit($o . $receivers);; - } + return implode(', ', $l); } /** diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 9e19724fa6..f1ced5b429 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -560,14 +560,13 @@ class Transmitter * * @param array $item Item array * @param boolean $blindcopy addressing via "bcc" or "cc"? - * @param boolean $expand_followers Expand the list of followers * @param integer $last_id Last item id for adding receivers * * @return array with permission data * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - private static function createPermissionBlockForItem(array $item, bool $blindcopy, bool $expand_followers, int $last_id = 0): array + private static function createPermissionBlockForItem(array $item, bool $blindcopy, int $last_id = 0): array { if ($last_id == 0) { $last_id = $item['id']; @@ -704,7 +703,7 @@ class Transmitter $data['to'][] = $actor_profile['followers']; } } else { - $receiver_list = Item::enumeratePermissions($item, true, $expand_followers); + $receiver_list = Item::enumeratePermissions($item, true, false); foreach ($terms as $term) { $cid = Contact::getIdForURL($term['url'], $item['uid']); @@ -851,6 +850,28 @@ class Transmitter unset($receivers['bcc']); } + if (!$blindcopy && count($receivers['audience']) == 1) { + $receivers['audience'] = $receivers['audience'][0]; + } elseif (!$receivers['audience']) { + unset($receivers['audience']); + } + + return $receivers; + } + + /** + * Store the receivers for the given item + * + * @param array $item + * @return void + */ + public static function storeReceiversForItem(array $item) + { + $receivers = self::createPermissionBlockForItem($item, true); + if (empty($receivers)) { + return; + } + foreach (['to' => Tag::TO, 'cc' => Tag::CC, 'bcc' => Tag::BCC, 'audience' => Tag::AUDIENCE] as $element => $type) { if (!empty($receivers[$element])) { foreach ($receivers[$element] as $receiver) { @@ -863,6 +884,44 @@ class Transmitter } } } + } + + /** + * Get a list of receivers for the provided uri-id + * + * @param array $item + * @param boolean $blindcopy + * @return void + */ + public static function getReceiversForUriId(int $uri_id, bool $blindcopy) + { + $receivers = [ + 'to' => [], + 'cc' => [], + 'bcc' => [], + 'audience' => [], + ]; + + foreach (Tag::getByURIId($uri_id, [Tag::TO, Tag::CC, Tag::BCC, Tag::AUDIENCE]) as $receiver) { + switch ($receiver['type']) { + case Tag::TO: + $receivers['to'][] = $receiver['url']; + break; + case Tag::CC: + $receivers['cc'][] = $receiver['url']; + break; + case Tag::BCC: + $receivers['bcc'][] = $receiver['url']; + break; + case Tag::AUDIENCE: + $receivers['audience'][] = $receiver['url']; + break; + } + } + + if (!$blindcopy) { + unset($receivers['bcc']); + } if (!$blindcopy && count($receivers['audience']) == 1) { $receivers['audience'] = $receivers['audience'][0]; @@ -932,11 +991,13 @@ class Transmitter } $condition = [ - 'uid' => $uid, - 'archive' => false, - 'pending' => false, - 'blocked' => false, - 'network' => Protocol::FEDERATED, + 'uid' => $uid, + 'self' => false, + 'archive' => false, + 'pending' => false, + 'blocked' => false, + 'network' => Protocol::FEDERATED, + 'contact-type' => [Contact::TYPE_UNKNOWN, Contact::TYPE_PERSON, Contact::TYPE_NEWS, Contact::TYPE_ORGANISATION], ]; if (!empty($uid)) { @@ -980,14 +1041,13 @@ class Transmitter * @param array $item Item array * @param integer $uid User ID * @param boolean $personal fetch personal inboxes - * @param integer $last_id Last item id for adding receivers * @return array with inboxes * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function fetchTargetInboxes(array $item, int $uid, bool $personal = false, int $last_id = 0): array + public static function fetchTargetInboxes(array $item, int $uid, bool $personal = false): array { - $permissions = self::createPermissionBlockForItem($item, true, true, $last_id); + $permissions = self::getReceiversForUriId($item['uri-id'], true); if (empty($permissions)) { return []; } @@ -1019,7 +1079,7 @@ class Transmitter } if ($item_profile && ($receiver == $item_profile['followers']) && ($uid == $profile_uid)) { - $inboxes = array_merge_recursive($inboxes, self::fetchTargetInboxesforUser($uid, $personal, self::isAPPost($last_id))); + $inboxes = array_merge_recursive($inboxes, self::fetchTargetInboxesforUser($uid, $personal, true)); } else { $profile = APContact::getByURL($receiver, false); if (!empty($profile)) { @@ -1119,7 +1179,7 @@ class Transmitter $data['actor'] = $mail['author-link']; $data['published'] = DateTimeFormat::utc($mail['created'] . '+00:00', DateTimeFormat::ATOM); $data['instrument'] = self::getService(); - $data = array_merge($data, self::createPermissionBlockForItem($mail, true, false)); + $data = array_merge($data, self::createPermissionBlockForItem($mail, true)); if (empty($data['to']) && !empty($data['cc'])) { $data['to'] = $data['cc']; @@ -1351,7 +1411,7 @@ class Transmitter $data['instrument'] = self::getService(); - $data = array_merge($data, self::createPermissionBlockForItem($item, false, false)); + $data = array_merge($data, self::createPermissionBlockForItem($item, false)); if (in_array($data['type'], ['Create', 'Update', 'Delete'])) { $data['object'] = self::createNote($item, $api_mode); @@ -1705,7 +1765,7 @@ class Transmitter $data['name'] = BBCode::toPlaintext($item['title'], false); } - $permission_block = self::createPermissionBlockForItem($item, false, false); + $permission_block = self::getReceiversForUriId($item['uri-id'], false); $real_quote = false; diff --git a/src/Util/HTTPSignature.php b/src/Util/HTTPSignature.php index ae7e0fe375..bc701d064e 100644 --- a/src/Util/HTTPSignature.php +++ b/src/Util/HTTPSignature.php @@ -434,6 +434,7 @@ class HTTPSignature } if (!$curlResult->isSuccess() || empty($curlResult->getBody())) { + Logger::debug('Fetching was unsuccessful', ['url' => $request, 'return-code' => $curlResult->getReturnCode(), 'error-number' => $curlResult->getErrorNumber(), 'error' => $curlResult->getError()]); return []; } diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php index e87a587f59..a15eca1bcf 100644 --- a/src/Worker/Notifier.php +++ b/src/Worker/Notifier.php @@ -815,7 +815,7 @@ class Notifier Logger::info('Remote item is no AP post. It will not be distributed.', ['id' => $target_item['id'], 'url' => $target_item['uri'], 'verb' => $target_item['verb']]); return ['count' => 0, 'contacts' => []]; } elseif ($parent['origin'] && (($target_item['gravity'] != Item::GRAVITY_ACTIVITY) || DI::config()->get('system', 'redistribute_activities'))) { - $inboxes = ActivityPub\Transmitter::fetchTargetInboxes($parent, $uid, false, $target_item['id']); + $inboxes = ActivityPub\Transmitter::fetchTargetInboxes($parent, $uid); if (in_array($target_item['private'], [Item::PUBLIC])) { $inboxes = ActivityPub\Transmitter::addRelayServerInboxesForItem($parent['id'], $inboxes);