Completed
Push — master ( e6780c...89b6ee )
by Morris
33:24 queued 16:36
created

AbstractPrincipalBackend::searchPrincipals()   C

Complexity

Conditions 14
Paths 35

Size

Total Lines 76

Duplication

Lines 44
Ratio 57.89 %

Importance

Changes 0
Metric Value
cc 14
nc 35
nop 3
dl 44
loc 76
rs 5.5369
c 0
b 0
f 0

How to fix   Long Method    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 2018, Georg Ehrke <[email protected]>
4
 *
5
 * @author Georg Ehrke <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
namespace OCA\DAV\CalDAV\ResourceBooking;
24
25
use OCP\IDBConnection;
26
use OCP\IGroupManager;
27
use OCP\ILogger;
28
use OCP\IUserSession;
29
use Sabre\DAVACL\PrincipalBackend\BackendInterface;
30
use Sabre\DAV\Exception;
31
use \Sabre\DAV\PropPatch;
32
33
abstract class AbstractPrincipalBackend implements BackendInterface {
34
35
	/** @var IDBConnection */
36
	private $db;
37
38
	/** @var IUserSession */
39
	private $userSession;
40
41
	/** @var IGroupManager */
42
	private $groupManager;
43
44
	/** @var ILogger */
45
	private $logger;
46
47
	/** @var string */
48
	private $principalPrefix;
49
50
	/** @var string */
51
	private $dbTableName;
52
53
	/**
54
	 * @param IDBConnection $dbConnection
55
	 * @param IUserSession $userSession
56
	 * @param IGroupManager $groupManager
57
	 * @param ILogger $logger
58
	 * @param string $principalPrefix
59
	 * @param string $dbPrefix
60
	 */
61
	public function __construct(IDBConnection $dbConnection,
62
								IUserSession $userSession,
63
								IGroupManager $groupManager,
64
								ILogger $logger,
65
								$principalPrefix, $dbPrefix) {
66
		$this->db = $dbConnection;
67
		$this->userSession = $userSession;
68
		$this->groupManager = $groupManager;
69
		$this->logger = $logger;
70
		$this->principalPrefix = $principalPrefix;
71
		$this->dbTableName = 'calendar_' . $dbPrefix . '_cache';
72
	}
73
74
	/**
75
	 * Returns a list of principals based on a prefix.
76
	 *
77
	 * This prefix will often contain something like 'principals'. You are only
78
	 * expected to return principals that are in this base path.
79
	 *
80
	 * You are expected to return at least a 'uri' for every user, you can
81
	 * return any additional properties if you wish so. Common properties are:
82
	 *   {DAV:}displayname
83
	 *
84
	 * @param string $prefixPath
85
	 * @return string[]
86
	 */
87
	public function getPrincipalsByPrefix($prefixPath) {
88
		$principals = [];
89
90
		if ($prefixPath === $this->principalPrefix) {
91
			$query = $this->db->getQueryBuilder();
92
			$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname'])
93
				->from($this->dbTableName);
94
			$stmt = $query->execute();
95
96
			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
97
				$principals[] = $this->rowToPrincipal($row);
98
			}
99
100
			$stmt->closeCursor();
101
		}
102
103
		return $principals;
104
	}
105
106
	/**
107
	 * Returns a specific principal, specified by it's path.
108
	 * The returned structure should be the exact same as from
109
	 * getPrincipalsByPrefix.
110
	 *
111
	 * @param string $path
112
	 * @return array
113
	 */
114
	public function getPrincipalByPath($path) {
115
		if (strpos($path, $this->principalPrefix) !== 0) {
116
			return null;
117
		}
118
		list(, $name) = \Sabre\Uri\split($path);
119
120
		list($backendId, $resourceId) = explode('-',  $name, 2);
121
122
		$query = $this->db->getQueryBuilder();
123
		$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname'])
124
			->from($this->dbTableName)
125
			->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
126
			->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
127
		$stmt = $query->execute();
128
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
129
130
		if(!$row) {
131
			return null;
132
		}
133
134
		return $this->rowToPrincipal($row);
135
	}
136
137
	/**
138
	 * Returns the list of members for a group-principal
139
	 *
140
	 * @param string $principal
141
	 * @return string[]
142
	 */
143
	public function getGroupMemberSet($principal) {
144
		return [];
145
	}
146
147
	/**
148
	 * Returns the list of groups a principal is a member of
149
	 *
150
	 * @param string $principal
151
	 * @return array
152
	 */
153
	public function getGroupMembership($principal) {
154
		return [];
155
	}
156
157
	/**
158
	 * Updates the list of group members for a group principal.
159
	 *
160
	 * The principals should be passed as a list of uri's.
161
	 *
162
	 * @param string $principal
163
	 * @param string[] $members
164
	 * @throws Exception
165
	 */
166
	public function setGroupMemberSet($principal, array $members) {
167
		throw new Exception('Setting members of the group is not supported yet');
168
	}
169
170
	/**
171
	 * @param string $path
172
	 * @param PropPatch $propPatch
173
	 * @return int
174
	 */
175
	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...
176
		return 0;
177
	}
178
179
	/**
180
	 * @param string $prefixPath
181
	 * @param array $searchProperties
182
	 * @param string $test
183
	 * @return array
184
	 */
185
	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...
186
		$results = [];
187
		if (\count($searchProperties) === 0) {
188
			return [];
189
		}
190
		if ($prefixPath !== $this->principalPrefix) {
191
			return [];
192
		}
193
194
		$user = $this->userSession->getUser();
195
		if (!$user) {
196
			return [];
197
		}
198
		$usersGroups = $this->groupManager->getUserGroupIds($user);
199
200
		foreach ($searchProperties as $prop => $value) {
201
			switch ($prop) {
202 View Code Duplication
				case '{http://sabredav.org/ns}email-address':
203
					$query = $this->db->getQueryBuilder();
204
					$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
205
						->from($this->dbTableName)
206
						->where($query->expr()->iLike('email', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%')));
207
208
					$stmt = $query->execute();
209
					$principals = [];
210
					while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
211
						if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
212
							continue;
213
						}
214
						$principals[] = $this->rowToPrincipal($row)['uri'];
215
					}
216
					$results[] = $principals;
217
218
					$stmt->closeCursor();
219
					break;
220
221 View Code Duplication
				case '{DAV:}displayname':
222
					$query = $this->db->getQueryBuilder();
223
					$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
224
						->from($this->dbTableName)
225
						->where($query->expr()->iLike('displayname', $query->createNamedParameter('%' . $this->db->escapeLikeParameter($value) . '%')));
226
227
					$stmt = $query->execute();
228
					$principals = [];
229
					while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
230
						if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
231
							continue;
232
						}
233
						$principals[] = $this->rowToPrincipal($row)['uri'];
234
					}
235
					$results[] = $principals;
236
237
					$stmt->closeCursor();
238
					break;
239
240
				default:
241
					$results[] = [];
242
					break;
243
			}
244
		}
245
246
		// results is an array of arrays, so this is not the first search result
247
		// but the results of the first searchProperty
248
		if (count($results) === 1) {
249
			return $results[0];
250
		}
251
252 View Code Duplication
		switch ($test) {
253
			case 'anyof':
254
				return array_values(array_unique(array_merge(...$results)));
255
256
			case 'allof':
257
			default:
258
				return array_values(array_intersect(...$results));
259
		}
260
	}
261
262
	/**
263
	 * @param string $uri
264
	 * @param string $principalPrefix
265
	 * @return null|string
266
	 */
267
	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...
268
		$user = $this->userSession->getUser();
269
		if (!$user) {
270
			return null;
271
		}
272
		$usersGroups = $this->groupManager->getUserGroupIds($user);
273
274
		if (strpos($uri, 'mailto:') === 0) {
275
			$email = substr($uri, 7);
276
			$query = $this->db->getQueryBuilder();
277
			$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
278
				->from($this->dbTableName)
279
				->where($query->expr()->eq('email', $query->createNamedParameter($email)));
280
281
			$stmt = $query->execute();
282
			$row = $stmt->fetch(\PDO::FETCH_ASSOC);
283
284
			if(!$row) {
285
				return null;
286
			}
287
			if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
288
				return null;
289
			}
290
291
			return $this->rowToPrincipal($row)['uri'];
292
		}
293
294
		if (strpos($uri, 'principal:') === 0) {
295
			$path = substr($uri, 10);
296
			if (strpos($path, $this->principalPrefix) !== 0) {
297
				return null;
298
			}
299
300
			list(, $name) = \Sabre\Uri\split($path);
301
			list($backendId, $resourceId) = explode('-',  $name, 2);
302
303
			$query = $this->db->getQueryBuilder();
304
			$query->select(['id', 'backend_id', 'resource_id', 'email', 'displayname', 'group_restrictions'])
305
				->from($this->dbTableName)
306
				->where($query->expr()->eq('backend_id', $query->createNamedParameter($backendId)))
307
				->andWhere($query->expr()->eq('resource_id', $query->createNamedParameter($resourceId)));
308
			$stmt = $query->execute();
309
			$row = $stmt->fetch(\PDO::FETCH_ASSOC);
310
311
			if(!$row) {
312
				return null;
313
			}
314
			if (!$this->isAllowedToAccessResource($row, $usersGroups)) {
315
				return null;
316
			}
317
318
			return $this->rowToPrincipal($row)['uri'];
319
		}
320
321
		return null;
322
	}
323
324
	/**
325
	 * convert database row to principal
326
	 */
327
	private function rowToPrincipal($row) {
328
		return [
329
			'uri' => $this->principalPrefix . '/' . $row['backend_id'] . '-' . $row['resource_id'],
330
			'{DAV:}displayname' => $row['displayname'],
331
			'{http://sabredav.org/ns}email-address' => $row['email']
332
		];
333
	}
334
335
	/**
336
	 * @param $row
337
	 * @param $userGroups
338
	 * @return bool
339
	 */
340
	private function isAllowedToAccessResource($row, $userGroups) {
341
		if (!isset($row['group_restrictions']) ||
342
			$row['group_restrictions'] === null ||
343
			$row['group_restrictions'] === '') {
344
			return true;
345
		}
346
347
		// group restrictions contains something, but not parsable, deny access and log warning
348
		$json = json_decode($row['group_restrictions']);
349
		if (!\is_array($json)) {
350
			$this->logger->info('group_restrictions field could not be parsed for ' . $this->dbTableName . '::' . $row['id'] . ', denying access to resource');
351
			return false;
352
		}
353
354
		// empty array => no group restrictions
355
		if (empty($json)) {
356
			return true;
357
		}
358
359
		return !empty(array_intersect($json, $userGroups));
360
	}
361
}
362