Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/api/maven_packages_spec.rb')
-rw-r--r--spec/requests/api/maven_packages_spec.rb569
1 files changed, 569 insertions, 0 deletions
diff --git a/spec/requests/api/maven_packages_spec.rb b/spec/requests/api/maven_packages_spec.rb
new file mode 100644
index 00000000000..189d6a4c1a4
--- /dev/null
+++ b/spec/requests/api/maven_packages_spec.rb
@@ -0,0 +1,569 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe API::MavenPackages do
+ include WorkhorseHelpers
+
+ let_it_be(:group) { create(:group) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :public, namespace: group) }
+ let_it_be(:package, reload: true) { create(:maven_package, project: project, name: project.full_path) }
+ let_it_be(:maven_metadatum, reload: true) { package.maven_metadatum }
+ let_it_be(:package_file) { package.package_files.with_file_name_like('%.xml').first }
+ let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').first }
+ let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
+ let_it_be(:job) { create(:ci_build, user: user) }
+ let_it_be(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
+ let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token, project: project) }
+
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:headers) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:headers_with_token) { headers.merge('Private-Token' => personal_access_token.token) }
+
+ let(:headers_with_deploy_token) do
+ headers.merge(
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ )
+ end
+
+ let(:version) { '1.0-SNAPSHOT' }
+
+ before do
+ project.add_developer(user)
+ end
+
+ shared_examples 'tracking the file download event' do
+ context 'with jar file' do
+ let_it_be(:package_file) { jar_file }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'pull_package'
+ end
+ end
+
+ shared_examples 'processing HEAD requests' do
+ subject { head api(url) }
+
+ before do
+ allow_any_instance_of(::Packages::PackageFileUploader).to receive(:fog_credentials).and_return(object_storage_credentials)
+ stub_package_file_object_storage(enabled: object_storage_enabled)
+ end
+
+ context 'with object storage enabled' do
+ let(:object_storage_enabled) { true }
+
+ before do
+ allow_any_instance_of(::Packages::PackageFileUploader).to receive(:file_storage?).and_return(false)
+ end
+
+ context 'non AWS provider' do
+ let(:object_storage_credentials) { { provider: 'Google' } }
+
+ it 'does not generated a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+ subject
+ end
+ end
+
+ context 'with AWS provider' do
+ let(:object_storage_credentials) { { provider: 'AWS', aws_access_key_id: 'test', aws_secret_access_key: 'test' } }
+
+ it 'generates a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).to receive(:head_url).and_call_original
+
+ subject
+ end
+ end
+ end
+
+ context 'with object storage disabled' do
+ let(:object_storage_enabled) { false }
+ let(:object_storage_credentials) { {} }
+
+ it 'does not generate a signed url for head' do
+ expect_any_instance_of(Fog::AWS::Storage::Files).not_to receive(:head_url)
+
+ subject
+ end
+ end
+ end
+
+ shared_examples 'downloads with a deploy token' do
+ it 'allows download with deploy token' do
+ download_file(
+ package_file.file_name,
+ {},
+ Gitlab::Auth::AuthFinders::DEPLOY_TOKEN_HEADER => deploy_token.token
+ )
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ shared_examples 'downloads with a job token' do
+ it 'allows download with job token' do
+ download_file(package_file.file_name, job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+ end
+
+ describe 'GET /api/v4/packages/maven/*path/:file_name' do
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ project.team.truncate
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'private project' do
+ subject { download_file_with_token(package_file.file_name) }
+
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'project name is different from a package name' do
+ before do
+ maven_metadatum.update!(path: "wrong_name/#{package.version}")
+ end
+
+ it 'rejects request' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/packages/maven/#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/packages/maven/*path/:file_name' do
+ let(:url) { "/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'GET /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+ before do
+ project.team.truncate
+ group.add_developer(user)
+ end
+
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'internal project' do
+ before do
+ group.group_member(user).destroy
+ project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ group.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/groups/#{group.id}/-/packages/maven/#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/groups/:id/-/packages/maven/*path/:file_name' do
+ let(:url) { "/groups/#{group.id}/-/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'GET /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ context 'a public project' do
+ subject { download_file(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'returns sha1 of the file' do
+ download_file(package_file.file_name + '.sha1')
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('text/plain')
+ expect(response.body).to eq(package_file.file_sha1)
+ end
+ end
+
+ context 'private project' do
+ before do
+ project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
+ end
+
+ subject { download_file_with_token(package_file.file_name) }
+
+ it_behaves_like 'tracking the file download event'
+
+ it 'returns the file' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq('application/octet-stream')
+ end
+
+ it 'denies download when not enough permissions' do
+ project.add_guest(user)
+
+ subject
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'denies download when no private token' do
+ download_file(package_file.file_name)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it_behaves_like 'downloads with a job token'
+
+ it_behaves_like 'downloads with a deploy token'
+ end
+
+ def download_file(file_name, params = {}, request_headers = headers)
+ get api("/projects/#{project.id}/packages/maven/" \
+ "#{maven_metadatum.path}/#{file_name}"), params: params, headers: request_headers
+ end
+
+ def download_file_with_token(file_name, params = {}, request_headers = headers_with_token)
+ download_file(file_name, params, request_headers)
+ end
+ end
+
+ describe 'HEAD /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ let(:url) { "/projects/#{project.id}/packages/maven/#{package.maven_metadatum.path}/#{package_file.file_name}" }
+
+ it_behaves_like 'processing HEAD requests'
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name/authorize' do
+ it 'rejects a malicious request' do
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2F.ssh%2Fauthorized_keys/authorize"), params: {}, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'authorizes posting package with a valid token' do
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
+ expect(json_response['TempPath']).not_to be_nil
+ end
+
+ it 'rejects request without a valid token' do
+ headers_with_token['Private-Token'] = 'foo'
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ it 'rejects request without a valid permission' do
+ project.add_guest(user)
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'rejects requests that did not go through gitlab-workhorse' do
+ headers.delete(Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER)
+
+ authorize_upload_with_token
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+
+ it 'authorizes upload with job token' do
+ authorize_upload(job_token: job.token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ it 'authorizes upload with deploy token' do
+ authorize_upload({}, headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ def authorize_upload(params = {}, request_headers = headers)
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/maven-metadata.xml/authorize"), params: params, headers: request_headers
+ end
+
+ def authorize_upload_with_token(params = {}, request_headers = headers_with_token)
+ authorize_upload(params, request_headers)
+ end
+ end
+
+ describe 'PUT /api/v4/projects/:id/packages/maven/*path/:file_name' do
+ let(:workhorse_token) { JWT.encode({ 'iss' => 'gitlab-workhorse' }, Gitlab::Workhorse.secret, 'HS256') }
+ let(:workhorse_header) { { 'GitLab-Workhorse' => '1.0', Gitlab::Workhorse::INTERNAL_API_REQUEST_HEADER => workhorse_token } }
+ let(:send_rewritten_field) { true }
+ let(:file_upload) { fixture_file_upload('spec/fixtures/packages/maven/my-app-1.0-20180724.124855-1.jar') }
+
+ before do
+ # by configuring this path we allow to pass temp file from any path
+ allow(Packages::PackageFileUploader).to receive(:workhorse_upload_path).and_return('/')
+ end
+
+ it 'rejects requests without a file from workhorse' do
+ upload_file_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ it 'rejects request without a token' do
+ upload_file
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+
+ context 'without workhorse rewritten field' do
+ let(:send_rewritten_field) { false }
+
+ it 'rejects the request' do
+ upload_file_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+ end
+
+ context 'when params from workhorse are correct' do
+ let(:params) { { file: file_upload } }
+
+ it 'rejects a malicious request' do
+ put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/%2e%2e%2f.ssh%2fauthorized_keys"), params: params, headers: headers_with_token
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ end
+
+ context 'without workhorse header' do
+ let(:workhorse_header) { {} }
+
+ subject { upload_file_with_token(params) }
+
+ it_behaves_like 'package workhorse uploads'
+ end
+
+ context 'event tracking' do
+ subject { upload_file_with_token(params) }
+
+ it_behaves_like 'a gitlab tracking event', described_class.name, 'push_package'
+ end
+
+ it 'creates package and stores package file' do
+ expect { upload_file_with_token(params) }.to change { project.packages.count }.by(1)
+ .and change { Packages::Maven::Metadatum.count }.by(1)
+ .and change { Packages::PackageFile.count }.by(1)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(jar_file.file_name).to eq(file_upload.original_filename)
+ end
+
+ it 'allows upload with job token' do
+ upload_file(params.merge(job_token: job.token))
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline
+ end
+
+ it 'allows upload with deploy token' do
+ upload_file(params, headers_with_deploy_token)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+
+ context 'version is not correct' do
+ let(:version) { '$%123' }
+
+ it 'rejects request' do
+ expect { upload_file_with_token(params) }.not_to change { project.packages.count }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to include('Validation failed')
+ end
+ end
+ end
+
+ def upload_file(params = {}, request_headers = headers)
+ url = "/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/my-app-1.0-20180724.124855-1.jar"
+ workhorse_finalize(
+ api(url),
+ method: :put,
+ file_key: :file,
+ params: params,
+ headers: request_headers,
+ send_rewritten_field: send_rewritten_field
+ )
+ end
+
+ def upload_file_with_token(params = {}, request_headers = headers_with_token)
+ upload_file(params, request_headers)
+ end
+ end
+end