Completed
Push — master ( d2af4c...350597 )
by Julius
09:27 queued 11s
created

FederationService::getRemoteCollaboraURL()   B

Complexity

Conditions 6
Paths 16

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 42

Importance

Changes 0
Metric Value
dl 0
loc 25
ccs 0
cts 24
cp 0
rs 8.8977
c 0
b 0
f 0
cc 6
nc 16
nop 1
crap 42
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
		if ($remoteCollabora = $this->cache->get('richdocuments_remote/' . $remote)) {
82
			return $remoteCollabora;
83
		}
84
		try {
85
			$client = $this->clientService->newClient();
86
			$response = $client->get($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation?format=json', ['timeout' => 5]);
87
			$data = \json_decode($response->getBody(), true);
88
			$remoteCollabora = $data['ocs']['data']['wopi_url'];
89
			$this->cache->set('richdocuments_remote/' . $remote, $remoteCollabora, 3600);
90
			return $remoteCollabora;
91
		} 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...
92
			$this->logger->info('Unable to determine collabora URL of remote server ' . $remote, ['exception' => $e]);
93
			$this->cache->set('richdocuments_remote/' . $remote, '', 300);
94
		}
95
		return '';
96
	}
97
98
	public function isTrustedRemote($domainWithPort) {
99
		if (strpos($domainWithPort, 'http://') === 0 || strpos($domainWithPort, 'https://') === 0) {
100
			$port = parse_url($domainWithPort, PHP_URL_PORT);
101
			$domainWithPort = parse_url($domainWithPort, PHP_URL_HOST) . ($port ? ':' . $port : '');
102
		}
103
104
		if ($this->trustedServers !== null && $this->trustedServers->isTrustedServer($domainWithPort)) {
105
			return true;
106
		}
107
108
		$domain = $this->getDomainWithoutPort($domainWithPort);
109
110
		$trustedList = $this->config->getSystemValue('gs.trustedHosts', []);
111
		if (!is_array($trustedList)) {
112
			return false;
113
		}
114
115
		foreach ($trustedList as $trusted) {
116
			if (!is_string($trusted)) {
117
				break;
118
			}
119
			$regex = '/^' . implode('[-\.a-zA-Z0-9]*', array_map(function ($v) {
120
					return preg_quote($v, '/');
121
				}, explode('*', $trusted))) . '$/i';
122
			if (preg_match($regex, $domain) || preg_match($regex, $domainWithPort)) {
123
				return true;
124
			}
125
		}
126
127
		return false;
128
	}
129
130
	/**
131
	 * Strips a potential port from a domain (in format domain:port)
132
	 * @param string $host
133
	 * @return string $host without appended port
134
	 */
135
	private function getDomainWithoutPort($host) {
136
		$pos = strrpos($host, ':');
137
		if ($pos !== false) {
138
			$port = substr($host, $pos + 1);
139
			if (is_numeric($port)) {
140
				$host = substr($host, 0, $pos);
141
			}
142
		}
143
		return $host;
144
	}
145
146
	public function getRemoteDirectUrl($remote, $shareToken, $filePath) {
147
		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...
148
			return '';
149
		}
150
		try {
151
			$client = $this->clientService->newClient();
152
			$response = $client->post($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation/direct?format=json', [
153
				'timeout' => 5,
154
				'body' => [
155
					'shareToken' => $shareToken,
156
					'filePath' => $filePath
157
				]
158
			]);
159
			$data = \json_decode($response->getBody(), true);
160
			return $data['ocs']['data'];
161
		} 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...
162
			$this->logger->info('Unable to determine collabora URL of remote server ' . $remote, ['exception' => $e]);
163
		}
164
		return null;
165
	}
166
167
	public function getRemoteFileDetails($remote, $remoteToken) {
168
		if (!$this->isTrustedRemote($remote)) {
169
			$this->logger->info('Unable to determine collabora URL of remote server ' . $remote . ' - Remote is not a trusted server');
170
			return null;
171
		}
172
		try {
173
			$client = $this->clientService->newClient();
174
			$response = $client->post($remote . '/ocs/v2.php/apps/richdocuments/api/v1/federation?format=json', [
175
				'timeout' => 5,
176
				'body' => [
177
					'token' => $remoteToken
178
				]
179
			]);
180
			$responseBody = $response->getBody();
181
			$data = \json_decode($responseBody, true, 512);
182
			$this->logger->debug('Reveived remote file details for ' . $remoteToken . ' from ' . $remote . ': ' . $responseBody);
183
			return $data['ocs']['data'];
184
		} 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...
185
			$this->logger->error('Unable to fetch remote file details for ' . $remoteToken . ' from ' . $remote, ['exception' => $e]);
186
		}
187
		return null;
188
	}
189
190
	/**
191
	 * @param File $item
192
	 * @return string|null
193
	 * @throws NotFoundException
194
	 * @throws InvalidPathException
195
	 */
196
	public function getRemoteRedirectURL(File $item, $direct = null) {
197
		if ($item->getStorage()->instanceOfStorage(SharingExternalStorage::class)) {
198
			$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...
199
			$remoteCollabora = $this->getRemoteCollaboraURL($remote);
200
			if ($remoteCollabora !== '') {
201
				if ($direct === null) {
202
					$wopi = $this->tokenManager->getRemoteToken($item);
203
				} else {
204
					$wopi = $this->tokenManager->getRemoteTokenFromDirect($item, $direct->getUid());
205
				}
206
				$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...
207
					'&remoteServer=' . $wopi->getServerHost() .
208
					'&remoteServerToken=' . $wopi->getToken();
209
				if ($item->getInternalPath() !== '') {
210
					$url .= '&filePath=' . $item->getInternalPath();
211
				}
212
				return $url;
213
			}
214
			throw new NotFoundException('Failed to connect to remote collabora instance for ' . $item->getId());
215
		}
216
		return null;
217
	}
218
}
219