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 = '

' . Piwik::translate('SitesManager_StepByStepGuide') . '

'; $this->addTagManagerCode($newContent); $out = $newContent; @@ -285,6 +301,12 @@ public function setTagManagerCode(&$out) public function embedReactTagManagerTrackingCode(&$out, SiteContentDetector $detector) { 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/trackingCodeReact"); $view->action = Piwik::getAction(); @@ -998,4 +1020,9 @@ public function getMessagesToWarnOnSiteRemoval(&$messages, $idSite) } } + private function isAccessRestrictedForUser(): bool + { + $idSite = \Piwik\Request::fromRequest()->getIntegerParameter('idSite', 0); + return !StaticContainer::get(SystemSettings::class)->doesCurrentUserHaveTagManagerAccess($idSite); + } } diff --git a/lang/en.json b/lang/en.json index 760e9800..41b0ba78 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1078,6 +1078,12 @@ "DeleteWebsiteExplanationLine2": "To save a copy of container configurations, click on the container name below and export the desired version:", "DeleteWebsiteExplanationLine3": "Click Confirm when you are ready to delete this website.", "ActivelySyncGtmDataLayerTitle": "Actively synchronise from the Google Tag Manager data layer", - "ActivelySyncGtmDataLayerDescription": "When enabled, any new values pushed to the Google Tag Manager data layer will be synchronised to the Matomo Tag Manager data layer." + "ActivelySyncGtmDataLayerDescription": "When enabled, any new values pushed to the Google Tag Manager data layer will be synchronised to the Matomo Tag Manager data layer.", + "SettingRestrictAccessTitle": "Restrict access to Matomo Tag Manager", + "SettingRestrictAccessDescription": "Define who can see the Matomo Tag Manager section. This can be helpful when you have many users with the View permission, or if your Tag Manager configuration is very complex.", + "SettingRestrictAccessView": "Users with at least view permission", + "SettingRestrictAccessWrite": "Users with at least write permission", + "SettingRestrictAccessAdmin": "Users with at least admin permission", + "SettingRestrictAccessSuperUser": "Users with at least superuser permission" } } diff --git a/tests/Integration/SystemSettingTest.php b/tests/Integration/SystemSettingTest.php index 7ff97d23..903d8580 100644 --- a/tests/Integration/SystemSettingTest.php +++ b/tests/Integration/SystemSettingTest.php @@ -8,9 +8,12 @@ namespace Piwik\Plugins\TagManager\tests\Integration; +use Piwik\Access; +use Piwik\Container\StaticContainer; use Piwik\Plugins\TagManager\Model\Environment; use Piwik\Plugins\TagManager\SystemSettings; use Piwik\Plugins\TagManager\tests\Framework\TestCase\IntegrationTestCase; +use Piwik\Tests\Framework\Mock\FakeAccess; /** * @group TagManager @@ -147,4 +150,102 @@ public function test_save_willNotFail() $this->settings->save(); } + public function testPermissionListValues() + { + $this->assertSame('view', SystemSettings::USER_PERMISSON_LIST[0]); + $this->assertSame('write', SystemSettings::USER_PERMISSON_LIST[1]); + $this->assertSame('admin', SystemSettings::USER_PERMISSON_LIST[2]); + $this->assertSame('superuser', SystemSettings::USER_PERMISSON_LIST[3]); + } + + public function testSaveRestrictTagManagerAccess() + { + $settingValue = SystemSettings::USER_PERMISSON_LIST[2]; + $this->settings->restrictTagManagerAccess->setValue($settingValue); + $this->settings->save(); + + $this->assertSame($settingValue, $this->settings->restrictTagManagerAccess->getValue()); + } + + /** + * @dataProvider getDoesCurrentUserHaveTagManagerAccessData + */ + public function testDoesCurrentUserHaveTagManagerAccess(?int $roleIndex, ?int $settingRoleIndex, bool $isSuperUser, bool $expected) + { + if ($settingRoleIndex !== null) { + $this->settings->restrictTagManagerAccess->setValue(SystemSettings::USER_PERMISSON_LIST[$settingRoleIndex]); + } + + if ($roleIndex !== null) { + $fakeAccess = new FakeAccess(); + switch ($roleIndex) { + case 0: + $fakeAccess->setIdSitesView([1, 2, 3]); + break; + case 1: + $fakeAccess->setIdSitesWrite([1, 2, 3]); + break; + case 2: + $fakeAccess->setIdSitesAdmin([1, 2, 3]); + break; + } + + StaticContainer::getContainer()->set('Piwik\Access', $fakeAccess); + } + + Access::getInstance()->setSuperUserAccess($isSuperUser); + + $this->assertSame($isSuperUser, Access::getInstance()->hasSuperUserAccess()); + + $roleName = $roleIndex === null ? 'None' : SystemSettings::USER_PERMISSON_LIST[$roleIndex]; + $settingMinRole = $settingRoleIndex === null ? 'None' : SystemSettings::USER_PERMISSON_LIST[$settingRoleIndex]; + $message = "Expected '{$expected}' for user role '{$roleName}', setting min role '{$settingMinRole}', and is superuser? '{$isSuperUser}'."; + $this->assertSame($expected, $this->settings->doesCurrentUserHaveTagManagerAccess(), $message); + } + + public function getDoesCurrentUserHaveTagManagerAccessData(): array + { + return [ + [null, null, false, false], + [null, null, true, true], + [0, null, true, true], + [0, null, true, true], + [1, null, true, true], + [1, null, true, true], + [2, null, true, true], + [2, null, true, true], + [null, 0, false, false], + [null, 0, true, true], + [0, 0, true, true], + [0, 0, true, true], + [1, 0, true, true], + [1, 0, true, true], + [2, 0, true, true], + [2, 0, true, true], + [null, 1, false, false], + [null, 1, true, true], + [0, 1, false, false], + [0, 1, true, true], + [1, 1, true, true], + [1, 1, true, true], + [2, 1, true, true], + [2, 1, true, true], + [null, 2, false, false], + [null, 2, true, true], + [0, 2, false, false], + [0, 2, true, true], + [1, 2, false, false], + [1, 2, true, true], + [2, 2, true, true], + [2, 2, true, true], + [null, 3, false, false], + [null, 3, true, true], + [0, 3, false, false], + [0, 3, true, true], + [1, 3, false, false], + [1, 3, true, true], + [2, 3, false, false], + [2, 3, true, true], + ]; + } } diff --git a/tests/UI/TagManager_spec.js b/tests/UI/TagManager_spec.js index faf0e560..bf919e7a 100644 --- a/tests/UI/TagManager_spec.js +++ b/tests/UI/TagManager_spec.js @@ -177,21 +177,57 @@ describe("TagManager", function () { await capture.modal(page, 'publish_with_content'); }); - it('should show the manage website screen', async function () { - const urlToTest = "?module=SitesManager&action=index&idSite=2&period=day&date=yesterday&showaddsite=false"; - await page.goto(urlToTest); - const pageElement = await page.$('.page'); - expect(await pageElement.screenshot()).to.matchImage('manageWebsites'); - }); - - it('should show the container detail when delete button is pressed', async function () { - const pageElement = await page.$('.page'); - await page.evaluate(function(){ - $('.card-content:eq(1) .icon-delete').click() - }); - await page.waitForTimeout(250); - await capture.modal(page, 'manageWebsitesDeleteAction'); - }); + it('should show the manage website screen', async function () { + const urlToTest = "?module=SitesManager&action=index&idSite=2&period=day&date=yesterday&showaddsite=false"; + await page.goto(urlToTest); + const pageElement = await page.$('.page'); + expect(await pageElement.screenshot()).to.matchImage('manageWebsites'); + }); + + it('should show the container detail when delete button is pressed', async function () { + const pageElement = await page.$('.page'); + await page.evaluate(function(){ + $('.card-content:eq(1) .icon-delete').click() + }); + await page.waitForTimeout(250); + await capture.modal(page, 'manageWebsitesDeleteAction'); + }); + + it("should display the MTM settings page", async function () { + await page.goto('?module=CoreAdminHome&action=generalSettings&idSite=1&period=day&date=yesterday#/TagManager'); + expect(await page.screenshotSelector('#TagManagerPluginSettings')).to.matchImage('settings_page'); + }); + + it("should be able to update restrict MTM access setting", async function () { + await page.evaluate(() => $('select[name="restrictTagManagerAccess"]').click()); + await page.evaluate(() => $('li:nth-child(2)').click()); + await page.evaluate(() => $('#TagManagerPluginSettings .pluginsSettingsSubmit').click()); + await page.type('.confirm-password-modal input[type=password]', superUserPassword); + await page.click('.confirm-password-modal .modal-close.btn'); + await page.waitForNetworkIdle(); + await page.mouse.move(-10, -10); + expect(await page.screenshotSelector('#TagManagerPluginSettings')).to.matchImage('update_restrict_setting'); + }); + + it('should fail to load MTM for view user', async function () { + permissions.setViewUser(); + await page.goto(generalParamsSite1 + urlBase + 'manageContainers'); + const bodyElement = await page.$('body'); + expect(await bodyElement.screenshot()).to.matchImage('view_access_restricted'); + }); + + it('should show MTM on tracking code page when user access is not restricted', async function () { + await page.goto("?idSite=1&module=CoreAdminHome&action=trackingCodeGenerator"); + await page.waitForNetworkIdle(); + expect(await page.screenshotSelector('div.pageWrap')).to.matchImage('tracking_code_normal'); + }); + it('should hide MTM from tracking code page when user access is restricted', async function () { + permissions.setViewUser(); + await page.goto("?idSite=1&module=CoreAdminHome&action=trackingCodeGenerator"); + await page.waitForNetworkIdle(); + + expect(await page.screenshotSelector('div.pageWrap')).to.matchImage('tracking_code_hidden'); + }); }); diff --git a/tests/UI/expected-screenshots/TagManager_settings_page.png b/tests/UI/expected-screenshots/TagManager_settings_page.png new file mode 100644 index 00000000..08021b07 --- /dev/null +++ b/tests/UI/expected-screenshots/TagManager_settings_page.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bc5e986cfd0bf80309463c7725c693ea4c7889b01247be59c1cab3c0206a6295 +size 134415 diff --git a/tests/UI/expected-screenshots/TagManager_tracking_code_hidden.png b/tests/UI/expected-screenshots/TagManager_tracking_code_hidden.png new file mode 100644 index 00000000..04b20463 --- /dev/null +++ b/tests/UI/expected-screenshots/TagManager_tracking_code_hidden.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6bd0f50017da8ccf1af4bdc567c30a11a03ae3b3db66723beb55d93a83e0c950 +size 365992 diff --git a/tests/UI/expected-screenshots/TagManager_tracking_code_normal.png b/tests/UI/expected-screenshots/TagManager_tracking_code_normal.png new file mode 100644 index 00000000..f8528827 --- /dev/null +++ b/tests/UI/expected-screenshots/TagManager_tracking_code_normal.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1ddfd0884e09e3e2c010d1c753ae3eeb1f2cc38ecc006bdd3c8acdacfd24f974 +size 418237 diff --git a/tests/UI/expected-screenshots/TagManager_update_restrict_setting.png b/tests/UI/expected-screenshots/TagManager_update_restrict_setting.png new file mode 100644 index 00000000..d4f0189e --- /dev/null +++ b/tests/UI/expected-screenshots/TagManager_update_restrict_setting.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fa5fa621b1fdb3e06ab1b241ae5ed7906099c28de2896e1d5d0034216f204484 +size 134420 diff --git a/tests/UI/expected-screenshots/TagManager_view_access_restricted.png b/tests/UI/expected-screenshots/TagManager_view_access_restricted.png new file mode 100644 index 00000000..55dd5bae --- /dev/null +++ b/tests/UI/expected-screenshots/TagManager_view_access_restricted.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e6f84fb4447ea73a2f5257b49031506e08df08e7ee8c660562c7f186498c3d21 +size 31001