diff options
Diffstat (limited to 'spec/lib/gitlab/middleware/multipart_spec.rb')
-rw-r--r-- | spec/lib/gitlab/middleware/multipart_spec.rb | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/spec/lib/gitlab/middleware/multipart_spec.rb b/spec/lib/gitlab/middleware/multipart_spec.rb new file mode 100644 index 00000000000..65ec3535271 --- /dev/null +++ b/spec/lib/gitlab/middleware/multipart_spec.rb @@ -0,0 +1,198 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Middleware::Multipart do + include MultipartHelpers + + describe '#call' do + let(:app) { double(:app) } + let(:middleware) { described_class.new(app) } + let(:secret) { Gitlab::Workhorse.secret } + let(:issuer) { 'gitlab-workhorse' } + + subject do + env = post_env( + rewritten_fields: rewritten_fields, + params: params, + secret: secret, + issuer: issuer + ) + middleware.call(env) + end + + context 'remote file mode' do + let(:mode) { :remote } + + it_behaves_like 'handling all upload parameters conditions' + + context 'and a path set' do + include_context 'with one temporary file for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) } + let(:params) { upload_parameters_for(key: 'file', mode: mode, filename: filename, remote_id: remote_id).merge('file.path' => '/should/not/be/read') } + + it 'builds an UploadedFile' do + expect_uploaded_files(original_filename: filename, remote_id: remote_id, size: uploaded_file.size, params_path: %w(file)) + + subject + end + end + end + + context 'local file mode' do + let(:mode) { :local } + + it_behaves_like 'handling all upload parameters conditions' + + context 'when file is' do + include_context 'with one temporary file for multipart' + + let(:allowed_paths) { [Dir.tmpdir] } + + before do + expect_next_instance_of(::Gitlab::Middleware::Multipart::Handler) do |handler| + expect(handler).to receive(:allowed_paths).and_return(allowed_paths) + end + end + + context 'in allowed paths' do + let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) } + let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename) } + + it 'builds an UploadedFile' do + expect_uploaded_files(filepath: uploaded_filepath, original_filename: filename, size: uploaded_file.size, params_path: %w(file)) + + subject + end + end + + context 'not in allowed paths' do + let(:allowed_paths) { [] } + + let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) } + let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode) } + + it 'returns an error' do + result = subject + + expect(result[0]).to eq(400) + expect(result[2]).to include('insecure path used') + end + end + end + end + + context 'with dummy params in remote mode' do + let(:rewritten_fields) { { 'file' => 'should/not/be/read' } } + let(:params) { upload_parameters_for(key: 'file', mode: mode) } + let(:mode) { :remote } + + context 'with an invalid secret' do + let(:secret) { 'INVALID_SECRET' } + + it { expect { subject }.to raise_error(JWT::VerificationError) } + end + + context 'with an invalid issuer' do + let(:issuer) { 'INVALID_ISSUER' } + + it { expect { subject }.to raise_error(JWT::InvalidIssuerError) } + end + + context 'with invalid rewritten field key' do + invalid_keys = [ + '[file]', + ';file', + 'file]', + ';file]', + 'file]]', + 'file;;' + ] + + invalid_keys.each do |invalid_key| + context invalid_key do + let(:rewritten_fields) { { invalid_key => 'should/not/be/read' } } + + it { expect { subject }.to raise_error(RuntimeError, "invalid field: \"#{invalid_key}\"") } + end + end + end + + context 'with an invalid upload key' do + include_context 'with one temporary file for multipart' + + RSpec.shared_examples 'rejecting the invalid key' do |key_in_header:, key_in_upload_params:, error_message:| + let(:rewritten_fields) { rewritten_fields_hash(key_in_header => uploaded_filepath) } + let(:params) { upload_parameters_for(filepath: uploaded_filepath, key: key_in_upload_params, mode: mode, filename: filename, remote_id: remote_id) } + + it 'raises an error' do + expect { subject }.to raise_error(RuntimeError, error_message) + end + end + + it_behaves_like 'rejecting the invalid key', + key_in_header: 'file', + key_in_upload_params: 'wrong_key', + error_message: 'Empty JWT param: file.gitlab-workhorse-upload' + it_behaves_like 'rejecting the invalid key', + key_in_header: 'user[avatar', + key_in_upload_params: 'user[avatar]', + error_message: 'invalid field: "user[avatar"' + it_behaves_like 'rejecting the invalid key', + key_in_header: '[user]avatar', + key_in_upload_params: 'user[avatar]', + error_message: 'invalid field: "[user]avatar"' + it_behaves_like 'rejecting the invalid key', + key_in_header: 'user[]avatar', + key_in_upload_params: 'user[avatar]', + error_message: 'invalid field: "user[]avatar"' + it_behaves_like 'rejecting the invalid key', + key_in_header: 'user[avatar[image[url]]]', + key_in_upload_params: 'user[avatar]', + error_message: 'invalid field: "user[avatar[image[url]]]"' + it_behaves_like 'rejecting the invalid key', + key_in_header: '[]', + key_in_upload_params: 'user[avatar]', + error_message: 'invalid field: "[]"' + it_behaves_like 'rejecting the invalid key', + key_in_header: 'x' * 11000, + key_in_upload_params: 'user[avatar]', + error_message: "invalid field: \"#{'x' * 11000}\"" + end + + context 'with a modified JWT payload' do + include_context 'with one temporary file for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) } + let(:crafted_payload) { Base64.urlsafe_encode64({ 'path' => 'test' }.to_json) } + let(:params) do + upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename, remote_id: remote_id).tap do |params| + header, _, sig = params['file.gitlab-workhorse-upload'].split('.') + params['file.gitlab-workhorse-upload'] = [header, crafted_payload, sig].join('.') + end + end + + it 'raises an error' do + expect { subject }.to raise_error(JWT::VerificationError, 'Signature verification raised') + end + end + + context 'with a modified JWT sig' do + include_context 'with one temporary file for multipart' + + let(:rewritten_fields) { rewritten_fields_hash('file' => uploaded_filepath) } + let(:params) do + upload_parameters_for(filepath: uploaded_filepath, key: 'file', mode: mode, filename: filename, remote_id: remote_id).tap do |params| + header, payload, sig = params['file.gitlab-workhorse-upload'].split('.') + params['file.gitlab-workhorse-upload'] = [header, payload, "#{sig}modified"].join('.') + end + end + + it 'raises an error' do + expect { subject }.to raise_error(JWT::VerificationError, 'Signature verification raised') + end + end + end + end +end |