forms
forms copied to clipboard
Failed to export Submissions: Not allowed to write to file
Please use the π reaction to show that you are affected by the same issue. Please don't comment if you have no relevant information to add!
Describe the bug
A Nextcloud instance that did have a working Forms app now generates an error on form submission.
To Reproduce Steps to reproduce the behaviour:
- Go to the form
- Complete it
- Click "Submit"
- The use has the error "There was an error submitting the form"
Expected behavior
A successfully submission.
Nextcloud (please complete the following information):
- Nextcloud-Version: 28.0.4.1
- Forms-Version: 4.2.0
Desktop (please complete the following information):
- OS: Debian
- Browser Firefox
- Version [e.g. 22]
Smartphone (please complete the following information):
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser Firefox/
- Version 126.0
Browser log
[ERROR] forms: Error while submitting the form
Object { app: "forms", level: 2, error: {β¦} }
NcSettingsSection-NSrCZ23G-6nKKlVz9.mjs:2:337806
value https://cloud.example.org/apps/forms/js/NcSettingsSection-NSrCZ23G-6nKKlVz9.mjs:2
value https://cloud.example.org/apps/forms/js/NcSettingsSection-NSrCZ23G-6nKKlVz9.mjs:2
onConfirmedSubmit https://cloud.example.org/apps/forms/js/Submit-PS6lxYc-.mjs:2
Nextcloud log
The following is written to the Nextcloud log:
Failed to export Submissions: Not allowed to write to file
Server logs
There is nothing in the Apache or PHP-FPM logs.
TMPDIR
The tempdirectory is writable.
Additional context
The error that is produced appears to be here:
https://github.com/nextcloud/forms/blob/1ba3a1404c3a23eedd8010175b60f10c5a70f688/lib/Service/SubmissionService.php#L226
The change to the server (the forms were working prior to this change), was updating from Debian Bullseye to Debian Bookworm, the instance uses PHP-FPM 8.3.
I have tried to duplicate this on another server with the same configuration but haven't been able to.
Did you restart your php processes? Are you using php-fpm? We had lots of errors in the past after upgrades due to cached code...
Browser log
Could you please provide the full object from the log? Including the error message?
@Chartman123 PHP-FPM had been restarted.
@susnux here is a full log entry, all I have changed is the IP address and I also ran it through jq for formatting:
{
"reqId": "oLRtH9XtF8rwtUiZidOT",
"level": 2,
"time": "2024-04-15T19:08:20+00:00",
"remoteAddr": "X.X.X.X",
"user": "--",
"app": "forms",
"method": "POST",
"url": "/ocs/v2.php/apps/forms/api/v2.4/submission/insert",
"message": "Failed to export Submissions: Not allowed to write to file",
"userAgent": "Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0",
"version": "28.0.4.1",
"data": {
"app": "forms"
}
}
Thank you, but @chriscroome what I meant is from you browser console there should be the network request that fails, could you please provide the response from the server?
For me it works and then looks like this:
But for you there should be a more meaningful error message
This is what I have after exporting it and changing the domain name:
[ERROR] forms: Error while submitting the form
Object { app: "forms", level: 2, error: {β¦} }
NcSettingsSection-NSrCZ23G-6nKKlVz9.mjs:2:337806
value https://cloud.example.com/apps/forms/js/NcSettingsSection-NSrCZ23G-6nKKlVz9.mjs:2
value https://cloud.example.com/apps/forms/js/NcSettingsSection-NSrCZ23G-6nKKlVz9.mjs:2
onConfirmedSubmit https://cloud.example.com/apps/forms/js/Submit-PS6lxYc-.mjs:2
Is that what you need?
I have also found 500 errors in the Apache and PHP-FPM logs, this in from the PHP-FPM access log:
- - 15/Apr/2024:19:31:23 +0000 "POST /ocs/v2.php" 500 /home/cloud/sites/nextcloud/ocs/v2.php 616.068 10240 45.45%
And this is from the Apache error log:
X.X.X.X - - [15/Apr/2024:19:31:23 +0000] "POST /ocs/v2.php/apps/forms/api/v2.4/submission/insert HTTP/2.0" 500 855 "-" "Mozilla/5.0 (X11; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0"
I could reproduce this on the instance, but there is not much helpful information for further debugging. I guess some permission issues.
Where is the file located you want to export submissions to? A groupfolder? Local storage? Is the file or parent folder shared?
I didn't set the form up so I don't know the answers to these questions I'm afraid but I have asked our client if they know the answers.
I think I have found the csv file on the file system and there doesn't apper to be any issue with the permissions or ownership -- the file is owned by the same user that PHP-FPM runs as and is 0644, the directory is 0775 and also owned by the same user that PHP-FPM runs as.
The issue is still present on the instance mentioned by chriscroome.
I was able to make a new test form that leads to the same error message "There was an error submitting the form" whenever a submission is attempted by a user (except the owner of the form, for whom there is not such message). The error comes up after a result spreadsheet is saved in a folder shared with the form owner (with edit rights).
If no spreadsheet is created, or if it is created in the form owner's personal space, no such issue was noticed.
@mariomorvan thanks for sharing this information with us. Are those "normal" shared folders or group folders or something else?
@mariomorvan thanks for sharing this information with us. Are those "normal" shared folders or group folders or something else?
@Chartman123 thanks for looking into this. Normal share as far as I understand the different kind of shares (we do not have installed group folders)
the logs show the following message associated with today's submission errors:
Exception OC\Files\View::basicOperation(): Argument #2 ($path) must be of type string, null given, called in /home/cloud/sites/nextcloud/lib/private/Files/View.php on line 528 in file '/home/cloud/sites/nextcloud/lib/private/Files/View.php' line 1128
Exception thrown: Exception
@mariomorvan @susnux I could reproduce it this way:
- User A shares folder with user B with full permissions
- User B creates a form and links a spreadsheet in the shared folder
- User B fills the form: everything works fine
- If shared to all users of the instance: submission by logged in users works
- Someone fills and submits the form via public share link (not logged in): submission fails
What's strange to me is that I don't get the original warning/error message but an error 500. Perhaps something else changed in the meantime. Heres the server logfile attached that I could see:
Logfile
{
"reqId": "xavjYSv75SOGCm3N4niG",
"level": 3,
"time": "2024-05-15T08:43:50+00:00",
"remoteAddr": "62.225.12.220",
"user": "--",
"app": "no app in context",
"method": "POST",
"url": "/ocs/v2.php/apps/forms/api/v2.4/submission/insert",
"message": "OC\\Files\\View::basicOperation(): Argument #2 ($path) must be of type string, null given, called in /var/www/html/lib/private/Files/View.php on line 530 in file '/var/www/html/lib/private/Files/View.php' line 1132",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/115.0",
"version": "29.0.0.19",
"exception": {
"Exception": "Exception",
"Message": "OC\\Files\\View::basicOperation(): Argument #2 ($path) must be of type string, null given, called in /var/www/html/lib/private/Files/View.php on line 530 in file '/var/www/html/lib/private/Files/View.php' line 1132",
"Code": 0,
"Trace": [
{
"file": "/var/www/html/lib/private/AppFramework/App.php",
"line": 184,
"function": "dispatch",
"class": "OC\\AppFramework\\Http\\Dispatcher",
"type": "->",
"args": [
[
"OCA\\Forms\\Controller\\ApiController"
],
"insertSubmission"
]
},
{
"file": "/var/www/html/lib/private/Route/Router.php",
"line": 338,
"function": "main",
"class": "OC\\AppFramework\\App",
"type": "::",
"args": [
"OCA\\Forms\\Controller\\ApiController",
"insertSubmission",
[
"OC\\AppFramework\\DependencyInjection\\DIContainer"
],
[
"v2.4",
"ocs.forms.api.insertsubmission"
]
]
},
{
"file": "/var/www/html/ocs/v1.php",
"line": 66,
"function": "match",
"class": "OC\\Route\\Router",
"type": "->",
"args": [
"/ocsapp/apps/forms/api/v2.4/submission/insert"
]
},
{
"file": "/var/www/html/ocs/v2.php",
"line": 23,
"args": [
"/var/www/html/ocs/v1.php"
],
"function": "require_once"
}
],
"File": "/var/www/html/lib/private/AppFramework/Http/Dispatcher.php",
"Line": 170,
"Previous": {
"Exception": "TypeError",
"Message": "OC\\Files\\View::basicOperation(): Argument #2 ($path) must be of type string, null given, called in /var/www/html/lib/private/Files/View.php on line 530",
"Code": 0,
"Trace": [
{
"file": "/var/www/html/lib/private/Files/View.php",
"line": 530,
"function": "basicOperation",
"class": "OC\\Files\\View",
"type": "->",
"args": [
"file_exists",
null
]
},
{
"file": "/var/www/html/lib/private/Files/Filesystem.php",
"line": 546,
"function": "file_exists",
"class": "OC\\Files\\View",
"type": "->",
"args": [
null
]
},
{
"file": "/var/www/html/apps/files_versions/lib/Storage.php",
"line": 190,
"function": "file_exists",
"class": "OC\\Files\\Filesystem",
"type": "::",
"args": [
null
]
},
{
"file": "/var/www/html/apps/files_versions/lib/Listener/FileEventsListener.php",
"line": 197,
"function": "store",
"class": "OCA\\Files_Versions\\Storage",
"type": "::",
"args": [
null
]
},
{
"file": "/var/www/html/apps/files_versions/lib/Listener/FileEventsListener.php",
"line": 103,
"function": "write_hook",
"class": "OCA\\Files_Versions\\Listener\\FileEventsListener",
"type": "->",
"args": [
[
"OC\\Files\\Node\\File"
]
]
},
{
"file": "/var/www/html/lib/private/EventDispatcher/ServiceEventListener.php",
"line": 86,
"function": "handle",
"class": "OCA\\Files_Versions\\Listener\\FileEventsListener",
"type": "->",
"args": [
[
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"
]
]
},
{
"file": "/var/www/html/3rdparty/symfony/event-dispatcher/EventDispatcher.php",
"line": 230,
"function": "__invoke",
"class": "OC\\EventDispatcher\\ServiceEventListener",
"type": "->",
"args": [
[
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"
],
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent",
[
"Symfony\\Component\\EventDispatcher\\EventDispatcher"
]
]
},
{
"file": "/var/www/html/3rdparty/symfony/event-dispatcher/EventDispatcher.php",
"line": 59,
"function": "callListeners",
"class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
"type": "->",
"args": [
[
[
"Closure"
],
[
"Closure"
]
],
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent",
[
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"
]
]
},
{
"file": "/var/www/html/lib/private/EventDispatcher/EventDispatcher.php",
"line": 86,
"function": "dispatch",
"class": "Symfony\\Component\\EventDispatcher\\EventDispatcher",
"type": "->",
"args": [
[
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"
],
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"
]
},
{
"file": "/var/www/html/lib/private/EventDispatcher/EventDispatcher.php",
"line": 98,
"function": "dispatch",
"class": "OC\\EventDispatcher\\EventDispatcher",
"type": "->",
"args": [
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent",
[
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"
]
]
},
{
"file": "/var/www/html/lib/private/Files/Node/HookConnector.php",
"line": 93,
"function": "dispatchTyped",
"class": "OC\\EventDispatcher\\EventDispatcher",
"type": "->",
"args": [
[
"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"
]
]
},
{
"file": "/var/www/html/lib/private/legacy/OC_Hook.php",
"line": 105,
"function": "write",
"class": "OC\\Files\\Node\\HookConnector",
"type": "->",
"args": [
[
true,
"/Test_Forms/Test (Antworten).xlsx"
]
]
},
{
"file": "/var/www/html/lib/private/Files/View.php",
"line": 1276,
"function": "emit",
"class": "OC_Hook",
"type": "::",
"args": [
"OC_Filesystem",
"write",
[
true,
"/Test_Forms/Test (Antworten).xlsx"
]
]
},
{
"file": "/var/www/html/lib/private/Files/View.php",
"line": 1148,
"function": "runHooks",
"class": "OC\\Files\\View",
"type": "->",
"args": [
[
"update",
"write"
],
"/Test_Forms/Test (Antworten).xlsx"
]
},
{
"file": "/var/www/html/lib/private/Files/View.php",
"line": 682,
"function": "basicOperation",
"class": "OC\\Files\\View",
"type": "->",
"args": [
"file_put_contents",
"/user.b/files/Test_Forms/Test (Antworten).xlsx",
[
"update",
"write"
],
null
]
},
{
"file": "/var/www/html/lib/private/Files/Node/File.php",
"line": 73,
"function": "file_put_contents",
"class": "OC\\Files\\View",
"type": "->",
"args": [
"/user.b/files/Test_Forms/Test (Antworten).xlsx",
null
]
},
{
"file": "/var/www/html/custom_apps/forms/lib/Service/SubmissionService.php",
"line": 224,
"function": "putContent",
"class": "OC\\Files\\Node\\File",
"type": "->",
"args": [
null
]
},
{
"file": "/var/www/html/custom_apps/forms/lib/Controller/ApiController.php",
"line": 1145,
"function": "writeFileToCloud",
"class": "OCA\\Forms\\Service\\SubmissionService",
"type": "->",
"args": [
[
"OCA\\Forms\\Db\\Form",
42
],
"/Test_Forms/Test (Antworten).xlsx",
"xlsx",
"user.b"
]
},
{
"file": "/var/www/html/lib/private/AppFramework/Http/Dispatcher.php",
"line": 232,
"function": "insertSubmission",
"class": "OCA\\Forms\\Controller\\ApiController",
"type": "->",
"args": [
42,
[
[
"b"
]
],
"ZadmE4kBLEXE4gSxTdmceJAg"
]
},
{
"file": "/var/www/html/lib/private/AppFramework/Http/Dispatcher.php",
"line": 138,
"function": "executeController",
"class": "OC\\AppFramework\\Http\\Dispatcher",
"type": "->",
"args": [
[
"OCA\\Forms\\Controller\\ApiController"
],
"insertSubmission"
]
},
{
"file": "/var/www/html/lib/private/AppFramework/App.php",
"line": 184,
"function": "dispatch",
"class": "OC\\AppFramework\\Http\\Dispatcher",
"type": "->",
"args": [
[
"OCA\\Forms\\Controller\\ApiController"
],
"insertSubmission"
]
},
{
"file": "/var/www/html/lib/private/Route/Router.php",
"line": 338,
"function": "main",
"class": "OC\\AppFramework\\App",
"type": "::",
"args": [
"OCA\\Forms\\Controller\\ApiController",
"insertSubmission",
[
"OC\\AppFramework\\DependencyInjection\\DIContainer"
],
[
"v2.4",
"ocs.forms.api.insertsubmission"
]
]
},
{
"file": "/var/www/html/ocs/v1.php",
"line": 66,
"function": "match",
"class": "OC\\Route\\Router",
"type": "->",
"args": [
"/ocsapp/apps/forms/api/v2.4/submission/insert"
]
},
{
"file": "/var/www/html/ocs/v2.php",
"line": 23,
"args": [
"/var/www/html/ocs/v1.php"
],
"function": "require_once"
}
],
"File": "/var/www/html/lib/private/Files/View.php",
"Line": 1132
},
"message": "OC\\Files\\View::basicOperation(): Argument #2 ($path) must be of type string, null given, called in /var/www/html/lib/private/Files/View.php on line 530 in file '/var/www/html/lib/private/Files/View.php' line 1132",
"exception": [],
"CustomMessage": "OC\\Files\\View::basicOperation(): Argument #2 ($path) must be of type string, null given, called in /var/www/html/lib/private/Files/View.php on line 530 in file '/var/www/html/lib/private/Files/View.php' line 1132"
},
"id": "664475f19288c"
}
Strangely I received a from @Chartman123 saying:
@chriscroome Could you please try to reproduce the error with the latest NC release? I tried it with NC30 RC2 and 29.0.5 and (if I didn't mix up things) for me it worked now...
However it doesn't appear above :shrug:
@mariomorvan is the admin on the Nextcloud instance where this was an issue -- @mariomorvan I have set off a job to update your Nextcloud to version 29.0.5, when it has completed could you test it again to see if this issue is resolved?
Yeah, I deleted my comment again after I found that in my test scenario the file wasn't linked at all... so of course no error message. It still happens in the latest NC versions
I think this is the same error as this one: https://github.com/nextcloud/forms/issues/2165 We need to externalize this spreadsheet writing in a background job.
Should be doable for 4.3
@susnux to me this looks like some problem with the permissions. What would be the difference if we put it into a background job?
What would be the difference if we put it into a background job?
We run on system permissions not the entering user's
We have this issue on Nextcloud v28.0.9 and v29. The spreadsheet is in a group folder to which the logged in user filling in the form does not have access. (Our cloud is set up so people only have access to shared folders - their personal quota is 0.) If the user is not logged in, it works fine. You need to attempt to write to the spreadsheet as the user who owns the form, even if it is being filled in by another user.
The attempt to submit is a POST to https://
which returns 500
<?xml version="1.0"?>
<ocs>
<meta>
<status>failure</status>
<statuscode>500</statuscode>
<message>Internal Server Error
</message>
</meta>
<data/>
</ocs>
The Nextcloud log entry says:
[no app in context] Error: Exception thrown: Exception
POST /ocs/v2.php/apps/forms/api/v2.4/submission/insert
from 46.102.211.217 by ddat at 13 Sept 2024, 12:28:06
{"reqId":"jiqkTnhkQekTNabpQKLe","level":3,"time":"2024-09-13T11:28:06+00:00","remoteAddr":"46.102.211.217","user":"ddat","app":"no app in context","method":"POST","url":"/ocs/v2.php/apps/forms/api/v2.4/submission/insert","message":"Exception thrown: Exception","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:130.0) Gecko/20100101 Firefox/130.0","version":"28.0.7.4","exception":{"Exception":"Exception","Message":"OC\\Files\\View::basicOperation(): Argument #2 ($path) must be of type string, null given, called in /var/www/html/lib/private/Files/View.php on line 529 in file '/var/www/html/lib/private/Files/View.php' line 1134","Code":0,"Trace":[{"file":"/var/www/html/lib/private/AppFramework/App.php","line":184,"function":"dispatch","class":"OC\\AppFramework\\Http\\Dispatcher","type":"->","args":[["OCA\\Forms\\Controller\\ApiController"],"insertSubmission"]},{"file":"/var/www/html/lib/private/Route/Router.php","line":315,"function":"main","class":"OC\\AppFramework\\App","type":"::","args":["OCA\\Forms\\Controller\\ApiController","insertSubmission",["OC\\AppFramework\\DependencyInjection\\DIContainer"],["v2.4","ocs.forms.api.insertSubmission"]]},{"file":"/var/www/html/ocs/v1.php","line":65,"function":"match","class":"OC\\Route\\Router","type":"->","args":["/ocsapp/apps/forms/api/v2.4/submission/insert"]},{"file":"/var/www/html/ocs/v2.php","line":23,"args":["/var/www/html/ocs/v1.php"],"function":"require_once"}],"File":"/var/www/html/lib/private/AppFramework/Http/Dispatcher.php","Line":169,"Previous":{"Exception":"TypeError","Message":"OC\\Files\\View::basicOperation(): Argument #2 ($path) must be of type string, null given, called in /var/www/html/lib/private/Files/View.php on line 529","Code":0,"Trace":[{"file":"/var/www/html/lib/private/Files/View.php","line":529,"function":"basicOperation","class":"OC\\Files\\View","type":"->","args":["file_exists",null]},{"file":"/var/www/html/lib/private/Files/Filesystem.php","line":545,"function":"file_exists","class":"OC\\Files\\View","type":"->","args":[null]},{"file":"/var/www/html/apps/files_versions/lib/Storage.php","line":190,"function":"file_exists","class":"OC\\Files\\Filesystem","type":"::","args":[null]},{"file":"/var/www/html/apps/files_versions/lib/Listener/FileEventsListener.php","line":196,"function":"store","class":"OCA\\Files_Versions\\Storage","type":"::","args":[null]},{"file":"/var/www/html/apps/files_versions/lib/Listener/FileEventsListener.php","line":102,"function":"write_hook","class":"OCA\\Files_Versions\\Listener\\FileEventsListener","type":"->","args":[["OC\\Files\\Node\\File"]]},{"file":"/var/www/html/lib/private/EventDispatcher/ServiceEventListener.php","line":86,"function":"handle","class":"OCA\\Files_Versions\\Listener\\FileEventsListener","type":"->","args":[["OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"]]},{"file":"/var/www/html/3rdparty/symfony/event-dispatcher/EventDispatcher.php","line":230,"function":"__invoke","class":"OC\\EventDispatcher\\ServiceEventListener","type":"->","args":[["OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"],"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent",["Symfony\\Component\\EventDispatcher\\EventDispatcher"]]},{"file":"/var/www/html/3rdparty/symfony/event-dispatcher/EventDispatcher.php","line":59,"function":"callListeners","class":"Symfony\\Component\\EventDispatcher\\EventDispatcher","type":"->","args":[[["Closure"],["Closure"]],"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent",["OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"]]},{"file":"/var/www/html/lib/private/EventDispatcher/EventDispatcher.php","line":94,"function":"dispatch","class":"Symfony\\Component\\EventDispatcher\\EventDispatcher","type":"->","args":[["OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"],"OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"]},{"file":"/var/www/html/lib/private/EventDispatcher/EventDispatcher.php","line":106,"function":"dispatch","class":"OC\\EventDispatcher\\EventDispatcher","type":"->","args":["OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent",["OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"]]},{"file":"/var/www/html/lib/private/Files/Node/HookConnector.php","line":100,"function":"dispatchTyped","class":"OC\\EventDispatcher\\EventDispatcher","type":"->","args":[["OCP\\Files\\Events\\Node\\BeforeNodeWrittenEvent"]]},{"file":"/var/www/html/lib/private/legacy/OC_Hook.php","line":105,"function":"write","class":"OC\\Files\\Node\\HookConnector","type":"->","args":[[true,"/Digital (UK Tech)/Digital Discussions Applications Team/Teams Working Documents/Test (responses).xlsx"]]},{"file":"/var/www/html/lib/private/Files/View.php","line":1278,"function":"emit","class":"OC_Hook","type":"::","args":["OC_Filesystem","write",[true,"/Digital (UK Tech)/Digital Discussions Applications Team/Teams Working Documents/Test (responses).xlsx"]]},{"file":"/var/www/html/lib/private/Files/View.php","line":1150,"function":"runHooks","class":"OC\\Files\\View","type":"->","args":[["update","write"],"/Digital (UK Tech)/Digital Discussions Applications Team/Teams Working Documents/Test (responses).xlsx"]},{"file":"/var/www/html/lib/private/Files/View.php","line":681,"function":"basicOperation","class":"OC\\Files\\View","type":"->","args":["file_put_contents","/nikkilocke/files/Digital (UK Tech)/Digital Discussions Applications Team/Teams Working Documents/Test (responses).xlsx",["update","write"],null]},{"file":"/var/www/html/lib/private/Files/Node/File.php","line":73,"function":"file_put_contents","class":"OC\\Files\\View","type":"->","args":["/nikkilocke/files/Digital (UK Tech)/Digital Discussions Applications Team/Teams Working Documents/Test (responses).xlsx",null]},{"file":"/var/www/html/custom_apps/forms/lib/Service/SubmissionService.php","line":224,"function":"putContent","class":"OC\\Files\\Node\\File","type":"->","args":[null]},{"file":"/var/www/html/custom_apps/forms/lib/Controller/ApiController.php","line":1145,"function":"writeFileToCloud","class":"OCA\\Forms\\Service\\SubmissionService","type":"->","args":[["OCA\\Forms\\Db\\Form",111],"/Digital (UK Tech)/Digital Discussions Applications Team/Teams Working Documents/Test (responses).xlsx","xlsx","nikkilocke"]},{"file":"/var/www/html/lib/private/AppFramework/Http/Dispatcher.php","line":230,"function":"insertSubmission","class":"OCA\\Forms\\Controller\\ApiController","type":"->","args":[111,[["457"],["1186"]],""]},{"file":"/var/www/html/lib/private/AppFramework/Http/Dispatcher.php","line":137,"function":"executeController","class":"OC\\AppFramework\\Http\\Dispatcher","type":"->","args":[["OCA\\Forms\\Controller\\ApiController"],"insertSubmission"]},{"file":"/var/www/html/lib/private/AppFramework/App.php","line":184,"function":"dispatch","class":"OC\\AppFramework\\Http\\Dispatcher","type":"->","args":[["OCA\\Forms\\Controller\\ApiController"],"insertSubmission"]},{"file":"/var/www/html/lib/private/Route/Router.php","line":315,"function":"main","class":"OC\\AppFramework\\App","type":"::","args":["OCA\\Forms\\Controller\\ApiController","insertSubmission",["OC\\AppFramework\\DependencyInjection\\DIContainer"],["v2.4","ocs.forms.api.insertSubmission"]]},{"file":"/var/www/html/ocs/v1.php","line":65,"function":"match","class":"OC\\Route\\Router","type":"->","args":["/ocsapp/apps/forms/api/v2.4/submission/insert"]},{"file":"/var/www/html/ocs/v2.php","line":23,"args":["/var/www/html/ocs/v1.php"],"function":"require_once"}],"File":"/var/www/html/lib/private/Files/View.php","Line":1134},"CustomMessage":"Exception thrown: Exception"},"id":"66e424be7a69f"}
forms-bug.permissions-hack.clean.diff.txt After further debugging, we have a workaround (patch), but I'm not 100% sure whether it is safe.
What is actually happening here is an interaction between several apps:
- The forms service accepts a form submission, and calls file_put_contents in Files (to update the linked spreadsheet)
- That calls basicOperation('file_put_contents', path) (with the real path, including the actual owner)
- That emits a BeforeNodeWrittenEvent, for hooks, such as the versioning app
- The versioning app receives the event
- FileEventsListener::store() calls file_exists() to check whether the file exists before writing a new version record
- That ultimately calls back into FileEventsListener::getPathForNode(), which returns null, because the owner of the file is the same as the logged in user (since the file is on a share), and does not have access to the file
However, FileEventListener is part of files_versions; it's not doing any meaningful security check here. In particular, if the user is logged out, it checks the path for a username, and uses that.
So if we patch FileEventListener to do the same thing when $user == $owner, it will correctly look up the filename against its real owner, and file_exists() returns true.
As I understand it, permissions checks have already been done by the time we get here; files_versions is just hooking into that process, creating version records, and presumably preventing old versions being deleted. And the forms app has already bypassed the permissions checks somehow; as discussed above, it'd be better to update the spreadsheet on a background thread, but right now it works (and implementing that is more work than I'm prepared to do for a workaround since you'll be doing it next year).
So my guess is that my patch is safe (though there are some fishy things around it). But I'm not very sure about this! If we can use this, it'd solve some immediate problems and we would not have to wait until next year.
Thoughts?
@toad thank you very much for digging into this... If you want you can open a pull request on the server repository (where files_versions is developed) and see what the maintainers think about it. :)
I've created a pull request for a shorter version of the patch: https://github.com/nextcloud/server/pull/48295
Note that this is against server, so I'm not sure whether I need a server ticket?
I haven't implemented tests yet; if my assumptions about the security implications are wrong then the whole thing is the wrong approach.
@come-nc if the form is filled via the public share link like in this comment, the if branch is used here and ownerId has the owner of the Form, not the user providing the share.
@come-nc if the form is filled via the public share link like in this comment, the
ifbranch is used here andownerIdhas the owner of the Form, not the user providing the share.
Thatβs not incorrect I think, since this owner does have write access to the file. Can you output $node->getOwner() after the if block?
Maybe also the result of $node->getPath() and $this->storage->getUserFolder($ownerId)->getRelativePath($node->getPath())?
Could you also please rename the storage property to rootFolder for consistency with the rest of Nextcould code :face_in_clouds:
Hmmm, I think this has evolved. Filling in from a public link does work for us - because of the owner guessing, I assume.
According to my logging, in the failing case (where the user is logged in and doesn't have access to the spreadsheet which is on a group share), $node->getOwner() was the logged in user's ID.
I think this comes from getFileInfo() and ultimately from GroupFolderStorage::getOwner().
Meanwhile node->getPath() is the full path, from the original owner, i.e. "/matthewtoseland/files/uk tech/test/test3 folder/forms_answers/Yet another test form (responses).xlsx"
Which is why guessing the owner from the path works (both in this case with my patch, and in the logged out case).
Could you also please rename the storage property to rootFolder for consistency with the rest of Nextcould code
Addressed in #2337
HookConnector::write() calls getNodeForPath(). The passed in path is relative at this point ("/uk tech/test/Yet another test form (responses).xlsx").
That calls Filesystem::getView()->getFileInfo() on path. Which calls Files::Storage::Wrapper::getOwner($path), and we eventually end up in GorupFolderStorage::getOwner(), which returns the current logged in user (since it's a group storage).
Which means the Node's passed in info sets the owner to the currently logged in user. Even though it has the correct absolute path which starts with the real owner.
Confirmed. The node has path = "/matthewtoseland/files/uk tech/test/test3 folder/New form title (responses).xlsx" and owner = "matthew-test3".
Is there a realistic alternative here? Maybe GroupFolderStorage could check whether the logged in user has access?
Or alternatively, is forms really doing something dodgy here? "The logged in user has access to any group share we interact with" seems like a reasonable invariant. Does it holds outside of forms?
That line of reasoning means you'd need the originally planned "write updates to the spreadsheet on another thread" change. :(
My main concern personally/professionally is whether the workaround patch is safe. We might just turn off versioning to get around it though.