Completed
Pull Request — master (#32303)
by Victor
10:59
created

OcmController::discovery()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 14
rs 9.7998
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\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
		$endPoint = $this->urlGenerator->linkToRouteAbsolute(
132
			"{$this->appName}.ocm.index"
133
		);
134
		return [
135
			'enabled' => true,
136
			'apiVersion' => self::API_VERSION,
137
			'endPoint' => \rtrim($endPoint, '/'),
138
			'shareTypes' => [
139
				'name' => FileNotification::RESOURCE_TYPE_FILE,
140
				'protocols' => $this->getProtocols()
141
			]
142
		];
143
	}
144
145
	/**
146
	 * @NoCSRFRequired
147
	 * @PublicPage
148
	 *
149
	 * @param string $shareWith identifier of the user or group
150
	 * 							to share the resource with
151
	 * @param string $name name of the shared resource
152
	 * @param string $description share description (optional)
153
	 * @param string $providerId Identifier of the resource at the provider side
154
	 * @param string $owner identifier of the user that owns the resource
155
	 * @param string $ownerDisplayName display name of the owner
156
	 * @param string $sender Provider specific identifier of the user that wants
157
	 *							to share the resource
158
	 * @param string $senderDisplayName Display name of the user that wants
159
	 * 									to share the resource
160
	 * @param string $shareType Share type ('user' is supported atm)
161
	 * @param string $resourceType only 'file' is supported atm
162
	 * @param array $protocol
163
	 * 		[
164
	 * 			'name' => (string) protocol name. Only 'webdav' is supported atm,
165
	 * 			'options' => [
166
	 * 				protocol specific options
167
	 * 				only `webdav` options are supported atm
168
	 * 				e.g. `uri`,	`access_token`, `password`, `permissions` etc.
169
	 *
170
	 * 				For backward compatibility the webdav protocol will use
171
	 * 				the 'sharedSecret" as username and password
172
	 * 			]
173
	 *
174
	 * @return JSONResponse
175
	 */
176
	public function createShare($shareWith,
177
								$name,
178
								$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...
179
								$providerId,
180
								$owner,
181
								$ownerDisplayName,
182
								$sender,
183
								$senderDisplayName,
184
								$shareType,
185
								$resourceType,
186
								$protocol
187
188
	) {
189
		try {
190
			$this->assertIncomingSharingEnabled();
191
			$hasMissingParams = $this->hasNull(
192
				[$shareWith, $name, $providerId, $owner, $shareType, $resourceType]
193
			);
194
			if ($hasMissingParams
195
				|| !\is_array($protocol)
196
				|| !isset($protocol['name'])
197
				|| !isset($protocol['options'])
198
				|| !\is_array($protocol['options'])
199
				|| !isset($protocol['options']['sharedSecret'])
200
			) {
201
				throw new BadRequestException(
202
					'server can not add remote share, missing parameter'
203
				);
204
			}
205
			if (!\OCP\Util::isValidFileName($name)) {
206
				throw new BadRequestException(
207
					'The mountpoint name contains invalid characters.'
208
				);
209
			}
210
211
			$shareWithAddress = new Address($shareWith);
212
			$localShareWith = $shareWithAddress->getUserId();
213
214
			// FIXME this should be a method in the user management instead
215
			$this->logger->debug(
216
				"shareWith before, $localShareWith",
217
				['app' => $this->appName]
218
			);
219
			\OCP\Util::emitHook(
220
				'\OCA\Files_Sharing\API\Server2Server',
221
				'preLoginNameUsedAsUserName',
222
				['uid' => &$localShareWith]
223
			);
224
			$this->logger->debug(
225
				"shareWith after, $localShareWith",
226
				['app' => $this->appName]
227
			);
228
229
			if ($this->isSupportedProtocol($protocol['name']) === false) {
230
				throw new NotImplementedException(
231
					"Protocol {$protocol['name']} is not supported"
232
				);
233
			}
234
235
			if ($this->isSupportedShareType($shareType) === false) {
236
				throw new NotImplementedException(
237
					"ShareType {$shareType} is not supported"
238
				);
239
			}
240
241
			if ($this->isSupportedResourceType($resourceType) === false) {
242
				throw new NotImplementedException(
243
					"ResourceType {$resourceType} is not supported"
244
				);
245
			}
246
247
			if (!$this->userManager->userExists($localShareWith)) {
248
				throw new BadRequestException("User $localShareWith does not exist");
249
			}
250
251
			$ownerAddress = new Address($owner, $ownerDisplayName);
252
			$sharedByAddress = new Address($sender, $senderDisplayName);
253
254
			$this->fedShareManager->createShare(
255
				$ownerAddress,
256
				$sharedByAddress,
257
				$localShareWith,
258
				$providerId,
259
				$name,
260
				$protocol['options']['sharedSecret']
261
			);
262
		} catch (OcmException $e) {
263
			return new JSONResponse(
264
				['message' => $e->getMessage()],
265
				$e->getHttpStatusCode()
266
			);
267
		} catch (\Exception $e) {
268
			$this->logger->error(
269
				"server can not add remote share, {$e->getMessage()}",
270
				['app' => 'federatefilesharing']
271
			);
272
			return new JSONResponse(
273
				[
274
					'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...
275
				],
276
				Http::STATUS_INTERNAL_SERVER_ERROR
277
			);
278
		}
279
		return new JSONResponse(
280
			[],
281
			Http::STATUS_CREATED
282
		);
283
	}
284
285
	/**
286
	 * @NoCSRFRequired
287
	 * @PublicPage
288
	 *
289
	 * @param string $notificationType notification type (SHARE_REMOVED, etc)
290
	 * @param string $resourceType only 'file' is supported atm
291
	 * @param string $providerId Identifier of the resource at the provider side
292
	 * @param array $notification
293
	 * 		[
294
	 * 			optional additional parameters, depending on the notification
295
	 * 				and the resource type
296
	 * 		]
297
	 *
298
	 * @return JSONResponse
299
	 */
300
	public function processNotification($notificationType,
301
										$resourceType,
302
										$providerId,
303
										$notification
304
	) {
305
		try {
306
			$hasMissingParams = $this->hasNull(
307
				[$notificationType, $resourceType, $providerId]
308
			);
309
			if ($hasMissingParams || !\is_array($notification)) {
310
				throw new BadRequestException(
311
					'server can not add remote share, missing parameter'
312
				);
313
			}
314
315
			if ($this->isSupportedResourceType($resourceType) === false) {
316
				throw new NotImplementedException(
317
					"ResourceType {$resourceType} is not supported"
318
				);
319
			}
320
			// TODO: check permissions/preconditions in all cases
321
			switch ($notificationType) {
322 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...
323
					$share = $this->getValidShare(
324
						$providerId, $notification['sharedSecret']
325
					);
326
					$this->fedShareManager->acceptShare($share);
327
					break;
328 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...
329
					$share = $this->getValidShare(
330
						$providerId, $notification['sharedSecret']
331
					);
332
					$this->fedShareManager->declineShare($share);
333
					break;
334
				case FileNotification::NOTIFICATION_TYPE_REQUEST_RESHARE:
335
					$shareWithAddress = new Address($notification['shareWith']);
336
					$localShareWith = $shareWithAddress->getUserId();
337
					$share = $this->getValidShare(
338
						$providerId, $notification['sharedSecret']
339
					);
340
					// TODO: permissions not needed ???
341
					$share = $this->fedShareManager->reShare(
342
						$share, $providerId, $localShareWith, 0
343
					);
344
					return new JSONResponse(
345
						[
346
							'sharedSecret' => $share->getToken(),
347
							'providerId' => $share->getId()
348
						],
349
						Http::STATUS_CREATED
350
					);
351
					break;
0 ignored issues
show
Unused Code introduced by
break is not strictly necessary here and could be removed.

The break statement is not necessary if it is preceded for example by a return statement:

switch ($x) {
    case 1:
        return 'foo';
        break; // This break is not necessary and can be left off.
}

If you would like to keep this construct to be consistent with other case statements, you can safely mark this issue as a false-positive.

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