Skip to content
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

Fix invalid direct upload form attribute #43707

Closed
wants to merge 2 commits into from

Conversation

gmcgibbon
Copy link
Member

@gmcgibbon gmcgibbon commented Nov 24, 2021

#38957

Closes #43698

@rails-bot rails-bot bot added the actionview label Nov 24, 2021
@brenogazzola
Copy link
Contributor

brenogazzola commented Nov 24, 2021

Found another possible problem (not sure if I should open another issue). If the form is not given a model, only a scope, it will not generate the data-direct-upload-token or data-direct-upload-attachment-name, and the upload will not work.

@gmcgibbon
Copy link
Member Author

gmcgibbon commented Nov 24, 2021

Found another possible problem (not sure if I should open another issue). If the form is not given a model, only a scope, it will not generate the data-direct-upload-token or data-direct-upload-attachment-name, and the upload will not work.

I think we should raise in that situation. Supplying the direct_upload key is clear intent that the developer wants to use Active Storage but we don't have a model to inflect on. I'll add that to this PR. Thanks!

@brenogazzola
Copy link
Contributor

Might be a good idea to add this to the upgrade guide so others can prepare in advance, as well as a notice about the change in the JS library. If I had not checked the discussion in the original PR I’d have been pretty confused.

@gmcgibbon gmcgibbon force-pushed the fix_invalid_direct_upload branch 2 times, most recently from 69f8da9 to 3330998 Compare November 29, 2021 18:51
@brenogazzola
Copy link
Contributor

brenogazzola commented Nov 29, 2021

I'm not sure if I'm missing something in the code, or if it was not considered in the original PR:

How do I handle form objects, which are simple POROs and not active record models?

@brenogazzola
Copy link
Contributor

One more thing: Would it be possible to turn off the token, at least in the test env? Since we upgraded, some system tests that we never had a problem with are now flakky due to InvalidDirectUploadTokenError.

@gmcgibbon
Copy link
Member Author

gmcgibbon commented Nov 30, 2021

I'm not sure if I'm missing something in the code, or if it was not considered in the original PR:
How do I handle form objects, which are simple POROs and not active record models?

It wasn't considered. Since upload tokens are coupled to Active Record models, you need a model and attachment to tie an upload to. I don't think plain Active Model would work with currently either. Can you provide an example snippet or test case to show how this previously worked? I can add test coverage so these contracts are better enforced.

@gmcgibbon
Copy link
Member Author

gmcgibbon commented Nov 30, 2021

One more thing: Would it be possible to turn off the token, at least in the test env? Since we upgraded, some system tests that we never had a problem with are now flakky due to InvalidDirectUploadTokenError.

We can make multi-service uploads opt-in depending on how much it is breaking apps. The feature seems to still need some work on the action view integration end. I'll review that once this patch is in. The unreleased changes to JS libraries may be causing your app some issues too if you aren't linking them to your app.

@brenogazzola
Copy link
Contributor

brenogazzola commented Nov 30, 2021

It wasn't considered. Since upload tokens are coupled to Active Record models, you need a model and attachment to tie an upload to. I don't think plain Active Model would work with currently either. Can you provide an example snippet or test case to show how this previously worked? I can add test coverage so these contracts are better enforced.

Previously direct upload made no assumptions about what its file was going to be attached to. It only uploaded the file to the configured service and returned to token that was used.

Here's the most bare bones use case that used to work and no longer does. Instead of using a form object, it uses a plain file_field_tag. I've also intentionally removed the attribute (nil) to ensure direct upload has absolutely no information about where the file is going to be attached.

View:

<%= form_with url: photos_path do |form| %>
  <%= file_field_tag nil, name: "photo[file]", direct_upload: true %>
  <%= form.submit "Save" %>
<% end %>

Controller:

def create
  Photo.create!(photos_params)
  head :ok
end

private
  def photo_params
    params.require(:photo).permit(:file)
  end

Model:

class Photo
  has_one_attached :file
end

@brenogazzola
Copy link
Contributor

We can make multi-service uploads opt-in depending on how much it is breaking apps. The feature seems to still need some work on the action view integration end. I'll review that once this patch is in. The unreleased changes to JS libraries may be causing your app some issues too if you aren't linking them to your app.

I haven't read through the entire implementation, but would it be possible to modify the direct_upload attribute to take a string with the model/attachment instead of a bool? That would solve most of the problems:

<%= file_field_tag nil, name: "photo[file]", direct_upload: "photo#file %>

@gmcgibbon
Copy link
Member Author

gmcgibbon commented Nov 30, 2021

Here's the most bare bones use case that used to work and no longer does

Is the photo class an Active Model, Active Record, or something else? There's no superclass specified.

but would it be possible to modify the direct_upload attribute to take a string with the model/attachment instead of a bool?

Yes, that's what I have in this PR to fix the contract of file_field_tag, but in a hash. file_field can still use direct_upload: true because the model and attachment can be inferred from the options[:object]. The approach would be <%= file_field_tag nil, name: "photo[file]", direct_upload: { model: "Photo", attachment: :file } %>, though I had thought about just specifying a token. I'm open to suggestions.

@brenogazzola
Copy link
Contributor

Sorry, Photo is a normal model. But in this case it does not make much difference. Since we are using a standard tag helper not a form builder one, direc upload does not have access to the forms object.

I’ve pulled the JS file from the gem into my JS folder and included it in my bundle. The uploads are all working now, but are still flakky in the CI.

@brenogazzola
Copy link
Contributor

brenogazzola commented Dec 1, 2021

Thinking a bit, couldn’t the direct uplod controller assume that if no attachment name is provided it can simply upload to the default service? And then only create the attachment name/token if the attachment in question defined a service other than the default?

This would mean no migration burden for projects already using active storage, while supporting projects that need per attachment service.

@brenogazzola
Copy link
Contributor

Hey @gmcgibbon anything I can help with this? I’m not skilled enough with JS to help there, but I’m fine with the ruby part.

@rafaelfranca rafaelfranca added this to the 7.0 milestone Dec 8, 2021
@gmcgibbon
Copy link
Member Author

Thinking a bit, couldn’t the direct uplod controller assume that if no attachment name is provided it can simply upload to the default service?

Yes, that's possible, but I'd rather it be consistent for all uploads to have tokens included. If this breaks apps, we could just make it opt-in behaviour while upgrading, but I'm not convinced that's necessary yet.

The uploads are all working now, but are still flakky in the CI.

If you could debug why the tests are flaky on your end, that would be helpful. I wouldn't expect them to be. I should have time to work on this next week.

@brenogazzola
Copy link
Contributor

Yes, that's possible, but I'd rather it be consistent for all uploads to have tokens included.

Alright. With your PR it’s not as much extra work as we had with the original one, so it sounds like a good compromise between correctness and ease.

If you could debug why the tests are flaky on your end,

I’ll see if I can figure it out. They were ocasionallu breaking in production too. File uploads worked most of the time but every once in a while I’d get an invalid token error.

@rafaelfranca rafaelfranca removed this from the 7.0 milestone Dec 15, 2021
@gmcgibbon gmcgibbon force-pushed the fix_invalid_direct_upload branch 4 times, most recently from 60a4c7c to 3bce049 Compare December 16, 2021 21:31
@rails-bot rails-bot bot added the docs label Dec 17, 2021
@brenogazzola
Copy link
Contributor

A user in issue #43895 has mentioned another use case that is no longer supported: Using direct upload without a session. Seems he has a react app and disable CSRF tokens and sessions.

@gmcgibbon
Copy link
Member Author

Sounds like we're rewriting this feature, and the implementation will change significantly, so I'm going to close this.

@gmcgibbon gmcgibbon closed this Jan 19, 2022
@gmcgibbon gmcgibbon deleted the fix_invalid_direct_upload branch January 19, 2022 18:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

file_field helper raises an exception if attribute does not match an active storage attachment
3 participants