Completed
Push — master ( fb5007...9bba10 )
by Maxence
02:15
created

GlobalScaleService::getInstances()   A

Complexity

Conditions 5
Paths 22

Size

Total Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 33
rs 9.0808
c 0
b 0
f 0
cc 5
nc 22
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 string
126
	 */
127
	public function asyncBroadcast(GSEvent $event): string {
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->getToken();
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
	 * @throws NoUserException
220
	 */
221
	public function getInstances(bool $all = false): array {
222
		/** @var string $lookup */
223
		try {
224
			$lookup = $this->configService->getGSStatus(ConfigService::GS_LOOKUP);
225
			$request = new Request(ConfigService::GS_LOOKUP_INSTANCES, Request::TYPE_POST);
226
			if ($this->configService->getAppValue(ConfigService::CIRCLES_SELF_SIGNED) === '1') {
227
				$request->setVerifyPeer(false);
228
			}
229
230
			$user = $this->getRandomUser();
231
			$data = $this->signer->sign('lookupserver', ['federationId' => $user->getCloudId()], $user);
232
			$request->setData($data);
233
			$request->setAddressFromUrl($lookup);
234
235
			try {
236
				$instances = $this->retrieveJson($request);
237
			} catch (RequestContentException | RequestNetworkException | RequestResultSizeException | RequestServerException | RequestResultNotJsonException $e) {
238
				$this->miscService->log(
239
					'Issue while retrieving instances from lookup: ' . get_class($e) . ' ' . $e->getMessage()
240
				);
241
242
				return [];
243
			}
244
		} catch (GSStatusException $e) {
245
			return $this->getLocalInstance($all);
246
		}
247
248
		if ($all) {
249
			return $instances;
250
		}
251
252
		return array_values(array_diff($instances, $this->configService->getTrustedDomains()));
253
	}
254
255
256
	/**
257
	 * @param bool $all
258
	 *
259
	 * @return array
260
	 */
261
	private function getLocalInstance(bool $all): array {
262
		if (!$all) {
263
			return [];
264
		}
265
266
		$absolute = $this->urlGenerator->linkToRouteAbsolute('circles.Navigation.navigate');
267
268
		return [parse_url($absolute, PHP_URL_HOST)];
269
	}
270
271
272
	/**
273
	 * @param GSEvent $event
274
	 *
275
	 * @return string
276
	 * @throws GlobalScaleEventException
277
	 */
278
	private function getClassNameFromEvent(GSEvent $event): string {
279
		$className = $event->getType();
280
		if (substr($className, 0, 25) !== '\OCA\Circles\GlobalScale\\' || strpos($className, '.')) {
281
			throw new GlobalScaleEventException(
282
				$className . ' does not seems to be a secured AGlobalScaleEvent'
283
			);
284
		}
285
286
		return $className;
287
	}
288
289
290
	/**
291
	 * @return IUser
292
	 * @throws NoUserException
293
	 */
294
	private function getRandomUser(): IUser {
295
		$user = $this->userSession->getUser();
296
		if ($user !== null) {
297
			return $user;
298
		}
299
300
		$random = $this->userManager->search('', 1);
301
		if (sizeof($random) > 0) {
302
			return array_shift($random);
303
		}
304
305
		throw new NoUserException();
306
	}
307
308
}
309
310