Completed
Push — master ( 3de7d3...149977 )
by Maxence
22s queued 11s
created

GlobalScaleService::asyncBroadcast()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 29
rs 9.456
c 0
b 0
f 0
cc 4
nc 8
nop 1
1
<?php declare(strict_types=1);
2
3
4
/**
5
 * Circles - Bring cloud-users closer together.
6
 *
7
 * This file is licensed under the Affero General Public License version 3 or
8
 * later. See the COPYING file.
9
 *
10
 * @author Maxence Lange <[email protected]>
11
 * @copyright 2019
12
 * @license GNU AGPL version 3 or any later version
13
 *
14
 * This program is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License as
16
 * published by the Free Software Foundation, either version 3 of the
17
 * License, or (at your option) any later version.
18
 *
19
 * This program is distributed in the hope that it will be useful,
20
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
22
 * GNU Affero General Public License for more details.
23
 *
24
 * You should have received a copy of the GNU Affero General Public License
25
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
26
 *
27
 */
28
29
30
namespace OCA\Circles\Service;
31
32
33
use daita\MySmallPhpTools\Exceptions\RequestContentException;
34
use daita\MySmallPhpTools\Exceptions\RequestNetworkException;
35
use daita\MySmallPhpTools\Exceptions\RequestResultNotJsonException;
36
use daita\MySmallPhpTools\Exceptions\RequestResultSizeException;
37
use daita\MySmallPhpTools\Exceptions\RequestServerException;
38
use daita\MySmallPhpTools\Model\Request;
39
use daita\MySmallPhpTools\Traits\TRequest;
40
use daita\MySmallPhpTools\Traits\TStringTools;
41
use OC;
42
use OC\Security\IdentityProof\Signer;
43
use OC\User\NoUserException;
44
use OCA\Circles\Db\GSEventsRequest;
45
use OCA\Circles\Exceptions\GlobalScaleEventException;
46
use OCA\Circles\Exceptions\GSKeyException;
47
use OCA\Circles\Exceptions\GSStatusException;
48
use OCA\Circles\GlobalScale\AGlobalScaleEvent;
49
use OCA\Circles\Model\GlobalScale\GSEvent;
50
use OCA\Circles\Model\GlobalScale\GSWrapper;
51
use OCP\AppFramework\QueryException;
52
use OCP\IURLGenerator;
53
use OCP\IUser;
54
use OCP\IUserManager;
55
use OCP\IUserSession;
56
57
58
/**
59
 * Class GlobalScaleService
60
 *
61
 * @package OCA\Circles\Service
62
 */
63
class GlobalScaleService {
64
65
66
	use TRequest;
67
	use TStringTools;
68
69
70
	/** @var IURLGenerator */
71
	private $urlGenerator;
72
73
	/** @var IUserManager */
74
	private $userManager;
75
76
	/** @var IUserSession */
77
	private $userSession;
78
79
	/** @var Signer */
80
	private $signer;
81
82
	/** @var GSEventsRequest */
83
	private $gsEventsRequest;
84
85
	/** @var ConfigService */
86
	private $configService;
87
88
	/** @var MiscService */
89
	private $miscService;
90
91
92
	/**
93
	 * GlobalScaleService constructor.
94
	 *
95
	 * @param IURLGenerator $urlGenerator
96
	 * @param IUserManager $userManager
97
	 * @param IUserSession $userSession
98
	 * @param Signer $signer
99
	 * @param GSEventsRequest $gsEventsRequest
100
	 * @param ConfigService $configService
101
	 * @param MiscService $miscService
102
	 */
103
	public function __construct(
104
		IURLGenerator $urlGenerator,
105
		IUserManager $userManager,
106
		IUserSession $userSession,
107
		Signer $signer,
108
		GSEventsRequest $gsEventsRequest,
109
		ConfigService $configService,
110
		MiscService $miscService
111
	) {
112
		$this->urlGenerator = $urlGenerator;
113
		$this->userManager = $userManager;
114
		$this->userSession = $userSession;
115
		$this->signer = $signer;
116
		$this->gsEventsRequest = $gsEventsRequest;
117
		$this->configService = $configService;
118
		$this->miscService = $miscService;
119
	}
120
121
122
	/**
123
	 * @param GSEvent $event
124
	 *
125
	 * @return GSWrapper
126
	 */
127
	public function asyncBroadcast(GSEvent $event): GSWrapper {
128
		$wrapper = new GSWrapper();
129
		$wrapper->setEvent($event);
130
		$wrapper->setToken($this->uuid());
131
		$wrapper->setCreation(time());
132
		$wrapper->setSeverity($event->getSeverity());
133
134
		foreach ($this->getInstances($event->isAsync()) as $instance) {
135
			$wrapper->setInstance($instance);
136
			$wrapper = $this->gsEventsRequest->create($wrapper);
137
		}
138
139
		$absolute = $this->urlGenerator->linkToRouteAbsolute(
140
			'circles.GlobalScale.asyncBroadcast', ['token' => $wrapper->getToken()]
141
		);
142
143
		$request = new Request('', Request::TYPE_PUT);
144
		if ($this->configService->getAppValue(ConfigService::CIRCLES_SELF_SIGNED) === '1') {
145
			$request->setVerifyPeer(false);
146
		}
147
		$request->setAddressFromUrl($absolute);
148
149
		try {
150
			$this->doRequest($request);
151
		} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
152
		}
153
154
		return $wrapper;
155
	}
156
157
158
	/**
159
	 * @param GSEvent $event
160
	 *
161
	 * @return AGlobalScaleEvent
162
	 * @throws GlobalScaleEventException
163
	 */
164
	public function getGlobalScaleEvent(GSEvent $event): AGlobalScaleEvent {
165
		$class = $this->getClassNameFromEvent($event);
166
		try {
167
			$gs = OC::$server->query($class);
168
			if (!$gs instanceof AGlobalScaleEvent) {
169
				throw new GlobalScaleEventException($class . ' not an AGlobalScaleEvent');
170
			}
171
172
			return $gs;
173
		} catch (QueryException $e) {
0 ignored issues
show
Bug introduced by
The class OCP\AppFramework\QueryException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
174
			throw new GlobalScaleEventException('AGlobalScaleEvent ' . $class . ' not found');
175
		}
176
	}
177
178
179
	/**
180
	 * @return string
181
	 */
182
	public function getKey(): string {
183
		// TODO: include a webfinger loader in core to share public keys
184
		try {
185
			$key = $this->configService->getGSStatus(ConfigService::GS_KEY);
186
		} catch (GSStatusException $e) {
187
			$key = $this->configService->getSystemValue('instanceid');
188
		}
189
190
		return md5('gskey:' . $key);
191
	}
192
193
	/**
194
	 * @param string $key
195
	 *
196
	 * @throws GSKeyException
197
	 */
198
	public function checkKey(string $key) {
199
		if ($key !== $this->getKey()) {
200
			throw new GSKeyException('invalid key');
201
		}
202
	}
203
204
205
	/**
206
	 * @param GSEvent $event
207
	 *
208
	 * @throws GSKeyException
209
	 */
210
	public function checkEvent(GSEvent $event): void {
211
		$this->checkKey($event->getKey());
212
	}
213
214
215
	/**
216
	 * @param bool $all
217
	 *
218
	 * @return array
219
	 */
220
	public function getInstances(bool $all = false): array {
221
		/** @var string $lookup */
222
		try {
223
			$lookup = $this->configService->getGSStatus(ConfigService::GS_LOOKUP);
224
			$request = new Request(ConfigService::GS_LOOKUP_INSTANCES, Request::TYPE_POST);
225
			if ($this->configService->getAppValue(ConfigService::CIRCLES_SELF_SIGNED) === '1') {
226
				$request->setVerifyPeer(false);
227
			}
228
229
			try {
230
				$user = $this->getRandomUser();
231
			} catch (NoUserException $e) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class OC\User\NoUserException does not exist. Did you forget a USE statement, or did you not list all dependencies?

Scrutinizer analyzes your composer.json/composer.lock file if available to determine the classes, and functions that are defined by your dependencies.

It seems like the listed class was neither found in your dependencies, nor was it found in the analyzed files in your repository. If you are using some other form of dependency management, you might want to disable this analysis.

Loading history...
232
			}
233
			$data = $this->signer->sign('lookupserver', ['federationId' => $user->getCloudId()], $user);
234
			$request->setData($data);
235
			$request->setAddressFromUrl($lookup);
236
237
			try {
238
				$instances = $this->retrieveJson($request);
239
			} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
240
				$this->miscService->log(
241
					'Issue while retrieving instances from lookup: ' . get_class($e) . ' ' . $e->getMessage()
242
				);
243
244
				return [];
245
			}
246
		} catch (GSStatusException $e) {
247
			return $this->getLocalInstance($all);
248
		}
249
250
		if ($all) {
251
			return $instances;
252
		}
253
254
		return array_values(array_diff($instances, $this->configService->getTrustedDomains()));
255
	}
256
257
258
	/**
259
	 * @param bool $all
260
	 *
261
	 * @return array
262
	 */
263
	private function getLocalInstance(bool $all): array {
264
		if (!$all) {
265
			return [];
266
		}
267
268
		$absolute = $this->urlGenerator->linkToRouteAbsolute('circles.Navigation.navigate');
269
270
		return [parse_url($absolute, PHP_URL_HOST)];
271
	}
272
273
274
	/**
275
	 * @param GSEvent $event
276
	 *
277
	 * @return string
278
	 * @throws GlobalScaleEventException
279
	 */
280
	private function getClassNameFromEvent(GSEvent $event): string {
281
		$className = $event->getType();
282
		if (substr($className, 0, 25) !== '\OCA\Circles\GlobalScale\\' || strpos($className, '.')) {
283
			throw new GlobalScaleEventException(
284
				$className . ' does not seems to be a secured AGlobalScaleEvent'
285
			);
286
		}
287
288
		return $className;
289
	}
290
291
292
	/**
293
	 * @return IUser
294
	 * @throws NoUserException
295
	 */
296
	private function getRandomUser(): IUser {
297
		$user = $this->userSession->getUser();
298
		if ($user !== null) {
299
			return $user;
300
		}
301
302
		$random = $this->userManager->search('', 1);
303
		if (sizeof($random) > 0) {
304
			return array_shift($random);
305
		}
306
307
		throw new NoUserException();
308
	}
309
310
}
311
312