Add direct upload support for personal snippets

This commit is contained in:
Jan Provaznik 2019-07-09 20:51:42 +02:00
parent 36a729f05f
commit 41d52bbfe9
12 changed files with 77 additions and 10 deletions

View File

@ -1 +1 @@
8.8.0 8.8.1

View File

@ -127,4 +127,8 @@ module UploadsActions
def model def model
strong_memoize(:model) { find_model } strong_memoize(:model) { find_model }
end end
def workhorse_authorize_request?
action_name == 'authorize'
end
end end

View File

@ -2,6 +2,7 @@
class UploadsController < ApplicationController class UploadsController < ApplicationController
include UploadsActions include UploadsActions
include WorkhorseRequest
UnknownUploadModelError = Class.new(StandardError) UnknownUploadModelError = Class.new(StandardError)
@ -21,7 +22,8 @@ class UploadsController < ApplicationController
before_action :upload_mount_satisfied? before_action :upload_mount_satisfied?
before_action :find_model before_action :find_model
before_action :authorize_access!, only: [:show] before_action :authorize_access!, only: [:show]
before_action :authorize_create_access!, only: [:create] before_action :authorize_create_access!, only: [:create, :authorize]
before_action :verify_workhorse_api!, only: [:authorize]
def uploader_class def uploader_class
PersonalFileUploader PersonalFileUploader
@ -72,7 +74,7 @@ class UploadsController < ApplicationController
end end
def render_unauthorized def render_unauthorized
if current_user if current_user || workhorse_authorize_request?
render_404 render_404
else else
authenticate_user! authenticate_user!

View File

@ -6,6 +6,10 @@ class PersonalFileUploader < FileUploader
options.storage_path options.storage_path
end end
def self.workhorse_local_upload_path
File.join(options.storage_path, 'uploads', TMP_UPLOAD_PATH)
end
def self.base_dir(model, _store = nil) def self.base_dir(model, _store = nil)
# base_dir is the path seen by the user when rendering Markdown, so # base_dir is the path seen by the user when rendering Markdown, so
# it should be the same for both local and object storage. It is # it should be the same for both local and object storage. It is

View File

@ -0,0 +1,5 @@
---
title: Remove EXIF from users/personal snippet uploads.
merge_request:
author:
type: security

View File

@ -30,6 +30,10 @@ scope path: :uploads do
to: 'uploads#create', to: 'uploads#create',
constraints: { model: /personal_snippet|user/, id: /\d+/ }, constraints: { model: /personal_snippet|user/, id: /\d+/ },
as: 'upload' as: 'upload'
post ':model/authorize',
to: 'uploads#authorize',
constraints: { model: /personal_snippet|user/ }
end end
# Redirect old note attachments path to new uploads path. # Redirect old note attachments path to new uploads path.

View File

@ -37,6 +37,8 @@ Parameter | Type | Description
`stop_id` | integer | Only uploads with equal or smaller ID will be processed `stop_id` | integer | Only uploads with equal or smaller ID will be processed
`dry_run` | boolean | Do not remove EXIF data, only check if EXIF data are present or not, default: true `dry_run` | boolean | Do not remove EXIF data, only check if EXIF data are present or not, default: true
`sleep_time` | float | Pause for number of seconds after processing each image, default: 0.3 seconds `sleep_time` | float | Pause for number of seconds after processing each image, default: 0.3 seconds
`uploader` | string | Run sanitization only for uploads of the given uploader (`FileUploader`, `PersonalFileUploader`, `NamespaceFileUploader`)
`since` | date | Run sanitization only for uploads newer than given date (e.g. `2019-05-01`)
If you have too many uploads, you can speed up sanitization by setting If you have too many uploads, you can speed up sanitization by setting
`sleep_time` to a lower value or by running multiple rake tasks in parallel, `sleep_time` to a lower value or by running multiple rake tasks in parallel,

View File

@ -53,15 +53,18 @@ module Gitlab
end end
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil) def batch_clean(start_id: nil, stop_id: nil, dry_run: true, sleep_time: nil, uploader: nil, since: nil)
relation = Upload.where('lower(path) like ? or lower(path) like ? or lower(path) like ?', relation = Upload.where('lower(path) like ? or lower(path) like ? or lower(path) like ?',
'%.jpg', '%.jpeg', '%.tiff') '%.jpg', '%.jpeg', '%.tiff')
relation = relation.where(uploader: uploader) if uploader
relation = relation.where('created_at > ?', since) if since
logger.info "running in dry run mode, no images will be rewritten" if dry_run logger.info "running in dry run mode, no images will be rewritten" if dry_run
find_params = { find_params = {
start: start_id.present? ? start_id.to_i : nil, start: start_id.present? ? start_id.to_i : nil,
finish: stop_id.present? ? stop_id.to_i : Upload.last&.id finish: stop_id.present? ? stop_id.to_i : Upload.last&.id,
batch_size: 1000
} }
relation.find_each(find_params) do |upload| relation.find_each(find_params) do |upload|

View File

@ -2,7 +2,7 @@ namespace :gitlab do
namespace :uploads do namespace :uploads do
namespace :sanitize do namespace :sanitize do
desc 'GitLab | Uploads | Remove EXIF from images.' desc 'GitLab | Uploads | Remove EXIF from images.'
task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time] => :environment do |task, args| task :remove_exif, [:start_id, :stop_id, :dry_run, :sleep_time, :uploader, :since] => :environment do |task, args|
args.with_defaults(dry_run: 'true') args.with_defaults(dry_run: 'true')
args.with_defaults(sleep_time: 0.3) args.with_defaults(sleep_time: 0.3)
@ -11,7 +11,9 @@ namespace :gitlab do
sanitizer = Gitlab::Sanitizers::Exif.new(logger: logger) sanitizer = Gitlab::Sanitizers::Exif.new(logger: logger)
sanitizer.batch_clean(start_id: args.start_id, stop_id: args.stop_id, sanitizer.batch_clean(start_id: args.start_id, stop_id: args.stop_id,
dry_run: args.dry_run != 'false', dry_run: args.dry_run != 'false',
sleep_time: args.sleep_time.to_f) sleep_time: args.sleep_time.to_f,
uploader: args.uploader,
since: args.since)
end end
end end
end end

View File

@ -21,8 +21,20 @@ shared_examples 'content publicly cached' do
end end
describe UploadsController do describe UploadsController do
include WorkhorseHelpers
let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) } let!(:user) { create(:user, avatar: fixture_file_upload("spec/fixtures/dk.png", "image/png")) }
describe 'POST #authorize' do
it_behaves_like 'handle uploads authorize' do
let(:uploader_class) { PersonalFileUploader }
let(:model) { create(:personal_snippet, :public) }
let(:params) do
{ model: 'personal_snippet', id: model.id }
end
end
end
describe 'POST create' do describe 'POST create' do
let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:jpg) { fixture_file_upload('spec/fixtures/rails_sample.jpg', 'image/jpg') }
let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') } let(:txt) { fixture_file_upload('spec/fixtures/doc_sample.txt', 'text/plain') }
@ -636,4 +648,10 @@ describe UploadsController do
end end
end end
end end
def post_authorize(verified: true)
request.headers.merge!(workhorse_internal_api_request_header) if verified
post :authorize, params: { model: 'personal_snippet', id: model.id }, format: :json
end
end end

View File

@ -5,7 +5,9 @@ describe Gitlab::Sanitizers::Exif do
describe '#batch_clean' do describe '#batch_clean' do
context 'with image uploads' do context 'with image uploads' do
let!(:uploads) { create_list(:upload, 3, :with_file, :issuable_upload) } set(:upload1) { create(:upload, :with_file, :issuable_upload) }
set(:upload2) { create(:upload, :with_file, :personal_snippet_upload) }
set(:upload3) { create(:upload, :with_file, created_at: 3.days.ago) }
it 'processes all uploads if range ID is not set' do it 'processes all uploads if range ID is not set' do
expect(sanitizer).to receive(:clean).exactly(3).times expect(sanitizer).to receive(:clean).exactly(3).times
@ -16,7 +18,19 @@ describe Gitlab::Sanitizers::Exif do
it 'processes only uploads in the selected range' do it 'processes only uploads in the selected range' do
expect(sanitizer).to receive(:clean).once expect(sanitizer).to receive(:clean).once
sanitizer.batch_clean(start_id: uploads[1].id, stop_id: uploads[1].id) sanitizer.batch_clean(start_id: upload1.id, stop_id: upload1.id)
end
it 'processes only uploads for the selected uploader' do
expect(sanitizer).to receive(:clean).once
sanitizer.batch_clean(uploader: 'PersonalFileUploader')
end
it 'processes only uploads created since specified date' do
expect(sanitizer).to receive(:clean).exactly(2).times
sanitizer.batch_clean(since: 2.days.ago)
end end
it 'pauses if sleep_time is set' do it 'pauses if sleep_time is set' do

View File

@ -7,6 +7,8 @@ shared_examples 'handle uploads' do
let(:secret) { FileUploader.generate_secret } let(:secret) { FileUploader.generate_secret }
let(:uploader_class) { FileUploader } let(:uploader_class) { FileUploader }
it_behaves_like 'handle uploads authorize'
describe "POST #create" do describe "POST #create" do
context 'when a user is not authorized to upload a file' do context 'when a user is not authorized to upload a file' do
it 'returns 404 status' do it 'returns 404 status' do
@ -271,7 +273,9 @@ shared_examples 'handle uploads' do
end end
end end
end end
end
shared_examples 'handle uploads authorize' do
describe "POST #authorize" do describe "POST #authorize" do
context 'when a user is not authorized to upload a file' do context 'when a user is not authorized to upload a file' do
it 'returns 404 status' do it 'returns 404 status' do
@ -284,7 +288,12 @@ shared_examples 'handle uploads' do
context 'when a user can upload a file' do context 'when a user can upload a file' do
before do before do
sign_in(user) sign_in(user)
model.add_developer(user)
if model.is_a?(PersonalSnippet)
model.update!(author: user)
else
model.add_developer(user)
end
end end
context 'and the request bypassed workhorse' do context 'and the request bypassed workhorse' do