@@ -267,7 +267,8 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse {
267267 'keyValuePairs ' => $ keyValuePairs
268268 ]);
269269
270- $ form = $ this ->getFormIfAllowed ($ formId );
270+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
271+ $ this ->obtainFormLock ($ form );
271272
272273 // Don't allow empty array
273274 if (sizeof ($ keyValuePairs ) === 0 ) {
@@ -277,6 +278,12 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse {
277278
278279 // Process owner transfer
279280 if (sizeof ($ keyValuePairs ) === 1 && key_exists ('ownerId ' , $ keyValuePairs )) {
281+ // Only allow owner transfer if current user is the form owner
282+ if ($ this ->currentUser ->getUID () !== $ form ->getOwnerId ()) {
283+ $ this ->logger ->debug ('Only the form owner can transfer ownership ' );
284+ throw new OCSForbiddenException ('Only the form owner can transfer ownership ' );
285+ }
286+
280287 $ this ->logger ->debug ('Updating owner: formId: {formId}, userId: {uid} ' , [
281288 'formId ' => $ formId ,
282289 'uid ' => $ keyValuePairs ['ownerId ' ]
@@ -477,7 +484,9 @@ public function getQuestion(int $formId, int $questionId): DataResponse {
477484 #[BruteForceProtection(action: 'form ' )]
478485 #[ApiRoute(verb: 'POST ' , url: '/api/v3/forms/{formId}/questions ' )]
479486 public function newQuestion (int $ formId , ?string $ type = null , string $ text = '' , ?int $ fromId = null ): DataResponse {
480- $ form = $ this ->getFormIfAllowed ($ formId );
487+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
488+ $ this ->obtainFormLock ($ form );
489+
481490 if ($ this ->formsService ->isFormArchived ($ form )) {
482491 $ this ->logger ->debug ('This form is archived and can not be modified ' );
483492 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -608,7 +617,9 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair
608617
609618 // Make sure we query the form first to check the user has permissions
610619 // So the user does not get information about "questions" if they do not even have permissions to the form
611- $ form = $ this ->getFormIfAllowed ($ formId );
620+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
621+ $ this ->obtainFormLock ($ form );
622+
612623 if ($ this ->formsService ->isFormArchived ($ form )) {
613624 $ this ->logger ->debug ('This form is archived and can not be modified ' );
614625 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -682,7 +693,9 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse {
682693 ]);
683694
684695
685- $ form = $ this ->getFormIfAllowed ($ formId );
696+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
697+ $ this ->obtainFormLock ($ form );
698+
686699 if ($ this ->formsService ->isFormArchived ($ form )) {
687700 $ this ->logger ->debug ('This form is archived and can not be modified ' );
688701 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -748,7 +761,9 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse {
748761 'newOrder ' => $ newOrder
749762 ]);
750763
751- $ form = $ this ->getFormIfAllowed ($ formId );
764+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
765+ $ this ->obtainFormLock ($ form );
766+
752767 if ($ this ->formsService ->isFormArchived ($ form )) {
753768 $ this ->logger ->debug ('This form is archived and can not be modified ' );
754769 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -847,7 +862,9 @@ public function newOption(int $formId, int $questionId, array $optionTexts): Dat
847862 'text ' => $ optionTexts ,
848863 ]);
849864
850- $ form = $ this ->getFormIfAllowed ($ formId );
865+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
866+ $ this ->obtainFormLock ($ form );
867+
851868 if ($ this ->formsService ->isFormArchived ($ form )) {
852869 $ this ->logger ->debug ('This form is archived and can not be modified ' );
853870 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -929,7 +946,9 @@ public function updateOption(int $formId, int $questionId, int $optionId, array
929946 'keyValuePairs ' => $ keyValuePairs
930947 ]);
931948
932- $ form = $ this ->getFormIfAllowed ($ formId );
949+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
950+ $ this ->obtainFormLock ($ form );
951+
933952 if ($ this ->formsService ->isFormArchived ($ form )) {
934953 $ this ->logger ->debug ('This form is archived and can not be modified ' );
935954 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -996,7 +1015,9 @@ public function deleteOption(int $formId, int $questionId, int $optionId): DataR
9961015 'optionId ' => $ optionId
9971016 ]);
9981017
999- $ form = $ this ->getFormIfAllowed ($ formId );
1018+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
1019+ $ this ->obtainFormLock ($ form );
1020+
10001021 if ($ this ->formsService ->isFormArchived ($ form )) {
10011022 $ this ->logger ->debug ('This form is archived and can not be modified ' );
10021023 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -1052,7 +1073,9 @@ public function deleteOption(int $formId, int $questionId, int $optionId): DataR
10521073 #[BruteForceProtection(action: 'form ' )]
10531074 #[ApiRoute(verb: 'PATCH ' , url: '/api/v3/forms/{formId}/questions/{questionId}/options ' )]
10541075 public function reorderOptions (int $ formId , int $ questionId , array $ newOrder ) {
1055- $ form = $ this ->getFormIfAllowed ($ formId );
1076+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
1077+ $ this ->obtainFormLock ($ form );
1078+
10561079 if ($ this ->formsService ->isFormArchived ($ form )) {
10571080 $ this ->logger ->debug ('This form is archived and can not be modified ' );
10581081 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -1790,6 +1813,12 @@ private function getFormIfAllowed(int $formId, string $permissions = 'all'): For
17901813 throw new NoSuchFormException ('This form is not owned by the current user and user has no `results_delete` permission ' , Http::STATUS_FORBIDDEN );
17911814 }
17921815 break ;
1816+ case Constants::PERMISSION_EDIT :
1817+ if (!$ this ->formsService ->canEditForm ($ form )) {
1818+ $ this ->logger ->debug ('This form is not owned by the current user and user has no `edit` permission ' );
1819+ throw new NoSuchFormException ('This form is not owned by the current user and user has no `edit` permission ' , Http::STATUS_FORBIDDEN );
1820+ }
1821+ break ;
17931822 default :
17941823 // By default we request full permissions
17951824 if ($ form ->getOwnerId () !== $ this ->currentUser ->getUID ()) {
@@ -1800,4 +1829,23 @@ private function getFormIfAllowed(int $formId, string $permissions = 'all'): For
18001829 }
18011830 return $ form ;
18021831 }
1832+
1833+ /**
1834+ * Locks the given form for the current user for a duration of 15 minutes.
1835+ *
1836+ * @param Form $form The form instance to lock.
1837+ */
1838+ private function obtainFormLock (Form $ form ): void {
1839+ // Only lock if not locked or locked by current user, or lock has expired
1840+ if (
1841+ $ form ->getLockedBy () === null ||
1842+ $ form ->getLockedBy () === $ this ->currentUser ->getUID () ||
1843+ $ form ->getLockedUntil () < time ()
1844+ ) {
1845+ $ form ->setLockedBy ($ this ->currentUser ->getUID ());
1846+ $ form ->setLockedUntil (time () + 15 * 60 );
1847+ } else {
1848+ throw new OCSForbiddenException ('Form is currently locked by another user. ' );
1849+ }
1850+ }
18031851}
0 commit comments