FederationService   A
last analyzed

Complexity

Total Complexity 33

Size/Duplication

Total Lines 179
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 12

Test Coverage

Coverage 0%

Importance

Changes 0
Metric Value
wmc 33
lcom 1
cbo 12
dl 0
loc 179
ccs 0
cts 92
cp 0
rs 9.76
c 0
b 0
f 0

7 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 10 2
B getRemoteCollaboraURL() 0 26 6
B isTrustedRemote() 0 31 11
A getDomainWithoutPort() 0 10 3
A getRemoteDirectUrl() 0 20 3
A getRemoteFileDetails() 0 22 3
A getRemoteRedirectURL() 0 22 5
1
<?php
2
/**
3
 * @copyright Copyright (c) 2019 Julius Härtl <[email protected]>
4
 *
5
 * @author Julius Härtl <[email protected]>
6
 *
7
 * @license GNU AGPL version 3 or any later version
8
 *
9
 * This program is free software: you can redistribute it and/or modify
10
 * it under the terms of the GNU Affero General Public License as
11
 * published by the Free Software Foundation, either version 3 of the
12
 * License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
 * GNU Affero General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU Affero General Public License
20
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
21
 *
22
 */
23
24
namespace OCA\Richdocuments\Service;
25
26
27
use OCA\Federation\TrustedServers;
28
use OCA\Files_Sharing\External\Storage as SharingExternalStorage;
29
use OCA\Richdocuments\TokenManager;
30
use OCP\AppFramework\Http\RedirectResponse;
31
use OCP\AppFramework\QueryException;
32
use OCP\Files\File;
33
use OCP\Files\InvalidPathException;
34
use OCP\Files\NotFoundException;
35
use OCP\Http\Client\IClientService;
36
use OCP\ICache;
37
use OCP\ICacheFactory;
38
use OCP\IConfig;
39
use OCP\ILogger;
40
41
class FederationService {
42
43
	/** @var ICache */
44
	private $cache;
45
	/** @var IClientService */
46
	private $clientService;
47
	/** @var ILogger  */
48
	private $logger;
49
	/** @var TrustedServers */
50
	private $trustedServers;
51
	/** @var IConfig */
52
	private $config;
53
	/** @var TokenManager */
54
	private $tokenManager;
55
56
	public function __construct(ICacheFactory $cacheFactory, IClientService $clientService, ILogger $logger, TokenManager $tokenManager, IConfig $config) {
57
		$this->cache = $cacheFactory->createDistributed('richdocuments_remote/');
58
		$this->clientService = $clientService;
59
		$this->logger = $logger;
60
		$this->tokenManager = $tokenManager;
61
		$this->config = $config;
62
		try {
63
			$this->trustedServers = \OC::$server->query( \OCA\Federation\TrustedServers::class);
64
		} catch (QueryException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
65
	}
66
67
	/**
68
	 * @param $remote
69
	 * @return string
70
	 * @throws \Exception
71
	 */
72
	public function getRemoteCollaboraURL($remote) {
73
		// If no protocol is provided we default to https
74
		if (strpos($remote, 'http://') !== 0 && strpos($remote, 'https://') !== 0) {
75
			$remote = 'https://' . $remote;
76
		}
77
78
		if (!$this->isTrustedRemote($remote)) {
79
			throw new \Exception('Unable to determine collabora URL of remote server ' . $remote . ' - Remote is not a trusted server');
80
		}
81
		$remoteCollabora = $this->cache->get('richdocuments_remote/' . $remote);
82
		if ($remoteCollabora !== null) {
83
			return $remoteCollabora;
84
		}
85
		try {
86
			$client = $this->clientService->newClient();
87
			$response = $client->get($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation?format=json', ['timeout' => 5]);
88
			$data = \json_decode($response->getBody(), true);
89
			$remoteCollabora = $data['ocs']['data']['wopi_url'];
90
			$this->cache->set('richdocuments_remote/' . $remote, $remoteCollabora, 3600);
91
			return $remoteCollabora;
92
		} catch (\Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable 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...
93
			$this->logger->info('Unable to determine collabora URL of remote server ' . $remote, ['exception' => $e]);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\ILogger::info() has been deprecated with message: 20.0.0 use \Psr\Log\LoggerInterface::info

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
94
			$this->cache->set('richdocuments_remote/' . $remote, '', 300);
95
		}
96
		return '';
97
	}
98
99
	public function isTrustedRemote($domainWithPort) {
100
		if (strpos($domainWithPort, 'http://') === 0 || strpos($domainWithPort, 'https://') === 0) {
101
			$port = parse_url($domainWithPort, PHP_URL_PORT);
102
			$domainWithPort = parse_url($domainWithPort, PHP_URL_HOST) . ($port ? ':' . $port : '');
103
		}
104
105
		if ($this->trustedServers !== null && $this->trustedServers->isTrustedServer($domainWithPort)) {
106
			return true;
107
		}
108
109
		$domain = $this->getDomainWithoutPort($domainWithPort);
110
111
		$trustedList = $this->config->getSystemValue('gs.trustedHosts', []);
112
		if (!is_array($trustedList)) {
113
			return false;
114
		}
115
116
		foreach ($trustedList as $trusted) {
117
			if (!is_string($trusted)) {
118
				break;
119
			}
120
			$regex = '/^' . implode('[-\.a-zA-Z0-9]*', array_map(function ($v) {
121
					return preg_quote($v, '/');
122
				}, explode('*', $trusted))) . '$/i';
123
			if (preg_match($regex, $domain) || preg_match($regex, $domainWithPort)) {
124
				return true;
125
			}
126
		}
127
128
		return false;
129
	}
130
131
	/**
132
	 * Strips a potential port from a domain (in format domain:port)
133
	 * @param string $host
134
	 * @return string $host without appended port
135
	 */
136
	private function getDomainWithoutPort($host) {
137
		$pos = strrpos($host, ':');
138
		if ($pos !== false) {
139
			$port = substr($host, $pos + 1);
140
			if (is_numeric($port)) {
141
				$host = substr($host, 0, $pos);
142
			}
143
		}
144
		return $host;
145
	}
146
147
	public function getRemoteDirectUrl($remote, $shareToken, $filePath) {
148
		if ($this->getRemoteCollaboraURL() === '') {
0 ignored issues
show
Bug introduced by
The call to getRemoteCollaboraURL() misses a required argument $remote.

This check looks for function calls that miss required arguments.

Loading history...
149
			return '';
150
		}
151
		try {
152
			$client = $this->clientService->newClient();
153
			$response = $client->post($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation/direct?format=json', [
154
				'timeout' => 5,
155
				'body' => [
156
					'shareToken' => $shareToken,
157
					'filePath' => $filePath
158
				]
159
			]);
160
			$data = \json_decode($response->getBody(), true);
161
			return $data['ocs']['data'];
162
		} catch (\Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable 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...
163
			$this->logger->info('Unable to determine collabora URL of remote server ' . $remote, ['exception' => $e]);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\ILogger::info() has been deprecated with message: 20.0.0 use \Psr\Log\LoggerInterface::info

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
164
		}
165
		return null;
166
	}
167
168
	public function getRemoteFileDetails($remote, $remoteToken) {
169
		if (!$this->isTrustedRemote($remote)) {
170
			$this->logger->info('Unable to determine collabora URL of remote server ' . $remote . ' - Remote is not a trusted server');
0 ignored issues
show
Deprecated Code introduced by
The method OCP\ILogger::info() has been deprecated with message: 20.0.0 use \Psr\Log\LoggerInterface::info

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
171
			return null;
172
		}
173
		try {
174
			$client = $this->clientService->newClient();
175
			$response = $client->post($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation?format=json', [
176
				'timeout' => 5,
177
				'body' => [
178
					'token' => $remoteToken
179
				]
180
			]);
181
			$responseBody = $response->getBody();
182
			$data = \json_decode($responseBody, true, 512);
183
			$this->logger->debug('Reveived remote file details for ' . $remoteToken . ' from ' . $remote . ': ' . $responseBody);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\ILogger::debug() has been deprecated with message: 20.0.0 use \Psr\Log\LoggerInterface::debug

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
184
			return $data['ocs']['data'];
185
		} catch (\Throwable $e) {
0 ignored issues
show
Bug introduced by
The class Throwable 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...
186
			$this->logger->error('Unable to fetch remote file details for ' . $remoteToken . ' from ' . $remote, ['exception' => $e]);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\ILogger::error() has been deprecated with message: 20.0.0 use \Psr\Log\LoggerInterface::error

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
187
		}
188
		return null;
189
	}
190
191
	/**
192
	 * @param File $item
193
	 * @return string|null
194
	 * @throws NotFoundException
195
	 * @throws InvalidPathException
196
	 */
197
	public function getRemoteRedirectURL(File $item, $direct = null) {
198
		if ($item->getStorage()->instanceOfStorage(SharingExternalStorage::class)) {
199
			$remote = $item->getStorage()->getRemote();
0 ignored issues
show
Bug introduced by
The method getRemote() does not seem to exist on object<OCP\Files\Storage>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
200
			$remoteCollabora = $this->getRemoteCollaboraURL($remote);
201
			if ($remoteCollabora !== '') {
202
				if ($direct === null) {
203
					$wopi = $this->tokenManager->getRemoteToken($item);
204
				} else {
205
					$wopi = $this->tokenManager->getRemoteTokenFromDirect($item, $direct->getUid());
206
				}
207
				$url = rtrim($remote, '/') . '/index.php/apps/richdocuments/remote?shareToken=' . $item->getStorage()->getToken() .
0 ignored issues
show
Bug introduced by
The method getToken() does not seem to exist on object<OCP\Files\Storage>.

This check looks for calls to methods that do not seem to exist on a given type. It looks for the method on the type itself as well as in inherited classes or implemented interfaces.

This is most likely a typographical error or the method has been renamed.

Loading history...
208
					'&remoteServer=' . $wopi->getServerHost() .
209
					'&remoteServerToken=' . $wopi->getToken();
210
				if ($item->getInternalPath() !== '') {
211
					$url .= '&filePath=' . $item->getInternalPath();
212
				}
213
				return $url;
214
			}
215
			throw new NotFoundException('Failed to connect to remote collabora instance for ' . $item->getId());
216
		}
217
		return null;
218
	}
219
}
220