Commit 5955e1d9 authored by Steve Abrams's avatar Steve Abrams

Deploy tokens as API auth method

Add deploy tokens as an authentication strategy for
API endpoints and add permissions to deploy tokens
on the project policy for package access.
Enables deploy tokens for Maven packages.
parent 9089200d
...@@ -522,12 +522,14 @@ class Project < ApplicationRecord ...@@ -522,12 +522,14 @@ class Project < ApplicationRecord
def self.public_or_visible_to_user(user = nil, min_access_level = nil) def self.public_or_visible_to_user(user = nil, min_access_level = nil)
min_access_level = nil if user&.admin? min_access_level = nil if user&.admin?
if user return public_to_user unless user
if user.is_a?(DeployToken)
user.projects
else
where('EXISTS (?) OR projects.visibility_level IN (?)', where('EXISTS (?) OR projects.visibility_level IN (?)',
user.authorizations_for_projects(min_access_level: min_access_level), user.authorizations_for_projects(min_access_level: min_access_level),
Gitlab::VisibilityLevel.levels_for_user(user)) Gitlab::VisibilityLevel.levels_for_user(user))
else
public_to_user
end end
end end
......
# frozen_string_literal: true # frozen_string_literal: true
# Include this module if we want to pass something else than the user to # Include this module to have an object respond to user messages without being
# check policies. This defines several methods which the policy checker # a user.
# would call and check. #
# Use Case 1:
# Pass something else than the user to check policies. This defines several
# methods which the policy checker would call and check.
#
# Use Case 2:
# Access the API with non-user object such as deploy tokens. This defines
# several methods which the API auth flow would call.
module PolicyActor module PolicyActor
extend ActiveSupport::Concern extend ActiveSupport::Concern
...@@ -37,6 +44,30 @@ module PolicyActor ...@@ -37,6 +44,30 @@ module PolicyActor
def alert_bot? def alert_bot?
false false
end end
def deactivated?
false
end
def confirmation_required_on_sign_in?
false
end
def can?(action, subject = :global)
Ability.allowed?(self, action, subject)
end
def preferred_language
nil
end
def requires_ldap_check?
false
end
def try_obtain_ldap_lease
nil
end
end end
PolicyActor.prepend_if_ee('EE::PolicyActor') PolicyActor.prepend_if_ee('EE::PolicyActor')
...@@ -84,6 +84,16 @@ class ProjectPolicy < BasePolicy ...@@ -84,6 +84,16 @@ class ProjectPolicy < BasePolicy
project.merge_requests_allowing_push_to_user(user).any? project.merge_requests_allowing_push_to_user(user).any?
end end
desc "Deploy token with read_package_registry scope"
condition(:read_package_registry_deploy_token) do
user.is_a?(DeployToken) && user.has_access_to?(project) && user.read_package_registry
end
desc "Deploy token with write_package_registry scope"
condition(:write_package_registry_deploy_token) do
user.is_a?(DeployToken) && user.has_access_to?(project) && user.write_package_registry
end
with_scope :subject with_scope :subject
condition(:forking_allowed) do condition(:forking_allowed) do
@subject.feature_available?(:forking, @user) @subject.feature_available?(:forking, @user)
...@@ -530,6 +540,16 @@ class ProjectPolicy < BasePolicy ...@@ -530,6 +540,16 @@ class ProjectPolicy < BasePolicy
prevent :destroy_design prevent :destroy_design
end end
rule { read_package_registry_deploy_token }.policy do
enable :read_package
enable :read_project
end
rule { write_package_registry_deploy_token }.policy do
enable :create_package
enable :read_project
end
private private
def team_member? def team_member?
......
---
title: Deploy token authentication for API with Maven endpoints
merge_request: 30332
author:
type: added
...@@ -207,9 +207,9 @@ Enter a project name or hit enter to use the directory name as project name. ...@@ -207,9 +207,9 @@ Enter a project name or hit enter to use the directory name as project name.
The next step is to add the GitLab Package Registry as a Maven remote. If a The next step is to add the GitLab Package Registry as a Maven remote. If a
project is private or you want to upload Maven artifacts to GitLab, project is private or you want to upload Maven artifacts to GitLab,
credentials will need to be provided for authorization too. Support is available credentials will need to be provided for authorization too. Support is available
for [personal access tokens](#authenticating-with-a-personal-access-token) and for [personal access tokens](#authenticating-with-a-personal-access-token),
[CI job tokens](#authenticating-with-a-ci-job-token) only. [CI job tokens](#authenticating-with-a-ci-job-token), and
[Deploy tokens](../../project/deploy_tokens/index.md) and regular username/password [deploy tokens](../../project/deploy_tokens/index.md) only. Regular username/password
credentials do not work. credentials do not work.
### Authenticating with a personal access token ### Authenticating with a personal access token
...@@ -324,6 +324,59 @@ repositories { ...@@ -324,6 +324,59 @@ repositories {
} }
``` ```
### Authenticating with a deploy token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/213566) in [GitLab Premium](https://about.gitlab.com/pricing/) 13.0.
To authenticate with a [deploy token](./../../project/deploy_tokens/index.md),
set the scope to `api` when creating one, and add it to your Maven or Gradle configuration
files.
#### Authenticating with a deploy token in Maven
Add a corresponding section to your
[`settings.xml`](https://maven.apache.org/settings.html) file:
```xml
<settings>
<servers>
<server>
<id>gitlab-maven</id>
<configuration>
<httpHeaders>
<property>
<name>Deploy-Token</name>
<value>REPLACE_WITH_YOUR_DEPLOY_TOKEN</value>
</property>
</httpHeaders>
</configuration>
</server>
</servers>
</settings>
```
#### Authenticating with a deploy token in Gradle
To authenticate with a deploy token, add a repositories section to your
[`build.gradle`](https://docs.gradle.org/current/userguide/tutorial_using_tasks.html)
file:
```groovy
repositories {
maven {
url "https://<gitlab-url>/api/v4/groups/<group>/-/packages/maven"
name "GitLab"
credentials(HttpHeaderCredentials) {
name = 'Deploy-Token'
value = '<deploy-token>'
}
authentication {
header(HttpHeaderAuthentication)
}
}
}
```
## Configuring your project to use the GitLab Maven repository URL ## Configuring your project to use the GitLab Maven repository URL
To download and upload packages from GitLab, you need a `repository` and To download and upload packages from GitLab, you need a `repository` and
...@@ -397,7 +450,7 @@ project's ID can be used for uploading. ...@@ -397,7 +450,7 @@ project's ID can be used for uploading.
### Group level Maven endpoint ### Group level Maven endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8798) in GitLab Premium 11.7. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8798) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
If you rely on many packages, it might be inefficient to include the `repository` section If you rely on many packages, it might be inefficient to include the `repository` section
with a unique URL for each package. Instead, you can use the group level endpoint for with a unique URL for each package. Instead, you can use the group level endpoint for
...@@ -460,7 +513,7 @@ For retrieving artifacts, you can use either the ...@@ -460,7 +513,7 @@ For retrieving artifacts, you can use either the
### Instance level Maven endpoint ### Instance level Maven endpoint
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8274) in GitLab Premium 11.7. > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/8274) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.7.
If you rely on many packages, it might be inefficient to include the `repository` section If you rely on many packages, it might be inefficient to include the `repository` section
with a unique URL for each package. Instead, you can use the instance level endpoint for with a unique URL for each package. Instead, you can use the instance level endpoint for
......
...@@ -84,7 +84,7 @@ module API ...@@ -84,7 +84,7 @@ module API
requires :path, type: String, desc: 'Package path' requires :path, type: String, desc: 'Package path'
requires :file_name, type: String, desc: 'Package file name' requires :file_name, type: String, desc: 'Package file name'
end end
route_setting :authentication, job_token_allowed: true route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get 'packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do get 'packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
file_name, format = extract_format(params[:file_name]) file_name, format = extract_format(params[:file_name])
...@@ -125,7 +125,7 @@ module API ...@@ -125,7 +125,7 @@ module API
requires :path, type: String, desc: 'Package path' requires :path, type: String, desc: 'Package path'
requires :file_name, type: String, desc: 'Package file name' requires :file_name, type: String, desc: 'Package file name'
end end
route_setting :authentication, job_token_allowed: true route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get ':id/-/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do get ':id/-/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
file_name, format = extract_format(params[:file_name]) file_name, format = extract_format(params[:file_name])
...@@ -170,7 +170,7 @@ module API ...@@ -170,7 +170,7 @@ module API
requires :path, type: String, desc: 'Package path' requires :path, type: String, desc: 'Package path'
requires :file_name, type: String, desc: 'Package file name' requires :file_name, type: String, desc: 'Package file name'
end end
route_setting :authentication, job_token_allowed: true route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do get ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
authorize_read_package!(user_project) authorize_read_package!(user_project)
...@@ -201,7 +201,7 @@ module API ...@@ -201,7 +201,7 @@ module API
requires :path, type: String, desc: 'Package path' requires :path, type: String, desc: 'Package path'
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex
end end
route_setting :authentication, job_token_allowed: true route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':id/packages/maven/*path/:file_name/authorize', requirements: MAVEN_ENDPOINT_REQUIREMENTS do put ':id/packages/maven/*path/:file_name/authorize', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
authorize_upload! authorize_upload!
...@@ -224,7 +224,7 @@ module API ...@@ -224,7 +224,7 @@ module API
optional 'file.sha1', type: String, desc: %q(sha1 checksum of the file (generated by Workhorse)) optional 'file.sha1', type: String, desc: %q(sha1 checksum of the file (generated by Workhorse))
optional 'file.sha256', type: String, desc: %q(sha256 checksum of the file (generated by Workhorse)) optional 'file.sha256', type: String, desc: %q(sha256 checksum of the file (generated by Workhorse))
end end
route_setting :authentication, job_token_allowed: true route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
authorize_upload! authorize_upload!
......
...@@ -8,6 +8,7 @@ module EE ...@@ -8,6 +8,7 @@ module EE
override :find_user_from_sources override :find_user_from_sources
def find_user_from_sources def find_user_from_sources
deploy_token_from_request ||
find_user_from_bearer_token || find_user_from_bearer_token ||
find_user_from_job_token || find_user_from_job_token ||
find_user_from_warden find_user_from_warden
......
...@@ -11,10 +11,19 @@ describe API::MavenPackages do ...@@ -11,10 +11,19 @@ describe API::MavenPackages do
let_it_be(:jar_file) { package.package_files.with_file_name_like('%.jar').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(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:job) { create(:ci_build, 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(: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) { { '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_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' } let(:version) { '1.0-SNAPSHOT' }
before do before do
...@@ -78,6 +87,28 @@ describe API::MavenPackages do ...@@ -78,6 +87,28 @@ describe API::MavenPackages do
end 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 describe 'GET /api/v4/packages/maven/*path/:file_name' do
context 'a public project' do context 'a public project' do
subject { download_file(package_file.file_name) } subject { download_file(package_file.file_name) }
...@@ -123,12 +154,9 @@ describe API::MavenPackages do ...@@ -123,12 +154,9 @@ describe API::MavenPackages do
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:forbidden)
end end
it 'allows download with job token' do it_behaves_like 'downloads with a job token'
download_file(package_file.file_name, job_token: job.token)
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'downloads with a deploy token'
expect(response.media_type).to eq('application/octet-stream')
end
end end
context 'private project' do context 'private project' do
...@@ -161,12 +189,9 @@ describe API::MavenPackages do ...@@ -161,12 +189,9 @@ describe API::MavenPackages do
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:forbidden)
end end
it 'allows download with job token' do it_behaves_like 'downloads with a job token'
download_file(package_file.file_name, job_token: job.token)
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'downloads with a deploy token'
expect(response.media_type).to eq('application/octet-stream')
end
end end
it 'rejects request if feature is not in the license' do it 'rejects request if feature is not in the license' do
...@@ -254,12 +279,9 @@ describe API::MavenPackages do ...@@ -254,12 +279,9 @@ describe API::MavenPackages do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'allows download with job token' do it_behaves_like 'downloads with a job token'
download_file(package_file.file_name, job_token: job.token)
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'downloads with a deploy token'
expect(response.media_type).to eq('application/octet-stream')
end
end end
context 'private project' do context 'private project' do
...@@ -292,12 +314,9 @@ describe API::MavenPackages do ...@@ -292,12 +314,9 @@ describe API::MavenPackages do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'allows download with job token' do it_behaves_like 'downloads with a job token'
download_file(package_file.file_name, job_token: job.token)
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'downloads with a deploy token'
expect(response.media_type).to eq('application/octet-stream')
end
end end
it 'rejects request if feature is not in the license' do it 'rejects request if feature is not in the license' do
...@@ -375,12 +394,9 @@ describe API::MavenPackages do ...@@ -375,12 +394,9 @@ describe API::MavenPackages do
expect(response).to have_gitlab_http_status(:not_found) expect(response).to have_gitlab_http_status(:not_found)
end end
it 'allows download with job token' do it_behaves_like 'downloads with a job token'
download_file(package_file.file_name, job_token: job.token)
expect(response).to have_gitlab_http_status(:ok) it_behaves_like 'downloads with a deploy token'
expect(response.media_type).to eq('application/octet-stream')
end
end end
it 'rejects request if feature is not in the license' do it 'rejects request if feature is not in the license' do
...@@ -452,6 +468,12 @@ describe API::MavenPackages do ...@@ -452,6 +468,12 @@ describe API::MavenPackages do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
end 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) 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 put api("/projects/#{project.id}/packages/maven/com/example/my-app/#{version}/maven-metadata.xml/authorize"), params: params, headers: request_headers
end end
...@@ -531,6 +553,12 @@ describe API::MavenPackages do ...@@ -531,6 +553,12 @@ describe API::MavenPackages do
expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline expect(project.reload.packages.last.build_info.pipeline).to eq job.pipeline
end 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 context 'version is not correct' do
let(:version) { '$%123' } let(:version) { '$%123' }
......
...@@ -65,6 +65,7 @@ module API ...@@ -65,6 +65,7 @@ module API
end end
def find_user_from_sources def find_user_from_sources
deploy_token_from_request ||
find_user_from_access_token || find_user_from_access_token ||
find_user_from_job_token || find_user_from_job_token ||
find_user_from_warden find_user_from_warden
...@@ -90,12 +91,16 @@ module API ...@@ -90,12 +91,16 @@ module API
end end
def api_access_allowed?(user) def api_access_allowed?(user)
Gitlab::UserAccess.new(user).allowed? && user.can?(:access_api) user_allowed_or_deploy_token?(user) && user.can?(:access_api)
end end
def api_access_denied_message(user) def api_access_denied_message(user)
Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message Gitlab::Auth::UserAccessDeniedReason.new(user).rejection_message
end end
def user_allowed_or_deploy_token?(user)
Gitlab::UserAccess.new(user).allowed? || user.is_a?(DeployToken)
end
end end
class_methods do class_methods do
......
...@@ -25,6 +25,7 @@ module Gitlab ...@@ -25,6 +25,7 @@ module Gitlab
PRIVATE_TOKEN_PARAM = :private_token PRIVATE_TOKEN_PARAM = :private_token
JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze JOB_TOKEN_HEADER = 'HTTP_JOB_TOKEN'.freeze
JOB_TOKEN_PARAM = :job_token JOB_TOKEN_PARAM = :job_token
DEPLOY_TOKEN_HEADER = 'HTTP_DEPLOY_TOKEN'.freeze
RUNNER_TOKEN_PARAM = :token RUNNER_TOKEN_PARAM = :token
RUNNER_JOB_TOKEN_PARAM = :token RUNNER_JOB_TOKEN_PARAM = :token
...@@ -101,6 +102,16 @@ module Gitlab ...@@ -101,6 +102,16 @@ module Gitlab
access_token.user || raise(UnauthorizedError) access_token.user || raise(UnauthorizedError)
end end
# This returns a deploy token, not a user since a deploy token does not
# belong to a user.
def deploy_token_from_request
return unless route_authentication_setting[:deploy_token_allowed]
token = current_request.env[DEPLOY_TOKEN_HEADER].presence
DeployToken.active.find_by_token(token)
end
def find_runner_from_token def find_runner_from_token
return unless api_request? return unless api_request?
......
...@@ -17,6 +17,10 @@ describe Gitlab::Auth::AuthFinders do ...@@ -17,6 +17,10 @@ describe Gitlab::Auth::AuthFinders do
request.update_param(key, value) request.update_param(key, value)
end end
def set_header(key, value)
env[key] = value
end
describe '#find_user_from_warden' do describe '#find_user_from_warden' do
context 'with CSRF token' do context 'with CSRF token' do
before do before do
...@@ -31,7 +35,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -31,7 +35,7 @@ describe Gitlab::Auth::AuthFinders do
context 'with valid credentials' do context 'with valid credentials' do
it 'returns the user' do it 'returns the user' do
env['warden'] = double("warden", authenticate: user) set_header('warden', double("warden", authenticate: user))
expect(find_user_from_warden).to eq user expect(find_user_from_warden).to eq user
end end
...@@ -41,7 +45,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -41,7 +45,7 @@ describe Gitlab::Auth::AuthFinders do
context 'without CSRF token' do context 'without CSRF token' do
it 'returns nil' do it 'returns nil' do
allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(false) allow(Gitlab::RequestForgeryProtection).to receive(:verified?).and_return(false)
env['warden'] = double("warden", authenticate: user) set_header('warden', double("warden", authenticate: user))
expect(find_user_from_warden).to be_nil expect(find_user_from_warden).to be_nil
end end
...@@ -51,8 +55,8 @@ describe Gitlab::Auth::AuthFinders do ...@@ -51,8 +55,8 @@ describe Gitlab::Auth::AuthFinders do
describe '#find_user_from_feed_token' do describe '#find_user_from_feed_token' do
context 'when the request format is atom' do context 'when the request format is atom' do
before do before do
env['SCRIPT_NAME'] = 'url.atom' set_header('SCRIPT_NAME', 'url.atom')
env['HTTP_ACCEPT'] = 'application/atom+xml' set_header('HTTP_ACCEPT', 'application/atom+xml')
end end
context 'when feed_token param is provided' do context 'when feed_token param is provided' do
...@@ -94,7 +98,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -94,7 +98,7 @@ describe Gitlab::Auth::AuthFinders do
context 'when the request format is not atom' do context 'when the request format is not atom' do
it 'returns nil' do it 'returns nil' do
env['SCRIPT_NAME'] = 'json' set_header('SCRIPT_NAME', 'json')
set_param(:feed_token, user.feed_token) set_param(:feed_token, user.feed_token)
...@@ -104,7 +108,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -104,7 +108,7 @@ describe Gitlab::Auth::AuthFinders do
context 'when the request format is empty' do context 'when the request format is empty' do
it 'the method call does not modify the original value' do it 'the method call does not modify the original value' do
env['SCRIPT_NAME'] = 'url.atom' set_header('SCRIPT_NAME', 'url.atom')
env.delete('action_dispatch.request.formats') env.delete('action_dispatch.request.formats')
...@@ -118,7 +122,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -118,7 +122,7 @@ describe Gitlab::Auth::AuthFinders do
describe '#find_user_from_static_object_token' do describe '#find_user_from_static_object_token' do
shared_examples 'static object request' do shared_examples 'static object request' do
before do before do
env['SCRIPT_NAME'] = path set_header('SCRIPT_NAME', path)
end end
context 'when token header param is present' do context 'when token header param is present' do
...@@ -174,7 +178,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -174,7 +178,7 @@ describe Gitlab::Auth::AuthFinders do
context 'when request format is not archive nor blob' do context 'when request format is not archive nor blob' do
before do before do
env['script_name'] = 'url' set_header('script_name', 'url')
end end
it 'returns nil' do it 'returns nil' do
...@@ -183,11 +187,46 @@ describe Gitlab::Auth::AuthFinders do ...@@ -183,11 +187,46 @@ describe Gitlab::Auth::AuthFinders do
end end
end end
describe '#deploy_token_from_request' do
let_it_be(:deploy_token) { create(:deploy_token) }
let_it_be(:route_authentication_setting) { { deploy_token_allowed: true } }
subject { deploy_token_from_request }
it { is_expected.to be_nil }
shared_examples 'an unauthenticated route' do
context 'when route is not allowed to use deploy_tokens' do
let(:route_authentication_setting) { { deploy_token_allowed: false } }
it { is_expected.to be_nil }
end
end
context 'with deploy token headers' do
before do
set_header(described_class::DEPLOY_TOKEN_HEADER, deploy_token.token)
end
it { is_expected.to eq deploy_token }
it_behaves_like 'an unauthenticated route'
context 'with incorrect token' do
before do
set_header(described_class::DEPLOY_TOKEN_HEADER, 'invalid_token')
end
it { is_expected.to be_nil }
end
end
end
describe '#find_user_from_access_token' do describe '#find_user_from_access_token' do
let(:personal_access_token) { create(:personal_access_token, user: user) } let(:personal_access_token) { create(:personal_access_token, user: user) }
before do before do
env['SCRIPT_NAME'] = 'url.atom' set_header('SCRIPT_NAME', 'url.atom')
end end
it 'returns nil if no access_token present' do it 'returns nil if no access_token present' do
...@@ -196,13 +235,13 @@ describe Gitlab::Auth::AuthFinders do ...@@ -196,13 +235,13 @@ describe Gitlab::Auth::AuthFinders do
context 'when validate_access_token! returns valid' do context 'when validate_access_token! returns valid' do
it 'returns user' do it 'returns user' do
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
expect(find_user_from_access_token).to eq user expect(find_user_from_access_token).to eq user
end end
it 'returns exception if token has no user' do it 'returns exception if token has no user' do
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil) allow_any_instance_of(PersonalAccessToken).to receive(:user).and_return(nil)
expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { find_user_from_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
...@@ -211,7 +250,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -211,7 +250,7 @@ describe Gitlab::Auth::AuthFinders do
context 'with OAuth headers' do context 'with OAuth headers' do
it 'returns user' do it 'returns user' do
env['HTTP_AUTHORIZATION'] = "Bearer #{personal_access_token.token}" set_header('HTTP_AUTHORIZATION', "Bearer #{personal_access_token.token}")
expect(find_user_from_access_token).to eq user expect(find_user_from_access_token).to eq user
end end
...@@ -228,7 +267,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -228,7 +267,7 @@ describe Gitlab::Auth::AuthFinders do
let(:personal_access_token) { create(:personal_access_token, user: user) } let(:personal_access_token) { create(:personal_access_token, user: user) }
before do before do
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
end end
it 'returns exception if token has no user' do it 'returns exception if token has no user' do
...@@ -252,19 +291,19 @@ describe Gitlab::Auth::AuthFinders do ...@@ -252,19 +291,19 @@ describe Gitlab::Auth::AuthFinders do
end end
it 'returns the user for RSS requests' do it 'returns the user for RSS requests' do
env['SCRIPT_NAME'] = 'url.atom' set_header('SCRIPT_NAME', 'url.atom')
expect(find_user_from_web_access_token(:rss)).to eq(user) expect(find_user_from_web_access_token(:rss)).to eq(user)
end end
it 'returns the user for ICS requests' do it 'returns the user for ICS requests' do
env['SCRIPT_NAME'] = 'url.ics' set_header('SCRIPT_NAME', 'url.ics')
expect(find_user_from_web_access_token(:ics)).to eq(user) expect(find_user_from_web_access_token(:ics)).to eq(user)
end end
it 'returns the user for API requests' do it 'returns the user for API requests' do
env['SCRIPT_NAME'] = '/api/endpoint' set_header('SCRIPT_NAME', '/api/endpoint')
expect(find_user_from_web_access_token(:api)).to eq(user) expect(find_user_from_web_access_token(:api)).to eq(user)
end end
...@@ -274,12 +313,12 @@ describe Gitlab::Auth::AuthFinders do ...@@ -274,12 +313,12 @@ describe Gitlab::Auth::AuthFinders do
let(:personal_access_token) { create(:personal_access_token, user: user) } let(:personal_access_token) { create(:personal_access_token, user: user) }
before do before do
env['SCRIPT_NAME'] = 'url.atom' set_header('SCRIPT_NAME', 'url.atom')
end end
context 'passed as header' do context 'passed as header' do
it 'returns token if valid personal_access_token' do it 'returns token if valid personal_access_token' do
env[described_class::PRIVATE_TOKEN_HEADER] = personal_access_token.token set_header(described_class::PRIVATE_TOKEN_HEADER, personal_access_token.token)
expect(find_personal_access_token).to eq personal_access_token expect(find_personal_access_token).to eq personal_access_token
end end
...@@ -298,7 +337,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -298,7 +337,7 @@ describe Gitlab::Auth::AuthFinders do
end end
it 'returns exception if invalid personal_access_token' do it 'returns exception if invalid personal_access_token' do
env[described_class::PRIVATE_TOKEN_HEADER] = 'invalid_token' set_header(described_class::PRIVATE_TOKEN_HEADER, 'invalid_token')
expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { find_personal_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
...@@ -310,7 +349,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -310,7 +349,7 @@ describe Gitlab::Auth::AuthFinders do
context 'passed as header' do context 'passed as header' do
it 'returns token if valid oauth_access_token' do it 'returns token if valid oauth_access_token' do
env['HTTP_AUTHORIZATION'] = "Bearer #{token.token}" set_header('HTTP_AUTHORIZATION', "Bearer #{token.token}")
expect(find_oauth_access_token.token).to eq token.token expect(find_oauth_access_token.token).to eq token.token
end end
...@@ -329,7 +368,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -329,7 +368,7 @@ describe Gitlab::Auth::AuthFinders do
end end
it 'returns exception if invalid oauth_access_token' do it 'returns exception if invalid oauth_access_token' do
env['HTTP_AUTHORIZATION'] = "Bearer invalid_token" set_header('HTTP_AUTHORIZATION', "Bearer invalid_token")
expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { find_oauth_access_token }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
...@@ -337,7 +376,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -337,7 +376,7 @@ describe Gitlab::Auth::AuthFinders do
describe '#find_personal_access_token_from_http_basic_auth' do describe '#find_personal_access_token_from_http_basic_auth' do
def auth_header_with(token) def auth_header_with(token)
env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials('username', token) set_header('HTTP_AUTHORIZATION', ActionController::HttpAuthentication::Basic.encode_credentials('username', token))
end end
context 'access token is valid' do context 'access token is valid' do
...@@ -389,7 +428,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -389,7 +428,7 @@ describe Gitlab::Auth::AuthFinders do
end end
def set_auth(username, password) def set_auth(username, password)
env['HTTP_AUTHORIZATION'] = basic_http_auth(username, password) set_header('HTTP_AUTHORIZATION', basic_http_auth(username, password))
end end
subject { find_user_from_basic_auth_job } subject { find_user_from_basic_auth_job }
...@@ -502,20 +541,20 @@ describe Gitlab::Auth::AuthFinders do ...@@ -502,20 +541,20 @@ describe Gitlab::Auth::AuthFinders do
context 'when the job token is in the headers' do context 'when the job token is in the headers' do
it 'returns the user if valid job token' do it 'returns the user if valid job token' do
env[described_class::JOB_TOKEN_HEADER] = job.token set_header(described_class::JOB_TOKEN_HEADER, job.token)
is_expected.to eq(user) is_expected.to eq(user)
expect(@current_authenticated_job).to eq(job) expect(@current_authenticated_job).to eq(job)
end end
it 'returns nil without job token' do it 'returns nil without job token' do
env[described_class::JOB_TOKEN_HEADER] = '' set_header(described_class::JOB_TOKEN_HEADER, '')
is_expected.to be_nil is_expected.to be_nil
end end
it 'returns exception if invalid job token' do it 'returns exception if invalid job token' do
env[described_class::JOB_TOKEN_HEADER] = 'invalid token' set_header(described_class::JOB_TOKEN_HEADER, 'invalid token')
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError) expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end end
...@@ -524,7 +563,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -524,7 +563,7 @@ describe Gitlab::Auth::AuthFinders do
let(:route_authentication_setting) { { job_token_allowed: false } } let(:route_authentication_setting) { { job_token_allowed: false } }
it 'sets current_user to nil' do it 'sets current_user to nil' do
env[described_class::JOB_TOKEN_HEADER] = job.token set_header(described_class::JOB_TOKEN_HEADER, job.token)
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true) allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
...@@ -586,7 +625,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -586,7 +625,7 @@ describe Gitlab::Auth::AuthFinders do
context 'with API requests' do context 'with API requests' do
before do before do
env['SCRIPT_NAME'] = '/api/endpoint' set_header('SCRIPT_NAME', '/api/endpoint')
end end
it 'returns the runner if token is valid' do it 'returns the runner if token is valid' do
...@@ -614,7 +653,7 @@ describe Gitlab::Auth::AuthFinders do ...@@ -614,7 +653,7 @@ describe Gitlab::Auth::AuthFinders do
context 'without API requests' do context 'without API requests' do
before do before do
env['SCRIPT_NAME'] = 'url.ics' set_header('SCRIPT_NAME', 'url.ics')
end end
it 'returns nil if token is valid' do it 'returns nil if token is valid' do
......
...@@ -3625,6 +3625,24 @@ describe Project do ...@@ -3625,6 +3625,24 @@ describe Project do
expect(projects).to contain_exactly(public_project) expect(projects).to contain_exactly(public_project)
end end
end end
context 'with deploy token users' do
let_it_be(:private_project) { create(:project, :private) }
subject { described_class.all.public_or_visible_to_user(user) }
context 'deploy token user without project' do
let_it_be(:user) { create(:deploy_token) }
it { is_expected.to eq [] }
end
context 'deploy token user with project' do
let_it_be(:user) { create(:deploy_token, projects: [private_project]) }
it { is_expected.to include(private_project) }
end
end
end end
describe '.ids_with_issuables_available_for' do describe '.ids_with_issuables_available_for' do
......
...@@ -672,4 +672,28 @@ describe ProjectPolicy do ...@@ -672,4 +672,28 @@ describe ProjectPolicy do
end end
end end
end end
context 'deploy token access' do
let!(:project_deploy_token) do
create(:project_deploy_token, project: project, deploy_token: deploy_token)
end
subject { described_class.new(deploy_token, project) }
context 'a deploy token with read_package_registry scope' do
let(:deploy_token) { create(:deploy_token, read_package_registry: true) }
it { is_expected.to be_allowed(:read_package) }
it { is_expected.to be_allowed(:read_project) }
it { is_expected.to be_disallowed(:create_package) }
end
context 'a deploy token with write_package_registry scope' do
let(:deploy_token) { create(:deploy_token, write_package_registry: true) }
it { is_expected.to be_allowed(:create_package) }
it { is_expected.to be_allowed(:read_project) }
it { is_expected.to be_disallowed(:destroy_package) }
end
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