Completed
Push — federated-circles ( 1e2c73...673aab )
by Maxence
02:33
created

FederatedService   A

Complexity

Total Complexity 33

Size/Duplication

Total Lines 428
Duplicated Lines 8.64 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 4
Bugs 0 Features 1
Metric Value
wmc 33
lcom 1
cbo 13
dl 37
loc 428
rs 9.3999
c 4
b 0
f 1

14 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 23 23 1
A linkCircle() 0 20 4
B requestLinkWithCircle() 0 29 2
A generateLinkRemoteURL() 7 7 2
A generatePayloadDeliveryURL() 7 7 2
B requestLink() 0 35 3
B parsingRequestLinkResult() 0 28 5
A initiateLink() 0 12 2
B receiveFrame() 0 27 3
A getLink() 0 3 1
A getLinks() 0 3 1
B initiateRemoteShare() 0 26 2
B sendRemoteShare() 0 30 4
A updateFrameWithCloudId() 0 4 1

How to fix   Duplicated Code   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

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 $sharesService;
0 ignored issues
show
Unused Code introduced by
The property $sharesService is not used and could be removed.

This check marks private properties in classes that are never used. Those properties can be removed.

Loading history...
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 SharesService $sharesService
0 ignored issues
show
Bug introduced by
There is no parameter named $sharesService. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

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