Completed
Push — federated-circles ( 673aab...31ccdf )
by Maxence
02:42
created

FederatedService::parsingRequestLinkResult()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 28
rs 8.439
cc 5
eloc 14
nc 5
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\Db\CirclesRequest;
33
use OCA\Circles\Db\FederatedLinksRequest;
34
use OCA\Circles\Exceptions\FederatedCircleLinkFormatException;
35
use OCA\Circles\Exceptions\FederatedCircleNotAllowedException;
36
use OCA\Circles\Exceptions\CircleTypeNotValid;
37
use OCA\Circles\Exceptions\FederatedRemoteCircleDoesNotExistException;
38
use OCA\Circles\Exceptions\FederatedRemoteDoesNotAllowException;
39
use OCA\Circles\Exceptions\MemberIsNotAdminException;
40
use OCA\Circles\Model\Circle;
41
use OCA\Circles\Model\FederatedLink;
42
use OCA\Circles\Model\SharingFrame;
43
use OCP\IL10N;
44
45
class FederatedService {
46
47
48
	/** @var string */
49
	private $userId;
50
51
	/** @var IL10N */
52
	private $l10n;
53
54
	/** @var CirclesRequest */
55
	private $circlesRequest;
56
57
	/** @var ConfigService */
58
	private $configService;
59
60
	/** @var CirclesService */
61
	private $circlesService;
62
63
	/** @var SharesService */
64
	private $broadcastService;
65
66
	/** @var FederatedLinksRequest */
67
	private $federatedLinksRequest;
68
69
	/** @var string */
70
	private $serverHost;
71
72
	/** @var ClientService */
73
	private $clientService;
74
75
	/** @var MiscService */
76
	private $miscService;
77
78
79
	/**
80
	 * CirclesService constructor.
81
	 *
82
	 * @param $userId
83
	 * @param IL10N $l10n
84
	 * @param CirclesRequest $circlesRequest
85
	 * @param ConfigService $configService
86
	 * @param DatabaseService $databaseService
87
	 * @param CirclesService $circlesService
88
	 * @param BroadcastService $broadcastService
89
	 * @param FederatedLinksRequest $federatedLinksRequest
90
	 * @param string $serverHost
91
	 * @param ClientService $clientService
92
	 * @param MiscService $miscService
93
	 */
94
	public function __construct(
95
		$userId,
96
		IL10N $l10n,
97
		CirclesRequest $circlesRequest,
98
		ConfigService $configService,
99
		DatabaseService $databaseService,
0 ignored issues
show
Unused Code introduced by
The parameter $databaseService 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...
100
		CirclesService $circlesService,
101
		BroadcastService $broadcastService,
102
		FederatedLinksRequest $federatedLinksRequest,
103
		string $serverHost,
104
		ClientService $clientService,
105
		MiscService $miscService
106
	) {
107
		$this->userId = $userId;
108
		$this->l10n = $l10n;
109
		$this->circlesRequest = $circlesRequest;
110
		$this->configService = $configService;
111
		$this->circlesService = $circlesService;
112
		$this->broadcastService = $broadcastService;
0 ignored issues
show
Documentation Bug introduced by
It seems like $broadcastService of type object<OCA\Circles\Service\BroadcastService> is incompatible with the declared type object<OCA\Circles\Service\SharesService> of property $broadcastService.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
113
		$this->federatedLinksRequest = $federatedLinksRequest;
114
		$this->serverHost = $serverHost;
115
		$this->clientService = $clientService;
116
		$this->miscService = $miscService;
117
	}
118
119
120
	/**
121
	 * linkCircle()
122
	 *
123
	 * link to a circle.
124
	 * Function will check if settings allow Federated links between circles, and the format of
125
	 * the link ($remote). If no exception, a request to the remote circle will be initiated
126
	 * using requestLinkWithCircle()
127
	 *
128
	 * $remote format: <circle_name>@<remote_host>
129
	 *
130
	 * @param int $circleId
131
	 * @param string $remote
132
	 *
133
	 * @throws Exception
134
	 * @throws FederatedCircleLinkFormatException
135
	 * @throws CircleTypeNotValid
136
	 * @throws MemberIsNotAdminException
137
	 *
138
	 * @return FederatedLink
139
	 */
140
	public function linkCircle($circleId, $remote) {
141
142
		if (!$this->configService->isFederatedAllowed()) {
143
			throw new FederatedCircleNotAllowedException(
144
				$this->l10n->t("Federated circles are not allowed on this Nextcloud")
145
			);
146
		}
147
148
		if (strpos($remote, '@') === false) {
149
			throw new FederatedCircleLinkFormatException(
150
				$this->l10n->t("Federated link does not have a valid format")
151
			);
152
		}
153
154
		try {
155
			return $this->requestLinkWithCircle($circleId, $remote);
156
		} catch (Exception $e) {
157
			throw $e;
158
		}
159
	}
160
161
162
	/**
163
	 * requestLinkWithCircle()
164
	 *
165
	 * Using CircleId, function will get more infos from the database.
166
	 * Will check if author is not admin and initiate a FederatedLink, save it
167
	 * in the database and send a request to the remote circle using requestLink()
168
	 * If any issue, entry is removed from the database.
169
	 *
170
	 * @param $circleId
171
	 * @param $remote
172
	 *
173
	 * @return FederatedLink
174
	 * @throws Exception
175
	 */
176
	private function requestLinkWithCircle($circleId, $remote) {
177
178
		$link = null;
179
		try {
180
			list($remoteCircle, $remoteAddress) = explode('@', $remote, 2);
181
182
			$circle = $this->circlesService->detailsCircle($circleId);
183
			$circle->getUser()
184
				   ->hasToBeAdmin();
185
			$circle->cantBePersonal();
186
187
			$link = new FederatedLink();
188
			$link->setCircleId($circleId)
189
				 ->setLocalAddress($this->serverHost)
190
				 ->setAddress($remoteAddress)
191
				 ->setRemoteCircleName($remoteCircle)
192
				 ->setStatus(FederatedLink::STATUS_LINK_SETUP)
193
				 ->generateToken();
194
195
			$this->federatedLinksRequest->create($link);
196
			$this->requestLink($circle, $link);
197
198
		} catch (Exception $e) {
199
			$this->federatedLinksRequest->delete($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by null on line 178 can be null; however, OCA\Circles\Db\FederatedLinksRequest::delete() 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...
200
			throw $e;
201
		}
202
203
		return $link;
204
	}
205
206
207
	/**
208
	 * @param string $remote
209
	 *
210
	 * @return string
211
	 */
212 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...
213
		if (strpos($remote, 'http') !== 0) {
214
			$remote = 'https://' . $remote;
215
		}
216
217
		return rtrim($remote, '/') . '/index.php/apps/circles/circles/link/';
218
	}
219
220
	/**
221
	 * @param string $remote
222
	 *
223
	 * @return string
224
	 */
225 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...
226
		if (strpos($remote, 'http') !== 0) {
227
			$remote = 'http://' . $remote;
228
		}
229
230
		return rtrim($remote, '/') . '/index.php/apps/circles/circles/payload/';
231
	}
232
233
234
	/**
235
	 * requestLink()
236
	 *
237
	 *
238
	 * @param Circle $circle
239
	 * @param FederatedLink $link
240
	 *
241
	 * @return int
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean?

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...
242
	 * @throws Exception
243
	 */
244
	private function requestLink(Circle $circle, FederatedLink &$link) {
245
		$args = [
246
			'token'      => $link->getToken(),
247
			'uniqueId'   => $circle->getUniqueId(),
248
			'sourceName' => $circle->getName(),
249
			'linkTo'     => $link->getRemoteCircleName(),
250
			'address'    => $link->getLocalAddress()
251
		];
252
253
		$client = $this->clientService->newClient();
254
255
		try {
256
			$request = $client->put(
257
				$this->generateLinkRemoteURL($link->getAddress()), [
258
																	 'body'            => $args,
259
																	 'timeout'         => 10,
260
																	 'connect_timeout' => 10,
261
																 ]
262
			);
263
264
			$result = json_decode($request->getBody(), true);
265
266
			$link->setStatus($result['status']);
267
			if (!$link->isValid()) {
268
				$this->parsingRequestLinkResult($result);
269
			}
270
271
			$link->setUniqueId($result['uniqueId']);
272
			$this->federatedLinksRequest->update($link);
273
274
			return true;
275
		} catch (Exception $e) {
276
			throw $e;
277
		}
278
	}
279
280
281
	private function parsingRequestLinkResult($result) {
282
283
		if ($result['reason'] === 'federated_not_allowed') {
284
			throw new FederatedRemoteDoesNotAllowException(
285
				$this->l10n->t('Federated circles are not allowed on the remote Nextcloud')
286
			);
287
		}
288
289
		if ($result['reason'] === 'duplicate_unique_id') {
290
			throw new FederatedRemoteDoesNotAllowException(
291
				$this->l10n->t('It seems that you are trying to link a circle to itself')
292
			);
293
		}
294
295
		if ($result['reason'] === 'duplicate_link') {
296
			throw new FederatedRemoteDoesNotAllowException(
297
				$this->l10n->t('This link exists already')
298
			);
299
		}
300
301
		if ($result['reason'] === 'circle_does_not_exist') {
302
			throw new FederatedRemoteCircleDoesNotExistException(
303
				$this->l10n->t('The requested remote circle does not exist')
304
			);
305
		}
306
307
		throw new Exception($result['reason']);
308
	}
309
310
311
	/**
312
	 * Create a new link into database and assign the correct status.
313
	 *
314
	 * @param Circle $circle
315
	 * @param FederatedLink $link
316
	 *
317
	 * @return bool
318
	 */
319
	public function initiateLink(Circle $circle, FederatedLink &$link) {
320
321
		$link->setCircleId($circle->getId());
322
323
		if ($circle->getType() === Circle::CIRCLES_PUBLIC) {
324
			$link->setStatus(FederatedLink::STATUS_LINK_UP);
325
		} else {
326
			$link->setStatus(FederatedLink::STATUS_REQUEST_SENT);
327
		}
328
329
		return $this->federatedLinksRequest->create($link);
330
	}
331
332
333
	/**
334
	 * @param string $token
335
	 * @param string $uniqueId
336
	 * @param SharingFrame $frame
337
	 *
338
	 * @return bool
339
	 * @throws Exception
340
	 */
341
	public function receiveFrame(string $token, string $uniqueId, SharingFrame &$frame) {
342
343
		$link = $this->circlesRequest->getLinkFromToken($token, $uniqueId);
344
		if ($link === null) {
345
			return false;
346
			// TODO: throw Exception
347
		}
348
349
		if ($this->circlesRequest->getFrame($link->getCircleId(), $frame->getUniqueId())) {
350
			$this->miscService->log("FRAME ALREADY EXIST !!!");
351
352
			return false;
353
			// TODO: throw Exception
354
		}
355
356
357
		$circle = $this->circlesRequest->getDetails($link->getCircleId());
358
		if ($circle === null) {
359
			throw new Exception('unknown_circle');
360
		}
361
		$frame->setCircleId($link->getCircleId());
362
		$frame->setCircleName($circle->getName());
363
364
		$this->circlesRequest->saveFrame($frame);
365
		$this->broadcastService->broadcastFrame($frame->getHeader('broadcast'), $frame);
0 ignored issues
show
Bug introduced by
The method broadcastFrame() does not seem to exist on object<OCA\Circles\Service\SharesService>.

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...
366
		$this->sendRemoteShare($frame);
367
368
		return true;
369
	}
370
371
	/**
372
	 * @param $circleId
373
	 * @param $uniqueId
374
	 *
375
	 * @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...
376
	 */
377
	public function getLink($circleId, $uniqueId) {
378
		return $this->federatedLinksRequest->getFromUniqueId($circleId, $uniqueId);
379
	}
380
381
382
	/**
383
	 * @param $circleId
384
	 *
385
	 * @return FederatedLink[]
386
	 */
387
	public function getLinks($circleId) {
388
		return $this->federatedLinksRequest->getLinked($circleId);
389
	}
390
391
392
	/**
393
	 * @param int $circleId
394
	 * @param string $uniqueId
395
	 *
396
	 * @return bool
397
	 * @throws Exception
398
	 */
399
	public function initiateRemoteShare(int $circleId, string $uniqueId) {
400
		$args = [
401
			'circleId' => $circleId,
402
			'uniqueId' => $uniqueId
403
		];
404
405
		$client = $this->clientService->newClient();
406
		try {
407
			$request = $client->post(
408
				$this->generatePayloadDeliveryURL($this->serverHost), [
409
																		'body'            => $args,
410
																		'timeout'         => 10,
411
																		'connect_timeout' => 10,
412
																	]
413
			);
414
415
			$result = json_decode($request->getBody(), true);
416
			$this->miscService->log(
417
				"initiateRemoteShare result: " . $uniqueId . '  ----  ' . var_export($result, true)
418
			);
419
420
			return true;
421
		} catch (Exception $e) {
422
			throw $e;
423
		}
424
	}
425
426
427
	/**
428
	 * @param SharingFrame $frame
429
	 *
430
	 * @throws Exception
431
	 */
432
	public function sendRemoteShare(SharingFrame $frame) {
433
434
		$circle = $this->circlesRequest->getDetails($frame->getCircleId());
435
		if ($circle === null) {
436
			throw new Exception('unknown_circle');
437
		}
438
439
		$links = $this->getLinks($frame->getCircleId());
440
		foreach ($links AS $link) {
441
442
			$args = [
443
				'token'    => $link->getToken(),
444
				'uniqueId' => $circle->getUniqueId(),
445
				'item'     => json_encode($frame)
446
			];
447
448
			$client = $this->clientService->newClient();
449
			try {
450
				$request = $client->put(
0 ignored issues
show
Unused Code introduced by
$request is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
451
					$this->generatePayloadDeliveryURL($link->getAddress()), [
452
																			  'body'            => $args,
453
																			  'timeout'         => 10,
454
																			  'connect_timeout' => 10,
455
																		  ]
456
				);
457
			} catch (Exception $e) {
458
				throw $e;
459
			}
460
		}
461
	}
462
463
464
	/**
465
	 * generateHeaders()
466
	 *
467
	 * Generate new headers for the current Payload, and save them in the SharingFrame.
468
	 *
469
	 * @param SharingFrame $frame
470
	 */
471
	public function updateFrameWithCloudId(SharingFrame $frame) {
472
		$frame->setCloudId($this->serverHost);
473
		$this->circlesRequest->updateFrame($frame);
474
	}
475
476
}