Commit b500d58c authored by Z.J. van de Weg's avatar Z.J. van de Weg Committed by Kamil Trzcinski

Allow API access with CI_JOB_TOKEN

parent a770e807
......@@ -33,10 +33,9 @@ class Ability
end
def allowed?(user, action, subject = :global, opts = {})
if subject.is_a?(Hash)
opts, subject = subject, :global
end
return user.abilities.include?(action) if user.is_a?(Ci::JobUser)
opts, subject = subject, :global if subject.is_a?(Hash)
policy = policy_for(user, subject)
case opts[:scope]
......
module Ci
# Empty class to differenciate between users that have authenticated by
# CI_JOB_TOKEN
class JobUser < User
def abilities
%i[read_build read_project access_git access_api]
end
end
end
......@@ -27,4 +27,6 @@ class BasePolicy < DeclarativePolicy::Base
with_scope :global
condition(:license_block) { License.block_changes? }
rule { ci_job_user }.prevent_all
end
---
title: Allow artifacts access with CI_JOB_TOKEN
merge_request:
author:
......@@ -8,6 +8,7 @@ module API
PRIVATE_TOKEN_HEADER = "HTTP_PRIVATE_TOKEN".freeze
PRIVATE_TOKEN_PARAM = :private_token
CI_JOB_TOKEN_PARAM = :ci_job_token
included do |base|
# OAuth2 Resource Server Authentication
......@@ -87,6 +88,16 @@ module API
find_user_by_authentication_token(token_string) || find_user_by_personal_access_token(token_string, scopes)
end
def find_user_by_ci_token
job_token = params[CI_JOB_TOKEN_PARAM].to_s
return nil unless job_token.present?
user = Ci::Build.find_by_token(job_token)&.user
user.becomes(Ci::JobUser) if user
end
def current_user
@current_user
end
......
......@@ -364,10 +364,12 @@ module API
def initial_current_user
return @initial_current_user if defined?(@initial_current_user)
Gitlab::Auth::UniqueIpsLimiter.limit_user! do
@initial_current_user ||= find_user_by_private_token(scopes: scopes_registered_for_endpoint)
@initial_current_user ||= doorkeeper_guard(scopes: scopes_registered_for_endpoint)
@initial_current_user ||= find_user_from_warden
@initial_current_user ||= find_user_by_ci_token
unless @initial_current_user && Gitlab::UserAccess.new(@initial_current_user).allowed?
@initial_current_user = nil
......
......@@ -191,49 +191,70 @@ describe API::Jobs do
end
describe 'GET /projects/:id/jobs/:job_id/artifacts' do
before do
stub_artifacts_object_storage
job
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
end
context 'job with artifacts' do
context 'when artifacts are stored locally' do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
context 'normal authenticatin' do
before do
stub_artifacts_object_storage
job
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts", api_user)
end
context 'authorized user' do
let(:download_headers) do
{ 'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
context 'job with artifacts' do
context 'when artifacts are stored locally' do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline) }
context 'authorized user' do
let(:download_headers) do
{ 'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
it 'returns specific job artifacts' do
expect(response).to have_http_status(200)
expect(response.headers).to include(download_headers)
expect(response.body).to match_file(job.artifacts_file.file.file)
end
end
it 'returns specific job artifacts' do
expect(response).to have_http_status(200)
expect(response.headers).to include(download_headers)
expect(response.body).to match_file(job.artifacts_file.file.file)
context 'unauthorized user' do
let(:api_user) { nil }
it 'does not return specific job artifacts' do
expect(response).to have_http_status(401)
end
end
end
context 'unauthorized user' do
let(:api_user) { nil }
context 'when artifacts are stored remotely' do
let(:job) { create(:ci_build, :artifacts, :remote_store, pipeline: pipeline) }
it 'does not return specific job artifacts' do
expect(response).to have_http_status(401)
it 'returns location redirect' do
expect(response).to have_http_status(302)
end
end
end
context 'when artifacts are stored remotely' do
let(:job) { create(:ci_build, :artifacts, :remote_store, pipeline: pipeline) }
it 'returns location redirect' do
expect(response).to have_http_status(302)
it 'does not return job artifacts if not uploaded' do
expect(response).to have_http_status(404)
end
end
end
it 'does not return job artifacts if not uploaded' do
expect(response).to have_http_status(404)
context 'authorized by ci_job_token' do
let(:job) { create(:ci_build, :artifacts, pipeline: pipeline, user: user) }
let(:download_headers) do
{ 'Content-Transfer-Encoding' => 'binary',
'Content-Disposition' => 'attachment; filename=ci_build_artifacts.zip' }
end
before do
get api("/projects/#{project.id}/jobs/#{job.id}/artifacts"), ci_job_token: job.token
end
it 'returns specific job artifacts' do
expect(response).to have_http_status(200)
expect(response.headers).to include(download_headers)
expect(response.body).to match_file(job.artifacts_file.file.file)
end
end
end
......
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