Completed
Push — stable13 ( f4efa5...c5b0ef )
by Roeland
13:00
created

Principal::findByUri()   B

Complexity

Conditions 11
Paths 22

Size

Total Lines 47

Duplication

Lines 14
Ratio 29.79 %

Importance

Changes 0
Metric Value
cc 11
nc 22
nop 2
dl 14
loc 47
rs 7.3166
c 0
b 0
f 0

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bart Visscher <[email protected]>
6
 * @author Jakob Sack <[email protected]>
7
 * @author Jörn Friedrich Dreyer <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author Morris Jobke <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 * @author Thomas Tanghus <[email protected]>
13
 * @author Vincent Petry <[email protected]>
14
 *
15
 * @license AGPL-3.0
16
 *
17
 * This code is free software: you can redistribute it and/or modify
18
 * it under the terms of the GNU Affero General Public License, version 3,
19
 * as published by the Free Software Foundation.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License, version 3,
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
28
 *
29
 */
30
31
namespace OCA\DAV\Connector\Sabre;
32
33
use OCP\IConfig;
34
use OCP\IGroup;
35
use OCP\IGroupManager;
36
use OCP\IUser;
37
use OCP\IUserManager;
38
use OCP\IUserSession;
39
use OCP\Share\IManager as IShareManager;
40
use Sabre\DAV\Exception;
41
use \Sabre\DAV\PropPatch;
42
use Sabre\DAVACL\PrincipalBackend\BackendInterface;
43
use Sabre\HTTP\URLUtil;
44
45
class Principal implements BackendInterface {
46
47
	/** @var IUserManager */
48
	private $userManager;
49
50
	/** @var IGroupManager */
51
	private $groupManager;
52
53
	/** @var IShareManager */
54
	private $shareManager;
55
56
	/** @var IUserSession */
57
	private $userSession;
58
59
	/** @var IConfig */
60
	private $config;
61
62
	/** @var string */
63
	private $principalPrefix;
64
65
	/** @var bool */
66
	private $hasGroups;
67
68
	/**
69
	 * @param IUserManager $userManager
70
	 * @param IGroupManager $groupManager
71
	 * @param IShareManager $shareManager
72
	 * @param IUserSession $userSession
73
	 * @param IConfig $config
74
	 * @param string $principalPrefix
75
	 */
76
	public function __construct(IUserManager $userManager,
77
								IGroupManager $groupManager,
78
								IShareManager $shareManager,
79
								IUserSession $userSession,
80
								IConfig $config,
81
								$principalPrefix = 'principals/users/') {
82
		$this->userManager = $userManager;
83
		$this->groupManager = $groupManager;
84
		$this->shareManager = $shareManager;
85
		$this->userSession = $userSession;
86
		$this->config = $config;
87
		$this->principalPrefix = trim($principalPrefix, '/');
88
		$this->hasGroups = ($principalPrefix === 'principals/users/');
89
	}
90
91
	/**
92
	 * Returns a list of principals based on a prefix.
93
	 *
94
	 * This prefix will often contain something like 'principals'. You are only
95
	 * expected to return principals that are in this base path.
96
	 *
97
	 * You are expected to return at least a 'uri' for every user, you can
98
	 * return any additional properties if you wish so. Common properties are:
99
	 *   {DAV:}displayname
100
	 *
101
	 * @param string $prefixPath
102
	 * @return string[]
103
	 */
104
	public function getPrincipalsByPrefix($prefixPath) {
105
		$principals = [];
106
107
		if ($prefixPath === $this->principalPrefix) {
108
			foreach($this->userManager->search('') as $user) {
109
				$principals[] = $this->userToPrincipal($user);
110
			}
111
		}
112
113
		return $principals;
114
	}
115
116
	/**
117
	 * Returns a specific principal, specified by it's path.
118
	 * The returned structure should be the exact same as from
119
	 * getPrincipalsByPrefix.
120
	 *
121
	 * @param string $path
122
	 * @return array
123
	 */
124
	public function getPrincipalByPath($path) {
125
		list($prefix, $name) = \Sabre\Uri\split($path);
126
127
		if ($prefix === $this->principalPrefix) {
128
			$user = $this->userManager->get($name);
129
130
			if ($user !== null) {
131
				return $this->userToPrincipal($user);
132
			}
133
		}
134
		return null;
135
	}
136
137
	/**
138
	 * Returns the list of members for a group-principal
139
	 *
140
	 * @param string $principal
141
	 * @return string[]
142
	 * @throws Exception
143
	 */
144 View Code Duplication
	public function getGroupMemberSet($principal) {
145
		// TODO: for now the group principal has only one member, the user itself
146
		$principal = $this->getPrincipalByPath($principal);
147
		if (!$principal) {
148
			throw new Exception('Principal not found');
149
		}
150
151
		return [$principal['uri']];
152
	}
153
154
	/**
155
	 * Returns the list of groups a principal is a member of
156
	 *
157
	 * @param string $principal
158
	 * @param bool $needGroups
159
	 * @return array
160
	 * @throws Exception
161
	 */
162
	public function getGroupMembership($principal, $needGroups = false) {
163
		list($prefix, $name) = \Sabre\Uri\split($principal);
164
165
		if ($prefix === $this->principalPrefix) {
166
			$user = $this->userManager->get($name);
167
			if (!$user) {
168
				throw new Exception('Principal not found');
169
			}
170
171
			if ($this->hasGroups || $needGroups) {
172
				$groups = $this->groupManager->getUserGroups($user);
173
				$groups = array_map(function($group) {
174
					/** @var IGroup $group */
175
					return 'principals/groups/' . urlencode($group->getGID());
176
				}, $groups);
177
178
				return $groups;
179
			}
180
		}
181
		return [];
182
	}
183
184
	/**
185
	 * Updates the list of group members for a group principal.
186
	 *
187
	 * The principals should be passed as a list of uri's.
188
	 *
189
	 * @param string $principal
190
	 * @param string[] $members
191
	 * @throws Exception
192
	 */
193
	public function setGroupMemberSet($principal, array $members) {
194
		throw new Exception('Setting members of the group is not supported yet');
195
	}
196
197
	/**
198
	 * @param string $path
199
	 * @param PropPatch $propPatch
200
	 * @return int
201
	 */
202
	function updatePrincipal($path, PropPatch $propPatch) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
203
		return 0;
204
	}
205
206
	/**
207
	 * Search user principals
208
	 *
209
	 * @param array $searchProperties
210
	 * @param string $test
211
	 * @return array
212
	 */
213
	protected function searchUserPrincipals(array $searchProperties, $test = 'allof') {
214
		$results = [];
215
216
		// If sharing is disabled, return the empty array
217
		$shareAPIEnabled = $this->shareManager->shareApiEnabled();
218
		if (!$shareAPIEnabled) {
219
			return [];
220
		}
221
222
		// If sharing is restricted to group members only,
223
		// return only members that have groups in common
224
		$restrictGroups = false;
225 View Code Duplication
		if ($this->shareManager->shareWithGroupMembersOnly()) {
226
			$user = $this->userSession->getUser();
227
			if (!$user) {
228
				return [];
229
			}
230
231
			$restrictGroups = $this->groupManager->getUserGroupIds($user);
232
		}
233
234
		foreach ($searchProperties as $prop => $value) {
235
			switch ($prop) {
236
				case '{http://sabredav.org/ns}email-address':
237
					$users = $this->userManager->getByEmail($value);
238
239
					$results[] = array_reduce($users, function(array $carry, IUser $user) use ($restrictGroups) {
240
						// is sharing restricted to groups only?
241 View Code Duplication
						if ($restrictGroups !== false) {
242
							$userGroups = $this->groupManager->getUserGroupIds($user);
243
							if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
244
								return $carry;
245
							}
246
						}
247
248
						$carry[] = $this->principalPrefix . '/' . $user->getUID();
249
						return $carry;
250
					}, []);
251
					break;
252
253
				default:
254
					$results[] = [];
255
					break;
256
			}
257
		}
258
259
		// results is an array of arrays, so this is not the first search result
260
		// but the results of the first searchProperty
261
		if (count($results) === 1) {
262
			return $results[0];
263
		}
264
265
		switch ($test) {
266
			case 'anyof':
267
				return array_unique(array_merge(...$results));
268
269
			case 'allof':
270
			default:
271
				return array_intersect(...$results);
272
		}
273
	}
274
275
	/**
276
	 * @param string $prefixPath
277
	 * @param array $searchProperties
278
	 * @param string $test
279
	 * @return array
280
	 */
281
	function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
282
		if (count($searchProperties) === 0) {
283
			return [];
284
		}
285
286
		switch ($prefixPath) {
287
			case 'principals/users':
288
				return $this->searchUserPrincipals($searchProperties, $test);
289
290
			default:
291
				return [];
292
		}
293
	}
294
295
	/**
296
	 * @param string $uri
297
	 * @param string $principalPrefix
298
	 * @return string
299
	 */
300
	function findByUri($uri, $principalPrefix) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
301
		// If sharing is disabled, return the empty array
302
		$shareAPIEnabled = $this->shareManager->shareApiEnabled();
303
		if (!$shareAPIEnabled) {
304
			return null;
305
		}
306
307
		// If sharing is restricted to group members only,
308
		// return only members that have groups in common
309
		$restrictGroups = false;
310 View Code Duplication
		if ($this->shareManager->shareWithGroupMembersOnly()) {
311
			$user = $this->userSession->getUser();
312
			if (!$user) {
313
				return null;
314
			}
315
316
			$restrictGroups = $this->groupManager->getUserGroupIds($user);
317
		}
318
319
		if (strpos($uri, 'mailto:') === 0) {
320
			if ($principalPrefix === 'principals/users') {
321
				$users = $this->userManager->getByEmail(substr($uri, 7));
322
				if (count($users) !== 1) {
323
					return null;
324
				}
325
				$user = $users[0];
326
327 View Code Duplication
				if ($restrictGroups !== false) {
328
					$userGroups = $this->groupManager->getUserGroupIds($user);
329
					if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
330
						return null;
331
					}
332
				}
333
334
				return $this->principalPrefix . '/' . $user->getUID();
335
			}
336
		}
337
		if (substr($uri, 0, 10) === 'principal:') {
338
			$principal = substr($uri, 10);
339
			$principal = $this->getPrincipalByPath($principal);
340
			if ($principal !== null) {
341
				return $principal['uri'];
342
			}
343
		}
344
345
		return null;
346
	}
347
348
	/**
349
	 * @param IUser $user
350
	 * @return array
351
	 */
352
	protected function userToPrincipal($user) {
353
		$userId = $user->getUID();
354
		$displayName = $user->getDisplayName();
355
		$principal = [
356
				'uri' => $this->principalPrefix . '/' . $userId,
357
				'{DAV:}displayname' => is_null($displayName) ? $userId : $displayName,
358
		];
359
360
		$email = $user->getEMailAddress();
361
		if (!empty($email)) {
362
			$principal['{http://sabredav.org/ns}email-address'] = $email;
363
		}
364
365
		return $principal;
366
	}
367
368
	public function getPrincipalPrefix() {
369
		return $this->principalPrefix;
370
	}
371
372
}
373