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

Notifications::sendRemoteShare()   B

Complexity

Conditions 8
Paths 5

Size

Total Lines 43
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 31
c 0
b 0
f 0
nc 5
nop 9
dl 0
loc 43
rs 8.1795

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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