Completed
Push — federated-circles ( 25ee14...bb3cdd )
by Maxence
02:31
created

FederatedService::requestLink()   B

Complexity

Conditions 3
Paths 11

Size

Total Lines 36
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 36
rs 8.8571
c 0
b 0
f 0
cc 3
eloc 23
nc 11
nop 2
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\CirclesMapper;
33
use OCA\Circles\Db\FederatedLinksRequest;
34
use OCA\Circles\Db\MembersMapper;
35
use OCA\Circles\Exceptions\FederatedCircleLinkFormatException;
36
use OCA\Circles\Exceptions\FederatedCircleNotAllowedException;
37
use OCA\Circles\Exceptions\CircleTypeNotValid;
38
use OCA\Circles\Exceptions\FederatedLinkCreationIssue;
39
use OCA\Circles\Exceptions\FederatedRemoteCircleDoesNotExistException;
40
use OCA\Circles\Exceptions\FederatedRemoteDoesNotAllowException;
41
use OCA\Circles\Exceptions\MemberIsNotAdminException;
42
use OCA\Circles\Model\Circle;
43
use OCA\Circles\Model\FederatedLink;
44
use OCP\IL10N;
45
46
class FederatedService {
47
48
49
	/** @var string */
50
	private $userId;
51
52
	/** @var IL10N */
53
	private $l10n;
54
55
	/** @var ConfigService */
56
	private $configService;
57
58
	/** @var CirclesService */
59
	private $circlesService;
60
61
	/** @var FederatedLinksRequest */
62
	private $federatedLinksRequest;
63
64
	/** @var CirclesMapper */
65
	private $dbCircles;
66
67
	/** @var MembersMapper */
68
	private $dbMembers;
69
70
	/** @var ClientService */
71
	private $clientService;
72
73
	/** @var MiscService */
74
	private $miscService;
75
76
77
	/**
78
	 * CirclesService constructor.
79
	 *
80
	 * @param $userId
81
	 * @param IL10N $l10n
82
	 * @param ConfigService $configService
83
	 * @param DatabaseService $databaseService
84
	 * @param CirclesService $circlesService
85
	 * @param FederatedLinksRequest $federatedLinksRequest
86
	 * @param string $serverHost
87
	 * @param ClientService $clientService
88
	 * @param MiscService $miscService
89
	 */
90
	public function __construct(
91
		$userId,
92
		IL10N $l10n,
93
		ConfigService $configService,
94
		DatabaseService $databaseService,
95
		CirclesService $circlesService,
96
		FederatedLinksRequest $federatedLinksRequest,
97
		string $serverHost,
98
		ClientService $clientService,
99
		MiscService $miscService
100
	) {
101
		$this->userId = $userId;
102
		$this->l10n = $l10n;
103
		$this->configService = $configService;
104
		$this->circlesService = $circlesService;
105
		$this->federatedLinksRequest = $federatedLinksRequest;
106
		$this->serverHost = $serverHost;
0 ignored issues
show
Bug introduced by
The property serverHost does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
107
		$this->clientService = $clientService;
108
109
		$this->miscService = $miscService;
110
111
		$this->dbCircles = $databaseService->getCirclesMapper();
112
		$this->dbMembers = $databaseService->getMembersMapper();
113
	}
114
115
116
	/**
117
	 * linkCircle()
118
	 *
119
	 * link to a circle.
120
	 * Function will check if settings allow Federated links between circles, and the format of
121
	 * the link ($remote). If no exception, a request to the remote circle will be initiated
122
	 * using requestLinkWithCircle()
123
	 *
124
	 * $remote format: <circle_name>@<remote_host>
125
	 *
126
	 * @param int $circleId
127
	 * @param string $remote
128
	 *
129
	 * @throws Exception
130
	 * @throws FederatedCircleLinkFormatException
131
	 * @throws CircleTypeNotValid
132
	 * @throws MemberIsNotAdminException
133
	 *
134
	 * @return FederatedLink
135
	 */
136
	public function linkCircle($circleId, $remote) {
137
138
		if (!$this->configService->isFederatedAllowed()) {
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->requestLinkWithCircle($circleId, $remote);
152
		} catch (Exception $e) {
153
			throw $e;
154
		}
155
	}
156
157
158
	/**
159
	 * requestLinkWithCircle()
160
	 *
161
	 * Using CircleId, function will get more infos from the database.
162
	 * Will check if author is not admin and initiate a FederatedLink, save it
163
	 * in the database and send a request to the remote circle using requestLink()
164
	 * If any issue, entry is removed from the database.
165
	 *
166
	 * @param $circleId
167
	 * @param $remote
168
	 *
169
	 * @return FederatedLink
170
	 * @throws Exception
171
	 */
172
	private function requestLinkWithCircle($circleId, $remote) {
173
174
		$link = null;
175
		try {
176
			list($remoteCircle, $remoteAddress) = explode('@', $remote, 2);
177
178
			$circle = $this->circlesService->detailsCircle($circleId);
179
			$circle->getUser()
180
				   ->hasToBeAdmin();
181
			$circle->cantBePersonal();
182
183
			$link = new FederatedLink();
184
			$link->setCircleId($circleId)
185
				 ->setLocalAddress($this->serverHost)
186
				 ->setAddress($remoteAddress)
187
				 ->setRemoteCircleName($remoteCircle)
188
				 ->setStatus(FederatedLink::STATUS_LINK_SETUP)
189
				 ->generateToken();
190
191
			$this->federatedLinksRequest->create($link);
192
			$this->requestLink($circle, $link);
193
194
		} catch (Exception $e) {
195
			$this->federatedLinksRequest->delete($link);
0 ignored issues
show
Bug introduced by
It seems like $link defined by null on line 174 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...
196
			throw $e;
197
		}
198
199
		return $link;
200
	}
201
202
203
	/**
204
	 * @param string $remote
205
	 *
206
	 * @return string
207
	 */
208
	private function generateLinkRemoteURL($remote) {
209
		if (strpos($remote, 'http') !== 0) {
210
			$remote = 'https://' . $remote;
211
		}
212
213
		return rtrim($remote, '/') . '/index.php/apps/circles/circles/link/';
214
	}
215
216
217
	/**
218
	 * requestLink()
219
	 *
220
	 *
221
	 * @param Circle $circle
222
	 * @param FederatedLink $link
223
	 *
224
	 * @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...
225
	 * @throws Exception
226
	 */
227
	private function requestLink(Circle $circle, FederatedLink &$link) {
228
		$args = [
229
			'token'      => $link->getToken(),
230
			'uniqueId'   => $circle->getUniqueId(),
231
			'sourceName' => $circle->getName(),
232
			'linkTo'     => $link->getRemoteCircleName(),
233
			'address'    => $link->getLocalAddress()
234
		];
235
236
		$client = $this->clientService->newClient();
237
238
		// TEST TEST TEST
239
		try {
240
			$request = $client->put(
241
				$this->generateLinkRemoteURL($link->getAddress()), [
242
																	 'body'            => $args,
243
																	 'timeout'         => 10,
244
																	 'connect_timeout' => 10,
245
																 ]
246
			);
247
248
			$result = json_decode($request->getBody(), true);
249
250
			$link->setStatus($result['status']);
251
			if (!$link->isValid()) {
252
				$this->parsingRequestLinkResult($result);
253
			}
254
255
			$link->setUniqueId($result['uniqueId']);
256
			$this->federatedLinksRequest->update($link);
257
258
			return true;
259
		} catch (Exception $e) {
260
			throw $e;
261
		}
262
	}
263
264
265
	private function parsingRequestLinkResult($result) {
266
267
		if ($result['reason'] === 'federated_not_allowed') {
268
			throw new FederatedRemoteDoesNotAllowException(
269
				$this->l10n->t('Federated circles are not allowed on the remote Nextcloud')
270
			);
271
		}
272
273
		if ($result['reason'] === 'duplicate_unique_id') {
274
			throw new FederatedRemoteDoesNotAllowException(
275
				$this->l10n->t('It seems that you are trying to link a circle to itself')
276
			);
277
		}
278
279
		if ($result['reason'] === 'duplicate_link') {
280
			throw new FederatedRemoteDoesNotAllowException(
281
				$this->l10n->t('This link exists already')
282
			);
283
		}
284
285
		if ($result['reason'] === 'circle_does_not_exist') {
286
			throw new FederatedRemoteCircleDoesNotExistException(
287
				$this->l10n->t('The requested remote circle does not exist')
288
			);
289
		}
290
291
		throw new Exception();
292
	}
293
294
295
	/**
296
	 * Create a new link into database and assign the correct status.
297
	 *
298
	 * @param Circle $circle
299
	 * @param FederatedLink $link
300
	 *
301
	 * @return bool
302
	 */
303
	public function initiateLink(Circle $circle, FederatedLink &$link) {
304
305
		$link->setCircleId($circle->getId());
306
307
		if ($circle->getType() === Circle::CIRCLES_PUBLIC) {
308
			$link->setStatus(FederatedLink::STATUS_LINK_UP);
309
		} else {
310
			$link->setStatus(FederatedLink::STATUS_REQUEST_SENT);
311
		}
312
313
		return $this->federatedLinksRequest->create($link);
314
	}
315
316
317
	/**
318
	 * @param $circleId
319
	 * @param $uniqueId
320
	 *
321
	 * @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...
322
	 */
323
	public function getLink($circleId, $uniqueId) {
324
		return $this->federatedLinksRequest->get($circleId, $uniqueId);
325
	}
326
327
}