Commit b70403cf authored by Ash McKenzie's avatar Ash McKenzie

Merge branch '9104-authenticate-with-ci_job_token-to-npm-registry' into 'master'

Authenticate with CI_JOB_TOKEN to NPM registry

Closes #9104

See merge request gitlab-org/gitlab!19059
parents d78caac0 153664b6
......@@ -109,6 +109,21 @@ Then, you could run `npm publish` either locally or via GitLab CI/CD:
- **GitLab CI/CD:** Set an `NPM_TOKEN` [variable](../../../ci/variables/README.md)
under your project's **Settings > CI/CD > Variables**.
### Authenticating with a CI job token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/9104) in GitLab Premium 12.5.
If you’re using NPM with GitLab CI/CD, a CI job token can be used instead of a personal access token.
The token will inherit the permissions of the user that generates the pipeline.
Add a corresponding section to your `.npmrc` file:
```ini
@foo:registry=https://gitlab.com/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=${env.CI_JOB_TOKEN}
//gitlab.com/api/v4/projects/{env.CI_PROJECT_ID>/packages/npm/:_authToken=${env.CI_JOB_TOKEN}
```
## Uploading packages
Before you will be able to upload a package, you need to specify the registry
......
---
title: CI_JOB_TOKEN can be accepted with 'Bearer ' prefix to allow for NPM registry
usage
merge_request: 19059
author:
type: added
......@@ -77,6 +77,7 @@ module API
requires :package_name, type: String, desc: 'Package name'
requires :versions, type: Hash, desc: 'Package version info'
end
route_setting :authentication, job_token_allowed: true
put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package!
......
......@@ -8,7 +8,7 @@ module EE
override :find_user_from_sources
def find_user_from_sources
find_user_from_access_token ||
find_user_from_bearer_token ||
find_user_from_job_token ||
find_user_from_warden
end
......
......@@ -10,6 +10,11 @@ module EE
JOB_TOKEN_HEADER = "HTTP_JOB_TOKEN".freeze
JOB_TOKEN_PARAM = :job_token
def find_user_from_bearer_token
find_user_from_job_bearer_token ||
find_user_from_access_token
end
def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed]
......@@ -31,9 +36,31 @@ module EE
super
end
override :validate_access_token!
def validate_access_token!(scopes: [])
# return early if we've already authenticated via a job token
@job_token_authentication.present? || super # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def scim_request?
current_request.path.starts_with?("/api/scim/")
end
private
def find_user_from_job_bearer_token
return unless route_authentication_setting[:job_token_allowed]
token = parsed_oauth_token
return unless token
job = ::Ci::Build.find_by_token(token)
return unless job
@job_token_authentication = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user
end
end
end
end
......
......@@ -18,9 +18,6 @@ describe Gitlab::Auth::UserAuthFinders do
request.update_param(key, value)
end
describe '#find_user_from_job_token' do
let(:job) { create(:ci_build, user: user) }
shared_examples 'find user from job token' do
context 'when route is allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: true } }
......@@ -28,16 +25,90 @@ describe Gitlab::Auth::UserAuthFinders do
it "returns an Unauthorized exception for an invalid token" do
set_token('invalid token')
expect { find_user_from_job_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
it "return user if token is valid" do
set_token(job.token)
expect(find_user_from_job_token).to eq(user)
expect(subject).to eq(user)
end
end
end
describe '#validate_access_token!' do
subject { validate_access_token! }
context 'with a job token' do
let(:route_authentication_setting) { { job_token_allowed: true } }
let(:job) { create(:ci_build, user: user) }
before do
env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
find_user_from_bearer_token
end
it 'does not raise an error' do
expect { subject }.not_to raise_error
end
end
context 'without a job token' do
let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
personal_access_token.revoke!
allow_any_instance_of(described_class).to receive(:access_token).and_return(personal_access_token)
end
it 'delegates the logic to super' do
expect { subject }.to raise_error(Gitlab::Auth::RevokedError)
end
end
end
describe '#find_user_from_bearer_token' do
let(:job) { create(:ci_build, user: user) }
subject { find_user_from_bearer_token }
context 'when the token is passed as an oauth token' do
def set_token(token)
env['HTTP_AUTHORIZATION'] = "Bearer #{token}"
end
context 'with a job token' do
it_behaves_like 'find user from job token'
end
context 'with oauth token' do
let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
let(:token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api').token }
before do
set_token(token)
end
it { is_expected.to eq user }
end
end
context 'with a personal access token' do
let(:pat) { create(:personal_access_token, user: user) }
let(:token) { pat.token }
before do
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = pat.token
end
it { is_expected.to eq user }
end
end
describe '#find_user_from_job_token' do
let(:job) { create(:ci_build, user: user) }
subject { find_user_from_job_token }
shared_examples 'job token disabled' do
context 'when route is not allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: false } }
......@@ -45,7 +116,7 @@ describe Gitlab::Auth::UserAuthFinders do
set_token(job.token)
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
expect(find_user_from_job_token).to be_nil
expect(subject).to be_nil
end
end
end
......@@ -56,6 +127,7 @@ describe Gitlab::Auth::UserAuthFinders do
end
it_behaves_like 'find user from job token'
it_behaves_like 'job token disabled'
end
context 'when the job token is in the params' do
......@@ -64,6 +136,7 @@ describe Gitlab::Auth::UserAuthFinders do
end
it_behaves_like 'find user from job token'
it_behaves_like 'job token disabled'
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