Completed
Push — master ( d5ab1b...abc532 )
by Maxence
02:50 queued 11s
created

ConfigService::getLocalInstance()   B

Complexity

Conditions 7
Paths 6

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 27
rs 8.5546
c 0
b 0
f 0
cc 7
nc 6
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\NC19Request;
30
use daita\MySmallPhpTools\Traits\TStringTools;
31
use OCA\Circles\Exceptions\GSStatusException;
32
use OCA\Circles\Model\Circle;
33
use OCP\IConfig;
34
use OCP\IRequest;
35
use OCP\IURLGenerator;
36
use OCP\PreConditionNotMetException;
37
use OCP\Util;
38
39
class ConfigService {
40
41
42
	use TStringTools;
43
44
45
	const CIRCLES_ALLOW_CIRCLES = 'allow_circles';
46
	const CIRCLES_CONTACT_BACKEND = 'contact_backend';
47
	const CIRCLES_STILL_FRONTEND = 'still_frontend';
48
	const CIRCLES_SWAP_TO_TEAMS = 'swap_to_teams';
49
	const CIRCLES_ALLOW_FEDERATED_CIRCLES = 'allow_federated';
50
	const CIRCLES_GS_ENABLED = 'gs_enabled';
51
	const CIRCLES_MEMBERS_LIMIT = 'members_limit';
52
	const CIRCLES_ACCOUNTS_ONLY = 'accounts_only';
53
	const CIRCLES_ALLOW_LINKED_GROUPS = 'allow_linked_groups';
54
	const CIRCLES_ALLOW_NON_SSL_LINKS = 'allow_non_ssl_links';
55
	const CIRCLES_NON_SSL_LOCAL = 'local_is_non_ssl';
56
	const CIRCLES_SELF_SIGNED = 'self_signed_cert';
57
	const LOCAL_CLOUD_ID = 'local_cloud_id';
58
	const CIRCLES_LOCAL_GSKEY = 'local_gskey';
59
	const CIRCLES_ACTIVITY_ON_CREATION = 'creation_activity';
60
	const CIRCLES_SKIP_INVITATION_STEP = 'skip_invitation_to_closed_circles';
61
	const CIRCLES_SEARCH_FROM_COLLABORATOR = 'search_from_collaborator';
62
	const CIRCLES_TEST_ASYNC_LOCK = 'test_async_lock';
63
	const CIRCLES_TEST_ASYNC_INIT = 'test_async_init';
64
	const CIRCLES_TEST_ASYNC_HAND = 'test_async_hand';
65
	const CIRCLES_TEST_ASYNC_COUNT = 'test_async_count';
66
	const FORCE_NC_BASE = 'force_nc_base';
67
	const TEST_NC_BASE = 'test_nc_base';
68
69
	const GS_ENABLED = 'enabled';
70
	const GS_MODE = 'mode';
71
	const GS_KEY = 'key';
72
	const GS_LOOKUP = 'lookup';
73
74
	const GS_LOOKUP_INSTANCES = '/instances';
75
	const GS_LOOKUP_USERS = '/users';
76
77
78
	private $defaults = [
79
		self::CIRCLES_ALLOW_CIRCLES            => Circle::CIRCLES_ALL,
80
		self::CIRCLES_CONTACT_BACKEND          => '0',
81
		self::CIRCLES_STILL_FRONTEND           => '0',
82
		self::CIRCLES_TEST_ASYNC_INIT          => '0',
83
		self::CIRCLES_SWAP_TO_TEAMS            => '0',
84
		self::CIRCLES_ACCOUNTS_ONLY            => '0',
85
		self::CIRCLES_MEMBERS_LIMIT            => '50',
86
		self::CIRCLES_ALLOW_LINKED_GROUPS      => '0',
87
		self::CIRCLES_ALLOW_FEDERATED_CIRCLES  => '0',
88
		self::CIRCLES_GS_ENABLED               => '0',
89
		self::CIRCLES_LOCAL_GSKEY              => '',
90
		self::CIRCLES_ALLOW_NON_SSL_LINKS      => '0',
91
		self::CIRCLES_NON_SSL_LOCAL            => '0',
92
		self::CIRCLES_SELF_SIGNED              => '0',
93
		self::LOCAL_CLOUD_ID                   => '',
94
		self::FORCE_NC_BASE                    => '',
95
		self::TEST_NC_BASE                     => '',
96
		self::CIRCLES_ACTIVITY_ON_CREATION     => '1',
97
		self::CIRCLES_SKIP_INVITATION_STEP     => '0',
98
		self::CIRCLES_SEARCH_FROM_COLLABORATOR => '0'
99
	];
100
101
	/** @var string */
102
	private $appName;
103
104
	/** @var IConfig */
105
	private $config;
106
107
	/** @var string */
108
	private $userId;
109
110
	/** @var IRequest */
111
	private $request;
112
113
	/** @var IURLGenerator */
114
	private $urlGenerator;
115
116
	/** @var MiscService */
117
	private $miscService;
118
119
	/** @var int */
120
	private $allowedCircle = -1;
121
122
	/** @var int */
123
	private $allowedLinkedGroups = -1;
124
125
	/** @var int */
126
	private $allowedFederatedCircles = -1;
127
128
	/** @var int */
129
	private $allowedNonSSLLinks = -1;
130
131
	/** @var int */
132
	private $localNonSSL = -1;
133
134
	/**
135
	 * ConfigService constructor.
136
	 *
137
	 * @param string $appName
138
	 * @param IConfig $config
139
	 * @param IRequest $request
140
	 * @param string $userId
141
	 * @param IURLGenerator $urlGenerator
142
	 * @param MiscService $miscService
143
	 */
144
	public function __construct(
145
		$appName, IConfig $config, IRequest $request, $userId, IURLGenerator $urlGenerator,
146
		MiscService $miscService
147
	) {
148
		$this->appName = $appName;
149
		$this->config = $config;
150
		$this->request = $request;
151
		$this->userId = $userId;
152
		$this->urlGenerator = $urlGenerator;
153
		$this->miscService = $miscService;
154
	}
155
156
157
	/**
158
	 * @return string
159
	 * @deprecated
160
	 */
161
	public function getLocalAddress() {
162
		return (($this->isLocalNonSSL()) ? 'http://' : '')
163
			   . $this->request->getServerHost();
164
	}
165
166
167
	/**
168
	 * returns if this type of circle is allowed by the current configuration.
169
	 *
170
	 * @param $type
171
	 *
172
	 * @return int
173
	 */
174
	public function isCircleAllowed($type) {
175
		if ($this->allowedCircle === -1) {
176
			$this->allowedCircle = (int)$this->getAppValue(self::CIRCLES_ALLOW_CIRCLES);
177
		}
178
179
		return ((int)$type & (int)$this->allowedCircle);
180
	}
181
182
183
	/**
184
	 * @return bool
185
	 * @throws GSStatusException
186
	 */
187
	public function isLinkedGroupsAllowed() {
188
		if ($this->allowedLinkedGroups === -1) {
189
			$allowed = ($this->getAppValue(self::CIRCLES_ALLOW_LINKED_GROUPS) === '1'
190
						&& !$this->getGSStatus(self::GS_ENABLED));
191
			$this->allowedLinkedGroups = ($allowed) ? 1 : 0;
192
		}
193
194
		return ($this->allowedLinkedGroups === 1);
195
	}
196
197
198
	/**
199
	 * @return bool
200
	 */
201
	public function isFederatedCirclesAllowed() {
202
		if ($this->allowedFederatedCircles === -1) {
203
			$this->allowedFederatedCircles =
204
				(int)$this->getAppValue(self::CIRCLES_ALLOW_FEDERATED_CIRCLES);
205
		}
206
207
		return ($this->allowedFederatedCircles === 1);
208
	}
209
210
	/**
211
	 * @return bool
212
	 */
213
	public function isInvitationSkipped() {
214
		return (int)$this->getAppValue(self::CIRCLES_SKIP_INVITATION_STEP) === 1;
215
	}
216
217
	/**
218
	 * @return bool
219
	 */
220
	public function isLocalNonSSL() {
221
		if ($this->localNonSSL === -1) {
222
			$this->localNonSSL =
223
				(int)$this->getAppValue(self::CIRCLES_NON_SSL_LOCAL);
224
		}
225
226
		return ($this->localNonSSL === 1);
227
	}
228
229
230
	/**
231
	 * @return bool
232
	 */
233
	public function isNonSSLLinksAllowed() {
234
		if ($this->allowedNonSSLLinks === -1) {
235
			$this->allowedNonSSLLinks =
236
				(int)$this->getAppValue(self::CIRCLES_ALLOW_NON_SSL_LINKS);
237
		}
238
239
		return ($this->allowedNonSSLLinks === 1);
240
	}
241
242
243
	/**
244
	 * @param string $remote
245
	 *
246
	 * @return string
247
	 */
248
	public function generateRemoteHost($remote) {
249
		if ((!$this->isNonSSLLinksAllowed() || strpos($remote, 'http://') !== 0)
250
			&& strpos($remote, 'https://') !== 0
251
		) {
252
			$remote = 'https://' . $remote;
253
		}
254
255
		return rtrim($remote, '/');
256
	}
257
258
259
	/**
260
	 * Get a value by key
261
	 *
262
	 * @param string $key
263
	 *
264
	 * @return string
265
	 */
266
	public function getCoreValue($key) {
267
		$defaultValue = null;
268
269
		return $this->config->getAppValue('core', $key, $defaultValue);
270
	}
271
272
	/**
273
	 * Get a value by key
274
	 *
275
	 * @param string $key
276
	 *
277
	 * @return string
278
	 */
279
	public function getSystemValue($key) {
280
		$defaultValue = null;
281
282
		return $this->config->getSystemValue($key, $defaultValue);
283
	}
284
285
286
	/**
287
	 * Get available hosts
288
	 *
289
	 * @return array
290
	 */
291
	public function getAvailableHosts(): array {
292
		return $this->config->getSystemValue('trusted_domains', []);
293
	}
294
295
296
	/**
297
	 * Get a value by key
298
	 *
299
	 * @param string $key
300
	 *
301
	 * @return string
302
	 */
303
	public function getAppValue($key) {
304
		$defaultValue = null;
305
306
		if (array_key_exists($key, $this->defaults)) {
307
			$defaultValue = $this->defaults[$key];
308
		}
309
310
		return $this->config->getAppValue($this->appName, $key, $defaultValue);
311
	}
312
313
	/**
314
	 * Set a value by key
315
	 *
316
	 * @param string $key
317
	 * @param string $value
318
	 *
319
	 * @return void
320
	 */
321
	public function setAppValue($key, $value) {
322
		$this->config->setAppValue($this->appName, $key, $value);
323
	}
324
325
	/**
326
	 * remove a key
327
	 *
328
	 * @param string $key
329
	 *
330
	 * @return string
331
	 */
332
	public function deleteAppValue($key) {
333
		return $this->config->deleteAppValue($this->appName, $key);
334
	}
335
336
	/**
337
	 * Get a user value by key
338
	 *
339
	 * @param string $key
340
	 *
341
	 * @return string
342
	 */
343
	public function getUserValue($key) {
344
		return $this->config->getUserValue($this->userId, $this->appName, $key);
345
	}
346
347
	/**
348
	 * Set a user value by key
349
	 *
350
	 * @param string $key
351
	 * @param string $value
352
	 *
353
	 * @return string
354
	 * @throws PreConditionNotMetException
355
	 */
356
	public function setUserValue($key, $value) {
357
		return $this->config->setUserValue($this->userId, $this->appName, $key, $value);
358
	}
359
360
361
	/**
362
	 * Get a user value by key and user
363
	 *
364
	 * @param string $userId
365
	 * @param string $key
366
	 *
367
	 * @param string $default
368
	 *
369
	 * @return string
370
	 */
371
	public function getCoreValueForUser($userId, $key, $default = '') {
372
		return $this->config->getUserValue($userId, 'core', $key, $default);
373
	}
374
375
376
	/**
377
	 * Get a user value by key and user
378
	 *
379
	 * @param string $userId
380
	 * @param string $key
381
	 *
382
	 * @return string
383
	 */
384
	public function getValueForUser($userId, $key) {
385
		return $this->config->getUserValue($userId, $this->appName, $key);
386
	}
387
388
	/**
389
	 * Set a user value by key
390
	 *
391
	 * @param string $userId
392
	 * @param string $key
393
	 * @param string $value
394
	 *
395
	 * @return string
396
	 * @throws PreConditionNotMetException
397
	 */
398
	public function setValueForUser($userId, $key, $value) {
399
		return $this->config->setUserValue($userId, $this->appName, $key, $value);
400
	}
401
402
	/**
403
	 * return the cloud version.
404
	 * if $complete is true, return a string x.y.z
405
	 *
406
	 * @param boolean $complete
407
	 *
408
	 * @return string|integer
409
	 */
410
	public function getCloudVersion($complete = false) {
411
		$ver = Util::getVersion();
412
413
		if ($complete) {
414
			return implode('.', $ver);
415
		}
416
417
		return $ver[0];
418
	}
419
420
421
	/**
422
	 * @return bool
423
	 */
424
	public function isAccountOnly() {
425
		return ($this->getAppValue(self::CIRCLES_ACCOUNTS_ONLY) === '1');
426
	}
427
428
429
	/**
430
	 * @return bool
431
	 */
432
	public function isContactsBackend(): bool {
433
		return ($this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '0'
434
				&& $this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND) !== '');
435
	}
436
437
438
	/**
439
	 * @return int
440
	 */
441
	public function contactsBackendType(): int {
442
		return (int)$this->getAppValue(ConfigService::CIRCLES_CONTACT_BACKEND);
443
	}
444
445
446
	/**
447
	 * @return bool
448
	 */
449
	public function stillFrontEnd(): bool {
450
		if (!$this->isContactsBackend()) {
451
			return true;
452
		}
453
454
		if ($this->getAppValue(self::CIRCLES_STILL_FRONTEND) === '1') {
0 ignored issues
show
Unused Code introduced by
This if statement, and the following return statement can be replaced with return $this->getAppValu...TILL_FRONTEND) === '1';.
Loading history...
455
			return true;
456
		}
457
458
		return false;
459
	}
460
461
462
	/**
463
	 * should the password for a mail share be send to the recipient
464
	 *
465
	 * @return bool
466
	 */
467
	public function sendPasswordByMail() {
468
		if ($this->isContactsBackend()) {
469
			return false;
470
		}
471
472
		return ($this->config->getAppValue('sharebymail', 'sendpasswordmail', 'yes') === 'yes');
473
	}
474
475
	/**
476
	 * do we require a share by mail to be password protected
477
	 *
478
	 * @param Circle $circle
479
	 *
480
	 * @return bool
481
	 */
482
	public function enforcePasswordProtection(Circle $circle) {
483
		if ($this->isContactsBackend()) {
484
			return false;
485
		}
486
487
		if ($circle->getSetting('password_enforcement') === 'true') {
488
			return true;
489
		}
490
491
		return ($this->config->getAppValue('sharebymail', 'enforcePasswordProtection', 'no') === 'yes');
492
	}
493
494
495
	/**
496
	 * @param string $type
497
	 *
498
	 * @return array|bool|mixed
499
	 * @throws GSStatusException
500
	 */
501
	public function getGSStatus(string $type = '') {
502
		$enabled = $this->config->getSystemValueBool('gs.enabled', false);
503
		$lookup = $this->config->getSystemValue('lookup_server', '');
504
505
		if ($lookup === '' || !$enabled) {
506
			if ($type === self::GS_ENABLED) {
507
				return false;
508
			}
509
510
			throw new GSStatusException('GS and lookup are not configured : ' . $lookup . ', ' . $enabled);
511
		}
512
513
		$clef = $this->config->getSystemValue('gss.jwt.key', '');
514
		$mode = $this->config->getSystemValue('gss.mode', '');
515
516
		switch ($type) {
517
			case self::GS_ENABLED:
518
				return $enabled;
519
520
			case self::GS_MODE:
521
				return $mode;
522
523
			case self::GS_KEY:
524
				return $clef;
525
526
			case self::GS_LOOKUP:
527
				return $lookup;
528
		}
529
530
		return [
531
			self::GS_ENABLED => $enabled,
532
			self::GS_LOOKUP  => $lookup,
533
			self::GS_MODE    => $clef,
534
			self::GS_KEY     => $mode,
535
		];
536
	}
537
538
539
	/**
540
	 * @return array
541
	 */
542
	public function getTrustedDomains(): array {
543
		return array_values($this->config->getSystemValue('trusted_domains', []));
544
	}
545
546
547
	/**
548
	 * - returns host+port, does not specify any protocol
549
	 * - can be forced using LOCAL_CLOUD_ID
550
	 * - use 'overwrite.cli.url'
551
	 * - can use the first entry from trusted_domains is LOCAL_CLOUD_ID = 'use-trusted-domain'
552
	 * - used mainly to assign instance and source to a request
553
	 * - important only in remote environment; can be totally random in a jailed environment
554
	 *
555
	 * @return string
556
	 */
557
	public function getLocalInstance(): string {
558
		$localCloudId = $this->getAppValue(self::LOCAL_CLOUD_ID);
559
		if ($localCloudId === '') {
560
			$cliUrl = $this->config->getSystemValue('overwrite.cli.url', '');
561
			$local = parse_url($cliUrl);
562
			if (!is_array($local) || !array_key_exists('host', $local)) {
563
				if ($cliUrl !== '') {
564
					return $cliUrl;
565
				}
566
567
				$randomCloudId = $this->uuid();
568
				$this->setAppValue(self::LOCAL_CLOUD_ID, $randomCloudId);
569
570
				return $randomCloudId;
571
			}
572
573
			if (array_key_exists('port', $local)) {
574
				return $local['host'] . ':' . $local['port'];
575
			} else {
576
				return $local['host'];
577
			}
578
		} else if ($localCloudId === 'use-trusted-domain') {
579
			return $this->getTrustedDomains()[0];
580
		} else {
581
			return $localCloudId;
582
		}
583
	}
584
585
586
	/**
587
	 * @param string $instance
588
	 *
589
	 * @return bool
590
	 */
591
	public function isLocalInstance(string $instance): bool {
592
		if ($instance === $this->getLocalInstance()) {
593
			return true;
594
		}
595
596
		if ($this->getAppValue(self::LOCAL_CLOUD_ID) === 'use-trusted-domain') {
597
			return (in_array($instance, $this->getTrustedDomains()));
598
		}
599
600
		return false;
601
	}
602
603
604
	/**
605
	 * @param NC19Request $request
606
	 * @param string $routeName
607
	 * @param array $args
608
	 */
609
	public function configureRequest(NC19Request $request, string $routeName = '', array $args = []) {
610
		$this->configureRequestAddress($request, $routeName, $args);
611
612
		if ($this->getForcedNcBase() === '') {
613
			$request->setProtocols(['https', 'http']);
614
		}
615
616
		$request->setVerifyPeer($this->getAppValue(ConfigService::CIRCLES_SELF_SIGNED) !== '1');
617
		$request->setLocalAddressAllowed(true);
618
	}
619
620
	/**
621
	 * - Create route using overwrite.cli.url.
622
	 * - can be forced using FORCE_NC_BASE or TEST_BC_BASE (temporary)
623
	 * - can also be overwritten in config/config.php: 'circles.force_nc_base'
624
	 * - perfect for loopback request.
625
	 *
626
	 * @param NC19Request $request
627
	 * @param string $routeName
628
	 * @param array $args
629
	 *
630
	 * @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...
631
	 */
632
	private function configureRequestAddress(NC19Request $request, string $routeName, array $args = []) {
633
		if ($routeName === '') {
634
			return;
635
		}
636
637
		$ncBase = $this->getForcedNcBase();
638
		if ($ncBase !== '') {
639
			$absolute = $this->cleanLinkToRoute($ncBase, $routeName, $args);
640
		} else {
641
			$absolute = $this->urlGenerator->linkToRouteAbsolute($routeName, $args);
642
		}
643
644
		$request->basedOnUrl($absolute);
645
	}
646
647
648
	/**
649
	 * - return force_nc_base from config/config.php, then from FORCE_NC_BASE.
650
	 *
651
	 * @return string
652
	 */
653
	private function getForcedNcBase(): string {
654
		if ($this->getAppValue(self::TEST_NC_BASE) !== '') {
655
			return $this->getAppValue(self::TEST_NC_BASE);
656
		}
657
658
		$fromConfig = $this->config->getSystemValue('circles.force_nc_base', '');
659
		if ($fromConfig !== '') {
660
			return $fromConfig;
661
		}
662
663
		return $this->getAppValue(self::FORCE_NC_BASE);
664
	}
665
666
667
	/**
668
	 * sometimes, linkToRoute will include the base path to the nextcloud which will be duplicate with ncBase
669
	 *
670
	 * @param string $ncBase
671
	 * @param string $routeName
672
	 * @param array $args
673
	 *
674
	 * @return string
675
	 */
676
	private function cleanLinkToRoute(string $ncBase, string $routeName, array $args): string {
677
		$link = $this->urlGenerator->linkToRoute($routeName, $args);
678
		$forcedPath = rtrim(parse_url($ncBase, PHP_URL_PATH), '/');
679
680
		if ($forcedPath !== '' && strpos($link, $forcedPath) === 0) {
681
			$ncBase = substr($ncBase, 0, -strlen($forcedPath));
682
		}
683
684
		return rtrim($ncBase, '/') . $link;
685
	}
686
687
}
688
689