Commit 7d28e39f authored by Stan Hu's avatar Stan Hu

Fix multipart attachments not uploading

Mixing and matching the use of Rack::Request and ActionDispatch::Request
in Rails 5 is bad, particularly if you have middleware that
manipulates or accesses environment variables.

`Gitlab::Middleware::Multipart` attempts to rewrite request parameters
to the proper values (e.g. replacing `data_file` with
`UploadedFile`). It does this by calling `Rack::Request#update_params`,
which essentially updates `env['rack.request.form_hash']`.

By changing to `ActionDispatch::Request`, the Go middleware was causing
the request parameters to be stored inside
`env['action_dispatch.request.request_parameters']`. Later calls to
`Rack::Request#update_params` would not have any effect because it would
attempt to update `env['rack.request.form_has']` instead of
`env['action_dispatch.request.request_parameters']`. As a result, the
controller still saw the old parameters.

Since the Go middleware appears to be using `ActionDispatch::Request`
for authorization methods, we can switch the multipart middleware to
use it too.

Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/9035
parent d432d674
...@@ -32,7 +32,7 @@ module Gitlab ...@@ -32,7 +32,7 @@ module Gitlab
class Handler class Handler
def initialize(env, message) def initialize(env, message)
@request = Rack::Request.new(env) @request = ActionDispatch::Request.new(env)
@rewritten_fields = message['rewritten_fields'] @rewritten_fields = message['rewritten_fields']
@open_files = [] @open_files = []
end end
......
...@@ -10,7 +10,9 @@ describe Gitlab::Middleware::Multipart do ...@@ -10,7 +10,9 @@ describe Gitlab::Middleware::Multipart do
shared_examples_for 'multipart upload files' do shared_examples_for 'multipart upload files' do
it 'opens top-level files' do it 'opens top-level files' do
Tempfile.open('top-level') do |tempfile| Tempfile.open('top-level') do |tempfile|
env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path, 'file.remote_id' => remote_id }, Gitlab::Workhorse.secret, 'gitlab-workhorse') rewritten = { 'file' => tempfile.path }
in_params = { 'file.name' => original_filename, 'file.path' => tempfile.path, 'file.remote_id' => remote_id }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(file)) expect_uploaded_file(tempfile, %w(file))
...@@ -21,7 +23,8 @@ describe Gitlab::Middleware::Multipart do ...@@ -21,7 +23,8 @@ describe Gitlab::Middleware::Multipart do
it 'opens files one level deep' do it 'opens files one level deep' do
Tempfile.open('one-level') do |tempfile| Tempfile.open('one-level') do |tempfile|
in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } } in_params = { 'user' => { 'avatar' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } }
env = post_env({ 'user[avatar]' => tempfile.path }, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse') rewritten = { 'user[avatar]' => tempfile.path }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(user avatar)) expect_uploaded_file(tempfile, %w(user avatar))
...@@ -32,7 +35,8 @@ describe Gitlab::Middleware::Multipart do ...@@ -32,7 +35,8 @@ describe Gitlab::Middleware::Multipart do
it 'opens files two levels deep' do it 'opens files two levels deep' do
Tempfile.open('two-levels') do |tempfile| Tempfile.open('two-levels') do |tempfile|
in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } } } in_params = { 'project' => { 'milestone' => { 'themesong' => { '.name' => original_filename, '.path' => tempfile.path, '.remote_id' => remote_id } } } }
env = post_env({ 'project[milestone][themesong]' => tempfile.path }, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse') rewritten = { 'project[milestone][themesong]' => tempfile.path }
env = post_env(rewritten, in_params, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect_uploaded_file(tempfile, %w(project milestone themesong)) expect_uploaded_file(tempfile, %w(project milestone themesong))
...@@ -42,7 +46,7 @@ describe Gitlab::Middleware::Multipart do ...@@ -42,7 +46,7 @@ describe Gitlab::Middleware::Multipart do
def expect_uploaded_file(tempfile, path, remote: false) def expect_uploaded_file(tempfile, path, remote: false)
expect(app).to receive(:call) do |env| expect(app).to receive(:call) do |env|
file = Rack::Request.new(env).params.dig(*path) file = get_params(env).dig(*path)
expect(file).to be_a(::UploadedFile) expect(file).to be_a(::UploadedFile)
expect(file.path).to eq(tempfile.path) expect(file.path).to eq(tempfile.path)
expect(file.original_filename).to eq(original_filename) expect(file.original_filename).to eq(original_filename)
...@@ -87,7 +91,7 @@ describe Gitlab::Middleware::Multipart do ...@@ -87,7 +91,7 @@ describe Gitlab::Middleware::Multipart do
env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path }, Gitlab::Workhorse.secret, 'gitlab-workhorse') env = post_env({ 'file' => tempfile.path }, { 'file.name' => original_filename, 'file.path' => tempfile.path }, Gitlab::Workhorse.secret, 'gitlab-workhorse')
expect(app).to receive(:call) do |env| expect(app).to receive(:call) do |env|
expect(Rack::Request.new(env).params['file']).to be_a(::UploadedFile) expect(get_params(env)['file']).to be_a(::UploadedFile)
end end
middleware.call(env) middleware.call(env)
...@@ -115,13 +119,21 @@ describe Gitlab::Middleware::Multipart do ...@@ -115,13 +119,21 @@ describe Gitlab::Middleware::Multipart do
allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir')) allow(Dir).to receive(:tmpdir).and_return(File.join(Dir.tmpdir, 'tmpsubdir'))
expect(app).to receive(:call) do |env| expect(app).to receive(:call) do |env|
expect(Rack::Request.new(env).params['file']).to be_a(::UploadedFile) expect(get_params(env)['file']).to be_a(::UploadedFile)
end end
middleware.call(env) middleware.call(env)
end end
end end
# Rails 5 doesn't combine the GET/POST parameters in
# ActionDispatch::HTTP::Parameters if action_dispatch.request.parameters is set:
# https://github.com/rails/rails/blob/aea6423f013ca48f7704c70deadf2cd6ac7d70a1/actionpack/lib/action_dispatch/http/parameters.rb#L41
def get_params(env)
req = ActionDispatch::Request.new(env)
req.GET.merge(req.POST)
end
def post_env(rewritten_fields, params, secret, issuer) def post_env(rewritten_fields, params, secret, issuer)
token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256') token = JWT.encode({ 'iss' => issuer, 'rewritten_fields' => rewritten_fields }, secret, 'HS256')
Rack::MockRequest.env_for( Rack::MockRequest.env_for(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment