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

RemoteEventService   B

Complexity

Total Complexity 46

Size/Duplication

Total Lines 385
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Importance

Changes 0
Metric Value
wmc 46
lcom 1
cbo 13
dl 0
loc 385
rs 8.72
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 1
A newEvent() 0 26 5
A initBroadcast() 0 30 4
A verifyViewer() 0 25 5
B isLocalEvent() 0 29 7
A getRemoteEvent() 0 19 4
A getInstances() 0 14 2
A getGlobalScaleInstances() 0 18 3
A getRemoteInstances() 0 3 1
B manageResults() 0 27 6
A confirmEvent() 0 31 1
B compareMembers() 0 20 7

How to fix   Complexity   

Complex Class

Complex classes like RemoteEventService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use RemoteEventService, and based on these observations, apply Extract Interface, too.

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