Completed
Pull Request — master (#551)
by Maxence
02:04
created

RemoteUpstreamService::checkCircle()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 8
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php
2
3
declare(strict_types=1);
4
5
6
/**
7
 * Circles - Bring cloud-users closer together.
8
 *
9
 * This file is licensed under the Affero General Public License version 3 or
10
 * later. See the COPYING file.
11
 *
12
 * @author Maxence Lange <[email protected]>
13
 * @copyright 2021
14
 * @license GNU AGPL version 3 or any later version
15
 *
16
 * This program is free software: you can redistribute it and/or modify
17
 * it under the terms of the GNU Affero General Public License as
18
 * published by the Free Software Foundation, either version 3 of the
19
 * License, or (at your option) any later version.
20
 *
21
 * This program is distributed in the hope that it will be useful,
22
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
24
 * GNU Affero General Public License for more details.
25
 *
26
 * You should have received a copy of the GNU Affero General Public License
27
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
28
 *
29
 */
30
31
32
namespace OCA\Circles\Service;
33
34
35
use daita\MySmallPhpTools\Exceptions\RequestContentException;
36
use daita\MySmallPhpTools\Exceptions\RequestNetworkException;
37
use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException;
38
use daita\MySmallPhpTools\Exceptions\RequestResultSizeException;
39
use daita\MySmallPhpTools\Exceptions\RequestServerException;
40
use daita\MySmallPhpTools\Model\Nextcloud\nc21\NC21Request;
41
use daita\MySmallPhpTools\Model\Request;
42
use daita\MySmallPhpTools\Model\SimpleDataStore;
43
use daita\MySmallPhpTools\Traits\Nextcloud\nc21\TNC21Request;
44
use Exception;
45
use OCA\Circles\Db\DeprecatedCirclesRequest;
46
use OCA\Circles\Db\DeprecatedMembersRequest;
47
use OCA\Circles\Db\GSEventsRequest;
48
use OCA\Circles\Exceptions\GlobalScaleEventException;
49
use OCA\Circles\Exceptions\GSStatusException;
50
use OCA\Circles\Exceptions\JsonException;
51
use OCA\Circles\Exceptions\ModelException;
52
use OCA\Circles\GlobalScale\CircleStatus;
53
use OCA\Circles\Model\DeprecatedCircle;
54
use OCA\Circles\Model\GlobalScale\GSEvent;
55
use OCA\Circles\Model\GlobalScale\GSWrapper;
56
use OCP\IURLGenerator;
57
58
59
/**
60
 * Class RemoteUpstreamService
61
 *
62
 * @package OCA\Circles\Service
63
 */
64
class RemoteUpstreamService {
65
66
67
	use TNC21Request;
68
69
70
	/** @var string */
71
	private $userId = '';
72
73
	/** @var IURLGenerator */
74
	private $urlGenerator;
75
76
	/** @var GSEventsRequest */
77
	private $gsEventsRequest;
78
79
	/** @var DeprecatedCirclesRequest */
80
	private $circlesRequest;
81
82
	/** @var DeprecatedMembersRequest */
83
	private $membersRequest;
84
85
	/** @var RemoteEventService */
86
	private $remoteEventService;
87
88
	/** @var ConfigService */
89
	private $configService;
90
91
	/** @var MiscService */
92
	private $miscService;
93
94
95
	/**
96
	 * GSUpstreamService constructor.
97
	 *
98
	 * @param $userId
99
	 * @param IURLGenerator $urlGenerator
100
	 * @param GSEventsRequest $gsEventsRequest
101
	 * @param DeprecatedCirclesRequest $circlesRequest
102
	 * @param DeprecatedMembersRequest $membersRequest
103
	 * @param RemoteEventService $remoteEventService
104
	 * @param ConfigService $configService
105
	 * @param MiscService $miscService
106
	 */
107
	public function __construct(
108
		$userId,
109
		IURLGenerator $urlGenerator,
110
		GSEventsRequest $gsEventsRequest,
111
		DeprecatedCirclesRequest $circlesRequest,
112
		DeprecatedMembersRequest $membersRequest,
113
		RemoteEventService $remoteEventService,
114
		ConfigService $configService,
115
		MiscService $miscService
116
	) {
117
		$this->userId = $userId;
118
		$this->urlGenerator = $urlGenerator;
119
		$this->gsEventsRequest = $gsEventsRequest;
120
		$this->circlesRequest = $circlesRequest;
121
		$this->membersRequest = $membersRequest;
122
		$this->remoteEventService = $remoteEventService;
123
		$this->configService = $configService;
124
		$this->miscService = $miscService;
125
	}
126
127
128
	/**
129
	 * @param GSWrapper $wrapper
130
	 */
131
	public function broadcastWrapper(GSWrapper $wrapper): void {
132
		$status = GSWrapper::STATUS_FAILED;
133
134
		try {
135
			$this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance());
136
			$status = GSWrapper::STATUS_DONE;
137
		} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
138
		}
139
140
		if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) {
141
			$wrapper->setStatus($status);
142
		} else {
143
			$wrapper->setStatus(GSWrapper::STATUS_OVER);
144
		}
145
146
		$this->gsEventsRequest->update($wrapper);
147
	}
148
149
150
	/**
151
	 * @param GSEvent $event
152
	 * @param string $instance
153
	 * @param string $protocol
0 ignored issues
show
Bug introduced by
There is no parameter named $protocol. 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...
154
	 *
155
	 * @throws RequestContentException
156
	 * @throws RequestNetworkException
157
	 * @throws RequestResultNotJsonException
158
	 * @throws RequestResultSizeException
159
	 * @throws RequestServerException
160
	 */
161
	public function broadcastEvent(GSEvent $event, string $instance): void {
162
		$this->signEvent($event);
163
164
		if ($this->configService->isLocalInstance($instance)) {
165
			$request = new NC21Request('', Request::TYPE_POST);
166
			$this->configService->configureRequest($request, 'circles.GlobalScale.broadcast');
167
		} else {
168
			$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.broadcast');
169
			$request = new NC21Request($path, Request::TYPE_POST);
170
			$this->configService->configureRequest($request);
171
			$request->setInstance($instance);
172
		}
173
174
		$request->setDataSerialize($event);
175
176
		$data = $this->retrieveJson($request);
177
		$event->setResult(new SimpleDataStore($this->getArray('result', $data, [])));
178
	}
179
180
181
	/**
182
	 * @param GSEvent $event
183
	 */
184
	private function signEvent(GSEvent $event) {
185
		$event->setKey($this->remoteEventService->getKey());
0 ignored issues
show
Bug introduced by
The method getKey() does not exist on OCA\Circles\Service\RemoteEventService. Did you maybe mean getKeyOrigin()?

This check marks calls to methods that do not seem to exist on an object.

This is most likely the result of a method being renamed without all references to it being renamed likewise.

Loading history...
186
	}
187
188
189
	/**
190
	 * @param bool $all
191
	 *
192
	 * @return array
193
	 */
194
	public function getInstances(bool $all = false): array {
195
		/** @var string $lookup */
196
		try {
197
			$lookup = $this->configService->getGSStatus(ConfigService::GS_LOOKUP);
198
			$request = new NC21Request(ConfigService::GS_LOOKUP_INSTANCES, Request::TYPE_POST);
199
			$this->configService->configureRequest($request);
200
			$request->basedOnUrl($lookup);
201
			$request->addData('authKey', $this->configService->getGSStatus(ConfigService::GS_KEY));
202
203
			try {
204
				$instances = $this->retrieveJson($request);
205
			} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
206
				$this->miscService->log(
207
					'Issue while retrieving instances from lookup: ' . get_class($e) . ' ' . $e->getMessage()
208
				);
209
210
				return [];
211
			}
212
		} catch (GSStatusException $e) {
213
			if (!$all) {
214
				return [];
215
			}
216
217
			return [$this->configService->getLocalInstance()];
218
		}
219
220
		if ($all) {
221
			return $instances;
222
		}
223
224
		return array_values(array_diff($instances, $this->configService->getTrustedDomains()));
225
	}
226
227
228
	/**
229
	 * @param string $token
230
	 *
231
	 * @return GSWrapper[]
232
	 * @throws JsonException
233
	 * @throws ModelException
234
	 */
235
	public function getEventsByToken(string $token): array {
236
		return $this->gsEventsRequest->getByToken($token);
237
	}
238
239
240
	/**
241
	 * @param array $sync
242
	 *
243
	 * @throws GSStatusException
244
	 */
245
	public function synchronize(array $sync) {
246
		$this->configService->getGSStatus();
247
248
		$this->synchronizeCircles($sync);
249
		$this->removeDeprecatedCircles();
250
		$this->removeDeprecatedEvents();
0 ignored issues
show
Unused Code introduced by
The call to the method OCA\Circles\Service\Remo...emoveDeprecatedEvents() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
251
	}
252
253
254
	/**
255
	 * @param array $circles
256
	 */
257
	public function synchronizeCircles(array $circles): void {
258
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
259
		$event->setSource($this->configService->getLocalInstance());
260
		$event->setData(new SimpleDataStore($circles));
261
262
		foreach ($this->getInstances() as $instance) {
263
			try {
264
				$this->broadcastEvent($event, $instance);
265
			} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
266
			}
267
		}
268
	}
269
270
271
	/**
272
	 *
273
	 */
274
	private function removeDeprecatedCircles() {
275
		$knownCircles = $this->circlesRequest->forceGetCircles();
276
		foreach ($knownCircles as $knownItem) {
277
			if ($knownItem->getOwner()
278
						  ->getInstance() === '') {
279
				continue;
280
			}
281
282
			try {
283
				$this->checkCircle($knownItem);
284
			} catch (GSStatusException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
285
			}
286
		}
287
	}
288
289
290
	/**
291
	 * @param DeprecatedCircle $circle
292
	 *
293
	 * @throws GSStatusException
294
	 */
295
	private function checkCircle(DeprecatedCircle $circle): void {
296
		$status = $this->confirmCircleStatus($circle);
297
298
		if (!$status) {
299
			$this->circlesRequest->destroyCircle($circle->getUniqueId());
300
			$this->membersRequest->removeAllFromCircle($circle->getUniqueId());
301
		}
302
	}
303
304
305
	/**
306
	 * @param DeprecatedCircle $circle
307
	 *
308
	 * @return bool
309
	 * @throws GSStatusException
310
	 */
311
	public function confirmCircleStatus(DeprecatedCircle $circle): bool {
312
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
313
		$event->setSource($this->configService->getLocalInstance());
314
		$event->setDeprecatedCircle($circle);
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Circles\Model\Global...::setDeprecatedCircle() has been deprecated.

This method has been deprecated.

Loading history...
315
316
		$this->signEvent($event);
317
318
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.status');
319
		$request = new NC21Request($path, Request::TYPE_POST);
320
		$this->configService->configureRequest($request);
321
		$request->setDataSerialize($event);
322
323
		$requestIssue = false;
324
		$notFound = false;
325
		$foundWithNoOwner = false;
326
		foreach ($this->remoteEventService->getInstances() as $instance) {
0 ignored issues
show
Bug introduced by
The method getInstances() cannot be called from this context as it is declared private in class OCA\Circles\Service\RemoteEventService.

This check looks for access to methods that are not accessible from the current context.

If you need to make a method accessible to another context you can raise its visibility level in the defining class.

Loading history...
327
			$request->setHost($instance);
328
329
			try {
330
				$result = $this->retrieveJson($request);
331
//				$this->miscService->log('result: ' . json_encode($result));
332
				if ($this->getInt('status', $result, 0) !== 1) {
333
					throw new RequestContentException('result status is not good');
334
				}
335
336
				$status = $this->getInt('success.data.status', $result);
337
338
				// if error, we assume the circle might still exist.
339
				if ($status === CircleStatus::STATUS_ERROR) {
340
					return true;
341
				}
342
343
				if ($status === CircleStatus::STATUS_OK) {
344
					return true;
345
				}
346
347
				// TODO: check the data.supposedOwner entry.
348
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
349
					$foundWithNoOwner = true;
350
				}
351
352
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
353
					$notFound = true;
354
				}
355
356
			} catch (RequestContentException
357
			| RequestNetworkException
358
			| RequestResultNotJsonException
359
			| RequestResultSizeException
360
			| RequestServerException $e) {
361
				$requestIssue = true;
362
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
363
				continue;
364
			}
365
		}
366
367
		// if no request issue, we can imagine that the instance that owns the circle is down.
368
		// We'll wait for more information (cf request exceptions management);
369
		if ($requestIssue) {
370
			return true;
371
		}
372
373
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
374
		if ($notFound && !$foundWithNoOwner) {
375
			return false;
376
		}
377
378
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
379
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
380
		if (!$notFound && $foundWithNoOwner) {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return !$notFound && $foundWithNoOwner;.
Loading history...
381
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
382
			return true;
383
		}
384
385
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
386
		return false;
387
	}
388
389
	/**
390
	 * @throws GSStatusException
391
	 */
392
	public function syncEvents() {
393
394
	}
395
396
	/**
397
	 *
398
	 */
399
	private function removeDeprecatedEvents() {
400
//		$this->deprecatedEvents();
401
402
	}
403
404
405
}
406
407