Completed
Push — master ( c57b95...1d3e11 )
by Maxence
30s queued 14s
created

ConfigService::isGSAvailable()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 0
1
<?php
2
/**
3
 * Circles - Bring cloud-users closer together.
4
 *
5
 * This file is licensed under the Affero General Public License version 3 or
6
 * later. See the COPYING file.
7
 *
8
 * @author Maxence Lange <[email protected]>
9
 * @copyright 2017
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\Circles\Service;
28
29
use daita\MySmallPhpTools\Model\Nextcloud\nc22\NC22Request;
30
use daita\MySmallPhpTools\Traits\TArrayTools;
31
use daita\MySmallPhpTools\Traits\TStringTools;
32
use OCA\Circles\AppInfo\Application;
33
use OCA\Circles\Exceptions\GSStatusException;
34
use OCA\Circles\Model\Circle;
35
use OCA\Circles\Model\DeprecatedCircle;
36
use OCA\Circles\Model\Member;
37
use OCP\IConfig;
38
use OCP\IURLGenerator;
39
40
class ConfigService {
41
42
43
	use TStringTools;
44
	use TArrayTools;
45
46
47
	const FRONTAL_CLOUD_ID = 'frontal_cloud_id';
48
	const FRONTAL_CLOUD_SCHEME = 'frontal_cloud_scheme';
49
	const INTERNAL_CLOUD_ID = 'internal_cloud_id';
50
	const INTERNAL_CLOUD_SCHEME = 'internal_cloud_scheme';
51
	const LOOPBACK_CLOUD_ID = 'loopback_cloud_id';
52
	const LOOPBACK_CLOUD_SCHEME = 'loopback_cloud_scheme';
53
	const SELF_SIGNED_CERT = 'self_signed_cert';
54
	const MEMBERS_LIMIT = 'members_limit';
55
	const ACTIVITY_ON_NEW_CIRCLE = 'creation_activity';
56
	const MIGRATION_22 = 'migration_22';
57
58
	// deprecated
59
	const CIRCLES_CONTACT_BACKEND = 'contact_backend';
60
	const CIRCLES_ACCOUNTS_ONLY = 'accounts_only'; // only UserType=1
61
	const CIRCLES_SEARCH_FROM_COLLABORATOR = 'search_from_collaborator';
62
63
64
	const FORCE_NC_BASE = 'force_nc_base';
65
	const TEST_NC_BASE = 'test_nc_base';
66
67
	const GS_MODE = 'mode';
68
	const GS_KEY = 'key';
69
70
	const GS_LOOKUP_INSTANCES = '/instances';
71
	const GS_LOOKUP_USERS = '/users';
72
73
74
	private $defaults = [
75
		self::FRONTAL_CLOUD_ID       => '',
76
		self::FRONTAL_CLOUD_SCHEME   => 'https',
77
		self::INTERNAL_CLOUD_ID      => '',
78
		self::INTERNAL_CLOUD_SCHEME  => 'https',
79
		self::LOOPBACK_CLOUD_ID      => '',
80
		self::LOOPBACK_CLOUD_SCHEME  => 'https',
81
		self::SELF_SIGNED_CERT       => '0',
82
		self::MEMBERS_LIMIT          => '50',
83
		self::ACTIVITY_ON_NEW_CIRCLE => '1',
84
		self::MIGRATION_22           => '0',
85
86
		self::FORCE_NC_BASE                    => '',
87
		self::TEST_NC_BASE                     => '',
88
		self::CIRCLES_CONTACT_BACKEND          => '0',
89
		self::CIRCLES_ACCOUNTS_ONLY            => '0',
90
		self::CIRCLES_SEARCH_FROM_COLLABORATOR => '0',
91
	];
92
93
94
	/** @var IConfig */
95
	private $config;
96
97
	/** @var IURLGenerator */
98
	private $urlGenerator;
99
100
101
	/**
102
	 * ConfigService constructor.
103
	 *
104
	 * @param IConfig $config
105
	 * @param IURLGenerator $urlGenerator
106
	 */
107
	public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
108
		$this->config = $config;
109
		$this->urlGenerator = $urlGenerator;
110
	}
111
112
113
	/**
114
	 * Get a value by key
115
	 *
116
	 * @param string $key
117
	 *
118
	 * @return string
119
	 */
120
	public function getAppValue(string $key): string {
121
		if (($value = $this->config->getAppValue(Application::APP_ID, $key, '')) !== '') {
122
			return $value;
123
		}
124
125
		if (($value = $this->config->getSystemValue('circles.' . $key, '')) !== '') {
126
			return $value;
127
		}
128
129
		return $this->get($key, $this->defaults);
130
	}
131
132
	/**
133
	 * @param string $key
134
	 *
135
	 * @return int
136
	 */
137
	public function getAppValueInt(string $key): int {
138
		return (int)$this->getAppValue($key);
139
	}
140
141
	/**
142
	 * @param string $key
143
	 *
144
	 * @return bool
145
	 */
146
	public function getAppValueBool(string $key): bool {
147
		return ($this->getAppValueInt($key) === 1);
148
	}
149
150
151
	/**
152
	 * Set a value by key
153
	 *
154
	 * @param string $key
155
	 * @param string $value
156
	 *
157
	 * @return void
158
	 */
159
	public function setAppValue(string $key, string $value): void {
160
		$this->config->setAppValue(Application::APP_ID, $key, $value);
161
	}
162
163
164
	/**
165
	 *
166
	 */
167
	public function unsetAppConfig(): void {
168
		$this->config->deleteAppValues(Application::APP_ID);
169
	}
170
171
172
	/**
173
	 * Get available hosts
174
	 *
175
	 * @return array
176
	 */
177
	public function getAvailableHosts(): array {
178
		return $this->config->getSystemValue('trusted_domains', []);
179
	}
180
181
182
	/**
183
	 * Get a user value by key and user
184
	 *
185
	 *
186
	 * @param string $userId
187
	 * @param string $key
188
	 *
189
	 * @param string $default
190
	 *
191
	 * @return string
192
	 */
193
	public function getCoreValueForUser($userId, $key, $default = '') {
194
		return $this->config->getUserValue($userId, 'core', $key, $default);
195
	}
196
197
198
	/**
199
	 * @return bool
200
	 * @deprecated
201
	 */
202
	public function isContactsBackend(): bool {
203
		return ($this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '0'
204
				&& $this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '');
205
	}
206
207
208
	/**
209
	 * @return int
210
	 * @deprecated
211
	 */
212
	public function contactsBackendType(): int {
213
		return (int)$this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND);
214
	}
215
216
217
	/**
218
	 * @return bool
219
	 * @deprecated
220
	 * should the password for a mail share be send to the recipient
221
	 *
222
	 */
223
	public function sendPasswordByMail() {
224
		if ($this->isContactsBackend()) {
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Circles\Service\Conf...ce::isContactsBackend() has been deprecated.

This method has been deprecated.

Loading history...
225
			return false;
226
		}
227
228
		return ($this->config->getAppValue('sharebymail', 'sendpasswordmail', 'yes') === 'yes');
229
	}
230
231
	/**
232
	 * @param DeprecatedCircle $circle
233
	 *
234
	 * @return bool
235
	 * @deprecated
236
	 * do we require a share by mail to be password protected
237
	 *
238
	 */
239
	public function enforcePasswordProtection(DeprecatedCircle $circle) {
240
		if ($this->isContactsBackend()) {
0 ignored issues
show
Deprecated Code introduced by
The method OCA\Circles\Service\Conf...ce::isContactsBackend() has been deprecated.

This method has been deprecated.

Loading history...
241
			return false;
242
		}
243
244
		if ($circle->getSetting('password_enforcement') === 'true') {
245
			return true;
246
		}
247
248
		return ($this->config->getAppValue('sharebymail', 'enforcePasswordProtection', 'no') === 'yes');
249
	}
250
251
252
	/**
253
	 * // TODO: fetch data from somewhere else than hard coded...
254
	 *
255
	 * @return array
256
	 */
257
	public function getSettings(): array {
258
		return [
259
			'allowedCircles'   => Circle::$DEF_CFG_MAX,
260
			'allowedUserTypes' => Member::$DEF_TYPE_MAX,
261
			'membersLimit'     => $this->getAppValueInt(self::MEMBERS_LIMIT)
262
		];
263
	}
264
265
266
	/**
267
	 * @return bool
268
	 */
269
	public function isGSAvailable(): bool {
270
		if (!empty($this->getGSSMockup())) {
271
			return true;
272
		}
273
274
		return $this->config->getSystemValueBool('gs.enabled', false);
275
	}
276
277
278
	/**
279
	 * @return string
280
	 * @throws GSStatusException
281
	 */
282
	public function getGSLookup(): string {
283
		$lookup = $this->config->getSystemValue('lookup_server', '');
284
285
		if (!$this->isGSAvailable() || $lookup === '') {
286
			throw new  GSStatusException();
287
		}
288
289
		return $lookup;
290
	}
291
292
293
	/**
294
	 * @return array
295
	 */
296
	public function getGSSMockup(): array {
297
		return $this->config->getSystemValue('gss.mockup', []);
298
	}
299
300
301
	/**
302
	 * @param string $type
303
	 *
304
	 * @return string
305
	 */
306
	public function getGSInfo(string $type): string {
307
		$clef = $this->config->getSystemValue('gss.jwt.key', '');
308
		$mode = $this->config->getSystemValue('gss.mode', '');
309
310
		switch ($type) {
311
			case self::GS_MODE:
312
				return $mode;
313
314
			case self::GS_KEY:
315
				return $clef;
316
		}
317
318
319
		return '';
320
	}
321
322
323
	/**
324
	 * @return array
325
	 * @throws GSStatusException
326
	 */
327
	public function getGSData(): array {
328
		return [
329
			'enabled'     => $this->isGSAvailable(),
330
			'lookup'      => $this->getGSLookup(),
331
			'mockup'      => $this->getGSSMockup(),
332
			self::GS_MODE => $this->config->getSystemValue('gss.mode', ''),
333
			self::GS_KEY  => $this->config->getSystemValue('gss.jwt.key', ''),
334
		];
335
	}
336
337
338
	/**
339
	 * @return array
340
	 */
341
	public function getTrustedDomains(): array {
342
		return array_values($this->config->getSystemValue('trusted_domains', []));
343
	}
344
345
346
	/**
347
	 * - returns host+port, does not specify any protocol
348
	 * - can be forced using FRONTAL_CLOUD_ID
349
	 * - use 'overwrite.cli.url'
350
	 * - can use the first entry from trusted_domains if FRONTAL_CLOUD_ID = 'use-trusted-domain'
351
	 * - used mainly to assign instance and source to a request
352
	 * - important only in remote environment; can be totally random in a jailed environment
353
	 *
354
	 * @return string
355
	 */
356
	public function getFrontalInstance(): string {
357
		$frontalCloudId = $this->getAppValue(self::FRONTAL_CLOUD_ID);
358
359
		// using old settings - Deprecated in NC25
360
		if ($frontalCloudId === '') {
361
			$frontalCloudId = $this->config->getAppValue(Application::APP_ID, 'local_cloud_id', '');
362
			if ($frontalCloudId !== '') {
363
				$this->setAppValue(self::FRONTAL_CLOUD_ID, $frontalCloudId);
364
			}
365
		}
366
367
		if ($frontalCloudId === '') {
368
			$cliUrl = $this->config->getSystemValue('overwrite.cli.url', '');
369
			$frontal = parse_url($cliUrl);
370
			if (!is_array($frontal) || !array_key_exists('host', $frontal)) {
371
				if ($cliUrl !== '') {
372
					return $cliUrl;
373
				}
374
375
				$randomCloudId = $this->uuid();
376
				$this->setAppValue(self::FRONTAL_CLOUD_ID, $randomCloudId);
377
378
				return $randomCloudId;
379
			}
380
381
			if (array_key_exists('port', $frontal)) {
382
				return $frontal['host'] . ':' . $frontal['port'];
383
			} else {
384
				return $frontal['host'];
385
			}
386
		} else if ($frontalCloudId === 'use-trusted-domain') {
387
			return $this->getTrustedDomains()[0];
388
		} else {
389
			return $frontalCloudId;
390
		}
391
	}
392
393
394
	/**
395
	 * returns address based on FRONTAL_CLOUD_ID, FRONTAL_CLOUD_SCHEME and a routeName
396
	 * perfect for urlId in ActivityPub env.
397
	 *
398
	 * @param string $route
399
	 * @param array $args
400
	 *
401
	 * @return string
402
	 */
403
	public function getFrontalPath(string $route = 'circles.Remote.appService', array $args = []): string {
404
		$base = $this->getAppValue(self::FRONTAL_CLOUD_SCHEME) . '://' . $this->getFrontalInstance();
405
406
		if ($route === '') {
407
			return $base;
408
		}
409
410
		return $base . $this->urlGenerator->linkToRoute($route, $args);
411
	}
412
413
	/**
414
	 * @param string $instance
415
	 *
416
	 * @return bool
417
	 */
418
	public function isLocalInstance(string $instance): bool {
419
		if (strtolower($instance) === strtolower($this->getFrontalInstance())) {
420
			return true;
421
		}
422
423
		if ($this->getAppValue(self::FRONTAL_CLOUD_ID) === 'use-trusted-domain') {
424
			return (in_array($instance, $this->getTrustedDomains()));
425
		}
426
427
		return false;
428
	}
429
430
431
	/**
432
	 * @param NC22Request $request
433
	 * @param string $routeName
434
	 * @param array $args
435
	 */
436
	public function configureRequest(NC22Request $request, string $routeName = '', array $args = []): void {
437
		$this->configureRequestAddress($request, $routeName, $args);
438
439
		if ($this->getForcedNcBase() === '') {
440
			$request->setProtocols(['https', 'http']);
441
		}
442
443
		$request->setVerifyPeer($this->getAppValue(ConfigService::SELF_SIGNED_CERT) !== '1');
444
		$request->setHttpErrorsAllowed(true);
445
		$request->setLocalAddressAllowed(true);
446
		$request->setFollowLocation(true);
447
		$request->setTimeout(5);
448
	}
449
450
	/**
451
	 * - Create route using overwrite.cli.url.
452
	 * - can be forced using FORCE_NC_BASE or TEST_BC_BASE (temporary)
453
	 * - can also be overwritten in config/config.php: 'circles.force_nc_base'
454
	 * - perfect for loopback request.
455
	 *
456
	 * @param NC22Request $request
457
	 * @param string $routeName
458
	 * @param array $args
459
	 */
460
	private function configureRequestAddress(
461
		NC22Request $request,
462
		string $routeName,
463
		array $args = []
464
	): void {
465
		if ($routeName === '') {
466
			return;
467
		}
468
469
		$ncBase = $this->getForcedNcBase();
470
		if ($ncBase !== '') {
471
			$absolute = $this->cleanLinkToRoute($ncBase, $routeName, $args);
472
		} else {
473
			$absolute = $this->urlGenerator->linkToRouteAbsolute($routeName, $args);
474
		}
475
476
		$request->basedOnUrl($absolute);
477
	}
478
479
480
	/**
481
	 * - return force_nc_base from config/config.php, then from FORCE_NC_BASE.
482
	 *
483
	 * @return string
484
	 */
485
	private function getForcedNcBase(): string {
486
		if ($this->getAppValue(self::TEST_NC_BASE) !== '') {
487
			return $this->getAppValue(self::TEST_NC_BASE);
488
		}
489
490
		$fromConfig = $this->config->getSystemValue('circles.force_nc_base', '');
491
		if ($fromConfig !== '') {
492
			return $fromConfig;
493
		}
494
495
		return $this->getAppValue(self::FORCE_NC_BASE);
496
	}
497
498
499
	/**
500
	 * sometimes, linkToRoute will include the base path to the nextcloud which will be duplicate with ncBase
501
	 *
502
	 * @param string $ncBase
503
	 * @param string $routeName
504
	 * @param array $args
505
	 *
506
	 * @return string
507
	 */
508
	private function cleanLinkToRoute(string $ncBase, string $routeName, array $args): string {
509
		$link = $this->urlGenerator->linkToRoute($routeName, $args);
510
		$forcedPath = rtrim(parse_url($ncBase, PHP_URL_PATH), '/');
511
512
		if ($forcedPath !== '' && strpos($link, $forcedPath) === 0) {
513
			$ncBase = substr($ncBase, 0, -strlen($forcedPath));
514
		}
515
516
		return rtrim($ncBase, '/') . $link;
517
	}
518
519
}
520
521