diff --git a/lib/Controller/ApiController.php b/lib/Controller/ApiController.php
index 7e4c6cfc0..b4dec544f 100644
--- a/lib/Controller/ApiController.php
+++ b/lib/Controller/ApiController.php
@@ -1016,9 +1016,13 @@ public function deleteSubmission(int $id): DataResponse {
throw new OCSBadRequestException();
}
+ // Either the current user needs to be the owner
if ($form->getOwnerId() !== $this->currentUser->getUID()) {
- $this->logger->debug('This form is not owned by the current user');
- throw new OCSForbiddenException();
+ // Or has permissions to remove submissions
+ if (!$this->formsService->canDeleteResults($form)) {
+ $this->logger->debug('This form is not owned by the current user and user has no results_delete permission');
+ throw new OCSForbiddenException();
+ }
}
// Delete submission (incl. Answers)
diff --git a/lib/Controller/ShareApiController.php b/lib/Controller/ShareApiController.php
index 75924e4e1..29fffefc0 100644
--- a/lib/Controller/ShareApiController.php
+++ b/lib/Controller/ShareApiController.php
@@ -313,6 +313,11 @@ protected function validatePermissions(array $permissions, int $shareType): bool
return false;
}
+ if (in_array(Constants::PERMISSION_RESULTS_DELETE, $sanitizedPermissions) && !in_array(Constants::PERMISSION_RESULTS, $sanitizedPermissions)) {
+ $this->logger->debug('Permission results_delete is only allowed when permission results is also set');
+ return false;
+ }
+
// Make sure only users can have special permissions
if (count($sanitizedPermissions) > 1) {
switch ($shareType) {
diff --git a/lib/Service/FormsService.php b/lib/Service/FormsService.php
index 8a0a8e2ef..0af552978 100644
--- a/lib/Service/FormsService.php
+++ b/lib/Service/FormsService.php
@@ -252,6 +252,16 @@ public function canSeeResults(Form $form): bool {
return in_array(Constants::PERMISSION_RESULTS, $this->getPermissions($form));
}
+ /**
+ * Can the current user delete results of a form
+ *
+ * @param Form $form
+ * @return boolean
+ */
+ public function canDeleteResults(Form $form): bool {
+ return in_array(Constants::PERMISSION_RESULTS_DELETE, $this->getPermissions($form));
+ }
+
/**
* Can the user submit a form
*
@@ -475,7 +485,7 @@ public function notifyNewSubmission(Form $form, string $submitter): void {
*
* @param int $formId The form to query shares for
* @param string $userId The user to check if shared with
- * @return array
+ * @return Share[]
*/
protected function getSharesWithUser(int $formId, string $userId): array {
$shareEntities = $this->shareMapper->findByForm($formId);
diff --git a/src/components/SidebarTabs/SharingShareDiv.vue b/src/components/SidebarTabs/SharingShareDiv.vue
index 462432c12..32795e70c 100644
--- a/src/components/SidebarTabs/SharingShareDiv.vue
+++ b/src/components/SidebarTabs/SharingShareDiv.vue
@@ -35,6 +35,9 @@
{{ t('forms', 'View responses') }}
+
+ {{ t('forms', 'Delete responses') }}
+
@@ -82,6 +85,9 @@ export default {
canAccessResults() {
return this.share.permissions.includes(this.PERMISSION_TYPES.PERMISSION_RESULTS)
},
+ canDeleteResults() {
+ return this.share.permissions.includes(this.PERMISSION_TYPES.PERMISSION_RESULTS_DELETE)
+ },
isNoUser() {
return this.share.shareType !== this.SHARE_TYPES.SHARE_TYPE_USER
},
@@ -109,9 +115,20 @@ export default {
* @param {boolean} hasPermission If the results permission should be granted
*/
updatePermissionResults(hasPermission) {
+ if (hasPermission === false) {
+ // ensure to remove the delete permission if results permission is dropped
+ this.updatePermission(this.PERMISSION_TYPES.PERMISSION_RESULTS_DELETE, false)
+ }
return this.updatePermission(this.PERMISSION_TYPES.PERMISSION_RESULTS, hasPermission)
},
+ /**
+ * @param {boolean} hasPermission If the results_delete permission should be granted
+ */
+ updatePermissionDeleteResults(hasPermission) {
+ return this.updatePermission(this.PERMISSION_TYPES.PERMISSION_RESULTS_DELETE, hasPermission)
+ },
+
/**
* Grant or remove permission from share
*
diff --git a/src/views/Results.vue b/src/views/Results.vue
index de7df5dd7..b11e294e0 100644
--- a/src/views/Results.vue
+++ b/src/views/Results.vue
@@ -276,6 +276,7 @@ export default {
try {
await axios.delete(generateOcsUrl('apps/forms/api/v2.1/submission/{id}', { id }))
+ showSuccess(t('forms', 'Submission deleted'))
const index = this.form.submissions.findIndex(search => search.id === id)
this.form.submissions.splice(index, 1)
emit('forms:last-updated:set', this.form.id)
diff --git a/tests/Unit/Controller/ShareApiControllerTest.php b/tests/Unit/Controller/ShareApiControllerTest.php
index d235a612c..97fff8ad7 100644
--- a/tests/Unit/Controller/ShareApiControllerTest.php
+++ b/tests/Unit/Controller/ShareApiControllerTest.php
@@ -594,6 +594,21 @@ public function dataUpdateShare() {
'expected' => 1,
'exception' => null
],
+ 'valid-permissions-share-delete' => [
+ 'share' => [
+ 'id' => 1,
+ 'formId' => 5,
+ 'shareType' => 0,
+ 'shareWith' => 'user1',
+ 'permissions' => [Constants::PERMISSION_SUBMIT]
+ ],
+ 'formOwner' => 'currentUser',
+ 'keyValuePairs' => [
+ 'permissions' => [Constants::PERMISSION_RESULTS, Constants::PERMISSION_RESULTS_DELETE, Constants::PERMISSION_SUBMIT]
+ ],
+ 'expected' => 1,
+ 'exception' => null
+ ],
'no-permission' => [
'share' => [
'id' => 1,
@@ -609,6 +624,37 @@ public function dataUpdateShare() {
'expected' => null,
'exception' => '\OCP\AppFramework\OCS\OCSBadRequestException'
],
+ 'invalid-permission-missing-submit' => [
+ 'share' => [
+ 'id' => 1,
+ 'formId' => 5,
+ 'shareType' => 0,
+ 'shareWith' => 'user1',
+ 'permissions' => [Constants::PERMISSION_SUBMIT],
+ ],
+ 'formOwner' => 'currentUser',
+ 'keyValuePairs' => [
+ 'permissions' => [Constants::PERMISSION_RESULTS_DELETE],
+ ],
+ 'expected' => null,
+ 'exception' => '\OCP\AppFramework\OCS\OCSBadRequestException'
+ ],
+ // PERMISSION_RESULTS_DELETE is only allowed if PERMISSION_RESULTS is set
+ 'invalid-permission-missing-results' => [
+ 'share' => [
+ 'id' => 1,
+ 'formId' => 5,
+ 'shareType' => 0,
+ 'shareWith' => 'user1',
+ 'permissions' => [Constants::PERMISSION_SUBMIT],
+ ],
+ 'formOwner' => 'currentUser',
+ 'keyValuePairs' => [
+ 'permissions' => [Constants::PERMISSION_SUBMIT, Constants::PERMISSION_RESULTS_DELETE],
+ ],
+ 'expected' => null,
+ 'exception' => '\OCP\AppFramework\OCS\OCSBadRequestException'
+ ],
'invalid-permission' => [
'share' => [
'id' => 1,
diff --git a/tests/Unit/Service/FormsServiceTest.php b/tests/Unit/Service/FormsServiceTest.php
index b03841cc4..1f62e4bd1 100644
--- a/tests/Unit/Service/FormsServiceTest.php
+++ b/tests/Unit/Service/FormsServiceTest.php
@@ -666,6 +666,73 @@ public function testCanSeeResults(string $ownerId, array $sharesArray, bool $exp
$this->assertEquals($expected, $this->formsService->canSeeResults($form));
}
+ public function dataCanDeleteResults() {
+ return [
+ 'allowFormOwner' => [
+ 'ownerId' => 'currentUser',
+ 'sharesArray' => [],
+ 'expected' => true
+ ],
+ 'allowShared' => [
+ 'ownerId' => 'someUser',
+ 'sharesArray' => [[
+ 'with' => 'currentUser',
+ 'type' => 0,
+ 'permissions' => [Constants::PERMISSION_SUBMIT, Constants::PERMISSION_RESULTS, Constants::PERMISSION_RESULTS_DELETE],
+ ]],
+ 'expected' => true
+ ],
+ 'disallowNotowned' => [
+ 'ownerId' => 'someUser',
+ 'sharesArray' => [],
+ 'expected' => false
+ ],
+ 'allowNotShared' => [
+ 'ownerId' => 'someUser',
+ 'sharesArray' => [[
+ 'with' => 'currentUser',
+ 'type' => 0,
+ 'permissions' => [Constants::PERMISSION_SUBMIT, Constants::PERMISSION_RESULTS],
+ ]],
+ 'expected' => false
+ ]
+ ];
+ }
+ /**
+ * @dataProvider dataCanDeleteResults
+ *
+ * @param string $ownerId
+ * @param array $sharesArray
+ * @param bool $expected
+ */
+ public function testCanDeleteResults(string $ownerId, array $sharesArray, bool $expected) {
+ $form = new Form();
+ $form->setId(42);
+ $form->setOwnerId($ownerId);
+ $form->setAccess([
+ 'permitAllUsers' => false,
+ 'showToAllUsers' => false,
+ ]);
+
+ $shares = [];
+ foreach ($sharesArray as $id => $share) {
+ $shareEntity = new Share();
+ $shareEntity->setId($id);
+ $shareEntity->setFormId(42);
+ $shareEntity->setShareType($share['type']);
+ $shareEntity->setShareWith($share['with']);
+ $shareEntity->setPermissions($share['permissions']);
+ $shares[] = $shareEntity;
+ }
+
+ $this->shareMapper->expects($this->any())
+ ->method('findByForm')
+ ->with(42)
+ ->willReturn($shares);
+
+ $this->assertEquals($expected, $this->formsService->canDeleteResults($form));
+ }
+
public function dataCanSubmit() {
return [
'allowFormOwner' => [