Completed
Push — master ( 8b683f...7025f1 )
by Morris
29:10 queued 12:40
created

Notifications::tryHttpPostToShareEndpoint()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 3
nc 4
nop 4
dl 0
loc 22
rs 9.568
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 *
5
 * @author Bjoern Schiessle <[email protected]>
6
 * @author Björn Schießle <[email protected]>
7
 * @author Lukas Reschke <[email protected]>
8
 *
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
26
namespace OCA\FederatedFileSharing;
27
28
use OCP\AppFramework\Http;
29
use OCP\BackgroundJob\IJobList;
30
use OCP\Federation\ICloudFederationFactory;
31
use OCP\Federation\ICloudFederationProviderManager;
32
use OCP\Http\Client\IClientService;
33
use OCP\OCS\IDiscoveryService;
34
35
class Notifications {
36
	const RESPONSE_FORMAT = 'json'; // default response format for ocs calls
37
38
	/** @var AddressHandler */
39
	private $addressHandler;
40
41
	/** @var IClientService */
42
	private $httpClientService;
43
44
	/** @var IDiscoveryService */
45
	private $discoveryService;
46
47
	/** @var IJobList  */
48
	private $jobList;
49
50
	/** @var ICloudFederationProviderManager */
51
	private $federationProviderManager;
52
53
	/** @var ICloudFederationFactory */
54
	private $cloudFederationFactory;
55
56
	/**
57
	 * @param AddressHandler $addressHandler
58
	 * @param IClientService $httpClientService
59
	 * @param IDiscoveryService $discoveryService
60
	 * @param IJobList $jobList
61
	 * @param ICloudFederationProviderManager $federationProviderManager
62
	 * @param ICloudFederationFactory $cloudFederationFactory
63
	 */
64
	public function __construct(
65
		AddressHandler $addressHandler,
66
		IClientService $httpClientService,
67
		IDiscoveryService $discoveryService,
68
		IJobList $jobList,
69
		ICloudFederationProviderManager $federationProviderManager,
70
		ICloudFederationFactory $cloudFederationFactory
71
	) {
72
		$this->addressHandler = $addressHandler;
73
		$this->httpClientService = $httpClientService;
74
		$this->discoveryService = $discoveryService;
75
		$this->jobList = $jobList;
76
		$this->federationProviderManager = $federationProviderManager;
77
		$this->cloudFederationFactory = $cloudFederationFactory;
78
	}
79
80
	/**
81
	 * send server-to-server share to remote server
82
	 *
83
	 * @param string $token
84
	 * @param string $shareWith
85
	 * @param string $name
86
	 * @param int $remote_id
87
	 * @param string $owner
88
	 * @param string $ownerFederatedId
89
	 * @param string $sharedBy
90
	 * @param string $sharedByFederatedId
91
	 * @return bool
92
	 * @throws \OC\HintException
93
	 * @throws \OC\ServerNotAvailableException
94
	 */
95
	public function sendRemoteShare($token, $shareWith, $name, $remote_id, $owner, $ownerFederatedId, $sharedBy, $sharedByFederatedId) {
96
97
		list($user, $remote) = $this->addressHandler->splitUserRemote($shareWith);
98
99
		if ($user && $remote) {
100
			$local = $this->addressHandler->generateRemoteURL();
101
102
			$fields = array(
103
				'shareWith' => $user,
104
				'token' => $token,
105
				'name' => $name,
106
				'remoteId' => $remote_id,
107
				'owner' => $owner,
108
				'ownerFederatedId' => $ownerFederatedId,
109
				'sharedBy' => $sharedBy,
110
				'sharedByFederatedId' => $sharedByFederatedId,
111
				'remote' => $local,
112
			);
113
114
			$result = $this->tryHttpPostToShareEndpoint($remote, '', $fields);
115
			$status = json_decode($result['result'], true);
116
117
			$ocsStatus = isset($status['ocs']);
118
			$ocsSuccess = $ocsStatus && ($status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200);
119
120
			if ($result['success'] && (!$ocsStatus ||$ocsSuccess)) {
121
				\OC_Hook::emit('OCP\Share', 'federated_share_added', ['server' => $remote]);
122
				return true;
123
			}
124
125
		}
126
127
		return false;
128
	}
129
130
	/**
131
	 * ask owner to re-share the file with the given user
132
	 *
133
	 * @param string $token
134
	 * @param int $id remote Id
135
	 * @param int $shareId internal share Id
136
	 * @param string $remote remote address of the owner
137
	 * @param string $shareWith
138
	 * @param int $permission
139
	 * @param string $filename
140
	 * @return bool
141
	 * @throws \OC\HintException
142
	 * @throws \OC\ServerNotAvailableException
143
	 */
144
	public function requestReShare($token, $id, $shareId, $remote, $shareWith, $permission, $filename) {
145
146
		$fields = array(
147
			'shareWith' => $shareWith,
148
			'token' => $token,
149
			'permission' => $permission,
150
			'remoteId' => $shareId,
151
		);
152
153
		$ocmFields = $fields;
154
		$ocmFields['remoteId'] = $id;
155
		$ocmFields['localId'] = $shareId;
156
		$ocmFields['name'] = $filename;
157
158
		$ocmResult = $this->tryOCMEndPoint($remote, $ocmFields, 'reshare');
159
		if (is_array($ocmResult) && isset($ocmResult['token']) && isset($ocmResult['providerId'])) {
160
			return [$ocmResult['token'], $ocmResult['providerId']];
161
		}
162
163
		$result = $this->tryLegacyEndPoint(rtrim($remote, '/'), '/' . $id . '/reshare', $fields);
164
		$status = json_decode($result['result'], true);
165
166
		$httpRequestSuccessful = $result['success'];
167
		$ocsCallSuccessful = $status['ocs']['meta']['statuscode'] === 100 || $status['ocs']['meta']['statuscode'] === 200;
168
		$validToken = isset($status['ocs']['data']['token']) && is_string($status['ocs']['data']['token']);
169
		$validRemoteId = isset($status['ocs']['data']['remoteId']);
170
171
		if ($httpRequestSuccessful && $ocsCallSuccessful && $validToken && $validRemoteId) {
172
			return [
173
				$status['ocs']['data']['token'],
174
				(int)$status['ocs']['data']['remoteId']
175
			];
176
		}
177
178
		return false;
179
	}
180
181
	/**
182
	 * send server-to-server unshare to remote server
183
	 *
184
	 * @param string $remote url
185
	 * @param int $id share id
186
	 * @param string $token
187
	 * @return bool
188
	 */
189
	public function sendRemoteUnShare($remote, $id, $token) {
190
		$this->sendUpdateToRemote($remote, $id, $token, 'unshare');
191
	}
192
193
	/**
194
	 * send server-to-server unshare to remote server
195
	 *
196
	 * @param string $remote url
197
	 * @param int $id share id
198
	 * @param string $token
199
	 * @return bool
200
	 */
201
	public function sendRevokeShare($remote, $id, $token) {
202
		$this->sendUpdateToRemote($remote, $id, $token, 'reshare_undo');
203
	}
204
205
	/**
206
	 * send notification to remote server if the permissions was changed
207
	 *
208
	 * @param string $remote
209
	 * @param int $remoteId
210
	 * @param string $token
211
	 * @param int $permissions
212
	 * @return bool
213
	 */
214
	public function sendPermissionChange($remote, $remoteId, $token, $permissions) {
215
		$this->sendUpdateToRemote($remote, $remoteId, $token, 'permissions', ['permissions' => $permissions]);
216
	}
217
218
	/**
219
	 * forward accept reShare to remote server
220
	 *
221
	 * @param string $remote
222
	 * @param int $remoteId
223
	 * @param string $token
224
	 */
225
	public function sendAcceptShare($remote, $remoteId, $token) {
226
		$this->sendUpdateToRemote($remote, $remoteId, $token, 'accept');
227
	}
228
229
	/**
230
	 * forward decline reShare to remote server
231
	 *
232
	 * @param string $remote
233
	 * @param int $remoteId
234
	 * @param string $token
235
	 */
236
	public function sendDeclineShare($remote, $remoteId, $token) {
237
		$this->sendUpdateToRemote($remote, $remoteId, $token, 'decline');
238
	}
239
240
	/**
241
	 * inform remote server whether server-to-server share was accepted/declined
242
	 *
243
	 * @param string $remote
244
	 * @param string $token
245
	 * @param int $remoteId Share id on the remote host
246
	 * @param string $action possible actions: accept, decline, unshare, revoke, permissions
247
	 * @param array $data
248
	 * @param int $try
249
	 * @return boolean
250
	 */
251
	public function sendUpdateToRemote($remote, $remoteId, $token, $action, $data = [], $try = 0) {
252
253
		$fields = [
254
			'token' => $token,
255
			'remoteId' => $remoteId
256
			];
257
		foreach ($data as $key => $value) {
258
			$fields[$key] = $value;
259
		}
260
261
		$result = $this->tryHttpPostToShareEndpoint(rtrim($remote, '/'), '/' . $remoteId . '/' . $action, $fields, $action);
262
		$status = json_decode($result['result'], true);
263
264
		if ($result['success'] &&
265
			($status['ocs']['meta']['statuscode'] === 100 ||
266
				$status['ocs']['meta']['statuscode'] === 200
267
			)
268
		) {
269
			return true;
270
		} elseif ($try === 0) {
271
			// only add new job on first try
272
			$this->jobList->add('OCA\FederatedFileSharing\BackgroundJob\RetryJob',
273
				[
274
					'remote' => $remote,
275
					'remoteId' => $remoteId,
276
					'token' => $token,
277
					'action' => $action,
278
					'data' => json_encode($data),
279
					'try' => $try,
280
					'lastRun' => $this->getTimestamp()
281
				]
282
			);
283
		}
284
285
		return false;
286
	}
287
288
289
	/**
290
	 * return current timestamp
291
	 *
292
	 * @return int
293
	 */
294
	protected function getTimestamp() {
295
		return time();
296
	}
297
298
	/**
299
	 * try http post with the given protocol, if no protocol is given we pick
300
	 * the secure one (https)
301
	 *
302
	 * @param string $remoteDomain
303
	 * @param string $urlSuffix
304
	 * @param array $fields post parameters
305
	 * @param string $action define the action (possible values: share, reshare, accept, decline, unshare, revoke, permissions)
306
	 * @return array
307
	 * @throws \Exception
308
	 */
309
	protected function tryHttpPostToShareEndpoint($remoteDomain, $urlSuffix, array $fields, $action="share") {
310
311
		if ($this->addressHandler->urlContainProtocol($remoteDomain) === false) {
312
			$remoteDomain = 'https://' . $remoteDomain;
313
		}
314
315
		$result = [
316
			'success' => false,
317
			'result' => '',
318
		];
319
320
		// if possible we use the new OCM API
321
		$ocmResult = $this->tryOCMEndPoint($remoteDomain, $fields, $action);
322
		if (is_array($ocmResult)) {
323
			$result['success'] = true;
324
			$result['result'] = json_encode([
325
				'ocs' => ['meta' => ['statuscode' => 200]]]);
326
			return $result;
327
		}
328
329
		return $this->tryLegacyEndPoint($remoteDomain, $urlSuffix, $fields);
330
	}
331
332
	/**
333
	 * try old federated sharing API if the OCM api doesn't work
334
	 *
335
	 * @param $remoteDomain
336
	 * @param $urlSuffix
337
	 * @param array $fields
338
	 * @return mixed
339
	 * @throws \Exception
340
	 */
341
	protected function tryLegacyEndPoint($remoteDomain, $urlSuffix, array $fields) {
342
343
		$result = [
344
			'success' => false,
345
			'result' => '',
346
		];
347
348
		// Fall back to old API
349
		$client = $this->httpClientService->newClient();
350
		$federationEndpoints = $this->discoveryService->discover($remoteDomain, 'FEDERATED_SHARING');
351
		$endpoint = isset($federationEndpoints['share']) ? $federationEndpoints['share'] : '/ocs/v2.php/cloud/shares';
352
		try {
353
			$response = $client->post($remoteDomain . $endpoint . $urlSuffix . '?format=' . self::RESPONSE_FORMAT, [
354
				'body' => $fields,
355
				'timeout' => 10,
356
				'connect_timeout' => 10,
357
			]);
358
			$result['result'] = $response->getBody();
359
			$result['success'] = true;
360
		} catch (\Exception $e) {
361
			// if flat re-sharing is not supported by the remote server
362
			// we re-throw the exception and fall back to the old behaviour.
363
			// (flat re-shares has been introduced in Nextcloud 9.1)
364
			if ($e->getCode() === Http::STATUS_INTERNAL_SERVER_ERROR) {
365
				throw $e;
366
			}
367
		}
368
369
		return $result;
370
371
	}
372
373
	/**
374
	 * send action regarding federated sharing to the remote server using the OCM API
375
	 *
376
	 * @param $remoteDomain
377
	 * @param $fields
378
	 * @param $action
379
	 *
380
	 * @return bool
381
	 */
382
	protected function tryOCMEndPoint($remoteDomain, $fields, $action) {
383
		switch ($action) {
384
			case 'share':
385
				$share = $this->cloudFederationFactory->getCloudFederationShare(
386
					$fields['shareWith'] . '@' . $remoteDomain,
387
					$fields['name'],
388
					'',
389
					$fields['remoteId'],
390
					$fields['ownerFederatedId'],
391
					$fields['owner'],
392
					$fields['sharedByFederatedId'],
393
					$fields['sharedBy'],
394
					$fields['token'],
395
					'user',
396
					'file'
397
				);
398
				return $this->federationProviderManager->sendShare($share);
399
			case 'reshare':
400
				// ask owner to reshare a file
401
				$notification = $this->cloudFederationFactory->getCloudFederationNotification();
402
				$notification->setMessage('REQUEST_RESHARE',
403
					'file',
404
					$fields['remoteId'],
405
					[
406
						'sharedSecret' => $fields['token'],
407
						'shareWith' => $fields['shareWith'],
408
						'senderId' => $fields['localId'],
409
						'message' => 'Ask owner to reshare the file'
410
					]
411
				);
412
				return $this->federationProviderManager->sendNotification($remoteDomain, $notification);
413 View Code Duplication
			case 'unshare':
414
				//owner unshares the file from the recipient again
415
				$notification = $this->cloudFederationFactory->getCloudFederationNotification();
416
				$notification->setMessage('SHARE_UNSHARED',
417
					'file',
418
					$fields['remoteId'],
419
					[
420
						'sharedSecret' => $fields['token'],
421
						'messgage' => 'file is no longer shared with you'
422
					]
423
				);
424
				return $this->federationProviderManager->sendNotification($remoteDomain, $notification);
425 View Code Duplication
			case 'reshare_undo':
426
				// if a reshare was unshared we send the information to the initiator/owner
427
				$notification = $this->cloudFederationFactory->getCloudFederationNotification();
428
				$notification->setMessage('RESHARE_UNDO',
429
					'file',
430
					$fields['remoteId'],
431
					[
432
						'sharedSecret' => $fields['token'],
433
						'message' => 'reshare was revoked'
434
					]
435
				);
436
				return $this->federationProviderManager->sendNotification($remoteDomain, $notification);
437
		}
438
439
		return false;
440
441
	}
442
}
443