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

GSUpstreamService::getEventsByToken()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
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);
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
	 * @param GSEvent $event
258
	 *asyncBroadcast
259
	 *
260
	 * @return bool
261
	 */
262
	private function isLocalEvent(GSEvent $event): bool {
263
		if ($event->isLocal()) {
264
			return true;
265
		}
266
267
		$circle = $event->getCircle();
268
		$owner = $circle->getOwner();
269
		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...
270
			|| in_array(
271
				$owner->getInstance(), $this->configService->getTrustedDomains()
272
			)) {
273
			return true;
274
		}
275
276
		return false;
277
	}
278
279
280
	/**
281
	 * @param string $token
282
	 *
283
	 * @return GSWrapper[]
284
	 * @throws JsonException
285
	 * @throws ModelException
286
	 */
287
	public function getEventsByToken(string $token): array {
288
		return $this->gsEventsRequest->getByToken($token);
289
	}
290
291
292
	/**
293
	 * @param array $circles
294
	 *
295
	 * @throws GSStatusException
296
	 */
297
	public function syncCircles(array $circles): void {
298
		$event = new GSEvent(GSEvent::GLOBAL_SYNC, true);
299
		$event->setSource($this->configService->getLocalCloudId());
300
		$event->setData(new SimpleDataStore($circles));
301
302
		foreach ($this->globalScaleService->getInstances() as $instance) {
303
			try {
304
				$this->broadcastEvent($event, $instance);
305
			} 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...
306
			}
307
		}
308
	}
309
310
311
	/**
312
	 * @param Circle $circle
313
	 *
314
	 * @return bool
315
	 * @throws GSStatusException
316
	 */
317
	public function confirmCircleStatus(Circle $circle): bool {
318
		$event = new GSEvent(GSEvent::CIRCLE_STATUS, true);
319
		$event->setSource($this->configService->getLocalCloudId());
320
		$event->setCircle($circle);
321
322
		$this->signEvent($event);
323
324
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.status');
325
		$request = new Request($path, Request::TYPE_POST);
326
327
		// TODO: test https first, then http
328
		$protocol = 'http';
329
		$request->setProtocol($protocol);
330
		$request->setDataSerialize($event);
331
332
		$requestIssue = false;
333
		$notFound = false;
334
		$foundWithNoOwner = false;
335
		foreach ($this->globalScaleService->getInstances() as $instance) {
336
			$request->setAddress($instance);
337
338
			try {
339
				$result = $this->retrieveJson($request);
340
				$this->miscService->log('result: ' . json_encode($result));
341
				if ($this->getInt('status', $result, 0) !== 1) {
342
					throw new RequestContentException('result status is not good');
343
				}
344
345
				$status = $this->getInt('success.data.status', $result);
346
347
				// if error, we assume the circle might still exist.
348
				if ($status === CircleStatus::STATUS_ERROR) {
349
					return true;
350
				}
351
352
				if ($status === CircleStatus::STATUS_OK) {
353
					return true;
354
				}
355
356
				// TODO: check the data.supposedOwner entry.
357
				if ($status === CircleStatus::STATUS_NOT_OWNER) {
358
					$foundWithNoOwner = true;
359
				}
360
361
				if ($status === CircleStatus::STATUS_NOT_FOUND) {
362
					$notFound = true;
363
				}
364
365
			} catch (RequestContentException
366
			| RequestNetworkException
367
			| RequestResultNotJsonException
368
			| RequestResultSizeException
369
			| RequestServerException $e) {
370
				$requestIssue = true;
371
				// TODO: log instances that have network issue, after too many tries (7d), remove this circle.
372
				continue;
373
			}
374
		}
375
376
		// if no request issue, we can imagine that the instance that owns the circle is down.
377
		// We'll wait for more information (cf request exceptions management);
378
		if ($requestIssue) {
379
			return true;
380
		}
381
382
		// circle were not found in any other instances, we can easily says that the circle does not exists anymore
383
		if ($notFound && !$foundWithNoOwner) {
384
			return false;
385
		}
386
387
		// circle were found everywhere but with no owner on every instance. we need to assign a new owner.
388
		// This should be done by checking admin rights. if no admin rights, let's assume that circle should be removed.
389
		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...
390
			// TODO: assign a new owner and check that when changing owner, we do check that the destination instance is updated FOR SURE!
391
			return true;
392
		}
393
394
		// some instances returned notFound, some returned circle with no owner. let's assume the circle is deprecated.
395
		return false;
396
	}
397
398
399
	/**
400
	 * @param string $token
401
	 */
402
	public function manageResults(string $token): void {
403
		try {
404
			$wrappers = $this->gsEventsRequest->getByToken($token);
405
		} catch (JsonException | ModelException $e) {
406
			return;
407
		}
408
409
		$event = null;
410
		$events = [];
411
		foreach ($wrappers as $wrapper) {
412
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
413
				return;
414
			}
415
416
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
417
		}
418
419
		if ($event === null) {
420
			return;
421
		}
422
423
		try {
424
			$gs = $this->globalScaleService->getGlobalScaleEvent($event);
425
			$gs->result($events);
426
		} catch (GlobalScaleEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
427
		}
428
	}
429
430
431
	/**
432
	 *
433
	 */
434
	public function deprecatedEvents(): void {
435
436
	}
437
438
}
439
440