Skip to content

Commit

Permalink
feat: add API v3 routes
Browse files Browse the repository at this point in the history
Signed-off-by: Christian Hartmann <[email protected]>
  • Loading branch information
Chartman123 committed Aug 14, 2024
1 parent 20d088e commit 429c7c6
Show file tree
Hide file tree
Showing 21 changed files with 3,508 additions and 569 deletions.
499 changes: 181 additions & 318 deletions appinfo/routes.php

Large diffs are not rendered by default.

1,425 changes: 1,336 additions & 89 deletions lib/Controller/ApiController.php

Large diffs are not rendered by default.

272 changes: 270 additions & 2 deletions lib/Controller/ShareApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,274 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar
return new DataResponse($shareData);
}

/**
* @CORS
* @NoAdminRequired
*
* Update permissions of a share
*
* @param int $formId of the form
* @param int $shareId of the share to update
* @param array $keyValuePairs Array of key=>value pairs to update.
* @return DataResponse
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function updateShare(int $formId, int $shareId, array $keyValuePairs): DataResponse {
$this->logger->debug('Updating share: {shareId} of form {formId}, permissions: {permissions}', [
'formId' => $formId,
'shareId' => $shareId,
'keyValuePairs' => $keyValuePairs
]);

try {
$formShare = $this->shareMapper->findById($shareId);
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find share', ['exception' => $e]);
throw new OCSBadRequestException('Could not find share');
}

if ($formId !== $formShare->getFormId()) {
$this->logger->debug('This share doesn\'t belong to the given Form');
throw new OCSBadRequestException('Share doesn\'t belong to given Form');
}

if ($form->getOwnerId() !== $this->currentUser->getUID()) {
$this->logger->debug('This form is not owned by the current user');
throw new OCSForbiddenException();
}

// Don't allow empty array
if (sizeof($keyValuePairs) === 0) {
$this->logger->info('Empty keyValuePairs, will not update.');
throw new OCSForbiddenException();
}

//Don't allow to change other properties than permissions
if (count($keyValuePairs) > 1 || !key_exists('permissions', $keyValuePairs)) {
$this->logger->debug('Not allowed to update other properties than permissions');
throw new OCSForbiddenException();
}

if (!$this->validatePermissions($keyValuePairs['permissions'], $formShare->getShareType())) {
throw new OCSBadRequestException('Invalid permission given');
}

$formShare->setPermissions($keyValuePairs['permissions']);
$formShare = $this->shareMapper->update($formShare);

if (in_array($formShare->getShareType(), [IShare::TYPE_USER, IShare::TYPE_GROUP, IShare::TYPE_USERGROUP, IShare::TYPE_CIRCLE], true)) {
$userFolder = $this->storage->getUserFolder($form->getOwnerId());
$uploadedFilesFolderPath = $this->formsService->getFormUploadedFilesFolderPath($form);
if ($userFolder->nodeExists($uploadedFilesFolderPath)) {
$folder = $userFolder->get($uploadedFilesFolderPath);
} else {
$folder = $userFolder->newFolder($uploadedFilesFolderPath);
}
/** @var \OCP\Files\Folder $folder */

if (in_array(Constants::PERMISSION_RESULTS, $keyValuePairs['permissions'], true)) {
$folderShare = $this->shareManager->newShare();
$folderShare->setShareType($formShare->getShareType());
$folderShare->setSharedWith($formShare->getShareWith());
$folderShare->setSharedBy($form->getOwnerId());
$folderShare->setPermissions(\OCP\Constants::PERMISSION_READ);
$folderShare->setNode($folder);
$folderShare->setShareOwner($form->getOwnerId());

$this->shareManager->createShare($folderShare);
} else {
$folderShares = $this->shareManager->getSharesBy($form->getOwnerId(), $formShare->getShareType(), $folder);
foreach ($folderShares as $folderShare) {
if ($folderShare->getSharedWith() === $formShare->getShareWith()) {
$this->shareManager->deleteShare($folderShare);
}
}
}
}

$this->formsService->setLastUpdatedTimestamp($formId);

return new DataResponse($formShare->getId());
}

/**
* @CORS
* @NoAdminRequired
*
* Delete a share
*
* @param int $formId of the form
* @param int $shareId of the share to delete
* @return DataResponse
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function deleteShare(int $formId, int $shareId): DataResponse {
$this->logger->debug('Deleting share: {shareId} of form {formId}', [
'formId' => $formId,
'shareId' => $shareId,
]);

try {
$share = $this->shareMapper->findById($shareId);
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find share', ['exception' => $e]);
throw new OCSBadRequestException('Could not find share');
}

if ($formId !== $share->getFormId()) {
$this->logger->debug('This share doesn\'t belong to the given Form');
throw new OCSBadRequestException('Share doesn\'t belong to given Form');
}

if ($form->getOwnerId() !== $this->currentUser->getUID()) {
$this->logger->debug('This form is not owned by the current user');
throw new OCSForbiddenException();
}

$this->shareMapper->deleteById($shareId);

$this->formsService->setLastUpdatedTimestamp($formId);

return new DataResponse($shareId);
}

/*
*
* Legacy API v2 methods (TODO: remove with Forms v5)
*
*/

/**
* @CORS
* @NoAdminRequired
*
* Add a new share
*
* @param int $formId The form to share
* @param int $shareType Nextcloud-ShareType
* @param string $shareWith ID of user/group/... to share with. For Empty shareWith and shareType Link, this will be set as RandomID.
* @return DataResponse
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function newShareLegacy(int $formId, int $shareType, string $shareWith = '', array $permissions = [Constants::PERMISSION_SUBMIT]): DataResponse {
$this->logger->debug('Adding new share: formId: {formId}, shareType: {shareType}, shareWith: {shareWith}, permissions: {permissions}', [
'formId' => $formId,
'shareType' => $shareType,
'shareWith' => $shareWith,
'permissions' => $permissions,
]);

// Only accept usable shareTypes
if (array_search($shareType, Constants::SHARE_TYPES_USED) === false) {
$this->logger->debug('Invalid shareType');
throw new OCSBadRequestException('Invalid shareType');
}

// Block LinkShares if not allowed
if ($shareType === IShare::TYPE_LINK && !$this->configService->getAllowPublicLink()) {
$this->logger->debug('Link Share not allowed.');
throw new OCSForbiddenException('Link Share not allowed.');
}

try {
$form = $this->formMapper->findById($formId);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form', ['exception' => $e]);
throw new OCSBadRequestException('Could not find form');
}

// Check for permission to share form
if ($form->getOwnerId() !== $this->currentUser->getUID()) {
$this->logger->debug('This form is not owned by the current user');
throw new OCSForbiddenException();
}

if (!$this->validatePermissions($permissions, $shareType)) {
throw new OCSBadRequestException('Invalid permission given');
}

// Create public-share hash, if necessary.
if ($shareType === IShare::TYPE_LINK) {
$shareWith = $this->secureRandom->generate(
24,
ISecureRandom::CHAR_HUMAN_READABLE
);
}

// Check for valid shareWith, needs to be done separately per shareType
switch ($shareType) {
case IShare::TYPE_USER:
if (!($this->userManager->get($shareWith) instanceof IUser)) {
$this->logger->debug('Invalid user to share with.');
throw new OCSBadRequestException('Invalid user to share with.');
}
break;

case IShare::TYPE_GROUP:
if (!($this->groupManager->get($shareWith) instanceof IGroup)) {
$this->logger->debug('Invalid group to share with.');
throw new OCSBadRequestException('Invalid group to share with.');
}
break;

case IShare::TYPE_LINK:
// Check if hash already exists. (Unfortunately not possible here by unique index on db.)
try {
// Try loading a share to the hash.
$nonex = $this->shareMapper->findPublicShareByHash($shareWith);

// If we come here, a share has been found --> The share hash already exists, thus aborting.
$this->logger->debug('Share Hash already exists.');
throw new OCSException('Share Hash exists. Please retry.');
} catch (DoesNotExistException $e) {
// Just continue, this is what we expect to happen (share hash not existing yet).
}
break;

case IShare::TYPE_CIRCLE:
if (!$this->circlesService->isCirclesEnabled()) {
$this->logger->debug('Teams app is disabled, sharing to teams not possible.');
throw new OCSException('Teams app is disabled.');
}
$circle = $this->circlesService->getCircle($shareWith);
if (is_null($circle)) {
$this->logger->debug('Invalid team to share with.');
throw new OCSBadRequestException('Invalid team to share with.');
}
break;

default:
// This passed the check for used shareTypes, but has not been found here.
$this->logger->warning('Unknown, but used shareType: {shareType}. Please file an issue on GitHub.', [ 'shareType' => $shareType ]);
throw new OCSException('Unknown shareType.');
}

$share = new Share();
$share->setFormId($formId);
$share->setShareType($shareType);
$share->setShareWith($shareWith);
$share->setPermissions($permissions);

/** @var Share */
$share = $this->shareMapper->insert($share);

// Create share-notifications (activity)
$this->formsService->notifyNewShares($form, $share);

$this->formsService->setLastUpdatedTimestamp($formId);

// Append displayName for Frontend
$shareData = $share->read();
$shareData['displayName'] = $this->formsService->getShareDisplayName($shareData);

return new DataResponse($shareData);
}

/**
* @CORS
* @NoAdminRequired
Expand All @@ -214,7 +482,7 @@ public function newShare(int $formId, int $shareType, string $shareWith = '', ar
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function deleteShare(int $id): DataResponse {
public function deleteShareLegacy(int $id): DataResponse {
$this->logger->debug('Deleting share: {id}', [
'id' => $id
]);
Expand Down Expand Up @@ -251,7 +519,7 @@ public function deleteShare(int $id): DataResponse {
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function updateShare(int $id, array $keyValuePairs): DataResponse {
public function updateShareLegacy(int $id, array $keyValuePairs): DataResponse {
$this->logger->debug('Updating share: {id}, permissions: {permissions}', [
'id' => $id,
'keyValuePairs' => $keyValuePairs
Expand Down
34 changes: 34 additions & 0 deletions lib/Service/FormsService.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,40 @@ public function getQuestions(int $formId): array {
}
}

/**
* Load specific question
*
* @param integer $questionId id of the question
* @return array
*/
public function getQuestion(int $questionId): array {
$question = [];
try {
$questionEntity = $this->questionMapper->findById($questionId);
$question = $questionEntity->read();
$question['options'] = $this->getOptions($question['id']);
$question['accept'] = [];
if ($question['type'] === Constants::ANSWER_TYPE_FILE) {
if ($question['extraSettings']['allowedFileTypes'] ?? null) {
$question['accept'] = array_keys(array_intersect(
$this->mimeTypeDetector->getAllAliases(),
$question['extraSettings']['allowedFileTypes']
));
}

if ($question['extraSettings']['allowedFileExtensions'] ?? null) {
foreach ($question['extraSettings']['allowedFileExtensions'] as $extension) {
$question['accept'][] = '.' . $extension;
}
}
}
} catch (DoesNotExistException $e) {
//handle silently
} finally {
return $question;
}
}

/**
* Load shares corresponding to form
*
Expand Down
10 changes: 5 additions & 5 deletions src/Forms.vue
Original file line number Diff line number Diff line change
Expand Up @@ -362,7 +362,7 @@ export default {
// Load Owned forms
try {
const response = await axios.get(
generateOcsUrl('apps/forms/api/v2.4/forms'),
generateOcsUrl('apps/forms/api/v3/forms'),
)
this.forms = OcsResponse2Data(response)
} catch (error) {
Expand All @@ -375,7 +375,7 @@ export default {
// Load shared forms
try {
const response = await axios.get(
generateOcsUrl('apps/forms/api/v2.4/shared_forms'),
generateOcsUrl('apps/forms/api/v3/forms?type=shared'),
)
this.allSharedForms = OcsResponse2Data(response)
} catch (error) {
Expand Down Expand Up @@ -413,7 +413,7 @@ export default {
) {
try {
const response = await axios.get(
generateOcsUrl('apps/forms/api/v2.4/partial_form/{hash}', {
generateOcsUrl('apps/forms/api/v3/forms/0?hash={hash}', {
hash,
}),
)
Expand Down Expand Up @@ -447,7 +447,7 @@ export default {
try {
// Request a new empty form
const response = await axios.post(
generateOcsUrl('apps/forms/api/v2.4/form'),
generateOcsUrl('apps/forms/api/v3/forms'),
)
const newForm = OcsResponse2Data(response)
this.forms.unshift(newForm)
Expand All @@ -467,7 +467,7 @@ export default {
async onCloneForm(id) {
try {
const response = await axios.post(
generateOcsUrl('apps/forms/api/v2.4/form/clone/{id}', { id }),
generateOcsUrl('apps/forms/api/v3/forms?fromId={id}', { id }),
)
const newForm = OcsResponse2Data(response)
this.forms.unshift(newForm)
Expand Down
Loading

0 comments on commit 429c7c6

Please sign in to comment.