Completed
Push — master ( 77c8bc...4a5e09 )
by Maxence
02:57
created

FederatedLinkService::eventOnUpdateLinkStatus()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 3
eloc 7
nc 4
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
	 * checkUpdateLinkFromRemoteLinkRemove();
346
	 *
347
	 * in case of a request of status update from remote for a link down, we check the current
348
	 * status of the link locally
349
	 *
350
	 * @param Circle $circle
351
	 * @param FederatedLink $link
352
	 * @param int $status
353
	 *
354
	 * @throws FederatedCircleStatusUpdateException
355
	 */
356
	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...
357
358
		if ((int)$status !== FederatedLink::STATUS_LINK_REMOVE) {
359
			return;
360
		}
361
362
		$curStatus = $link->getStatus();
363
		$this->checkUpdateLinkFromRemoteLinkRequestSent($circle, $link);
364
		$this->checkUpdateLinkFromRemoteLinkRequested($circle, $link);
365
		$this->checkUpdateLinkFromRemoteLinkDown($circle, $link);
366
367
		if ($curStatus !== $link->getStatus()) {
368
			return;
369
		}
370
371
		throw new FederatedCircleStatusUpdateException(
372
			$this->l10n->t('Cannot proceed with this status update')
373
		);
374
	}
375
376
377
	/**
378
	 * @param Circle $circle
379
	 * @param FederatedLink $link
380
	 */
381 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...
382
383
		if ($link->getStatus() !== FederatedLink::STATUS_REQUEST_SENT) {
384
			return;
385
		}
386
387
		$link->setStatus(FederatedLink::STATUS_REQUEST_DECLINED);
388
		$this->eventsService->onLinkRequestRejected($circle, $link);
389
	}
390
391
392
	/**
393
	 * @param Circle $circle
394
	 * @param FederatedLink $link
395
	 */
396 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...
397
398
		if ($link->getStatus() !== FederatedLink::STATUS_LINK_REQUESTED) {
399
			return;
400
		}
401
402
		$link->setStatus(FederatedLink::STATUS_LINK_REMOVE);
403
		$this->eventsService->onLinkRequestCanceled($circle, $link);
404
	}
405
406
407
	/**
408
	 * @param Circle $circle
409
	 * @param FederatedLink $link
410
	 */
411 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...
412
413
		if ($link->getStatus() < FederatedLink::STATUS_LINK_DOWN) {
414
			return;
415
		}
416
417
		$link->setStatus(FederatedLink::STATUS_LINK_DOWN);
418
		$this->eventsService->onLinkDown($circle, $link);
419
	}
420
421
422
	/**
423
	 * @param FederatedLink $link
424
	 * @param array $options
425
	 *
426
	 * @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...
427
	 */
428
	public static function generateClientBodyData(FederatedLink $link, $options = []) {
429
		$args = array_merge(
430
			$options, [
431
						'apiVersion' => Circles::version(),
432
						'token'      => $link->getToken(true),
433
						'uniqueId'   => $link->getCircleId(true),
434
						'linkTo'     => $link->getRemoteCircleName(),
435
						'address'    => $link->getLocalAddress(),
436
						'status'     => $link->getStatus()
437
					]
438
		);
439
440
		return [
441
			'body'            => ['data' => $args],
442
			'timeout'         => 5,
443
			'connect_timeout' => 5,
444
		];
445
	}
446
447
448
}