Completed
Push — master ( d4ce93...77c8bc )
by Maxence
02:50
created

checkUpdateLinkFromRemoteLinkUp()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
dl 0
loc 15
rs 9.4285
c 1
b 0
f 0
cc 3
eloc 9
nc 3
nop 3
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 OCA\Circles\Api\v1\Circles;
32
use OCA\Circles\AppInfo\Application;
33
use OCA\Circles\Db\CirclesRequest;
34
use OCA\Circles\Db\FederatedLinksRequest;
35
use OCA\Circles\Exceptions\CircleTypeNotValidException;
36
use OCA\Circles\Exceptions\FederatedCircleLinkFormatException;
37
use OCA\Circles\Exceptions\FederatedCircleStatusUpdateException;
38
use OCA\Circles\Exceptions\FederatedLinkCreationException;
39
use OCA\Circles\Exceptions\FederatedLinkDoesNotExistException;
40
use OCA\Circles\Exceptions\FederatedLinkUpdateException;
41
use OCA\Circles\Exceptions\FederatedRemoteIsDownException;
42
use OCA\Circles\Exceptions\MemberIsNotAdminException;
43
use OCA\Circles\Model\Circle;
44
use OCA\Circles\Model\FederatedLink;
45
use OCP\Http\Client\IClientService;
46
use OCP\Http\Client\IResponse;
47
use OCP\IL10N;
48
49
class FederatedLinkService {
50
51
	const REMOTE_URL_LINK = '/index.php/apps/circles/v1/link';
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 IClientService */
78
	private $clientService;
79
80
	/** @var MiscService */
81
	private $miscService;
82
83
84
	/**
85
	 * FederatedLinkService constructor.
86
	 *
87
	 * @param string $UserId
88
	 * @param IL10N $l10n
89
	 * @param CirclesRequest $circlesRequest
90
	 * @param ConfigService $configService
91
	 * @param CirclesService $circlesService
92
	 * @param BroadcastService $broadcastService
93
	 * @param FederatedLinksRequest $federatedLinksRequest
94
	 * @param EventsService $eventsService
95
	 * @param IClientService $clientService
96
	 * @param MiscService $miscService
97
	 */
98 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...
Coding Style Naming introduced by
The parameter $UserId is not named in camelCase.

This check marks parameter names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
99
		$UserId, IL10N $l10n, CirclesRequest $circlesRequest, ConfigService $configService,
100
		CirclesService $circlesService, BroadcastService $broadcastService,
101
		FederatedLinksRequest $federatedLinksRequest, EventsService $eventsService,
102
		IClientService $clientService, MiscService $miscService
103
	) {
104
		$this->userId = $UserId;
105
		$this->l10n = $l10n;
106
		$this->circlesRequest = $circlesRequest;
107
		$this->configService = $configService;
108
		$this->circlesService = $circlesService;
109
		$this->broadcastService = $broadcastService;
110
		$this->federatedLinksRequest = $federatedLinksRequest;
111
		$this->eventsService = $eventsService;
112
113
		$this->clientService = $clientService;
114
		$this->miscService = $miscService;
115
	}
116
117
//
118
119
	/**
120
	 * linkStatus()
121
	 *
122
	 * Update the status of a link.
123
	 * Function will check if user can edit the status, will update it and send the update to
124
	 * remote
125
	 *
126
	 * @param string $linkUniqueId
127
	 * @param int $status
128
	 *
129
	 * @throws Exception
130
	 * @throws FederatedCircleLinkFormatException
131
	 * @throws CircleTypeNotValidException
132
	 * @throws MemberIsNotAdminException
133
	 *
134
	 * @return FederatedLink[]
135
	 */
136
	public function linkStatus($linkUniqueId, $status) {
137
138
		try {
139
			$link = $this->federatedLinksRequest->getLinkFromId($linkUniqueId);
140
			$circle = $this->circlesRequest->getCircle($link->getCircleId(), $this->userId);
141
			$circle->hasToBeFederated();
142
			$circle->getHigherViewer()
143
				   ->hasToBeAdmin();
144
			$link->hasToBeValidStatusUpdate($status);
145
146
			if ($link->getStatus() !== $status) {
147
				$this->updateLinkStatus($link, $circle, $status);
148
			}
149
150
			return $this->federatedLinksRequest->getLinksFromCircle($circle->getUniqueId());
151
152
		} catch (Exception $e) {
153
			throw $e;
154
		}
155
	}
156
157
158
	/**
159
	 * @param FederatedLink $link
160
	 * @param Circle $circle
161
	 * @param int $status
162
	 *
163
	 * @return FederatedLink[]
164
	 * @throws Exception
165
	 */
166
	private function updateLinkStatus(FederatedLink $link, Circle $circle, $status) {
167
168
		$this->eventOnUpdateLinkStatus($link, $circle, $status);
169
170
		$link->setStatus($status);
171
		$link->setCircleId($circle->getUniqueId(true));
172
173
		try {
174
			$this->updateLinkRemote($link);
175
		} catch (Exception $e) {
176
			if ($status !== FederatedLink::STATUS_LINK_REMOVE) {
177
				throw $e;
178
			}
179
		}
180
181
		$this->federatedLinksRequest->update($link);
182
183
		return $this->federatedLinksRequest->getLinksFromCircle($circle->getUniqueId());
184
	}
185
186
187
	/**
188
	 * eventOnLinkStatus();
189
	 *
190
	 * Called by linkStatus() to manage events when status is changing.
191
	 * If status does not need update, returns false;
192
	 *
193
	 * @param FederatedLink $link
194
	 * @param Circle $circle
195
	 * @param int $status
196
	 *
197
	 * @return bool
198
	 */
199
	private function eventOnUpdateLinkStatus(FederatedLink $link, Circle $circle, $status) {
200
201
		if ($status === FederatedLink::STATUS_LINK_REMOVE) {
202
			$this->eventsService->onLinkRemove($circle, $link);
203
		}
204
205
		if ($status === FederatedLink::STATUS_LINK_UP) {
206
			$this->eventsService->onLinkRequestAccepting($circle, $link);
207
			$this->eventsService->onLinkUp($circle, $link);
208
		}
209
210
		return true;
211
	}
212
213
214
	/**
215
	 * @param string $remote
216
	 *
217
	 * @return string
218
	 */
219
	public function generateLinkRemoteURL($remote) {
220
		return $this->configService->generateRemoteHost($remote) . self::REMOTE_URL_LINK;
221
	}
222
223
224
	public function parseClientRequestResult(IResponse $response) {
225
		$result = json_decode($response->getBody(), true);
226
		if ($result === null) {
227
			throw new FederatedRemoteIsDownException(
228
				$this->l10n->t('The remote host is down or the Circles app is not installed on it')
229
			);
230
		}
231
232
		return $result;
233
	}
234
235
236
	/**
237
	 * @param string $token
238
	 * @param string $uniqueId
239
	 * @param int $status
240
	 *
241
	 * @return FederatedLink
242
	 * @throws Exception
243
	 */
244
	public function updateLinkFromRemote($token, $uniqueId, $status) {
245
		try {
246
			$link = $this->federatedLinksRequest->getLinkFromToken($token, $uniqueId);
247
			$circle = $this->circlesRequest->forceGetCircle($link->getCircleId());
248
			$circle->hasToBeFederated();
249
250
			$this->checkUpdateLinkFromRemote($status);
251
			$this->checkUpdateLinkFromRemoteLinkUp($circle, $link, $status);
252
			$this->checkUpdateLinkFromRemoteLinkRemove($circle, $link, $status);
253
254
			if ($link->getStatus() !== $status) {
255
				$this->federatedLinksRequest->update($link);
256
			}
257
258
			return $link;
259
		} catch (Exception $e) {
260
			throw $e;
261
		}
262
	}
263
264
265
	/**
266
	 * checkUpdateLinkFromRemote();
267
	 *
268
	 * will throw exception is the status sent by remote is not correct
269
	 *
270
	 * @param int $status
271
	 *
272
	 * @throws FederatedCircleStatusUpdateException
273
	 */
274
	private function checkUpdateLinkFromRemote($status) {
275
		$status = (int)$status;
276
		if ($status !== FederatedLink::STATUS_LINK_UP
277
			&& $status !== FederatedLink::STATUS_LINK_REMOVE
278
		) {
279
			throw new FederatedCircleStatusUpdateException(
280
				$this->l10n->t('Cannot proceed with this status update')
281
			);
282
		}
283
	}
284
285
286
	/**
287
	 * checkUpdateLinkFromRemoteLinkUp()
288
	 *
289
	 * in case of a request of status update from remote for a link up, we check the current
290
	 * status of the link locally.
291
	 *
292
	 * @param Circle $circle
293
	 * @param FederatedLink $link
294
	 * @param int $status
295
	 *
296
	 * @throws FederatedCircleStatusUpdateException
297
	 */
298
	private function checkUpdateLinkFromRemoteLinkUp(Circle $circle, FederatedLink $link, $status) {
299
		if ((int)$status !== FederatedLink::STATUS_LINK_UP) {
300
			return;
301
		}
302
303
		if ($link->getStatus() !== FederatedLink::STATUS_REQUEST_SENT) {
304
			throw new FederatedCircleStatusUpdateException(
305
				$this->l10n->t('Cannot proceed with this status update')
306
			);
307
		}
308
309
		$this->eventsService->onLinkRequestAccepted($circle, $link);
310
		$this->eventsService->onLinkUp($circle, $link);
311
		$link->setStatus($status);
312
	}
313
314
315
	/**
316
	 * updateLinkRemote()
317
	 *
318
	 * Send a request to the remote of the link to update its status.
319
	 *
320
	 * @param FederatedLink $link
321
	 *
322
	 * @return bool
323
	 * @throws Exception
324
	 */
325
	private function updateLinkRemote(FederatedLink &$link) {
326
327
		try {
328
			$client = $this->clientService->newClient();
329
			$body = self::generateClientBodyData($link);
330
			$response = $client->post($this->generateLinkRemoteURL($link->getAddress()), $body);
331
			$result = $this->parseClientRequestResult($response);
332
333
			if ($result['status'] === -1) {
334
				throw new FederatedLinkUpdateException($result['reason']);
335
			}
336
337
			return true;
338
		} catch (Exception $e) {
339
			throw $e;
340
		}
341
	}
342
343
344
	/**
345
	 * Create a new link into database and assign the correct status.
346
	 *
347
	 * @param Circle $circle
348
	 * @param FederatedLink $link
349
	 *
350
	 * @throws Exception
351
	 */
352
	public function initiateLink(Circle $circle, FederatedLink &$link) {
353
354
		try {
355
			$this->checkLinkRequestValidity($circle, $link);
356
			$link->setCircleId($circle->getUniqueId());
357
358
			if ($circle->getSetting('allow_links_auto') === 'true') {
359
				$link->setStatus(FederatedLink::STATUS_LINK_UP);
360
				$this->eventsService->onLinkUp($circle, $link);
361
			} else {
362
				$link->setStatus(FederatedLink::STATUS_LINK_REQUESTED);
363
				$this->eventsService->onLinkRequestReceived($circle, $link);
364
			}
365
366
			$this->federatedLinksRequest->create($link);
367
		} catch (Exception $e) {
368
			throw $e;
369
		}
370
	}
371
372
373
	/**
374
	 * @param Circle $circle
375
	 * @param FederatedLink $link
376
	 *
377
	 * @throws FederatedLinkCreationException
378
	 */
379
	private function checkLinkRequestValidity($circle, $link) {
380
		if ($circle->getUniqueId(true) === $link->getUniqueId(true)) {
381
			throw new FederatedLinkCreationException('duplicate_unique_id');
382
		}
383
384
		try {
385
			$this->federatedLinksRequest->getLinkFromCircle(
386
				$circle->getUniqueId(), $link->getUniqueId(true)
387
			);
388
			throw new FederatedLinkCreationException('duplicate_link');
389
		} catch (FederatedLinkDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
390
		}
391
392
		if ($circle->getSetting('allow_links') !== 'true') {
393
			throw new FederatedLinkCreationException('circle_links_disable');
394
		}
395
	}
396
397
398
	/**
399
	 * checkUpdateLinkFromRemoteLinkRemove();
400
	 *
401
	 * in case of a request of status update from remote for a link down, we check the current
402
	 * status of the link locally
403
	 *
404
	 * @param Circle $circle
405
	 * @param FederatedLink $link
406
	 * @param int $status
407
	 *
408
	 * @throws FederatedCircleStatusUpdateException
409
	 */
410
	private function checkUpdateLinkFromRemoteLinkRemove(Circle $circle, FederatedLink $link, $status) {
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 100 characters; contains 101 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
411
412
		if ((int)$status !== FederatedLink::STATUS_LINK_REMOVE) {
413
			return;
414
		}
415
416
		$curStatus = $link->getStatus();
417
		$this->checkUpdateLinkFromRemoteLinkRequestSent($circle, $link);
418
		$this->checkUpdateLinkFromRemoteLinkRequested($circle, $link);
419
		$this->checkUpdateLinkFromRemoteLinkDown($circle, $link);
420
421
		if ($curStatus !== $link->getStatus()) {
422
			return;
423
		}
424
425
		throw new FederatedCircleStatusUpdateException(
426
			$this->l10n->t('Cannot proceed with this status update')
427
		);
428
	}
429
430
431
	/**
432
	 * @param Circle $circle
433
	 * @param FederatedLink $link
434
	 */
435 View Code Duplication
	private function checkUpdateLinkFromRemoteLinkRequestSent(Circle $circle, FederatedLink &$link) {
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...
436
437
		if ($link->getStatus() !== FederatedLink::STATUS_REQUEST_SENT) {
438
			return;
439
		}
440
441
		$link->setStatus(FederatedLink::STATUS_REQUEST_DECLINED);
442
		$this->eventsService->onLinkRequestRejected($circle, $link);
443
	}
444
445
446
	/**
447
	 * @param Circle $circle
448
	 * @param FederatedLink $link
449
	 */
450 View Code Duplication
	private function checkUpdateLinkFromRemoteLinkRequested(Circle $circle, FederatedLink &$link) {
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...
451
452
		if ($link->getStatus() !== FederatedLink::STATUS_LINK_REQUESTED) {
453
			return;
454
		}
455
456
		$link->setStatus(FederatedLink::STATUS_LINK_REMOVE);
457
		$this->eventsService->onLinkRequestCanceled($circle, $link);
458
	}
459
460
461
	/**
462
	 * @param Circle $circle
463
	 * @param FederatedLink $link
464
	 */
465 View Code Duplication
	private function checkUpdateLinkFromRemoteLinkDown(Circle $circle, FederatedLink &$link) {
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...
466
467
		if ($link->getStatus() < FederatedLink::STATUS_LINK_DOWN) {
468
			return;
469
		}
470
471
		$link->setStatus(FederatedLink::STATUS_LINK_DOWN);
472
		$this->eventsService->onLinkDown($circle, $link);
473
	}
474
475
476
	/**
477
	 * @param FederatedLink $link
478
	 * @param array $options
479
	 *
480
	 * @return array
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use array<string,array<string,array>|integer>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
481
	 */
482
	public static function generateClientBodyData(FederatedLink $link, $options = []) {
483
		$args = array_merge(
484
			$options, [
485
						'apiVersion' => Circles::version(),
486
						'token'      => $link->getToken(true),
487
						'uniqueId'   => $link->getCircleId(true),
488
						'linkTo'     => $link->getRemoteCircleName(),
489
						'address'    => $link->getLocalAddress(),
490
						'status'     => $link->getStatus()
491
					]
492
		);
493
494
		return [
495
			'body'            => ['data' => $args],
496
			'timeout'         => 5,
497
			'connect_timeout' => 5,
498
		];
499
	}
500
501
502
}