Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion appinfo/info.xml
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Loading