Completed
Pull Request — master (#551)
by Maxence
01:55
created

RemoteEventService::manageResults()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.8657
c 0
b 0
f 0
cc 6
nc 10
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
namespace OCA\Circles\Service;
32
33
34
use daita\MySmallPhpTools\ActivityPub\Nextcloud\nc21\NC21Signature;
35
use daita\MySmallPhpTools\Exceptions\RequestNetworkException;
36
use daita\MySmallPhpTools\Model\Nextcloud\nc21\NC21Request;
37
use daita\MySmallPhpTools\Model\Request;
38
use daita\MySmallPhpTools\Traits\Nextcloud\nc21\TNC21Request;
39
use daita\MySmallPhpTools\Traits\TStringTools;
40
use OC;
41
use OC\Security\IdentityProof\Signer;
42
use OCA\Circles\Db\CircleRequest;
43
use OCA\Circles\Db\RemoteRequest;
44
use OCA\Circles\Db\RemoteWrapperRequest;
45
use OCA\Circles\Exceptions\CircleNotFoundException;
46
use OCA\Circles\Exceptions\JsonException;
47
use OCA\Circles\Exceptions\ModelException;
48
use OCA\Circles\Exceptions\OwnerNotFoundException;
49
use OCA\Circles\Exceptions\RemoteEventException;
50
use OCA\Circles\IRemoteEvent;
51
use OCA\Circles\IRemoteEventBypassLocalCircleCheck;
52
use OCA\Circles\Model\Circle;
53
use OCA\Circles\Model\GlobalScale\GSWrapper;
54
use OCA\Circles\Model\Remote\RemoteEvent;
55
use OCA\Circles\Model\Remote\RemoteInstance;
56
use OCA\Circles\Model\Remote\RemoteWrapper;
57
use OCP\IURLGenerator;
58
use OCP\IUserManager;
59
use OCP\IUserSession;
60
use ReflectionClass;
61
use ReflectionException;
62
63
64
/**
65
 * Class RemoteService
66
 *
67
 * @package OCA\Circles\Service
68
 */
69
class RemoteEventService extends NC21Signature {
70
71
72
	use TNC21Request;
73
	use TStringTools;
74
75
76
	/** @var IURLGenerator */
77
	private $urlGenerator;
78
79
	/** @var IUserManager */
80
	private $userManager;
81
82
	/** @var IUserSession */
83
	private $userSession;
84
85
	/** @var Signer */
86
	private $signer;
87
88
	/** @var RemoteWrapperRequest */
89
	private $remoteWrapperRequest;
90
91
	/** @var RemoteRequest */
92
	private $remoteRequest;
93
94
	/** @var CircleRequest */
95
	private $circleRequest;
96
97
98
	/** @var ConfigService */
99
	private $configService;
100
101
	/** @var MiscService */
102
	private $miscService;
103
104
105
	/**
106
	 * GlobalScaleService constructor.
107
	 *
108
	 * @param IURLGenerator $urlGenerator
109
	 * @param IUserManager $userManager
110
	 * @param IUserSession $userSession
111
	 * @param Signer $signer
112
	 * @param RemoteWrapperRequest $remoteWrapperRequest
113
	 * @param RemoteRequest $remoteRequest
114
	 * @param CircleRequest $circleRequest
115
	 * @param ConfigService $configService
116
	 * @param MiscService $miscService
117
	 */
118
	public function __construct(
119
		IURLGenerator $urlGenerator,
120
		IUserManager $userManager,
121
		IUserSession $userSession,
122
		Signer $signer,
123
		RemoteWrapperRequest $remoteWrapperRequest,
124
		RemoteRequest $remoteRequest,
125
		CircleRequest $circleRequest,
126
		ConfigService $configService,
127
		MiscService $miscService
128
	) {
129
		$this->urlGenerator = $urlGenerator;
130
		$this->userManager = $userManager;
131
		$this->userSession = $userSession;
132
		$this->signer = $signer;
133
		$this->remoteWrapperRequest = $remoteWrapperRequest;
134
		$this->remoteRequest = $remoteRequest;
135
		$this->circleRequest = $circleRequest;
136
		$this->configService = $configService;
137
		$this->miscService = $miscService;
138
	}
139
140
141
	/**
142
	 * Called when creating a new Event.
143
	 * This method will manage the event locally and upstream the payload if needed.
144
	 *
145
	 * @param RemoteEvent $event
146
	 *
147
	 * @throws RemoteEventException
148
	 * @throws OwnerNotFoundException
149
	 */
150
	public function newEvent(RemoteEvent $event): void {
151
		$event->setSource($this->configService->getLocalInstance());
152
		if (!$event->hasCircle()) {
153
			throw new RemoteEventException('Event does not contains Circle');
154
		}
155
156
		$this->verifyViewer($event);
157
		try {
158
			$gs = $this->getRemoteEvent($event);
159
		} catch (RemoteEventException $e) {
160
			$this->e($e);
161
			throw $e;
162
		}
163
164
		try {
165
			if ($this->isLocalEvent($event)) {
166
				$gs->verify($event);
167
				if (!$event->isAsync()) {
168
					$gs->manage($event);
169
				}
170
171
				$this->initBroadcast($event);
172
			} else {
173
				//	$this->confirmEvent($event);
174
			}
175
		} catch (CircleNotFoundException $e) {
176
			$this->e($e, ['event' => $event]);
177
		}
178
179
	}
180
181
182
	/**
183
	 * @param RemoteEvent $event
184
	 */
185
	private function verifyViewer(RemoteEvent $event): void {
186
		if (!$event->getCircle()->hasViewer()) {
187
			return;
188
		}
189
190
		$circle = $event->getCircle();
191
		$viewer = $circle->getViewer();
192
193
		if (!$this->configService->isLocalInstance($viewer->getInstance())) {
194
			return;
195
		}
196
197
		try {
198
			$localCircle = $this->circleRequest->getCircle($circle->getId(), $viewer);
199
		} catch (CircleNotFoundException $e) {
200
			return;
201
		}
202
203
		if (!$circle->compareWith($localCircle) || !$viewer->compareWith($localCircle->getViewer())) {
204
			return;
205
		}
206
207
		$event->setVerifiedViewer(true)
208
			  ->setVerifiedCircle(true);
209
	}
210
211
212
	/**
213
	 * We check that the event can be managed/checked locally or if the owner of the circle belongs to
214
	 * an other instance of Nextcloud
215
	 *
216
	 * @param RemoteEvent $event
217
	 *
218
	 * @return bool
219
	 * @throws CircleNotFoundException
220
	 * @throws OwnerNotFoundException
221
	 */
222
	public function isLocalEvent(RemoteEvent $event): bool {
223
		if ($event->isLocal()) {
224
			return true;
225
		}
226
227
		$circle = $event->getCircle();
228
		if (!$circle->hasOwner()) {
229
			return ($this->configService->isLocalInstance($circle->getInstance()));
230
		}
231
232
		if ($event->isVerifiedCircle()) {
233
			$localCircle = $event->getCircle();
234
		} else {
235
			$localCircle = $this->circleRequest->getCircle($circle->getId());
236
		}
237
238
		$owner = $localCircle->getOwner();
239
		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...$owner->getInstance());.
Loading history...
240
			|| $this->configService->isLocalInstance($owner->getInstance())) {
241
			return true;
242
		}
243
244
		return false;
245
	}
246
247
248
	/**
249
	 * // TODO Rename Model/RemoteEvent and/or IRemoteEvent
250
	 *
251
	 * @param RemoteEvent $event
252
	 *
253
	 * @return IRemoteEvent
254
	 * @throws RemoteEventException
255
	 */
256
	public function getRemoteEvent(RemoteEvent $event): IRemoteEvent {
257
		$class = $event->getClass();
258
		try {
259
			$test = new ReflectionClass($class);
260
		} catch (ReflectionException $e) {
261
			throw new RemoteEventException('ReflectionException with ' . $class . ': ' . $e->getMessage());
262
		}
263
264
		if (!in_array(IRemoteEvent::class, $test->getInterfaceNames())) {
265
			throw new RemoteEventException($class . ' does not implements IRemoteEvent');
266
		}
267
268
		$gs = OC::$server->get($class);
269
		if (!$gs instanceof IRemoteEvent) {
270
			throw new RemoteEventException($class . ' not an IRemoteEvent');
271
		}
272
273
		$this->setRemoteEventBypass($event, $gs);
274
275
		return $gs;
276
	}
277
278
279
	/**
280
	 * Some event might need to bypass some checks
281
	 *
282
	 * @param RemoteEvent $event
283
	 * @param IRemoteEvent $gs
284
	 */
285
	private function setRemoteEventBypass(RemoteEvent $event, IRemoteEvent $gs) {
286
		if ($gs instanceof IRemoteEventBypassLocalCircleCheck) {
287
			$event->bypass(RemoteEvent::BYPASS_LOCALCIRCLECHECK);
288
		}
289
	}
290
291
292
	/**
293
	 * async the process, generate a local request that will be closed.
294
	 *
295
	 * @param RemoteEvent $event
296
	 */
297
	public function initBroadcast(RemoteEvent $event): void {
298
		$instances = $this->getInstances($event->isAsync());
299
		if (empty($instances)) {
300
			return;
301
		}
302
303
		$wrapper = new RemoteWrapper();
304
		$wrapper->setEvent($event);
305
		$wrapper->setToken($this->uuid());
306
		$wrapper->setCreation(time());
307
		$wrapper->setSeverity($event->getSeverity());
308
309
		foreach ($instances as $instance) {
310
			$wrapper->setInstance($instance);
311
			$this->remoteWrapperRequest->create($wrapper);
312
		}
313
314
		$request = new NC21Request('', Request::TYPE_POST);
315
		$this->configService->configureRequest(
316
			$request, 'circles.RemoteWrapper.asyncBroadcast', ['token' => $wrapper->getToken()]
317
		);
318
319
		$event->setWrapperToken($wrapper->getToken());
320
321
		try {
322
			$this->doRequest($request);
323
		} catch (RequestNetworkException $e) {
324
			$this->e($e, ['wrapper' => $wrapper]);
325
		}
326
	}
327
328
329
	/**
330
	 * @param bool $all
331
	 * @param Circle|null $circle
332
	 *
333
	 * @return array
334
	 */
335
	public function getInstances(bool $all = false, ?Circle $circle = null): array {
336
		$local = $this->configService->getLocalInstance();
337
		$instances = $this->remoteRequest->getOutgoingRecipient($circle);
338
		$instances = array_merge(
339
			[$local], array_map(
340
						function(RemoteInstance $instance): string {
341
							return $instance->getInstance();
342
						}, $instances
343
					)
344
		);
345
346
		if ($all) {
347
			return $instances;
348
		}
349
350
		return array_values(
351
			array_diff($instances, array_merge($this->configService->getTrustedDomains(), [$local]))
352
		);
353
	}
354
355
356
	/**
357
	 * @param array $current
358
	 */
359
	private function updateGlobalScaleInstances(array $current): void {
0 ignored issues
show
Unused Code introduced by
The parameter $current is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
360
//		$known = $this->remoteRequest->getFromType(RemoteInstance::TYPE_GLOBAL_SCALE);
361
	}
362
363
	/**
364
	 * @return array
365
	 */
366
	private function getRemoteInstances(): array {
367
		return [];
368
	}
369
370
371
	/**
372
	 * should be used to manage results from events, like sending mails on user creation
373
	 *
374
	 * @param string $token
375
	 */
376
	public function manageResults(string $token): void {
377
		try {
378
			$wrappers = $this->remoteWrapperRequest->getByToken($token);
379
		} catch (JsonException | ModelException $e) {
380
			return;
381
		}
382
383
		$event = null;
384
		$events = [];
385
		foreach ($wrappers as $wrapper) {
386
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
387
				return;
388
			}
389
390
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
391
		}
392
393
		if ($event === null) {
394
			return;
395
		}
396
397
		try {
398
			$gs = $this->getRemoteEvent($event);
399
			$gs->result($events);
400
		} catch (RemoteEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
401
		}
402
	}
403
404
405
	/**
406
	 * @param RemoteEvent $event
407
	 */
408
	private function confirmEvent(RemoteEvent $event): void {
409
//		$this->signEvent($event);
410
411
		$circle = $event->getCircle();
412
		$owner = $circle->getOwner();
413
		$path = $this->urlGenerator->linkToRoute('circles.RemoteWrapper.event');
414
415
		$request = new NC21Request($path, Request::TYPE_POST);
416
		$this->configService->configureRequest($request);
417
		$request->basedOnUrl($owner->getInstance());
418
419
		$request->setDataSerialize($event);
420
421
		$result = $this->retrieveJson($request);
0 ignored issues
show
Unused Code introduced by
$result is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
422
		$this->debug('confirming RemoteEvent', ['event' => $event, 'request' => $request]);
423
//
424
//		if ($this->getInt('status', $result) === 0) {
425
//			throw new GlobalScaleEventException($this->get('error', $result));
426
//		}
427
428
//		$updatedData = $this->getArray('event', $result);
429
//		$this->miscService->log('updatedEvent: ' . json_encode($updatedData), 0);
430
//		if (!empty($updatedData)) {
431
//			$updated = new GSEvent();
432
//			try {
433
//				$updated->import($updatedData);
434
//				$event = $updated;
435
//			} catch (Exception $e) {
436
//			}
437
//		}
438
	}
439
440
}
441
442