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

OcmController::discovery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 13
rs 9.8333
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
	 *
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 InvalidShareException(
191
					'server can not add remote share, missing parameter'
192
				);
193
			}
194
			if (!\OCP\Util::isValidFileName($name)) {
195
				throw new InvalidShareException(
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
				return new JSONResponse(
220
					['message' => "Protocol {$protocol['name']} is not supported"],
221
					Http::STATUS_NOT_IMPLEMENTED
222
				);
223
			}
224
225
			if ($this->isSupportedShareType($shareType) === false) {
226
				return new JSONResponse(
227
					['message' => "ShareType {$shareType} is not supported"],
228
					Http::STATUS_NOT_IMPLEMENTED
229
				);
230
			}
231
232 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...
233
				return new JSONResponse(
234
					['message' => "ResourceType {$resourceType} is not supported"],
235
					Http::STATUS_NOT_IMPLEMENTED
236
				);
237
			}
238
239
			if (!$this->userManager->userExists($localShareWith)) {
240
				throw new InvalidShareException("User $localShareWith does not exist");
241
			}
242
243
			$ownerAddress = new Address($owner, $ownerDisplayName);
244
			$sharedByAddress = new Address($sender, $senderDisplayName);
245
246
			$this->fedShareManager->createShare(
247
				$ownerAddress,
248
				$sharedByAddress,
249
				$localShareWith,
250
				$providerId,
251
				$name,
252
				$protocol['options']['sharedSecret']
253
			);
254
		} catch (InvalidShareException $e) {
255
			return new JSONResponse(
256
				['message' => $e->getMessage()],
257
				Http::STATUS_BAD_REQUEST
258
			);
259
		} catch (NotSupportedException $e) {
260
			return new JSONResponse(
261
				['message' => 'Server does not support federated cloud sharing'],
262
				Http::STATUS_SERVICE_UNAVAILABLE
263
			);
264
		} catch (\Exception $e) {
265
			$this->logger->error(
266
				"server can not add remote share, {$e->getMessage()}",
267
				['app' => 'federatefilesharing']
268
			);
269
			return new JSONResponse(
270
				[
271
					'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...
272
				],
273
				Http::STATUS_INTERNAL_SERVER_ERROR
274
			);
275
		}
276
		return new JSONResponse(
277
			[],
278
			Http::STATUS_CREATED
279
		);
280
	}
281
282
	/**
283
	 * @param string $notificationType notification type (SHARE_REMOVED, etc)
284
	 * @param string $resourceType only 'file' is supported atm
285
	 * @param string $providerId Identifier of the resource at the provider side
286
	 * @param array $notification
287
	 * 		[
288
	 * 			optional additional parameters, depending on the notification
289
	 * 				and the resource type
290
	 * 		]
291
	 *
292
	 * @return JSONResponse
293
	 */
294
	public function processNotification($notificationType,
295
										$resourceType,
296
										$providerId,
297
										$notification
298
	) {
299
		try {
300
			$hasMissingParams = $this->hasNull(
301
				[$notificationType, $resourceType, $providerId]
302
			);
303
			if ($hasMissingParams || !\is_array($notification)) {
304
				throw new InvalidShareException(
305
					'server can not add remote share, missing parameter'
306
				);
307
			}
308
309 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...
310
				return new JSONResponse(
311
					['message' => "ResourceType {$resourceType} is not supported"],
312
					Http::STATUS_NOT_IMPLEMENTED
313
				);
314
			}
315
			// TODO: check permissions/preconditions in all cases
316
			switch ($notificationType) {
317 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...
318
					$share = $this->getValidShare(
319
						$providerId, $notification['sharedSecret']
320
					);
321
					$this->fedShareManager->acceptShare($share);
322
					break;
323 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...
324
					$share = $this->getValidShare(
325
						$providerId, $notification['sharedSecret']
326
					);
327
					$this->fedShareManager->declineShare($share);
328
					break;
329
				case FileNotification::NOTIFICATION_TYPE_REQUEST_RESHARE:
330
					$shareWithAddress = new Address($notification['shareWith']);
331
					$localShareWith = $shareWithAddress->getUserId();
332
					$share = $this->getValidShare(
333
						$providerId, $notification['sharedSecret']
334
					);
335
					// TODO: permissions not needed ???
336
					$this->fedShareManager->reShare(
337
						$share, $providerId, $localShareWith, 0
338
					);
339
					break;
340
				case FileNotification::NOTIFICATION_TYPE_RESHARE_CHANGE_PERMISSION:
341
					$permissions = $notification['permission'];
342
					// TODO: Map OCM permissions to numeric
343
					$share = $this->getValidShare(
344
						$providerId, $notification['sharedSecret']
345
					);
346
					$this->fedShareManager->updatePermissions($share, $permissions);
347
					break;
348
				case FileNotification::NOTIFICATION_TYPE_SHARE_UNSHARED:
349
					$this->fedShareManager->unshare(
350
						$providerId, $notification['sharedSecret']
351
					);
352
					break;
353 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...
354
					$share = $this->getValidShare(
355
						$providerId, $notification['sharedSecret']
356
					);
357
					$this->fedShareManager->revoke($share);
358
					break;
359
				default:
360
					return new JSONResponse(
361
						['message' => "Notification of type {$notificationType} is not supported"],
362
						Http::STATUS_NOT_IMPLEMENTED
363
					);
364
			}
365
		} catch (Share\Exceptions\ShareNotFound $e) {
366
			return new JSONResponse(
367
				['message' => $e->getMessage()],
368
				Http::STATUS_BAD_REQUEST
369
			);
370
		} catch (InvalidShareException $e) {
371
			return new JSONResponse(
372
				['message' => 'Missing arguments'],
373
				Http::STATUS_BAD_REQUEST
374
			);
375
		}
376
		return new JSONResponse(
377
			[],
378
			Http::STATUS_CREATED
379
		);
380
	}
381
382
	/**
383
	 * Get list of supported protocols
384
	 *
385
	 * @return array
386
	 */
387
	protected function getProtocols() {
388
		return [
389
			'webdav' => '/public.php/webdav/'
390
		];
391
	}
392
393
	/**
394
	 * Check if value is null or an array has any null item
395
	 *
396
	 * @param mixed $param
397
	 *
398
	 * @return bool
399
	 */
400 View Code Duplication
	protected function hasNull($param) {
401
		if (\is_array($param)) {
402
			return \in_array(null, $param, true);
403
		} else {
404
			return $param === null;
405
		}
406
	}
407
408
	/**
409
	 * Get share by id, validate it's type and token
410
	 *
411
	 * @param int $id
412
	 * @param string $sharedSecret
413
	 *
414
	 * @return IShare
415
	 *
416
	 * @throws InvalidShareException
417
	 * @throws Share\Exceptions\ShareNotFound
418
	 */
419 View Code Duplication
	protected function getValidShare($id, $sharedSecret) {
420
		$share = $this->federatedShareProvider->getShareById($id);
421
		if ($share->getShareType() !== FederatedShareProvider::SHARE_TYPE_REMOTE
422
			|| $share->getToken() !== $sharedSecret
423
		) {
424
			throw new InvalidShareException();
425
		}
426
		return $share;
427
	}
428
429
	/**
430
	 * @param string $resourceType
431
	 *
432
	 * @return bool
433
	 */
434
	protected function isSupportedResourceType($resourceType) {
435
		return $resourceType === FileNotification::RESOURCE_TYPE_FILE;
436
	}
437
438
	/**
439
	 * @param string $shareType
440
	 * @return bool
441
	 */
442
	protected function isSupportedShareType($shareType) {
443
		// TODO: make it a constant
444
		return $shareType === 'user';
445
	}
446
447
	/**
448
	 * @param string $protocolName
449
	 * @return bool
450
	 */
451
	protected function isSupportedProtocol($protocolName) {
452
		$supportedProtocols = $this->getProtocols();
453
		$supportedProtocolNames = \array_keys($supportedProtocols);
454
		return \in_array($protocolName, $supportedProtocolNames);
455
	}
456
}
457