Completed
Push — master ( 5a8cbd...82d575 )
by Maxence
06:28 queued 02:55
created

FederatedService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 1
Metric Value
dl 0
loc 23
rs 9.0856
c 2
b 0
f 1
cc 1
eloc 21
nc 1
nop 10

How to fix   Many Parameters   

Many Parameters

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

There are several approaches to avoid long parameter lists:

1
<?php
2
/**
3
 * 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 BroadcastService */
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 CirclesService $circlesService
87
	 * @param BroadcastService $broadcastService
88
	 * @param FederatedLinksRequest $federatedLinksRequest
89
	 * @param string $serverHost
90
	 * @param ClientService $clientService
91
	 * @param MiscService $miscService
92
	 */
93
	public function __construct(
94
		$userId,
95
		IL10N $l10n,
96
		CirclesRequest $circlesRequest,
97
		ConfigService $configService,
98
		CirclesService $circlesService,
99
		BroadcastService $broadcastService,
100
		FederatedLinksRequest $federatedLinksRequest,
101
		$serverHost,
102
		ClientService $clientService,
103
		MiscService $miscService
104
	) {
105
		$this->userId = $userId;
106
		$this->l10n = $l10n;
107
		$this->circlesRequest = $circlesRequest;
108
		$this->configService = $configService;
109
		$this->circlesService = $circlesService;
110
		$this->broadcastService = $broadcastService;
111
		$this->federatedLinksRequest = $federatedLinksRequest;
112
		$this->serverHost = (string)$serverHost;
113
		$this->clientService = $clientService;
114
		$this->miscService = $miscService;
115
	}
116
117
118
	/**
119
	 * linkCircle()
120
	 *
121
	 * link to a circle.
122
	 * Function will check if settings allow Federated links between circles, and the format of
123
	 * the link ($remote). If no exception, a request to the remote circle will be initiated
124
	 * using requestLinkWithCircle()
125
	 *
126
	 * $remote format: <circle_name>@<remote_host>
127
	 *
128
	 * @param int $circleId
129
	 * @param string $remote
130
	 *
131
	 * @throws Exception
132
	 * @throws FederatedCircleLinkFormatException
133
	 * @throws CircleTypeNotValid
134
	 * @throws MemberIsNotAdminException
135
	 *
136
	 * @return FederatedLink
137
	 */
138
	public function linkCircle($circleId, $remote) {
139
140
		if (!$this->configService->isFederatedAllowed()) {
141
			throw new FederatedCircleNotAllowedException(
142
				$this->l10n->t("Federated circles are not allowed on this Nextcloud")
143
			);
144
		}
145
146
		if (strpos($remote, '@') === false) {
147
			throw new FederatedCircleLinkFormatException(
148
				$this->l10n->t("Federated link does not have a valid format")
149
			);
150
		}
151
152
		try {
153
			return $this->requestLinkWithCircle($circleId, $remote);
154
		} catch (Exception $e) {
155
			throw $e;
156
		}
157
	}
158
159
160
	/**
161
	 * requestLinkWithCircle()
162
	 *
163
	 * Using CircleId, function will get more infos from the database.
164
	 * Will check if author is not admin and initiate a FederatedLink, save it
165
	 * in the database and send a request to the remote circle using requestLink()
166
	 * If any issue, entry is removed from the database.
167
	 *
168
	 * @param integer $circleId
169
	 * @param string $remote
170
	 *
171
	 * @return FederatedLink
172
	 * @throws Exception
173
	 */
174
	private function requestLinkWithCircle($circleId, $remote) {
175
176
		$link = null;
177
		try {
178
			list($remoteCircle, $remoteAddress) = explode('@', $remote, 2);
179
180
			$circle = $this->circlesService->detailsCircle($circleId);
181
			$circle->getUser()
182
				   ->hasToBeAdmin();
183
			$circle->cantBePersonal();
184
185
			$link = new FederatedLink();
186
			$link->setCircleId($circleId)
187
				 ->setLocalAddress($this->serverHost)
188
				 ->setAddress($remoteAddress)
189
				 ->setRemoteCircleName($remoteCircle)
190
				 ->setStatus(FederatedLink::STATUS_LINK_SETUP)
191
				 ->generateToken();
192
193
			$this->federatedLinksRequest->create($link);
194
			$this->requestLink($circle, $link);
195
196
		} catch (Exception $e) {
197
			$this->federatedLinksRequest->delete($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by null on line 176 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...
198
			throw $e;
199
		}
200
201
		return $link;
202
	}
203
204
205
	/**
206
	 * @param string $remote
207
	 *
208
	 * @return string
209
	 */
210 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...
211
		if (strpos($remote, 'https') !== 0) {
212
			$remote = 'https://'.$remote;
213
		}
214
215
		return rtrim($remote, '/').'/index.php/apps/circles/circles/link/';
216
	}
217
218
	/**
219
	 * @param string $remote
220
	 *
221
	 * @return string
222
	 */
223 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...
224
		if (strpos($remote, 'https') !== 0) {
225
			$remote = 'https://'.$remote;
226
		}
227
228
		return rtrim($remote, '/').'/index.php/apps/circles/circles/payload/';
229
	}
230
231
232
	/**
233
	 * requestLink()
234
	 *
235
	 *
236
	 * @param Circle $circle
237
	 * @param FederatedLink $link
238
	 *
239
	 * @return boolean
240
	 * @throws Exception
241
	 */
242
	private function requestLink(Circle $circle, FederatedLink & $link) {
243
		$args = [
244
			'token'      => $link->getToken(),
245
			'uniqueId'   => $circle->getUniqueId(),
246
			'sourceName' => $circle->getName(),
247
			'linkTo'     => $link->getRemoteCircleName(),
248
			'address'    => $link->getLocalAddress()
249
		];
250
251
		$client = $this->clientService->newClient();
252
253
		try {
254
			$request = $client->put(
255
				$this->generateLinkRemoteURL($link->getAddress()), [
256
																	 'body'            => $args,
257
																	 'timeout'         => 10,
258
																	 'connect_timeout' => 10,
259
																 ]
260
			);
261
262
			$result = json_decode($request->getBody(), true);
263
264
			$link->setStatus($result['status']);
265
			if (!$link->isValid()) {
266
				$this->parsingRequestLinkResult($result);
267
			}
268
269
			$link->setUniqueId($result['uniqueId']);
270
			$this->federatedLinksRequest->update($link);
271
272
			return true;
273
		} catch (Exception $e) {
274
			throw $e;
275
		}
276
	}
277
278
279
	private function parsingRequestLinkResult($result) {
280
281
		if ($result['reason'] === 'federated_not_allowed') {
282
			throw new FederatedRemoteDoesNotAllowException(
283
				$this->l10n->t('Federated circles are not allowed on the remote Nextcloud')
284
			);
285
		}
286
287
		if ($result['reason'] === 'duplicate_unique_id') {
288
			throw new FederatedRemoteDoesNotAllowException(
289
				$this->l10n->t('It seems that you are trying to link a circle to itself')
290
			);
291
		}
292
293
		if ($result['reason'] === 'duplicate_link') {
294
			throw new FederatedRemoteDoesNotAllowException(
295
				$this->l10n->t('This link exists already')
296
			);
297
		}
298
299
		if ($result['reason'] === 'circle_does_not_exist') {
300
			throw new FederatedRemoteCircleDoesNotExistException(
301
				$this->l10n->t('The requested remote circle does not exist')
302
			);
303
		}
304
305
		throw new Exception($result['reason']);
306
	}
307
308
309
	/**
310
	 * Create a new link into database and assign the correct status.
311
	 *
312
	 * @param Circle $circle
313
	 * @param FederatedLink $link
314
	 *
315
	 * @return bool
316
	 */
317
	public function initiateLink(Circle $circle, FederatedLink & $link) {
318
319
		$link->setCircleId($circle->getId());
320
321
		if ($circle->getType() === Circle::CIRCLES_PUBLIC) {
322
			$link->setStatus(FederatedLink::STATUS_LINK_UP);
323
		} else {
324
			$link->setStatus(FederatedLink::STATUS_REQUEST_SENT);
325
		}
326
327
		return $this->federatedLinksRequest->create($link);
328
	}
329
330
331
	/**
332
	 * @param string $token
333
	 * @param string $uniqueId
334
	 * @param SharingFrame $frame
335
	 *
336
	 * @return bool
337
	 * @throws Exception
338
	 */
339
	public function receiveFrame($token, $uniqueId, SharingFrame & $frame) {
340
341
		$link = $this->circlesRequest->getLinkFromToken((string)$token, (string)$uniqueId);
342
		if ($link === null) {
343
			return false;
344
			// TODO: throw Exception
345
		}
346
347
		if ($this->circlesRequest->getFrame($link->getCircleId(), $frame->getUniqueId())) {
348
			$this->miscService->log("FRAME ALREADY EXIST !!!");
349
350
			return false;
351
			// TODO: throw Exception
352
		}
353
354
355
		$circle = $this->circlesRequest->getDetails($link->getCircleId());
356
		if ($circle === null) {
357
			throw new Exception('unknown_circle');
358
		}
359
		$frame->setCircleId($link->getCircleId());
360
		$frame->setCircleName($circle->getName());
361
362
		$this->circlesRequest->saveFrame($frame);
363
		$this->broadcastService->broadcastFrame($frame->getHeader('broadcast'), $frame);
364
365
		return true;
366
	}
367
368
	/**
369
	 * @param integer $circleId
370
	 * @param string $uniqueId
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
	 */
374
	public function getLink($circleId, $uniqueId) {
375
		return $this->federatedLinksRequest->getFromUniqueId($circleId, $uniqueId);
376
	}
377
378
379
	/**
380
	 * @param integer $circleId
381
	 *
382
	 * @return FederatedLink[]
383
	 */
384
	public function getLinks($circleId) {
385
		return $this->federatedLinksRequest->getLinked($circleId);
386
	}
387
388
389
	/**
390
	 * @param int $circleId
391
	 * @param string $uniqueId
392
	 *
393
	 * @return bool
394
	 * @throws Exception
395
	 */
396
	public function initiateRemoteShare($circleId, $uniqueId) {
397
		$args = [
398
			'circleId' => (int)$circleId,
399
			'uniqueId' => (string)$uniqueId
400
		];
401
402
		$client = $this->clientService->newClient();
403
		try {
404
			$request = $client->post(
405
				$this->generatePayloadDeliveryURL($this->serverHost), [
406
																		'body'            => $args,
407
																		'timeout'         => 10,
408
																		'connect_timeout' => 10,
409
																	]
410
			);
411
412
			$result = json_decode($request->getBody(), true);
413
			$this->miscService->log(
414
				"initiateRemoteShare result: ".$uniqueId.'  ----  '.var_export($result, true)
415
			);
416
417
			return true;
418
		} catch (Exception $e) {
419
			throw $e;
420
		}
421
	}
422
423
424
	/**
425
	 * @param SharingFrame $frame
426
	 *
427
	 * @throws Exception
428
	 */
429
	public function sendRemoteShare(SharingFrame $frame) {
430
431
		$circle = $this->circlesRequest->getDetails($frame->getCircleId());
432
		if ($circle === null) {
433
			throw new Exception('unknown_circle');
434
		}
435
436
		$links = $this->getLinks($frame->getCircleId());
437
		foreach ($links AS $link) {
438
439
			$args = [
440
				'token'    => $link->getToken(),
441
				'uniqueId' => $circle->getUniqueId(),
442
				'item'     => json_encode($frame)
443
			];
444
445
			$client = $this->clientService->newClient();
446
			try {
447
				$client->put(
448
					$this->generatePayloadDeliveryURL($link->getAddress()), [
449
																			  'body'            => $args,
450
																			  'timeout'         => 10,
451
																			  'connect_timeout' => 10,
452
																		  ]
453
				);
454
			} catch (Exception $e) {
455
				throw $e;
456
			}
457
		}
458
	}
459
460
461
	/**
462
	 * generateHeaders()
463
	 *
464
	 * Generate new headers for the current Payload, and save them in the SharingFrame.
465
	 *
466
	 * @param SharingFrame $frame
467
	 */
468
	public function updateFrameWithCloudId(SharingFrame $frame) {
469
		$frame->setCloudId($this->serverHost);
470
		$this->circlesRequest->updateFrame($frame);
471
	}
472
473
}