From 26aa38341ffe72f48264b2d58d484263a28e074c Mon Sep 17 00:00:00 2001 From: Michael Date: Fri, 21 Jul 2023 07:06:55 +0000 Subject: [PATCH 1/5] The access to the profile and the list of followers/followings can now be restricted --- src/Module/Profile/Profile.php | 2 +- src/Protocol/ActivityPub.php | 43 ++++++++++++++++++++++++ src/Protocol/ActivityPub/Transmitter.php | 15 +++++---- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/src/Module/Profile/Profile.php b/src/Module/Profile/Profile.php index 5e5028cb8..7ee953a62 100644 --- a/src/Module/Profile/Profile.php +++ b/src/Module/Profile/Profile.php @@ -84,7 +84,7 @@ class Profile extends BaseProfile $user = $this->database->selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname'] ?? '', 'account_removed' => false]); if ($user) { try { - $data = ActivityPub\Transmitter::getProfile($user['uid']); + $data = ActivityPub\Transmitter::getProfile($user['uid'], !ActivityPub::isAcceptedRequester($user['uid'])); header('Access-Control-Allow-Origin: *'); header('Cache-Control: max-age=23200, stale-while-revalidate=23200'); System::jsonExit($data, 'application/activity+json'); diff --git a/src/Protocol/ActivityPub.php b/src/Protocol/ActivityPub.php index ad1e9367c..4a1030eaa 100644 --- a/src/Protocol/ActivityPub.php +++ b/src/Protocol/ActivityPub.php @@ -23,10 +23,13 @@ namespace Friendica\Protocol; use Friendica\Core\Logger; use Friendica\Core\Protocol; +use Friendica\Core\System; use Friendica\Model\APContact; +use Friendica\Model\Contact; use Friendica\Model\User; use Friendica\Util\HTTPSignature; use Friendica\Util\JsonLD; +use Friendica\Util\Network; /** * ActivityPub Protocol class @@ -277,4 +280,44 @@ class ActivityPub { return !empty(APContact::getByURL($url, $update)); } + + public static function isAcceptedRequester(int $uid = 0): bool + { + $called_by = System::callstack(1); + + $signer = HTTPSignature::getSigner('', $_SERVER); + if (!$signer) { + Logger::debug('No signer', ['uid' => $uid, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'called_by' => $called_by]); + return false; + } + + $apcontact = APContact::getByURL($signer); + if (empty($apcontact)) { + Logger::debug('APContact not found', ['uid' => $uid, 'handle' => $signer, 'called_by' => $called_by]); + return false; + } + + if (empty($apcontact['gsid'] || empty($apcontact['baseurl']))) { + Logger::debug('No server found', ['uid' => $uid, 'signer' => $signer, 'called_by' => $called_by]); + return false; + } + + // Check added as a precaution. It should not occur. + if (Network::isUrlBlocked($apcontact['baseurl'])) { + Logger::info('Requesting domain is blocked', ['uid' => $uid, 'id' => $apcontact['gsid'], 'url' => $apcontact['baseurl'], 'signer' => $signer, 'called_by' => $called_by]); + return false; + } + + $contact = Contact::getByURL($signer, false, ['id', 'baseurl', 'gsid']); + if (!empty($contact) && Contact\User::isBlocked($contact['id'], $uid)) { + Logger::info('Requesting contact is blocked', ['uid' => $uid, 'id' => $contact['id'], 'signer' => $signer, 'baseurl' => $contact['baseurl'], 'called_by' => $called_by]); + return false; + } + + // @todo Look for user blocked domains + + Logger::debug('Server is an accepted requester', ['uid' => $uid, 'id' => $contact['gsid'], 'url' => $contact['baseurl'], 'signer' => $signer, 'called_by' => $called_by]); + + return true; + } } diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 6c0cbd92b..bda202421 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -195,7 +195,7 @@ class Transmitter } // When we hide our friends we will only show the pure number but don't allow more. - $show_contacts = empty($owner['hide-friends']); + $show_contacts = ActivityPub::isAcceptedRequester($owner['uid']) && empty($owner['hide-friends']); // Allow fetching the contact list when the requester is part of the list. if (($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) && !empty($requester)) { @@ -337,12 +337,13 @@ class Transmitter /** * Return the ActivityPub profile of the given user * - * @param int $uid User ID + * @param int $uid User ID + * @param bool $limited If limited, only the basic information is returned * @return array with profile data * @throws HTTPException\NotFoundException * @throws HTTPException\InternalServerErrorException */ - public static function getProfile(int $uid): array + public static function getProfile(int $uid, bool $limited = false): array { $owner = User::getOwnerDataById($uid); if (!isset($owner['id'])) { @@ -372,16 +373,16 @@ class Transmitter $data['preferredUsername'] = $owner['nick']; $data['name'] = $owner['name']; - if (!empty($owner['country-name'] . $owner['region'] . $owner['locality'])) { + if (!$limited && !empty($owner['country-name'] . $owner['region'] . $owner['locality'])) { $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $owner['country-name'], 'vcard:region' => $owner['region'], 'vcard:locality' => $owner['locality']]; } - if (!empty($owner['about'])) { + if (!$limited && !empty($owner['about'])) { $data['summary'] = BBCode::convertForUriId($owner['uri-id'] ?? 0, $owner['about'], BBCode::EXTERNAL); } - if (!empty($owner['xmpp']) || !empty($owner['matrix'])) { + if (!$limited && (!empty($owner['xmpp']) || !empty($owner['matrix']))) { $data['vcard:hasInstantMessage'] = []; if (!empty($owner['xmpp'])) { @@ -399,7 +400,7 @@ class Transmitter 'owner' => $owner['url'], 'publicKeyPem' => $owner['pubkey']]; $data['endpoints'] = ['sharedInbox' => DI::baseUrl() . '/inbox']; - if ($uid != 0) { + if (!$limited && $uid != 0) { $data['icon'] = ['type' => 'Image', 'url' => User::getAvatarUrl($owner)]; $resourceid = Photo::ridFromURI($owner['photo']); From 579b7065bd7c6957b089e7bdcbf8a67306a6cbbd Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 22 Jul 2023 01:40:21 +0000 Subject: [PATCH 2/5] full instead of limited --- src/Module/Profile/Profile.php | 2 +- src/Protocol/ActivityPub/Transmitter.php | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Module/Profile/Profile.php b/src/Module/Profile/Profile.php index 7ee953a62..054943c13 100644 --- a/src/Module/Profile/Profile.php +++ b/src/Module/Profile/Profile.php @@ -84,7 +84,7 @@ class Profile extends BaseProfile $user = $this->database->selectFirst('user', ['uid'], ['nickname' => $this->parameters['nickname'] ?? '', 'account_removed' => false]); if ($user) { try { - $data = ActivityPub\Transmitter::getProfile($user['uid'], !ActivityPub::isAcceptedRequester($user['uid'])); + $data = ActivityPub\Transmitter::getProfile($user['uid'], ActivityPub::isAcceptedRequester($user['uid'])); header('Access-Control-Allow-Origin: *'); header('Cache-Control: max-age=23200, stale-while-revalidate=23200'); System::jsonExit($data, 'application/activity+json'); diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index bda202421..4b0450a0c 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -337,13 +337,13 @@ class Transmitter /** * Return the ActivityPub profile of the given user * - * @param int $uid User ID - * @param bool $limited If limited, only the basic information is returned + * @param int $uid User ID + * @param bool $full If not full, only the basic information is returned * @return array with profile data * @throws HTTPException\NotFoundException * @throws HTTPException\InternalServerErrorException */ - public static function getProfile(int $uid, bool $limited = false): array + public static function getProfile(int $uid, bool $full = true): array { $owner = User::getOwnerDataById($uid); if (!isset($owner['id'])) { @@ -373,16 +373,16 @@ class Transmitter $data['preferredUsername'] = $owner['nick']; $data['name'] = $owner['name']; - if (!$limited && !empty($owner['country-name'] . $owner['region'] . $owner['locality'])) { + if (!$full && !empty($owner['country-name'] . $owner['region'] . $owner['locality'])) { $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $owner['country-name'], 'vcard:region' => $owner['region'], 'vcard:locality' => $owner['locality']]; } - if (!$limited && !empty($owner['about'])) { + if ($full && !empty($owner['about'])) { $data['summary'] = BBCode::convertForUriId($owner['uri-id'] ?? 0, $owner['about'], BBCode::EXTERNAL); } - if (!$limited && (!empty($owner['xmpp']) || !empty($owner['matrix']))) { + if ($full && (!empty($owner['xmpp']) || !empty($owner['matrix']))) { $data['vcard:hasInstantMessage'] = []; if (!empty($owner['xmpp'])) { @@ -400,7 +400,7 @@ class Transmitter 'owner' => $owner['url'], 'publicKeyPem' => $owner['pubkey']]; $data['endpoints'] = ['sharedInbox' => DI::baseUrl() . '/inbox']; - if (!$limited && $uid != 0) { + if ($full && $uid != 0) { $data['icon'] = ['type' => 'Image', 'url' => User::getAvatarUrl($owner)]; $resourceid = Photo::ridFromURI($owner['photo']); From f0563df9914fbeb06bbce85603f13a600143f389 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 22 Jul 2023 16:00:09 +0000 Subject: [PATCH 3/5] Fix notice --- src/Protocol/ActivityPub.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Protocol/ActivityPub.php b/src/Protocol/ActivityPub.php index 4a1030eaa..152cca98a 100644 --- a/src/Protocol/ActivityPub.php +++ b/src/Protocol/ActivityPub.php @@ -316,7 +316,7 @@ class ActivityPub // @todo Look for user blocked domains - Logger::debug('Server is an accepted requester', ['uid' => $uid, 'id' => $contact['gsid'], 'url' => $contact['baseurl'], 'signer' => $signer, 'called_by' => $called_by]); + Logger::debug('Server is an accepted requester', ['uid' => $uid, 'id' => $apcontact['gsid'], 'url' => $apcontact['baseurl'], 'signer' => $signer, 'called_by' => $called_by]); return true; } From 60c7bc90e6c622161d61fca4a3545eabceec1abf Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 23 Jul 2023 13:59:01 +0000 Subject: [PATCH 4/5] Removed unneeded check --- src/Protocol/ActivityPub.php | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/Protocol/ActivityPub.php b/src/Protocol/ActivityPub.php index 152cca98a..570348be2 100644 --- a/src/Protocol/ActivityPub.php +++ b/src/Protocol/ActivityPub.php @@ -29,7 +29,6 @@ use Friendica\Model\Contact; use Friendica\Model\User; use Friendica\Util\HTTPSignature; use Friendica\Util\JsonLD; -use Friendica\Util\Network; /** * ActivityPub Protocol class @@ -287,13 +286,13 @@ class ActivityPub $signer = HTTPSignature::getSigner('', $_SERVER); if (!$signer) { - Logger::debug('No signer', ['uid' => $uid, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'called_by' => $called_by]); + Logger::debug('No signer or invalid signature', ['uid' => $uid, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'called_by' => $called_by]); return false; } $apcontact = APContact::getByURL($signer); if (empty($apcontact)) { - Logger::debug('APContact not found', ['uid' => $uid, 'handle' => $signer, 'called_by' => $called_by]); + Logger::info('APContact not found', ['uid' => $uid, 'handle' => $signer, 'called_by' => $called_by]); return false; } @@ -302,12 +301,6 @@ class ActivityPub return false; } - // Check added as a precaution. It should not occur. - if (Network::isUrlBlocked($apcontact['baseurl'])) { - Logger::info('Requesting domain is blocked', ['uid' => $uid, 'id' => $apcontact['gsid'], 'url' => $apcontact['baseurl'], 'signer' => $signer, 'called_by' => $called_by]); - return false; - } - $contact = Contact::getByURL($signer, false, ['id', 'baseurl', 'gsid']); if (!empty($contact) && Contact\User::isBlocked($contact['id'], $uid)) { Logger::info('Requesting contact is blocked', ['uid' => $uid, 'id' => $contact['id'], 'signer' => $signer, 'baseurl' => $contact['baseurl'], 'called_by' => $called_by]); From 9e726eadeeccbeda62d340d2d2a4c6d95c47aa73 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 23 Jul 2023 14:27:08 +0000 Subject: [PATCH 5/5] Coded reformatted --- src/Protocol/ActivityPub.php | 45 +++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/src/Protocol/ActivityPub.php b/src/Protocol/ActivityPub.php index 570348be2..4be88b341 100644 --- a/src/Protocol/ActivityPub.php +++ b/src/Protocol/ActivityPub.php @@ -61,26 +61,29 @@ use Friendica\Util\JsonLD; class ActivityPub { const PUBLIC_COLLECTION = 'https://www.w3.org/ns/activitystreams#Public'; - const CONTEXT = ['https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1', - ['vcard' => 'http://www.w3.org/2006/vcard/ns#', - 'dfrn' => 'http://purl.org/macgirvin/dfrn/1.0/', - 'diaspora' => 'https://diasporafoundation.org/ns/', - 'litepub' => 'http://litepub.social/ns#', - 'toot' => 'http://joinmastodon.org/ns#', - 'featured' => [ - "@id" => "toot:featured", - "@type" => "@id", - ], - 'schema' => 'http://schema.org#', - 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', - 'sensitive' => 'as:sensitive', 'Hashtag' => 'as:Hashtag', - 'quoteUrl' => 'as:quoteUrl', - 'conversation' => 'ostatus:conversation', - 'directMessage' => 'litepub:directMessage', - 'discoverable' => 'toot:discoverable', - 'PropertyValue' => 'schema:PropertyValue', - 'value' => 'schema:value', - ]]; + const CONTEXT = [ + 'https://www.w3.org/ns/activitystreams', 'https://w3id.org/security/v1', + [ + 'vcard' => 'http://www.w3.org/2006/vcard/ns#', + 'dfrn' => 'http://purl.org/macgirvin/dfrn/1.0/', + 'diaspora' => 'https://diasporafoundation.org/ns/', + 'litepub' => 'http://litepub.social/ns#', + 'toot' => 'http://joinmastodon.org/ns#', + 'featured' => [ + "@id" => "toot:featured", + "@type" => "@id", + ], + 'schema' => 'http://schema.org#', + 'manuallyApprovesFollowers' => 'as:manuallyApprovesFollowers', + 'sensitive' => 'as:sensitive', 'Hashtag' => 'as:Hashtag', + 'quoteUrl' => 'as:quoteUrl', + 'conversation' => 'ostatus:conversation', + 'directMessage' => 'litepub:directMessage', + 'discoverable' => 'toot:discoverable', + 'PropertyValue' => 'schema:PropertyValue', + 'value' => 'schema:value', + ] + ]; const ACCOUNT_TYPES = ['Person', 'Organization', 'Service', 'Group', 'Application', 'Tombstone']; /** * Checks if the web request is done for the AP protocol @@ -119,7 +122,7 @@ class ActivityPub { $accounttype = -1; - switch($apcontact['type']) { + switch ($apcontact['type']) { case 'Person': $accounttype = User::ACCOUNT_TYPE_PERSON; break;