From: Hypolite Petovan Date: Fri, 10 May 2024 00:46:49 +0000 (-0400) Subject: Escape output of PermissionTooltip module X-Git-Url: https://git.mxchange.org/?a=commitdiff_plain;h=a6cb3ed9030aa606b87bc6db4cfb1caa41b10bc2;p=friendica.git Escape output of PermissionTooltip module - Create AclReceivers and AddressedReceivers entities to collect contact names - Create privacy/permission_tooltip.tpl to escape contact names - Move PermissionTooltip module to Privacy namespace - Thanks to @apexrabbit for the report! --- diff --git a/src/Module/PermissionTooltip.php b/src/Module/PermissionTooltip.php deleted file mode 100644 index 5c6b68b4cf..0000000000 --- a/src/Module/PermissionTooltip.php +++ /dev/null @@ -1,275 +0,0 @@ -. - * - */ - -namespace Friendica\Module; - -use Friendica\App; -use Friendica\Core\Config\Capability\IManageConfigValues; -use Friendica\Core\Hook; -use Friendica\Core\L10n; -use Friendica\Core\Protocol; -use Friendica\Core\Session\Capability\IHandleUserSessions; -use Friendica\Database\Database; -use Friendica\Model; -use Friendica\Network\HTTPException; -use Friendica\Network\HTTPException\InternalServerErrorException; -use Friendica\Security\PermissionSet\Repository\PermissionSet; -use Friendica\Util\ACLFormatter; -use Friendica\Util\Profiler; -use Psr\Log\LoggerInterface; - -/** - * Outputs the permission tooltip HTML content for the provided item, photo or event id. - */ -class PermissionTooltip extends \Friendica\BaseModule -{ - private Database $dba; - private ACLFormatter $aclFormatter; - private IHandleUserSessions $session; - private IManageConfigValues $config; - private PermissionSet $permissionSet; - - public function __construct(PermissionSet $permissionSet, IManageConfigValues $config, IHandleUserSessions $session, ACLFormatter $aclFormatter, Database $dba, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) - { - parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); - - $this->dba = $dba; - $this->aclFormatter = $aclFormatter; - $this->session = $session; - $this->config = $config; - $this->permissionSet = $permissionSet; - } - - protected function rawContent(array $request = []) - { - $type = $this->parameters['type']; - $referenceId = $this->parameters['id']; - - $expectedTypes = ['item', 'photo', 'event']; - if (!in_array($type, $expectedTypes)) { - throw new HTTPException\BadRequestException($this->t('Wrong type "%s", expected one of: %s', $type, implode(', ', $expectedTypes))); - } - - $condition = ['id' => $referenceId, 'uid' => [0, $this->session->getLocalUserId()]]; - if ($type == 'item') { - $fields = ['uid', 'psid', 'private', 'uri-id', 'origin', 'network']; - $model = Model\Post::selectFirst($fields, $condition, ['order' => ['uid' => true]]); - - if ($model['origin'] || ($model['network'] != Protocol::ACTIVITYPUB)) { - $permissionSet = $this->permissionSet->selectOneById($model['psid'], $model['uid']); - $model['allow_cid'] = $permissionSet->allow_cid; - $model['allow_gid'] = $permissionSet->allow_gid; - $model['deny_cid'] = $permissionSet->deny_cid; - $model['deny_gid'] = $permissionSet->deny_gid; - } else { - $model['allow_cid'] = []; - $model['allow_gid'] = []; - $model['deny_cid'] = []; - $model['deny_gid'] = []; - } - } else { - $fields = ['uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']; - $model = $this->dba->selectFirst($type, $fields, $condition); - $model['allow_cid'] = $this->aclFormatter->expand($model['allow_cid']); - $model['allow_gid'] = $this->aclFormatter->expand($model['allow_gid']); - $model['deny_cid'] = $this->aclFormatter->expand($model['deny_cid']); - $model['deny_gid'] = $this->aclFormatter->expand($model['deny_gid']); - } - - if (!$this->dba->isResult($model)) { - throw new HttpException\NotFoundException($this->t('Model not found')); - } - - // Kept for backwards compatibility - Hook::callAll('lockview_content', $model); - - if ($type == 'item') { - $receivers = $this->fetchReceivers($model['uri-id']); - if (empty($receivers)) { - switch ($model['private']) { - case Model\Item::PUBLIC: - $receivers = $this->t('Public'); - break; - - case Model\Item::UNLISTED: - $receivers = $this->t('Unlisted'); - break; - - case Model\Item::PRIVATE: - $receivers = $this->t('Limited/Private'); - break; - } - } - } else { - $receivers = ''; - } - - if (empty($model['allow_cid']) - && empty($model['allow_gid']) - && empty($model['deny_cid']) - && empty($model['deny_gid']) - && empty($receivers)) - { - echo $this->t('Remote privacy information not available.'); - exit; - } - - if (!empty($model['allow_cid']) || !empty($model['allow_gid']) || !empty($model['deny_cid']) || !empty($model['deny_gid'])) { - $receivers = $this->fetchReceiversFromACL($model); - } - - $this->httpExit($this->t('Visible to:') . '
' . $receivers); - } - - /** - * Fetch a list of receivers based on the ACL data - * @throws \Exception - */ - private function fetchReceiversFromACL(array $model): string - { - $allowed_users = $model['allow_cid']; - $allowed_circles = $model['allow_gid']; - $deny_users = $model['deny_cid']; - $deny_circles = $model['deny_gid']; - - $l = []; - - if (count($allowed_circles)) { - $key = array_search(Model\Circle::FOLLOWERS, $allowed_circles); - if ($key !== false) { - $l[] = '' . $this->t('Followers') . ''; - unset($allowed_circles[$key]); - } - - $key = array_search(Model\Circle::MUTUALS, $allowed_circles); - if ($key !== false) { - $l[] = '' . $this->t('Mutuals') . ''; - unset($allowed_circles[$key]); - } - - foreach ($this->dba->selectToArray('group', ['name'], ['id' => $allowed_circles]) as $circle) { - $l[] = '' . $circle['name'] . ''; - } - } - - foreach ($this->dba->selectToArray('contact', ['name'], ['id' => $allowed_users]) as $contact) { - $l[] = $contact['name']; - } - - if (count($deny_circles)) { - $key = array_search(Model\Circle::FOLLOWERS, $deny_circles); - if ($key !== false) { - $l[] = '' . $this->t('Followers') . ''; - unset($deny_circles[$key]); - } - - $key = array_search(Model\Circle::MUTUALS, $deny_circles); - if ($key !== false) { - $l[] = '' . $this->t('Mutuals') . ''; - unset($deny_circles[$key]); - } - - foreach ($this->dba->selectToArray('group', ['name'], ['id' => $allowed_circles]) as $circle) { - $l[] = '' . $circle['name'] . ''; - } - } - - foreach ($this->dba->selectToArray('contact', ['name'], ['id' => $deny_users]) as $contact) { - $l[] = '' . $contact['name'] . ''; - } - - return implode(', ', $l); - } - - /** - * Fetch a list of receivers - * @throws InternalServerErrorException - */ - private function fetchReceivers(int $uriId): string - { - $own_url = ''; - $uid = $this->session->getLocalUserId(); - if ($uid) { - $owner = Model\User::getOwnerDataById($uid); - if (!empty($owner['url'])) { - $own_url = $owner['url']; - } - } - - $receivers = []; - foreach (Model\Tag::getByURIId($uriId, [Model\Tag::TO, Model\Tag::CC, Model\Tag::BCC, Model\Tag::AUDIENCE, Model\Tag::ATTRIBUTED]) as $receiver) { - // We only display BCC when it contains the current user - if (($receiver['type'] == Model\Tag::BCC) && ($receiver['url'] != $own_url)) { - continue; - } - - switch (Model\Tag::getTargetType($receiver['url'], false)) { - case Model\Tag::PUBLIC_COLLECTION: - $receivers[$receiver['type']][] = $this->t('Public'); - break; - case Model\Tag::GENERAL_COLLECTION: - $receivers[$receiver['type']][] = $this->t('Collection (%s)', $receiver['name']); - break; - case Model\Tag::FOLLOWER_COLLECTION: - $apcontact = $this->dba->selectFirst('apcontact', ['name'], ['followers' => $receiver['url']]); - $receivers[$receiver['type']][] = $this->t('Followers (%s)', $apcontact['name'] ?? $receiver['name']); - break; - case Model\Tag::ACCOUNT: - $apcontact = Model\APContact::getByURL($receiver['url'], false); - $receivers[$receiver['type']][] = $apcontact['name'] ?? $receiver['name']; - break; - default: - $receivers[$receiver['type']][] = $receiver['name']; - break; - } - } - - $output = ''; - - foreach ($receivers as $type => $receiver) { - $max = $this->config->get('system', 'max_receivers'); - $total = count($receiver); - if ($total > $max) { - $receiver = array_slice($receiver, 0, $max); - $receiver[] = $this->t('%d more', $total - $max); - } - switch ($type) { - case Model\Tag::TO: - $output .= $this->t('To: %s
', implode(', ', $receiver)); - break; - case Model\Tag::CC: - $output .= $this->t('CC: %s
', implode(', ', $receiver)); - break; - case Model\Tag::BCC: - $output .= $this->t('BCC: %s
', implode(', ', $receiver)); - break; - case Model\Tag::AUDIENCE: - $output .= $this->t('Audience: %s
', implode(', ', $receiver)); - break; - case Model\Tag::ATTRIBUTED: - $output .= $this->t('Attributed To: %s
', implode(', ', $receiver)); - break; - } - } - - return $output; - } -} diff --git a/src/Module/Privacy/PermissionTooltip.php b/src/Module/Privacy/PermissionTooltip.php new file mode 100644 index 0000000000..cca7f7f9a3 --- /dev/null +++ b/src/Module/Privacy/PermissionTooltip.php @@ -0,0 +1,267 @@ +. + * + */ + +namespace Friendica\Module\Privacy; + +use Friendica\App; +use Friendica\Core\Config\Capability\IManageConfigValues; +use Friendica\Core\Hook; +use Friendica\Core\L10n; +use Friendica\Core\Protocol; +use Friendica\Core\Renderer; +use Friendica\Core\Session\Capability\IHandleUserSessions; +use Friendica\Database\Database; +use Friendica\Model; +use Friendica\Module\Response; +use Friendica\Network\HTTPException; +use Friendica\Network\HTTPException\InternalServerErrorException; +use Friendica\Privacy\Entity; +use Friendica\Security\PermissionSet\Repository\PermissionSet; +use Friendica\Util\ACLFormatter; +use Friendica\Util\Profiler; +use Psr\Log\LoggerInterface; + +/** + * Outputs the permission tooltip HTML content for the provided item, photo or event id. + */ +class PermissionTooltip extends \Friendica\BaseModule +{ + private Database $dba; + private ACLFormatter $aclFormatter; + private IHandleUserSessions $session; + private IManageConfigValues $config; + private PermissionSet $permissionSet; + + public function __construct(PermissionSet $permissionSet, IManageConfigValues $config, IHandleUserSessions $session, ACLFormatter $aclFormatter, Database $dba, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = []) + { + parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters); + + $this->dba = $dba; + $this->aclFormatter = $aclFormatter; + $this->session = $session; + $this->config = $config; + $this->permissionSet = $permissionSet; + } + + protected function rawContent(array $request = []) + { + $type = $this->parameters['type']; + $referenceId = $this->parameters['id']; + + $expectedTypes = ['item', 'photo', 'event']; + if (!in_array($type, $expectedTypes)) { + throw new HTTPException\BadRequestException($this->t('Wrong type "%s", expected one of: %s', $type, implode(', ', $expectedTypes))); + } + + $condition = ['id' => $referenceId, 'uid' => [0, $this->session->getLocalUserId()]]; + if ($type == 'item') { + $fields = ['uid', 'psid', 'private', 'uri-id', 'origin', 'network']; + $model = Model\Post::selectFirst($fields, $condition, ['order' => ['uid' => true]]); + + if ($model['origin'] || ($model['network'] != Protocol::ACTIVITYPUB)) { + $permissionSet = $this->permissionSet->selectOneById($model['psid'], $model['uid']); + $model['allow_cid'] = $permissionSet->allow_cid; + $model['allow_gid'] = $permissionSet->allow_gid; + $model['deny_cid'] = $permissionSet->deny_cid; + $model['deny_gid'] = $permissionSet->deny_gid; + } else { + $model['allow_cid'] = []; + $model['allow_gid'] = []; + $model['deny_cid'] = []; + $model['deny_gid'] = []; + } + } else { + $fields = ['uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid']; + $model = $this->dba->selectFirst($type, $fields, $condition); + $model['allow_cid'] = $this->aclFormatter->expand($model['allow_cid']); + $model['allow_gid'] = $this->aclFormatter->expand($model['allow_gid']); + $model['deny_cid'] = $this->aclFormatter->expand($model['deny_cid']); + $model['deny_gid'] = $this->aclFormatter->expand($model['deny_gid']); + } + + if (!$this->dba->isResult($model)) { + throw new HttpException\NotFoundException($this->t('Model not found')); + } + + // Kept for backwards compatibility + Hook::callAll('lockview_content', $model); + + $aclReceivers = new Entity\AclReceivers(); + $addressedReceivers = new Entity\AddressedReceivers(); + if (!empty($model['allow_cid']) || !empty($model['allow_gid']) || !empty($model['deny_cid']) || !empty($model['deny_gid'])) { + $aclReceivers = $this->fetchReceiversFromACL($model); + } elseif ($type == 'item') { + $addressedReceivers = $this->fetchAddressedReceivers($model['uri-id']); + } + + $privacy = ''; + switch ($model['private'] ?? null) { + case Model\Item::PUBLIC: $privacy = $this->t('Public'); break; + case Model\Item::UNLISTED: $privacy = $this->t('Unlisted'); break; + case Model\Item::PRIVATE: $privacy = $this->t('Limited/Private'); break; + } + + if ($aclReceivers->isEmpty() && $addressedReceivers->isEmpty() && empty($privacy)) + { + echo $this->t('Remote privacy information not available.'); + exit; + } + + $tpl = Renderer::getMarkupTemplate('privacy/permission_tooltip.tpl'); + $output = Renderer::replaceMacros($tpl, [ + '$l10n' => [ + 'visible_to' => $this->t('Visible to:'), + 'to' => $this->t('To:'), + 'cc' => $this->t('CC:'), + 'bcc' => $this->t('BCC:'), + 'audience' => $this->t('Audience:'), + 'attributed' => $this->t('Attributed To:'), + ], + '$aclReceivers' => $aclReceivers, + '$addressedReceivers' => $addressedReceivers, + '$privacy' => $privacy, + ]); + + $this->httpExit($output); + } + + /** + * @throws \Exception + */ + private function fetchReceiversFromACL(array $model): Entity\AclReceivers + { + $allow_cid = $model['allow_cid']; + $allow_gid = $model['allow_gid']; + $deny_cid = $model['deny_cid']; + $deny_gid = $model['deny_gid']; + + $allowContacts = []; + $allowCircles = []; + $denyContacts = []; + $denyCircles = []; + + if (count($allow_gid)) { + $key = array_search(Model\Circle::FOLLOWERS, $allow_gid); + if ($key !== false) { + $allowCircles[] = $this->t('Followers'); + unset($allow_gid[$key]); + } + + $key = array_search(Model\Circle::MUTUALS, $allow_gid); + if ($key !== false) { + $allowCircles[] = $this->t('Mutuals'); + unset($allow_gid[$key]); + } + + foreach ($this->dba->selectToArray('group', ['name'], ['id' => $allow_gid]) as $circle) { + $allowCircles[] = $circle['name']; + } + } + + foreach ($this->dba->selectToArray('contact', ['name'], ['id' => $allow_cid]) as $contact) { + $allowContacts[] = $contact['name']; + } + + if (count($deny_gid)) { + $key = array_search(Model\Circle::FOLLOWERS, $deny_gid); + if ($key !== false) { + $denyCircles[] = $this->t('Followers'); + unset($deny_gid[$key]); + } + + $key = array_search(Model\Circle::MUTUALS, $deny_gid); + if ($key !== false) { + $denyCircles[] = $this->t('Mutuals'); + unset($deny_gid[$key]); + } + + foreach ($this->dba->selectToArray('group', ['name'], ['id' => $allow_gid]) as $circle) { + $denyCircles[] = $circle['name']; + } + } + + foreach ($this->dba->selectToArray('contact', ['name'], ['id' => $deny_cid]) as $contact) { + $denyContacts[] = $contact['name']; + } + + return new Entity\AclReceivers($allowContacts, $allowCircles, $denyContacts, $denyCircles); + } + + /** + * @throws InternalServerErrorException + */ + private function fetchAddressedReceivers(int $uriId): Entity\AddressedReceivers + { + $own_url = ''; + $uid = $this->session->getLocalUserId(); + if ($uid) { + $owner = Model\User::getOwnerDataById($uid); + if (!empty($owner['url'])) { + $own_url = $owner['url']; + } + } + + $receivers = []; + foreach (Model\Tag::getByURIId($uriId, [Model\Tag::TO, Model\Tag::CC, Model\Tag::BCC, Model\Tag::AUDIENCE, Model\Tag::ATTRIBUTED]) as $receiver) { + // We only display BCC when it contains the current user + if (($receiver['type'] == Model\Tag::BCC) && ($receiver['url'] != $own_url)) { + continue; + } + + switch (Model\Tag::getTargetType($receiver['url'], false)) { + case Model\Tag::PUBLIC_COLLECTION: + $receivers[$receiver['type']][] = $this->t('Public'); + break; + case Model\Tag::GENERAL_COLLECTION: + $receivers[$receiver['type']][] = $this->t('Collection (%s)', $receiver['name']); + break; + case Model\Tag::FOLLOWER_COLLECTION: + $apcontact = $this->dba->selectFirst('apcontact', ['name'], ['followers' => $receiver['url']]); + $receivers[$receiver['type']][] = $this->t('Followers (%s)', $apcontact['name'] ?? $receiver['name']); + break; + case Model\Tag::ACCOUNT: + $apcontact = Model\APContact::getByURL($receiver['url'], false); + $receivers[$receiver['type']][] = $apcontact['name'] ?? $receiver['name']; + break; + default: + $receivers[$receiver['type']][] = $receiver['name']; + break; + } + } + + foreach ($receivers as $type => $receiver) { + $max = $this->config->get('system', 'max_receivers'); + $total = count($receiver); + if ($total > $max) { + $receivers[$type] = array_slice($receiver, 0, $max); + $receivers[$type][] = $this->t('%d more', $total - $max); + } + } + + return new Entity\AddressedReceivers( + $receivers[Model\Tag::TO] ?? [], + $receivers[Model\Tag::CC] ?? [], + $receivers[Model\Tag::BCC] ?? [], + $receivers[Model\Tag::AUDIENCE] ?? [], + $receivers[Model\Tag::ATTRIBUTED] ?? [], + ); + } +} diff --git a/src/Privacy/Entity/AclReceivers.php b/src/Privacy/Entity/AclReceivers.php new file mode 100644 index 0000000000..3212c82166 --- /dev/null +++ b/src/Privacy/Entity/AclReceivers.php @@ -0,0 +1,45 @@ +. + * + */ + +namespace Friendica\Privacy\Entity; + +use Friendica\BaseEntity; + +class AclReceivers extends BaseEntity +{ + protected array $allowContacts = []; + protected array $allowCircles = []; + protected array $denyContacts = []; + protected array $denyCircles = []; + + public function __construct(array $allowContacts = [], array $allowCircles = [], array $denyContacts = [], array $denyCircles = []) + { + $this->allowContacts = $allowContacts; + $this->allowCircles = $allowCircles; + $this->denyContacts = $denyContacts; + $this->denyCircles = $denyCircles; + } + + public function isEmpty(): bool + { + return empty($this->allowContacts) && empty($this->allowCircles) && empty($this->denyContacts) && empty($this->denyCircles); + } +} diff --git a/src/Privacy/Entity/AddressedReceivers.php b/src/Privacy/Entity/AddressedReceivers.php new file mode 100644 index 0000000000..ca20db38c7 --- /dev/null +++ b/src/Privacy/Entity/AddressedReceivers.php @@ -0,0 +1,47 @@ +. + * + */ + +namespace Friendica\Privacy\Entity; + +use Friendica\BaseEntity; + +class AddressedReceivers extends BaseEntity +{ + protected array $to = []; + protected array $cc = []; + protected array $bcc = []; + protected array $audience = []; + protected array $attributed = []; + + public function __construct(array $to = [], array $cc = [], array $bcc = [], array $audience = [], array $attributed = []) + { + $this->to = $to; + $this->cc = $cc; + $this->bcc = $bcc; + $this->audience = $audience; + $this->attributed = $attributed; + } + + public function isEmpty(): bool + { + return empty($this->to) && empty($this->cc) && empty($this->bcc) && empty($this->audience) && empty($this->attributed); + } +} diff --git a/static/routes.config.php b/static/routes.config.php index 32d328b77b..dfd414c444 100644 --- a/static/routes.config.php +++ b/static/routes.config.php @@ -565,7 +565,7 @@ return [ '/opensearch' => [Module\OpenSearch::class, [R::GET]], '/parseurl' => [Module\ParseUrl::class, [R::GET]], - '/permission/tooltip/{type}/{id:\d+}' => [Module\PermissionTooltip::class, [R::GET]], + '/permission/tooltip/{type}/{id:\d+}' => [Module\Privacy\PermissionTooltip::class, [R::GET]], '/photo' => [ '/{size:thumb_small|scaled_full}_{name}' => [Module\Photo::class, [R::GET]], diff --git a/view/templates/privacy/permission_tooltip.tpl b/view/templates/privacy/permission_tooltip.tpl new file mode 100644 index 0000000000..a458755206 --- /dev/null +++ b/view/templates/privacy/permission_tooltip.tpl @@ -0,0 +1,50 @@ +{{$l10n.visible_to}}
+{{if !$aclReceivers->isEmpty()}} + {{foreach from=$aclReceivers->allowCircles item=circle name=allowCircles}} + {{$circle}} + {{if !$smarty.foreach.allowCircles.last}}, {{/if}} + {{/foreach}} + {{if $aclReceivers->allowContacts && $aclReceivers->allowCircles}}, {{/if}} + {{foreach from=$aclReceivers->allowContacts item=contact name=allowContacts}} + {{$contact}} + {{if !$smarty.foreach.allowContacts.last}}, {{/if}} + {{/foreach}} + {{if $aclReceivers->denyCircles && ($aclReceivers->allowContacts || $aclReceivers->allowCircles)}}, {{/if}} + {{foreach from=$aclReceivers->denyCircles item=circle name=denyCircles}} + {{$circle}} + {{if !$smarty.foreach.denyCircles.last}}, {{/if}} + {{/foreach}} + {{if $aclReceivers->denyContacts && ($aclReceivers->denyCircles || $aclReceivers->allowContacts || $aclReceivers->allowCircles)}}, {{/if}} + {{foreach from=$aclReceivers->denyContacts item=contact name=denyContacts}} + {{$contact}} + {{if !$smarty.foreach.denyContacts.last}}, {{/if}} + {{/foreach}} +{{elseif !$addressedReceivers->isEmpty()}} + {{if $addressedReceivers->to}} + {{$l10n.to}} + {{', '|join:$addressedReceivers->to}} +
+ {{/if}} + {{if $addressedReceivers->cc}} + {{$l10n.cc}} + {{', '|join:$addressedReceivers->cc}} +
+ {{/if}} + {{if $addressedReceivers->bcc}} + {{$l10n.bcc}} + {{', '|join:$addressedReceivers->bcc}} +
+ {{/if}} + {{if $addressedReceivers->audience}} + {{$l10n.audience}} + {{', '|join:$addressedReceivers->audience}} +
+ {{/if}} + {{if $addressedReceivers->attributed}} + {{$l10n.attributed}} + {{', '|join:$addressedReceivers->attributed}} +
+ {{/if}} +{{else}} + {{$privacy}} +{{/if}}