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

RemoteEventService::newEvent()   B

Complexity

Conditions 6
Paths 11

Size

Total Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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