Completed
Pull Request — master (#32303)
by Victor
11:09
created

Notifications::sendRemoteShare()   A

Complexity

Conditions 5
Paths 6

Size

Total Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
nc 6
nop 6
dl 0
loc 27
rs 9.1768
c 0
b 0
f 0
1
<?php
2
/**
3
 * @author Björn Schießle <[email protected]>
4
 * @author Joas Schilling <[email protected]>
5
 * @author Lukas Reschke <[email protected]>
6
 * @author Thomas Müller <[email protected]>
7
 * @author Vincent Petry <[email protected]>
8
 *
9
 * @copyright Copyright (c) 2018, ownCloud GmbH
10
 * @license AGPL-3.0
11
 *
12
 * This code is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License, version 3,
14
 * as published by the Free Software Foundation.
15
 *
16
 * This program is distributed in the hope that it will be useful,
17
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
 * GNU Affero General Public License for more details.
20
 *
21
 * You should have received a copy of the GNU Affero General Public License, version 3,
22
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
23
 *
24
 */
25
26
namespace OCA\FederatedFileSharing;
27
28
use OCP\AppFramework\Http;
29
use OCP\BackgroundJob\IJobList;
30
use OCP\Http\Client\IClientService;
31
use OCP\IConfig;
32
33
class Notifications {
34
	const RESPONSE_FORMAT = 'json'; // default response format for ocs calls
35
36
	/** @var AddressHandler */
37
	private $addressHandler;
38
39
	/** @var IClientService */
40
	private $httpClientService;
41
42
	/** @var DiscoveryManager */
43
	private $discoveryManager;
44
45 1
	/** @var IJobList  */
46
	private $jobList;
47
48
	/** @var IConfig */
49 1
	private $config;
50 1
51 1
	/**
52
	 * @param AddressHandler $addressHandler
53
	 * @param IClientService $httpClientService
54
	 * @param DiscoveryManager $discoveryManager
55
	 * @param IJobList $jobList
56
	 * @param IConfig $config
57
	 */
58
	public function __construct(
59
		AddressHandler $addressHandler,
60
		IClientService $httpClientService,
61
		DiscoveryManager $discoveryManager,
62
		IJobList $jobList,
63
		IConfig $config
64
	) {
65
		$this->addressHandler = $addressHandler;
66
		$this->httpClientService = $httpClientService;
67
		$this->discoveryManager = $discoveryManager;
68
		$this->jobList = $jobList;
69
		$this->config = $config;
70
	}
71
72
	/**
73
	 * send server-to-server share to remote server
74
	 *
75
	 * @param Address $shareWithAddress
76
	 * @param Address $ownerAddress
77
	 * @param Address $sharedByAddress
78
	 * @param string $token
79
	 * @param string $shareWith
0 ignored issues
show
Documentation introduced by
There is no parameter named $shareWith. Did you maybe mean $shareWithAddress?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

Consider the following example. The parameter $ireland is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $ireland
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was changed, but the annotation was not.

Loading history...
80
	 * @param string $name
81
	 * @param int $remote_id
82
	 *
83
	 * @return bool
84
	 *
85
	 * @throws \OC\HintException
86
	 * @throws \OC\ServerNotAvailableException
87
	 */
88
	public function sendRemoteShare(Address $shareWithAddress,
89
		Address $ownerAddress,
90
		Address $sharedByAddress,
91
		$token,
92
		$name,
93
		$remote_id
94
	) {
95
		$remoteShareSuccess = false;
96
		if ($shareWithAddress->getUserId() && $shareWithAddress->getCloudId()) {
97
			$remoteShareSuccess = $this->sendOcmRemoteShare(
98
				$shareWithAddress, $ownerAddress, $sharedByAddress, $token, $name, $remote_id
99
			);
100
			if (!$remoteShareSuccess) {
101
				$remoteShareSuccess = $this->sendPreOcmRemoteShare(
102
					$shareWithAddress, $ownerAddress, $sharedByAddress, $token, $name, $remote_id
103
				);
104
			}
105
		}
106
		if ($remoteShareSuccess) {
107
			\OC_Hook::emit(
108
				'OCP\Share',
109
				'federated_share_added',
110
				['server' => $shareWithAddress->getHostName()]
111
			);
112
		}
113
		return $remoteShareSuccess;
114
	}
115
116
	/**
117
	 * ask owner to re-share the file with the given user
118
	 *
119
	 * @param string $token
120
	 * @param int $id remote Id
121
	 * @param int $shareId internal share Id
122
	 * @param string $remote remote address of the owner
123
	 * @param string $shareWith
124
	 * @param int $permission
125
	 * @return bool
126
	 * @throws \Exception
127
	 */
128
	public function requestReShare($token, $id, $shareId, $remote, $shareWith, $permission) {
129
		$fields = [
130
			'shareWith' => $shareWith,
131
			'token' => $token,
132
			'permission' => $permission,
133
			'remoteId' => $shareId
134
		];
135
136
		$url = $this->addressHandler->removeProtocolFromUrl($remote);
137
		$result = $this->tryHttpPostToShareEndpoint(\rtrim($url, '/'), '/' . $id . '/reshare', $fields);
138
		$status = \json_decode($result['result'], true);
139
140
		$httpRequestSuccessful = $result['success'];
141
		$validToken = isset($status['ocs']['data']['token']) && \is_string($status['ocs']['data']['token']);
142
		$validRemoteId = isset($status['ocs']['data']['remoteId']);
143
144
		if ($httpRequestSuccessful && $this->isOcsStatusOk($status) && $validToken && $validRemoteId) {
145
			return [
146
				$status['ocs']['data']['token'],
147
				(int)$status['ocs']['data']['remoteId']
148
			];
149
		}
150
151
		return false;
152
	}
153
154
	/**
155
	 * send server-to-server unshare to remote server
156
	 *
157
	 * @param string $remote url
158
	 * @param int $id share id
159
	 * @param string $token
160
	 * @return bool
161
	 */
162
	public function sendRemoteUnShare($remote, $id, $token) {
163
		return $this->sendUpdateToRemote($remote, $id, $token, 'unshare');
164
	}
165
166
	/**
167
	 * send server-to-server unshare to remote server
168
	 *
169
	 * @param string $remote url
170
	 * @param int $id share id
171
	 * @param string $token
172
	 * @return bool
173
	 */
174
	public function sendRevokeShare($remote, $id, $token) {
175
		return $this->sendUpdateToRemote($remote, $id, $token, 'revoke');
176
	}
177
178
	/**
179
	 * send notification to remote server if the permissions was changed
180
	 *
181
	 * @param string $remote
182
	 * @param int $remoteId
183
	 * @param string $token
184
	 * @param int $permissions
185
	 * @return bool
186
	 */
187
	public function sendPermissionChange($remote, $remoteId, $token, $permissions) {
188
		return $this->sendUpdateToRemote($remote, $remoteId, $token, 'permissions', ['permissions' => $permissions]);
189
	}
190
191
	/**
192
	 * forward accept reShare to remote server
193
	 *
194
	 * @param string $remote
195
	 * @param int $remoteId
196
	 * @param string $token
197
	 */
198
	public function sendAcceptShare($remote, $remoteId, $token) {
199
		$this->sendUpdateToRemote($remote, $remoteId, $token, 'accept');
200
	}
201
202
	/**
203
	 * forward decline reShare to remote server
204
	 *
205
	 * @param string $remote
206
	 * @param int $remoteId
207
	 * @param string $token
208
	 */
209
	public function sendDeclineShare($remote, $remoteId, $token) {
210
		$this->sendUpdateToRemote($remote, $remoteId, $token, 'decline');
211
	}
212
213
	/**
214
	 * inform remote server whether server-to-server share was accepted/declined
215
	 *
216
	 * @param string $remote
217
	 * @param int $remoteId Share id on the remote host
218
	 * @param string $token
219
	 * @param string $action possible actions:
220
	 * 	                     accept, decline, unshare, revoke, permissions
221
	 * @param array $data
222
	 * @param int $try
223
	 *
224
	 * @return boolean
225
	 *
226
	 * @throws \Exception
227
	 */
228
	public function sendUpdateToRemote($remote, $remoteId, $token, $action, $data = [], $try = 0) {
229
		$fields = ['token' => $token];
230
		foreach ($data as $key => $value) {
231
			$fields[$key] = $value;
232
		}
233
234
		$url = $this->addressHandler->removeProtocolFromUrl($remote);
235
		$result = $this->tryHttpPostToShareEndpoint(\rtrim($url, '/'), '/' . $remoteId . '/' . $action, $fields);
236
		$status = \json_decode($result['result'], true);
237
238
		if ($result['success'] && $this->isOcsStatusOk($status)) {
239
			return true;
240
		} elseif ($try === 0) {
241
			// only add new job on first try
242
			$this->jobList->add('OCA\FederatedFileSharing\BackgroundJob\RetryJob',
243
				[
244
					'remote' => $remote,
245
					'remoteId' => $remoteId,
246
					'token' => $token,
247
					'action' => $action,
248
					'data' => \json_encode($data),
249
					'try' => $try,
250
					'lastRun' => $this->getTimestamp()
251
				]
252
			);
253
		}
254
255
		return false;
256
	}
257
258
	/**
259
	 * return current timestamp
260
	 *
261
	 * @return int
262
	 */
263
	protected function getTimestamp() {
264
		return \time();
265
	}
266
267
	/**
268
	 * try http post first with https and then with http as a fallback
269
	 *
270
	 * @param string $remoteDomain
271
	 * @param string $urlSuffix
272
	 * @param array $fields post parameters
273
	 * @return array
274
	 * @throws \Exception
275
	 */
276
	protected function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields, $useOcm = false) {
277
		$client = $this->httpClientService->newClient();
278
		$protocol = 'https://';
279
		$result = [
280
			'success' => false,
281
			'result' => '',
282
		];
283
		$try = 0;
284
285
		while ($result['success'] === false && $try < 2) {
286
			if ($useOcm) {
287
				$endpoint = $this->discoveryManager->getOcmShareEndpoint($protocol . $remoteDomain);
288
			} else {
289
				$relativePath = $this->discoveryManager->getShareEndpoint($protocol . $remoteDomain);
290
				$endpoint = $protocol . $remoteDomain . $relativePath . $urlSuffix . '?format=' . self::RESPONSE_FORMAT;
291
			}
292
293
			try {
294
				$response = $client->post($endpoint, [
295
					'body' => $fields,
296
					'timeout' => 10,
297
					'connect_timeout' => 10,
298
				]);
299
				$result['result'] = $response->getBody();
300
				$result['statusCode'] = $response->getStatusCode();
301
				$result['success'] = true;
302
				break;
303
			} catch (\Exception $e) {
304
				// if flat re-sharing is not supported by the remote server
305
				// we re-throw the exception and fall back to the old behaviour.
306
				// (flat re-shares has been introduced in ownCloud 9.1)
307
				if ($e->getCode() === Http::STATUS_INTERNAL_SERVER_ERROR) {
308
					throw $e;
309
				}
310
				$allowHttpFallback = $this->config->getSystemValue('sharing.federation.allowHttpFallback', false) === true;
311
				if (!$allowHttpFallback) {
312
					break;
313
				}
314
				$try++;
315
				$protocol = 'http://';
316
			}
317
		}
318
319
		return $result;
320
	}
321
322
	protected function sendOcmRemoteShare(Address $shareWithAddress, Address $ownerAddress, Address $sharedByAddress, $token, $name, $remote_id) {
323
		$fields = [
324
			'shareWith' => $shareWithAddress->getCloudId(),
325
			'name' => $name,
326
			'providerId' => $remote_id,
327
			'owner' => $ownerAddress->getCloudId(),
328
			'ownerDisplayName' => $ownerAddress->getDisplayName(),
329
			'sender' => $sharedByAddress->getCloudId(),
330
			'senderDisplayName' => $sharedByAddress->getDisplayName(),
331
			'shareType' => 'user',
332
			'resourceType' => 'file',
333
			'protocol' => [
334
				'name' => 'webdav',
335
				'options' => [
336
					'sharedSecret' => $token
337
				]
338
			]
339
		];
340
341
		$url = $shareWithAddress->getCleanHostName();
342
		$result = $this->tryHttpPostToShareEndpoint($url, '', $fields, true);
343
344
		if (isset($result['statusCode']) && $result['statusCode'] === 201) {
345
			return true;
346
		}
347
		return false;
348
	}
349
350
	protected function sendPreOcmRemoteShare(Address $shareWithAddress, Address $ownerAddress, Address $sharedByAddress, $token, $name, $remote_id) {
351
		$fields = [
352
			'shareWith' => $shareWithAddress->getUserId(),
353
			'token' => $token,
354
			'name' => $name,
355
			'remoteId' => $remote_id,
356
			'owner' => $ownerAddress->getUserId(),
357
			'ownerFederatedId' => $ownerAddress->getCloudId(),
358
			'sharedBy' => $sharedByAddress->getUserId(),
359
			'sharedByFederatedId' => $sharedByAddress->getUserId(),
360
			'remote' => $this->addressHandler->generateRemoteURL(),
361
		];
362
		$url = $shareWithAddress->getCleanHostName();
363
		$result = $this->tryHttpPostToShareEndpoint($url, '', $fields);
364
		$status = \json_decode($result['result'], true);
365
366
		if ($result['success'] && $this->isOcsStatusOk($status)) {
367
			return true;
368
		}
369
		return false;
370
	}
371
372
	/**
373
	 * Validate ocs response - 100 or 200 means success
374
	 *
375
	 * @param array $status
376
	 *
377
	 * @return bool
378
	 */
379
	private function isOcsStatusOk($status) {
380
		return \in_array($status['ocs']['meta']['statuscode'], [100, 200]);
381
	}
382
}
383