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

RemoteEventService::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 19

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 19
rs 9.6333
c 0
b 0
f 0
cc 1
nc 1
nop 8

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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\GSEventsRequest;
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 GSEventsRequest */
86
	private $gsEventsRequest;
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 GSEventsRequest $gsEventsRequest
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
		GSEventsRequest $gsEventsRequest,
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->gsEventsRequest = $gsEventsRequest;
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
	 * @return string
0 ignored issues
show
Documentation introduced by
Should the return type not be string|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
138
	 * @throws RemoteEventException
139
	 */
140
	public function newEvent(RemoteEvent $event): void {
141
		// TODO: setSource
142
		// TODO: Verify Viewer
143
		//$event->setSource($this->configService->getLocalInstance());
144
		$this->verifyViewer($event);
145
		try {
146
			$gs = $this->getRemoteEvent($event);
147
		} catch (RemoteEventException $e) {
148
			$this->e($e);
149
			throw $e;
150
		}
151
152
		try {
153
			if ($this->isLocalEvent($event)) {
154
				$gs->verify($event);
155
				if (!$event->isAsync()) {
156
					$gs->manage($event);
157
				}
158
159
				$this->initBroadcast($event);
160
			} else {
161
				//	$this->confirmEvent($event);
162
			}
163
		} catch (CircleNotFoundException $e) {
164
			$this->e($e, ['event' => $event]);
165
		}
166
167
	}
168
169
170
	/**
171
	 * async the process, generate a local request that will be closed.
172
	 *
173
	 * @param RemoteEvent $event
174
	 */
175
	public function initBroadcast(RemoteEvent $event): void {
176
		$wrapper = new RemoteWrapper();
177
		$wrapper->setEvent($event);
178
		$wrapper->setToken($this->uuid());
179
		$wrapper->setCreation(time());
180
		$wrapper->setSeverity($event->getSeverity());
181
182
		foreach ($this->getInstances($event->isAsync()) as $instance) {
183
			$wrapper->setInstance($instance);
184
			$wrapper = $this->gsEventsRequest->create($wrapper);
0 ignored issues
show
Bug introduced by
It seems like $wrapper can also be of type object<OCA\Circles\Model\Remote\RemoteWrapper>; however, OCA\Circles\Db\GSEventsRequest::create() does only seem to accept object<OCA\Circles\Model\GlobalScale\GSWrapper>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
185
		}
186
187
		$event->setWrapperToken($wrapper->getToken());
188
189
		$request = new NC21Request('', Request::TYPE_POST);
190
		$this->configService->configureRequest(
191
			$request, 'circles.GlobalScale.asyncBroadcast', ['token' => $wrapper->getToken()]
192
		);
193
194
		try {
195
			$this->doRequest($request);
196
		} catch (RequestNetworkException $e) {
197
			$this->e($e, ['wrapper' => $wrapper]);
198
		}
199
	}
200
201
202
	private function verifyViewer(RemoteEvent $event) {
203
		if (!$event->hasCircle() || !$event->getCircle()->hasViewer()) {
204
			return;
205
		}
206
207
		// TODO: Verify/Set Source of Viewer (check based on the source of the request)
208
//		if ($event->isLocal()) {
209
//		}
210
211
		$circle = $event->getCircle();
212
		$viewer = $circle->getViewer();
213
214
		try {
215
			$localCircle = $this->circleRequest->getCircle($circle->getId(), $viewer);
216
		} catch (CircleNotFoundException $e) {
217
			return;
218
		}
219
220
		if (!$this->compareMembers($viewer, $localCircle->getViewer())) {
221
			return;
222
		}
223
224
		$event->setVerifiedViewer(true);
225
		$event->setCircle($localCircle);
226
	}
227
228
229
	/**
230
	 * We check that the event can be managed/checked locally or if the owner of the circle belongs to
231
	 * an other instance of Nextcloud
232
	 *
233
	 * @param RemoteEvent $event
234
	 *
235
	 * @return bool
236
	 * @throws CircleNotFoundException
237
	 */
238
	private function isLocalEvent(RemoteEvent $event): bool {
239
		if ($event->isLocal()) {
240
			return true;
241
		}
242
243
		if (!$event->hasCircle()) {
244
			return false;
245
		}
246
247
		$circle = $event->getCircle();
248
		if (!$circle->hasOwner()) {
249
			// TODO: Check on circle with no owner (add getInstance() to Circle)
250
			return false;
251
		}
252
253
		if ($event->isVerifiedViewer()) {
254
			$localCircle = $event->getCircle();
255
		} else {
256
			$localCircle = $this->circleRequest->getCircle($circle->getId());
257
		}
258
259
		$owner = $localCircle->getOwner();
260
		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...
261
			|| $this->configService->isLocalInstance($owner->getInstance())) {
262
			return true;
263
		}
264
265
		return false;
266
	}
267
268
269
	/**
270
	 * @param RemoteEvent $event
271
	 *
272
	 * @return IRemoteEvent
273
	 * @throws RemoteEventException
274
	 */
275
	public function getRemoteEvent(RemoteEvent $event): IRemoteEvent {
276
		$class = $event->getClass();
277
		try {
278
			$test = new ReflectionClass($class);
279
		} catch (ReflectionException $e) {
280
			throw new RemoteEventException('ReflectionException with ' . $class . ': ' . $e->getMessage());
281
		}
282
283
		if (!in_array(IRemoteEvent::class, $test->getInterfaceNames())) {
284
			throw new RemoteEventException($class . ' does not implements IRemoteEvent');
285
		}
286
287
		$gs = OC::$server->get($class);
288
		if (!$gs instanceof IRemoteEvent) {
289
			throw new RemoteEventException($class . ' not an IRemoteEvent');
290
		}
291
292
		return $gs;
293
	}
294
295
296
	/**
297
	 * @param bool $all
298
	 *
299
	 * @return array
300
	 */
301
	private function getInstances(bool $all = false): array {
302
		$gsInstances = $this->getGlobalScaleInstances();
303
		$remoteInstances = $this->getRemoteInstances();
304
305
		$instances = array_merge([$this->configService->getLocalInstance()], $gsInstances, $remoteInstances);
306
		if ($all) {
307
			return $instances;
308
		}
309
310
		return array_values(array_diff($instances, $this->configService->getTrustedDomains()));
311
	}
312
313
314
	/**
315
	 * @return array
316
	 */
317
	private function getGlobalScaleInstances(): array {
318
		try {
319
			$lookup = $this->configService->getGSStatus(ConfigService::GS_LOOKUP);
320
			$request = new NC21Request(ConfigService::GS_LOOKUP_INSTANCES, Request::TYPE_POST);
321
			$this->configService->configureRequest($request);
322
			$request->basedOnUrl($lookup);
323
			$request->addData('authKey', $this->configService->getGSStatus(ConfigService::GS_KEY));
324
325
			try {
326
				return $this->retrieveJson($request);
327
			} catch (RequestNetworkException $e) {
328
				$this->e($e, ['request' => $request]);
329
			}
330
		} catch (GSStatusException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
331
		}
332
333
		return [];
334
	}
335
336
337
	/**
338
	 * @return array
339
	 */
340
	private function getRemoteInstances(): array {
341
		return [];
342
	}
343
344
345
	/**
346
	 * should be used to manage results from events, like sending mails on user creation
347
	 *
348
	 * @param string $token
349
	 */
350
	public function manageResults(string $token): void {
351
		try {
352
			$wrappers = $this->gsEventsRequest->getByToken($token);
353
		} catch (JsonException | ModelException $e) {
354
			return;
355
		}
356
357
		$event = null;
358
		$events = [];
359
		foreach ($wrappers as $wrapper) {
360
			if ($wrapper->getStatus() !== GSWrapper::STATUS_DONE) {
361
				return;
362
			}
363
364
			$events[$wrapper->getInstance()] = $event = $wrapper->getEvent();
365
		}
366
367
		if ($event === null) {
368
			return;
369
		}
370
371
		try {
372
			$gs = $this->getRemoteEvent($event);
0 ignored issues
show
Documentation introduced by
$event is of type object<OCA\Circles\Model\GlobalScale\GSEvent>, but the function expects a object<OCA\Circles\Model\Remote\RemoteEvent>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
373
			$gs->result($events);
374
		} catch (RemoteEventException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
375
		}
376
	}
377
378
379
	/**
380
	 * @param RemoteEvent $event
381
	 */
382
	private function confirmEvent(RemoteEvent $event): void {
383
//		$this->signEvent($event);
384
385
		$circle = $event->getCircle();
386
		$owner = $circle->getOwner();
387
		$path = $this->urlGenerator->linkToRoute('circles.GlobalScale.event');
388
389
		$request = new NC21Request($path, Request::TYPE_POST);
390
		$this->configService->configureRequest($request);
391
		$request->basedOnUrl($owner->getInstance());
392
393
		$request->setDataSerialize($event);
394
395
		$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...
396
		$this->debug('confirming RemoteEvent', ['event' => $event, 'request' => $request]);
397
//
398
//		if ($this->getInt('status', $result) === 0) {
399
//			throw new GlobalScaleEventException($this->get('error', $result));
400
//		}
401
402
//		$updatedData = $this->getArray('event', $result);
403
//		$this->miscService->log('updatedEvent: ' . json_encode($updatedData), 0);
404
//		if (!empty($updatedData)) {
405
//			$updated = new GSEvent();
406
//			try {
407
//				$updated->import($updatedData);
408
//				$event = $updated;
409
//			} catch (Exception $e) {
410
//			}
411
//		}
412
	}
413
414
415
	/**
416
	 * @param Member $member1
417
	 * @param Member $member2
418
	 *
419
	 * @return bool
420
	 */
421
	private function compareMembers(Member $member1, Member $member2): bool {
422
//		if ($member1->getInstance() === '') {
423
//			$member1->setInstance($this->configService->getLocalInstance());
424
//		}
425
//
426
//		if ($member2->getInstance() === '') {
427
//			$member2->setInstance($this->configService->getLocalInstance());
428
//		}
429
430
		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...
431
			|| $member1->getUserId() !== $member2->getUserId()
432
			|| $member1->getUserType() <> $member2->getUserType()
433
			|| $member1->getLevel() <> $member2->getLevel()
434
			|| $member1->getStatus() !== $member2->getStatus()
435
			|| $member1->getInstance() !== $member2->getInstance()) {
436
			return false;
437
		}
438
439
		return true;
440
	}
441
442
}
443
444