Completed
Pull Request — master (#32303)
by Victor
12:54
created

OcmController::createShare()   C

Complexity

Conditions 14
Paths 45

Size

Total Lines 108

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 14
nc 45
nop 11
dl 0
loc 108
rs 5.0133
c 0
b 0
f 0

How to fix   Long Method    Complexity    Many Parameters   

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:

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
 * @author Viktar Dubiniuk <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2018, ownCloud GmbH
6
 * @license AGPL-3.0
7
 *
8
 * This code is free software: you can redistribute it and/or modify
9
 * it under the terms of the GNU Affero General Public License, version 3,
10
 * as published by the Free Software Foundation.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
 * GNU Affero General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU Affero General Public License, version 3,
18
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
19
 *
20
 */
21
22
namespace OCA\FederatedFileSharing\Controller;
23
24
use OCA\FederatedFileSharing\Address;
25
use OCA\FederatedFileSharing\AddressHandler;
26
use OCA\FederatedFileSharing\FederatedShareProvider;
27
use OCA\FederatedFileSharing\Ocm\Exception\BadRequestException;
28
use OCA\FederatedFileSharing\Ocm\Exception\ForbiddenException;
29
use OCA\FederatedFileSharing\Ocm\Exception\NotImplementedException;
30
use OCA\FederatedFileSharing\Ocm\Notification\FileNotification;
31
use OCP\App\IAppManager;
32
use OCP\AppFramework\Http\JSONResponse;
33
use OCA\FederatedFileSharing\FedShareManager;
34
use OCA\FederatedFileSharing\Ocm\Exception\OcmException;
35
use OCP\AppFramework\Controller;
36
use OCP\AppFramework\Http;
37
use OCP\ILogger;
38
use OCP\IRequest;
39
use OCP\IURLGenerator;
40
use OCP\IUserManager;
41
use OCP\Share;
42
use OCP\Share\IShare;
43
44
/**
45
 * Class OcmController
46
 *
47
 * @package OCA\FederatedFileSharing\Controller
48
 */
49
class OcmController extends Controller {
50
	const API_VERSION = '1.0-proposal1';
51
52
	/**
53
	 * @var FederatedShareProvider
54
	 */
55
	private $federatedShareProvider;
56
57
	/**
58
	 * @var IURLGenerator
59
	 */
60
	protected $urlGenerator;
61
62
	/**
63
	 * @var IAppManager
64
	 */
65
	private $appManager;
66
67
	/**
68
	 * @var IUserManager
69
	 */
70
	protected $userManager;
71
72
	/**
73
	 * @var AddressHandler
74
	 */
75
	protected $addressHandler;
76
77
	/**
78
	 * @var FedShareManager
79
	 */
80
	protected $fedShareManager;
81
82
	/**
83
	 * @var ILogger
84
	 */
85
	protected $logger;
86
87
	/**
88
	 * OcmController constructor.
89
	 *
90
	 * @param string $appName
91
	 * @param IRequest $request
92
	 * @param FederatedShareProvider $federatedShareProvider
93
	 * @param IURLGenerator $urlGenerator
94
	 * @param IAppManager $appManager
95
	 * @param IUserManager $userManager
96
	 * @param AddressHandler $addressHandler
97
	 * @param FedShareManager $fedShareManager
98
	 * @param ILogger $logger
99
	 */
100 View Code Duplication
	public function __construct($appName,
101
									IRequest $request,
102
									FederatedShareProvider $federatedShareProvider,
103
									IURLGenerator $urlGenerator,
104
									IAppManager $appManager,
105
									IUserManager $userManager,
106
									AddressHandler $addressHandler,
107
									FedShareManager $fedShareManager,
108
									ILogger $logger
109
	) {
110
		parent::__construct($appName, $request);
111
112
		$this->federatedShareProvider = $federatedShareProvider;
113
		$this->urlGenerator = $urlGenerator;
114
		$this->appManager = $appManager;
115
		$this->userManager = $userManager;
116
		$this->addressHandler = $addressHandler;
117
		$this->fedShareManager = $fedShareManager;
118
		$this->logger = $logger;
119
	}
120
121
	/**
122
	 * @NoCSRFRequired
123
	 * @PublicPage
124
	 *
125
	 * EndPoint discovery
126
	 * Responds to /ocm-provider/ requests
127
	 *
128
	 * @return array
129
	 */
130
	public function discovery() {
131
		return [
132
			'enabled' => true,
133
			'apiVersion' => self::API_VERSION,
134
			'endPoint' => $this->urlGenerator->linkToRouteAbsolute(
135
				"{$this->appName}.ocm.index"
136
			),
137
			'shareTypes' => [
138
				'name' => FileNotification::RESOURCE_TYPE_FILE,
139
				'protocols' => $this->getProtocols()
140
			]
141
		];
142
	}
143
144
	/**
145
	 * @NoCSRFRequired
146
	 * @PublicPage
147
	 *
148
	 * @param string $shareWith identifier of the user or group
149
	 * 							to share the resource with
150
	 * @param string $name name of the shared resource
151
	 * @param string $description share description (optional)
152
	 * @param string $providerId Identifier of the resource at the provider side
153
	 * @param string $owner identifier of the user that owns the resource
154
	 * @param string $ownerDisplayName display name of the owner
155
	 * @param string $sender Provider specific identifier of the user that wants
156
	 *							to share the resource
157
	 * @param string $senderDisplayName Display name of the user that wants
158
	 * 									to share the resource
159
	 * @param string $shareType Share type ('user' is supported atm)
160
	 * @param string $resourceType only 'file' is supported atm
161
	 * @param array $protocol
162
	 * 		[
163
	 * 			'name' => (string) protocol name. Only 'webdav' is supported atm,
164
	 * 			'options' => [
165
	 * 				protocol specific options
166
	 * 				only `webdav` options are supported atm
167
	 * 				e.g. `uri`,	`access_token`, `password`, `permissions` etc.
168
	 *
169
	 * 				For backward compatibility the webdav protocol will use
170
	 * 				the 'sharedSecret" as username and password
171
	 * 			]
172
	 *
173
	 * @return JSONResponse
174
	 */
175
	public function createShare($shareWith,
176
								$name,
177
								$description,
0 ignored issues
show
Unused Code introduced by
The parameter $description is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
178
								$providerId,
179
								$owner,
180
								$ownerDisplayName,
181
								$sender,
182
								$senderDisplayName,
183
								$shareType,
184
								$resourceType,
185
								$protocol
186
187
	) {
188
		try {
189
			$this->assertIncomingSharingEnabled();
190
			$hasMissingParams = $this->hasNull(
191
				[$shareWith, $name, $providerId, $owner, $shareType, $resourceType]
192
			);
193
			if ($hasMissingParams
194
				|| !\is_array($protocol)
195
				|| !isset($protocol['name'])
196
				|| !isset($protocol['options'])
197
				|| !\is_array($protocol['options'])
198
				|| !isset($protocol['options']['sharedSecret'])
199
			) {
200
				throw new BadRequestException(
201
					'server can not add remote share, missing parameter'
202
				);
203
			}
204
			if (!\OCP\Util::isValidFileName($name)) {
205
				throw new BadRequestException(
206
					'The mountpoint name contains invalid characters.'
207
				);
208
			}
209
210
			$shareWithAddress = new Address($shareWith);
211
			$localShareWith = $shareWithAddress->getUserId();
212
213
			// FIXME this should be a method in the user management instead
214
			$this->logger->debug(
215
				"shareWith before, $localShareWith",
216
				['app' => $this->appName]
217
			);
218
			\OCP\Util::emitHook(
219
				'\OCA\Files_Sharing\API\Server2Server',
220
				'preLoginNameUsedAsUserName',
221
				['uid' => &$localShareWith]
222
			);
223
			$this->logger->debug(
224
				"shareWith after, $localShareWith",
225
				['app' => $this->appName]
226
			);
227
228
			if ($this->isSupportedProtocol($protocol['name']) === false) {
229
				throw new NotImplementedException(
230
					"Protocol {$protocol['name']} is not supported"
231
				);
232
			}
233
234
			if ($this->isSupportedShareType($shareType) === false) {
235
				throw new NotImplementedException(
236
					"ShareType {$shareType} is not supported"
237
				);
238
			}
239
240
			if ($this->isSupportedResourceType($resourceType) === false) {
241
				throw new NotImplementedException(
242
					"ResourceType {$resourceType} is not supported"
243
				);
244
			}
245
246
			if (!$this->userManager->userExists($localShareWith)) {
247
				throw new BadRequestException("User $localShareWith does not exist");
248
			}
249
250
			$ownerAddress = new Address($owner, $ownerDisplayName);
251
			$sharedByAddress = new Address($sender, $senderDisplayName);
252
253
			$this->fedShareManager->createShare(
254
				$ownerAddress,
255
				$sharedByAddress,
256
				$localShareWith,
257
				$providerId,
258
				$name,
259
				$protocol['options']['sharedSecret']
260
			);
261
		} catch (OcmException $e) {
262
			return new JSONResponse(
263
				['message' => $e->getMessage()],
264
				$e->getHttpStatusCode()
265
			);
266
		} catch (\Exception $e) {
267
			$this->logger->error(
268
				"server can not add remote share, {$e->getMessage()}",
269
				['app' => 'federatefilesharing']
270
			);
271
			return new JSONResponse(
272
				[
273
					'message' => "internal server error, was not able to add share from {$ownerAddress->getHostName()}"
0 ignored issues
show
Bug introduced by
The variable $ownerAddress does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
274
				],
275
				Http::STATUS_INTERNAL_SERVER_ERROR
276
			);
277
		}
278
		return new JSONResponse(
279
			[],
280
			Http::STATUS_CREATED
281
		);
282
	}
283
284
	/**
285
	 * @NoCSRFRequired
286
	 * @PublicPage
287
	 *
288
	 * @param string $notificationType notification type (SHARE_REMOVED, etc)
289
	 * @param string $resourceType only 'file' is supported atm
290
	 * @param string $providerId Identifier of the resource at the provider side
291
	 * @param array $notification
292
	 * 		[
293
	 * 			optional additional parameters, depending on the notification
294
	 * 				and the resource type
295
	 * 		]
296
	 *
297
	 * @return JSONResponse
298
	 */
299
	public function processNotification($notificationType,
300
										$resourceType,
301
										$providerId,
302
										$notification
303
	) {
304
		try {
305
			$hasMissingParams = $this->hasNull(
306
				[$notificationType, $resourceType, $providerId]
307
			);
308
			if ($hasMissingParams || !\is_array($notification)) {
309
				throw new BadRequestException(
310
					'server can not add remote share, missing parameter'
311
				);
312
			}
313
314
			if ($this->isSupportedResourceType($resourceType) === false) {
315
				throw new NotImplementedException(
316
					"ResourceType {$resourceType} is not supported"
317
				);
318
			}
319
			// TODO: check permissions/preconditions in all cases
320
			switch ($notificationType) {
321 View Code Duplication
				case FileNotification::NOTIFICATION_TYPE_SHARE_ACCEPTED:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
322
					$share = $this->getValidShare(
323
						$providerId, $notification['sharedSecret']
324
					);
325
					$this->fedShareManager->acceptShare($share);
326
					break;
327 View Code Duplication
				case FileNotification::NOTIFICATION_TYPE_SHARE_DECLINED:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
328
					$share = $this->getValidShare(
329
						$providerId, $notification['sharedSecret']
330
					);
331
					$this->fedShareManager->declineShare($share);
332
					break;
333
				case FileNotification::NOTIFICATION_TYPE_REQUEST_RESHARE:
334
					$shareWithAddress = new Address($notification['shareWith']);
335
					$localShareWith = $shareWithAddress->getUserId();
336
					$share = $this->getValidShare(
337
						$providerId, $notification['sharedSecret']
338
					);
339
					// TODO: permissions not needed ???
340
					$this->fedShareManager->reShare(
341
						$share, $providerId, $localShareWith, 0
342
					);
343
					break;
344
				case FileNotification::NOTIFICATION_TYPE_RESHARE_CHANGE_PERMISSION:
345
					$permissions = $notification['permission'];
346
					// TODO: Map OCM permissions to numeric
347
					$share = $this->getValidShare(
348
						$providerId, $notification['sharedSecret']
349
					);
350
					$this->fedShareManager->updatePermissions($share, $permissions);
351
					break;
352
				case FileNotification::NOTIFICATION_TYPE_SHARE_UNSHARED:
353
					$this->fedShareManager->unshare(
354
						$providerId, $notification['sharedSecret']
355
					);
356
					break;
357 View Code Duplication
				case FileNotification::NOTIFICATION_TYPE_RESHARE_UNDO:
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
358
					$share = $this->getValidShare(
359
						$providerId, $notification['sharedSecret']
360
					);
361
					$this->fedShareManager->revoke($share);
362
					break;
363
				default:
364
					return new JSONResponse(
365
						['message' => "Notification of type {$notificationType} is not supported"],
366
						Http::STATUS_NOT_IMPLEMENTED
367
					);
368
			}
369
		} catch (OcmException $e) {
370
			return new JSONResponse(
371
				['message' => $e->getMessage()],
372
				$e->getHttpStatusCode()
373
			);
374
		} catch (Share\Exceptions\ShareNotFound $e) {
375
			return new JSONResponse(
376
				['message' => $e->getMessage()],
377
				Http::STATUS_BAD_REQUEST
378
			);
379
		} catch (\Exception $e) {
380
			$this->logger->error(
381
				"server can not process notification, {$e->getMessage()}",
382
				['app' => 'federatefilesharing']
383
			);
384
			return new JSONResponse(
385
				[
386
					'message' => "internal server error, was not able to process notification"
387
				],
388
				Http::STATUS_INTERNAL_SERVER_ERROR
389
			);
390
		}
391
		return new JSONResponse(
392
			[],
393
			Http::STATUS_CREATED
394
		);
395
	}
396
397
	/**
398
	 * Get list of supported protocols
399
	 *
400
	 * @return array
401
	 */
402
	protected function getProtocols() {
403
		return [
404
			'webdav' => '/public.php/webdav/'
405
		];
406
	}
407
408
	/**
409
	 * Make sure that incoming shares are enabled
410
	 *
411
	 * @return void
412
	 *
413
	 * @throws NotImplementedException
414
	 */
415
	protected function assertIncomingSharingEnabled() {
416
		if (!$this->appManager->isEnabledForUser('files_sharing')
417
			|| !$this->federatedShareProvider->isIncomingServer2serverShareEnabled()
418
		) {
419
			throw new NotImplementedException();
420
		}
421
	}
422
423
	/**
424
	 * Make sure that outgoing shares are enabled
425
	 *
426
	 * @return void
427
	 *
428
	 * @throws NotImplementedException
429
	 */
430
	protected function assertOutgoingSharingEnabled() {
431
		if (!$this->appManager->isEnabledForUser('files_sharing')
432
			|| !$this->federatedShareProvider->isOutgoingServer2serverShareEnabled()
433
		) {
434
			throw new NotImplementedException();
435
		}
436
	}
437
438
	/**
439
	 * Check if value is null or an array has any null item
440
	 *
441
	 * @param mixed $param
442
	 *
443
	 * @return bool
444
	 */
445 View Code Duplication
	protected function hasNull($param) {
446
		if (\is_array($param)) {
447
			return \in_array(null, $param, true);
448
		} else {
449
			return $param === null;
450
		}
451
	}
452
453
	/**
454
	 * Get share by id, validate it's type and token
455
	 *
456
	 * @param int $id
457
	 * @param string $sharedSecret
458
	 *
459
	 * @return IShare
460
	 *
461
	 * @throws BadRequestException
462
	 * @throws ForbiddenException
463
	 * @throws Share\Exceptions\ShareNotFound
464
	 */
465
	protected function getValidShare($id, $sharedSecret) {
466
		$share = $this->federatedShareProvider->getShareById($id);
467
		if ($share->getShareType() !== FederatedShareProvider::SHARE_TYPE_REMOTE) {
468
			throw new BadRequestException("Share with id {$id} des not exist");
469
		}
470
471
		if ($share->getToken() !== $sharedSecret) {
472
			throw new ForbiddenException("The secret doesn not match");
473
		}
474
		return $share;
475
	}
476
477
	/**
478
	 * @param string $resourceType
479
	 *
480
	 * @return bool
481
	 */
482
	protected function isSupportedResourceType($resourceType) {
483
		return $resourceType === FileNotification::RESOURCE_TYPE_FILE;
484
	}
485
486
	/**
487
	 * @param string $shareType
488
	 * @return bool
489
	 */
490
	protected function isSupportedShareType($shareType) {
491
		// TODO: make it a constant
492
		return $shareType === 'user';
493
	}
494
495
	/**
496
	 * @param string $protocolName
497
	 * @return bool
498
	 */
499
	protected function isSupportedProtocol($protocolName) {
500
		$supportedProtocols = $this->getProtocols();
501
		$supportedProtocolNames = \array_keys($supportedProtocols);
502
		return \in_array($protocolName, $supportedProtocolNames);
503
	}
504
}
505