Completed
Push — master ( 537a78...b29bba )
by Maxence
03:35
created

FederatedService::updateLinkRemote()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 24
rs 8.9713
cc 2
eloc 16
nc 2
nop 1
1
<?php
2
/**
3
 * Circles - Bring cloud-users closer together.
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2017
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\Circles\Service;
28
29
30
use Exception;
31
use OC\Http\Client\ClientService;
32
use OCA\Circles\Api\v1\Circles;
33
use OCA\Circles\Db\CirclesRequest;
34
use OCA\Circles\Db\FederatedLinksRequest;
35
use OCA\Circles\Exceptions\CircleDoesNotExistException;
36
use OCA\Circles\Exceptions\FederatedCircleLinkFormatException;
37
use OCA\Circles\Exceptions\FederatedCircleNotAllowedException;
38
use OCA\Circles\Exceptions\CircleTypeNotValid;
39
use OCA\Circles\Exceptions\FederatedCircleStatusUpdateException;
40
use OCA\Circles\Exceptions\FederatedRemoteCircleDoesNotExistException;
41
use OCA\Circles\Exceptions\FederatedRemoteDoesNotAllowException;
42
use OCA\Circles\Exceptions\FrameAlreadyExistException;
43
use OCA\Circles\Exceptions\LinkCreationException;
44
use OCA\Circles\Exceptions\MemberIsNotAdminException;
45
use OCA\Circles\Model\Circle;
46
use OCA\Circles\Model\FederatedLink;
47
use OCA\Circles\Model\SharingFrame;
48
use OCP\IL10N;
49
50
class FederatedService {
51
52
53
	/** @var string */
54
	private $userId;
55
56
	/** @var IL10N */
57
	private $l10n;
58
59
	/** @var CirclesRequest */
60
	private $circlesRequest;
61
62
	/** @var ConfigService */
63
	private $configService;
64
65
	/** @var CirclesService */
66
	private $circlesService;
67
68
	/** @var BroadcastService */
69
	private $broadcastService;
70
71
	/** @var FederatedLinksRequest */
72
	private $federatedLinksRequest;
73
74
	/** @var EventsService */
75
	private $eventsService;
76
77
	/** @var string */
78
	private $serverHost;
79
80
	/** @var ClientService */
81
	private $clientService;
82
83
	/** @var MiscService */
84
	private $miscService;
85
86
	/** @var bool */
87
	private $localTest = false;
88
89
	/**
90
	 * CirclesService constructor.
91
	 *
92
	 * @param $userId
93
	 * @param IL10N $l10n
94
	 * @param CirclesRequest $circlesRequest
95
	 * @param ConfigService $configService
96
	 * @param CirclesService $circlesService
97
	 * @param BroadcastService $broadcastService
98
	 * @param FederatedLinksRequest $federatedLinksRequest
99
	 * @param EventsService $eventsService
100
	 * @param string $serverHost
101
	 * @param ClientService $clientService
102
	 * @param MiscService $miscService
103
	 */
104
	public function __construct(
105
		$userId,
106
		IL10N $l10n,
107
		CirclesRequest $circlesRequest,
108
		ConfigService $configService,
109
		CirclesService $circlesService,
110
		BroadcastService $broadcastService,
111
		FederatedLinksRequest $federatedLinksRequest,
112
		EventsService $eventsService, $serverHost,
113
		ClientService $clientService,
114
		MiscService $miscService
115
	) {
116
		$this->userId = $userId;
117
		$this->l10n = $l10n;
118
		$this->circlesRequest = $circlesRequest;
119
		$this->configService = $configService;
120
		$this->circlesService = $circlesService;
121
		$this->broadcastService = $broadcastService;
122
		$this->federatedLinksRequest = $federatedLinksRequest;
123
		$this->eventsService = $eventsService;
124
		$this->serverHost = (string)$serverHost;
125
		$this->clientService = $clientService;
126
		$this->miscService = $miscService;
127
	}
128
129
130
	/**
131
	 * linkCircle();
132
	 *
133
	 * link to a circle.
134
	 * Function will check if settings allow Federated links between circles, and the format of
135
	 * the link ($remote). If no exception, a request to the remote circle will be initiated
136
	 * using requestLinkWithCircle()
137
	 *
138
	 * $remote format: <circle_name>@<remote_host>
139
	 *
140
	 * @param int $circleId
141
	 * @param string $remote
142
	 *
143
	 * @throws Exception
144
	 * @throws FederatedCircleLinkFormatException
145
	 * @throws CircleTypeNotValid
146
	 *
147
	 * @return FederatedLink
148
	 */
149
	public function linkCircle($circleId, $remote) {
150
151
		if (!$this->configService->isFederatedAllowed()) {
152
			throw new FederatedCircleNotAllowedException(
153
				$this->l10n->t("Federated circles are not allowed on this Nextcloud")
154
			);
155
		}
156
157
		if (strpos($remote, '@') === false) {
158
			throw new FederatedCircleLinkFormatException(
159
				$this->l10n->t("Federated link does not have a valid format")
160
			);
161
		}
162
163
		try {
164
			return $this->requestLinkWithCircle($circleId, $remote);
165
		} catch (Exception $e) {
166
			throw $e;
167
		}
168
	}
169
170
171
	/**
172
	 * linkStatus()
173
	 *
174
	 * Update the status of a link.
175
	 * Function will check if user can edit the status, will update it and send the update to
176
	 * remote
177
	 *
178
	 * @param int $linkId
179
	 * @param int $status
180
	 *
181
	 * @throws Exception
182
	 * @throws FederatedCircleLinkFormatException
183
	 * @throws CircleTypeNotValid
184
	 * @throws MemberIsNotAdminException
185
	 *
186
	 * @return FederatedLink[]
187
	 */
188
	public function linkStatus($linkId, $status) {
189
190
		$status = (int)$status;
191
		$link = null;
192
		try {
193
194
			$link = $this->circlesRequest->getLinkFromId($linkId);
195
			$circle = $this->circlesRequest->getCircleFromId($link->getCircleId(), $this->userId);
196
			$circle->hasToBeFederated();
197
			$circle->getUser()
198
				   ->hasToBeAdmin();
199
			$link->hasToBeValidStatusUpdate($status);
200
201
			if (!$this->eventOnLinkStatus($circle, $link, $status)) {
0 ignored issues
show
Bug introduced by
It seems like $circle defined by $this->circlesRequest->g...cleId(), $this->userId) on line 195 can be null; however, OCA\Circles\Service\Fede...ce::eventOnLinkStatus() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Bug introduced by
It seems like $link defined by $this->circlesRequest->getLinkFromId($linkId) on line 194 can be null; however, OCA\Circles\Service\Fede...ce::eventOnLinkStatus() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
202
				return $this->circlesRequest->getLinksFromCircle($circle->getId());
203
			}
204
205
			$link->setStatus($status);
206
			$this->federatedLinksRequest->update($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by $this->circlesRequest->getLinkFromId($linkId) on line 194 can be null; however, OCA\Circles\Db\FederatedLinksRequest::update() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
207
208
		} catch (Exception $e) {
209
			throw $e;
210
		}
211
212
		try {
213
			$link->setUniqueId($circle->getUniqueId(true));
214
			$this->updateLinkRemote($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by $this->circlesRequest->getLinkFromId($linkId) on line 194 can be null; however, OCA\Circles\Service\Fede...ice::updateLinkRemote() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
215
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
216
		}
217
218
		return $this->circlesRequest->getLinksFromCircle($circle->getId());
219
	}
220
221
222
	/**
223
	 * eventOnLinkStatus();
224
	 *
225
	 * Called by linkStatus() to manage events when status is changing.
226
	 * If status does not need update, returns false;
227
	 *
228
	 * @param Circle $circle
229
	 * @param FederatedLink $link
230
	 * @param $status
231
	 *
232
	 * @return bool
233
	 */
234
	private function eventOnLinkStatus(Circle $circle, FederatedLink $link, $status) {
235
		if ($link->getStatus() === $status) {
236
			return false;
237
		}
238
239
		if ($status === FederatedLink::STATUS_LINK_REMOVE) {
240
			$this->eventsService->onLinkRemove($circle, $link);
241
		}
242
243
		if ($status === FederatedLink::STATUS_LINK_UP) {
244
			$this->eventsService->onLinkRequestAccepting($circle, $link);
245
			$this->eventsService->onLinkUp($circle, $link);
246
		}
247
248
		return true;
249
	}
250
251
252
	/**
253
	 * requestLinkWithCircle()
254
	 *
255
	 * Using CircleId, function will get more infos from the database.
256
	 * Will check if author is at least admin and initiate a FederatedLink, save it
257
	 * in the database and send a request to the remote circle using requestLink()
258
	 * If any issue, entry is removed from the database.
259
	 *
260
	 * @param integer $circleId
261
	 * @param string $remote
262
	 *
263
	 * @return FederatedLink
264
	 * @throws Exception
265
	 */
266
	private function requestLinkWithCircle($circleId, $remote) {
267
268
		$link = null;
269
		try {
270
			list($remoteCircle, $remoteAddress) = explode('@', $remote, 2);
271
272
			$circle = $this->circlesService->detailsCircle($circleId);
273
			$circle->getUser()
274
				   ->hasToBeAdmin();
275
			$circle->hasToBeFederated();
276
			$circle->cantBePersonal();
277
278
			$link = new FederatedLink();
279
			$link->setCircleId($circleId)
280
				 ->setLocalAddress($this->serverHost)
281
				 ->setAddress($remoteAddress)
282
				 ->setRemoteCircleName($remoteCircle)
283
				 ->setStatus(FederatedLink::STATUS_LINK_SETUP)
284
				 ->generateToken();
285
286
			$this->federatedLinksRequest->create($link);
287
			$this->requestLink($circle, $link);
288
289
		} catch (Exception $e) {
290
			if ($link !== null) {
291
				$this->federatedLinksRequest->delete($link);
292
			}
293
			throw $e;
294
		}
295
296
		return $link;
297
	}
298
299
300
	/**
301
	 * @param string $remote
302
	 *
303
	 * @return string
304
	 */
305 View Code Duplication
	private function generateLinkRemoteURL($remote) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
306
		if ($this->localTest === false && strpos($remote, 'https') !== 0) {
307
			$remote = 'https://' . $remote;
308
		}
309
310
		return rtrim($remote, '/') . '/index.php/apps/circles/v1/link';
311
	}
312
313
314
	/**
315
	 * @param string $remote
316
	 *
317
	 * @return string
318
	 */
319 View Code Duplication
	private function generatePayloadDeliveryURL($remote) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
320
		if ($this->localTest === false && strpos($remote, 'https') !== 0) {
321
			$remote = 'https://' . $remote;
322
		}
323
324
		return rtrim($remote, '/') . '/index.php/apps/circles/v1/payload';
325
	}
326
327
328
	public function allowNonSSLLink() {
329
		$this->localTest = true;
330
	}
331
332
333
	/**
334
	 * requestLink()
335
	 *
336
	 *
337
	 * @param Circle $circle
338
	 * @param FederatedLink $link
339
	 *
340
	 * @return boolean
341
	 * @throws Exception
342
	 */
343
	private function requestLink(Circle $circle, FederatedLink & $link) {
344
		$args = [
345
			'apiVersion' => Circles::API_VERSION,
346
			'token'      => $link->getToken(true),
347
			'uniqueId'   => $circle->getUniqueId(true),
348
			'sourceName' => $circle->getName(),
349
			'linkTo'     => $link->getRemoteCircleName(),
350
			'address'    => $link->getLocalAddress()
351
		];
352
353
		$client = $this->clientService->newClient();
354
355
		try {
356
			$request = $client->put(
357
				$this->generateLinkRemoteURL($link->getAddress()), [
358
																	 'body'            => $args,
359
																	 'timeout'         => 10,
360
																	 'connect_timeout' => 10,
361
																 ]
362
			);
363
364
			$result = json_decode($request->getBody(), true);
365
			$this->eventOnRequestLink(
366
				$circle, $link, $result['status'],
367
				((key_exists('reason', $result)) ? $result['reason'] : '')
368
			);
369
370
			$link->setUniqueId($result['uniqueId']);
371
			$this->federatedLinksRequest->update($link);
372
373
			return true;
374
		} catch (Exception $e) {
375
			throw $e;
376
		}
377
	}
378
379
380
	/**
381
	 * eventOnRequestLink();
382
	 *
383
	 * Called by requestLink() will update status and event
384
	 * Will also manage errors returned by the remote link
385
	 *
386
	 * @param Circle $circle
387
	 * @param FederatedLink $link
388
	 * @param $status
389
	 * @param $reason
390
	 *
391
	 * @throws Exception
392
	 */
393
	private function eventOnRequestLink(Circle $circle, FederatedLink $link, $status, $reason) {
394
395
		try {
396
			if ($status === FederatedLink::STATUS_LINK_UP) {
397
				$link->setStatus(FederatedLink::STATUS_LINK_UP);
398
				$this->eventsService->onLinkUp($circle, $link);
399
			} else if ($status === FederatedLink::STATUS_LINK_REQUESTED) {
400
				$link->setStatus(FederatedLink::STATUS_REQUEST_SENT);
401
				$this->eventsService->onLinkRequestSent($circle, $link);
402
			} else {
403
				$this->parseRequestLinkError($reason);
404
			}
405
		} catch (Exception $e) {
406
			throw $e;
407
		}
408
	}
409
410
411
	/**
412
	 * parseRequestLinkError();
413
	 *
414
	 * Will parse the error reason returned by requestLink() and throw an Exception
415
	 *
416
	 * @param $reason
417
	 *
418
	 * @throws Exception
419
	 * @throws FederatedRemoteCircleDoesNotExistException
420
	 * @throws FederatedRemoteDoesNotAllowException
421
	 */
422
	private function parseRequestLinkError($reason) {
423
424
		if ($reason === 'federated_not_allowed') {
425
			throw new FederatedRemoteDoesNotAllowException(
426
				$this->l10n->t('Federated circles are not allowed on the remote Nextcloud')
427
			);
428
		}
429
430
		if ($reason === 'circle_links_disable') {
431
			throw new FederatedRemoteDoesNotAllowException(
432
				$this->l10n->t('The remote circle does not accept Federated Links')
433
			);
434
		}
435
436
		if ($reason === 'duplicate_unique_id') {
437
			throw new FederatedRemoteDoesNotAllowException(
438
				$this->l10n->t('It seems that you are trying to link a circle to itself')
439
			);
440
		}
441
442
		if ($reason === 'duplicate_link') {
443
			throw new FederatedRemoteDoesNotAllowException(
444
				$this->l10n->t('This link exists already')
445
			);
446
		}
447
448
		if ($reason === 'circle_does_not_exist') {
449
			throw new FederatedRemoteCircleDoesNotExistException(
450
				$this->l10n->t('The requested remote circle does not exist')
451
			);
452
		}
453
454
		throw new Exception($reason);
455
	}
456
457
458
	/**
459
	 * @param $token
460
	 * @param $uniqueId
461
	 * @param $status
462
	 *
463
	 * @return FederatedLink
0 ignored issues
show
Documentation introduced by
Should the return type not be FederatedLink|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
464
	 * @throws Exception
465
	 */
466
	public function updateLinkFromRemote($token, $uniqueId, $status) {
467
		try {
468
			$link = $this->circlesRequest->getLinkFromToken($token, $uniqueId);
469
			$circle = $this->circlesRequest->getCircleFromId($link->getCircleId());
470
			$circle->hasToBeFederated();
471
472
			$this->checkUpdateLinkFromRemote($status);
473
			$this->checkUpdateLinkFromRemoteLinkUp($circle, $link, $status);
0 ignored issues
show
Bug introduced by
It seems like $circle defined by $this->circlesRequest->g...d($link->getCircleId()) on line 469 can be null; however, OCA\Circles\Service\Fede...eLinkFromRemoteLinkUp() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Bug introduced by
It seems like $link defined by $this->circlesRequest->g...oken($token, $uniqueId) on line 468 can be null; however, OCA\Circles\Service\Fede...eLinkFromRemoteLinkUp() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
474
			$this->checkUpdateLinkFromRemoteLinkRemove($circle, $link, $status);
0 ignored issues
show
Bug introduced by
It seems like $circle defined by $this->circlesRequest->g...d($link->getCircleId()) on line 469 can be null; however, OCA\Circles\Service\Fede...kFromRemoteLinkRemove() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
Bug introduced by
It seems like $link defined by $this->circlesRequest->g...oken($token, $uniqueId) on line 468 can be null; however, OCA\Circles\Service\Fede...kFromRemoteLinkRemove() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
475
476
			if ($link->getStatus() !== $status) {
477
				$this->federatedLinksRequest->update($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by $this->circlesRequest->g...oken($token, $uniqueId) on line 468 can be null; however, OCA\Circles\Db\FederatedLinksRequest::update() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
478
			}
479
480
			return $link;
481
		} catch (Exception $e) {
482
			throw $e;
483
		}
484
	}
485
486
	/**
487
	 * checkUpdateLinkFromRemote();
488
	 *
489
	 * will throw exception is the status sent by remote is not correct
490
	 *
491
	 * @param $status
492
	 *
493
	 * @throws FederatedCircleStatusUpdateException
494
	 * @internal param FederatedLink $link
495
	 */
496
	private function checkUpdateLinkFromRemote($status) {
497
		$status = (int)$status;
498
		if ($status !== FederatedLink::STATUS_LINK_UP
499
			&& $status !== FederatedLink::STATUS_LINK_REMOVE
500
		) {
501
			throw new FederatedCircleStatusUpdateException(
502
				$this->l10n->t('Cannot proceed with this status update')
503
			);
504
		}
505
	}
506
507
508
	/**
509
	 * checkUpdateLinkFromRemoteLinkUp()
510
	 *
511
	 * in case of a request of status update from remote for a link up, we check the current
512
	 * status of the link locally.
513
	 *
514
	 * @param Circle $circle
515
	 * @param FederatedLink $link
516
	 * @param $status
517
	 *
518
	 * @throws FederatedCircleStatusUpdateException
519
	 */
520
	private function checkUpdateLinkFromRemoteLinkUp(Circle $circle, FederatedLink $link, $status) {
521
		if ((int)$status !== FederatedLink::STATUS_LINK_UP) {
522
			return;
523
		}
524
525
		if ($link->getStatus() !== FederatedLink::STATUS_REQUEST_SENT) {
526
			throw new FederatedCircleStatusUpdateException(
527
				$this->l10n->t('Cannot proceed with this status update')
528
			);
529
		}
530
531
		$this->eventsService->onLinkRequestAccepted($circle, $link);
532
		$this->eventsService->onLinkUp($circle, $link);
533
		$link->setStatus($status);
534
	}
535
536
537
	/**
538
	 * checkUpdateLinkFromRemoteLinkRemove();
539
	 *
540
	 * in case of a request of status update from remote for a link down, we check the current
541
	 * status of the link locally
542
	 *
543
	 * @param Circle $circle
544
	 * @param FederatedLink $link
545
	 * @param $status
546
	 *
547
	 * @throws FederatedCircleStatusUpdateException
548
	 */
549
	private function checkUpdateLinkFromRemoteLinkRemove(
550
		Circle $circle, FederatedLink $link, $status
551
	) {
552
		if ((int)$status !== FederatedLink::STATUS_LINK_REMOVE) {
553
			return;
554
		}
555
556 View Code Duplication
		if ($link->getStatus() === FederatedLink::STATUS_REQUEST_SENT) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
557
			$link->setStatus(FederatedLink::STATUS_REQUEST_DECLINED);
558
			$this->eventsService->onLinkRequestRejected($circle, $link);
559
560
			return;
561
		}
562
563 View Code Duplication
		if ($link->getStatus() === FederatedLink::STATUS_LINK_REQUESTED) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
564
			$link->setStatus(FederatedLink::STATUS_LINK_REMOVE);
565
			$this->eventsService->onLinkRequestCanceled($circle, $link);
566
567
			return;
568
		}
569
570 View Code Duplication
		if ($link->getStatus() > FederatedLink::STATUS_LINK_DOWN) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
571
			$link->setStatus(FederatedLink::STATUS_LINK_DOWN);
572
			$this->eventsService->onLinkDown($circle, $link);
573
574
			return;
575
		}
576
577
		throw new FederatedCircleStatusUpdateException(
578
			$this->l10n->t('Cannot proceed with this status update')
579
		);
580
	}
581
582
583
	/**
584
	 * updateLinkRemote()
585
	 *
586
	 * Send a request to the remote of the link to update its status.
587
	 *
588
	 * @param FederatedLink $link
589
	 *
590
	 * @return boolean
591
	 * @throws Exception
592
	 */
593
	public function updateLinkRemote(FederatedLink & $link) {
594
		$args = [
595
			'apiVersion' => Circles::API_VERSION,
596
			'token'      => $link->getToken(true),
597
			'uniqueId'   => $link->getUniqueId(true),
598
			'status'     => $link->getStatus()
599
		];
600
601
		$client = $this->clientService->newClient();
602
603
		try {
604
			$client->post(
605
				$this->generateLinkRemoteURL($link->getAddress()), [
606
																	 'body'            => $args,
607
																	 'timeout'         => 10,
608
																	 'connect_timeout' => 10,
609
																 ]
610
			);
611
612
			return true;
613
		} catch (Exception $e) {
614
			throw $e;
615
		}
616
	}
617
618
619
	/**
620
	 * Create a new link into database and assign the correct status.
621
	 *
622
	 * @param Circle $circle
623
	 * @param FederatedLink $link
624
	 *
625
	 * @throws Exception
626
	 */
627
	public function initiateLink(Circle $circle, FederatedLink & $link) {
628
629
		try {
630
			$this->checkLinkRequestValidity($circle, $link);
631
			$link->setCircleId($circle->getId());
632
633
			if ($circle->getSetting('allow_links_auto') === 'true') {
634
				$link->setStatus(FederatedLink::STATUS_LINK_UP);
635
				$this->eventsService->onLinkUp($circle, $link);
636
			} else {
637
				$link->setStatus(FederatedLink::STATUS_LINK_REQUESTED);
638
				$this->eventsService->onLinkRequestReceived($circle, $link);
639
			}
640
641
			$this->federatedLinksRequest->create($link);
642
		} catch (Exception $e) {
643
			throw $e;
644
		}
645
	}
646
647
648
	/**
649
	 * @param Circle $circle
650
	 * @param FederatedLink $link
651
	 *
652
	 * @throws LinkCreationException
653
	 */
654
	private function checkLinkRequestValidity($circle, $link) {
655
		if ($circle->getUniqueId(true) === $link->getUniqueId(true)) {
656
			throw new LinkCreationException('duplicate_unique_id');
657
		}
658
659
		if ($this->getLink($circle->getId(), $link->getUniqueId(true)) !== null) {
660
			throw new LinkCreationException('duplicate_link');
661
		}
662
663
		if ($circle->getSetting('allow_links') !== 'true') {
664
			throw new LinkCreationException('circle_links_disable');
665
		}
666
	}
667
668
669
	/**
670
	 * @param string $token
671
	 * @param string $uniqueId
672
	 * @param SharingFrame $frame
673
	 *
674
	 * @return bool
675
	 * @throws Exception
676
	 */
677
	public function receiveFrame($token, $uniqueId, SharingFrame & $frame) {
678
		try {
679
680
			$link = $this->circlesRequest->getLinkFromToken((string)$token, (string)$uniqueId);
681
		} catch (Exception $e) {
682
			throw $e;
683
		}
684
685
		if ($this->circlesRequest->getFrame($link->getCircleId(), $frame->getUniqueId())) {
686
			$this->miscService->log("Frame already exist");
687
			throw new FrameAlreadyExistException('shares_is_already_known');
688
		}
689
690
		try {
691
			$circle = $this->circlesRequest->getCircleFromId($link->getCircleId());
692
		} catch (CircleDoesNotExistException $e) {
693
			throw new CircleDoesNotExistException('unknown_circle');
694
		}
695
696
		$frame->setCircleId($link->getCircleId());
697
		$frame->setCircleName($circle->getName());
698
699
		$this->circlesRequest->saveFrame($frame);
700
		$this->broadcastService->broadcastFrame($frame->getHeader('broadcast'), $frame);
701
702
		return true;
703
	}
704
705
	/**
706
	 * @param integer $circleId
707
	 * @param string $uniqueId
708
	 *
709
	 * @return FederatedLink
0 ignored issues
show
Documentation introduced by
Should the return type not be FederatedLink|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
710
	 */
711
	public function getLink($circleId, $uniqueId) {
712
		return $this->federatedLinksRequest->getFromUniqueId($circleId, $uniqueId);
713
	}
714
715
716
	/**
717
	 * @param integer $circleId
718
	 *
719
	 * @return FederatedLink[]
720
	 */
721
	public function getLinks($circleId) {
722
		return $this->federatedLinksRequest->getLinked($circleId);
723
	}
724
725
726
	/**
727
	 * @param int $circleId
728
	 * @param string $uniqueId
729
	 *
730
	 * @return bool
731
	 * @throws Exception
732
	 */
733
	public function initiateRemoteShare($circleId, $uniqueId) {
734
		$args = [
735
			'apiVersion' => Circles::API_VERSION,
736
			'circleId'   => (int)$circleId,
737
			'uniqueId'   => (string)$uniqueId
738
		];
739
740
		$client = $this->clientService->newClient();
741
		try {
742
			$request = $client->post(
743
				$this->generatePayloadDeliveryURL($this->serverHost), [
744
																		'body'            => $args,
745
																		'timeout'         => 10,
746
																		'connect_timeout' => 10,
747
																	]
748
			);
749
750
			$result = json_decode($request->getBody(), true);
751
			$this->miscService->log(
752
				"initiateRemoteShare result: " . $uniqueId . '  ----  ' . var_export($result, true)
753
			);
754
755
			return true;
756
		} catch (Exception $e) {
757
			throw $e;
758
		}
759
	}
760
761
762
	/**
763
	 * @param SharingFrame $frame
764
	 *
765
	 * @throws Exception
766
	 */
767
	public function sendRemoteShare(SharingFrame $frame) {
768
769
		$circle = $this->circlesRequest->getCircleFromId($frame->getCircleId());
770
		if ($circle === null) {
771
			throw new Exception('unknown_circle');
772
		}
773
774
		$links = $this->getLinks($frame->getCircleId());
775
		foreach ($links AS $link) {
776
777
			$args = [
778
				'apiVersion' => Circles::API_VERSION,
779
				'token'      => $link->getToken(true),
780
				'uniqueId'   => $circle->getUniqueId(true),
781
				'item'       => json_encode($frame)
782
			];
783
784
			$client = $this->clientService->newClient();
785
			try {
786
				$client->put(
787
					$this->generatePayloadDeliveryURL($link->getAddress()), [
788
																			  'body'            => $args,
789
																			  'timeout'         => 10,
790
																			  'connect_timeout' => 10,
791
																		  ]
792
				);
793
			} catch (Exception $e) {
794
				throw $e;
795
			}
796
		}
797
	}
798
799
800
	/**
801
	 * generateHeaders()
802
	 *
803
	 * Generate new headers for the current Payload, and save them in the SharingFrame.
804
	 *
805
	 * @param SharingFrame $frame
806
	 */
807
	public function updateFrameWithCloudId(SharingFrame $frame) {
808
		$frame->setCloudId($this->serverHost);
809
		$this->circlesRequest->updateFrame($frame);
810
	}
811
812
}