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

RemoteEventService::newEvent()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 31
rs 8.8017
c 0
b 0
f 0
cc 6
nc 11
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\Exceptions\ViewerNotConfirmedException;
51
use OCA\Circles\IRemoteEvent;
52
use OCA\Circles\IRemoteEventBypassLocalCircleCheck;
53
use OCA\Circles\IRemoteEventBypassViewerCheck;
54
use OCA\Circles\Model\Circle;
55
use OCA\Circles\Model\GlobalScale\GSWrapper;
56
use OCA\Circles\Model\Remote\RemoteEvent;
57
use OCA\Circles\Model\Remote\RemoteInstance;
58
use OCA\Circles\Model\Remote\RemoteWrapper;
59
use OCP\IURLGenerator;
60
use OCP\IUserManager;
61
use OCP\IUserSession;
62
use ReflectionClass;
63
use ReflectionException;
64
65
66
/**
67
 * Class RemoteService
68
 *
69
 * @package OCA\Circles\Service
70
 */
71
class RemoteEventService extends NC21Signature {
72
73
74
	use TNC21Request;
75
	use TStringTools;
76
77
78
	/** @var IURLGenerator */
79
	private $urlGenerator;
80
81
	/** @var IUserManager */
82
	private $userManager;
83
84
	/** @var IUserSession */
85
	private $userSession;
86
87
	/** @var Signer */
88
	private $signer;
89
90
	/** @var RemoteWrapperRequest */
91
	private $remoteWrapperRequest;
92
93
	/** @var RemoteRequest */
94
	private $remoteRequest;
95
96
	/** @var CircleRequest */
97
	private $circleRequest;
98
99
100
	/** @var ConfigService */
101
	private $configService;
102
103
	/** @var MiscService */
104
	private $miscService;
105
106
107
	/**
108
	 * GlobalScaleService constructor.
109
	 *
110
	 * @param IURLGenerator $urlGenerator
111
	 * @param IUserManager $userManager
112
	 * @param IUserSession $userSession
113
	 * @param Signer $signer
114
	 * @param RemoteWrapperRequest $remoteWrapperRequest
115
	 * @param RemoteRequest $remoteRequest
116
	 * @param CircleRequest $circleRequest
117
	 * @param ConfigService $configService
118
	 * @param MiscService $miscService
119
	 */
120
	public function __construct(
121
		IURLGenerator $urlGenerator,
122
		IUserManager $userManager,
123
		IUserSession $userSession,
124
		Signer $signer,
125
		RemoteWrapperRequest $remoteWrapperRequest,
126
		RemoteRequest $remoteRequest,
127
		CircleRequest $circleRequest,
128
		ConfigService $configService,
129
		MiscService $miscService
130
	) {
131
		$this->urlGenerator = $urlGenerator;
132
		$this->userManager = $userManager;
133
		$this->userSession = $userSession;
134
		$this->signer = $signer;
135
		$this->remoteWrapperRequest = $remoteWrapperRequest;
136
		$this->remoteRequest = $remoteRequest;
137
		$this->circleRequest = $circleRequest;
138
		$this->configService = $configService;
139
		$this->miscService = $miscService;
140
	}
141
142
143
	/**
144
	 * Called when creating a new Event.
145
	 * This method will manage the event locally and upstream the payload if needed.
146
	 *
147
	 * @param RemoteEvent $event
148
	 *
149
	 * @throws RemoteEventException
150
	 * @throws OwnerNotFoundException
151
	 * @throws ViewerNotConfirmedException
152
	 */
153
	public function newEvent(RemoteEvent $event): void {
154
		$event->setSource($this->configService->getLocalInstance());
155
		if (!$event->hasCircle()) {
156
			throw new RemoteEventException('Event does not contains Circle');
157
		}
158
159
		try {
160
			$gs = $this->getRemoteEvent($event);
161
		} catch (RemoteEventException $e) {
162
			$this->e($e);
163
			throw $e;
164
		}
165
166
		$this->confirmViewer($event);
167
168
		try {
169
			if ($this->configService->isLocalInstance($event->getCircle()->getInstance())) {
170
				$gs->verify($event);
171
				if (!$event->isAsync()) {
172
					$gs->manage($event);
173
				}
174
175
				$this->initBroadcast($event);
176
			} else {
177
				//	$this->confirmEvent($event);
178
			}
179
		} catch (CircleNotFoundException $e) {
180
			$this->e($e, ['event' => $event]);
181
		}
182
183
	}
184
185
186
	/**
187
	 * This confirmation is optional, method is just here to avoid going too far away on the process
188
	 *
189
	 * @param RemoteEvent $event
190
	 *
191
	 * @throws ViewerNotConfirmedException
192
	 */
193
	private function confirmViewer(RemoteEvent $event): void {
194
		if ($event->canBypass(RemoteEvent::BYPASS_VIEWERCHECK)) {
195
			return;
196
		}
197
198
		$circle = $event->getCircle();
199
		if (!$circle->hasViewer()
200
			|| !$this->configService->isLocalInstance(
201
				$circle->getViewer()->getInstance()
202
			)) {
203
			throw new ViewerNotConfirmedException('viewer does not exist or is not local');
204
		}
205
	}
206
207
208
209
//	/**
210
//	 * We check that the event can be managed/checked locally or if the owner of the circle belongs to
211
//	 * an other instance of Nextcloud
212
//	 *
213
//	 * @param RemoteEvent $event
214
//	 *
215
//	 * @return bool
216
//	 * @throws CircleNotFoundException
217
//	 * @throws OwnerNotFoundException
218
//	 */
219
//	public function isLocalEvent(RemoteEvent $event): bool {
220
////		if ($event->isLocal()) {
221
////			return true;
222
////		}
223
//
224
//		$circle = $event->getCircle();
225
//
226
////		if (!$circle->hasOwner()) {
227
//		return ($this->configService->isLocalInstance($circle->getInstance()));
228
////		}
229
//
230
////		if ($event->isVerifiedCircle()) {
231
////			$localCircle = $event->getCircle();
232
////		} else {
233
////			$localCircle = $this->circleRequest->getCircle($circle->getId());
234
////		}
235
////
236
////		$owner = $localCircle->getOwner();
237
////		if ($owner->getInstance() === ''
238
////			|| $this->configService->isLocalInstance($owner->getInstance())) {
239
////			return true;
240
////		}
241
////
242
////		return false;
243
//	}
244
245
246
	/**
247
	 * // TODO Rename Model/RemoteEvent and/or IRemoteEvent
248
	 *
249
	 * @param RemoteEvent $event
250
	 *
251
	 * @return IRemoteEvent
252
	 * @throws RemoteEventException
253
	 */
254
	public function getRemoteEvent(RemoteEvent $event): IRemoteEvent {
255
		$class = $event->getClass();
256
		try {
257
			$test = new ReflectionClass($class);
258
		} catch (ReflectionException $e) {
259
			throw new RemoteEventException('ReflectionException with ' . $class . ': ' . $e->getMessage());
260
		}
261
262
		if (!in_array(IRemoteEvent::class, $test->getInterfaceNames())) {
263
			throw new RemoteEventException($class . ' does not implements IRemoteEvent');
264
		}
265
266
		$gs = OC::$server->get($class);
267
		if (!$gs instanceof IRemoteEvent) {
268
			throw new RemoteEventException($class . ' not an IRemoteEvent');
269
		}
270
271
		$this->setRemoteEventBypass($event, $gs);
272
273
		return $gs;
274
	}
275
276
277
	/**
278
	 * Some event might need to bypass some checks
279
	 *
280
	 * @param RemoteEvent $event
281
	 * @param IRemoteEvent $gs
282
	 */
283
	private function setRemoteEventBypass(RemoteEvent $event, IRemoteEvent $gs) {
284
		if ($gs instanceof IRemoteEventBypassLocalCircleCheck) {
285
			$event->bypass(RemoteEvent::BYPASS_LOCALCIRCLECHECK);
286
		}
287
		if ($gs instanceof IRemoteEventBypassViewerCheck) {
288
			$event->bypass(RemoteEvent::BYPASS_VIEWERCHECK);
289
		}
290
	}
291
292
293
	/**
294
	 * async the process, generate a local request that will be closed.
295
	 *
296
	 * @param RemoteEvent $event
297
	 */
298
	public function initBroadcast(RemoteEvent $event): void {
299
		$instances = $this->getInstances($event->isAsync());
300
		if (empty($instances)) {
301
			return;
302
		}
303
304
		$wrapper = new RemoteWrapper();
305
		$wrapper->setEvent($event);
306
		$wrapper->setToken($this->uuid());
307
		$wrapper->setCreation(time());
308
		$wrapper->setSeverity($event->getSeverity());
309
310
		foreach ($instances as $instance) {
311
			$wrapper->setInstance($instance);
312
			$this->remoteWrapperRequest->create($wrapper);
313
		}
314
315
		$request = new NC21Request('', Request::TYPE_POST);
316
		$this->configService->configureRequest(
317
			$request, 'circles.RemoteWrapper.asyncBroadcast', ['token' => $wrapper->getToken()]
318
		);
319
320
		$event->setWrapperToken($wrapper->getToken());
321
322
		try {
323
			$this->doRequest($request);
324
		} catch (RequestNetworkException $e) {
325
			$this->e($e, ['wrapper' => $wrapper]);
326
		}
327
	}
328
329
330
	/**
331
	 * @param bool $all
332
	 * @param Circle|null $circle
333
	 *
334
	 * @return array
335
	 */
336
	public function getInstances(bool $all = false, ?Circle $circle = null): array {
337
		$local = $this->configService->getLocalInstance();
338
		$instances = $this->remoteRequest->getOutgoingRecipient($circle);
339
		$instances = array_merge(
340
			[$local], array_map(
341
						function(RemoteInstance $instance): string {
342
							return $instance->getInstance();
343
						}, $instances
344
					)
345
		);
346
347
		if ($all) {
348
			return $instances;
349
		}
350
351
		return array_values(
352
			array_diff($instances, array_merge($this->configService->getTrustedDomains(), [$local]))
353
		);
354
	}
355
356
357
	/**
358
	 * @param array $current
359
	 */
360
	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...
361
//		$known = $this->remoteRequest->getFromType(RemoteInstance::TYPE_GLOBAL_SCALE);
362
	}
363
364
	/**
365
	 * @return array
366
	 */
367
	private function getRemoteInstances(): array {
368
		return [];
369
	}
370
371
372
	/**
373
	 * should be used to manage results from events, like sending mails on user creation
374
	 *
375
	 * @param string $token
376
	 */
377
	public function manageResults(string $token): void {
378
		try {
379
			$wrappers = $this->remoteWrapperRequest->getByToken($token);
380
		} catch (JsonException | ModelException $e) {
381
			return;
382
		}
383
384
		$event = null;
385
		$events = [];
386
		foreach ($wrappers as $wrapper) {
387
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
388
				return;
389
			}
390
391
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
392
		}
393
394
		if ($event === null) {
395
			return;
396
		}
397
398
		try {
399
			$gs = $this->getRemoteEvent($event);
400
			$gs->result($events);
401
		} catch (RemoteEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
402
		}
403
	}
404
405
406
	/**
407
	 * @param RemoteEvent $event
408
	 */
409
	private function confirmEvent(RemoteEvent $event): void {
410
//		$this->signEvent($event);
411
412
		$circle = $event->getCircle();
413
		$owner = $circle->getOwner();
414
		$path = $this->urlGenerator->linkToRoute('circles.RemoteWrapper.event');
415
416
		$request = new NC21Request($path, Request::TYPE_POST);
417
		$this->configService->configureRequest($request);
418
		$request->basedOnUrl($owner->getInstance());
419
420
		$request->setDataSerialize($event);
421
422
		$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...
423
		$this->debug('confirming RemoteEvent', ['event' => $event, 'request' => $request]);
424
//
425
//		if ($this->getInt('status', $result) === 0) {
426
//			throw new GlobalScaleEventException($this->get('error', $result));
427
//		}
428
429
//		$updatedData = $this->getArray('event', $result);
430
//		$this->miscService->log('updatedEvent: ' . json_encode($updatedData), 0);
431
//		if (!empty($updatedData)) {
432
//			$updated = new GSEvent();
433
//			try {
434
//				$updated->import($updatedData);
435
//				$event = $updated;
436
//			} catch (Exception $e) {
437
//			}
438
//		}
439
	}
440
441
}
442
443