Skip to content

Conversation

ghiculescu
Copy link
Member

@ghiculescu ghiculescu commented Jan 19, 2021

This PR is a proposed architecture for validation in Active Storage. It builds on @georgeclaghorn's proposed architecture here: #35390 (comment)

It leans heavily on #35390 - most of the code from that PR is included in it, with some light refactoring.


How it works

There were four high level points in the proposed architecture:

To start, we’d offer validations for the most commonly-validated blob attributes, content_type and byte_size

I included the validators in #35390 mostly unchanged.

The direct upload preflight blob creation request already sends content_type and byte_size attributes. We’d validate these attributes upfront, refusing to create a blob record and return a signed direct upload URL if they’re invalid.

To do this, Active Storage needs to know the model to run validations for. This is passed as a new data attribute, data-direct-upload-model. It's handled automatically if you use FormBuilder#file_field, or you can add it manually.

This attribute eventually makes its way to ActiveStorage::Blob#valid_with?, which returns true if:

  • There's no Active Storage validators on the model, or:
  • All Active Storage validators would pass with the given blob.

A validator is an "Active Storage validator" if it's a subclass of ActiveStorage::Validations::BaseValidator.

Tests for this are here.

because only server-side validation is secure, we’d need to validate content_type again on attach

I haven't explicitly tested for this yet but I believe #35390 handles it.

I don’t think we need to validate byte_size again on attach because signed direct upload URLs account for the Content-Length header. If I’m wrong about that and we have to validate byte_size again, fine. Same as above.

If the validator is defined, we validate it :)


I'm adding @abhchand as a co-author since I used so much of #35390

Co-authored-by: Abhishek Chandrasekhar me@abhchand.me

@ghiculescu ghiculescu force-pushed the as-direct-validation branch 6 times, most recently from 921fb6e to 892f685 Compare January 19, 2021 19:24
@ghiculescu ghiculescu marked this pull request as ready for review January 19, 2021 19:24
return true if klass_name.nil?
return true unless defined?(ActiveRecord::Base)

model = ActiveRecord::Base.const_get(klass_name) rescue nil
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't love this, but can't think of a safer way to get the model here.

@louim
Copy link
Contributor

louim commented Jan 19, 2021

Hey @ghiculescu,

See here how to setup the commit(s) with co-authoring. In your case, that would mean adding: Co-authored-by: Abhishek Chandrasekhar <me@abhchand.me> at the bottom of the commits messages you want to co-author.

@ghiculescu
Copy link
Member Author

@louim done - thanks!

@ghiculescu ghiculescu force-pushed the as-direct-validation branch 5 times, most recently from 2e99dda to c1a0e81 Compare January 20, 2021 00:12
@ghiculescu ghiculescu force-pushed the as-direct-validation branch 2 times, most recently from 912f87d to c898d46 Compare January 21, 2021 22:41
@abhchand
Copy link
Contributor

abhchand commented Feb 9, 2021

Hey @ghiculescu , just stopping by to say thanks for including me as an author on the commit. That's nice of you!

If I find time between work and personal life I'll try to weigh in here, but those two things are keeping me pretty busy at the moment :)

@ghiculescu
Copy link
Member Author

ghiculescu commented Feb 9, 2021

@georgeclaghorn I would love to include this in Rails 7. Do you have any feedback on the direction taken here or anything else you'd like to see included? (let me know if there's a better person to direct this question to)

@ghiculescu ghiculescu force-pushed the as-direct-validation branch from c898d46 to 72b9109 Compare February 9, 2021 18:00
@georgeclaghorn georgeclaghorn self-assigned this Mar 5, 2021
@ghiculescu ghiculescu force-pushed the as-direct-validation branch from 72b9109 to 15426e6 Compare April 13, 2021 20:28
@georgeclaghorn georgeclaghorn removed their assignment Apr 30, 2021
@rails-bot
Copy link

rails-bot bot commented Jul 29, 2021

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Jul 29, 2021
@rails-bot rails-bot bot removed the stale label Jul 29, 2021
@ghiculescu ghiculescu force-pushed the as-direct-validation branch 2 times, most recently from af9cb12 to fbe8adf Compare July 29, 2021 15:39
Co-authored-by: Abhishek Chandrasekhar <me@abhchand.me>
@brunoprietog
Copy link
Contributor

Any news on this? I would love to have it in rails 7

@reckerswartz
Copy link

@ghiculescu can you also add other validators?

like

presence: true
limit: { min: 1, max: 3 }

@tomrossi7
Copy link
Contributor

Anything I can do to help get this PR moving along? We would love to see this get merged into Rails and are happy to commit resources to help!

@ghiculescu
Copy link
Member Author

@tomrossi7 if you're interested in fixing up the conflicts and trying to move this forward, I can give you access to my fork?

if options[:direct_upload_model]
options["data-direct-upload-model"] = options.delete(:direct_upload_model)
elsif options[:object]
options["data-direct-upload-model"] = options[:object].class.name
Copy link
Contributor

@seanpdoyle seanpdoyle Dec 21, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a signed reference (like what Turbo Rails does for Stream names) to prevent tampering?

Similarly, could the model: Post option be signed in the same way?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also need a helper method for when not using FormBuilder:

<input type=file data-direct-upload-url="<%= rails_direct_uploads_url %>" data-direct-upload-model="<%= rails_direct_uploads_signed_model('Post') %>" />

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't see how just the model is sufficient. Shouldn't it be model.attribute as models can have more than one attached attribute:

class User < ApplicationRecord
  has_one_attached :avatar
  has_one_attached :cover_photo
  has_many_attached :highlights
end

Copy link
Contributor

@seanabrahams seanabrahams Jun 1, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this be a signed reference (like what Turbo Rails does for Stream names) to prevent tampering?

Similarly, could the model: Post option be signed in the same way?

Yes, it could use ActiveStorage.verifier.

Could do something like:

if options[:direct_upload_model_and_attribute] # Note change in name from direct_upload_model to direct_upload_model_and_attribute
  options["data-direct-upload-model-and-attribute"] = ActiveStorage.verifier.generate(options.delete(:direct_upload_model_and_attribute))
ActiveStorage.verifier.generate("User.avatar")
=> "BAhJIhBVc2VyLmF2YXRhcgY6BkVU--a6f0ee7e289bbf19f91634986e33c15298a6f408"
ActiveStorage.verifier.verified("BAhJIhBVc2VyLmF2YXRhcgY6BkVU--a6f0ee7e289bbf19f91634986e33c15298a6f408")
=> "User.avatar"

@rails-bot
Copy link

rails-bot bot commented Mar 21, 2022

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.
Thank you for your contributions.

@rails-bot rails-bot bot added the stale label Mar 21, 2022
@rails-bot rails-bot bot closed this Mar 28, 2022
Copy link
Contributor

@seanabrahams seanabrahams left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's see if we can make some progress on this.

Comment on lines +603 to +604
validates :avatar, attachment_byte_content_type: { in: %w[image/jpeg image/png] }
validates :avatar, attachment_byte_content_type: { not: %w[application/pdf] }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be:

    validates :avatar, attachment_content_type: { in: %w[image/jpeg image/png] }
    validates :avatar, attachment_content_type: { not: %w[application/pdf] }

?

Comment on lines +610 to +611
validates :avatar, attachment_byte_content_type: %w[image/jpeg image/png]
validates :avatar, attachment_byte_content_type: "image/jpeg"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

    validates :avatar, attachment_content_type: %w[image/jpeg image/png]
    validates :avatar, attachment_content_type: "image/jpeg"

?

if options[:direct_upload_model]
options["data-direct-upload-model"] = options.delete(:direct_upload_model)
elsif options[:object]
options["data-direct-upload-model"] = options[:object].class.name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would also need a helper method for when not using FormBuilder:

<input type=file data-direct-upload-url="<%= rails_direct_uploads_url %>" data-direct-upload-model="<%= rails_direct_uploads_signed_model('Post') %>" />

if options[:direct_upload_model]
options["data-direct-upload-model"] = options.delete(:direct_upload_model)
elsif options[:object]
options["data-direct-upload-model"] = options[:object].class.name
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't see how just the model is sufficient. Shouldn't it be model.attribute as models can have more than one attached attribute:

class User < ApplicationRecord
  has_one_attached :avatar
  has_one_attached :cover_photo
  has_many_attached :highlights
end

@ghiculescu
Copy link
Member Author

@seanabrahams i don’t have much capacity to work on this at the moment, would you like to take it over?

@seanabrahams
Copy link
Contributor

@ghiculescu I'll see what I can do 👍

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.

9 participants