Maintenance: Refactoring of Avatar storage logic.

This commit is contained in:
Dominik Klein 2021-09-22 13:19:28 +02:00 committed by Thorsten Eckel
parent 867b36baa8
commit 7f178484c7
3 changed files with 77 additions and 11 deletions

View file

@ -722,31 +722,28 @@ curl http://localhost/api/v1/users/image/8d6cca1c6bdc226cf2ba131e264ca2c7 -v -u
=end =end
def image def image
# cache image # cache image
response.headers['Expires'] = 1.year.from_now.httpdate response.headers['Expires'] = 1.year.from_now.httpdate
response.headers['Cache-Control'] = 'cache, store, max-age=31536000, must-revalidate' response.headers['Cache-Control'] = 'cache, store, max-age=31536000, must-revalidate'
response.headers['Pragma'] = 'cache' response.headers['Pragma'] = 'cache'
file = Avatar.get_by_hash(params[:hash]) file = Avatar.get_by_hash(params[:hash])
if file if file
file_content_type = file.preferences['Content-Type'] || file.preferences['Mime-Type']
return serve_default_image if ActiveStorage.content_types_allowed_inline.exclude?(file_content_type)
send_data( send_data(
file.content, file.content,
filename: file.filename, filename: file.filename,
type: file.preferences['Content-Type'] || file.preferences['Mime-Type'], type: file_content_type,
disposition: 'inline' disposition: 'inline'
) )
return return
end end
# serve default image serve_default_image
image = 'R0lGODdhMAAwAOMAAMzMzJaWlr6+vqqqqqOjo8XFxbe3t7GxsZycnAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAMAAwAAAEcxDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru98TwuAA+KQAQqJK8EAgBAgMEqmkzUgBIeSwWGZtR5XhSqAULACCoGCJGwlm1MGQrq9RqgB8fm4ZTUgDBIEcRR9fz6HiImKi4yNjo+QkZKTlJWWkBEAOw=='
send_data(
Base64.decode64(image),
filename: 'image.gif',
type: 'image/gif',
disposition: 'inline'
)
end end
=begin =begin
@ -778,6 +775,11 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
return return
end end
if ActiveStorage::Variant::WEB_IMAGE_CONTENT_TYPES.exclude?(file_full[:mime_type])
render json: { error: 'Mime type is invalid' }, status: :unprocessable_entity
return
end
begin begin
file_resize = StaticAssets.data_url_attributes(params[:avatar_resize]) file_resize = StaticAssets.data_url_attributes(params[:avatar_resize])
rescue rescue
@ -1061,4 +1063,15 @@ curl http://localhost/api/v1/users/avatar -v -u #{login}:#{password} -H "Content
render json: { message: 'ok' }, status: :created render json: { message: 'ok' }, status: :created
end end
def serve_default_image
image = 'R0lGODdhMAAwAOMAAMzMzJaWlr6+vqqqqqOjo8XFxbe3t7GxsZycnAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAMAAwAAAEcxDISau9OOvNu/9gKI5kaZ5oqq5s675wLM90bd94ru98TwuAA+KQAQqJK8EAgBAgMEqmkzUgBIeSwWGZtR5XhSqAULACCoGCJGwlm1MGQrq9RqgB8fm4ZTUgDBIEcRR9fz6HiImKi4yNjo+QkZKTlJWWkBEAOw=='
send_data(
Base64.decode64(image),
filename: 'image.gif',
type: 'image/gif',
disposition: 'inline'
)
end
end end

View file

@ -72,7 +72,6 @@ add avatar by url
=end =end
def self.add(data) def self.add(data)
# lookups # lookups
if data[:object] if data[:object]
object_id = ObjectLookup.by_name(data[:object]) object_id = ObjectLookup.by_name(data[:object])

View file

@ -1517,6 +1517,60 @@ RSpec.describe 'User', type: :request do
expect { make_request(avatar_full: base64, avatar_resize: base64) } expect { make_request(avatar_full: base64, avatar_resize: base64) }
.to change { Avatar.list('User', user.id) } .to change { Avatar.list('User', user.id) }
end end
context 'with a not allowed mime-type' do
let(:base64) { 'data:image/svg+xml;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' }
it 'returns verbose error for a not allowed mime-type' do
make_request(avatar_full: base64)
expect(json_response).to include('error' => 'Mime type is invalid')
end
end
end
describe 'GET /api/v1/users/image/:hash', authenticated_as: :user do
let(:user) { create(:user) }
let(:avatar_mime_type) { 'image/png' }
let(:avatar) do
file = File.open('test/data/image/1000x1000.png', 'rb')
contents = file.read
Avatar.add(
object: 'User',
o_id: user.id,
default: true,
resize: {
content: contents,
mime_type: avatar_mime_type,
},
source: 'web',
deletable: true,
updated_by_id: 1,
created_by_id: 1,
)
end
let(:avatar_content) { Avatar.get_by_hash(avatar.store_hash).content }
before do
user.update!(image: avatar.store_hash)
end
def make_request(image_hash, params: {})
get "/api/v1/users/image/#{image_hash}", params: params, as: :json
end
it 'returns verbose error when full image is missing' do
make_request(avatar.store_hash)
expect(response.body).to eq(avatar_content)
end
context 'with a not allowed inline mime-type' do
let(:avatar_mime_type) { 'image/svg+xml' }
it 'returns the default image' do
make_request(avatar.store_hash)
expect(response.headers['Content-Type']).to include('image/gif')
end
end
end end
describe 'GET /api/v1/users/search, checks usage of the ids parameter', authenticated_as: :agent do describe 'GET /api/v1/users/search, checks usage of the ids parameter', authenticated_as: :agent do