Passed
Push — master ( b85c2d...d5c2e7 )
by Christoph
15:18 queued 13s
created

SyncService::syncRemoteAddressBook()   B

Complexity

Conditions 6
Paths 6

Size

Total Lines 37
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 23
nc 6
nop 8
dl 0
loc 37
rs 8.9297
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Bjoern Schiessle <[email protected]>
7
 * @author Björn Schießle <[email protected]>
8
 * @author Christoph Wurst <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 * @author Morris Jobke <[email protected]>
11
 * @author Thomas Citharel <[email protected]>
12
 * @author Thomas Müller <[email protected]>
13
 * @author Anna Larch <[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
namespace OCA\DAV\CardDAV;
31
32
use OC\Accounts\AccountManager;
33
use OCP\AppFramework\Http;
34
use OCP\IUser;
35
use OCP\IUserManager;
36
use Psr\Log\LoggerInterface;
37
use Sabre\DAV\Client;
38
use Sabre\DAV\Xml\Response\MultiStatus;
39
use Sabre\DAV\Xml\Service;
40
use Sabre\HTTP\ClientHttpException;
41
use Sabre\VObject\Reader;
42
43
class SyncService {
44
	private CardDavBackend $backend;
45
	private IUserManager $userManager;
46
	private LoggerInterface $logger;
47
	private ?array $localSystemAddressBook = null;
48
	private Converter $converter;
49
	protected string $certPath;
50
51
	public function __construct(CardDavBackend $backend,
52
								IUserManager $userManager,
53
								LoggerInterface $logger,
54
								Converter $converter) {
55
		$this->backend = $backend;
56
		$this->userManager = $userManager;
57
		$this->logger = $logger;
58
		$this->converter = $converter;
59
		$this->certPath = '';
60
	}
61
62
	/**
63
	 * @throws \Exception
64
	 */
65
	public function syncRemoteAddressBook(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken, string $targetBookHash, string $targetPrincipal, array $targetProperties): string {
66
		// 1. create addressbook
67
		$book = $this->ensureSystemAddressBookExists($targetPrincipal, $targetBookHash, $targetProperties);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $book is correct as $this->ensureSystemAddre...ash, $targetProperties) targeting OCA\DAV\CardDAV\SyncServ...stemAddressBookExists() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
68
		$addressBookId = $book['id'];
69
70
		// 2. query changes
71
		try {
72
			$response = $this->requestSyncReport($url, $userName, $addressBookUrl, $sharedSecret, $syncToken);
73
		} catch (ClientHttpException $ex) {
74
			if ($ex->getCode() === Http::STATUS_UNAUTHORIZED) {
75
				// remote server revoked access to the address book, remove it
76
				$this->backend->deleteAddressBook($addressBookId);
77
				$this->logger->error('Authorization failed, remove address book: ' . $url, ['app' => 'dav']);
78
				throw $ex;
79
			}
80
			$this->logger->error('Client exception:', ['app' => 'dav', 'exception' => $ex]);
81
			throw $ex;
82
		}
83
84
		// 3. apply changes
85
		// TODO: use multi-get for download
86
		foreach ($response['response'] as $resource => $status) {
87
			$cardUri = basename($resource);
88
			if (isset($status[200])) {
89
				$vCard = $this->download($url, $userName, $sharedSecret, $resource);
90
				$existingCard = $this->backend->getCard($addressBookId, $cardUri);
91
				if ($existingCard === false) {
92
					$this->backend->createCard($addressBookId, $cardUri, $vCard['body']);
93
				} else {
94
					$this->backend->updateCard($addressBookId, $cardUri, $vCard['body']);
95
				}
96
			} else {
97
				$this->backend->deleteCard($addressBookId, $cardUri);
98
			}
99
		}
100
101
		return $response['token'];
102
	}
103
104
	/**
105
	 * @throws \Sabre\DAV\Exception\BadRequest
106
	 */
107
	public function ensureSystemAddressBookExists(string $principal, string $uri, array $properties): ?array {
108
		$book = $this->backend->getAddressBooksByUri($principal, $uri);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $book is correct as $this->backend->getAddre...ByUri($principal, $uri) targeting OCA\DAV\CardDAV\CardDavB...:getAddressBooksByUri() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
109
		if (!is_null($book)) {
0 ignored issues
show
introduced by
The condition is_null($book) is always true.
Loading history...
110
			return $book;
111
		}
112
		// FIXME This might break in clustered DB setup
113
		$this->backend->createAddressBook($principal, $uri, $properties);
114
115
		return $this->backend->getAddressBooksByUri($principal, $uri);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->backend->getAddre...ByUri($principal, $uri) targeting OCA\DAV\CardDAV\CardDavB...:getAddressBooksByUri() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
116
	}
117
118
	/**
119
	 * Check if there is a valid certPath we should use
120
	 */
121
	protected function getCertPath(): string {
122
123
		// we already have a valid certPath
124
		if ($this->certPath !== '') {
125
			return $this->certPath;
126
		}
127
128
		$certManager = \OC::$server->getCertificateManager();
129
		$certPath = $certManager->getAbsoluteBundlePath();
130
		if (file_exists($certPath)) {
131
			$this->certPath = $certPath;
132
		}
133
134
		return $this->certPath;
135
	}
136
137
	protected function getClient(string $url, string $userName, string $sharedSecret): Client {
138
		$settings = [
139
			'baseUri' => $url . '/',
140
			'userName' => $userName,
141
			'password' => $sharedSecret,
142
		];
143
		$client = new Client($settings);
144
		$certPath = $this->getCertPath();
145
		$client->setThrowExceptions(true);
146
147
		if ($certPath !== '' && strpos($url, 'http://') !== 0) {
148
			$client->addCurlSetting(CURLOPT_CAINFO, $this->certPath);
149
		}
150
151
		return $client;
152
	}
153
154
	protected function requestSyncReport(string $url, string $userName, string $addressBookUrl, string $sharedSecret, ?string $syncToken): array {
155
		$client = $this->getClient($url, $userName, $sharedSecret);
156
157
		$body = $this->buildSyncCollectionRequestBody($syncToken);
158
159
		$response = $client->request('REPORT', $addressBookUrl, $body, [
160
			'Content-Type' => 'application/xml'
161
		]);
162
163
		return $this->parseMultiStatus($response['body']);
164
	}
165
166
	protected function download(string $url, string $userName, string $sharedSecret, string $resourcePath): array {
167
		$client = $this->getClient($url, $userName, $sharedSecret);
168
		return $client->request('GET', $resourcePath);
169
	}
170
171
	private function buildSyncCollectionRequestBody(?string $syncToken): string {
172
		$dom = new \DOMDocument('1.0', 'UTF-8');
173
		$dom->formatOutput = true;
174
		$root = $dom->createElementNS('DAV:', 'd:sync-collection');
175
		$sync = $dom->createElement('d:sync-token', $syncToken);
176
		$prop = $dom->createElement('d:prop');
177
		$cont = $dom->createElement('d:getcontenttype');
178
		$etag = $dom->createElement('d:getetag');
179
180
		$prop->appendChild($cont);
181
		$prop->appendChild($etag);
182
		$root->appendChild($sync);
183
		$root->appendChild($prop);
184
		$dom->appendChild($root);
185
		return $dom->saveXML();
186
	}
187
188
	/**
189
	 * @param string $body
190
	 * @return array
191
	 * @throws \Sabre\Xml\ParseException
192
	 */
193
	private function parseMultiStatus($body) {
194
		$xml = new Service();
195
196
		/** @var MultiStatus $multiStatus */
197
		$multiStatus = $xml->expect('{DAV:}multistatus', $body);
198
199
		$result = [];
200
		foreach ($multiStatus->getResponses() as $response) {
201
			$result[$response->getHref()] = $response->getResponseProperties();
202
		}
203
204
		return ['response' => $result, 'token' => $multiStatus->getSyncToken()];
205
	}
206
207
	/**
208
	 * @param IUser $user
209
	 */
210
	public function updateUser(IUser $user) {
211
		$systemAddressBook = $this->getLocalSystemAddressBook();
212
		$addressBookId = $systemAddressBook['id'];
213
214
		$cardId = self::getCardUri($user);
215
		if ($user->isEnabled()) {
216
			$card = $this->backend->getCard($addressBookId, $cardId);
217
			if ($card === false) {
0 ignored issues
show
introduced by
The condition $card === false is always false.
Loading history...
218
				$vCard = $this->converter->createCardFromUser($user);
219
				if ($vCard !== null) {
220
					$this->backend->createCard($addressBookId, $cardId, $vCard->serialize(), false);
221
				}
222
			} else {
223
				$vCard = $this->converter->createCardFromUser($user);
224
				if (is_null($vCard)) {
225
					$this->backend->deleteCard($addressBookId, $cardId);
226
				} else {
227
					$this->backend->updateCard($addressBookId, $cardId, $vCard->serialize());
228
				}
229
			}
230
		} else {
231
			$this->backend->deleteCard($addressBookId, $cardId);
232
		}
233
	}
234
235
	/**
236
	 * @param IUser|string $userOrCardId
237
	 */
238
	public function deleteUser($userOrCardId) {
239
		$systemAddressBook = $this->getLocalSystemAddressBook();
240
		if ($userOrCardId instanceof IUser) {
241
			$userOrCardId = self::getCardUri($userOrCardId);
242
		}
243
		$this->backend->deleteCard($systemAddressBook['id'], $userOrCardId);
244
	}
245
246
	/**
247
	 * @return array|null
248
	 */
249
	public function getLocalSystemAddressBook() {
250
		if (is_null($this->localSystemAddressBook)) {
251
			$systemPrincipal = "principals/system/system";
252
			$this->localSystemAddressBook = $this->ensureSystemAddressBookExists($systemPrincipal, 'system', [
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $this->localSystemAddressBook is correct as $this->ensureSystemAddre...ers of this instance')) targeting OCA\DAV\CardDAV\SyncServ...stemAddressBookExists() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
253
				'{' . Plugin::NS_CARDDAV . '}addressbook-description' => 'System addressbook which holds all users of this instance'
254
			]);
255
		}
256
257
		return $this->localSystemAddressBook;
258
	}
259
260
	public function syncInstance(\Closure $progressCallback = null) {
261
		$systemAddressBook = $this->getLocalSystemAddressBook();
262
		$this->userManager->callForAllUsers(function ($user) use ($systemAddressBook, $progressCallback) {
0 ignored issues
show
Unused Code introduced by
The import $systemAddressBook 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...
263
			$this->updateUser($user);
264
			if (!is_null($progressCallback)) {
265
				$progressCallback();
266
			}
267
		});
268
269
		// remove no longer existing
270
		$allCards = $this->backend->getCards($systemAddressBook['id']);
271
		foreach ($allCards as $card) {
272
			$vCard = Reader::read($card['carddata']);
273
			$uid = $vCard->UID->getValue();
0 ignored issues
show
Bug introduced by
The method getValue() does not exist on null. ( Ignorable by Annotation )

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

273
			/** @scrutinizer ignore-call */ 
274
   $uid = $vCard->UID->getValue();

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...
274
			// load backend and see if user exists
275
			if (!$this->userManager->userExists($uid)) {
276
				$this->deleteUser($card['uri']);
277
			}
278
		}
279
	}
280
281
	/**
282
	 * @param IUser $user
283
	 * @return string
284
	 */
285
	public static function getCardUri(IUser $user): string {
286
		return $user->getBackendClassName() . ':' . $user->getUID() . '.vcf';
287
	}
288
}
289