You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

425 lines
11 KiB

3 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Bart Visscher <bartv@thisnet.nl>
  7. * @author Bernhard Posselt <dev@bernhard-posselt.com>
  8. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  9. * @author Joas Schilling <coding@schilljs.com>
  10. * @author John Molakvoæ (skjnldsv) <skjnldsv@protonmail.com>
  11. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  12. * @author Knut Ahlers <knut@ahlers.me>
  13. * @author Lukas Reschke <lukas@statuscode.ch>
  14. * @author macjohnny <estebanmarin@gmx.ch>
  15. * @author Morris Jobke <hey@morrisjobke.de>
  16. * @author Robin Appelman <robin@icewind.nl>
  17. * @author Robin McCorkell <robin@mccorkell.me.uk>
  18. * @author Roeland Jago Douma <roeland@famdouma.nl>
  19. * @author Roman Kreisel <mail@romankreisel.de>
  20. * @author Thomas Müller <thomas.mueller@tmit.eu>
  21. * @author Vincent Petry <pvince81@owncloud.com>
  22. * @author Vinicius Cubas Brand <vinicius@eita.org.br>
  23. * @author voxsim "Simon Vocella"
  24. *
  25. * @license AGPL-3.0
  26. *
  27. * This code is free software: you can redistribute it and/or modify
  28. * it under the terms of the GNU Affero General Public License, version 3,
  29. * as published by the Free Software Foundation.
  30. *
  31. * This program is distributed in the hope that it will be useful,
  32. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  33. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  34. * GNU Affero General Public License for more details.
  35. *
  36. * You should have received a copy of the GNU Affero General Public License, version 3,
  37. * along with this program. If not, see <http://www.gnu.org/licenses/>
  38. *
  39. */
  40. namespace OC\Group;
  41. use OC\Hooks\PublicEmitter;
  42. use OCP\GroupInterface;
  43. use OCP\IGroup;
  44. use OCP\IGroupManager;
  45. use OCP\ILogger;
  46. use OCP\IUser;
  47. use Symfony\Component\EventDispatcher\EventDispatcherInterface;
  48. /**
  49. * Class Manager
  50. *
  51. * Hooks available in scope \OC\Group:
  52. * - preAddUser(\OC\Group\Group $group, \OC\User\User $user)
  53. * - postAddUser(\OC\Group\Group $group, \OC\User\User $user)
  54. * - preRemoveUser(\OC\Group\Group $group, \OC\User\User $user)
  55. * - postRemoveUser(\OC\Group\Group $group, \OC\User\User $user)
  56. * - preDelete(\OC\Group\Group $group)
  57. * - postDelete(\OC\Group\Group $group)
  58. * - preCreate(string $groupId)
  59. * - postCreate(\OC\Group\Group $group)
  60. *
  61. * @package OC\Group
  62. */
  63. class Manager extends PublicEmitter implements IGroupManager {
  64. /** @var GroupInterface[] */
  65. private $backends = [];
  66. /** @var \OC\User\Manager */
  67. private $userManager;
  68. /** @var EventDispatcherInterface */
  69. private $dispatcher;
  70. /** @var ILogger */
  71. private $logger;
  72. /** @var \OC\Group\Group[] */
  73. private $cachedGroups = [];
  74. /** @var (string[])[] */
  75. private $cachedUserGroups = [];
  76. /** @var \OC\SubAdmin */
  77. private $subAdmin = null;
  78. /**
  79. * @param \OC\User\Manager $userManager
  80. * @param EventDispatcherInterface $dispatcher
  81. * @param ILogger $logger
  82. */
  83. public function __construct(\OC\User\Manager $userManager,
  84. EventDispatcherInterface $dispatcher,
  85. ILogger $logger) {
  86. $this->userManager = $userManager;
  87. $this->dispatcher = $dispatcher;
  88. $this->logger = $logger;
  89. $cachedGroups = &$this->cachedGroups;
  90. $cachedUserGroups = &$this->cachedUserGroups;
  91. $this->listen('\OC\Group', 'postDelete', function ($group) use (&$cachedGroups, &$cachedUserGroups) {
  92. /**
  93. * @var \OC\Group\Group $group
  94. */
  95. unset($cachedGroups[$group->getGID()]);
  96. $cachedUserGroups = [];
  97. });
  98. $this->listen('\OC\Group', 'postAddUser', function ($group) use (&$cachedUserGroups) {
  99. /**
  100. * @var \OC\Group\Group $group
  101. */
  102. $cachedUserGroups = [];
  103. });
  104. $this->listen('\OC\Group', 'postRemoveUser', function ($group) use (&$cachedUserGroups) {
  105. /**
  106. * @var \OC\Group\Group $group
  107. */
  108. $cachedUserGroups = [];
  109. });
  110. }
  111. /**
  112. * Checks whether a given backend is used
  113. *
  114. * @param string $backendClass Full classname including complete namespace
  115. * @return bool
  116. */
  117. public function isBackendUsed($backendClass) {
  118. $backendClass = strtolower(ltrim($backendClass, '\\'));
  119. foreach ($this->backends as $backend) {
  120. if (strtolower(get_class($backend)) === $backendClass) {
  121. return true;
  122. }
  123. }
  124. return false;
  125. }
  126. /**
  127. * @param \OCP\GroupInterface $backend
  128. */
  129. public function addBackend($backend) {
  130. $this->backends[] = $backend;
  131. $this->clearCaches();
  132. }
  133. public function clearBackends() {
  134. $this->backends = [];
  135. $this->clearCaches();
  136. }
  137. /**
  138. * Get the active backends
  139. *
  140. * @return \OCP\GroupInterface[]
  141. */
  142. public function getBackends() {
  143. return $this->backends;
  144. }
  145. protected function clearCaches() {
  146. $this->cachedGroups = [];
  147. $this->cachedUserGroups = [];
  148. }
  149. /**
  150. * @param string $gid
  151. * @return IGroup|null
  152. */
  153. public function get($gid) {
  154. if (isset($this->cachedGroups[$gid])) {
  155. return $this->cachedGroups[$gid];
  156. }
  157. return $this->getGroupObject($gid);
  158. }
  159. /**
  160. * @param string $gid
  161. * @param string $displayName
  162. * @return \OCP\IGroup|null
  163. */
  164. protected function getGroupObject($gid, $displayName = null) {
  165. $backends = [];
  166. foreach ($this->backends as $backend) {
  167. if ($backend->implementsActions(Backend::GROUP_DETAILS)) {
  168. $groupData = $backend->getGroupDetails($gid);
  169. if (is_array($groupData) && !empty($groupData)) {
  170. // take the display name from the first backend that has a non-null one
  171. if (is_null($displayName) && isset($groupData['displayName'])) {
  172. $displayName = $groupData['displayName'];
  173. }
  174. $backends[] = $backend;
  175. }
  176. } elseif ($backend->groupExists($gid)) {
  177. $backends[] = $backend;
  178. }
  179. }
  180. if (count($backends) === 0) {
  181. return null;
  182. }
  183. $this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this, $displayName);
  184. return $this->cachedGroups[$gid];
  185. }
  186. /**
  187. * @param string $gid
  188. * @return bool
  189. */
  190. public function groupExists($gid) {
  191. return $this->get($gid) instanceof IGroup;
  192. }
  193. /**
  194. * @param string $gid
  195. * @return IGroup|null
  196. */
  197. public function createGroup($gid) {
  198. if ($gid === '' || $gid === null) {
  199. return null;
  200. } elseif ($group = $this->get($gid)) {
  201. return $group;
  202. } else {
  203. $this->emit('\OC\Group', 'preCreate', [$gid]);
  204. foreach ($this->backends as $backend) {
  205. if ($backend->implementsActions(Backend::CREATE_GROUP)) {
  206. if ($backend->createGroup($gid)) {
  207. $group = $this->getGroupObject($gid);
  208. $this->emit('\OC\Group', 'postCreate', [$group]);
  209. return $group;
  210. }
  211. }
  212. }
  213. return null;
  214. }
  215. }
  216. /**
  217. * @param string $search
  218. * @param int $limit
  219. * @param int $offset
  220. * @return \OC\Group\Group[]
  221. */
  222. public function search($search, $limit = null, $offset = null) {
  223. $groups = [];
  224. foreach ($this->backends as $backend) {
  225. $groupIds = $backend->getGroups($search, $limit, $offset);
  226. foreach ($groupIds as $groupId) {
  227. $aGroup = $this->get($groupId);
  228. if ($aGroup instanceof IGroup) {
  229. $groups[$groupId] = $aGroup;
  230. } else {
  231. $this->logger->debug('Group "' . $groupId . '" was returned by search but not found through direct access', ['app' => 'core']);
  232. }
  233. }
  234. if (!is_null($limit) and $limit <= 0) {
  235. return array_values($groups);
  236. }
  237. }
  238. return array_values($groups);
  239. }
  240. /**
  241. * @param IUser|null $user
  242. * @return \OC\Group\Group[]
  243. */
  244. public function getUserGroups(IUser $user = null) {
  245. if (!$user instanceof IUser) {
  246. return [];
  247. }
  248. return $this->getUserIdGroups($user->getUID());
  249. }
  250. /**
  251. * @param string $uid the user id
  252. * @return \OC\Group\Group[]
  253. */
  254. public function getUserIdGroups($uid) {
  255. $groups = [];
  256. foreach ($this->getUserIdGroupIds($uid) as $groupId) {
  257. $aGroup = $this->get($groupId);
  258. if ($aGroup instanceof IGroup) {
  259. $groups[$groupId] = $aGroup;
  260. } else {
  261. $this->logger->debug('User "' . $uid . '" belongs to deleted group: "' . $groupId . '"', ['app' => 'core']);
  262. }
  263. }
  264. return $groups;
  265. }
  266. /**
  267. * Checks if a userId is in the admin group
  268. *
  269. * @param string $userId
  270. * @return bool if admin
  271. */
  272. public function isAdmin($userId) {
  273. foreach ($this->backends as $backend) {
  274. if ($backend->implementsActions(Backend::IS_ADMIN) && $backend->isAdmin($userId)) {
  275. return true;
  276. }
  277. }
  278. return $this->isInGroup($userId, 'admin');
  279. }
  280. /**
  281. * Checks if a userId is in a group
  282. *
  283. * @param string $userId
  284. * @param string $group
  285. * @return bool if in group
  286. */
  287. public function isInGroup($userId, $group) {
  288. return array_search($group, $this->getUserIdGroupIds($userId)) !== false;
  289. }
  290. /**
  291. * get a list of group ids for a user
  292. *
  293. * @param IUser $user
  294. * @return array with group ids
  295. */
  296. public function getUserGroupIds(IUser $user) {
  297. return $this->getUserIdGroupIds($user->getUID());
  298. }
  299. /**
  300. * @param string $uid the user id
  301. * @return GroupInterface[]
  302. */
  303. private function getUserIdGroupIds($uid) {
  304. if (!isset($this->cachedUserGroups[$uid])) {
  305. $groups = [];
  306. foreach ($this->backends as $backend) {
  307. if ($groupIds = $backend->getUserGroups($uid)) {
  308. $groups = array_merge($groups, $groupIds);
  309. }
  310. }
  311. $this->cachedUserGroups[$uid] = $groups;
  312. }
  313. return $this->cachedUserGroups[$uid];
  314. }
  315. /**
  316. * get an array of groupid and displayName for a user
  317. *
  318. * @param IUser $user
  319. * @return array ['displayName' => displayname]
  320. */
  321. public function getUserGroupNames(IUser $user) {
  322. return array_map(function ($group) {
  323. return ['displayName' => $group->getDisplayName()];
  324. }, $this->getUserGroups($user));
  325. }
  326. /**
  327. * get a list of all display names in a group
  328. *
  329. * @param string $gid
  330. * @param string $search
  331. * @param int $limit
  332. * @param int $offset
  333. * @return array an array of display names (value) and user ids (key)
  334. */
  335. public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) {
  336. $group = $this->get($gid);
  337. if (is_null($group)) {
  338. return [];
  339. }
  340. $search = trim($search);
  341. $groupUsers = [];
  342. if (!empty($search)) {
  343. // only user backends have the capability to do a complex search for users
  344. $searchOffset = 0;
  345. $searchLimit = $limit * 100;
  346. if ($limit === -1) {
  347. $searchLimit = 500;
  348. }
  349. do {
  350. $filteredUsers = $this->userManager->searchDisplayName($search, $searchLimit, $searchOffset);
  351. foreach ($filteredUsers as $filteredUser) {
  352. if ($group->inGroup($filteredUser)) {
  353. $groupUsers[] = $filteredUser;
  354. }
  355. }
  356. $searchOffset += $searchLimit;
  357. } while (count($groupUsers) < $searchLimit + $offset && count($filteredUsers) >= $searchLimit);
  358. if ($limit === -1) {
  359. $groupUsers = array_slice($groupUsers, $offset);
  360. } else {
  361. $groupUsers = array_slice($groupUsers, $offset, $limit);
  362. }
  363. } else {
  364. $groupUsers = $group->searchUsers('', $limit, $offset);
  365. }
  366. $matchingUsers = [];
  367. foreach ($groupUsers as $groupUser) {
  368. $matchingUsers[(string) $groupUser->getUID()] = $groupUser->getDisplayName();
  369. }
  370. return $matchingUsers;
  371. }
  372. /**
  373. * @return \OC\SubAdmin
  374. */
  375. public function getSubAdmin() {
  376. if (!$this->subAdmin) {
  377. $this->subAdmin = new \OC\SubAdmin(
  378. $this->userManager,
  379. $this,
  380. \OC::$server->getDatabaseConnection()
  381. );
  382. }
  383. return $this->subAdmin;
  384. }
  385. }