Passed
Push — master ( 268acd...1eb084 )
by Morris
15:42 queued 11s
created

Notifications::requestReShare()   C

Complexity

Conditions 12
Paths 17

Size

Total Lines 49
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
eloc 35
c 0
b 0
f 0
nc 17
nop 7
dl 0
loc 49
rs 6.9666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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