Passed
Push — master ( b585ae...489fec )
by Morris
13:22 queued 11s
created

ContactsStore::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 4
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright 2017 Christoph Wurst <[email protected]>
4
 * @copyright 2017 Lukas Reschke <[email protected]>
5
 *
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Daniel Calviño Sánchez <[email protected]>
8
 * @author Georg Ehrke <[email protected]>
9
 * @author Julius Härtl <[email protected]>
10
 * @author Lukas Reschke <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Tobia De Koninck <[email protected]>
13
 *
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
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
27
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
namespace OC\Contacts\ContactsMenu;
32
33
use OCP\Contacts\ContactsMenu\IContactsStore;
34
use OCP\Contacts\ContactsMenu\IEntry;
35
use OCP\Contacts\IManager;
36
use OCP\IConfig;
37
use OCP\IGroupManager;
38
use OCP\IUser;
39
use OCP\IUserManager;
40
41
class ContactsStore implements IContactsStore {
42
43
	/** @var IManager */
44
	private $contactsManager;
45
46
	/** @var IConfig */
47
	private $config;
48
49
	/** @var IUserManager */
50
	private $userManager;
51
52
	/** @var IGroupManager */
53
	private $groupManager;
54
55
	/**
56
	 * @param IManager $contactsManager
57
	 * @param IConfig $config
58
	 * @param IUserManager $userManager
59
	 * @param IGroupManager $groupManager
60
	 */
61
	public function __construct(IManager $contactsManager,
62
								IConfig $config,
63
								IUserManager $userManager,
64
								IGroupManager $groupManager) {
65
		$this->contactsManager = $contactsManager;
66
		$this->config = $config;
67
		$this->userManager = $userManager;
68
		$this->groupManager = $groupManager;
69
	}
70
71
	/**
72
	 * @param IUser $user
73
	 * @param string|null $filter
74
	 * @return IEntry[]
75
	 */
76
	public function getContacts(IUser $user, $filter, ?int $limit = null, ?int $offset = null) {
77
		$options = [];
78
		if ($limit !== null) {
79
			$options['limit'] = $limit;
80
		}
81
		if ($offset !== null) {
82
			$options['offset'] = $offset;
83
		}
84
85
		$allContacts = $this->contactsManager->search(
86
			$filter ?: '',
87
			[
88
				'FN',
89
				'EMAIL'
90
			],
91
			$options
92
		);
93
94
		$entries = array_map(function (array $contact) {
95
			return $this->contactArrayToEntry($contact);
96
		}, $allContacts);
97
		return $this->filterContacts(
98
			$user,
99
			$entries,
100
			$filter
101
		);
102
	}
103
104
	/**
105
	 * Filters the contacts. Applies 3 filters:
106
	 *  1. filter the current user
107
	 *  2. if the `shareapi_allow_share_dialog_user_enumeration` config option is
108
	 * enabled it will filter all local users
109
	 *  3. if the `shareapi_exclude_groups` config option is enabled and the
110
	 * current user is in an excluded group it will filter all local users.
111
	 *  4. if the `shareapi_only_share_with_group_members` config option is
112
	 * enabled it will filter all users which doens't have a common group
113
	 * with the current user.
114
	 *
115
	 * @param IUser $self
116
	 * @param Entry[] $entries
117
	 * @param string $filter
118
	 * @return Entry[] the filtered contacts
119
	 */
120
	private function filterContacts(IUser $self,
121
									array $entries,
122
									$filter) {
123
		$disallowEnumeration = $this->config->getAppValue('core', 'shareapi_allow_share_dialog_user_enumeration', 'yes') !== 'yes';
124
		$restrictEnumeration = $this->config->getAppValue('core', 'shareapi_restrict_user_enumeration_to_group', 'no') === 'yes';
125
		$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups', 'no') === 'yes';
126
127
		// whether to filter out local users
128
		$skipLocal = false;
129
		// whether to filter out all users which doesn't have the same group as the current user
130
		$ownGroupsOnly = $this->config->getAppValue('core', 'shareapi_only_share_with_group_members', 'no') === 'yes' || $restrictEnumeration;
131
132
		$selfGroups = $this->groupManager->getUserGroupIds($self);
133
134
		if ($excludedGroups) {
135
			$excludedGroups = $this->config->getAppValue('core', 'shareapi_exclude_groups_list', '');
136
			$decodedExcludeGroups = json_decode($excludedGroups, true);
137
			$excludeGroupsList = ($decodedExcludeGroups !== null) ? $decodedExcludeGroups : [];
138
139
			if (count(array_intersect($excludeGroupsList, $selfGroups)) !== 0) {
140
				// a group of the current user is excluded -> filter all local users
141
				$skipLocal = true;
142
			}
143
		}
144
145
		$selfUID = $self->getUID();
146
147
		return array_values(array_filter($entries, function (IEntry $entry) use ($self, $skipLocal, $ownGroupsOnly, $selfGroups, $selfUID, $disallowEnumeration, $filter) {
0 ignored issues
show
Unused Code introduced by
The import $self is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
148
			if ($skipLocal && $entry->getProperty('isLocalSystemBook') === true) {
149
				return false;
150
			}
151
152
			// Prevent enumerating local users
153
			if ($disallowEnumeration && $entry->getProperty('isLocalSystemBook')) {
154
				$filterUser = true;
155
156
				$mailAddresses = $entry->getEMailAddresses();
157
				foreach ($mailAddresses as $mailAddress) {
158
					if ($mailAddress === $filter) {
159
						$filterUser = false;
160
						break;
161
					}
162
				}
163
164
				if ($entry->getProperty('UID') && $entry->getProperty('UID') === $filter) {
165
					$filterUser = false;
166
				}
167
168
				if ($filterUser) {
169
					return false;
170
				}
171
			}
172
173
			if ($ownGroupsOnly && $entry->getProperty('isLocalSystemBook') === true) {
174
				$uid = $this->userManager->get($entry->getProperty('UID'));
175
176
				if ($uid === null) {
177
					return false;
178
				}
179
180
				$contactGroups = $this->groupManager->getUserGroupIds($uid);
181
				if (count(array_intersect($contactGroups, $selfGroups)) === 0) {
182
					// no groups in common, so shouldn't see the contact
183
					return false;
184
				}
185
			}
186
187
			return $entry->getProperty('UID') !== $selfUID;
188
		}));
189
	}
190
191
	/**
192
	 * @param IUser $user
193
	 * @param integer $shareType
194
	 * @param string $shareWith
195
	 * @return IEntry|null
196
	 */
197
	public function findOne(IUser $user, $shareType, $shareWith) {
198
		switch ($shareType) {
199
			case 0:
200
			case 6:
201
				$filter = ['UID'];
202
				break;
203
			case 4:
204
				$filter = ['EMAIL'];
205
				break;
206
			default:
207
				return null;
208
		}
209
210
		$userId = $user->getUID();
211
		$allContacts = $this->contactsManager->search($shareWith, $filter);
212
		$contacts = array_filter($allContacts, function ($contact) use ($userId) {
213
			return $contact['UID'] !== $userId;
214
		});
215
		$match = null;
216
217
		foreach ($contacts as $contact) {
218
			if ($shareType === 4 && isset($contact['EMAIL'])) {
219
				if (in_array($shareWith, $contact['EMAIL'])) {
220
					$match = $contact;
221
					break;
222
				}
223
			}
224
			if ($shareType === 0 || $shareType === 6) {
225
				$isLocal = $contact['isLocalSystemBook'] ?? false;
226
				if ($contact['UID'] === $shareWith && $isLocal === true) {
227
					$match = $contact;
228
					break;
229
				}
230
			}
231
		}
232
233
		if ($match) {
234
			$match = $this->filterContacts($user, [$this->contactArrayToEntry($match)], $shareWith);
235
			if (count($match) === 1) {
236
				$match = $match[0];
237
			} else {
238
				$match = null;
239
			}
240
		}
241
242
		return $match;
243
	}
244
245
	/**
246
	 * @param array $contact
247
	 * @return Entry
248
	 */
249
	private function contactArrayToEntry(array $contact) {
250
		$entry = new Entry();
251
252
		if (isset($contact['id'])) {
253
			$entry->setId($contact['id']);
254
		}
255
256
		if (isset($contact['FN'])) {
257
			$entry->setFullName($contact['FN']);
258
		}
259
260
		$avatarPrefix = "VALUE=uri:";
261
		if (isset($contact['PHOTO']) && strpos($contact['PHOTO'], $avatarPrefix) === 0) {
262
			$entry->setAvatar(substr($contact['PHOTO'], strlen($avatarPrefix)));
263
		}
264
265
		if (isset($contact['EMAIL'])) {
266
			foreach ($contact['EMAIL'] as $email) {
267
				$entry->addEMailAddress($email);
268
			}
269
		}
270
271
		// Attach all other properties to the entry too because some
272
		// providers might make use of it.
273
		$entry->setProperties($contact);
274
275
		return $entry;
276
	}
277
}
278