diff --git a/API.php b/API.php index d859f970..465d66cd 100644 --- a/API.php +++ b/API.php @@ -156,6 +156,7 @@ public function __construct(Tag $tags, Trigger $triggers, Variable $variables, C public function getAvailableContexts() { Piwik::checkUserHasSomeViewAccess(); + $this->checkUserHasTagManagerAccess(); $contexts = $this->contextProvider->getAllContexts(); @@ -177,6 +178,7 @@ public function getAvailableContexts() public function getAvailableEnvironments() { Piwik::checkUserHasSomeViewAccess(); + $this->checkUserHasTagManagerAccess(); return $this->environment->getEnvironments(); } @@ -190,6 +192,7 @@ public function getAvailableEnvironments() public function getAvailableEnvironmentsWithPublishCapability($idSite) { Piwik::checkUserHasSomeViewAccess(); + $this->checkUserHasTagManagerAccess($idSite); $environments = $this->environment->getEnvironments(); @@ -210,6 +213,7 @@ public function getAvailableEnvironmentsWithPublishCapability($idSite) public function getAvailableTagFireLimits() { Piwik::checkUserHasSomeViewAccess(); + $this->checkUserHasTagManagerAccess(); return $this->tags->getFireLimits(); } @@ -222,6 +226,7 @@ public function getAvailableTagFireLimits() public function getAvailableComparisons() { Piwik::checkUserHasSomeViewAccess(); + $this->checkUserHasTagManagerAccess(); return $this->comparisons->getSupportedComparisons(); } @@ -234,6 +239,7 @@ public function getAvailableComparisons() public function getAvailableTagTypesInContext($idContext) { Piwik::checkUserHasSomeViewAccess(); + $this->checkUserHasTagManagerAccess(); $this->contextProvider->checkIsValidContext($idContext); @@ -262,6 +268,7 @@ public function getAvailableTagTypesInContext($idContext) public function getAvailableTriggerTypesInContext($idContext) { Piwik::checkUserHasSomeViewAccess(); + $this->checkUserHasTagManagerAccess(); $this->contextProvider->checkIsValidContext($idContext); @@ -286,6 +293,7 @@ public function getAvailableTriggerTypesInContext($idContext) public function getAvailableVariableTypesInContext($idContext) { Piwik::checkUserHasSomeViewAccess(); + $this->checkUserHasTagManagerAccess(); $this->contextProvider->checkIsValidContext($idContext); @@ -1448,4 +1456,19 @@ private function decodeQuotes($value) { return htmlspecialchars_decode($value, ENT_QUOTES); } + + /** + * Check whether the current user has MTM access. If the site ID isn't provided, try looking it up on the request + * + * @param $idSite + * @return void + * @throws \Piwik\NoAccessException + */ + private function checkUserHasTagManagerAccess($idSite = null) + { + if (empty($idSite)) { + $idSite = \Piwik\Request::fromRequest()->getIntegerParameter('idSite', 0); + } + $this->accessValidator->checkUserHasTagManagerAccess($idSite); + } } diff --git a/Controller.php b/Controller.php index 22b3b803..7337892a 100644 --- a/Controller.php +++ b/Controller.php @@ -9,6 +9,7 @@ use Piwik\API\Request; use Piwik\Common; +use Piwik\Container\StaticContainer; use Piwik\DataTable\Filter\SafeDecodeLabel; use Piwik\Filechecks; use Piwik\Menu\MenuTop; @@ -156,6 +157,8 @@ public function dashboard() public function manageTags() { + $this->accessValidator->checkUserHasTagManagerAccess($this->idSite); + $tagsHelpText = $this->renderTemplate('helpContent', [ 'subHeading' => Piwik::translate('TagManager_ManageTagsHelp1'), 'paragraphs' => [ @@ -176,6 +179,8 @@ public function manageTags() public function manageTriggers() { + $this->accessValidator->checkUserHasTagManagerAccess($this->idSite); + $triggersHelpText = $this->renderTemplate('helpContent', [ 'subHeading' => Piwik::translate('TagManager_ManageTriggersHelp1'), 'paragraphs' => [ @@ -198,6 +203,8 @@ public function manageTriggers() public function manageVariables() { + $this->accessValidator->checkUserHasTagManagerAccess($this->idSite); + $variablesHelpText = $this->renderTemplate('helpContent', [ 'subHeading' => Piwik::translate('TagManager_ManageVariablesHelp1'), 'paragraphs' => [ @@ -220,6 +227,8 @@ public function manageVariables() public function manageVersions() { + $this->accessValidator->checkUserHasTagManagerAccess($this->idSite); + $idSite = Common::getRequestVar('idSite', null, 'int'); $this->accessValidator->checkWriteCapability($idSite); $path = TagManager::getAbsolutePathToContainerDirectory(); @@ -316,6 +325,7 @@ private function renderManageContainerTemplate($template, $variables = array()) public function exportContainerVersion() { $this->checkSitePermission(); + $this->accessValidator->checkUserHasTagManagerAccess($this->idSite); $jsonCallback = Common::getRequestVar('callback', false); diff --git a/Input/AccessValidator.php b/Input/AccessValidator.php index 582b028e..c5b03aa7 100644 --- a/Input/AccessValidator.php +++ b/Input/AccessValidator.php @@ -8,6 +8,8 @@ namespace Piwik\Plugins\TagManager\Input; +use Piwik\Container\StaticContainer; +use Piwik\NoAccessException; use Piwik\Plugins\TagManager\Access\Capability\PublishLiveContainer; use Piwik\Plugins\TagManager\Access\Capability\TagManagerWrite; use Piwik\Plugins\TagManager\Access\Capability\UseCustomTemplates; @@ -31,18 +33,21 @@ public function checkViewPermission($idSite) { $this->checkSiteExists($idSite); Piwik::checkUserHasViewAccess($idSite); + $this->checkUserHasTagManagerAccess($idSite); } public function checkWriteCapability($idSite) { $this->checkSiteExists($idSite); Piwik::checkUserHasCapability($idSite, TagManagerWrite::ID); + $this->checkUserHasTagManagerAccess($idSite); } public function checkPublishLiveEnvironmentCapability($idSite) { $this->checkSiteExists($idSite); Piwik::checkUserHasCapability($idSite, PublishLiveContainer::ID); + $this->checkUserHasTagManagerAccess($idSite); } public function checkUseCustomTemplatesCapability($idSite) @@ -59,6 +64,17 @@ public function checkUseCustomTemplatesCapability($idSite) } } + public function checkUserHasTagManagerAccess($idSite): void + { + // If the user has access, return before the exception is thrown + if (StaticContainer::get(SystemSettings::class)->doesCurrentUserHaveTagManagerAccess(intval($idSite))) { + return; + } + + $minimumRole = StaticContainer::get(SystemSettings::class)->restrictTagManagerAccess->getValue(); + throw new NoAccessException(Piwik::translate('General_ExceptionPrivilegeAccessWebsite', ["'{$minimumRole}'", $idSite])); + } + public function hasUseCustomTemplatesCapability($idSite) { try { diff --git a/Menu.php b/Menu.php index 4fb7bd98..290c47d7 100644 --- a/Menu.php +++ b/Menu.php @@ -30,6 +30,12 @@ public function __construct(AccessValidator $accessValidator) public function configureTopMenu(MenuTop $menu) { + // Check whether to show the MTM top menu. If not, simply return early + $idSite = \Piwik\Request::fromRequest()->getIntegerParameter('idSite', 0); + if (!StaticContainer::get(SystemSettings::class)->doesCurrentUserHaveTagManagerAccess($idSite)) { + return; + } + list($defaultAction, $defaultParams) = self::getDefaultAction(); if ($defaultAction) { $menu->addItem('TagManager_TagManager', null, $this->urlForAction($defaultAction, $defaultParams), $orderId = 30); diff --git a/SystemSettings.php b/SystemSettings.php index 2d220ac5..8f3a2a91 100644 --- a/SystemSettings.php +++ b/SystemSettings.php @@ -7,11 +7,13 @@ */ namespace Piwik\Plugins\TagManager; +use Piwik\Access; use Piwik\Container\StaticContainer; use Piwik\Date; use Piwik\Piwik; use Piwik\Plugins\TagManager\Context\BaseContext; use Piwik\Plugins\TagManager\Model\Environment; +use Piwik\Settings\Plugin\SystemSetting; use Piwik\Settings\Setting; use Piwik\Settings\FieldConfig; @@ -21,6 +23,16 @@ class SystemSettings extends \Piwik\Settings\Plugin\SystemSettings const CUSTOM_TEMPLATES_ADMIN = 'admin'; const CUSTOM_TEMPLATES_SUPERUSER = 'superuser'; + const USER_PERMISSON_LIST = [ + \Piwik\Access\Role\View::ID, + \Piwik\Access\Role\Write::ID, + \Piwik\Access\Role\Admin::ID, + self::CUSTOM_TEMPLATES_SUPERUSER + ]; + + /** @var Setting */ + public $restrictTagManagerAccess; + /** @var Setting */ public $restrictCustomTemplates; @@ -31,10 +43,75 @@ class SystemSettings extends \Piwik\Settings\Plugin\SystemSettings protected function init() { + $this->restrictTagManagerAccess = $this->createRestrictAccessSetting(); $this->restrictCustomTemplates = $this->createCustomTemplatesSetting(); $this->environments = $this->createEnvironmentsSetting(); } + private function createRestrictAccessSetting(): SystemSetting + { + return $this->makeSetting('restrictTagManagerAccess', \Piwik\Access\Role\View::ID, FieldConfig::TYPE_STRING, function (FieldConfig $field) { + $field->title = Piwik::translate('TagManager_SettingRestrictAccessTitle'); + $field->uiControl = FieldConfig::UI_CONTROL_SINGLE_SELECT; + $field->description = Piwik::translate('TagManager_SettingRestrictAccessDescription'); + $field->availableValues = [ + self::USER_PERMISSON_LIST[$this->getPermissionIndex('view')] => Piwik::translate('TagManager_SettingRestrictAccessView'), + self::USER_PERMISSON_LIST[$this->getPermissionIndex('write')] => Piwik::translate('TagManager_SettingRestrictAccessWrite'), + self::USER_PERMISSON_LIST[$this->getPermissionIndex('admin')] => Piwik::translate('TagManager_SettingRestrictAccessAdmin'), + self::USER_PERMISSON_LIST[$this->getPermissionIndex('superuser')] => Piwik::translate('TagManager_SettingRestrictAccessSuperUser') + ]; + }); + } + + private function getPermissionIndex(string $permission): int + { + $index = array_search($permission, self::USER_PERMISSON_LIST); + if ($index !== false) { + return $index; + } + + throw new \Exception('Permission \'' . $permission . '\' not found'); + } + + /** + * Check whether the currently logged-in user has access to MTM. This expects the site ID, but allows 0 for the + * Administration area, where there isn't necessarily a specific site selected. + * + * @param int $idSite ID of the site currently being viewed. 0 or nothing should be passed if in Administration area + * @return bool Whether the user has access to MTM + */ + public function doesCurrentUserHaveTagManagerAccess(int $idSite = 0): bool + { + // First check for superuser access, since the setting won't matter at that point + $access = StaticContainer::get(Access::class); + if ($access->hasSuperUserAccess()) { + return true; + } + + $settingValue = $this->restrictTagManagerAccess->getValue(); + + // We need to allow checks with no site ID since we might be in the Administration section + if ($idSite === 0) { + switch ($settingValue) { + case self::USER_PERMISSON_LIST[$this->getPermissionIndex('view')]: + return !empty($access->getSitesIdWithAtLeastViewAccess()); + case self::USER_PERMISSON_LIST[$this->getPermissionIndex('write')]: + return $access->isUserHasSomeWriteAccess(); + case self::USER_PERMISSON_LIST[$this->getPermissionIndex('admin')]: + return $access->isUserHasSomeAdminAccess(); + // Those should be the only available options, since we already checked for superuser + default: + return false; + } + } + + $role = $access->getRoleForSite($idSite); + $roleIndex = in_array($role, self::USER_PERMISSON_LIST) ? array_search($role, self::USER_PERMISSON_LIST) : 0; + $settingIndex = in_array($settingValue, self::USER_PERMISSON_LIST) ? array_search($settingValue, self::USER_PERMISSON_LIST) : 0; + + return $roleIndex >= $settingIndex; + } + private function createCustomTemplatesSetting() { return $this->makeSetting('restrictCustomTemplates', self::CUSTOM_TEMPLATES_ADMIN, FieldConfig::TYPE_STRING, function (FieldConfig $field) { diff --git a/TagManager.php b/TagManager.php index 9bd9b1f2..3c3cae1a 100644 --- a/TagManager.php +++ b/TagManager.php @@ -261,12 +261,23 @@ public function getQueryParametersToExclude(&$parametersToExclude) public function endTrackingCodePageTableOfContents(&$out) { + // Check whether to show the MTM code. If not, simply return early + if ($this->isAccessRestrictedForUser()) { + return; + } + $out .= '' . Piwik::translate('TagManager_TagManager') . ''; } public function addTagManagerCode(&$out) { Piwik::checkUserHasSomeViewAccess(); + + // Check whether to show the MTM code. If not, simply return early + if ($this->isAccessRestrictedForUser()) { + return; + } + $model = $this->getContainerModel(); $view = new View("@TagManager/trackingCode"); $view->action = Piwik::getAction(); @@ -277,6 +288,11 @@ public function addTagManagerCode(&$out) public function setTagManagerCode(&$out) { + // Check whether to show the MTM code. If not, simply return early + if ($this->isAccessRestrictedForUser()) { + return; + } + $newContent = '