Created
January 29, 2017 15:08
-
-
Save vbalazs/5af0bae67f0c3a9ca799da7b6c8be33b to your computer and use it in GitHub Desktop.
Grape validator to check file existence, size and content type on a given temporary bucket. This is useful when your frontend app only has access to a S3 bucket for temp files (with auto expiry policies for example). It uploads images from javascript and just passes the file id to the Grape API backend where we copy the image to the correct plac…
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Validators | |
# Provides fast image validation on the temp S3 bucket based on the file's metadata | |
# It checks for Content Type and file size | |
class S3TempImage < Grape::Validations::Base | |
MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB | |
attr_reader :attr_name, :file_name, :file_metadata | |
def validate_param!(attr_name, params) | |
@attr_name = attr_name | |
@file_name = params[attr_name] | |
# noop on false or blank | |
return if @option == false || file_name.blank? | |
fail_with('must be an uploaded image') unless file_exists? | |
fail_with('must be an image') unless valid_image_type? | |
fail_with('must be under 10 MB') unless valid_image_size? | |
end | |
def s3_client | |
@s3_client ||= Aws::S3::Client.new | |
end | |
private | |
def fail_with(message) | |
raise Grape::Exceptions::Validation, params: [@scope.full_name(attr_name)], | |
message: message | |
end | |
def bucket_name | |
ENV.fetch('AWS_TEMP_BUCKET_NAME') | |
end | |
def file_exists? | |
@file_metadata = s3_client.head_object({ bucket: bucket_name, key: file_name }) | |
rescue Aws::S3::Errors::NotFound => _ | |
false | |
end | |
def valid_image_type? | |
file_metadata.content_type.start_with?('image/') | |
end | |
def valid_image_size? | |
file_metadata.content_length.between?(100, MAX_FILE_SIZE) | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# rubocop:disable Rails/HttpPositionalArguments | |
require 'spec_helper' | |
require 'api/admin/validators/s3_temp_image' | |
describe Validators::S3TempImage do | |
module ValidationsSpec | |
module S3TempImageValidatorSpec | |
class API < Grape::API | |
default_format :json | |
params do | |
optional :photo, s3_temp_image: false | |
end | |
get '/noop_on_false' | |
params do | |
optional :photo, s3_temp_image: true | |
end | |
get '/allows_blank' | |
params do | |
optional :photo, s3_temp_image: true | |
end | |
get '/disallow_non_image_file' | |
end | |
end | |
end | |
def app | |
ValidationsSpec::S3TempImageValidatorSpec::API | |
end | |
context 'when it is disabled' do | |
it 'should not validate the param' do | |
get '/noop_on_false', photo: 'xyz' | |
expect(last_response.status).to eq(200) | |
end | |
end | |
context 'when param is empty' do | |
it 'should pass it, can mean delete' do | |
get '/allows_blank', photo: '' | |
expect(last_response.status).to eq(200) | |
end | |
end | |
context 'when param is non-empty' do | |
let(:s3_client) { Aws::S3::Client.new(stub_responses: true) } | |
before { allow_any_instance_of(described_class).to receive(:s3_client).and_return(s3_client) } | |
it 'should fail on non-existent files' do | |
s3_client.stub_responses(:head_object, 'NotFound') | |
get '/disallow_non_image_file', photo: '404.jpg' | |
expect(last_response.status).to eq(400) | |
expect(last_response.body).to eq('{"error":"photo must be an uploaded image"}') | |
end | |
it 'should fail on non-image files' do | |
s3_client.stub_responses(:head_object, { content_type: 'text/html' }) | |
get '/disallow_non_image_file', photo: 'invalid_image.html.jpg' | |
expect(last_response.status).to eq(400) | |
expect(last_response.body).to eq('{"error":"photo must be an image"}') | |
end | |
it 'should fail on too big images' do | |
s3_client.stub_responses(:head_object, | |
{ | |
content_type: 'image/jpeg', | |
content_length: 12_000_000 # ~12MB | |
}) | |
get '/disallow_non_image_file', photo: 'too_big.jpg' | |
expect(last_response.status).to eq(400) | |
expect(last_response.body).to eq('{"error":"photo must be under 10 MB"}') | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment