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

parseRequestLinkError()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 17
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 17
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 11
nc 2
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 OCA\Circles\Db\CirclesRequest;
32
use OCA\Circles\Db\FederatedLinksRequest;
33
use OCA\Circles\Exceptions\CircleTypeNotValidException;
34
use OCA\Circles\Exceptions\FederatedCircleLinkFormatException;
35
use OCA\Circles\Exceptions\FederatedCircleNotAllowedException;
36
use OCA\Circles\Exceptions\FederatedLinkCreationException;
37
use OCA\Circles\Exceptions\FederatedLinkDoesNotExistException;
38
use OCA\Circles\Exceptions\FederatedRemoteDoesNotAllowException;
39
use OCA\Circles\Model\Circle;
40
use OCA\Circles\Model\FederatedLink;
41
use OCP\Http\Client\IClientService;
42
use OCP\IL10N;
43
44
class FederatedLinkCreationService {
45
46
	/** @var string */
47
	private $userId;
48
49
	/** @var IL10N */
50
	private $l10n;
51
52
	/** @var CirclesRequest */
53
	private $circlesRequest;
54
55
	/** @var ConfigService */
56
	private $configService;
57
58
	/** @var CirclesService */
59
	private $circlesService;
60
61
	/** @var BroadcastService */
62
	private $broadcastService;
63
64
	/** @var BroadcastService */
65
	private $federatedLinkService;
66
67
	/** @var FederatedLinksRequest */
68
	private $federatedLinksRequest;
69
70
	/** @var EventsService */
71
	private $eventsService;
72
73
	/** @var IClientService */
74
	private $clientService;
75
76
	/** @var MiscService */
77
	private $miscService;
78
79
80
	/**
81
	 * FederatedLinkCreationService constructor.
82
	 *
83
	 * @param string $UserId
84
	 * @param IL10N $l10n
85
	 * @param CirclesRequest $circlesRequest
86
	 * @param ConfigService $configService
87
	 * @param CirclesService $circlesService
88
	 * @param BroadcastService $broadcastService
89
	 * @param FederatedLinkService $federatedService
90
	 * @param FederatedLinksRequest $federatedLinksRequest
91
	 * @param EventsService $eventsService
92
	 * @param IClientService $clientService
93
	 * @param MiscService $miscService
94
	 */
95 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...
96
		$UserId, IL10N $l10n, CirclesRequest $circlesRequest, ConfigService $configService,
97
		CirclesService $circlesService, BroadcastService $broadcastService,
98
		FederatedLinkService $federatedService,
99
		FederatedLinksRequest $federatedLinksRequest, EventsService $eventsService,
100
		IClientService $clientService, MiscService $miscService
101
	) {
102
		$this->userId = $UserId;
103
		$this->l10n = $l10n;
104
		$this->circlesRequest = $circlesRequest;
105
		$this->configService = $configService;
106
		$this->circlesService = $circlesService;
107
		$this->broadcastService = $broadcastService;
108
		$this->federatedLinkService = $federatedService;
0 ignored issues
show
Documentation Bug introduced by
It seems like $federatedService of type object<OCA\Circles\Service\FederatedLinkService> is incompatible with the declared type object<OCA\Circles\Service\BroadcastService> of property $federatedLinkService.

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...
109
		$this->federatedLinksRequest = $federatedLinksRequest;
110
		$this->eventsService = $eventsService;
111
112
		$this->clientService = $clientService;
113
		$this->miscService = $miscService;
114
	}
115
116
117
	/**
118
	 * createLinkWithRemoteCircle();
119
	 *
120
	 * link to a circle.
121
	 * Function will check if settings allow Federated links between circles, and the format of
122
	 * the link ($remote). If no exception, a request to the remote circle will be initiated
123
	 * using requestLinkWithRemoteCircle()
124
	 *
125
	 * $remote format: <circle_name>@<remote_host>
126
	 *
127
	 * @param string $circleUniqueId
128
	 * @param string $remote
129
	 *
130
	 * @throws Exception
131
	 * @throws FederatedCircleLinkFormatException
132
	 * @throws CircleTypeNotValidException
133
	 *
134
	 * @return FederatedLink
135
	 */
136
	public function createLinkWithRemoteCircle($circleUniqueId, $remote) {
137
138
		if (!$this->configService->isFederatedCirclesAllowed()) {
139
			throw new FederatedCircleNotAllowedException(
140
				$this->l10n->t("Federated circles are not allowed on this Nextcloud")
141
			);
142
		}
143
144
		if (strpos($remote, '@') === false) {
145
			throw new FederatedCircleLinkFormatException(
146
				$this->l10n->t("Federated link does not have a valid format")
147
			);
148
		}
149
150
		try {
151
			return $this->requestLinkWithRemoteCircle($circleUniqueId, $remote);
152
		} catch (Exception $e) {
153
			throw $e;
154
		}
155
	}
156
157
158
159
	/**
160
	 * Create a new link into database and assign the correct status.
161
	 *
162
	 * @param Circle $circle
163
	 * @param FederatedLink $link
164
	 *
165
	 * @throws Exception
166
	 */
167
	public function requestedLinkFromRemoteCircle(Circle $circle, FederatedLink &$link) {
168
169
		try {
170
			$this->checkLinkRequestValidity($circle, $link);
171
			$link->setCircleId($circle->getUniqueId());
172
173
			if ($circle->getSetting('allow_links_auto') === 'true') {
174
				$link->setStatus(FederatedLink::STATUS_LINK_UP);
175
				$this->eventsService->onLinkUp($circle, $link);
176
			} else {
177
				$link->setStatus(FederatedLink::STATUS_LINK_REQUESTED);
178
				$this->eventsService->onLinkRequestReceived($circle, $link);
179
			}
180
181
			$this->federatedLinksRequest->create($link);
182
		} catch (Exception $e) {
183
			throw $e;
184
		}
185
	}
186
187
188
189
190
	/**
191
	 * @param Circle $circle
192
	 * @param FederatedLink $link
193
	 *
194
	 * @throws FederatedLinkCreationException
195
	 */
196
	private function checkLinkRequestValidity($circle, $link) {
197
		if ($circle->getUniqueId(true) === $link->getUniqueId(true)) {
198
			throw new FederatedLinkCreationException('duplicate_unique_id');
199
		}
200
201
		try {
202
			$this->federatedLinksRequest->getLinkFromCircle(
203
				$circle->getUniqueId(), $link->getUniqueId(true)
204
			);
205
			throw new FederatedLinkCreationException('duplicate_link');
206
		} catch (FederatedLinkDoesNotExistException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
207
		}
208
209
		if ($circle->getSetting('allow_links') !== 'true') {
210
			throw new FederatedLinkCreationException('circle_links_disable');
211
		}
212
	}
213
214
215
216
	/**
217
	 * requestLinkWithRemoteCircle()
218
	 *
219
	 * Using CircleId, function will get more infos from the database.
220
	 * Will check if author is at least admin and initiate a FederatedLink, save it
221
	 * in the database and send a request to the remote circle using requestLink()
222
	 * If any issue, entry is removed from the database.
223
	 *
224
	 * @param string $circleUniqueId
225
	 * @param string $remote
226
	 *
227
	 * @return FederatedLink
228
	 * @throws Exception
229
	 */
230
	private function requestLinkWithRemoteCircle($circleUniqueId, $remote) {
231
232
		$link = null;
233
		try {
234
			$circle = $this->circlesService->detailsCircle($circleUniqueId);
235
			$circle->getHigherViewer()
236
				   ->hasToBeAdmin();
237
			$circle->hasToBeFederated();
238
			$circle->cantBePersonal();
239
240
			$link = $this->generateNewLink($circle->getUniqueId(), $remote);
241
			$this->forceRequestNewLink($circle, $link);
242
		} catch (Exception $e) {
243
			$this->federatedLinksRequest->delete($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by null on line 232 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...
244
			throw $e;
245
		}
246
247
		return $link;
248
	}
249
250
251
	/**
252
	 * @param $circleUniqueId
253
	 * @param $remote
254
	 *
255
	 * @return FederatedLink
256
	 */
257
	private function generateNewLink($circleUniqueId, $remote) {
258
259
		$link = new FederatedLink();
260
		list($remoteCircle, $remoteAddress) = explode('@', $remote, 2);
261
262
		$link->setCircleId($circleUniqueId)
263
			 ->setLocalAddress($this->configService->getLocalAddress())
264
			 ->setAddress($remoteAddress)
265
			 ->setRemoteCircleName($remoteCircle)
266
			 ->setStatus(FederatedLink::STATUS_LINK_SETUP)
267
			 ->generateToken();
268
269
		$this->federatedLinksRequest->create($link);
270
271
		return $link;
272
	}
273
274
275
	/**
276
	 * requestLink()
277
	 *
278
	 *
279
	 * @param Circle $circle
280
	 * @param FederatedLink $link
281
	 *
282
	 * @return boolean
283
	 * @throws Exception
284
	 */
285
	private function forceRequestNewLink(Circle $circle, FederatedLink &$link) {
286
		try {
287
			$client = $this->clientService->newClient();
288
			$args = ['sourceName' => $circle->getName()];
289
			$url = $this->federatedLinkService->generateLinkRemoteURL($link->getAddress());
0 ignored issues
show
Bug introduced by
The method generateLinkRemoteURL() does not seem to exist on object<OCA\Circles\Service\BroadcastService>.

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...
290
291
			$response = $client->put($url, FederatedLinkService::generateClientBodyData($link, $args));
292
			$result = $this->federatedLinkService->parseClientRequestResult($response);
0 ignored issues
show
Bug introduced by
The method parseClientRequestResult() does not seem to exist on object<OCA\Circles\Service\BroadcastService>.

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...
293
294
			$reason = ((key_exists('reason', $result)) ? $result['reason'] : '');
295
			$this->eventOnRequestLink($circle, $link, $result['status'], $reason);
296
297
			$link->setUniqueId($result['uniqueId']);
298
			$this->federatedLinksRequest->update($link);
299
300
			return true;
301
		} catch (Exception $e) {
302
			throw $e;
303
		}
304
	}
305
306
307
	/**
308
	 * eventOnRequestLink();
309
	 *
310
	 * Called by requestLink() will update status and event
311
	 * Will also manage errors returned by the remote link
312
	 *
313
	 * @param Circle $circle
314
	 * @param FederatedLink $link
315
	 * @param int $status
316
	 * @param string $reason
317
	 *
318
	 * @throws Exception
319
	 */
320
	private function eventOnRequestLink(Circle $circle, FederatedLink &$link, $status, $reason) {
321
322
		switch ($status) {
323
			case FederatedLink::STATUS_LINK_UP:
324
				$link->setStatus(FederatedLink::STATUS_LINK_UP);
325
				$this->eventsService->onLinkUp($circle, $link);
326
				break;
327
328
			case  FederatedLink::STATUS_LINK_REQUESTED:
0 ignored issues
show
Coding Style introduced by
As per coding-style, case should be followed by a single space.

As per the PSR-2 coding standard, there must be a space after the case keyword, instead of the test immediately following it.

switch (true) {
    case!isset($a):  //wrong
        doSomething();
        break;
    case !isset($b):  //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
329
				$link->setStatus(FederatedLink::STATUS_REQUEST_SENT);
330
				$this->eventsService->onLinkRequestSent($circle, $link);
331
				break;
332
333
			default:
334
				$this->parseRequestLinkError($reason);
335
		}
336
	}
337
338
339
	/**
340
	 * parseRequestLinkError();
341
	 *
342
	 * Will parse the error reason returned by requestLink() and throw an Exception
343
	 *
344
	 * @param $reason
345
	 *
346
	 * @throws Exception
347
	 * @throws FederatedRemoteDoesNotAllowException
348
	 */
349
	private function parseRequestLinkError($reason) {
350
351
		$convert = [
352
			'federated_not_allowed' => $this->l10n->t(
353
				'Federated circles are not allowed on the remote Nextcloud'
354
			),
355
			'circle_links_disable'  => $this->l10n->t('Remote circle does not accept federated links'),
356
			'duplicate_unique_id'   => $this->l10n->t('Trying to link a circle to itself'),
357
			'duplicate_link'        => $this->l10n->t('This link exists already'),
358
			'circle_does_not_exist' => $this->l10n->t('The requested remote circle does not exist')
359
		];
360
361
		if (key_exists($reason, $convert)) {
362
			throw new FederatedRemoteDoesNotAllowException($convert[$reason]);
363
		}
364
		throw new Exception($reason);
365
	}
366
367
368
}