Completed
Push — master ( c3c93d...f456aa )
by Maxence
03:02 queued 11s
created

ConfigService::getAppValue()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 11
rs 9.9
c 0
b 0
f 0
cc 3
nc 3
nop 1
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
32
namespace OCA\Circles\Service;
33
34
35
use daita\MySmallPhpTools\Model\Nextcloud\nc22\NC22Request;
36
use daita\MySmallPhpTools\Traits\TArrayTools;
37
use daita\MySmallPhpTools\Traits\TStringTools;
38
use OCA\Circles\AppInfo\Application;
39
use OCA\Circles\Exceptions\GSStatusException;
40
use OCA\Circles\IFederatedUser;
41
use OCA\Circles\Model\Circle;
42
use OCA\Circles\Model\DeprecatedCircle;
43
use OCA\Circles\Model\Member;
44
use OCP\IConfig;
45
use OCP\IURLGenerator;
46
47
48
/**
49
 * Class ConfigService
50
 *
51
 * @package OCA\Circles\Service
52
 */
53
class ConfigService {
54
55
56
	use TStringTools;
57
	use TArrayTools;
58
59
60
	const FRONTAL_CLOUD_ID = 'frontal_cloud_id';
61
	const FRONTAL_CLOUD_SCHEME = 'frontal_cloud_scheme';
62
	const INTERNAL_CLOUD_ID = 'internal_cloud_id';
63
	const INTERNAL_CLOUD_SCHEME = 'internal_cloud_scheme';
64
	const LOOPBACK_CLOUD_ID = 'loopback_cloud_id';
65
	const LOOPBACK_CLOUD_SCHEME = 'loopback_cloud_scheme';
66
	const IFACE0_CLOUD_ID = 'iface0_cloud_id';
67
	const IFACE0_CLOUD_SCHEME = 'iface0_cloud_scheme';
68
	const IFACE1_CLOUD_ID = 'iface1_cloud_id';
69
	const IFACE1_CLOUD_SCHEME = 'iface1_cloud_scheme';
70
	const IFACE2_CLOUD_ID = 'iface2_cloud_id';
71
	const IFACE2_CLOUD_SCHEME = 'iface2_cloud_scheme';
72
	const IFACE3_CLOUD_ID = 'iface3_cloud_id';
73
	const IFACE3_CLOUD_SCHEME = 'iface3_cloud_scheme';
74
	const IFACE4_CLOUD_ID = 'iface4_cloud_id';
75
	const IFACE4_CLOUD_SCHEME = 'iface4_cloud_scheme';
76
	const IFACE_TEST_ID = 'iface_test_id';
77
	const IFACE_TEST_SCHEME = 'iface_test_scheme';
78
	const IFACE_TEST_TOKEN = 'iface_test_token';
79
80
	const HARD_MODERATION = 'hard_moderation';
81
	const ROUTE_TO_CIRCLE = 'route_to_circle';
82
	const EVENT_EXAMPLES = 'event_examples';
83
84
	const SELF_SIGNED_CERT = 'self_signed_cert';
85
	const MEMBERS_LIMIT = 'members_limit';
86
	const ACTIVITY_ON_NEW_CIRCLE = 'creation_activity';
87
	const MIGRATION_22 = 'migration_22';
88
89
	const LOOPBACK_TMP_ID = 'loopback_tmp_id';
90
	const LOOPBACK_TMP_SCHEME = 'loopback_tmp_scheme';
91
92
	const GS_MODE = 'mode';
93
	const GS_KEY = 'key';
94
95
	const GS_LOOKUP_INSTANCES = '/instances';
96
	const GS_LOOKUP_USERS = '/users';
97
98
99
	// deprecated -- removing in NC25
100
	const CIRCLES_CONTACT_BACKEND = 'contact_backend';
101
	const CIRCLES_ACCOUNTS_ONLY = 'accounts_only'; // only UserType=1
102
	const CIRCLES_SEARCH_FROM_COLLABORATOR = 'search_from_collaborator';
103
104
	const FORCE_NC_BASE = 'force_nc_base';
105
	const TEST_NC_BASE = 'test_nc_base';
106
107
108
	private $defaults = [
109
		self::FRONTAL_CLOUD_ID      => '',
110
		self::FRONTAL_CLOUD_SCHEME  => 'https',
111
		self::INTERNAL_CLOUD_ID     => '',
112
		self::INTERNAL_CLOUD_SCHEME => 'https',
113
		self::LOOPBACK_CLOUD_ID     => '',
114
		self::LOOPBACK_CLOUD_SCHEME => 'https',
115
		self::LOOPBACK_TMP_ID       => '',
116
		self::LOOPBACK_TMP_SCHEME   => '',
117
		self::IFACE0_CLOUD_ID       => '',
118
		self::IFACE0_CLOUD_SCHEME   => 'https',
119
		self::IFACE1_CLOUD_ID       => '',
120
		self::IFACE1_CLOUD_SCHEME   => 'https',
121
		self::IFACE2_CLOUD_ID       => '',
122
		self::IFACE2_CLOUD_SCHEME   => 'https',
123
		self::IFACE3_CLOUD_ID       => '',
124
		self::IFACE3_CLOUD_SCHEME   => 'https',
125
		self::IFACE4_CLOUD_ID       => '',
126
		self::IFACE4_CLOUD_SCHEME   => 'https',
127
		self::IFACE_TEST_ID         => '',
128
		self::IFACE_TEST_SCHEME     => 'https',
129
		self::IFACE_TEST_TOKEN      => '',
130
131
		self::HARD_MODERATION => '0',
132
		self::ROUTE_TO_CIRCLE => 'contacts.contacts.directcircle',
133
		self::EVENT_EXAMPLES  => '0',
134
135
		self::SELF_SIGNED_CERT       => '0',
136
		self::MEMBERS_LIMIT          => '50',
137
		self::ACTIVITY_ON_NEW_CIRCLE => '1',
138
		self::MIGRATION_22           => '0',
139
140
		self::FORCE_NC_BASE                    => '',
141
		self::TEST_NC_BASE                     => '',
142
		self::CIRCLES_CONTACT_BACKEND          => '0',
143
		self::CIRCLES_ACCOUNTS_ONLY            => '0',
144
		self::CIRCLES_SEARCH_FROM_COLLABORATOR => '0',
145
	];
146
147
148
	/** @var IConfig */
149
	private $config;
150
151
	/** @var IURLGenerator */
152
	private $urlGenerator;
153
154
155
	/**
156
	 * ConfigService constructor.
157
	 *
158
	 * @param IConfig $config
159
	 * @param IURLGenerator $urlGenerator
160
	 */
161
	public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
162
		$this->config = $config;
163
		$this->urlGenerator = $urlGenerator;
164
	}
165
166
167
	/**
168
	 * Get a value by key
169
	 *
170
	 * @param string $key
171
	 *
172
	 * @return string
173
	 */
174
	public function getAppValue(string $key): string {
175
		if (($value = $this->config->getAppValue(Application::APP_ID, $key, '')) !== '') {
176
			return $value;
177
		}
178
179
		if (($value = $this->config->getSystemValue(Application::APP_ID . '.' . $key, '')) !== '') {
180
			return $value;
181
		}
182
183
		return $this->get($key, $this->defaults);
184
	}
185
186
	/**
187
	 * @param string $key
188
	 *
189
	 * @return int
190
	 */
191
	public function getAppValueInt(string $key): int {
192
		return (int)$this->getAppValue($key);
193
	}
194
195
	/**
196
	 * @param string $key
197
	 *
198
	 * @return bool
199
	 */
200
	public function getAppValueBool(string $key): bool {
201
		return ($this->getAppValueInt($key) === 1);
202
	}
203
204
205
	/**
206
	 * Set a value by key
207
	 *
208
	 * @param string $key
209
	 * @param string $value
210
	 *
211
	 * @return void
212
	 */
213
	public function setAppValue(string $key, string $value): void {
214
		$this->config->setAppValue(Application::APP_ID, $key, $value);
215
	}
216
217
218
	/**
219
	 *
220
	 */
221
	public function unsetAppConfig(): void {
222
		$this->config->deleteAppValues(Application::APP_ID);
223
	}
224
225
226
	/**
227
	 * Get available hosts
228
	 *
229
	 * @return array
230
	 */
231
	public function getAvailableHosts(): array {
232
		return $this->config->getSystemValue('trusted_domains', []);
233
	}
234
235
236
	/**
237
	 * Get a user value by key and user
238
	 *
239
	 *
240
	 * @param string $userId
241
	 * @param string $key
242
	 *
243
	 * @param string $default
244
	 *
245
	 * @return string
246
	 */
247
	public function getCoreValueForUser($userId, $key, $default = '') {
248
		return $this->config->getUserValue($userId, 'core', $key, $default);
249
	}
250
251
252
	/**
253
	 * @return bool
254
	 * @deprecated
255
	 */
256
	public function isContactsBackend(): bool {
257
		return ($this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '0'
258
				&& $this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '');
259
	}
260
261
262
	/**
263
	 * @return int
264
	 * @deprecated
265
	 */
266
	public function contactsBackendType(): int {
267
		return (int)$this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND);
268
	}
269
270
271
	/**
272
	 * @return bool
273
	 * @deprecated
274
	 * should the password for a mail share be send to the recipient
275
	 *
276
	 */
277
	public function sendPasswordByMail() {
278
		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...
279
			return false;
280
		}
281
282
		return ($this->config->getAppValue('sharebymail', 'sendpasswordmail', 'yes') === 'yes');
283
	}
284
285
	/**
286
	 * @param DeprecatedCircle $circle
287
	 *
288
	 * @return bool
289
	 * @deprecated
290
	 * do we require a share by mail to be password protected
291
	 *
292
	 */
293
	public function enforcePasswordProtection(DeprecatedCircle $circle) {
294
		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...
295
			return false;
296
		}
297
298
		if ($circle->getSetting('password_enforcement') === 'true') {
299
			return true;
300
		}
301
302
		return ($this->config->getAppValue('sharebymail', 'enforcePasswordProtection', 'no') === 'yes');
303
	}
304
305
306
	/**
307
	 * // TODO: fetch data from somewhere else than hard coded...
308
	 *
309
	 * @return array
310
	 */
311
	public function getSettings(): array {
312
		return [
313
			'allowedCircles'   => Circle::$DEF_CFG_MAX,
314
			'allowedUserTypes' => Member::$DEF_TYPE_MAX,
315
			'membersLimit'     => $this->getAppValueInt(self::MEMBERS_LIMIT)
316
		];
317
	}
318
319
320
	/**
321
	 * @return bool
322
	 */
323
	public function isGSAvailable(): bool {
324
		if (!empty($this->getGSSMockup())) {
325
			return true;
326
		}
327
328
		return $this->config->getSystemValueBool('gs.enabled', false);
329
	}
330
331
332
	/**
333
	 * @return string
334
	 * @throws GSStatusException
335
	 */
336
	public function getGSLookup(): string {
337
		$lookup = $this->config->getSystemValue('lookup_server', '');
338
339
		if (!$this->isGSAvailable() || $lookup === '') {
340
			throw new  GSStatusException();
341
		}
342
343
		return $lookup;
344
	}
345
346
347
	/**
348
	 * @return array
349
	 */
350
	public function getGSSMockup(): array {
351
		return $this->config->getSystemValue('gss.mockup', []);
352
	}
353
354
355
	/**
356
	 * @param string $type
357
	 *
358
	 * @return string
359
	 */
360
	public function getGSInfo(string $type): string {
361
		$clef = $this->config->getSystemValue('gss.jwt.key', '');
362
		$mode = $this->config->getSystemValue('gss.mode', '');
363
364
		switch ($type) {
365
			case self::GS_MODE:
366
				return $mode;
367
368
			case self::GS_KEY:
369
				return $clef;
370
		}
371
372
373
		return '';
374
	}
375
376
377
	/**
378
	 * @return array
379
	 * @throws GSStatusException
380
	 */
381
	public function getGSData(): array {
382
		return [
383
			'enabled'     => $this->isGSAvailable(),
384
			'lookup'      => $this->getGSLookup(),
385
			'mockup'      => $this->getGSSMockup(),
386
			self::GS_MODE => $this->config->getSystemValue('gss.mode', ''),
387
			self::GS_KEY  => $this->config->getSystemValue('gss.jwt.key', ''),
388
		];
389
	}
390
391
392
	/**
393
	 * @return array
394
	 */
395
	public function getTrustedDomains(): array {
396
		return array_map(
397
			function(string $address) {
398
				return strtolower($address);
399
			}, $this->config->getSystemValue('trusted_domains', [])
400
		);
401
	}
402
403
404
	/**
405
	 * @return string
406
	 */
407
	public function getLoopbackInstance(): string {
408
		$loopbackCloudId = $this->getAppValue(self::LOOPBACK_TMP_ID);
409
		if ($loopbackCloudId !== '') {
410
			return $loopbackCloudId;
411
		}
412
413
		$loopbackCloudId = $this->getAppValue(self::LOOPBACK_CLOUD_ID);
414
		if ($loopbackCloudId !== '') {
415
			return $loopbackCloudId;
416
		}
417
418
		$cliUrl = $this->getAppValue(self::FORCE_NC_BASE);
419
		if ($cliUrl === '') {
420
			$cliUrl = $this->config->getSystemValue('circles.force_nc_base', '');
421
		}
422
423
		if ($cliUrl === '') {
424
			$cliUrl = $this->config->getSystemValue('overwrite.cli.url', '');
425
		}
426
427
		$loopback = parse_url($cliUrl);
428
		if (!is_array($loopback) || !array_key_exists('host', $loopback)) {
429
			return $cliUrl;
430
		}
431
432
		if (array_key_exists('port', $loopback)) {
433
			$loopbackCloudId = $loopback['host'] . ':' . $loopback['port'];
434
		} else {
435
			$loopbackCloudId = $loopback['host'];
436
		}
437
438
		if (array_key_exists('scheme', $loopback)
439
			&& $this->getAppValue(self::LOOPBACK_TMP_SCHEME) !== $loopback['scheme']) {
440
			$this->setAppValue(self::LOOPBACK_TMP_SCHEME, $loopback['scheme']);
441
		}
442
443
		return $loopbackCloudId;
444
	}
445
446
	/**
447
	 * returns loopback address based on getLoopbackInstance and LOOPBACK_CLOUD_SCHEME
448
	 * should be used to async process
449
	 *
450
	 * @param string $route
451
	 * @param array $args
452
	 *
453
	 * @return string
454
	 */
455
	public function getLoopbackPath(string $route = '', array $args = []): string {
456
		$instance = $this->getLoopbackInstance();
457
		$scheme = $this->getAppValue(self::LOOPBACK_TMP_SCHEME);
458
		if ($scheme === '') {
459
			$scheme = $this->getAppValue(self::LOOPBACK_CLOUD_SCHEME);
460
		}
461
462
		$base = $scheme . '://' . $instance;
463
		if ($route === '') {
464
			return $base;
465
		}
466
467
		return $base . $this->urlGenerator->linkToRoute($route, $args);
468
	}
469
470
471
	/**
472
	 * - must be configured using INTERNAL_CLOUD_ID
473
	 * - returns host+port, does not specify any protocol
474
	 * - used mainly to assign instance and source to a request to local GlobalScale
475
	 * - important only in GlobalScale environment
476
	 *
477
	 * @return string
478
	 *
479
	 */
480
	public function getInternalInstance(): string {
481
		return $this->getAppValue(self::INTERNAL_CLOUD_ID);
482
	}
483
484
485
	/**
486
	 * - must be configured using FRONTAL_CLOUD_ID
487
	 * - returns host+port, does not specify any protocol
488
	 * - used mainly to assign instance and source to a request
489
	 * - important only in remote environment
490
	 *
491
	 * @return string
492
	 */
493
	public function getFrontalInstance(): string {
494
		$frontalCloudId = $this->getAppValue(self::FRONTAL_CLOUD_ID);
495
496
		// using old settings local_cloud_id from NC20, deprecated in NC25
497
		if ($frontalCloudId === '') {
498
			$frontalCloudId = $this->config->getAppValue(Application::APP_ID, 'local_cloud_id', '');
499
			if ($frontalCloudId !== '') {
500
				$this->setAppValue(self::FRONTAL_CLOUD_ID, $frontalCloudId);
501
			}
502
		}
503
504
		return $frontalCloudId;
505
	}
506
507
508
	/**
509
	 * @param int $iface
510
	 *
511
	 * @return string
512
	 */
513
	public function getIfaceInstance(int $iface): string {
514
		switch ($iface) {
515
			case InterfaceService::IFACE0:
516
				return $this->getAppValue(self::IFACE0_CLOUD_ID);
517
			case InterfaceService::IFACE1:
518
				return $this->getAppValue(self::IFACE1_CLOUD_ID);
519
			case InterfaceService::IFACE2:
520
				return $this->getAppValue(self::IFACE2_CLOUD_ID);
521
			case InterfaceService::IFACE3:
522
				return $this->getAppValue(self::IFACE3_CLOUD_ID);
523
			case InterfaceService::IFACE4:
524
				return $this->getAppValue(self::IFACE4_CLOUD_ID);
525
		}
526
527
		return '';
528
	}
529
530
531
	/**
532
	 * @param string $instance
533
	 *
534
	 * @return bool
535
	 */
536
	public function isLocalInstance(string $instance): bool {
537
		if ($instance === '') {
538
			return true;
539
		}
540
541
		$instance = strtolower($instance);
542
		if ($instance === strtolower($this->getInternalInstance())) {
543
			return true;
544
		}
545
546
		if ($instance === strtolower($this->getFrontalInstance())) {
547
			return true;
548
		}
549
550
		if ($instance === strtolower($this->getLoopbackInstance())) {
551
			return true;
552
		}
553
554
		return (in_array($instance, $this->getTrustedDomains()));
555
	}
556
557
558
	/**
559
	 * @param IFederatedUser $federatedUser
560
	 *
561
	 * @return string
562
	 */
563
	public function displayFederatedUser(IFederatedUser $federatedUser): string {
564
		return $federatedUser->getUserId() . $this->displayInstance($federatedUser->getInstance(), true);
565
	}
566
567
	/**
568
	 * @param string $instance
569
	 * @param bool $showAt
570
	 *
571
	 * @return string
572
	 */
573
	public function displayInstance(string $instance, bool $showAt = false): string {
574
		if ($this->isLocalInstance($instance)) {
575
			return '';
576
		}
577
578
		return (($showAt) ? '@' : '') . $instance;
579
	}
580
581
582
	/**
583
	 * - Create route using getLoopbackAddress()
584
	 * - perfect for loopback request.
585
	 *
586
	 * @param NC22Request $request
587
	 * @param string $route
588
	 * @param array $args
589
	 */
590
	public function configureLoopbackRequest(
591
		NC22Request $request,
592
		string $route = '',
593
		array $args = []
594
	): void {
595
		$this->configureRequest($request);
596
		$request->setVerifyPeer(false);
597
		$request->basedOnUrl($this->getLoopbackPath($route, $args));
598
	}
599
600
601
	/**
602
	 * @param NC22Request $request
603
	 */
604
	public function configureRequest(NC22Request $request): void {
605
		$request->setVerifyPeer($this->getAppValue(ConfigService::SELF_SIGNED_CERT) !== '1');
606
		$request->setProtocols(['https', 'http']);
607
		$request->setHttpErrorsAllowed(true);
608
		$request->setLocalAddressAllowed(true);
609
		$request->setFollowLocation(true);
610
		$request->setTimeout(5);
611
	}
612
613
}
614
615