Skip to content

Commit

Permalink
Merge pull request #1087 from nextcloud/enh/sharing
Browse files Browse the repository at this point in the history
New Sharing
  • Loading branch information
jotoeri committed May 31, 2022
2 parents daf8282 + b3a4e1b commit e6231fa
Show file tree
Hide file tree
Showing 33 changed files with 3,205 additions and 1,187 deletions.
2 changes: 1 addition & 1 deletion appinfo/info.xml
Expand Up @@ -11,7 +11,7 @@
- **🔒 Data under your control!** Unlike in Google Forms, Typeform, Doodle and others, the survey info and responses are kept private on your instance.
- **🙋 Get involved!** We have lots of stuff planned like more question types, collaboration on forms, [and much more](https://github.com/nextcloud/forms/milestones)!
]]></description>
<version>2.5.0</version>
<version>3.0.0-alpha.0</version>
<licence>agpl</licence>

<author>Affan Hussain</author>
Expand Down
37 changes: 35 additions & 2 deletions appinfo/routes.php
Expand Up @@ -25,15 +25,22 @@

return [
'routes' => [
// Public Share Link
[
'name' => 'page#public_link_view',
'url' => '/s/{hash}',
'verb' => 'GET'

],
// Internal views
[
'name' => 'page#views',
'url' => '/{hash}/{view}',
'verb' => 'GET'
],
// Share-Link & public submit
// Internal Form Link
[
'name' => 'page#goto_form',
'name' => 'page#internal_link_view',
'url' => '/{hash}',
'verb' => 'GET'
],
Expand Down Expand Up @@ -95,6 +102,14 @@
'apiVersion' => 'v1(\.1)?'
]
],
[
'name' => 'api#getPartialForm',
'url' => '/api/{apiVersion}/partial_form/{hash}',
'verb' => 'GET',
'requirements' => [
'apiVersion' => 'v2'
]
],
[
'name' => 'api#getSharedForms',
'url' => '/api/{apiVersion}/shared_forms',
Expand Down Expand Up @@ -164,6 +179,24 @@
]
],

// Shares
[
'name' => 'shareApi#newShare',
'url' => '/api/{apiVersion}/share',
'verb' => 'POST',
'requirements' => [
'apiVersion' => 'v2'
]
],
[
'name' => 'shareApi#deleteShare',
'url' => '/api/{apiVersion}/share/{id}',
'verb' => 'DELETE',
'requirements' => [
'apiVersion' => 'v2'
]
],

// Submissions
[
'name' => 'api#getSubmissions',
Expand Down
4 changes: 2 additions & 2 deletions css/icons.scss
Expand Up @@ -38,8 +38,8 @@
@include icon-color('checkmark', 'actions', $color-success, 1, true);
}

.icon-clippy-primary {
@include icon-color('clippy', 'actions', $color-primary-text, 1, true);
.icon-share-primary {
@include icon-color('share', 'actions', $color-primary-text, 1, true);
}

.icon-comment-yes {
Expand Down
26 changes: 26 additions & 0 deletions lib/Constants.php
Expand Up @@ -23,6 +23,8 @@

namespace OCA\Forms;

use OCP\Share\IShare;

class Constants {
/**
* Maximum String lengths, the database is set to store.
Expand Down Expand Up @@ -82,4 +84,28 @@ class Constants {
self::ANSWER_TYPE_DATETIME => 'Y-m-d H:i',
self::ANSWER_TYPE_TIME => 'H:i'
];

/**
* !! Keep in sync with src/mixins/ShareTypes.js !!
*/
public const SHARE_TYPES_USED = [
IShare::TYPE_USER,
IShare::TYPE_GROUP,
IShare::TYPE_LINK
];

/**
* !! Keep in sync with src/mixins/PermissionTypes.js !!
* Permission values equal the route names, thus making it easy on frontend to evaluate.
*/
// Define Form Permissions
public const PERMISSION_EDIT = 'edit';
public const PERMISSION_RESULTS = 'results';
public const PERMISSION_SUBMIT = 'submit';

public const PERMISSION_ALL = [
self::PERMISSION_EDIT,
self::PERMISSION_RESULTS,
self::PERMISSION_SUBMIT
];
}
118 changes: 66 additions & 52 deletions lib/Controller/ApiController.php
Expand Up @@ -27,8 +27,6 @@

namespace OCA\Forms\Controller;

use Exception;

use OCA\Forms\Activity\ActivityManager;
use OCA\Forms\Constants;
use OCA\Forms\Db\Answer;
Expand All @@ -39,6 +37,7 @@
use OCA\Forms\Db\OptionMapper;
use OCA\Forms\Db\Question;
use OCA\Forms\Db\QuestionMapper;
use OCA\Forms\Db\ShareMapper;
use OCA\Forms\Db\Submission;
use OCA\Forms\Db\SubmissionMapper;
use OCA\Forms\Service\FormsService;
Expand Down Expand Up @@ -79,6 +78,9 @@ class ApiController extends OCSController {
/** @var QuestionMapper */
private $questionMapper;

/** @var ShareMapper */
private $shareMapper;

/** @var SubmissionMapper */
private $submissionMapper;

Expand Down Expand Up @@ -109,6 +111,7 @@ public function __construct(string $appName,
FormMapper $formMapper,
OptionMapper $optionMapper,
QuestionMapper $questionMapper,
ShareMapper $shareMapper,
SubmissionMapper $submissionMapper,
FormsService $formsService,
SubmissionService $submissionService,
Expand All @@ -125,6 +128,7 @@ public function __construct(string $appName,
$this->formMapper = $formMapper;
$this->optionMapper = $optionMapper;
$this->questionMapper = $questionMapper;
$this->shareMapper = $shareMapper;
$this->submissionMapper = $submissionMapper;
$this->formsService = $formsService;
$this->submissionService = $submissionService;
Expand All @@ -149,13 +153,7 @@ public function getForms(): DataResponse {

$result = [];
foreach ($forms as $form) {
$result[] = [
'id' => $form->getId(),
'hash' => $form->getHash(),
'title' => $form->getTitle(),
'expires' => $form->getExpires(),
'partial' => true
];
$result[] = $this->formsService->getPartialFormArray($form->getId());
}

return new DataResponse($result);
Expand All @@ -173,26 +171,41 @@ public function getSharedForms(): DataResponse {

$result = [];
foreach ($forms as $form) {
// Don't add if user is owner, user has no access, form has expired, form is link-shared
if ($form->getOwnerId() === $this->currentUser->getUID()
|| !$this->formsService->hasUserAccess($form->getId())
|| $this->formsService->hasFormExpired($form->getId())
|| $form->getAccess()['type'] === 'public') {
// Check if the form should be shown on sidebar
if (!$this->formsService->isSharedFormShown($form->getId())) {
continue;
}

$result[] = [
'id' => $form->getId(),
'hash' => $form->getHash(),
'title' => $form->getTitle(),
'expires' => $form->getExpires(),
'partial' => true
];
$result[] = $this->formsService->getPartialFormArray($form->getId());
}

return new DataResponse($result);
}

/**
* @NoAdminRequired
*
* Get a partial form by its hash. Implicitely checks, if the user has access.
*
* @param string $hash The form hash
* @return DataResponse
* @throws OCSBadRequestException if forbidden or not found
*/
public function getPartialForm(string $hash): DataResponse {
try {
$form = $this->formMapper->findByHash($hash);
} catch (IMapperException $e) {
$this->logger->debug('Could not find form');
throw new OCSBadRequestException();
}

if (!$this->formsService->hasUserAccess($form->getId())) {
$this->logger->debug('User has no permissions to get this form');
throw new OCSForbiddenException();
}

return new DataResponse($this->formsService->getPartialFormArray($form->getId()));
}

/**
* @NoAdminRequired
*
Expand Down Expand Up @@ -246,9 +259,8 @@ public function newForm(): DataResponse {
$form->setTitle('');
$form->setDescription('');
$form->setAccess([
'type' => 'public',
'users' => [],
'groups' => []
'permitAllUsers' => false,
'showToAllUsers' => false,
]);
$form->setSubmitOnce(true);

Expand All @@ -257,6 +269,7 @@ public function newForm(): DataResponse {
// Return like getForm(), just without loading Questions (as there are none).
$result = $form->read();
$result['questions'] = [];
$result['shares'] = [];

return new DataResponse($result);
}
Expand Down Expand Up @@ -371,28 +384,6 @@ public function updateForm(int $id, array $keyValuePairs): DataResponse {
throw new OCSForbiddenException();
}

// Handle access-changes
if (array_key_exists('access', $keyValuePairs)) {
// Make sure we only store id of shares
try {
$keyValuePairs['access']['users'] = array_map(function (array $user): string {
return $user['shareWith'];
}, $keyValuePairs['access']['users']);
$keyValuePairs['access']['groups'] = array_map(function (array $group): string {
return $group['shareWith'];
}, $keyValuePairs['access']['groups']);
} catch (Exception $e) {
$this->logger->debug('Malformed access');
throw new OCSBadRequestException('Malformed access');
}

// For selected sharing, notify users (creates Activity)
if ($keyValuePairs['access']['type'] === 'selected') {
$oldAccess = $form->getAccess();
$this->formsService->notifyNewShares($form, $oldAccess, $keyValuePairs['access']);
}
}

// Create FormEntity with given Params & Id.
$form = Form::fromParams($keyValuePairs);
$form->setId($id);
Expand Down Expand Up @@ -947,14 +938,16 @@ public function getSubmissions(string $hash): DataResponse {
*
* @param int $formId the form id
* @param array $answers [question_id => arrayOfString]
* @param string $shareHash public share-hash -> Necessary to submit on public link-shares.
* @return DataResponse
* @throws OCSBadRequestException
* @throws OCSForbiddenException
*/
public function insertSubmission(int $formId, array $answers): DataResponse {
$this->logger->debug('Inserting submission: formId: {formId}, answers: {answers}', [
public function insertSubmission(int $formId, array $answers, string $shareHash = ''): DataResponse {
$this->logger->debug('Inserting submission: formId: {formId}, answers: {answers}, shareHash: {shareHash}', [
'formId' => $formId,
'answers' => $answers,
'shareHash' => $shareHash,
]);

try {
Expand All @@ -965,9 +958,30 @@ public function insertSubmission(int $formId, array $answers): DataResponse {
throw new OCSBadRequestException();
}

// Does the user have access to the form
if (!$this->formsService->hasUserAccess($form->getId())) {
throw new OCSForbiddenException('Not allowed to access this form');
// Does the user have access to the form (Either by logged in user, or by providing public share-hash.)
try {
$isPublicShare = false;

// If hash given, find the corresponding share & check if hash corresponds to given formId.
if ($shareHash !== '') {
// public by legacy Link
if ($form->getAccess()['legacyLink'] && $shareHash === $form->getHash()) {
$isPublicShare = true;
}

// Public link share
$share = $this->shareMapper->findPublicShareByHash($shareHash);
if ($share->getFormId() === $formId) {
$isPublicShare = true;
}
}
} catch (DoesNotExistException $e) {
// $isPublicShare already false.
} finally {
// Now forbid, if no public share and no direct share.
if (!$isPublicShare && !$this->formsService->hasUserAccess($form->getId())) {
throw new OCSForbiddenException('Not allowed to access this form');
}
}

// Not allowed if form has expired.
Expand Down

0 comments on commit e6231fa

Please sign in to comment.