Skip to content

Commit

Permalink
cloud_federation_api: Introduce OpenAPI spec
Browse files Browse the repository at this point in the history
Signed-off-by: jld3103 <jld3103yt@gmail.com>
  • Loading branch information
provokateurin committed Jan 27, 2023
1 parent be1de30 commit 14c9c4f
Show file tree
Hide file tree
Showing 5 changed files with 473 additions and 84 deletions.
131 changes: 47 additions & 84 deletions apps/cloud_federation_api/lib/Controller/RequestHandlerController.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* @author Bjoern Schiessle <bjoern@schiessle.org>
* @author Christoph Wurst <christoph@winzerhof-wurst.at>
* @author Roeland Jago Douma <roeland@famdouma.nl>
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
Expand All @@ -22,9 +23,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\CloudFederationAPI\Controller;

use Exception;
use OCA\CloudFederationAPI\Config;
use OCA\CloudFederationAPI\Model\Response\CloudFederationAddShare;
use OCA\CloudFederationAPI\Model\Response\CloudFederationError;
use OCA\CloudFederationAPI\Model\Response\CloudFederationValidationError;
use OCP\AppFramework\Controller;
use OCP\AppFramework\Http;
use OCP\AppFramework\Http\JSONResponse;
Expand All @@ -41,6 +47,7 @@
use OCP\IURLGenerator;
use OCP\IUserManager;
use OCP\Share\Exceptions\ShareNotFound;
use OCP\Util;
use Psr\Log\LoggerInterface;

/**
Expand Down Expand Up @@ -108,46 +115,36 @@ public function __construct($appName,
*
* @param string $shareWith
* @param string $name resource name (e.g. document.odt)
* @param string $description share description (optional)
* @param string|null $description share description
* @param string $providerId resource UID on the provider side
* @param string $owner provider specific UID of the user who owns the resource
* @param string $ownerDisplayName display name of the user who shared the item
* @param string $sharedBy provider specific UID of the user who shared the resource
* @param string $sharedByDisplayName display name of the user who shared the resource
* @param array $protocol (e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]])
* @param string $shareType ('group' or 'user' share)
* @param $resourceType ('file', 'calendar',...)
* @return Http\DataResponse|JSONResponse
* @param string|null $ownerDisplayName display name of the user who shared the item
* @param string|null $sharedBy provider specific UID of the user who shared the resource
* @param string|null $sharedByDisplayName display name of the user who shared the resource
* @param array{name: string[], options: array{}} $protocol e,.g. ['name' => 'webdav', 'options' => ['username' => 'john', 'permissions' => 31]]
* @param string $shareType 'group' or 'user' share
* @param string $resourceType 'file', 'calendar',...
*
* @return JSONResponse<CloudFederationAddShare> 201 The notification was successfully received. The display name of the recepient might be returned in the body.
* @return JSONResponse<CloudFederationValidationError> 400 Bad request due to invalid parameters, e.g. when `shareWith` is not found or required properties are missing.
* @return JSONResponse<CloudFederationError> 501 Share type or the resource type is not supported.
*
* Example: curl -H "Content-Type: application/json" -X POST -d '{"shareWith":"admin1@serve1","name":"welcome server2.txt","description":"desc","providerId":"2","owner":"admin2@http://localhost/server2","ownerDisplayName":"admin2 display","shareType":"user","resourceType":"file","protocol":{"name":"webdav","options":{"sharedSecret":"secret","permissions":"webdav-property"}}}' http://localhost/server/index.php/ocm/shares
*/
public function addShare($shareWith, $name, $description, $providerId, $owner, $ownerDisplayName, $sharedBy, $sharedByDisplayName, $protocol, $shareType, $resourceType) {
public function addShare(string $shareWith, string $name, ?string $description, string $providerId, string $owner, ?string $ownerDisplayName, ?string $sharedBy, ?string $sharedByDisplayName, array $protocol, string $shareType, string $resourceType) {

// check if all required parameters are set
if ($shareWith === null ||
$name === null ||
$providerId === null ||
$owner === null ||
$resourceType === null ||
$shareType === null ||
!is_array($protocol) ||
!isset($protocol['name']) ||
if (!isset($protocol['name']) ||
!isset($protocol['options']) ||
!is_array($protocol['options']) ||
!isset($protocol['options']['sharedSecret'])
) {
return new JSONResponse(
['message' => 'Missing arguments'],
Http::STATUS_BAD_REQUEST
);
return new JSONResponse(new CloudFederationValidationError(message: 'Missing arguments'), Http::STATUS_BAD_REQUEST);
}

$supportedShareTypes = $this->config->getSupportedShareTypes($resourceType);
if (!in_array($shareType, $supportedShareTypes)) {
return new JSONResponse(
['message' => 'Share type "' . $shareType . '" not implemented'],
Http::STATUS_NOT_IMPLEMENTED
);
return new JSONResponse(new CloudFederationError(message: 'Share type "' . $shareType . '" not implemented'), Http::STATUS_NOT_IMPLEMENTED);
}

$cloudId = $this->cloudIdManager->resolveCloudId($shareWith);
Expand All @@ -157,21 +154,15 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
$shareWith = $this->mapUid($shareWith);

if (!$this->userManager->userExists($shareWith)) {
$response = new JSONResponse(
['message' => 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
Http::STATUS_BAD_REQUEST
);
$response = new JSONResponse(new CloudFederationValidationError(message: 'User "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()), Http::STATUS_BAD_REQUEST);
$response->throttle();
return $response;
}
}

if ($shareType === 'group') {
if (!$this->groupManager->groupExists($shareWith)) {
$response = new JSONResponse(
['message' => 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()],
Http::STATUS_BAD_REQUEST
);
$response = new JSONResponse(new CloudFederationValidationError(message: 'Group "' . $shareWith . '" does not exists at ' . $this->urlGenerator->getBaseUrl()), Http::STATUS_BAD_REQUEST);
$response->throttle();
return $response;
}
Expand All @@ -193,21 +184,12 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
$share->setProtocol($protocol);
$provider->shareReceived($share);
} catch (ProviderDoesNotExistsException $e) {
return new JSONResponse(
['message' => $e->getMessage()],
Http::STATUS_NOT_IMPLEMENTED
);
return new JSONResponse(new CloudFederationError(message: $e->getMessage()), Http::STATUS_NOT_IMPLEMENTED);
} catch (ProviderCouldNotAddShareException $e) {
return new JSONResponse(
['message' => $e->getMessage()],
$e->getCode()
);
} catch (\Exception $e) {
return new JSONResponse(new CloudFederationError(message: $e->getMessage()), $e->getCode());
} catch (Exception $e) {
$this->logger->error($e->getMessage(), ['exception' => $e]);
return new JSONResponse(
['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
Http::STATUS_BAD_REQUEST
);
return new JSONResponse(new CloudFederationValidationError(message: 'Internal error at ' . $this->urlGenerator->getBaseUrl()), Http::STATUS_BAD_REQUEST);
}

$user = $this->userManager->get($shareWith);
Expand All @@ -216,9 +198,7 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
$recipientDisplayName = $user->getDisplayName();
}

return new JSONResponse(
['recipientDisplayName' => $recipientDisplayName],
Http::STATUS_CREATED);
return new JSONResponse(new CloudFederationAddShare($recipientDisplayName), Http::STATUS_CREATED);
}

/**
Expand All @@ -228,60 +208,43 @@ public function addShare($shareWith, $name, $description, $providerId, $owner, $
* @PublicPage
* @BruteForceProtection(action=receiveFederatedShareNotification)
*
* @param string $notificationType (notification type, e.g. SHARE_ACCEPTED)
* @param string $resourceType (calendar, file, contact,...)
* @param string $providerId id of the share
* @param array $notification the actual payload of the notification
* @return JSONResponse
* @param string $notificationType notification type, e.g. SHARE_ACCEPTED
* @param string $resourceType calendar, file, contact,...
* @param string|null $providerId id of the share
* @param array{}|null $notification the actual payload of the notification
* @return JSONResponse<array{}> 201 The notification was successfully received
* @return JSONResponse<CloudFederationValidationError> 400 Bad request due to invalid parameters, e.g. when `type` is invalid or missing.
* @return JSONResponse<CloudFederationError> 501 The resource type is not supported.
*/
public function receiveNotification($notificationType, $resourceType, $providerId, array $notification) {
public function receiveNotification(string $notificationType, string $resourceType, ?string $providerId, ?array $notification) {

// check if all required parameters are set
if ($notificationType === null ||
$resourceType === null ||
$providerId === null ||
!is_array($notification)
) {
return new JSONResponse(
['message' => 'Missing arguments'],
Http::STATUS_BAD_REQUEST
);
if ($providerId === null || !is_array($notification)) {
return new JSONResponse(new CloudFederationValidationError(message: 'Missing arguments'), Http::STATUS_BAD_REQUEST);
}

try {
$provider = $this->cloudFederationProviderManager->getCloudFederationProvider($resourceType);
$result = $provider->notificationReceived($notificationType, $providerId, $notification);
} catch (ProviderDoesNotExistsException $e) {
return new JSONResponse(
['message' => $e->getMessage()],
Http::STATUS_BAD_REQUEST
);
return new JSONResponse(new CloudFederationValidationError(message: $e->getMessage()), Http::STATUS_BAD_REQUEST);
} catch (ShareNotFound $e) {
$response = new JSONResponse(
['message' => $e->getMessage()],
Http::STATUS_BAD_REQUEST
);
$response = new JSONResponse(new CloudFederationValidationError(message: $e->getMessage()), Http::STATUS_BAD_REQUEST);
$response->throttle();
return $response;
} catch (ActionNotSupportedException $e) {
return new JSONResponse(
['message' => $e->getMessage()],
Http::STATUS_NOT_IMPLEMENTED
);
return new JSONResponse(new CloudFederationError(message: $e->getMessage()), Http::STATUS_NOT_IMPLEMENTED);
} catch (BadRequestException $e) {
return new JSONResponse($e->getReturnMessage(), Http::STATUS_BAD_REQUEST);
} catch (AuthenticationFailedException $e) {
$response = new JSONResponse(['message' => 'RESOURCE_NOT_FOUND'], Http::STATUS_FORBIDDEN);
$response = new JSONResponse(new CloudFederationError(message: 'RESOURCE_NOT_FOUND'), Http::STATUS_FORBIDDEN);
$response->throttle();
return $response;
} catch (\Exception $e) {
return new JSONResponse(
['message' => 'Internal error at ' . $this->urlGenerator->getBaseUrl()],
Http::STATUS_BAD_REQUEST
);
} catch (Exception $e) {
return new JSONResponse(new CloudFederationValidationError(message: 'Internal error at ' . $this->urlGenerator->getBaseUrl()), Http::STATUS_BAD_REQUEST);
}

return new JSONResponse($result,Http::STATUS_CREATED);
return new JSONResponse($result, Http::STATUS_CREATED);
}

/**
Expand All @@ -293,7 +256,7 @@ public function receiveNotification($notificationType, $resourceType, $providerI
private function mapUid($uid) {
// FIXME this should be a method in the user management instead
$this->logger->debug('shareWith before, ' . $uid, ['app' => $this->appName]);
\OCP\Util::emitHook(
Util::emitHook(
'\OCA\Files_Sharing\API\Server2Server',
'preLoginNameUsedAsUserName',
['uid' => &$uid]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com>
*
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\CloudFederationAPI\Model\Response;

use JsonSerializable;

class CloudFederationAddShare implements JsonSerializable {
public function __construct(protected ?string $recipientDisplayName) {
}

public function jsonSerialize(): array {
return ['recipientDisplayName' => $this->recipientDisplayName];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php
declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com>
*
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\CloudFederationAPI\Model\Response;

use JsonSerializable;

class CloudFederationError implements JsonSerializable {
public function __construct(protected string $message) {
}

public function jsonSerialize(): array {
return ['message' => $this->message];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);

/**
* @copyright Copyright (c) 2023 Kate Döen <kate.doeen@nextcloud.com>
*
* @author Kate Döen <kate.doeen@nextcloud.com>
*
* @license GNU AGPL version 3 or any later version
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

namespace OCA\CloudFederationAPI\Model\Response;

use JsonSerializable;

class CloudFederationValidationError extends CloudFederationError implements JsonSerializable {
/**
* @param array{name: string|null, message: string|null}[]|null $validationErrors
*/
public function __construct(string $message, protected ?array $validationErrors = null) {
parent::__construct($message);
}

public function jsonSerialize(): array {
return array_merge(parent::jsonSerialize(), ['validationErrors' => $this->validationErrors]);
}
}
Loading

0 comments on commit 14c9c4f

Please sign in to comment.