Completed
Push — master ( 0a2d75...d5a533 )
by Maxence
03:06
created

FederatedService::initiateLink()   A

Complexity

Conditions 3
Paths 11

Size

Total Lines 19
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
c 3
b 0
f 0
dl 0
loc 19
rs 9.4285
cc 3
eloc 13
nc 11
nop 2
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
	 * @throws MemberIsNotAdminException
147
	 *
148
	 * @return FederatedLink
149
	 */
150
	public function linkCircle($circleId, $remote) {
151
152
		if (!$this->configService->isFederatedAllowed()) {
153
			throw new FederatedCircleNotAllowedException(
154
				$this->l10n->t("Federated circles are not allowed on this Nextcloud")
155
			);
156
		}
157
158
		if (strpos($remote, '@') === false) {
159
			throw new FederatedCircleLinkFormatException(
160
				$this->l10n->t("Federated link does not have a valid format")
161
			);
162
		}
163
164
		try {
165
			return $this->requestLinkWithCircle($circleId, $remote);
166
		} catch (Exception $e) {
167
			throw $e;
168
		}
169
	}
170
171
172
	/**
173
	 * linkStatus()
174
	 *
175
	 * Update the status of a link.
176
	 * Function will check if user can edit the status, will update it and send the update to
177
	 * remote
178
	 *
179
	 * @param int $linkId
180
	 * @param int $status
181
	 *
182
	 * @throws Exception
183
	 * @throws FederatedCircleLinkFormatException
184
	 * @throws CircleTypeNotValid
185
	 * @throws MemberIsNotAdminException
186
	 *
187
	 * @return FederatedLink[]
188
	 */
189
	public function linkStatus($linkId, $status) {
190
191
		$status = (int)$status;
192
		$link = null;
193
		try {
194
195
			$link = $this->circlesRequest->getLinkFromId($linkId);
196
			$circle = $this->circlesRequest->getCircleFromId($link->getCircleId(), $this->userId);
197
			$circle->hasToBeFederated();
198
			$circle->getUser()
199
				   ->hasToBeAdmin();
200
			$link->hasToBeValidStatusUpdate($status);
201
202
			if ($link->getStatus() === $status) {
203
				return $this->circlesRequest->getLinksFromCircle($circle->getId());
204
			}
205
206
			if ($status === FederatedLink::STATUS_LINK_REMOVE) {
207
				$this->eventsService->onLinkRemove($circle, $link);
0 ignored issues
show
Bug introduced by
It seems like $circle defined by $this->circlesRequest->g...cleId(), $this->userId) on line 196 can be null; however, OCA\Circles\Service\EventsService::onLinkRemove() 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 195 can be null; however, OCA\Circles\Service\EventsService::onLinkRemove() 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...
208
			}
209
210
			if ($status === FederatedLink::STATUS_LINK_UP) {
211
				$this->eventsService->onLinkRequestAccepting($circle, $link);
0 ignored issues
show
Bug introduced by
It seems like $circle defined by $this->circlesRequest->g...cleId(), $this->userId) on line 196 can be null; however, OCA\Circles\Service\Even...nLinkRequestAccepting() 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 195 can be null; however, OCA\Circles\Service\Even...nLinkRequestAccepting() 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...
212
				$this->eventsService->onLinkUp($circle, $link);
0 ignored issues
show
Bug introduced by
It seems like $circle defined by $this->circlesRequest->g...cleId(), $this->userId) on line 196 can be null; however, OCA\Circles\Service\EventsService::onLinkUp() 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 195 can be null; however, OCA\Circles\Service\EventsService::onLinkUp() 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...
213
			}
214
215
			$link->setStatus($status);
216
			$this->federatedLinksRequest->update($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by $this->circlesRequest->getLinkFromId($linkId) on line 195 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...
217
218
		} catch (Exception $e) {
219
			throw $e;
220
		}
221
222
		try {
223
			$link->setUniqueId($circle->getUniqueId(true));
224
			$this->updateLinkRemote($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by $this->circlesRequest->getLinkFromId($linkId) on line 195 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...
225
		} catch (Exception $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
226
		}
227
228
		return $this->circlesRequest->getLinksFromCircle($circle->getId());
229
	}
230
231
232
	/**
233
	 * requestLinkWithCircle()
234
	 *
235
	 * Using CircleId, function will get more infos from the database.
236
	 * Will check if author is at least admin and initiate a FederatedLink, save it
237
	 * in the database and send a request to the remote circle using requestLink()
238
	 * If any issue, entry is removed from the database.
239
	 *
240
	 * @param integer $circleId
241
	 * @param string $remote
242
	 *
243
	 * @return FederatedLink
244
	 * @throws Exception
245
	 */
246
	private function requestLinkWithCircle($circleId, $remote) {
247
248
		$link = null;
249
		try {
250
			list($remoteCircle, $remoteAddress) = explode('@', $remote, 2);
251
252
			$circle = $this->circlesService->detailsCircle($circleId);
253
			$circle->getUser()
254
				   ->hasToBeAdmin();
255
			$circle->hasToBeFederated();
256
			$circle->cantBePersonal();
257
258
			$link = new FederatedLink();
259
			$link->setCircleId($circleId)
260
				 ->setLocalAddress($this->serverHost)
261
				 ->setAddress($remoteAddress)
262
				 ->setRemoteCircleName($remoteCircle)
263
				 ->setStatus(FederatedLink::STATUS_LINK_SETUP)
264
				 ->generateToken();
265
266
			$this->federatedLinksRequest->create($link);
267
			$this->requestLink($circle, $link);
268
269
		} catch (Exception $e) {
270
			if ($link !== null) {
271
				$this->federatedLinksRequest->delete($link);
272
			}
273
			throw $e;
274
		}
275
276
		return $link;
277
	}
278
279
280
	/**
281
	 * @param string $remote
282
	 *
283
	 * @return string
284
	 */
285 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...
286
		if ($this->localTest === false && strpos($remote, 'https') !== 0) {
287
			$remote = 'https://' . $remote;
288
		}
289
290
		return rtrim($remote, '/') . '/index.php/apps/circles/v1/link';
291
	}
292
293
294
	/**
295
	 * @param string $remote
296
	 *
297
	 * @return string
298
	 */
299 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...
300
		if ($this->localTest === false && strpos($remote, 'https') !== 0) {
301
			$remote = 'https://' . $remote;
302
		}
303
304
		return rtrim($remote, '/') . '/index.php/apps/circles/v1/payload';
305
	}
306
307
308
	public function allowNonSSLLink() {
309
		$this->localTest = true;
310
	}
311
312
313
	/**
314
	 * requestLink()
315
	 *
316
	 *
317
	 * @param Circle $circle
318
	 * @param FederatedLink $link
319
	 *
320
	 * @return boolean
321
	 * @throws Exception
322
	 */
323
	private function requestLink(Circle $circle, FederatedLink & $link) {
324
		$args = [
325
			'apiVersion' => Circles::API_VERSION,
326
			'token'      => $link->getToken(true),
327
			'uniqueId'   => $circle->getUniqueId(true),
328
			'sourceName' => $circle->getName(),
329
			'linkTo'     => $link->getRemoteCircleName(),
330
			'address'    => $link->getLocalAddress()
331
		];
332
333
		$client = $this->clientService->newClient();
334
335
		try {
336
			$request = $client->put(
337
				$this->generateLinkRemoteURL($link->getAddress()), [
338
																	 'body'            => $args,
339
																	 'timeout'         => 10,
340
																	 'connect_timeout' => 10,
341
																 ]
342
			);
343
344
			$result = json_decode($request->getBody(), true);
345
			$link->setUniqueId($result['uniqueId']);
346
347
			if ($result['status'] === FederatedLink::STATUS_LINK_UP) {
348
				$link->setStatus(FederatedLink::STATUS_LINK_UP);
349
				$this->eventsService->onLinkUp($circle, $link);
350
			} else if ($result['status'] === FederatedLink::STATUS_LINK_REQUESTED) {
351
				$link->setStatus(FederatedLink::STATUS_REQUEST_SENT);
352
				$this->eventsService->onLinkRequestSent($circle, $link);
353
			} else {
354
				$this->parsingRequestLinkResult($result);
355
			}
356
357
//			$this->federatedLinksRequest->uniqueness($link);
0 ignored issues
show
Unused Code Comprehensibility introduced by
70% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
358
			$this->federatedLinksRequest->update($link);
359
360
			return true;
361
		} catch (Exception $e) {
362
			throw $e;
363
		}
364
	}
365
366
367
	/**
368
	 * @param $token
369
	 * @param $uniqueId
370
	 * @param $status
371
	 *
372
	 * @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...
373
	 * @throws Exception
374
	 */
375
	public function updateLinkFromRemote($token, $uniqueId, $status) {
376
		try {
377
			$link = $this->circlesRequest->getLinkFromToken($token, $uniqueId);
378
			$circle = $this->circlesRequest->getCircleFromId($link->getCircleId());
379
			$circle->hasToBeFederated();
380
381
			$this->checkUpdateLinkFromRemote($link, $status);
0 ignored issues
show
Bug introduced by
It seems like $link defined by $this->circlesRequest->g...oken($token, $uniqueId) on line 377 can be null; however, OCA\Circles\Service\Fede...kUpdateLinkFromRemote() 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...
382
			$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 378 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 377 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...
383
			$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 378 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 377 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...
384
385
			if ($link->getStatus() !== $status) {
386
				$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 377 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...
387
			}
388
389
			return $link;
390
		} catch (Exception $e) {
391
			throw $e;
392
		}
393
	}
394
395
	/**
396
	 * @param FederatedLink $link
397
	 * @param $status
398
	 *
399
	 * @throws FederatedCircleStatusUpdateException
400
	 */
401
	private function checkUpdateLinkFromRemote(FederatedLink $link, $status) {
0 ignored issues
show
Unused Code introduced by
The parameter $link is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
402
		$status = (int)$status;
403
		if ($status === FederatedLink::STATUS_LINK_UP
404
			|| $status === FederatedLink::STATUS_LINK_REMOVE
405
		) {
406
			return;
407
		}
408
409
		throw new FederatedCircleStatusUpdateException(
410
			$this->l10n->t('Cannot proceed with this status update')
411
		);
412
	}
413
414
415
	/**
416
	 * @param Circle $circle
417
	 * @param FederatedLink $link
418
	 * @param $status
419
	 *
420
	 * @throws FederatedCircleStatusUpdateException
421
	 */
422
	private function checkUpdateLinkFromRemoteLinkUp(Circle $circle, FederatedLink $link, $status) {
423
		if ((int)$status !== FederatedLink::STATUS_LINK_UP) {
424
			return;
425
		}
426
427
		if ($link->getStatus() !== FederatedLink::STATUS_REQUEST_SENT) {
428
			throw new FederatedCircleStatusUpdateException(
429
				$this->l10n->t('Cannot proceed with this status update')
430
			);
431
		}
432
433
		$this->eventsService->onLinkRequestAccepted($circle, $link);
434
		$this->eventsService->onLinkUp($circle, $link);
435
		$link->setStatus($status);
436
	}
437
438
439
	/**
440
	 * @param Circle $circle
441
	 * @param FederatedLink $link
442
	 * @param $status
443
	 *
444
	 * @throws FederatedCircleStatusUpdateException
445
	 */
446
	private function checkUpdateLinkFromRemoteLinkRemove(
447
		Circle $circle, FederatedLink $link, $status
448
	) {
449
		if ((int)$status !== FederatedLink::STATUS_LINK_REMOVE) {
450
			return;
451
		}
452
453
		if ($link->getStatus() === FederatedLink::STATUS_REQUEST_SENT) {
454
			$link->setStatus(FederatedLink::STATUS_REQUEST_DECLINED);
455
			$this->eventsService->onLinkRequestRejected($circle, $link);
456
457
			return;
458
		} else if ($link->getStatus() === FederatedLink::STATUS_LINK_REQUESTED) {
459
			$link->setStatus(FederatedLink::STATUS_LINK_REMOVE);
460
			$this->eventsService->onLinkRequestCanceled($circle, $link);
461
462
			return;
463
		} else if ($link->getStatus() > FederatedLink::STATUS_LINK_DOWN) {
464
			$link->setStatus(FederatedLink::STATUS_LINK_DOWN);
465
			$this->eventsService->onLinkDown($circle, $link);
466
467
			return;
468
		}
469
		throw new FederatedCircleStatusUpdateException(
470
			$this->l10n->t('Cannot proceed with this status update')
471
		);
472
	}
473
474
475
	/**
476
	 * updateLinkRemote()
477
	 *
478
	 * Send a request to the remote of the link to update its status.
479
	 *
480
	 * @param FederatedLink $link
481
	 *
482
	 * @return boolean
483
	 * @throws Exception
484
	 */
485
	public function updateLinkRemote(FederatedLink & $link) {
486
		$args = [
487
			'apiVersion' => Circles::API_VERSION,
488
			'token'      => $link->getToken(true),
489
			'uniqueId'   => $link->getUniqueId(true),
490
			'status'     => $link->getStatus()
491
		];
492
493
		$client = $this->clientService->newClient();
494
495
		try {
496
			$client->post(
497
				$this->generateLinkRemoteURL($link->getAddress()), [
498
																	 'body'            => $args,
499
																	 'timeout'         => 10,
500
																	 'connect_timeout' => 10,
501
																 ]
502
			);
503
504
			return true;
505
		} catch (Exception $e) {
506
			throw $e;
507
		}
508
	}
509
510
511
	private function parsingRequestLinkResult($result) {
512
513
		if ($result['reason'] === 'federated_not_allowed') {
514
			throw new FederatedRemoteDoesNotAllowException(
515
				$this->l10n->t('Federated circles are not allowed on the remote Nextcloud')
516
			);
517
		}
518
519
		if ($result['reason'] === 'circle_links_disable') {
520
			throw new FederatedRemoteDoesNotAllowException(
521
				$this->l10n->t('The remote circle does not accept Federated Links')
522
			);
523
		}
524
525
		if ($result['reason'] === 'duplicate_unique_id') {
526
			throw new FederatedRemoteDoesNotAllowException(
527
				$this->l10n->t('It seems that you are trying to link a circle to itself')
528
			);
529
		}
530
531
		if ($result['reason'] === 'duplicate_link') {
532
			throw new FederatedRemoteDoesNotAllowException(
533
				$this->l10n->t('This link exists already')
534
			);
535
		}
536
537
		if ($result['reason'] === 'circle_does_not_exist') {
538
			throw new FederatedRemoteCircleDoesNotExistException(
539
				$this->l10n->t('The requested remote circle does not exist')
540
			);
541
		}
542
543
		throw new Exception($result['reason']);
544
	}
545
546
547
	/**
548
	 * Create a new link into database and assign the correct status.
549
	 *
550
	 * @param Circle $circle
551
	 * @param FederatedLink $link
552
	 *
553
	 * @throws Exception
554
	 */
555
	public function initiateLink(Circle $circle, FederatedLink & $link) {
556
557
		try {
558
			$this->checkLinkRequestValidity($circle, $link);
559
			$link->setCircleId($circle->getId());
560
561
			if ($circle->getSetting('allow_links_auto') === 'true') {
562
				$link->setStatus(FederatedLink::STATUS_LINK_UP);
563
				$this->eventsService->onLinkUp($circle, $link);
564
			} else {
565
				$link->setStatus(FederatedLink::STATUS_LINK_REQUESTED);
566
				$this->eventsService->onLinkRequestReceived($circle, $link);
567
			}
568
569
			$this->federatedLinksRequest->create($link);
570
		} catch (Exception $e) {
571
			throw $e;
572
		}
573
	}
574
575
576
	/**
577
	 * @param Circle $circle
578
	 * @param FederatedLink $link
579
	 *
580
	 * @throws LinkCreationException
581
	 */
582
	private function checkLinkRequestValidity($circle, $link) {
583
		if ($circle->getUniqueId(true) === $link->getUniqueId(true)) {
584
			throw new LinkCreationException('duplicate_unique_id');
585
		}
586
587
		if ($this->getLink($circle->getId(), $link->getUniqueId(true)) !== null) {
588
			throw new LinkCreationException('duplicate_link');
589
		}
590
591
		if ($circle->getSetting('allow_links') !== 'true') {
592
			throw new LinkCreationException('circle_links_disable');
593
		}
594
	}
595
596
597
	/**
598
	 * @param string $token
599
	 * @param string $uniqueId
600
	 * @param SharingFrame $frame
601
	 *
602
	 * @return bool
603
	 * @throws Exception
604
	 */
605
	public function receiveFrame($token, $uniqueId, SharingFrame & $frame) {
606
		try {
607
608
			$link = $this->circlesRequest->getLinkFromToken((string)$token, (string)$uniqueId);
609
		} catch (Exception $e) {
610
			throw $e;
611
		}
612
613
		if ($this->circlesRequest->getFrame($link->getCircleId(), $frame->getUniqueId())) {
614
			$this->miscService->log("Frame already exist");
615
			throw new FrameAlreadyExistException('shares_is_already_known');
616
		}
617
618
		try {
619
			$circle = $this->circlesRequest->getCircleFromId($link->getCircleId());
620
		} catch (CircleDoesNotExistException $e) {
621
			throw new CircleDoesNotExistException('unknown_circle');
622
		}
623
624
		$frame->setCircleId($link->getCircleId());
625
		$frame->setCircleName($circle->getName());
626
627
		$this->circlesRequest->saveFrame($frame);
628
		$this->broadcastService->broadcastFrame($frame->getHeader('broadcast'), $frame);
629
630
		return true;
631
	}
632
633
	/**
634
	 * @param integer $circleId
635
	 * @param string $uniqueId
636
	 *
637
	 * @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...
638
	 */
639
	public function getLink($circleId, $uniqueId) {
640
		return $this->federatedLinksRequest->getFromUniqueId($circleId, $uniqueId);
641
	}
642
643
644
	/**
645
	 * @param integer $circleId
646
	 *
647
	 * @return FederatedLink[]
648
	 */
649
	public function getLinks($circleId) {
650
		return $this->federatedLinksRequest->getLinked($circleId);
651
	}
652
653
654
	/**
655
	 * @param int $circleId
656
	 * @param string $uniqueId
657
	 *
658
	 * @return bool
659
	 * @throws Exception
660
	 */
661
	public function initiateRemoteShare($circleId, $uniqueId) {
662
		$args = [
663
			'apiVersion' => Circles::API_VERSION,
664
			'circleId'   => (int)$circleId,
665
			'uniqueId'   => (string)$uniqueId
666
		];
667
668
		$client = $this->clientService->newClient();
669
		try {
670
			$request = $client->post(
671
				$this->generatePayloadDeliveryURL($this->serverHost), [
672
																		'body'            => $args,
673
																		'timeout'         => 10,
674
																		'connect_timeout' => 10,
675
																	]
676
			);
677
678
			$result = json_decode($request->getBody(), true);
679
			$this->miscService->log(
680
				"initiateRemoteShare result: " . $uniqueId . '  ----  ' . var_export($result, true)
681
			);
682
683
			return true;
684
		} catch (Exception $e) {
685
			throw $e;
686
		}
687
	}
688
689
690
	/**
691
	 * @param SharingFrame $frame
692
	 *
693
	 * @throws Exception
694
	 */
695
	public function sendRemoteShare(SharingFrame $frame) {
696
697
		$circle = $this->circlesRequest->getCircleFromId($frame->getCircleId());
698
		if ($circle === null) {
699
			throw new Exception('unknown_circle');
700
		}
701
702
		$links = $this->getLinks($frame->getCircleId());
703
		foreach ($links AS $link) {
704
705
			$args = [
706
				'apiVersion' => Circles::API_VERSION,
707
				'token'      => $link->getToken(true),
708
				'uniqueId'   => $circle->getUniqueId(true),
709
				'item'       => json_encode($frame)
710
			];
711
712
			$client = $this->clientService->newClient();
713
			try {
714
				$client->put(
715
					$this->generatePayloadDeliveryURL($link->getAddress()), [
716
																			  'body'            => $args,
717
																			  'timeout'         => 10,
718
																			  'connect_timeout' => 10,
719
																		  ]
720
				);
721
			} catch (Exception $e) {
722
				throw $e;
723
			}
724
		}
725
	}
726
727
728
	/**
729
	 * generateHeaders()
730
	 *
731
	 * Generate new headers for the current Payload, and save them in the SharingFrame.
732
	 *
733
	 * @param SharingFrame $frame
734
	 */
735
	public function updateFrameWithCloudId(SharingFrame $frame) {
736
		$frame->setCloudId($this->serverHost);
737
		$this->circlesRequest->updateFrame($frame);
738
	}
739
740
}