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

OcmController::getValidShare()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 10

Duplication

Lines 9
Ratio 90 %

Importance

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