Passed
Push — master ( 68a02b...b29c3a )
by Christoph
15:41 queued 11s
created

GroupPrincipalBackend::updatePrincipal()   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 2
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2018, Georg Ehrke
5
 *
6
 * @author Arthur Schiwon <[email protected]>
7
 * @author Christoph Wurst <[email protected]>
8
 * @author Georg Ehrke <[email protected]>
9
 * @author John Molakvoæ (skjnldsv) <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Thomas Müller <[email protected]>
12
 *
13
 * @license AGPL-3.0
14
 *
15
 * This code is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License, version 3,
17
 * as published by the Free Software Foundation.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License, version 3,
25
 * along with this program. If not, see <http://www.gnu.org/licenses/>
26
 *
27
 */
28
29
namespace OCA\DAV\DAV;
30
31
use OCP\Constants;
32
use OCP\IConfig;
33
use OCP\IGroup;
34
use OCP\IGroupManager;
35
use OCP\IUser;
36
use OCP\IUserSession;
37
use OCP\Share\IManager as IShareManager;
38
use Sabre\DAV\Exception;
39
use Sabre\DAV\PropPatch;
40
use Sabre\DAVACL\PrincipalBackend\BackendInterface;
41
42
class GroupPrincipalBackend implements BackendInterface {
43
	public const PRINCIPAL_PREFIX = 'principals/groups';
44
45
	/** @var IGroupManager */
46
	private $groupManager;
47
48
	/** @var IUserSession */
49
	private $userSession;
50
51
	/** @var IShareManager */
52
	private $shareManager;
53
	/** @var IConfig */
54
	private $config;
55
56
	/**
57
	 * @param IGroupManager $IGroupManager
58
	 * @param IUserSession $userSession
59
	 * @param IShareManager $shareManager
60
	 */
61
	public function __construct(
62
		IGroupManager $IGroupManager,
63
		IUserSession $userSession,
64
		IShareManager $shareManager,
65
		IConfig $config
66
	) {
67
		$this->groupManager = $IGroupManager;
68
		$this->userSession = $userSession;
69
		$this->shareManager = $shareManager;
70
		$this->config = $config;
71
	}
72
73
	/**
74
	 * Returns a list of principals based on a prefix.
75
	 *
76
	 * This prefix will often contain something like 'principals'. You are only
77
	 * expected to return principals that are in this base path.
78
	 *
79
	 * You are expected to return at least a 'uri' for every user, you can
80
	 * return any additional properties if you wish so. Common properties are:
81
	 *   {DAV:}displayname
82
	 *
83
	 * @param string $prefixPath
84
	 * @return string[]
85
	 */
86
	public function getPrincipalsByPrefix($prefixPath) {
87
		$principals = [];
88
89
		if ($prefixPath === self::PRINCIPAL_PREFIX) {
90
			foreach ($this->groupManager->search('') as $user) {
91
				$principals[] = $this->groupToPrincipal($user);
92
			}
93
		}
94
95
		return $principals;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $principals returns an array which contains values of type array<string,string> which are incompatible with the documented value type string.
Loading history...
96
	}
97
98
	/**
99
	 * Returns a specific principal, specified by it's path.
100
	 * The returned structure should be the exact same as from
101
	 * getPrincipalsByPrefix.
102
	 *
103
	 * @param string $path
104
	 * @return array
105
	 */
106
	public function getPrincipalByPath($path) {
107
		$elements = explode('/', $path,  3);
108
		if ($elements[0] !== 'principals') {
109
			return null;
110
		}
111
		if ($elements[1] !== 'groups') {
112
			return null;
113
		}
114
		$name = urldecode($elements[2]);
115
		$group = $this->groupManager->get($name);
116
117
		if (!is_null($group)) {
118
			return $this->groupToPrincipal($group);
119
		}
120
121
		return null;
122
	}
123
124
	/**
125
	 * Returns the list of members for a group-principal
126
	 *
127
	 * @param string $principal
128
	 * @return string[]
129
	 * @throws Exception
130
	 */
131
	public function getGroupMemberSet($principal) {
132
		$elements = explode('/', $principal);
133
		if ($elements[0] !== 'principals') {
134
			return [];
135
		}
136
		if ($elements[1] !== 'groups') {
137
			return [];
138
		}
139
		$name = $elements[2];
140
		$group = $this->groupManager->get($name);
141
142
		if (is_null($group)) {
143
			return [];
144
		}
145
146
		return array_map(function ($user) {
147
			return $this->userToPrincipal($user);
148
		}, $group->getUsers());
149
	}
150
151
	/**
152
	 * Returns the list of groups a principal is a member of
153
	 *
154
	 * @param string $principal
155
	 * @return array
156
	 * @throws Exception
157
	 */
158
	public function getGroupMembership($principal) {
159
		return [];
160
	}
161
162
	/**
163
	 * Updates the list of group members for a group principal.
164
	 *
165
	 * The principals should be passed as a list of uri's.
166
	 *
167
	 * @param string $principal
168
	 * @param string[] $members
169
	 * @throws Exception
170
	 */
171
	public function setGroupMemberSet($principal, array $members) {
172
		throw new Exception('Setting members of the group is not supported yet');
173
	}
174
175
	/**
176
	 * @param string $path
177
	 * @param PropPatch $propPatch
178
	 * @return int
179
	 */
180
	public function updatePrincipal($path, PropPatch $propPatch) {
181
		return 0;
182
	}
183
184
	/**
185
	 * @param string $prefixPath
186
	 * @param array $searchProperties
187
	 * @param string $test
188
	 * @return array
189
	 */
190
	public function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
191
		$results = [];
192
193
		if (\count($searchProperties) === 0) {
194
			return [];
195
		}
196
		if ($prefixPath !== self::PRINCIPAL_PREFIX) {
197
			return [];
198
		}
199
		// If sharing or group sharing is disabled, return the empty array
200
		if (!$this->groupSharingEnabled()) {
201
			return [];
202
		}
203
204
		// If sharing is restricted to group members only,
205
		// return only members that have groups in common
206
		$restrictGroups = false;
207
		if ($this->shareManager->shareWithGroupMembersOnly()) {
208
			$user = $this->userSession->getUser();
209
			if (!$user) {
210
				return [];
211
			}
212
213
			$restrictGroups = $this->groupManager->getUserGroupIds($user);
214
		}
215
216
		$searchLimit = $this->config->getSystemValueInt('sharing.maxAutocompleteResults', Constants::SHARING_MAX_AUTOCOMPLETE_RESULTS_DEFAULT);
217
		if ($searchLimit <= 0) {
218
			$searchLimit = null;
219
		}
220
		foreach ($searchProperties as $prop => $value) {
221
			switch ($prop) {
222
				case '{DAV:}displayname':
223
					$groups = $this->groupManager->search($value, $searchLimit);
224
225
					$results[] = array_reduce($groups, function (array $carry, IGroup $group) use ($restrictGroups) {
226
						$gid = $group->getGID();
227
						// is sharing restricted to groups only?
228
						if ($restrictGroups !== false) {
229
							if (!\in_array($gid, $restrictGroups, true)) {
230
								return $carry;
231
							}
232
						}
233
234
						$carry[] = self::PRINCIPAL_PREFIX . '/' . urlencode($gid);
235
						return $carry;
236
					}, []);
237
					break;
238
239
				case '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set':
240
					// If you add support for more search properties that qualify as a user-address,
241
					// please also add them to the array below
242
					$results[] = $this->searchPrincipals(self::PRINCIPAL_PREFIX, [
243
					], 'anyof');
244
					break;
245
246
				default:
247
					$results[] = [];
248
					break;
249
			}
250
		}
251
252
		// results is an array of arrays, so this is not the first search result
253
		// but the results of the first searchProperty
254
		if (count($results) === 1) {
255
			return $results[0];
256
		}
257
258
		switch ($test) {
259
			case 'anyof':
260
				return array_values(array_unique(array_merge(...$results)));
261
262
			case 'allof':
263
			default:
264
				return array_values(array_intersect(...$results));
265
		}
266
	}
267
268
	/**
269
	 * @param string $uri
270
	 * @param string $principalPrefix
271
	 * @return string
272
	 */
273
	public function findByUri($uri, $principalPrefix) {
274
		// If sharing is disabled, return the empty array
275
		if (!$this->groupSharingEnabled()) {
276
			return null;
277
		}
278
279
		// If sharing is restricted to group members only,
280
		// return only members that have groups in common
281
		$restrictGroups = false;
282
		if ($this->shareManager->shareWithGroupMembersOnly()) {
283
			$user = $this->userSession->getUser();
284
			if (!$user) {
285
				return null;
286
			}
287
288
			$restrictGroups = $this->groupManager->getUserGroupIds($user);
289
		}
290
291
		if (strpos($uri, 'principal:principals/groups/') === 0) {
292
			$name = urlencode(substr($uri, 28));
293
			if ($restrictGroups !== false && !\in_array($name, $restrictGroups, true)) {
294
				return null;
295
			}
296
297
			return substr($uri, 10);
298
		}
299
300
		return null;
301
	}
302
303
	/**
304
	 * @param IGroup $group
305
	 * @return array
306
	 */
307
	protected function groupToPrincipal($group) {
308
		$groupId = $group->getGID();
309
		// getDisplayName returns UID if none
310
		$displayName = $group->getDisplayName();
311
312
		return [
313
			'uri' => 'principals/groups/' . urlencode($groupId),
314
			'{DAV:}displayname' => $displayName,
315
			'{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'GROUP',
316
		];
317
	}
318
319
	/**
320
	 * @param IUser $user
321
	 * @return array
322
	 */
323
	protected function userToPrincipal($user) {
324
		$userId = $user->getUID();
325
		// getDisplayName returns UID if none
326
		$displayName = $user->getDisplayName();
327
328
		$principal = [
329
			'uri' => 'principals/users/' . $userId,
330
			'{DAV:}displayname' => $displayName,
331
			'{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL',
332
		];
333
334
		$email = $user->getEMailAddress();
335
		if (!empty($email)) {
336
			$principal['{http://sabredav.org/ns}email-address'] = $email;
337
		}
338
339
		return $principal;
340
	}
341
342
	/**
343
	 * @return bool
344
	 */
345
	private function groupSharingEnabled(): bool {
346
		return $this->shareManager->shareApiEnabled() && $this->shareManager->allowGroupSharing();
347
	}
348
}
349