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

OcmController::processNotification()   C

Complexity

Conditions 13
Paths 67

Size

Total Lines 97

Duplication

Lines 18
Ratio 18.56 %

Importance

Changes 0
Metric Value
cc 13
nc 67
nop 4
dl 18
loc 97
rs 5.3587
c 0
b 0
f 0

How to fix   Long Method    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
 * @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\AppFramework\Http\JSONResponse;
32
use OCA\FederatedFileSharing\FedShareManager;
33
use OCA\FederatedFileSharing\Ocm\Exception\OcmException;
34
use OCP\AppFramework\Controller;
35
use OCP\AppFramework\Http;
36
use OCP\ILogger;
37
use OCP\IRequest;
38
use OCP\IURLGenerator;
39
use OCP\IUserManager;
40
use OCP\Share;
41
use OCP\Share\IShare;
42
43
/**
44
 * Class OcmController
45
 *
46
 * @package OCA\FederatedFileSharing\Controller
47
 */
48
class OcmController extends Controller {
49
	const API_VERSION = '1.0-proposal1';
50
51
	/**
52
	 * @var FederatedShareProvider
53
	 */
54
	private $federatedShareProvider;
55
56
	/**
57
	 * @var IURLGenerator
58
	 */
59
	protected $urlGenerator;
60
61
	/**
62
	 * @var IUserManager
63
	 */
64
	protected $userManager;
65
66
	/**
67
	 * @var AddressHandler
68
	 */
69
	protected $addressHandler;
70
71
	/**
72
	 * @var FedShareManager
73
	 */
74
	protected $fedShareManager;
75
76
	/**
77
	 * @var ILogger
78
	 */
79
	protected $logger;
80
81
	/**
82
	 * OcmController constructor.
83
	 *
84
	 * @param string $appName
85
	 * @param IRequest $request
86
	 * @param FederatedShareProvider $federatedShareProvider
87
	 * @param IURLGenerator $urlGenerator
88
	 * @param IUserManager $userManager
89
	 * @param AddressHandler $addressHandler
90
	 * @param FedShareManager $fedShareManager
91
	 * @param ILogger $logger
92
	 */
93 View Code Duplication
	public function __construct($appName,
94
									IRequest $request,
95
									FederatedShareProvider $federatedShareProvider,
96
									IURLGenerator $urlGenerator,
97
									IUserManager $userManager,
98
									AddressHandler $addressHandler,
99
									FedShareManager $fedShareManager,
100
									ILogger $logger
101
	) {
102
		parent::__construct($appName, $request);
103
104
		$this->federatedShareProvider = $federatedShareProvider;
105
		$this->urlGenerator = $urlGenerator;
106
		$this->userManager = $userManager;
107
		$this->addressHandler = $addressHandler;
108
		$this->fedShareManager = $fedShareManager;
109
		$this->logger = $logger;
110
	}
111
112
	/**
113
	 * @NoCSRFRequired
114
	 * @PublicPage
115
	 *
116
	 * EndPoint discovery
117
	 * Responds to /ocm-provider/ requests
118
	 *
119
	 * @return array
120
	 */
121
	public function discovery() {
122
		return [
123
			'enabled' => true,
124
			'apiVersion' => self::API_VERSION,
125
			'endPoint' => $this->urlGenerator->linkToRouteAbsolute(
126
				"{$this->appName}.ocm.index"
127
			),
128
			'shareTypes' => [
129
				'name' => FileNotification::RESOURCE_TYPE_FILE,
130
				'protocols' => $this->getProtocols()
131
			]
132
		];
133
	}
134
135
	/**
136
	 * @NoCSRFRequired
137
	 * @PublicPage
138
	 *
139
	 * @param string $shareWith identifier of the user or group
140
	 * 							to share the resource with
141
	 * @param string $name name of the shared resource
142
	 * @param string $description share description (optional)
143
	 * @param string $providerId Identifier of the resource at the provider side
144
	 * @param string $owner identifier of the user that owns the resource
145
	 * @param string $ownerDisplayName display name of the owner
146
	 * @param string $sender Provider specific identifier of the user that wants
147
	 *							to share the resource
148
	 * @param string $senderDisplayName Display name of the user that wants
149
	 * 									to share the resource
150
	 * @param string $shareType Share type ('user' is supported atm)
151
	 * @param string $resourceType only 'file' is supported atm
152
	 * @param array $protocol
153
	 * 		[
154
	 * 			'name' => (string) protocol name. Only 'webdav' is supported atm,
155
	 * 			'options' => [
156
	 * 				protocol specific options
157
	 * 				only `webdav` options are supported atm
158
	 * 				e.g. `uri`,	`access_token`, `password`, `permissions` etc.
159
	 *
160
	 * 				For backward compatibility the webdav protocol will use
161
	 * 				the 'sharedSecret" as username and password
162
	 * 			]
163
	 *
164
	 * @return JSONResponse
165
	 */
166
	public function createShare($shareWith,
167
								$name,
168
								$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...
169
								$providerId,
170
								$owner,
171
								$ownerDisplayName,
172
								$sender,
173
								$senderDisplayName,
174
								$shareType,
175
								$resourceType,
176
								$protocol
177
178
	) {
179
		try {
180
			$hasMissingParams = $this->hasNull(
181
				[$shareWith, $name, $providerId, $owner, $shareType, $resourceType]
182
			);
183
			if ($hasMissingParams
184
				|| !\is_array($protocol)
185
				|| !isset($protocol['name'])
186
				|| !isset($protocol['options'])
187
				|| !\is_array($protocol['options'])
188
				|| !isset($protocol['options']['sharedSecret'])
189
			) {
190
				throw new BadRequestException(
191
					'server can not add remote share, missing parameter'
192
				);
193
			}
194
			if (!\OCP\Util::isValidFileName($name)) {
195
				throw new BadRequestException(
196
					'The mountpoint name contains invalid characters.'
197
				);
198
			}
199
200
			$shareWithAddress = new Address($shareWith);
201
			$localShareWith = $shareWithAddress->getUserId();
202
203
			// FIXME this should be a method in the user management instead
204
			$this->logger->debug(
205
				"shareWith before, $localShareWith",
206
				['app' => $this->appName]
207
			);
208
			\OCP\Util::emitHook(
209
				'\OCA\Files_Sharing\API\Server2Server',
210
				'preLoginNameUsedAsUserName',
211
				['uid' => &$localShareWith]
212
			);
213
			$this->logger->debug(
214
				"shareWith after, $localShareWith",
215
				['app' => $this->appName]
216
			);
217
218
			if ($this->isSupportedProtocol($protocol['name']) === false) {
219
				throw new NotImplementedException(
220
					"Protocol {$protocol['name']} is not supported"
221
				);
222
			}
223
224
			if ($this->isSupportedShareType($shareType) === false) {
225
				throw new NotImplementedException(
226
					"ShareType {$shareType} is not supported"
227
				);
228
			}
229
230
			if ($this->isSupportedResourceType($resourceType) === false) {
231
				throw new NotImplementedException(
232
					"ResourceType {$resourceType} is not supported"
233
				);
234
			}
235
236
			if (!$this->userManager->userExists($localShareWith)) {
237
				throw new BadRequestException("User $localShareWith does not exist");
238
			}
239
240
			$ownerAddress = new Address($owner, $ownerDisplayName);
241
			$sharedByAddress = new Address($sender, $senderDisplayName);
242
243
			$this->fedShareManager->createShare(
244
				$ownerAddress,
245
				$sharedByAddress,
246
				$localShareWith,
247
				$providerId,
248
				$name,
249
				$protocol['options']['sharedSecret']
250
			);
251
		} catch (OcmException $e) {
252
			return new JSONResponse(
253
				['message' => $e->getMessage()],
254
				$e->getHttpStatusCode()
255
			);
256
		} catch (\Exception $e) {
257
			$this->logger->error(
258
				"server can not add remote share, {$e->getMessage()}",
259
				['app' => 'federatefilesharing']
260
			);
261
			return new JSONResponse(
262
				[
263
					'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...
264
				],
265
				Http::STATUS_INTERNAL_SERVER_ERROR
266
			);
267
		}
268
		return new JSONResponse(
269
			[],
270
			Http::STATUS_CREATED
271
		);
272
	}
273
274
	/**
275
	 * @NoCSRFRequired
276
	 * @PublicPage
277
	 *
278
	 * @param string $notificationType notification type (SHARE_REMOVED, etc)
279
	 * @param string $resourceType only 'file' is supported atm
280
	 * @param string $providerId Identifier of the resource at the provider side
281
	 * @param array $notification
282
	 * 		[
283
	 * 			optional additional parameters, depending on the notification
284
	 * 				and the resource type
285
	 * 		]
286
	 *
287
	 * @return JSONResponse
288
	 */
289
	public function processNotification($notificationType,
290
										$resourceType,
291
										$providerId,
292
										$notification
293
	) {
294
		try {
295
			$hasMissingParams = $this->hasNull(
296
				[$notificationType, $resourceType, $providerId]
297
			);
298
			if ($hasMissingParams || !\is_array($notification)) {
299
				throw new BadRequestException(
300
					'server can not add remote share, missing parameter'
301
				);
302
			}
303
304
			if ($this->isSupportedResourceType($resourceType) === false) {
305
				throw new NotImplementedException(
306
					"ResourceType {$resourceType} is not supported"
307
				);
308
			}
309
			// TODO: check permissions/preconditions in all cases
310
			switch ($notificationType) {
311 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...
312
					$share = $this->getValidShare(
313
						$providerId, $notification['sharedSecret']
314
					);
315
					$this->fedShareManager->acceptShare($share);
316
					break;
317 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...
318
					$share = $this->getValidShare(
319
						$providerId, $notification['sharedSecret']
320
					);
321
					$this->fedShareManager->declineShare($share);
322
					break;
323
				case FileNotification::NOTIFICATION_TYPE_REQUEST_RESHARE:
324
					$shareWithAddress = new Address($notification['shareWith']);
325
					$localShareWith = $shareWithAddress->getUserId();
326
					$share = $this->getValidShare(
327
						$providerId, $notification['sharedSecret']
328
					);
329
					// TODO: permissions not needed ???
330
					$this->fedShareManager->reShare(
331
						$share, $providerId, $localShareWith, 0
332
					);
333
					break;
334
				case FileNotification::NOTIFICATION_TYPE_RESHARE_CHANGE_PERMISSION:
335
					$permissions = $notification['permission'];
336
					// TODO: Map OCM permissions to numeric
337
					$share = $this->getValidShare(
338
						$providerId, $notification['sharedSecret']
339
					);
340
					$this->fedShareManager->updatePermissions($share, $permissions);
341
					break;
342
				case FileNotification::NOTIFICATION_TYPE_SHARE_UNSHARED:
343
					$this->fedShareManager->unshare(
344
						$providerId, $notification['sharedSecret']
345
					);
346
					break;
347 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...
348
					$share = $this->getValidShare(
349
						$providerId, $notification['sharedSecret']
350
					);
351
					$this->fedShareManager->revoke($share);
352
					break;
353
				default:
354
					return new JSONResponse(
355
						['message' => "Notification of type {$notificationType} is not supported"],
356
						Http::STATUS_NOT_IMPLEMENTED
357
					);
358
			}
359
		} catch (OcmException $e) {
360
			return new JSONResponse(
361
				['message' => $e->getMessage()],
362
				$e->getHttpStatusCode()
363
			);
364
		} catch (Share\Exceptions\ShareNotFound $e) {
365
			return new JSONResponse(
366
				['message' => $e->getMessage()],
367
				Http::STATUS_BAD_REQUEST
368
			);
369
		} catch (\Exception $e) {
370
			$this->logger->error(
371
				"server can not process notification, {$e->getMessage()}",
372
				['app' => 'federatefilesharing']
373
			);
374
			return new JSONResponse(
375
				[
376
					'message' => "internal server error, was not able to process notification"
377
				],
378
				Http::STATUS_INTERNAL_SERVER_ERROR
379
			);
380
		}
381
		return new JSONResponse(
382
			[],
383
			Http::STATUS_CREATED
384
		);
385
	}
386
387
	/**
388
	 * Get list of supported protocols
389
	 *
390
	 * @return array
391
	 */
392
	protected function getProtocols() {
393
		return [
394
			'webdav' => '/public.php/webdav/'
395
		];
396
	}
397
398
	/**
399
	 * Check if value is null or an array has any null item
400
	 *
401
	 * @param mixed $param
402
	 *
403
	 * @return bool
404
	 */
405 View Code Duplication
	protected function hasNull($param) {
406
		if (\is_array($param)) {
407
			return \in_array(null, $param, true);
408
		} else {
409
			return $param === null;
410
		}
411
	}
412
413
	/**
414
	 * Get share by id, validate it's type and token
415
	 *
416
	 * @param int $id
417
	 * @param string $sharedSecret
418
	 *
419
	 * @return IShare
420
	 *
421
	 * @throws BadRequestException
422
	 * @throws ForbiddenException
423
	 * @throws Share\Exceptions\ShareNotFound
424
	 */
425
	protected function getValidShare($id, $sharedSecret) {
426
		$share = $this->federatedShareProvider->getShareById($id);
427
		if ($share->getShareType() !== FederatedShareProvider::SHARE_TYPE_REMOTE) {
428
			throw new BadRequestException("Share with id {$id} des not exist");
429
		}
430
431
		if ($share->getToken() !== $sharedSecret) {
432
			throw new ForbiddenException("The secret doesn not match");
433
		}
434
		return $share;
435
	}
436
437
	/**
438
	 * @param string $resourceType
439
	 *
440
	 * @return bool
441
	 */
442
	protected function isSupportedResourceType($resourceType) {
443
		return $resourceType === FileNotification::RESOURCE_TYPE_FILE;
444
	}
445
446
	/**
447
	 * @param string $shareType
448
	 * @return bool
449
	 */
450
	protected function isSupportedShareType($shareType) {
451
		// TODO: make it a constant
452
		return $shareType === 'user';
453
	}
454
455
	/**
456
	 * @param string $protocolName
457
	 * @return bool
458
	 */
459
	protected function isSupportedProtocol($protocolName) {
460
		$supportedProtocols = $this->getProtocols();
461
		$supportedProtocolNames = \array_keys($supportedProtocols);
462
		return \in_array($protocolName, $supportedProtocolNames);
463
	}
464
}
465