Passed
Push — master ( bd0a1b...cac844 )
by Roeland
28:13 queued 17:19
created

Manager::groupExists()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bart Visscher <[email protected]>
7
 * @author Bernhard Posselt <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author John Molakvoæ (skjnldsv) <[email protected]>
11
 * @author Jörn Friedrich Dreyer <[email protected]>
12
 * @author Knut Ahlers <[email protected]>
13
 * @author Lukas Reschke <[email protected]>
14
 * @author macjohnny <[email protected]>
15
 * @author Morris Jobke <[email protected]>
16
 * @author Robin Appelman <[email protected]>
17
 * @author Robin McCorkell <[email protected]>
18
 * @author Roeland Jago Douma <[email protected]>
19
 * @author Roman Kreisel <[email protected]>
20
 * @author Thomas Müller <[email protected]>
21
 * @author Vincent Petry <[email protected]>
22
 * @author Vinicius Cubas Brand <[email protected]>
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
41
namespace OC\Group;
42
43
use OC\Hooks\PublicEmitter;
44
use OCP\GroupInterface;
45
use OCP\IGroup;
46
use OCP\IGroupManager;
47
use OCP\ILogger;
48
use OCP\IUser;
49
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
50
51
/**
52
 * Class Manager
53
 *
54
 * Hooks available in scope \OC\Group:
55
 * - preAddUser(\OC\Group\Group $group, \OC\User\User $user)
56
 * - postAddUser(\OC\Group\Group $group, \OC\User\User $user)
57
 * - preRemoveUser(\OC\Group\Group $group, \OC\User\User $user)
58
 * - postRemoveUser(\OC\Group\Group $group, \OC\User\User $user)
59
 * - preDelete(\OC\Group\Group $group)
60
 * - postDelete(\OC\Group\Group $group)
61
 * - preCreate(string $groupId)
62
 * - postCreate(\OC\Group\Group $group)
63
 *
64
 * @package OC\Group
65
 */
66
class Manager extends PublicEmitter implements IGroupManager {
0 ignored issues
show
Deprecated Code introduced by
The class OC\Hooks\PublicEmitter has been deprecated: 18.0.0 use events and the \OCP\EventDispatcher\IEventDispatcher service ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-deprecated  annotation

66
class Manager extends /** @scrutinizer ignore-deprecated */ PublicEmitter implements IGroupManager {
Loading history...
67
	/** @var GroupInterface[] */
68
	private $backends = [];
69
70
	/** @var \OC\User\Manager */
71
	private $userManager;
72
	/** @var EventDispatcherInterface */
73
	private $dispatcher;
74
	/** @var ILogger */
75
	private $logger;
76
77
	/** @var \OC\Group\Group[] */
78
	private $cachedGroups = [];
79
80
	/** @var (string[])[] */
81
	private $cachedUserGroups = [];
82
83
	/** @var \OC\SubAdmin */
84
	private $subAdmin = null;
85
86
	/**
87
	 * @param \OC\User\Manager $userManager
88
	 * @param EventDispatcherInterface $dispatcher
89
	 * @param ILogger $logger
90
	 */
91
	public function __construct(\OC\User\Manager $userManager,
92
								EventDispatcherInterface $dispatcher,
93
								ILogger $logger) {
94
		$this->userManager = $userManager;
95
		$this->dispatcher = $dispatcher;
96
		$this->logger = $logger;
97
98
		$cachedGroups = &$this->cachedGroups;
99
		$cachedUserGroups = &$this->cachedUserGroups;
100
		$this->listen('\OC\Group', 'postDelete', function ($group) use (&$cachedGroups, &$cachedUserGroups) {
101
			/**
102
			 * @var \OC\Group\Group $group
103
			 */
104
			unset($cachedGroups[$group->getGID()]);
105
			$cachedUserGroups = [];
106
		});
107
		$this->listen('\OC\Group', 'postAddUser', function ($group) use (&$cachedUserGroups) {
0 ignored issues
show
Unused Code introduced by
The parameter $group is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

107
		$this->listen('\OC\Group', 'postAddUser', function (/** @scrutinizer ignore-unused */ $group) use (&$cachedUserGroups) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
108
			/**
109
			 * @var \OC\Group\Group $group
110
			 */
111
			$cachedUserGroups = [];
112
		});
113
		$this->listen('\OC\Group', 'postRemoveUser', function ($group) use (&$cachedUserGroups) {
0 ignored issues
show
Unused Code introduced by
The parameter $group is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

113
		$this->listen('\OC\Group', 'postRemoveUser', function (/** @scrutinizer ignore-unused */ $group) use (&$cachedUserGroups) {

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
114
			/**
115
			 * @var \OC\Group\Group $group
116
			 */
117
			$cachedUserGroups = [];
118
		});
119
	}
120
121
	/**
122
	 * Checks whether a given backend is used
123
	 *
124
	 * @param string $backendClass Full classname including complete namespace
125
	 * @return bool
126
	 */
127
	public function isBackendUsed($backendClass) {
128
		$backendClass = strtolower(ltrim($backendClass, '\\'));
129
130
		foreach ($this->backends as $backend) {
131
			if (strtolower(get_class($backend)) === $backendClass) {
132
				return true;
133
			}
134
		}
135
136
		return false;
137
	}
138
139
	/**
140
	 * @param \OCP\GroupInterface $backend
141
	 */
142
	public function addBackend($backend) {
143
		$this->backends[] = $backend;
144
		$this->clearCaches();
145
	}
146
147
	public function clearBackends() {
148
		$this->backends = [];
149
		$this->clearCaches();
150
	}
151
152
	/**
153
	 * Get the active backends
154
	 *
155
	 * @return \OCP\GroupInterface[]
156
	 */
157
	public function getBackends() {
158
		return $this->backends;
159
	}
160
161
162
	protected function clearCaches() {
163
		$this->cachedGroups = [];
164
		$this->cachedUserGroups = [];
165
	}
166
167
	/**
168
	 * @param string $gid
169
	 * @return IGroup|null
170
	 */
171
	public function get($gid) {
172
		if (isset($this->cachedGroups[$gid])) {
173
			return $this->cachedGroups[$gid];
174
		}
175
		return $this->getGroupObject($gid);
176
	}
177
178
	/**
179
	 * @param string $gid
180
	 * @param string $displayName
181
	 * @return \OCP\IGroup|null
182
	 */
183
	protected function getGroupObject($gid, $displayName = null) {
184
		$backends = [];
185
		foreach ($this->backends as $backend) {
186
			if ($backend->implementsActions(Backend::GROUP_DETAILS)) {
187
				$groupData = $backend->getGroupDetails($gid);
0 ignored issues
show
Bug introduced by
The method getGroupDetails() does not exist on OCP\GroupInterface. Did you maybe mean getGroups()? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

187
				/** @scrutinizer ignore-call */ 
188
    $groupData = $backend->getGroupDetails($gid);

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
188
				if (is_array($groupData) && !empty($groupData)) {
189
					// take the display name from the first backend that has a non-null one
190
					if (is_null($displayName) && isset($groupData['displayName'])) {
191
						$displayName = $groupData['displayName'];
192
					}
193
					$backends[] = $backend;
194
				}
195
			} elseif ($backend->groupExists($gid)) {
196
				$backends[] = $backend;
197
			}
198
		}
199
		if (count($backends) === 0) {
200
			return null;
201
		}
202
		$this->cachedGroups[$gid] = new Group($gid, $backends, $this->dispatcher, $this->userManager, $this, $displayName);
203
		return $this->cachedGroups[$gid];
204
	}
205
206
	/**
207
	 * @param string $gid
208
	 * @return bool
209
	 */
210
	public function groupExists($gid) {
211
		return $this->get($gid) instanceof IGroup;
212
	}
213
214
	/**
215
	 * @param string $gid
216
	 * @return IGroup|null
217
	 */
218
	public function createGroup($gid) {
219
		if ($gid === '' || $gid === null) {
220
			return null;
221
		} elseif ($group = $this->get($gid)) {
222
			return $group;
223
		} else {
224
			$this->emit('\OC\Group', 'preCreate', [$gid]);
225
			foreach ($this->backends as $backend) {
226
				if ($backend->implementsActions(Backend::CREATE_GROUP)) {
227
					if ($backend->createGroup($gid)) {
0 ignored issues
show
Bug introduced by
The method createGroup() does not exist on OCP\GroupInterface. It seems like you code against a sub-type of said class. However, the method does not exist in OCP\Group\Backend\ABackend or OC\Group\Backend. Are you sure you never get one of those? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

227
					if ($backend->/** @scrutinizer ignore-call */ createGroup($gid)) {
Loading history...
228
						$group = $this->getGroupObject($gid);
229
						$this->emit('\OC\Group', 'postCreate', [$group]);
230
						return $group;
231
					}
232
				}
233
			}
234
			return null;
235
		}
236
	}
237
238
	/**
239
	 * @param string $search
240
	 * @param int $limit
241
	 * @param int $offset
242
	 * @return \OC\Group\Group[]
243
	 */
244
	public function search($search, $limit = null, $offset = null) {
245
		$groups = [];
246
		foreach ($this->backends as $backend) {
247
			$groupIds = $backend->getGroups($search, $limit, $offset);
248
			foreach ($groupIds as $groupId) {
249
				$aGroup = $this->get($groupId);
250
				if ($aGroup instanceof IGroup) {
251
					$groups[$groupId] = $aGroup;
252
				} else {
253
					$this->logger->debug('Group "' . $groupId . '" was returned by search but not found through direct access', ['app' => 'core']);
254
				}
255
			}
256
			if (!is_null($limit) and $limit <= 0) {
257
				return array_values($groups);
258
			}
259
		}
260
		return array_values($groups);
261
	}
262
263
	/**
264
	 * @param IUser|null $user
265
	 * @return \OC\Group\Group[]
266
	 */
267
	public function getUserGroups(IUser $user = null) {
268
		if (!$user instanceof IUser) {
269
			return [];
270
		}
271
		return $this->getUserIdGroups($user->getUID());
272
	}
273
274
	/**
275
	 * @param string $uid the user id
276
	 * @return \OC\Group\Group[]
277
	 */
278
	public function getUserIdGroups($uid) {
279
		$groups = [];
280
281
		foreach ($this->getUserIdGroupIds($uid) as $groupId) {
282
			$aGroup = $this->get($groupId);
0 ignored issues
show
Bug introduced by
$groupId of type OCP\GroupInterface is incompatible with the type string expected by parameter $gid of OC\Group\Manager::get(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

282
			$aGroup = $this->get(/** @scrutinizer ignore-type */ $groupId);
Loading history...
283
			if ($aGroup instanceof IGroup) {
284
				$groups[$groupId] = $aGroup;
285
			} else {
286
				$this->logger->debug('User "' . $uid . '" belongs to deleted group: "' . $groupId . '"', ['app' => 'core']);
0 ignored issues
show
Bug introduced by
Are you sure $groupId of type OCP\GroupInterface can be used in concatenation? Consider adding a __toString()-method. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

286
				$this->logger->debug('User "' . $uid . '" belongs to deleted group: "' . /** @scrutinizer ignore-type */ $groupId . '"', ['app' => 'core']);
Loading history...
287
			}
288
		}
289
290
		return $groups;
291
	}
292
293
	/**
294
	 * Checks if a userId is in the admin group
295
	 *
296
	 * @param string $userId
297
	 * @return bool if admin
298
	 */
299
	public function isAdmin($userId) {
300
		foreach ($this->backends as $backend) {
301
			if ($backend->implementsActions(Backend::IS_ADMIN) && $backend->isAdmin($userId)) {
0 ignored issues
show
Bug introduced by
The method isAdmin() does not exist on OCP\GroupInterface. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

301
			if ($backend->implementsActions(Backend::IS_ADMIN) && $backend->/** @scrutinizer ignore-call */ isAdmin($userId)) {

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
302
				return true;
303
			}
304
		}
305
		return $this->isInGroup($userId, 'admin');
306
	}
307
308
	/**
309
	 * Checks if a userId is in a group
310
	 *
311
	 * @param string $userId
312
	 * @param string $group
313
	 * @return bool if in group
314
	 */
315
	public function isInGroup($userId, $group) {
316
		return array_search($group, $this->getUserIdGroupIds($userId)) !== false;
317
	}
318
319
	/**
320
	 * get a list of group ids for a user
321
	 *
322
	 * @param IUser $user
323
	 * @return array with group ids
324
	 */
325
	public function getUserGroupIds(IUser $user) {
326
		return $this->getUserIdGroupIds($user->getUID());
327
	}
328
329
	/**
330
	 * @param string $uid the user id
331
	 * @return GroupInterface[]
332
	 */
333
	private function getUserIdGroupIds($uid) {
334
		if (!isset($this->cachedUserGroups[$uid])) {
335
			$groups = [];
336
			foreach ($this->backends as $backend) {
337
				if ($groupIds = $backend->getUserGroups($uid)) {
338
					$groups = array_merge($groups, $groupIds);
339
				}
340
			}
341
			$this->cachedUserGroups[$uid] = $groups;
342
		}
343
344
		return $this->cachedUserGroups[$uid];
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->cachedUserGroups[$uid] returns the type string[] which is incompatible with the documented return type OCP\GroupInterface[].
Loading history...
345
	}
346
347
	/**
348
	 * get an array of groupid and displayName for a user
349
	 *
350
	 * @param IUser $user
351
	 * @return array ['displayName' => displayname]
352
	 */
353
	public function getUserGroupNames(IUser $user) {
354
		return array_map(function ($group) {
355
			return ['displayName' => $group->getDisplayName()];
356
		}, $this->getUserGroups($user));
357
	}
358
359
	/**
360
	 * get a list of all display names in a group
361
	 *
362
	 * @param string $gid
363
	 * @param string $search
364
	 * @param int $limit
365
	 * @param int $offset
366
	 * @return array an array of display names (value) and user ids (key)
367
	 */
368
	public function displayNamesInGroup($gid, $search = '', $limit = -1, $offset = 0) {
369
		$group = $this->get($gid);
370
		if (is_null($group)) {
371
			return [];
372
		}
373
374
		$search = trim($search);
375
		$groupUsers = [];
376
377
		if (!empty($search)) {
378
			// only user backends have the capability to do a complex search for users
379
			$searchOffset = 0;
380
			$searchLimit = $limit * 100;
381
			if ($limit === -1) {
382
				$searchLimit = 500;
383
			}
384
385
			do {
386
				$filteredUsers = $this->userManager->searchDisplayName($search, $searchLimit, $searchOffset);
387
				foreach ($filteredUsers as $filteredUser) {
388
					if ($group->inGroup($filteredUser)) {
389
						$groupUsers[] = $filteredUser;
390
					}
391
				}
392
				$searchOffset += $searchLimit;
393
			} while (count($groupUsers) < $searchLimit + $offset && count($filteredUsers) >= $searchLimit);
394
395
			if ($limit === -1) {
396
				$groupUsers = array_slice($groupUsers, $offset);
397
			} else {
398
				$groupUsers = array_slice($groupUsers, $offset, $limit);
399
			}
400
		} else {
401
			$groupUsers = $group->searchUsers('', $limit, $offset);
402
		}
403
404
		$matchingUsers = [];
405
		foreach ($groupUsers as $groupUser) {
406
			$matchingUsers[(string) $groupUser->getUID()] = $groupUser->getDisplayName();
407
		}
408
		return $matchingUsers;
409
	}
410
411
	/**
412
	 * @return \OC\SubAdmin
413
	 */
414
	public function getSubAdmin() {
415
		if (!$this->subAdmin) {
416
			$this->subAdmin = new \OC\SubAdmin(
417
				$this->userManager,
418
				$this,
419
				\OC::$server->getDatabaseConnection()
420
			);
421
		}
422
423
		return $this->subAdmin;
424
	}
425
}
426