-
Notifications
You must be signed in to change notification settings - Fork 202
Add a file upload utility #818
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a file upload utility #818
Conversation
This comment has been minimized.
This comment has been minimized.
Codecov Report
@@ Coverage Diff @@
## 8.x-4.x #818 +/- ##
=============================================
- Coverage 76.08% 71.15% -4.93%
- Complexity 583 606 +23
=============================================
Files 85 86 +1
Lines 1271 1359 +88
=============================================
Hits 967 967
- Misses 304 392 +88
Continue to review full report at Codecov.
|
Codecov Report
@@ Coverage Diff @@
## 8.x-4.x #818 +/- ##
==========================================
Coverage ? 51.68%
Complexity ? 651
==========================================
Files ? 115
Lines ? 1662
Branches ? 0
==========================================
Hits ? 859
Misses ? 803
Partials ? 0
Continue to review full report at Codecov.
|
|
@rthideaway Instead of making this a service, this should just be a DataProducer. Can you rewrite it accordingly? To understand how file uploads work in this module, check out this test: https://github.com/drupal-graphql/graphql/blob/8.x-4.x/tests/src/Kernel/Framework/UploadMutationTest.php |
|
To understand how the file makes its way from the file content of the request into the variable, check out this: https://github.com/drupal-graphql/graphql/blob/8.x-4.x/src/Routing/QueryRouteEnhancer.php#L38-L42 |
|
will address this today/tomorrow. |
|
@fubhy how did imagine this to work? What should be the outcome of data producer, the File entity? Was playing with it but figured out that having separate file upload data producer makes the logic more complicated for us. Imagine the following very basic use case:
How the file upload data producer is integrated to it? Because if there should be separate file upload data producer upfront, I guess we need to use compose. But how? File data producer needs some file field config metadata so it can validate the file (list of allowed extensions, max upload size, destination file path, etc). So my compose already needs three steps:
In theory also, what if I don't know to which file field I am uploading beforehand as it will be decided during execution of Resume data producer. Can't think of any case now, but for me the file upload works better when it's processed within specific data producer as a service. Like with this resume, if user does not have permission to create a resume I simply don't process the file, right? Am I missing something here? Sorry if yes :) |
|
That all sounds like it might be better done in a Trait then?! Or a service after all? Mutations are different and more complicated than normal field resolvers I guess and harder to compose. |
|
We already have a service (in this patch). But trait is fine for me as well. Maybe you could take a look at this patch what would you change here? The thing is we need to also get the violations of that trait/service, so we can display them to user (incorrect file extension, file too big, etc) |
src/GraphQL/Utility/FileUpload.php
Outdated
| * @param mixed $violations | ||
| * List of violations. | ||
| */ | ||
| protected function registerViolations($violations) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a service. You can't store stuff within it across potentially multiple mutations or you'll leak violations from one mutations into another.
The service itself (if we add one) should only do low level operations worthy of a service that do not assume too many concrete implementation details.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Atm, there are only two public methods, getViolations and createTemporaryFileUpload. The second one is resetting violations at the start of the process. We used it like calling method for creating file and when empty then getting violations. Next mutation can be called without interfering the first one, as the violations got reseted.
But agree this does not look right. So what do you suggest to do here then, how to return violations? Some object? Could return something like FileUploadResponse, would be that ok?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will just try something and let's see
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to avoid side-effects as much as possible in our code base. Hence, returning a wrapper object instead might make sense indeed.
src/GraphQL/Utility/FileUpload.php
Outdated
| * @param array $settings | ||
| * Fle settings as specified in regular file field config. | ||
| * | ||
| * @return \Drupal\file\FileInterface|bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of returning FALSE i'd vote for returning NULL in case of a failure.
src/GraphQL/Utility/FileUpload.php
Outdated
| $this->logger->error(sprintf('Unknown error while uploading the file "%s".', $file->getFilename())); | ||
| return FALSE; | ||
| } | ||
| $upload_dir = $settings['uri_scheme'] . '://' . trim($settings['file_directory'], '/'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We use camelCase across the board!
| $name = $file->getClientOriginalName(); | ||
| $mime = $this->mimeTypeGuesser->guess($name); | ||
| $destination = $this->fileSystem->getDestinationFilename("{$upload_dir}/{$name}", $this->fileSystem::EXISTS_RENAME); | ||
| // Begin building file entity. |
This comment was marked as resolved.
This comment was marked as resolved.
Sorry, something went wrong.
src/GraphQL/Utility/FileUpload.php
Outdated
| * @param mixed $violations | ||
| * List of violations. | ||
| */ | ||
| protected function registerViolations($violations) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd like to avoid side-effects as much as possible in our code base. Hence, returning a wrapper object instead might make sense indeed.
|
@fubhy addressed your remarks, please re-check it, once you find the time |
|
This would be a useful feature. |
klausi
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I see 3 things we need to do here:
- Adapt Response objects to structure from #1013
- Add test overage. This is security critical code since file uploads are a big attack vector.
- Update documentation how you have to assemble a file upload in your custom data producer
We could split up 2. and 3. into new pull requests to not make this too long?
| /** | ||
| * File upload response wrapper. | ||
| */ | ||
| class FileUploadResponse implements FileUploadResponseInterface { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we now started a Response namespace in #1013 . We should put any new response classes into this same namespace for consistency.
We can probably unify this with the ResponseInterface we have now?
|
I could take care of number 3 no problem. @rthideaway would you adapt this to the new structure (1) and maybe make a test for it |
|
I will look into test coverage now and what we could do here. |
| $validators['file_validate_size'] = [$this->getMaxUploadSize($settings)]; | ||
|
|
||
| // Add the extension check if necessary. | ||
| if (!empty($settings['file_extensions'])) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For security reasons we should have a default value of "txt" here if the user forgot to configure allowed file extensions by accident.
|
First set of tests created in #1112 |
|
Merging this here for credits, will then merge #1112 |
No description provided.