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

Image attachment content type validation fix for ActiveStorage #4021

Merged
merged 2 commits into from
Apr 15, 2021

Conversation

cpfergus1
Copy link
Contributor

@cpfergus1 cpfergus1 commented Apr 8, 2021

Description

Ref #4017

This PR aimed to solve the bugs described in Issue #4017 by implementing a validation for content type and to handle a FileNotFound error when ActiveStorage fails to upload to a remote service such as S3.

The validation allows file types jpeg, png, jpg and gif. More file types can be added and removed by editing the accepted_image_types definition found in active_storage_attachment.rb. Validation tests have been added to the image_spec.rb in core.

The handling of the FileNotFound error maybe a temporary fix as we are aiming to discover another pathway to handle the exception and remove the invalid objects simultaneously. Currently when the error is raised on method call, it returns a corrupted image file path.

Checklist:

  • I have followed Pull Request guidelines
  • I have added a detailed description into each commit message
  • I have updated Guides and README accordingly to this change (if needed)
  • I have added tests to cover this change (if needed)
  • I have attached screenshots to this PR for visual changes (if needed)

@cpfergus1
Copy link
Contributor Author

cpfergus1 commented Apr 8, 2021

Failed on paperclip

Failure/Error: image.attachment.attach(io: invalid_image_file, filename: 'file.txt')

NoMethodError:
  undefined method `attach' for #<Paperclip::Attachment:0x0000557665d96ff8>
./spec/models/spree/image_spec.rb:23:in `block (3 levels) in <top (required)>'

describe 'validations' do
let(:valid_image_file) { File.open(File.join('lib', 'spree', 'testing_support', 'fixtures', 'blank.jpg')) }
let(:invalid_image_file) { File.open(File.join('lib', 'spree', 'testing_support', 'fixtures', 'file.txt')) }
let(:image) { create(:image) }
Copy link
Member

Choose a reason for hiding this comment

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

I think that using this syntax the specs should work with both adapters:

subject { create(:image, attachment: valid_image_file) }

@@ -107,6 +107,8 @@ def filename

def url(style = default_style)
attachment.url(style)
rescue ActiveStorage::FileNotFoundError
'/images/corrupted-image.png'
Copy link
Member

Choose a reason for hiding this comment

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

This image is not provided by the system, and I think it shouldn't because the frontend and the backend have separate assets folders while this concern code would serve both of them. One alternative could be using a base64 string here with a standard broken image icon, but it's not ideal as well.

On a side note: what about removing this fix from this PR and opening another one just for the other issue?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Removed temporary fix to be brought up in another branch

@cpfergus1
Copy link
Contributor Author

cpfergus1 commented Apr 12, 2021

Removed AS:FileNotFileError catch from this PR. Will patch that fix in another PR for further Discussion.

@cpfergus1 cpfergus1 changed the title Image attachment content type validation and FileNotFound Fix Image attachment content type validation Fix Apr 12, 2021
@cpfergus1 cpfergus1 requested a review from kennyadsl April 12, 2021 04:11
Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

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

Left a couple of suggestions. When done, can you please also squash all the commits into a single one for a better git history when the PR will get merged?

@@ -9,6 +9,26 @@
let(:default_style) { :product }
end

describe 'validations' do
Copy link
Member

@kennyadsl kennyadsl Apr 12, 2021

Choose a reason for hiding this comment

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

I feel like there's no need for the describe blocks here since we don't have any shared object between the test cases into it. What about just using two different tests as:

it 'is valid when attachment has allowed content type' do
  image = build(:image, 
    attachment: File.open(File.join('lib', 'spree', 'testing_support', 'fixtures', 'blank.jpg'))
  )
  
  expect(image).to be_valid
end

it 'is not valid when attachment has restricted content type' do
  image = build(:image, 
    attachment: File.open(File.join('lib', 'spree', 'testing_support', 'fixtures', 'file.txt'))
  )
  
  expect(image).to_not be_valid
end

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This syntax has been implemented!

core/app/models/spree/image/active_storage_attachment.rb Outdated Show resolved Hide resolved
@cpfergus1 cpfergus1 force-pushed the Issue-4017 branch 3 times, most recently from b5af38f to 60c6b56 Compare April 12, 2021 19:30
Copy link
Member

@kennyadsl kennyadsl left a comment

Choose a reason for hiding this comment

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

Thanks, @cpfergus1! 🎉

@kennyadsl kennyadsl added this to the 2.11 milestone Apr 13, 2021
@kennyadsl kennyadsl added Needs Backport changelog:solidus_core Changes to the solidus_core gem labels Apr 13, 2021
@kennyadsl kennyadsl changed the title Image attachment content type validation Fix Image attachment content type validation fix for ActiveStorage Apr 13, 2021
end

def accepted_image_types
%w(image/jpeg image/jpg image/png image/gif)
Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about having the list of accepted mime types in a constant?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Defined in a ENV constant or where would the constant be defined?

Copy link
Contributor

Choose a reason for hiding this comment

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

No, no... I mean as a constant in Spree::Image::ActiveStorageAttachment, like in:

module Spree::Image::ActiveStorageAttachment
  ACCEPTED_IMAGE_TYPES = %w(....).freeze
end

I mean, it's information that doesn't depend on the state of an object, and it has no computational logic, so in my view, it belongs to the constants domain.

Sorry not having been clearer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I see, thank you for clarifying

@@ -22,11 +22,12 @@

context "when include_images is included in the initialization params" do
let(:params) { { include_images: true, keyword: @product1.name, taxon: @taxon.id } }
let(:image_file) { File.open(File.join('lib', 'spree', 'testing_support', 'fixtures', 'blank.jpg')) }
Copy link
Contributor

Choose a reason for hiding this comment

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

As we are using File.open(File.join('lib', 'spree', 'testing_support', 'fixtures', **) in several place, what do you think about extracting it to a test helper method?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think this is a great idea, I can certainly implement this now.

@@ -9,6 +9,18 @@
let(:default_style) { :product }
end

it 'is valid when attachment has allowed content type' do
image = build(:image,
attachment: File.open(File.join('lib', 'spree', 'testing_support', 'fixtures', 'blank.jpg')))
Copy link
Contributor

Choose a reason for hiding this comment

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

I like to put a blank line between Given/Then/When contexts in the specs... However, I think this is not consistently used in the codebase, so feel free to ignore the comment

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not noticing this in the rest of the examples, I think I will leave it as is to conform for the time being. Thanks for the suggestion!

Copy link
Contributor

@waiting-for-dev waiting-for-dev left a comment

Choose a reason for hiding this comment

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

Good job @cpfergus1 . I think this validation is definitely the first step to have in place before attempting to fix edge cases 👏 👏

@cpfergus1 cpfergus1 force-pushed the Issue-4017 branch 2 times, most recently from 08f1a4d to 3ddee2d Compare April 14, 2021 13:21
@cpfergus1 cpfergus1 requested a review from kennyadsl April 14, 2021 14:24
@@ -3,10 +3,15 @@
module Spree::Image::ActiveStorageAttachment
extend ActiveSupport::Concern
include Spree::ActiveStorageAdapter
ACCEPTED_IMAGE_TYPES = %w(image/jpeg image/jpg image/png image/gif).freeze
Copy link
Member

Choose a reason for hiding this comment

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

What about adding an application configuration for this so that it's easier to configure for stores that want to restrict the list further?

Copy link
Member

Choose a reason for hiding this comment

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

On a side note: we can see also how Paperclip is restricting file types and use the same config for Paperclip as well.

Copy link
Contributor Author

@cpfergus1 cpfergus1 Apr 14, 2021

Choose a reason for hiding this comment

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

I replaced both %() in paperclipattachment and activestorage attachment with an new application configuration: Spree::Config.allowed_image_mime_types.

This commit implements validations for content type. Specifically,this
new validation ensures image file types .png/.jpeg/.jpg/.gif are the only
accepted file formats at this time, but can be edited by altering the
accepted_image_types method to allow other formats should the developer
desire to implement that functionality. This method was tested by
building the objects and testing validity with both a valid image file
and an invalid .txt file.

Commit History:
Created validation tests for images

Implemented validation of filetypes

Renamed validation

Patched file not found error for url

Core test update to be compatible with new validation

Core test update to be compatible with new validation

Updated test to have file names

Implemented compatible syntax for AS and Paperclip

Separate NoFileError to another branch

Refactored validation tests

Returned mistakenly deleted white space

Removed describe block

Changed accepted image types method to constant

Implemented application configuration for Image MIME types

Added documentation to configuration
Commit History:
Changed file name to represent module name
Removed unnecessary line breaks
@kennyadsl kennyadsl merged commit e09aef7 into solidusio:master Apr 15, 2021
@kennyadsl kennyadsl deleted the Issue-4017 branch April 15, 2021 08:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
changelog:solidus_core Changes to the solidus_core gem
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants