aboutsummaryrefslogtreecommitdiff
path: root/lib/plugins/db/RolePersistence.php
diff options
context:
space:
mode:
Diffstat (limited to 'lib/plugins/db/RolePersistence.php')
-rw-r--r--lib/plugins/db/RolePersistence.php721
1 files changed, 721 insertions, 0 deletions
diff --git a/lib/plugins/db/RolePersistence.php b/lib/plugins/db/RolePersistence.php
new file mode 100644
index 0000000..54a0053
--- /dev/null
+++ b/lib/plugins/db/RolePersistence.php
@@ -0,0 +1,721 @@
+<?php
+/**
+ * RolePersistence.php
+ *
+ * Funktionen für das Rollenmanagement
+ *
+ * @author Dennis Reil <dennis.reil@offis.de>
+ * @author Michael Riehemann <michael.riehemann@uni-oldenburg.de>
+ * @package pluginengine
+ * @subpackage db
+ * @copyright 2009 Stud.IP
+ * @license http://www.gnu.org/licenses/gpl.html GPL Licence 3
+ */
+class RolePersistence
+{
+ const ROLES_CACHE_KEY = 'roles';
+ const USER_ROLES_CACHE_KEY = 'roles/user';
+ const PLUGIN_ROLES_CACHE_KEY = 'roles/plugin';
+
+ protected static $all_roles = null;
+
+ /**
+ * Returns all available roles.
+ *
+ * @param bool $grouped Return the roles grouped by system type or other
+ * @return Role[]|array{system: Role[], other: Role[]}
+ */
+ public static function getAllRoles(bool $grouped = false): array
+ {
+ if (self::$all_roles === null) {
+ // read cache
+ $cache = \Studip\Cache\Factory::getCache();
+
+ // cache miss, retrieve from database
+ self::$all_roles = $cache->read(self::ROLES_CACHE_KEY);
+ if (!self::$all_roles) {
+ $query = "SELECT `roleid`, `rolename`, `system` = 'y' AS `is_system`
+ FROM `roles`
+ ORDER BY `rolename`";
+ $statement = DBManager::get()->query($query);
+ $statement->setFetchMode(PDO::FETCH_ASSOC);
+
+ self::$all_roles = [];
+ foreach ($statement as $row) {
+ self::$all_roles[$row['roleid']] = new Role($row['roleid'], $row['rolename'], $row['is_system']);
+ }
+
+ $cache->write(self::ROLES_CACHE_KEY, self::$all_roles);
+ }
+ }
+
+ if (!$grouped) {
+ return self::$all_roles;
+ }
+
+ $groups = ['system' => [], 'other' => []];
+ foreach (self::$all_roles as $id => $role) {
+ $index = $role->getSystemtype() ? 'system' : 'other';
+ $groups[$index][$id] = $role;
+ }
+
+ return $groups;
+ }
+
+ public static function getRoleIdByName($name)
+ {
+ foreach (self::getAllRoles() as $id => $role) {
+ if ($role->getRolename() === $name) {
+ return $id;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Inserts the role into the database or does an update, if it's already there
+ *
+ * @param Role $role
+ * @return int the role id
+ */
+ public static function saveRole($role)
+ {
+ $query = "INSERT INTO `roles` (`roleid`, `rolename`, `system`)
+ VALUES (?, ?, 'n')
+ ON DUPLICATE KEY UPDATE `rolename` = VALUES(`rolename`)";
+ $statement = DBManager::get()->prepare($query);
+ $statement->execute([$role->getRoleId(), $role->getRolename()]);
+
+ if ($role->getRoleid() === Role::UNKNOWN_ROLE_ID) {
+ $role_id = DBManager::get()->lastInsertId();
+ $role->setRoleid($role_id);
+
+ $event = 'RoleDidCreate';
+ } else {
+ $event = 'RoleDidUpdate';
+ }
+
+ // sweep roles cache, see #getAllRoles
+ self::expireRolesCache();
+
+ NotificationCenter::postNotification(
+ $event,
+ $role->getRoleid(),
+ $role->getRolename()
+ );
+
+ return $role->getRoleid();
+ }
+
+ /**
+ * Delete role if not a permanent role. System roles cannot be deleted.
+ *
+ * @param Role $role
+ */
+ public static function deleteRole($role): bool
+ {
+ $id = $role->getRoleid();
+ $name = $role->getRolename();
+
+ $query = "SELECT `pluginid` FROM `roles_plugins` WHERE `roleid` = ?";
+ $statement = DBManager::get()->prepare($query);
+ $statement->execute([$id]);
+ $statement->setFetchMode(PDO::FETCH_COLUMN, 0);
+
+ $result = DBManager::get()->execute(
+ "DELETE `roles`, `roles_user`, `roles_plugins`, `roles_studipperms`
+ FROM `roles`
+ LEFT JOIN `roles_user` USING (`roleid`)
+ LEFT JOIN `roles_plugins` USING (`roleid`)
+ LEFT JOIN `roles_studipperms` USING (`roleid`)
+ WHERE `roleid` = ? AND `system` = 'n'",
+ [$id]
+ );
+
+ if ($result === 0) {
+ return false;
+ }
+
+ // sweep roles cache
+ self::expireRolesCache();
+ self::expireUserCache();
+
+ foreach ($statement as $plugin_id) {
+ self::expirePluginCache($plugin_id);
+ }
+
+ NotificationCenter::postNotification('RoleDidDelete', $id, $name);
+
+ return true;
+ }
+
+ /**
+ * Delete role by name if not a permanent role. System roles cannot be
+ * deleted.
+ *
+ * @param string $role_name
+ *
+ * @return bool
+ */
+ public static function deleteRoleByName(string $role_name): bool
+ {
+ foreach (self::getAllRoles() as $role) {
+ if ($role->getRolename() === $role_name) {
+ return self::deleteRole($role);
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Saves a role assignment to the database
+ *
+ * @param User $user
+ * @param Role $role
+ * @param string $institut_id
+ */
+ public static function assignRole(User $user, $role, $institut_id = '')
+ {
+ // role is not in database
+ // save it to the database first
+ if ($role->getRoleid() === Role::UNKNOWN_ROLE_ID) {
+ $roleid = self::saveRole($role);
+ } else {
+ $roleid = $role->getRoleid();
+ }
+
+ $query = "REPLACE INTO `roles_user` (`roleid`, `userid`, `institut_id`)
+ VALUES (?, ?, ?)";
+ $statement = DBManager::get()->prepare($query);
+ $statement->execute([$roleid, $user->id, $institut_id]);
+
+ unset(self::getUserRolesCache()[$user->id]);
+
+ NotificationCenter::postNotification(
+ 'RoleAssignmentDidCreate',
+ $roleid,
+ $user->id,
+ $institut_id
+ );
+ }
+
+ /**
+ * Assigns a role to a stud.ip permission. System roles cannot be assigned
+ * to permissions.
+ *
+ * @param string $perm
+ * @param Role $role
+ *
+ * @return bool
+ * @throws Exception
+ */
+ public static function assignRoleToPerm(string $perm, Role $role): bool
+ {
+ if ($role->getSystemtype()) {
+ throw new Exception('Cannot assign system roles to permissions.');
+ }
+
+ if (!in_array($perm, ['user', 'autor', 'tutor', 'dozent', 'admin', 'root'])) {
+ throw new Exception("Invalid permission {$perm}");
+ }
+
+ $query = "INSERT INTO `roles_studipperms` (`roleid`, `permname`)
+ VALUES (?, ?)";
+ $result = DBManager::get()->execute($query, [$role->getRoleid(), $perm]);
+
+ if ($result === 0) {
+ return false;
+ }
+
+ User::findEachByPerms(
+ function (User $user) {
+ self::expireUserCache($user->id);
+ },
+ $perm
+ );
+
+ return true;
+ }
+
+ /**
+ * Gets all assigned roles from the database for a user
+ *
+ * @param int $userid
+ * @param boolean $implicit
+ * @return array
+ */
+ public static function getAssignedRoles($user_id, $implicit = false)
+ {
+ return array_intersect_key(
+ self::getAllRoles(),
+ self::loadUserRoles($user_id, $implicit)
+ );
+ }
+
+ /**
+ * Returns institutes for which the given user has the given role.
+ * @param string $user_id User id
+ * @param int $role_id Role id
+ * @return array of institute ids
+ */
+ public static function getAssignedRoleInstitutes($user_id, $role_id)
+ {
+ $roles = self::loadUserRoles($user_id);
+ return $roles[$role_id] ?? [];
+ }
+
+ /**
+ * Checks a role assignment for an user
+ * optionally check for institute
+ *
+ * @param string $userid
+ * @param string $assignedrole
+ * @param string $institut_id
+ * @return boolean
+ */
+ public static function isAssignedRole($userid, $assignedrole, $institut_id = '')
+ {
+ if (!$userid) {
+ return false;
+ }
+
+ $faculty_id = $institut_id
+ ? Institute::find($institut_id)->fakultaets_id
+ : null;
+
+ $role_id = self::getRoleIdByName($assignedrole);
+ $user_roles = self::loadUserRoles($userid, true);
+
+ return isset($user_roles[$role_id])
+ && (
+ !$institut_id
+ || in_array($institut_id, $user_roles[$role_id])
+ || in_array($faculty_id, $user_roles[$role_id])
+ );
+ }
+
+ private static function loadUserRoles($user_id, $implicit = false)
+ {
+ $cache = self::getUserRolesCache();
+
+ if (!isset($cache[$user_id])) {
+ $query = "SELECT `roleid`, `institut_id`, 1 AS explicit
+ FROM `roles_user`
+ WHERE `userid` = :user_id
+
+ UNION ALL
+
+ SELECT `roleid`, '' AS institut_id, 0 AS explicit
+ FROM `roles_studipperms`
+ WHERE `permname` = :perm";
+ $statement = DBManager::get()->prepare($query);
+ $statement->bindValue(':user_id', $user_id);
+ $statement->bindValue(':perm', empty($GLOBALS['perm']) ? 'nobody' : $GLOBALS['perm']->get_perm($user_id));
+ $statement->execute();
+ $statement->setFetchMode(PDO::FETCH_ASSOC);
+
+ $roles = [];
+ foreach ($statement as $row) {
+ if (!isset($roles[$row['roleid']])) {
+ $roles[$row['roleid']] = [
+ 'id' => $row['roleid'],
+ 'institutes' => [],
+ 'explicit' => (bool) $row['explicit'],
+ ];
+ }
+ if ($row['institut_id']) {
+ $roles[$row['roleid']]['institutes'][] = $row['institut_id'];
+ }
+ }
+
+ $cache[$user_id] = $roles;
+ }
+
+ // Filter implicit roles away if necessary
+ $roles = array_filter(
+ $cache[$user_id],
+ function ($role) use ($implicit) {
+ return $implicit || $role['explicit'];
+ }
+ );
+
+ return array_column($roles, 'institutes', 'id');
+ }
+
+ /**
+ * Deletes a role assignment from the database
+ *
+ * @param User $user
+ * @param Role $role
+ * @param String $institut_id
+ */
+ public static function deleteRoleAssignment(User $user, $role, $institut_id = null)
+ {
+ $query = "DELETE FROM `roles_user`
+ WHERE `roleid` = ?
+ AND `userid` = ?
+ AND `institut_id` = IFNULL(?, `institut_id`)";
+ DBManager::get()->execute(
+ $query,
+ [$role->getRoleid(), $user->id, $institut_id]
+ );
+
+ unset(self::getUserRolesCache()[$user->id]);
+
+ NotificationCenter::postNotification(
+ 'RoleAssignmentDidDelete',
+ $role->getRoleid(),
+ $user->id,
+ $institut_id
+ );
+ }
+
+ /**
+ * Removes a role from a stud.ip permission. System roles cannot be removed
+ * from permissions.
+ *
+ * @param string $perm
+ * @param Role $role
+ *
+ * @return bool
+ * @throws Exception
+ */
+ public static function deleteRoleAssignmentFromPerm(string $perm, Role $role): bool
+ {
+ if ($role->getSystemtype()) {
+ throw new Exception('Cannot remove system role assignment from permissions.');
+ }
+
+ if (!in_array($perm, ['user', 'autor', 'tutor', 'dozent', 'admin', 'root'])) {
+ throw new Exception("Invalid permission {$perm}");
+ }
+
+ $query = "DELETE FROM `roles_studipperms`
+ WHERE `roleid` = ?
+ AND `permname` = ?";
+ $result = DBManager::get()->execute($query, [$role->getRoleid(), $perm]);
+
+ if ($result === 0) {
+ return false;
+ }
+
+ User::findEachByPerms(
+ function (User $user) {
+ self::expireUserCache($user->id);
+ },
+ $perm
+ );
+
+ return true;
+ }
+
+
+ /**
+ * Get's all Role-Assignments for a certain user.
+ * If no user is set, all role assignments are returned.
+ *
+ * @param User $user
+ * @return array with roleids and the assigned userids
+ * @deprecated seems to be unused (and was corrupt for some versions)
+ */
+ public static function getAllRoleAssignments($user = null)
+ {
+ $query = "SELECT `roleid`, `userid`
+ FROM `roles_user`
+ WHERE `userid` = IFNULL(?, `userid`)";
+ return DBManager::get()->fetchPairs($query, [$user]);
+ }
+
+ /**
+ * Enter description here...
+ *
+ * @param int $pluginid
+ * @param array $roleids
+ */
+ public static function assignPluginRoles($plugin_id, $role_ids)
+ {
+ $plugin_id = (int) $plugin_id;
+
+ $query = "REPLACE INTO `roles_plugins` (`roleid`, `pluginid`)
+ VALUES (:role_id, :plugin_id)";
+ $statement = DBManager::get()->prepare($query);
+ $statement->bindValue(':plugin_id', $plugin_id);
+
+ foreach ($role_ids as $role_id) {
+ $statement->bindValue(':role_id', $role_id);
+ $statement->execute();
+ }
+
+ self::expirePluginCache($plugin_id);
+
+ foreach ($role_ids as $role_id) {
+ NotificationCenter::postNotification(
+ 'PluginRoleAssignmentDidCreate',
+ $role_id,
+ $plugin_id
+ );
+ }
+ }
+
+ /**
+ * Removes the given roles' assignments from the given plugin.
+ *
+ * @param int $pluginid
+ * @param array $roleids
+ */
+ public static function deleteAssignedPluginRoles($plugin_id, $role_ids)
+ {
+ $plugin_id = (int) $plugin_id;
+
+ $query = "DELETE FROM `roles_plugins`
+ WHERE `pluginid` = :plugin_id
+ AND `roleid` = :role_id";
+ $statement = DBManager::get()->prepare($query);
+ $statement->bindValue(':plugin_id', $plugin_id);
+
+ foreach ($role_ids as $role_id) {
+ $statement->bindValue(':role_id', $role_id);
+ $statement->execute();
+ }
+
+ self::expirePluginCache($plugin_id);
+
+ foreach ($role_ids as $role_id) {
+ NotificationCenter::postNotification(
+ 'PluginRoleAssignmentDidDelete',
+ $role_id,
+ $plugin_id
+ );
+ }
+ }
+
+ /**
+ * Return all roles assigned to a plugin.
+ *
+ * @param int $pluginid
+ * @return array
+ */
+ public static function getAssignedPluginRoles($plugin_id)
+ {
+ $plugin_id = (int) $plugin_id;
+
+ // read plugin roles from cache
+ $cache = self::getPluginRolesCache();
+
+ // cache miss, retrieve roles from database
+ if (!isset($cache[$plugin_id])) {
+ $query = "SELECT `roleid` FROM `roles_plugins` WHERE `pluginid` = ?";
+ $statement = DBManager::get()->prepare($query);
+ $statement->execute([$plugin_id]);
+ $role_ids = $statement->fetchAll(PDO::FETCH_COLUMN);
+
+ // write to cache
+ $cache[$plugin_id] = $role_ids;
+ }
+
+ $roles = self::getAllRoles();
+ return array_filter(array_map(
+ function ($role_id) use ($roles) {
+ if (!isset($roles[$role_id])) {
+ return false;
+ }
+ return $roles[$role_id];
+ },
+ $cache[$plugin_id]
+ ));
+ }
+
+ /**
+ * Returns all users that have a specific role - given by it's name.
+ *
+ * @param string $role_name Name of the role
+ * @param bool $only_explicit Only select explicit assignments from table
+ * `roles_user` if true, otherwise also select
+ * by perm defined in table `roles_studipperms`
+ *
+ * @return User[]
+ */
+ public static function getUsersWithRoleByName(string $role_name, bool $only_explicit = true): array
+ {
+ $role_id = self::getRoleIdByName($role_name);
+ if ($role_id === false) {
+ throw new Exception("Unknown role name {$role_name}");
+ }
+
+ return self::getUsersWithRoleById($role_id, $only_explicit);
+ }
+
+ /**
+ * Returns all users that have a specific role - given by it's id.
+ *
+ * @param int $role_id Id of the role
+ * @param bool $only_explicit Only select explicit assignments from table
+ * `roles_user` if true, otherwise also select
+ * by perm defined in table `roles_studipperms`
+ *
+ * @return User[]
+ */
+ public static function getUsersWithRoleById(int $role_id, bool $only_explicit = true): array
+ {
+ $query = "SELECT `userid` AS `user_id`
+ FROM `roles_user`
+ WHERE `roleid` = :role_id";
+
+ if (!$only_explicit) {
+ $query = "SELECT DISTINCT `user_id`
+ FROM (
+ {$query}
+
+ UNION ALL
+
+ SELECT `user_id`
+ FROM `roles_studipperms` AS `rsp`
+ JOIN `auth_user_md5` AS `aum`
+ ON (`rsp`.`permname` = `aum`.`perms`)
+ WHERE `rsp`.`roleid` = :role_id
+ ) AS tmp";
+ }
+
+ $user_ids = DBManager::get()->fetchFirst($query, [':role_id' => $role_id]);
+
+ return User::findMany($user_ids);
+ }
+
+ /**
+ * Returns statistic values for each role:
+ *
+ * - number of explicitely assigned users
+ * - number of implicitely assigned users
+ * - number of assigned plugins
+ *
+ * @return array
+ */
+ public static function getStatistics()
+ {
+ // Get basic statistics
+ $query = "SELECT r.`roleid`,
+ COUNT(DISTINCT ru.`userid`) AS explicit,
+ COUNT(DISTINCT rp.`pluginid`) AS plugins
+ FROM roles AS r
+ -- Explicit assignment
+ LEFT JOIN `roles_user` AS ru
+ ON r.`roleid` = ru.`roleid` AND ru.`userid` IN (SELECT `user_id` FROM `auth_user_md5`)
+ -- Plugins
+ LEFT JOIN `roles_plugins` AS rp
+ ON r.`roleid` = rp.`roleid` AND rp.`pluginid` IN (SELECT `pluginid` FROM `plugins`)
+ GROUP BY r.`roleid`";
+ $result = DBManager::get()->fetchGrouped($query);
+
+ // Fetch implicit assignments in a second query due to performance
+ // reasons
+ foreach (self::countImplicitUsers(array_keys($result)) as $id => $count) {
+ $result[$id]['implicit'] = $count;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Counts the implicitely assigned users for a role.
+ * @param mixed $role_id Role id or array of role ids
+ * @return mixed number of implictit for the role (if one role id is given)
+ * or associative array [role id => number of implicit users]
+ * when given a list of role ids
+ */
+ public static function countImplicitUsers($role_id)
+ {
+ // Ensure that the result array has an entry for every role id
+ $result = array_fill_keys((array) $role_id, 0);
+
+ $query = "SELECT rsp.`roleid`, COUNT(*) AS implicit
+ FROM `roles_studipperms` AS rsp
+ JOIN `auth_user_md5` AS a ON rsp.`permname` = a.`perms`
+ LEFT JOIN `roles_user` AS ru
+ ON a.`user_id` = ru.`userid` AND rsp.`roleid` = ru.`roleid`
+ WHERE rsp.`roleid` IN (?)
+ AND ru.`userid` IS NULL
+ GROUP BY rsp.`roleid`";
+ $statement = DBManager::get()->prepare($query);
+ $statement->execute([$role_id]);
+ $statement->setFetchMode(PDO::FETCH_ASSOC);
+
+ foreach ($statement as $row) {
+ $result[$row['roleid']] = (int) $row['implicit'];
+ }
+
+ return is_array($role_id)
+ ? $result
+ : $result[$role_id];
+ }
+
+ // Cache operations
+ private static $user_roles_cache = null;
+ private static $plugin_roles_cache = null;
+
+ private static function getUserRolesCache(): StudipCachedArray
+ {
+ if (self::$user_roles_cache === null) {
+ self::$user_roles_cache = new StudipCachedArray(self::USER_ROLES_CACHE_KEY);
+ }
+ return self::$user_roles_cache;
+ }
+
+ private static function getPluginRolesCache(): StudipCachedArray
+ {
+ if (self::$plugin_roles_cache === null) {
+ self::$plugin_roles_cache = new StudipCachedArray(self::PLUGIN_ROLES_CACHE_KEY);
+ }
+ return self::$plugin_roles_cache;
+ }
+
+ /**
+ * Expires all cached roles.
+ */
+ public static function expireRolesCache()
+ {
+ self::$all_roles = null;
+ \Studip\Cache\Factory::getCache()->expire(self::ROLES_CACHE_KEY);
+ }
+
+ /**
+ * Expires all cached user role assignments.
+ *
+ * @param string|null $user_id Optional user id to expire the cache for.
+ * If none is given, the whole cache is cleared.
+ */
+ public static function expireUserCache($user_id = null)
+ {
+ if ($user_id === null) {
+ self::getUserRolesCache()->expire();
+ } else {
+ unset(self::getUserRolesCache()[$user_id]);
+ }
+ }
+
+ /**
+ * Expires all cached plugin role assignments.
+ *
+ * @param string|int|null $plugin_id Optional plugin id to expire the cache
+ * for. If none is given, the whole cache
+ * is cleared.
+ */
+ public static function expirePluginCache($plugin_id = null)
+ {
+ if ($plugin_id === null) {
+ self::getPluginRolesCache()->expire();
+ } else {
+ unset(self::getPluginRolesCache()[$plugin_id]);
+ }
+ }
+
+ /**
+ * Expires all caches
+ */
+ public static function expireCaches(): void
+ {
+ self::expireRolesCache();
+ self::expireUserCache();
+ self::expirePluginCache();
+ }
+}