diff options
author | Thomas Steur <tsteur@users.noreply.github.com> | 2018-07-18 07:47:13 +0300 |
---|---|---|
committer | diosmosis <diosmosis@users.noreply.github.com> | 2018-07-18 07:47:13 +0300 |
commit | 105e007721b5c0ea12ff2596d8d82c721021fb4e (patch) | |
tree | 558162844ba663781fdf0ec691642f0dc453e94e /plugins/UsersManager | |
parent | 74334d8d0908910ed3cc4a9a918436d9f9ccc3f6 (diff) |
Introducing a new role "write" and possibility to define capabilities (#13163)
* started working on some ACL concept
* acl implementation
* add category
* small tweaks
* more tweaks
* more api methods and fixes
* cache capabilities
* various enhancements, fixes, tweaks
* more tweaks
* added more tests and fixed some bugs
* fix parameter
* make sure to be BC
* make sure to be BC
* fix some tests
* more apis, translations, changelog entry, ...
* update db
* correct error message
* fix capabilities were not detected in tests
* directly access provider
* fix and add test
* JS api to check capabilities, better structure for capabilities in tests
* add ability to inject permissions
* apply review changes
* fix test
Diffstat (limited to 'plugins/UsersManager')
-rw-r--r-- | plugins/UsersManager/API.php | 298 | ||||
-rw-r--r-- | plugins/UsersManager/Model.php | 12 | ||||
-rw-r--r-- | plugins/UsersManager/UsersManager.php | 31 | ||||
-rw-r--r-- | plugins/UsersManager/lang/en.json | 9 | ||||
-rw-r--r-- | plugins/UsersManager/tests/Integration/APITest.php | 372 | ||||
-rw-r--r-- | plugins/UsersManager/tests/Integration/UsersManagerTest.php | 37 |
6 files changed, 711 insertions, 48 deletions
diff --git a/plugins/UsersManager/API.php b/plugins/UsersManager/API.php index f134d4647e..241595f571 100644 --- a/plugins/UsersManager/API.php +++ b/plugins/UsersManager/API.php @@ -52,16 +52,35 @@ class API extends \Piwik\Plugin\API */ private $userFilter; + /** + * @var Access\RolesProvider + */ + private $roleProvider; + + /** + * @var Access\CapabilitiesProvider + */ + private $capabilityProvider; + const PREFERENCE_DEFAULT_REPORT = 'defaultReport'; const PREFERENCE_DEFAULT_REPORT_DATE = 'defaultReportDate'; private static $instance = null; - public function __construct(Model $model, UserAccessFilter $filter, Password $password) + public function __construct(Model $model, UserAccessFilter $filter, Password $password, Access\RolesProvider $roleProvider = null, Access\CapabilitiesProvider $capabilityProvider = null) { $this->model = $model; $this->userFilter = $filter; $this->password = $password; + + if (!isset($roleProvider)) { + $roleProvider = StaticContainer::get('Piwik\Access\RolesProvider'); + } + if (!isset($capabilityProvider)) { + $capabilityProvider = StaticContainer::get('Piwik\Access\CapabilitiesProvider'); + } + $this->roleProvider = $roleProvider; + $this->capabilityProvider = $capabilityProvider; } /** @@ -93,6 +112,52 @@ class API extends \Piwik\Plugin\API } /** + * Get the list of all available roles. + * It does not return the super user role, and neither the "noaccess" role. + * @return array[] Returns an array containing information about each role + */ + public function getAvailableRoles() + { + Piwik::checkUserHasSomeAdminAccess(); + + $response = array(); + + foreach ($this->roleProvider->getAllRoles() as $role) { + $response[] = array( + 'id' => $role->getId(), + 'name' => $role->getName(), + 'description' => $role->getDescription(), + 'helpUrl' => $role->getHelpUrl() + ); + } + + return $response; + } + + /** + * Get the list of all available capabilities. + * @return array[] Returns an array containing information about each capability + */ + public function getAvailableCapabilities() + { + Piwik::checkUserHasSomeAdminAccess(); + + $response = array(); + + foreach ($this->capabilityProvider->getAllCapabilities() as $capability) { + $response[] = array( + 'id' => $capability->getId(), + 'name' => $capability->getName(), + 'description' => $capability->getDescription(), + 'helpUrl' => $capability->getHelpUrl(), + 'includedInRoles' => $capability->getIncludedInRoles() + ); + } + + return $response; + } + + /** * Sets a user preference * @param string $userLogin * @param string $preferenceName @@ -269,6 +334,17 @@ class API extends \Piwik\Plugin\API return $userSites; } + private function checkAccessType($access) + { + $roles = $this->roleProvider->getAllRoleIds(); + $capabilities = $this->capabilityProvider->getAllCapabilityIds(); + $list = array_merge($roles, $capabilities); + + if (!in_array($access, $list, true)) { + throw new Exception(Piwik::translate("UsersManager_ExceptionAccessValues", implode(", ", $list))); + } + } + /** * For each user, returns their access level for the given $idSite. * If a user doesn't have any access to the $idSite ('noaccess'), @@ -727,28 +803,201 @@ class API extends \Piwik\Plugin\API * If access = 'view' or 'admin' the current access level is deleted and updated with the new value. * * @param string $userLogin The user login - * @param string $access Access to grant. Must have one of the following value : noaccess, view, admin + * @param string|array $access Access to grant. Must have one of the following value : noaccess, view, write, admin. + * May also be an array to sent additional capabilities * @param int|array $idSites The array of idSites on which to apply the access level for the user. * If the value is "all" then we apply the access level to all the websites ID for which the current authentificated user has an 'admin' access. * * @throws Exception if the user doesn't exist * @throws Exception if the access parameter doesn't have a correct value * @throws Exception if any of the given website ID doesn't exist - * - * @return bool true on success */ public function setUserAccess($userLogin, $access, $idSites) { - $this->checkAccessType($access); + $idSites = $this->getIdSitesCheckAdminAccess($idSites); + + if ($userLogin === 'anonymous' && + (is_array($access) || !in_array($access, array('view', 'noaccess'), true))) { + throw new Exception(Piwik::translate("UsersManager_ExceptionAnonymousAccessNotPossible", array('noaccess', 'view'))); + } + + $roles = array(); + $capabilities = array(); + + if (is_array($access)) { + // we require one role, and optionally multiple capabilties + $roles = array(); + foreach ($access as $entry) { + if ($this->roleProvider->isValidRole($entry)) { + $roles[] = $entry; + } else { + $this->checkAccessType($entry); + $capabilities[] = $entry; + } + } + + if (count($roles) < 1) { + $ids = implode(', ', $this->roleProvider->getAllRoleIds()); + throw new Exception(Piwik::translate('UsersManager_ExceptionNoRoleSet', $ids)); + } + + if (count($roles) > 1) { + $ids = implode(', ', $this->roleProvider->getAllRoleIds()); + throw new Exception(Piwik::translate('UsersManager_ExceptionMultipleRoleSet', $ids)); + } + + } else { + // as only one access is set, we require it to be a role or "noaccess"... + if ($access !== 'noaccess') { + $this->roleProvider->checkValidRole($access); + $roles[] = $access; + } + } + + $this->checkUserExists($userLogin); + $this->checkUserHasNotSuperUserAccess($userLogin); + + $this->model->deleteUserAccess($userLogin, $idSites); + + if ($access === 'noaccess') { + // if the access is noaccess then we don't save it as this is the default value + // when no access are specified + Piwik::postEvent('UsersManager.removeSiteAccess', array($userLogin, $idSites)); + } else { + $role = array_shift($roles); + $this->model->addUserAccess($userLogin, $role, $idSites); + } + + if (!empty($capabilities)) { + $this->addCapabilities($userLogin, $capabilities, $idSites); + } + + // we reload the access list which doesn't yet take in consideration this new user access + $this->reloadPermissions(); + } + + /** + * Adds the given capabilities to the given user for the given sites. + * The capability will be added only when the user also has access to a site, for example View, Write, or Admin. + * Note: You can neither add any capability to a super user, nor to the anonymous user. + * Note: If the user has assigned a role which already grants the given capability, the capability will not be added in + * the backend. + * + * @param string $userLogin The user login + * @param string|string[] $capabilities To fetch a list of available capabilities call "UsersManager.getAvailableCapabilities". + * @param int|int[] $idSites + * @throws Exception + */ + public function addCapabilities($userLogin, $capabilities, $idSites) + { + $idSites = $this->getIdSitesCheckAdminAccess($idSites); + + if ($userLogin == 'anonymous') { + throw new Exception(Piwik::translate("UsersManager_ExceptionAnonymousNoCapabilities")); + } + $this->checkUserExists($userLogin); $this->checkUserHasNotSuperUserAccess($userLogin); - if ($userLogin == 'anonymous' - && $access == 'admin' - ) { - throw new Exception(Piwik::translate("UsersManager_ExceptionAdminAnonymous")); + if (!is_array($capabilities)){ + $capabilities = array($capabilities); + } + + foreach ($capabilities as $entry) { + $this->capabilityProvider->checkValidCapability($entry); + } + + list($sitesIdWithRole, $sitesIdWithCapability) = $this->getRolesAndCapabilitiesForLogin($userLogin); + + foreach ($capabilities as $entry) { + $cap = $this->capabilityProvider->getCapability($entry); + + foreach ($idSites as $idSite) { + $hasRole = array_key_exists($idSite, $sitesIdWithRole); + $hasCapabilityAlready = array_key_exists($idSite, $sitesIdWithCapability) && in_array($entry, $sitesIdWithCapability[$idSite], true); + + // so far we are adding the capability only to people that also have a role... + // to be defined how to handle this... eg we are not throwing an exception currently + // as it might be used as part of bulk action etc. + if ($hasRole && !$hasCapabilityAlready) { + $theRole = $sitesIdWithRole[$idSite]; + if ($cap->hasRoleCapability($theRole)) { + // todo this behaviour needs to be defined... + // when the role already supports this capability we do not add it again + continue; + } + + $this->model->addUserAccess($userLogin, $entry, array($idSite)); + } + } + + } + + // we reload the access list which doesn't yet take in consideration this new user access + $this->reloadPermissions(); + } + + private function getRolesAndCapabilitiesForLogin($userLogin) + { + $sites = $this->model->getSitesAccessFromUser($userLogin); + $roleIds = $this->roleProvider->getAllRoleIds(); + + $sitesIdWithRole = array(); + $sitesIdWithCapability = array(); + foreach ($sites as $site) { + if (in_array($site['access'], $roleIds, true)) { + $sitesIdWithRole[(int) $site['site']] = $site['access']; + } else { + if (!isset($sitesIdWithCapability[(int) $site['site']])) { + $sitesIdWithCapability[(int) $site['site']] = array(); + } + $sitesIdWithCapability[(int) $site['site']][] = $site['access']; + } + } + return [$sitesIdWithRole, $sitesIdWithCapability]; + } + + /** + * Removes the given capabilities from the given user for the given sites. + * The capability will be only removed if it is actually granted as a separate capability. If the user has a role + * that includes a specific capability, for example "Admin", then the capability will not be removed because the + * assigned role will always include this capability. + * + * @param string $userLogin The user login + * @param string|string[] $capabilities To fetch a list of available capabilities call "UsersManager.getAvailableCapabilities". + * @param int|int[] $idSites + * @throws Exception + */ + public function removeCapabilities($userLogin, $capabilities, $idSites) + { + $idSites = $this->getIdSitesCheckAdminAccess($idSites); + + $this->checkUserExists($userLogin); + + if (!is_array($capabilities)){ + $capabilities = array($capabilities); + } + + foreach ($capabilities as $capability) { + $this->capabilityProvider->checkValidCapability($capability); + } + + foreach ($capabilities as $capability) { + $this->model->removeUserAccess($userLogin, $capability, $idSites); } + // we reload the access list which doesn't yet take in consideration this removed capability + $this->reloadPermissions(); + } + + private function reloadPermissions() + { + Access::getInstance()->reloadAccess(); + Cache::deleteTrackerCache(); + } + + private function getIdSitesCheckAdminAccess($idSites) + { // in case idSites is all we grant access to all the websites on which the current connected user has an 'admin' access if ($idSites === 'all') { $idSites = \Piwik\Plugins\SitesManager\API::getInstance()->getSitesIdWithAdminAccess(); @@ -760,27 +1009,16 @@ class API extends \Piwik\Plugin\API if (empty($idSites)) { throw new Exception('Specify at least one website ID in &idSites='); } + // it is possible to set user access on websites only for the websites admin // basically an admin can give the view or the admin access to any user for the websites they manage Piwik::checkUserHasAdminAccess($idSites); - $this->model->deleteUserAccess($userLogin, $idSites); - - // if the access is noaccess then we don't save it as this is the default value - // when no access are specified - if ($access != 'noaccess') { - $this->model->addUserAccess($userLogin, $access, $idSites); - } else { - if (!empty($idSites) && !is_array($idSites)) { - $idSites = array($idSites); - } - - Piwik::postEvent('UsersManager.removeSiteAccess', array($userLogin, $idSites)); + if (!is_array($idSites)) { + $idSites = array($idSites); } - // we reload the access list which doesn't yet take in consideration this new user access - Access::getInstance()->reloadAccess(); - Cache::deleteTrackerCache(); + return $idSites; } /** @@ -823,18 +1061,6 @@ class API extends \Piwik\Plugin\API } } - private function checkAccessType($access) - { - $accessList = Access::getListAccess(); - - // do not allow to set the superUser access - unset($accessList[array_search("superuser", $accessList)]); - - if (!in_array($access, $accessList)) { - throw new Exception(Piwik::translate("UsersManager_ExceptionAccessValues", implode(", ", $accessList))); - } - } - private function isUserTheOnlyUserHavingSuperUserAccess($userLogin) { $superUsers = $this->getUsersHavingSuperUserAccess(); diff --git a/plugins/UsersManager/Model.php b/plugins/UsersManager/Model.php index a45aebaf2d..d33c808c48 100644 --- a/plugins/UsersManager/Model.php +++ b/plugins/UsersManager/Model.php @@ -291,6 +291,18 @@ class Model } } + public function removeUserAccess($userLogin, $access, $idSites) + { + $db = $this->getDb(); + + $table = Common::prefixTable("access"); + + foreach ($idSites as $idsite) { + $bind = array($userLogin, $idsite, $access); + $db->query("DELETE FROM " . $table . " WHERE login = ? and idsite = ? and access = ?", $bind); + } + } + public function deleteUserOnly($userLogin) { $db = $this->getDb(); diff --git a/plugins/UsersManager/UsersManager.php b/plugins/UsersManager/UsersManager.php index 95620a6182..4f699aab5f 100644 --- a/plugins/UsersManager/UsersManager.php +++ b/plugins/UsersManager/UsersManager.php @@ -9,7 +9,10 @@ namespace Piwik\Plugins\UsersManager; use Exception; +use Piwik\Access\Role\Admin; +use Piwik\Access\Role\Write; use Piwik\API\Request; +use Piwik\Common; use Piwik\Option; use Piwik\Piwik; use Piwik\Plugins\CoreHome\SystemSummary; @@ -69,22 +72,30 @@ class UsersManager extends \Piwik\Plugin */ public function recordAdminUsersInCache(&$attributes, $idSite) { - // add the 'hosts' entry in the website array $model = new Model(); - $logins = $model->getUsersLoginWithSiteAccess($idSite, 'admin'); + $adminLogins = $model->getUsersLoginWithSiteAccess($idSite, Admin::ID); + $writeLogins = $model->getUsersLoginWithSiteAccess($idSite, Write::ID); - if (empty($logins)) { - return; - } + $attributes['tracking_token_auth'] = array(); - $users = $model->getUsers($logins); + if (!empty($adminLogins)) { + $users = $model->getUsers($adminLogins); + foreach ($users as $user) { + $attributes['tracking_token_auth'][] = self::hashTrackingToken($user['token_auth'], $idSite); + } + } - $tokens = array(); - foreach ($users as $user) { - $tokens[] = $user['token_auth']; + if (!empty($writeLogins)) { + $users = $model->getUsers($writeLogins); + foreach ($users as $user) { + $attributes['tracking_token_auth'][] = self::hashTrackingToken($user['token_auth'], $idSite); + } } + } - $attributes['admin_token_auth'] = $tokens; + public static function hashTrackingToken($tokenAuth, $idSite) + { + return sha1($idSite . $tokenAuth . SettingsPiwik::getSalt()); } public function getCronArchiveTokenAuth(&$tokens) diff --git a/plugins/UsersManager/lang/en.json b/plugins/UsersManager/lang/en.json index 90112e56ba..158c12e225 100644 --- a/plugins/UsersManager/lang/en.json +++ b/plugins/UsersManager/lang/en.json @@ -19,7 +19,10 @@ "EmailYourAdministrator": "%1$sE-mail your administrator about this problem%2$s.", "EnterUsernameOrEmail": "Enter a username or email address", "ExceptionAccessValues": "The parameter access must have one of the following values: [ %s ]", - "ExceptionAdminAnonymous": "You cannot grant 'admin' access to the 'anonymous' user.", + "ExceptionNoRoleSet": "No role is set but one of these needs to be set: %s", + "ExceptionMultipleRoleSet": "Only one role can be set but multiple have been set. Use only one of: %s", + "ExceptionAnonymousNoCapabilities": "You cannot grant any capability to the 'anonymous' user.", + "ExceptionAnonymousAccessNotPossible": "You can only set access %1$s or %2$s access to the 'anonymous' user.", "ExceptionDeleteDoesNotExist": "User '%s' doesn't exist therefore it can't be deleted.", "ExceptionDeleteOnlyUserWithSuperUserAccess": "Deleting user '%s' is not possible.", "ExceptionEditAnonymous": "The anonymous user cannot be edited or deleted. It is used by Matomo to define a user that has not logged in yet. For example, you can make your statistics public by granting the 'view' access to the 'anonymous' user.", @@ -55,8 +58,12 @@ "NoUsersExist": "There are no users yet.", "PluginDescription": "Users Management lets you add new users, edit existing users and give them access to view or administrate websites. ", "PrivAdmin": "Admin", + "PrivAdminDescription": "Users with this role can manage a website and give other users access to the website. They can also do everything the %s role can do.", + "PrivWrite": "Write", + "PrivWriteDescription": "Users with this role can view all content plus create, manage and delete entities such as Goals, Funnels, Heatmaps, Session Recordings and Forms for this website.", "PrivNone": "No access", "PrivView": "View", + "PrivViewDescription": "A user with this role can view all reports.", "RemoveUserAccess":"Remove access for '%1$s' for %2$s.", "ReportDateToLoadByDefault": "Report date to load by default", "ReportToLoadByDefault": "Report to load by default", diff --git a/plugins/UsersManager/tests/Integration/APITest.php b/plugins/UsersManager/tests/Integration/APITest.php index 91f8e0f49f..4c23d5f7f0 100644 --- a/plugins/UsersManager/tests/Integration/APITest.php +++ b/plugins/UsersManager/tests/Integration/APITest.php @@ -8,14 +8,114 @@ namespace Piwik\Plugins\UsersManager\tests; +use Piwik\Access\Role\View; +use Piwik\Access\Role\Write; use Piwik\Auth\Password; use Piwik\Option; use Piwik\Piwik; use Piwik\Plugins\UsersManager\API; +use Piwik\Plugins\UsersManager\Model; use Piwik\Plugins\UsersManager\UsersManager; use Piwik\Tests\Framework\Fixture; use Piwik\Tests\Framework\Mock\FakeAccess; use Piwik\Tests\Framework\TestCase\IntegrationTestCase; +use Piwik\Access\Role\Admin; +use Piwik\Access\Capability; + +class TestCap1 extends Capability +{ + const ID = 'test_cap1'; + + public function getId() + { + return self::ID; + } + + public function getCategory() + { + return 'Test'; + } + + public function getName() + { + return 'Cap1'; + } + + public function getDescription() + { + return ''; + } + + public function getIncludedInRoles() + { + return array( + Admin::ID + ); + } +} + +class TestCap2 extends Capability +{ + const ID = 'test_cap2'; + + public function getId() + { + return self::ID; + } + + public function getCategory() + { + return 'Test'; + } + + public function getName() + { + return 'Cap2'; + } + + public function getDescription() + { + return ''; + } + + public function getIncludedInRoles() + { + return array( + Write::ID, Admin::ID + ); + } +} + +class TestCap3 extends Capability +{ + const ID = 'test_cap3'; + + public function getId() + { + return self::ID; + } + + public function getCategory() + { + return 'Test'; + } + + public function getName() + { + return 'Cap3'; + } + + public function getDescription() + { + return ''; + } + + public function getIncludedInRoles() + { + return array(Admin::ID); + } +} + /** * @group UsersManager @@ -28,6 +128,11 @@ class APITest extends IntegrationTestCase * @var API */ private $api; + + /** + * @var Model + */ + private $model; private $login = 'userLogin'; @@ -36,6 +141,7 @@ class APITest extends IntegrationTestCase parent::setUp(); $this->api = API::getInstance(); + $this->model = new Model(); FakeAccess::$superUser = true; @@ -224,6 +330,263 @@ class APITest extends IntegrationTestCase $this->assertEquals($expected, $access); } + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionMultipleRoleSet + */ + public function test_setUserAccess_MultipleRolesCannotBeSet() + { + $this->api->setUserAccess($this->login, array('view', 'admin'), array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionNoRoleSet + */ + public function test_setUserAccess_NeedsAtLeastOneRole() + { + $this->api->setUserAccess($this->login, array(TestCap2::ID), array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionAccessValues + */ + public function test_setUserAccess_NeedsAtLeastOneRoleAsString() + { + $this->api->setUserAccess($this->login, TestCap2::ID, array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionAccessValues + */ + public function test_setUserAccess_InvalidCapability() + { + $this->api->setUserAccess($this->login, array('admin', 'foobar'), array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionNoRoleSet + */ + public function test_setUserAccess_NeedsAtLeastOneRoleNoneGiven() + { + $this->api->setUserAccess($this->login, array(), array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionAnonymousAccessNotPossible + */ + public function test_setUserAccess_CannotSetAdminToAnonymous() + { + $this->api->setUserAccess('anonymous', 'admin', array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionAnonymousAccessNotPossible + */ + public function test_setUserAccess_CannotSetWriteToAnonymous() + { + $this->api->setUserAccess('anonymous', 'write', array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionUserDoesNotExist + */ + public function test_setUserAccess_UserDoesNotExist() + { + $this->api->setUserAccess('foobar', Admin::ID, array(1)); + } + + public function test_setUserAccess_SetRoleAndCapabilities() + { + $access = array(TestCap2::ID, View::ID, TestCap3::ID); + $this->api->setUserAccess($this->login, $access, array(1)); + + $access = $this->model->getSitesAccessFromUser($this->login); + + $expected = array( + array('site' => '1', 'access' => 'view'), + array('site' => '1', 'access' => TestCap2::ID), + array('site' => '1', 'access' => TestCap3::ID), + ); + $this->assertEquals($expected, $access); + } + + public function test_setUserAccess_SetRoleAsString() + { + $this->api->setUserAccess($this->login, View::ID, array(1)); + + $access = $this->model->getSitesAccessFromUser($this->login); + $this->assertEquals(array(array('site' => '1', 'access' => 'view')), $access); + } + + public function test_setUserAccess_SetRoleAsArray() + { + $this->api->setUserAccess($this->login, array(View::ID), array(1)); + + $access = $this->model->getSitesAccessFromUser($this->login); + $this->assertEquals(array(array('site' => '1', 'access' => 'view')), $access); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionAccessValues + */ + public function test_addCapabilities_failsWhenNotCapabilityIsGivenAsString() + { + $this->api->addCapabilities($this->login, View::ID, array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionAccessValues + */ + public function test_addCapabilities_failsWhenNotCapabilityIsGivenAsArray() + { + $this->api->addCapabilities($this->login, array(TestCap2::ID, View::ID), array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionUserDoesNotExist + */ + public function test_addCapabilities_failsWhenUserDoesNotExist() + { + $this->api->addCapabilities('foobar', array(TestCap2::ID), array(1)); + } + + public function test_addCapabilities_DoesNotAddSameCapabilityTwice() + { + $addAccess = array(TestCap2::ID, View::ID, TestCap3::ID); + $this->api->setUserAccess($this->login, $addAccess, array(1)); + + $access = $this->model->getSitesAccessFromUser($this->login); + + $expected = array( + array('site' => '1', 'access' => 'view'), + array('site' => '1', 'access' => TestCap2::ID), + array('site' => '1', 'access' => TestCap3::ID), + ); + $this->assertEquals($expected, $access); + + $this->api->addCapabilities($this->login, array(TestCap2::ID, TestCap3::ID), array(1)); + + $access = $this->model->getSitesAccessFromUser($this->login); + $this->assertEquals($expected, $access); + + $this->api->addCapabilities($this->login, array(TestCap2::ID, TestCap1::ID, TestCap3::ID), array(1)); + + $expected[] = array('site' => '1', 'access' => TestCap1::ID); + $access = $this->model->getSitesAccessFromUser($this->login); + $this->assertEquals($expected, $access); + } + + public function test_addCapabilities_DoesNotAddCapabilityToUserWithNoRole() + { + $access = $this->model->getSitesAccessFromUser($this->login); + + $this->assertEquals(array(), $access); + + $this->api->addCapabilities($this->login, array(TestCap2::ID, TestCap3::ID), array(1)); + + $this->assertEquals(array(), $access); + } + + public function test_addCapabilities_DoesNotAddCapabilitiesWhichAreIncludedInRoleAlready() + { + $this->api->setUserAccess($this->login, Write::ID, array(1)); + + $access = $this->model->getSitesAccessFromUser($this->login); + + $expected = array( + array('site' => '1', 'access' => 'write'), + ); + $this->assertEquals($expected, $access); + + $this->api->addCapabilities($this->login, array(TestCap2::ID, TestCap3::ID), array(1)); + + $expected[] = array('site' => '1', 'access' => TestCap3::ID); + $access = $this->model->getSitesAccessFromUser($this->login); + + // did not add TestCap2 + $this->assertEquals($expected, $access); + } + + public function test_addCapabilities_DoesAddCapabilitiesWhichAreNotIncludedInRoleYetAlready() + { + $this->api->setUserAccess($this->login, Admin::ID, array(1)); + + $access = $this->model->getSitesAccessFromUser($this->login); + + $expected = array( + array('site' => '1', 'access' => 'admin'), + ); + $this->assertEquals($expected, $access); + + $this->api->addCapabilities($this->login, array(TestCap2::ID, TestCap1::ID, TestCap3::ID), array(1)); + + $access = $this->model->getSitesAccessFromUser($this->login); + $this->assertEquals($expected, $access); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionAccessValues + */ + public function test_removeCapabilities_failsWhenNotCapabilityIsGivenAsString() + { + $this->api->removeCapabilities($this->login, View::ID, array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionAccessValues + */ + public function test_removeCapabilities_failsWhenNotCapabilityIsGivenAsArray() + { + $this->api->removeCapabilities($this->login, array(TestCap2::ID, View::ID), array(1)); + } + + /** + * @expectedException \Exception + * @expectedExceptionMessage UsersManager_ExceptionUserDoesNotExist + */ + public function test_removeCapabilities_failsWhenUserDoesNotExist() + { + $this->api->removeCapabilities('foobar', array(TestCap2::ID), array(1)); + } + + public function test_removeCapabilities() + { + $addAccess = array(View::ID, TestCap2::ID, TestCap3::ID, TestCap1::ID); + $this->api->setUserAccess($this->login, $addAccess, array(1)); + + $access = $this->getAccessInSite($this->login, 1); + $this->assertEquals($addAccess, $access); + + $this->api->removeCapabilities($this->login, array(TestCap3::ID, TestCap2::ID), 1); + + $access = $this->getAccessInSite($this->login, 1); + $this->assertEquals(array(View::ID, TestCap1::ID), $access); + } + + private function getAccessInSite($login, $idSite) + { + $access = $this->model->getSitesAccessFromUser($login); + $ids = array(); + foreach ($access as $entry) { + if ($entry['site'] == $idSite) { + $ids[] = $entry['access']; + } + } + return $ids; + } + private function getPreferenceId($preferenceName) { return $this->login . '_' . $preferenceName; @@ -232,7 +595,14 @@ class APITest extends IntegrationTestCase public function provideContainerConfig() { return array( - 'Piwik\Access' => new FakeAccess() + 'Piwik\Access' => new FakeAccess(), + 'observers.global' => \DI\add([ + ['Access.Capability.addCapabilities', function (&$capabilities) { + $capabilities[] = new TestCap1(); + $capabilities[] = new TestCap2(); + $capabilities[] = new TestCap3(); + }], + ]), ); } } diff --git a/plugins/UsersManager/tests/Integration/UsersManagerTest.php b/plugins/UsersManager/tests/Integration/UsersManagerTest.php index 4a849cb155..790387ce8b 100644 --- a/plugins/UsersManager/tests/Integration/UsersManagerTest.php +++ b/plugins/UsersManager/tests/Integration/UsersManagerTest.php @@ -480,6 +480,7 @@ class UsersManagerTest extends IntegrationTestCase */ public function testSetUserAccessNoLogin() { + FakeAccess::clearAccess($superUser = false, $admin =array(1), $view = array()); $this->api->setUserAccess("nologin", "view", 1); } @@ -490,6 +491,7 @@ class UsersManagerTest extends IntegrationTestCase public function testSetUserAccessWrongAccessSpecified() { $this->api->addUser("gegg4564eqgeqag", "geqgegagae", "tegst@tesgt.com", "alias"); + FakeAccess::clearAccess($superUser = false, $admin =array(1), $view = array()); $this->api->setUserAccess("gegg4564eqgeqag", "viewnotknown", 1); } @@ -500,6 +502,7 @@ class UsersManagerTest extends IntegrationTestCase public function testSetUserAccess_ShouldFail_SuperUserAccessIsNotAllowed() { $this->api->addUser("gegg4564eqgeqag", "geqgegagae", "tegst@tesgt.com", "alias"); + FakeAccess::clearAccess($superUser = false, $admin =array(1), $view = array()); $this->api->setUserAccess("gegg4564eqgeqag", "superuser", 1); } @@ -509,6 +512,7 @@ class UsersManagerTest extends IntegrationTestCase */ public function testSetUserAccess_ShouldFail_IfLoginIsConfigSuperUserLogin() { + FakeAccess::clearAccess($superUser = false, $admin =array(1), $view = array()); $this->api->setUserAccess('superusertest', 'view', 1); } @@ -521,6 +525,7 @@ class UsersManagerTest extends IntegrationTestCase $this->api->addUser("gegg4564eqgeqag", "geqgegagae", "tegst@tesgt.com", "alias"); $this->api->setSuperUserAccess('gegg4564eqgeqag', true); + FakeAccess::clearAccess($superUser = false, $idSitesAdmin = array(1)); $this->api->setUserAccess('gegg4564eqgeqag', 'view', 1); } @@ -969,6 +974,38 @@ class UsersManagerTest extends IntegrationTestCase $this->assertEquals('yesterday', $this->api->getUserPreference('someUser', $defaultReportDatePref)); } + public function testGetAvailableRoles() + { + $this->addSites(1); + $roles = $this->api->getAvailableRoles(); + $expected = array( + array ( + 'id' => 'view', + 'name' => 'UsersManager_PrivView', + 'description' => 'UsersManager_PrivViewDescription', + 'helpUrl' => 'https://matomo.org/faq/general/faq_70/' + ), array ( + 'id' => 'write', + 'name' => 'UsersManager_PrivWrite', + 'description' => 'UsersManager_PrivWriteDescription', + 'helpUrl' => '' + ), + array ( + 'id' => 'admin', + 'name' => 'UsersManager_PrivAdmin', + 'description' => 'UsersManager_PrivAdminDescription', + 'helpUrl' => 'https://matomo.org/faq/general/faq_69/', + ) + ); + $this->assertEquals($expected, $roles); + } + + public function testGetAvailableCapabilities() + { + $this->addSites(1); + $this->assertSame(array(), $this->api->getAvailableCapabilities()); + } + private function addSites($numberOfSites) { $idSites = array(); |