summaryrefslogtreecommitdiffstats
path: root/modules-available/permissionmanager/inc
diff options
context:
space:
mode:
Diffstat (limited to 'modules-available/permissionmanager/inc')
-rw-r--r--modules-available/permissionmanager/inc/getpermissiondata.inc.php55
-rw-r--r--modules-available/permissionmanager/inc/permissiondbupdate.inc.php110
-rw-r--r--modules-available/permissionmanager/inc/permissionutil.inc.php225
3 files changed, 292 insertions, 98 deletions
diff --git a/modules-available/permissionmanager/inc/getpermissiondata.inc.php b/modules-available/permissionmanager/inc/getpermissiondata.inc.php
index 982fa0b7..660c94ae 100644
--- a/modules-available/permissionmanager/inc/getpermissiondata.inc.php
+++ b/modules-available/permissionmanager/inc/getpermissiondata.inc.php
@@ -1,27 +1,32 @@
<?php
-class GetPermissionData {
+class GetPermissionData
+{
+
+ const WITH_USER_COUNT = 1;
+ const WITH_LOCATION_COUNT = 2;
/**
* Get data for all users.
*
* @return array array of users (each with userid, username and roles (each with roleid and rolename))
*/
- public static function getUserData() {
+ public static function getUserData()
+ {
$res = Database::simpleQuery("SELECT user.userid AS userid, user.login AS login, role.rolename AS rolename, role.roleid AS roleid
FROM user
- LEFT JOIN user_x_role ON user.userid = user_x_role.userid
- LEFT JOIN role ON user_x_role.roleid = role.roleid
+ LEFT JOIN role_x_user ON user.userid = role_x_user.userid
+ LEFT JOIN role ON role_x_user.roleid = role.roleid
");
- $userdata= array();
+ $userdata = array();
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $userdata[$row['userid'].' '.$row['login']][] = array(
+ $userdata[$row['userid'] . ' ' . $row['login']][] = array(
'roleid' => $row['roleid'],
'rolename' => $row['rolename']
);
}
$data = array();
- foreach($userdata AS $user => $roles) {
+ foreach ($userdata AS $user => $roles) {
$user = explode(" ", $user, 2);
$data[] = array(
'userid' => $user[0],
@@ -37,8 +42,9 @@ class GetPermissionData {
*
* @return array array of locations (each including the roles that have permissions for them)
*/
- public static function getLocationData() {
- $res = Database::simpleQuery("SELECT role.roleid as roleid, rolename, GROUP_CONCAT(COALESCE(locationid, 0)) AS locationids FROM role
+ public static function getLocationData()
+ {
+ $res = Database::simpleQuery("SELECT role.roleid AS roleid, rolename, GROUP_CONCAT(COALESCE(locationid, 0)) AS locationids FROM role
INNER JOIN role_x_location ON role.roleid = role_x_location.roleid GROUP BY roleid ORDER BY rolename ASC");
$locations = Location::getLocations(0, 0, false, true);
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
@@ -61,18 +67,26 @@ class GetPermissionData {
/**
* Get all roles.
*
+ * @param int $flags Bitmask specifying additional data to fetch (WITH_* constants of this class)
* @return array array roles (each with roleid and rolename)
*/
- public static function getRoles() {
- $res = Database::simpleQuery("SELECT roleid, rolename FROM role ORDER BY rolename ASC");
- $data = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $data[] = array(
- 'roleid' => $row['roleid'],
- 'rolename' => $row['rolename']
- );
+ public static function getRoles($flags = 0)
+ {
+ $cols = $joins = '';
+ if ($flags & self::WITH_USER_COUNT) {
+ $cols .= ', Count(DISTINCT rxu.userid) AS users';
+ $joins .= ' LEFT JOIN role_x_user rxu ON (r.roleid = rxu.roleid)';
}
- return $data;
+ if ($flags & self::WITH_LOCATION_COUNT) {
+ $cols .= ', Count(DISTINCT rxl.locationid) AS locations';
+ $joins .= ' LEFT JOIN role_x_location rxl ON (r.roleid = rxl.roleid)';
+ }
+ if (!empty($joins)) {
+ $joins .= ' GROUP BY r.roleid';
+ }
+ return Database::queryAll("SELECT r.roleid, r.rolename, r.roledescription $cols FROM role r
+ $joins
+ ORDER BY rolename ASC");
}
/**
@@ -81,8 +95,9 @@ class GetPermissionData {
* @param string $roleid id of the role
* @return array array containing an array of permissions and an array of locations
*/
- public static function getRoleData($roleid) {
- $query = "SELECT roleid, rolename FROM role WHERE roleid = :roleid";
+ public static function getRoleData($roleid)
+ {
+ $query = "SELECT roleid, rolename, roledescription FROM role WHERE roleid = :roleid";
$data = Database::queryFirst($query, array("roleid" => $roleid));
$query = "SELECT roleid, locationid FROM role_x_location WHERE roleid = :roleid";
$res = Database::simpleQuery($query, array("roleid" => $roleid));
diff --git a/modules-available/permissionmanager/inc/permissiondbupdate.inc.php b/modules-available/permissionmanager/inc/permissiondbupdate.inc.php
index ffe5fac0..0cd89b3a 100644
--- a/modules-available/permissionmanager/inc/permissiondbupdate.inc.php
+++ b/modules-available/permissionmanager/inc/permissiondbupdate.inc.php
@@ -1,67 +1,107 @@
<?php
-class PermissionDbUpdate {
+class PermissionDbUpdate
+{
/**
- * Insert all user/role combinations into the user_x_role table.
+ * Insert all user/role combinations into the role_x_user table.
*
- * @param array $users userids
- * @param array $roles roleids
+ * @param int[] $users userids
+ * @param int[] $roles roleids
*/
- public static function addRoleToUser($users, $roles) {
- $query = "INSERT IGNORE INTO user_x_role (userid, roleid) VALUES (:userid, :roleid)";
- foreach($users AS $userid) {
+ public static function addRoleToUser($users, $roles)
+ {
+ if (empty($users) || empty($roles))
+ return 0;
+ $arg = array();
+ foreach ($users AS $userid) {
foreach ($roles AS $roleid) {
- Database::exec($query, array("userid" => $userid, "roleid" => $roleid));
+ $arg[] = compact('userid', 'roleid');
}
}
+ return Database::exec("INSERT IGNORE INTO role_x_user (userid, roleid) VALUES :arg",
+ ['arg' => $arg]);
}
/**
- * Remove all user/role combinations from the user_x_role table.
+ * Remove all user/role combinations from the role_x_user table.
*
- * @param array $users userids
- * @param array $roles roleids
+ * @param int[] $users userids
+ * @param int[] $roles roleids
*/
- public static function removeRoleFromUser($users, $roles) {
- $query = "DELETE FROM user_x_role WHERE userid IN (:users) AND roleid IN (:roles)";
- Database::exec($query, array("users" => $users, "roles" => $roles));
+ public static function removeRoleFromUser($users, $roles)
+ {
+ if (empty($users) || empty($roles))
+ return 0;
+ $query = "DELETE FROM role_x_user WHERE userid IN (:users) AND roleid IN (:roles)";
+ return Database::exec($query, array("users" => $users, "roles" => $roles));
+ }
+
+ /**
+ * Assign the specified roles to given users, removing any roles from the users
+ * that are not in the given set.
+ *
+ * @param int[] $users list of user ids
+ * @param int[] $roles list of role ids
+ */
+ public static function setRolesForUser($users, $roles)
+ {
+ $count = Database::exec("DELETE FROM role_x_user WHERE userid in (:users) AND roleid NOT IN (:roles)",
+ compact('users', 'roles'));
+ return $count + self::addRoleToUser($users, $roles);
}
/**
* Delete role from the role table.
*
- * @param string $roleid roleid
+ * @param int $roleid roleid
*/
- public static function deleteRole($roleid) {
- Database::exec("DELETE FROM role WHERE roleid = :roleid", array("roleid" => $roleid));
+ public static function deleteRole($roleid)
+ {
+ return Database::exec("DELETE FROM role WHERE roleid = :roleid", array("roleid" => $roleid));
}
/**
* Save changes to a role or create a new one.
*
- * @param string $rolename rolename
- * @param array $locations array of locations
- * @param array $permissions array of permissions
- * @param string|null $roleid roleid or null if the role does not exist yet
+ * @param string $roleName rolename
+ * @param int[] $locations array of locations
+ * @param string[] $permissions array of permissions
+ * @param int|null $roleId roleid or null if the role does not exist yet
*/
- public static function saveRole($rolename, $locations, $permissions, $roleid = NULL) {
- if ($roleid) {
- Database::exec("UPDATE role SET rolename = :rolename WHERE roleid = :roleid",
- array("rolename" => $rolename, "roleid" => $roleid));
- Database::exec("DELETE FROM role_x_location WHERE roleid = :roleid", array("roleid" => $roleid));
- Database::exec("DELETE FROM role_x_permission WHERE roleid = :roleid", array("roleid" => $roleid));
+ public static function saveRole($roleName, $roleDescription, $locations, $permissions, $roleId = null)
+ {
+ foreach ($permissions as &$permission) {
+ $permission = strtolower($permission);
+ }
+ unset($permission);
+ if ($roleId) {
+ Database::exec("UPDATE role SET rolename = :rolename, roledescription = :roledescription WHERE roleid = :roleid",
+ array("rolename" => $roleName, "roledescription" => $roleDescription, "roleid" => $roleId));
+ Database::exec("DELETE FROM role_x_location
+ WHERE roleid = :roleid AND (locationid NOT IN (:locations) OR locationid IS NULL)",
+ array("roleid" => $roleId, 'locations' => $locations));
+ Database::exec("DELETE FROM role_x_permission
+ WHERE roleid = :roleid AND permissionid NOT IN (:permissions)",
+ array("roleid" => $roleId, 'permissions' => $permissions));
} else {
- Database::exec("INSERT INTO role (rolename) VALUES (:rolename)", array("rolename" => $rolename));
- $roleid = Database::lastInsertId();
+ Database::exec("INSERT INTO role (rolename, roledescription) VALUES (:rolename, :roledescription)",
+ array("rolename" => $roleName, "roledescription" => $roleDescription));
+ $roleId = Database::lastInsertId();
}
- foreach ($locations as $locationid) {
- Database::exec("INSERT INTO role_x_location (roleid, locationid) VALUES (:roleid, :locationid)",
- array("roleid" => $roleid, "locationid" => $locationid));
+
+ if (!empty($locations)) {
+ $arg = array_map(function ($loc) use ($roleId) {
+ return compact('roleId', 'loc');
+ }, $locations);
+ Database::exec("INSERT IGNORE INTO role_x_location (roleid, locationid) VALUES :arg", ['arg' => $arg]);
}
- foreach ($permissions as $permissionid) {
- Database::exec("INSERT INTO role_x_permission (roleid, permissionid) VALUES (:roleid, :permissionid)",
- array("roleid" => $roleid, "permissionid" => $permissionid));
+
+ if (!empty($permissions)) {
+ $arg = array_map(function ($perm) use ($roleId) {
+ return compact('roleId', 'perm');
+ }, $permissions);
+ Database::exec("INSERT IGNORE INTO role_x_permission (roleid, permissionid) VALUES :arg", ['arg' => $arg]);
}
}
diff --git a/modules-available/permissionmanager/inc/permissionutil.inc.php b/modules-available/permissionmanager/inc/permissionutil.inc.php
index 5ff41046..a3a2b610 100644
--- a/modules-available/permissionmanager/inc/permissionutil.inc.php
+++ b/modules-available/permissionmanager/inc/permissionutil.inc.php
@@ -2,6 +2,44 @@
class PermissionUtil
{
+
+ /**
+ * Generate all possible variants to match against, eg. $permissionid = a.b.c then we get:
+ * [ *, a.*, a.b.*, a.b.c ]
+ * In case $permissionid ends with an asterisk, also set $wildcard and $wclen, e.g.
+ * $permissionid = a.b.* --> $wildcard = a.b. and $wclen = 4
+ *
+ * @param $permission string|string[] permission to mangle
+ * @param string[] $compare all the generated variants
+ * @param string|false $wildcard if $permission is a wildcard string this returns the matching variant
+ * @param int|false $wclen if $permission is a wildcard string, this is the length of the matching variant
+ */
+ private static function makeComparisonVariants($permission, &$compare, &$wildcard, &$wclen)
+ {
+ if (!is_array($permission)) {
+ $permission = explode('.', $permission);
+ }
+ $partCount = count($permission);
+ $compare = [];
+ for ($i = 0; $i < $partCount; ++$i) {
+ $compare[] = $permission[0];
+ }
+ for ($i = 1; $i < $partCount; ++$i) {
+ $compare[$i - 1] .= '.*';
+ for ($j = $i; $j < $partCount; ++$j) {
+ $compare[$j] .= '.' . $permission[$i];
+ }
+ }
+ $compare[] = '*';
+
+ if ($permission[$partCount - 1] === '*') {
+ $wildcard = substr($compare[$partCount - 1], 0, -1);
+ $wclen = strlen($wildcard);
+ } else {
+ $wclen = $wildcard = false;
+ }
+ }
+
/**
* Check if the user has the given permission (for the given location).
*
@@ -10,26 +48,48 @@ class PermissionUtil
* @param int|null $locationid locationid to check or null if the location should be disregarded
* @return bool true if user has permission, false if not
*/
- public static function userHasPermission($userid, $permissionid, $locationid) {
- $locations = array();
- if (!is_null($locationid)) {
- $locations = Location::getLocationRootChain($locationid);
- if (count($locations) == 0) return false;
- else $locations[] = 0;
+ public static function userHasPermission($userid, $permissionid, $locationid)
+ {
+ $permissionid = strtolower($permissionid);
+ self::validatePermission($permissionid);
+ $parts = explode('.', $permissionid);
+ // Special case: To prevent lockout, userid === 1 always has permissionmanager.*
+ if ($parts[0] === 'permissionmanager' && User::getId() === 1)
+ return true;
+ // Limit query to first part of permissionid, which is always the module id
+ $prefix = $parts[0] . '.%';
+ if (is_null($locationid)) {
+ $res = Database::simpleQuery("SELECT permissionid FROM role_x_permission
+ INNER JOIN role_x_user USING (roleid)
+ WHERE role_x_user.userid = :userid AND (permissionid LIKE :prefix OR permissionid LIKE '*')",
+ compact('userid', 'prefix'));
+ } else {
+ if ($locationid === 0) {
+ $locations = [0];
+ } else {
+ $locations = Location::getLocationRootChain($locationid);
+ if (empty($locations)) { // Non-existent location, still continue as user might have global perms
+ $locations = [0];
+ }
+ }
+ $res = Database::simpleQuery("SELECT permissionid FROM role_x_permission
+ INNER JOIN role_x_user USING (roleid)
+ INNER JOIN role_x_location USING (roleid)
+ WHERE role_x_user.userid = :userid AND (permissionid LIKE :prefix OR permissionid LIKE '*')
+ AND (locationid IN (:locations) OR locationid IS NULL)",
+ compact('userid', 'prefix', 'locations'));
}
+ // Quick bailout - no results
+ if ($res->rowCount() === 0)
+ return false;
- $res = Database::simpleQuery("SELECT permissionid, locationid FROM user_x_role
- INNER JOIN role_x_permission ON user_x_role.roleid = role_x_permission.roleid
- LEFT JOIN (SELECT roleid, COALESCE(locationid, 0) AS locationid FROM role_x_location) t1
- ON role_x_permission.roleid = t1.roleid
- WHERE user_x_role.userid = :userid", array("userid" => $userid));
-
+ // Compare to database result
+ self::makeComparisonVariants($parts, $compare, $wildcard, $wclen);
while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $userPermission = rtrim($row["permissionid"], ".*").".";
- if ((is_null($locationid) || (!is_null($row["locationid"]) && in_array($row["locationid"], $locations))) &&
- (substr($permissionid.".", 0, strlen($userPermission)) === $userPermission || $userPermission === ".")) {
+ if (in_array($row['permissionid'], $compare, true))
+ return true;
+ if ($wildcard !== false && strncmp($row['permissionid'], $wildcard, $wclen) === 0)
return true;
- }
}
return false;
}
@@ -41,26 +101,40 @@ class PermissionUtil
* @param string $permissionid permissionid to check
* @return array array of locationids where the user has the given permission
*/
- public static function getAllowedLocations($userid, $permissionid) {
-
- $res = Database::simpleQuery("SELECT permissionid, COALESCE(locationid, 0) AS locationid FROM user_x_role
- INNER JOIN role_x_permission ON user_x_role.roleid = role_x_permission.roleid
- INNER JOIN role_x_location ON role_x_permission.roleid = role_x_location.roleid
- WHERE user_x_role.userid = :userid", array("userid" => $userid));
+ public static function getAllowedLocations($userid, $permissionid)
+ {
+ $permissionid = strtolower($permissionid);
+ self::validatePermission($permissionid);
+ $parts = explode('.', $permissionid);
+ // Special case: To prevent lockout, userid === 1 always has permissionmanager.*
+ if ($parts[0] === 'permissionmanager' && User::getId() === 1) {
+ $allowedLocations = [true];
+ } else {
+ // Limit query to first part of permissionid, which is always the module id
+ $prefix = $parts[0] . '.%';
+ $res = Database::simpleQuery("SELECT permissionid, locationid FROM role_x_permission
+ INNER JOIN role_x_user USING (roleid)
+ INNER JOIN role_x_location USING (roleid)
+ WHERE role_x_user.userid = :userid AND (permissionid LIKE :prefix OR permissionid LIKE '*')",
+ compact('userid', 'prefix'));
- $allowedLocations = array();
- while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
- $userPermission = rtrim($row["permissionid"], ".*").".";
- if (substr($permissionid.".", 0, strlen($userPermission)) === $userPermission || $userPermission === ".") {
- $allowedLocations[$row["locationid"]] = 1;
+ // Gather locationid from relevant rows
+ self::makeComparisonVariants($parts, $compare, $wildcard, $wclen);
+ $allowedLocations = array();
+ while ($row = $res->fetch(PDO::FETCH_ASSOC)) {
+ if (in_array($row['permissionid'], $compare, true)
+ || ($wildcard !== false && strncmp($row['permissionid'], $wildcard, $wclen) === 0)) {
+ $allowedLocations[(int)$row['locationid']] = true;
+ }
}
}
- $allowedLocations = array_keys($allowedLocations);
$locations = Location::getTree();
- if (in_array("0", $allowedLocations)) {
- $allowedLocations = array_map("intval", Location::extractIds($locations));
- $allowedLocations[] = 0;
+ if (isset($allowedLocations[0])) {
+ // Trivial case - have permission for all locations, so populate list with all valid locationds
+ $allowedLocations = Location::extractIds($locations);
+ $allowedLocations[] = 0; // .. plus 0 to show that we have global perms
} else {
+ // We have a specific list of locationds - add any sublocations to list
$allowedLocations = self::getSublocations($locations, $allowedLocations);
}
return $allowedLocations;
@@ -70,17 +144,18 @@ class PermissionUtil
* Extend an array of locations by adding all sublocations.
*
* @param array $tree tree of all locations (structured like Location::getTree())
- * @param array $locations the array of locationids to extend
+ * @param array $allowedLocations the array of locationids to extend
* @return array extended array of locationids
*/
- public static function getSublocations($tree, $locations) {
- $result = array_flip($locations);
+ public static function getSublocations($tree, $allowedLocations)
+ {
+ $result = $allowedLocations;
foreach ($tree as $location) {
if (array_key_exists("children", $location)) {
- if (in_array($location["locationid"], $locations)) {
+ if (isset($allowedLocations[$location["locationid"]])) {
$result += array_flip(Location::extractIds($location["children"]));
} else {
- $result += array_flip(self::getSublocations($location["children"], $locations));
+ $result += array_flip(self::getSublocations($location["children"], $allowedLocations));
}
}
}
@@ -88,6 +163,37 @@ class PermissionUtil
}
/**
+ * If in debug mode, validate that the checked permission is actually defined
+ * in the according permissions.json and complain if that's not the case.
+ * This is supposed to catch misspelled permission checks.
+ *
+ * @param string $permissionId permission to check
+ */
+ private static function validatePermission($permissionId)
+ {
+ if (!CONFIG_DEBUG || $permissionId === '*')
+ return;
+ $split = explode('.', $permissionId, 2);
+ if (count($split) !== 2) {
+ trigger_error('[skip:3]Cannot check malformed permission "' . $permissionId . '"', E_USER_WARNING);
+ return;
+ }
+ if ($split[1] === '*')
+ return;
+ $data = json_decode(file_get_contents('modules/' . $split[0] . '/permissions/permissions.json'), true);
+ if (substr($split[1], -2) === '.*') {
+ $len = strlen($split[1]) - 1;
+ foreach ($data as $perm => $v) {
+ if (strncmp($split[1], $perm, $len) === 0)
+ return;
+ }
+ trigger_error('[skip:3]Permission "' . $permissionId . '" does not match anything defined for module', E_USER_WARNING);
+ } elseif (!is_array($data) || !array_key_exists($split[1], $data)) {
+ trigger_error('[skip:3]Permission "' . $permissionId . '" not defined for module', E_USER_WARNING);
+ }
+ }
+
+ /**
* Get all permissions of all active modules that have permissions in their permissions/permissions.json file.
*
* @return array permission tree as a multidimensional array
@@ -100,30 +206,62 @@ class PermissionUtil
if (!is_array($data))
continue;
preg_match('#^modules/([^/]+)/#', $file, $out);
- foreach( $data as $p ) {
- $description = Dictionary::translateFileModule($out[1], "permissions", $p);
- self::putInPermissionTree($out[1].".".$p, $description, $permissions);
+ $moduleId = $out[1];
+ if (Module::get($moduleId) === false)
+ continue;
+ foreach ($data as $perm => $permissionFlags) {
+ $description = Dictionary::translateFileModule($moduleId, "permissions", $perm);
+ self::putInPermissionTree($moduleId . "." . $perm, $permissionFlags['location-aware'], $description, $permissions);
}
}
ksort($permissions);
global $MENU_CAT_OVERRIDE;
$sortingOrder = $MENU_CAT_OVERRIDE;
- foreach ($permissions as $module => $v) $sortingOrder[Module::get($module)->getCategory()][] = $module;
+ foreach ($permissions as $module => $v) {
+ $sortingOrder[Module::get($module)->getCategory()][] = $module;
+ }
$permissions = array_replace(array_flip(call_user_func_array('array_merge', $sortingOrder)), $permissions);
- foreach ($permissions as $module => $v) if (is_int($v)) unset($permissions[$module]);
+ foreach ($permissions as $module => $v) {
+ if (is_int($v)) {
+ unset($permissions[$module]);
+ }
+ }
return $permissions;
}
/**
+ * Get all existing roles.
+ *
+ * @param int|false $userid Which user to consider, false = none
+ * @param bool $onlyMatching true = filter roles the user doesn't have
+ * @return array list of roles
+ */
+ public static function getRoles($userid = false, $onlyMatching = true)
+ {
+ if ($userid === false) {
+ return Database::queryAll('SELECT roleid, rolename FROM role ORDER BY rolename ASC');
+ }
+ $ret = Database::queryAll('SELECT r.roleid, r.rolename, u.userid AS hasRole FROM role r
+ LEFT JOIN role_x_user u ON (r.roleid = u.roleid AND u.userid = :userid)
+ GROUP BY r.roleid
+ ORDER BY rolename ASC', ['userid' => $userid]);
+ foreach ($ret as &$role) {
+ settype($role['hasRole'], 'bool');
+ }
+ return $ret;
+ }
+
+ /**
* Place a permission into the given permission tree.
*
* @param string $permission the permission to place in the tree
+ * @param bool $locationAware whether this permissions can be restricted to specific locations only
* @param string $description the description of the permission
* @param array $tree the permission tree to modify
*/
- private static function putInPermissionTree($permission, $description, &$tree)
+ private static function putInPermissionTree($permission, $locationAware, $description, &$tree)
{
$subPermissions = explode('.', $permission);
foreach ($subPermissions as $subPermission) {
@@ -134,6 +272,7 @@ class PermissionUtil
$tree =& $tree[$subPermission];
}
}
- $tree = $description;
+ $tree = array('description' => $description, 'location-aware' => $locationAware, 'isLeaf' => true);
}
+
} \ No newline at end of file