Completed
Pull Request — master (#597)
by Maxence
07:37 queued 05:01
created

ConfigService::getInstancePathBasedOnHost()   A

Complexity

Conditions 1
Paths 1

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