Completed
Pull Request — master (#362)
by Maxence
01:51
created

GSUpstreamService::confirmEvent()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 2
nc 2
nop 1
1
<?php declare(strict_types=1);
2
3
4
/**
5
 * Circles - Bring cloud-users closer together.
6
 *
7
 * This file is licensed under the Affero General Public License version 3 or
8
 * later. See the COPYING file.
9
 *
10
 * @author Maxence Lange <[email protected]>
11
 * @copyright 2017
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
30
namespace OCA\Circles\Service;
31
32
33
use daita\MySmallPhpTools\Exceptions\RequestContentException;
34
use daita\MySmallPhpTools\Exceptions\RequestNetworkException;
35
use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException;
36
use daita\MySmallPhpTools\Exceptions\RequestResultSizeException;
37
use daita\MySmallPhpTools\Exceptions\RequestServerException;
38
use daita\MySmallPhpTools\Model\Request;
39
use daita\MySmallPhpTools\Model\SimpleDataStore;
40
use daita\MySmallPhpTools\Traits\TArrayTools;
41
use daita\MySmallPhpTools\Traits\TRequest;
42
use Exception;
43
use OCA\Circles\Db\CirclesRequest;
44
use OCA\Circles\Db\GSEventsRequest;
45
use OCA\Circles\Exceptions\GlobalScaleEventException;
46
use OCA\Circles\Exceptions\GSStatusException;
47
use OCA\Circles\Exceptions\JsonException;
48
use OCA\Circles\Exceptions\ModelException;
49
use OCA\Circles\GlobalScale\CircleStatus;
50
use OCA\Circles\Model\Circle;
51
use OCA\Circles\Model\GlobalScale\GSEvent;
52
use OCA\Circles\Model\GlobalScale\GSWrapper;
53
use OCP\IURLGenerator;
54
55
56
/**
57
 * Class GSUpstreamService
58
 *
59
 * @package OCA\Circles\Service
60
 */
61
class GSUpstreamService {
62
63
64
	use TRequest;
65
	use TArrayTools;
66
67
68
	/** @var string */
69
	private $userId = '';
70
71
	/** @var IURLGenerator */
72
	private $urlGenerator;
73
74
	/** @var GSEventsRequest */
75
	private $gsEventsRequest;
76
77
	/** @var CirclesRequest */
78
	private $circlesRequest;
79
80
	/** @var GlobalScaleService */
81
	private $globalScaleService;
82
83
	/** @var ConfigService */
84
	private $configService;
85
86
	/** @var MiscService */
87
	private $miscService;
88
89
90
	/**
91
	 * GSUpstreamService constructor.
92
	 *
93
	 * @param $userId
94
	 * @param IURLGenerator $urlGenerator
95
	 * @param GSEventsRequest $gsEventsRequest
96
	 * @param CirclesRequest $circlesRequest
97
	 * @param GlobalScaleService $globalScaleService
98
	 * @param ConfigService $configService
99
	 * @param MiscService $miscService
100
	 */
101
	public function __construct(
102
		$userId,
103
		IURLGenerator $urlGenerator,
104
		GSEventsRequest $gsEventsRequest,
105
		CirclesRequest $circlesRequest,
106
		GlobalScaleService $globalScaleService,
107
		ConfigService $configService,
108
		MiscService $miscService
109
	) {
110
		$this->userId = $userId;
111
		$this->urlGenerator = $urlGenerator;
112
		$this->gsEventsRequest = $gsEventsRequest;
113
		$this->circlesRequest = $circlesRequest;
114
		$this->globalScaleService = $globalScaleService;
115
		$this->configService = $configService;
116
		$this->miscService = $miscService;
117
	}
118
119
120
	/**
121
	 * @param GSEvent $event
122
	 * @param int $severity
0 ignored issues
show
Bug introduced by
There is no parameter named $severity. 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...
123
	 *
124
	 * @throws Exception
125
	 */
126
	public function newEvent(GSEvent $event) {
127
		try {
128
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
129
130
			$this->fillEvent($event);
131
			if ($this->isLocalEvent($event)) {
132
				$gs->verify($event, true);
133
				$gs->manage($event);
134
135
				$this->globalScaleService->asyncBroadcast($event);
136
			} else {
137
				$gs->verify($event); // needed ? as we check event on the 'master' of the circle
138
				$this->confirmEvent($event);
139
				$gs->manage($event);
140
			}
141
		} catch (Exception $e) {
142
			$this->miscService->log(
143
				get_class($e) . ' on new event: ' . $e->getMessage() . ' - ' . json_encode($event), 1
144
			);
145
			throw $e;
146
		}
147
	}
148
149
150
	/**
151
	 * @param GSWrapper $wrapper
152
	 * @param string $protocol
153
	 */
154
	public function broadcastWrapper(GSWrapper $wrapper, string $protocol): void {
155
		$status = GSWrapper::STATUS_FAILED;
156
157
		try {
158
			$this->broadcastEvent($wrapper->getEvent(), $wrapper->getInstance(), $protocol);
159
			$status = GSWrapper::STATUS_DONE;
160
		} 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...
161
		}
162
163
		if ($wrapper->getSeverity() === GSEvent::SEVERITY_HIGH) {
164
			$wrapper->setStatus($status);
165
		} else {
166
			$wrapper->setStatus(GSWrapper::STATUS_OVER);
167
		}
168
169
		$this->gsEventsRequest->update($wrapper);
170
	}
171
172
173
	/**
174
	 * @param GSEvent $event
175
	 * @param string $instance
176
	 * @param string $protocol
177
	 *
178
	 * @throws RequestContentException
179
	 * @throws RequestNetworkException
180
	 * @throws RequestResultNotJsonException
181
	 * @throws RequestResultSizeException
182
	 * @throws RequestServerException
183
	 */
184
	public function broadcastEvent(GSEvent $event, string $instance, string $protocol = ''): void {
185
		$this->signEvent($event);
186
187
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.broadcast');
188
		$request = new Request($path, Request::TYPE_POST);
189
190
		if ($protocol === '') {
191
			// TODO: test https first, then http
192
			$protocol = 'http';
193
		}
194
		$request->setProtocol($protocol);
195
		$request->setDataSerialize($event);
196
197
		$request->setAddress($instance);
198
199
		$data = $this->retrieveJson($request);
200
		$event->setResult(new SimpleDataStore($this->getArray('result', $data, [])));
201
	}
202
203
204
	/**
205
	 * @param GSEvent $event
206
	 *
207
	 * @throws RequestContentException
208
	 * @throws RequestNetworkException
209
	 * @throws RequestResultSizeException
210
	 * @throws RequestServerException
211
	 * @throws RequestResultNotJsonException
212
	 * @throws GlobalScaleEventException
213
	 */
214
	public function confirmEvent(GSEvent $event): void {
215
		$this->signEvent($event);
216
217
		$circle = $event->getCircle();
218
		$owner = $circle->getOwner();
219
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.event');
220
221
		$request = new Request($path, Request::TYPE_POST);
222
		$request->setProtocol($_SERVER['REQUEST_SCHEME']);
223
		$request->setAddressFromUrl($owner->getInstance());
224
		$request->setDataSerialize($event);
225
226
		$result = $this->retrieveJson($request);
227
		$this->miscService->log('result ' . json_encode($result));
228
		if ($this->getInt('status', $result) === 0) {
229
			throw new GlobalScaleEventException($this->get('error', $result));
230
		}
231
	}
232
233
234
	/**
235
	 * @param GSEvent $event
236
	 *
237
	 * @throws GSStatusException
238
	 */
239
	private function fillEvent(GSEvent $event): void {
240
		if (!$this->configService->getGSStatus(ConfigService::GS_ENABLED)) {
241
			return;
242
		}
243
244
		$event->setSource($this->configService->getLocalCloudId());
245
	}
246
247
248
	/**
249
	 * @param GSEvent $event
250
	 */
251
	private function signEvent(GSevent $event) {
252
		$event->setKey($this->globalScaleService->getKey());
253
	}
254
255
256
	/**
257
	 * We check that the event can be managed/checked locally or if the owner of the circle belongs to
258
	 * an other instance of Nextcloud
259
	 *
260
	 * @param GSEvent $event
261
	 *asyncBroadcast
262
	 *
263
	 * @return bool
264
	 */
265
	private function isLocalEvent(GSEvent $event): bool {
266
		if ($event->isLocal()) {
267
			return true;
268
		}
269
270
		$circle = $event->getCircle();
271
		$owner = $circle->getOwner();
272
		if ($owner->getInstance() === ''
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $owner->getInstan...->getTrustedDomains());.
Loading history...
273
			|| in_array(
274
				$owner->getInstance(), $this->configService->getTrustedDomains()
275
			)) {
276
			return true;
277
		}
278
279
		return false;
280
	}
281
282
283
	/**
284
	 * @param string $token
285
	 *
286
	 * @return GSWrapper[]
287
	 * @throws JsonException
288
	 * @throws ModelException
289
	 */
290
	public function getEventsByToken(string $token): array {
291
		return $this->gsEventsRequest->getByToken($token);
292
	}
293
294
295
	/**
296
	 * @param array $circles
297
	 *
298
	 * @throws GSStatusException
299
	 */
300
	public function syncCircles(array $circles): void {
301
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
302
		$event->setSource($this->configService->getLocalCloudId());
303
		$event->setData(new SimpleDataStore($circles));
304
305
		foreach ($this->globalScaleService->getInstances() as $instance) {
306
			try {
307
				$this->broadcastEvent($event, $instance);
308
			} 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...
309
			}
310
		}
311
	}
312
313
314
	/**
315
	 * @param Circle $circle
316
	 *
317
	 * @return bool
318
	 * @throws GSStatusException
319
	 */
320
	public function confirmCircleStatus(Circle $circle): bool {
321
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
322
		$event->setSource($this->configService->getLocalCloudId());
323
		$event->setCircle($circle);
324
325
		$this->signEvent($event);
326
327
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.status');
328
		$request = new Request($path, Request::TYPE_POST);
329
330
		// TODO: test https first, then http
331
		$protocol = 'http';
332
		$request->setProtocol($protocol);
333
		$request->setDataSerialize($event);
334
335
		$requestIssue = false;
336
		$notFound = false;
337
		$foundWithNoOwner = false;
338
		foreach ($this->globalScaleService->getInstances() as $instance) {
339
			$request->setAddress($instance);
340
341
			try {
342
				$result = $this->retrieveJson($request);
343
				$this->miscService->log('result: ' . json_encode($result));
344
				if ($this->getInt('status', $result, 0) !== 1) {
345
					throw new RequestContentException('result status is not good');
346
				}
347
348
				$status = $this->getInt('success.data.status', $result);
349
350
				// if error, we assume the circle might still exist.
351
				if ($status === CircleStatus::STATUS_ERROR) {
352
					return true;
353
				}
354
355
				if ($status === CircleStatus::STATUS_OK) {
356
					return true;
357
				}
358
359
				// TODO: check the data.supposedOwner entry.
360
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
361
					$foundWithNoOwner = true;
362
				}
363
364
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
365
					$notFound = true;
366
				}
367
368
			} catch (RequestContentException
369
			| RequestNetworkException
370
			| RequestResultNotJsonException
371
			| RequestResultSizeException
372
			| RequestServerException $e) {
373
				$requestIssue = true;
374
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
375
				continue;
376
			}
377
		}
378
379
		// if no request issue, we can imagine that the instance that owns the circle is down.
380
		// We'll wait for more information (cf request exceptions management);
381
		if ($requestIssue) {
382
			return true;
383
		}
384
385
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
386
		if ($notFound && !$foundWithNoOwner) {
387
			return false;
388
		}
389
390
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
391
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
392
		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...
393
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
394
			return true;
395
		}
396
397
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
398
		return false;
399
	}
400
401
402
	/**
403
	 * @param string $token
404
	 */
405
	public function manageResults(string $token): void {
406
		try {
407
			$wrappers = $this->gsEventsRequest->getByToken($token);
408
		} catch (JsonException | ModelException $e) {
409
			return;
410
		}
411
412
		$event = null;
413
		$events = [];
414
		foreach ($wrappers as $wrapper) {
415
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
416
				return;
417
			}
418
419
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
420
		}
421
422
		if ($event === null) {
423
			return;
424
		}
425
426
		try {
427
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
428
			$gs->result($events);
429
		} catch (GlobalScaleEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
430
		}
431
	}
432
433
434
	/**
435
	 *
436
	 */
437
	public function deprecatedEvents(): void {
438
439
	}
440
441
}
442
443