5252use OCP \IUser ;
5353use OCP \IUserManager ;
5454use OCP \IUserSession ;
55-
55+ use Psalm \ Internal \ Scanner \ UnresolvedConstant \ KeyValuePair ;
5656use Psr \Log \LoggerInterface ;
5757
5858/**
@@ -190,6 +190,8 @@ public function newForm(?int $fromId = null): DataResponse {
190190 unset($ formData ['state ' ]);
191191 unset($ formData ['fileId ' ]);
192192 unset($ formData ['fileFormat ' ]);
193+ unset($ formData ['locked_by ' ]);
194+ unset($ formData ['locked_until ' ]);
193195 $ formData ['hash ' ] = $ this ->formsService ->generateFormHash ();
194196 // TRANSLATORS Appendix to the form Title of a duplicated/copied form.
195197 $ formData ['title ' ] .= ' - ' . $ this ->l10n ->t ('Copy ' );
@@ -267,7 +269,9 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse {
267269 'keyValuePairs ' => $ keyValuePairs
268270 ]);
269271
270- $ form = $ this ->getFormIfAllowed ($ formId );
272+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
273+ $ currentUserId = $ this ->currentUser ->getUID ();
274+ $ this ->obtainFormLock ($ form );
271275
272276 // Don't allow empty array
273277 if (sizeof ($ keyValuePairs ) === 0 ) {
@@ -277,6 +281,12 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse {
277281
278282 // Process owner transfer
279283 if (sizeof ($ keyValuePairs ) === 1 && key_exists ('ownerId ' , $ keyValuePairs )) {
284+ // Only allow owner transfer if current user is the form owner
285+ if ($ currentUserId !== $ form ->getOwnerId ()) {
286+ $ this ->logger ->debug ('Only the form owner can transfer ownership ' );
287+ throw new OCSForbiddenException ('Only the form owner can transfer ownership ' );
288+ }
289+
280290 $ this ->logger ->debug ('Updating owner: formId: {formId}, userId: {uid} ' , [
281291 'formId ' => $ formId ,
282292 'uid ' => $ keyValuePairs ['ownerId ' ]
@@ -288,23 +298,55 @@ public function updateForm(int $formId, array $keyValuePairs): DataResponse {
288298 throw new OCSBadRequestException ('Could not find new form owner ' );
289299 }
290300
291- // update form owner
301+ // update form owner and remove form lock
292302 $ form ->setOwnerId ($ keyValuePairs ['ownerId ' ]);
303+ $ form ->setLockedBy (null );
304+ $ form ->setLockedUntil (null );
293305
294306 // Update changed Columns in Db.
295307 $ this ->formMapper ->update ($ form );
296308
297309 return new DataResponse ($ form ->getOwnerId ());
298310 }
299311
300- // Don't allow to change params id, hash, ownerId, created, lastUpdated, fileId
312+ // Process form unlocking
301313 if (
302- key_exists ( ' id ' , $ keyValuePairs ) || key_exists ( ' hash ' , $ keyValuePairs ) ||
303- key_exists ( ' ownerId ' , $ keyValuePairs ) || key_exists ( ' created ' , $ keyValuePairs) ||
304- isset ( $ keyValuePairs [ ' fileId ' ]) || key_exists ( ' lastUpdated ' , $ keyValuePairs )
314+ sizeof ( $ keyValuePairs ) === 2
315+ && array_key_exists ( ' locked_by ' , $ keyValuePairs ) && is_null ( $ keyValuePairs[ ' locked_by ' ])
316+ && array_key_exists ( ' locked_until ' , $ keyValuePairs ) && is_null ( $ keyValuePairs[ ' locked_until ' ] )
305317 ) {
306- $ this ->logger ->info ('Not allowed to update id, hash, ownerId, created, fileId or lastUpdated ' );
307- throw new OCSForbiddenException ('Not allowed to update id, hash, ownerId, created, fileId or lastUpdated ' );
318+ // Only allow form unlocking if for form owner or lock user
319+ if ($ currentUserId !== $ form ->getOwnerId () && $ currentUserId !== $ form ->getLockedBy ()) {
320+ $ this ->logger ->debug ('Only the form owner or the user who obtained the lock can unlock the form ' );
321+ throw new OCSForbiddenException ('Only the form owner or the user who obtained the lock can unlock the form ' );
322+ }
323+
324+ // remove form lock
325+ $ form ->setLockedBy (null );
326+ $ form ->setLockedUntil (null );
327+
328+ // Update changed Columns in Db.
329+ $ this ->formMapper ->update ($ form );
330+
331+ return new DataResponse ($ form ->getId ());
332+ }
333+
334+ // Don't allow to change the following attributes
335+ $ forbiddenKeys = [
336+ 'id ' , 'hash ' , 'ownerId ' , 'created ' , 'lastUpdated ' , 'lockedBy ' , 'lockedUntil '
337+ ];
338+
339+ foreach ($ forbiddenKeys as $ key ) {
340+ if (array_key_exists ($ key , $ keyValuePairs )) {
341+ $ this ->logger ->info ("Not allowed to update {$ key }" );
342+ throw new OCSForbiddenException ("Not allowed to update {$ key }" );
343+ }
344+ }
345+
346+ // Don't allow to change fileId
347+ if (isset ($ keyValuePairs ['fileId ' ])) {
348+ $ this ->logger ->info ('Not allowed to update fileId ' );
349+ throw new OCSForbiddenException ('Not allowed to update fileId ' );
308350 }
309351
310352 // Do not allow changing showToAllUsers if disabled
@@ -477,7 +519,9 @@ public function getQuestion(int $formId, int $questionId): DataResponse {
477519 #[BruteForceProtection(action: 'form ' )]
478520 #[ApiRoute(verb: 'POST ' , url: '/api/v3/forms/{formId}/questions ' )]
479521 public function newQuestion (int $ formId , ?string $ type = null , string $ text = '' , ?int $ fromId = null ): DataResponse {
480- $ form = $ this ->getFormIfAllowed ($ formId );
522+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
523+ $ this ->obtainFormLock ($ form );
524+
481525 if ($ this ->formsService ->isFormArchived ($ form )) {
482526 $ this ->logger ->debug ('This form is archived and can not be modified ' );
483527 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -608,7 +652,9 @@ public function updateQuestion(int $formId, int $questionId, array $keyValuePair
608652
609653 // Make sure we query the form first to check the user has permissions
610654 // So the user does not get information about "questions" if they do not even have permissions to the form
611- $ form = $ this ->getFormIfAllowed ($ formId );
655+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
656+ $ this ->obtainFormLock ($ form );
657+
612658 if ($ this ->formsService ->isFormArchived ($ form )) {
613659 $ this ->logger ->debug ('This form is archived and can not be modified ' );
614660 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -682,7 +728,9 @@ public function deleteQuestion(int $formId, int $questionId): DataResponse {
682728 ]);
683729
684730
685- $ form = $ this ->getFormIfAllowed ($ formId );
731+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
732+ $ this ->obtainFormLock ($ form );
733+
686734 if ($ this ->formsService ->isFormArchived ($ form )) {
687735 $ this ->logger ->debug ('This form is archived and can not be modified ' );
688736 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -748,7 +796,9 @@ public function reorderQuestions(int $formId, array $newOrder): DataResponse {
748796 'newOrder ' => $ newOrder
749797 ]);
750798
751- $ form = $ this ->getFormIfAllowed ($ formId );
799+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
800+ $ this ->obtainFormLock ($ form );
801+
752802 if ($ this ->formsService ->isFormArchived ($ form )) {
753803 $ this ->logger ->debug ('This form is archived and can not be modified ' );
754804 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -847,7 +897,9 @@ public function newOption(int $formId, int $questionId, array $optionTexts): Dat
847897 'text ' => $ optionTexts ,
848898 ]);
849899
850- $ form = $ this ->getFormIfAllowed ($ formId );
900+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
901+ $ this ->obtainFormLock ($ form );
902+
851903 if ($ this ->formsService ->isFormArchived ($ form )) {
852904 $ this ->logger ->debug ('This form is archived and can not be modified ' );
853905 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -929,7 +981,9 @@ public function updateOption(int $formId, int $questionId, int $optionId, array
929981 'keyValuePairs ' => $ keyValuePairs
930982 ]);
931983
932- $ form = $ this ->getFormIfAllowed ($ formId );
984+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
985+ $ this ->obtainFormLock ($ form );
986+
933987 if ($ this ->formsService ->isFormArchived ($ form )) {
934988 $ this ->logger ->debug ('This form is archived and can not be modified ' );
935989 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -996,7 +1050,9 @@ public function deleteOption(int $formId, int $questionId, int $optionId): DataR
9961050 'optionId ' => $ optionId
9971051 ]);
9981052
999- $ form = $ this ->getFormIfAllowed ($ formId );
1053+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
1054+ $ this ->obtainFormLock ($ form );
1055+
10001056 if ($ this ->formsService ->isFormArchived ($ form )) {
10011057 $ this ->logger ->debug ('This form is archived and can not be modified ' );
10021058 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -1052,7 +1108,9 @@ public function deleteOption(int $formId, int $questionId, int $optionId): DataR
10521108 #[BruteForceProtection(action: 'form ' )]
10531109 #[ApiRoute(verb: 'PATCH ' , url: '/api/v3/forms/{formId}/questions/{questionId}/options ' )]
10541110 public function reorderOptions (int $ formId , int $ questionId , array $ newOrder ) {
1055- $ form = $ this ->getFormIfAllowed ($ formId );
1111+ $ form = $ this ->getFormIfAllowed ($ formId , Constants::PERMISSION_EDIT );
1112+ $ this ->obtainFormLock ($ form );
1113+
10561114 if ($ this ->formsService ->isFormArchived ($ form )) {
10571115 $ this ->logger ->debug ('This form is archived and can not be modified ' );
10581116 throw new OCSForbiddenException ('This form is archived and can not be modified ' );
@@ -1797,6 +1855,12 @@ private function getFormIfAllowed(int $formId, string $permissions = 'all'): For
17971855 throw new NoSuchFormException ('This form is not owned by the current user and user has no `results_delete` permission ' , Http::STATUS_FORBIDDEN );
17981856 }
17991857 break ;
1858+ case Constants::PERMISSION_EDIT :
1859+ if (!$ this ->formsService ->canEditForm ($ form )) {
1860+ $ this ->logger ->debug ('This form is not owned by the current user and user has no `edit` permission ' );
1861+ throw new NoSuchFormException ('This form is not owned by the current user and user has no `edit` permission ' , Http::STATUS_FORBIDDEN );
1862+ }
1863+ break ;
18001864 default :
18011865 // By default we request full permissions
18021866 if ($ form ->getOwnerId () !== $ this ->currentUser ->getUID ()) {
@@ -1807,4 +1871,23 @@ private function getFormIfAllowed(int $formId, string $permissions = 'all'): For
18071871 }
18081872 return $ form ;
18091873 }
1874+
1875+ /**
1876+ * Locks the given form for the current user for a duration of 15 minutes.
1877+ *
1878+ * @param Form $form The form instance to lock.
1879+ */
1880+ private function obtainFormLock (Form $ form ): void {
1881+ // Only lock if not locked or locked by current user, or lock has expired
1882+ if (
1883+ $ form ->getLockedBy () === null ||
1884+ $ form ->getLockedBy () === $ this ->currentUser ->getUID () ||
1885+ $ form ->getLockedUntil () < time ()
1886+ ) {
1887+ $ form ->setLockedBy ($ this ->currentUser ->getUID ());
1888+ $ form ->setLockedUntil (time () + 15 * 60 );
1889+ } else {
1890+ throw new OCSForbiddenException ('Form is currently locked by another user. ' );
1891+ }
1892+ }
18101893}
0 commit comments