Commit 0956ef50 authored by David Fernandez's avatar David Fernandez

Merge branch '280586-dependency-proxy-deploy-tokens' into 'master'

Deploy token access for the dependency proxy

See merge request gitlab-org/gitlab!64363
parents 0d56b371 b723758c
# frozen_string_literal: true
module DependencyProxy
module Auth
extend ActiveSupport::Concern
included do
# We disable `authenticate_user!` since the `DependencyProxy::Auth` performs auth using JWT token
skip_before_action :authenticate_user!, raise: false
prepend_before_action :authenticate_user_from_jwt_token!
end
def authenticate_user_from_jwt_token!
return unless dependency_proxy_for_private_groups?
authenticate_with_http_token do |token, _|
user = user_from_token(token)
sign_in(user) if user
end
request_bearer_token! unless current_user
end
private
def dependency_proxy_for_private_groups?
Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true)
end
def request_bearer_token!
# unfortunately, we cannot use https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html#method-i-authentication_request
response.headers['WWW-Authenticate'] = ::DependencyProxy::Registry.authenticate_header
render plain: '', status: :unauthorized
end
def user_from_token(token)
token_payload = DependencyProxy::AuthTokenService.decoded_token_payload(token)
User.find(token_payload['user_id'])
rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature
nil
end
end
end
...@@ -12,15 +12,15 @@ module DependencyProxy ...@@ -12,15 +12,15 @@ module DependencyProxy
private private
def verify_dependency_proxy_enabled! def verify_dependency_proxy_enabled!
render_404 unless group.dependency_proxy_feature_available? render_404 unless group&.dependency_proxy_feature_available?
end end
def authorize_read_dependency_proxy! def authorize_read_dependency_proxy!
access_denied! unless can?(current_user, :read_dependency_proxy, group) access_denied! unless can?(auth_user, :read_dependency_proxy, group)
end end
def authorize_admin_dependency_proxy! def authorize_admin_dependency_proxy!
access_denied! unless can?(current_user, :admin_dependency_proxy, group) access_denied! unless can?(auth_user, :admin_dependency_proxy, group)
end end
end end
end end
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
module Groups module Groups
class DependencyProxiesController < Groups::ApplicationController class DependencyProxiesController < Groups::ApplicationController
include DependencyProxy::GroupAccess include ::DependencyProxy::GroupAccess
before_action :authorize_admin_dependency_proxy!, only: :update before_action :authorize_admin_dependency_proxy!, only: :update
before_action :dependency_proxy before_action :dependency_proxy
......
# frozen_string_literal: true
module Groups
module DependencyProxy
class ApplicationController < ::ApplicationController
EMPTY_AUTH_RESULT = Gitlab::Auth::Result.new(nil, nil, nil, nil).freeze
delegate :actor, to: :@authentication_result, allow_nil: true
# This allows auth_user to be set in the base ApplicationController
alias_method :authenticated_user, :actor
# We disable `authenticate_user!` since the `DependencyProxy::ApplicationController` performs auth using JWT token
skip_before_action :authenticate_user!, raise: false
prepend_before_action :authenticate_user_from_jwt_token!
def authenticate_user_from_jwt_token!
return unless dependency_proxy_for_private_groups?
if Feature.enabled?(:dependency_proxy_deploy_tokens)
authenticate_with_http_token do |token, _|
@authentication_result = EMPTY_AUTH_RESULT
found_user = user_from_token(token)
sign_in(found_user) if found_user.is_a?(User)
end
request_bearer_token! unless authenticated_user
else
authenticate_with_http_token do |token, _|
user = user_from_token(token)
sign_in(user) if user
end
request_bearer_token! unless current_user
end
end
private
def dependency_proxy_for_private_groups?
Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true)
end
def request_bearer_token!
# unfortunately, we cannot use https://api.rubyonrails.org/classes/ActionController/HttpAuthentication/Token.html#method-i-authentication_request
response.headers['WWW-Authenticate'] = ::DependencyProxy::Registry.authenticate_header
render plain: '', status: :unauthorized
end
def user_from_token(token)
token_payload = ::DependencyProxy::AuthTokenService.decoded_token_payload(token)
return User.find(token_payload['user_id']) unless Feature.enabled?(:dependency_proxy_deploy_tokens)
if token_payload['user_id']
token_user = User.find(token_payload['user_id'])
return unless token_user
@authentication_result = Gitlab::Auth::Result.new(token_user, nil, :user, [])
return token_user
elsif token_payload['deploy_token']
deploy_token = DeployToken.active.find_by_token(token_payload['deploy_token'])
return unless deploy_token
@authentication_result = Gitlab::Auth::Result.new(deploy_token, nil, :deploy_token, [])
return deploy_token
end
nil
rescue JWT::DecodeError, JWT::ExpiredSignature, JWT::ImmatureSignature
nil
end
end
end
end
# frozen_string_literal: true # frozen_string_literal: true
class Groups::DependencyProxyAuthController < ApplicationController class Groups::DependencyProxyAuthController < ::Groups::DependencyProxy::ApplicationController
include DependencyProxy::Auth
feature_category :dependency_proxy feature_category :dependency_proxy
def authenticate def authenticate
......
# frozen_string_literal: true # frozen_string_literal: true
class Groups::DependencyProxyForContainersController < Groups::ApplicationController class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy::ApplicationController
include DependencyProxy::Auth include Gitlab::Utils::StrongMemoize
include DependencyProxy::GroupAccess include DependencyProxy::GroupAccess
include SendFileUpload include SendFileUpload
include ::PackagesHelper # for event tracking include ::PackagesHelper # for event tracking
before_action :ensure_group
before_action :ensure_token_granted! before_action :ensure_token_granted!
before_action :ensure_feature_enabled! before_action :ensure_feature_enabled!
...@@ -24,7 +25,7 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro ...@@ -24,7 +25,7 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
content_type = result[:manifest].content_type content_type = result[:manifest].content_type
event_name = tracking_event_name(object_type: :manifest, from_cache: result[:from_cache]) event_name = tracking_event_name(object_type: :manifest, from_cache: result[:from_cache])
track_package_event(event_name, :dependency_proxy, namespace: group, user: current_user) track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
send_upload( send_upload(
result[:manifest].file, result[:manifest].file,
proxy: true, proxy: true,
...@@ -42,7 +43,7 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro ...@@ -42,7 +43,7 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
if result[:status] == :success if result[:status] == :success
event_name = tracking_event_name(object_type: :blob, from_cache: result[:from_cache]) event_name = tracking_event_name(object_type: :blob, from_cache: result[:from_cache])
track_package_event(event_name, :dependency_proxy, namespace: group, user: current_user) track_package_event(event_name, :dependency_proxy, namespace: group, user: auth_user)
send_upload(result[:blob].file) send_upload(result[:blob].file)
else else
head result[:http_status] head result[:http_status]
...@@ -51,6 +52,12 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro ...@@ -51,6 +52,12 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
private private
def group
strong_memoize(:group) do
Group.find_by_full_path(params[:group_id], follow_redirects: request.get?)
end
end
def image def image
params[:image] params[:image]
end end
...@@ -71,6 +78,10 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro ...@@ -71,6 +78,10 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
group.dependency_proxy_setting || group.create_dependency_proxy_setting group.dependency_proxy_setting || group.create_dependency_proxy_setting
end end
def ensure_group
render_404 unless group
end
def ensure_feature_enabled! def ensure_feature_enabled!
render_404 unless dependency_proxy.enabled render_404 unless dependency_proxy.enabled
end end
......
...@@ -10,6 +10,7 @@ class DeployToken < ApplicationRecord ...@@ -10,6 +10,7 @@ class DeployToken < ApplicationRecord
AVAILABLE_SCOPES = %i(read_repository read_registry write_registry AVAILABLE_SCOPES = %i(read_repository read_registry write_registry
read_package_registry write_package_registry).freeze read_package_registry write_package_registry).freeze
GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token' GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'
REQUIRED_DEPENDENCY_PROXY_SCOPES = %i[read_registry write_registry].freeze
default_value_for(:expires_at) { Forever.date } default_value_for(:expires_at) { Forever.date }
...@@ -46,6 +47,12 @@ class DeployToken < ApplicationRecord ...@@ -46,6 +47,12 @@ class DeployToken < ApplicationRecord
active.find_by(name: GITLAB_DEPLOY_TOKEN_NAME) active.find_by(name: GITLAB_DEPLOY_TOKEN_NAME)
end end
def valid_for_dependency_proxy?
group_type? &&
active? &&
REQUIRED_DEPENDENCY_PROXY_SCOPES.all? { |scope| scope.in?(scopes) }
end
def revoke! def revoke!
update!(revoked: true) update!(revoked: true)
end end
...@@ -73,6 +80,14 @@ class DeployToken < ApplicationRecord ...@@ -73,6 +80,14 @@ class DeployToken < ApplicationRecord
holder.has_access_to?(requested_project) holder.has_access_to?(requested_project)
end end
def has_access_to_group?(requested_group)
return false unless active?
return false unless group_type?
return false unless holder
holder.has_access_to_group?(requested_group)
end
# This is temporal. Currently we limit DeployToken # This is temporal. Currently we limit DeployToken
# to a single project or group, later we're going to # to a single project or group, later we're going to
# extend that to be for multiple projects and namespaces. # extend that to be for multiple projects and namespaces.
......
...@@ -11,9 +11,14 @@ class GroupDeployToken < ApplicationRecord ...@@ -11,9 +11,14 @@ class GroupDeployToken < ApplicationRecord
def has_access_to?(requested_project) def has_access_to?(requested_project)
requested_project_group = requested_project&.group requested_project_group = requested_project&.group
return false unless requested_project_group return false unless requested_project_group
return true if requested_project_group.id == group_id
requested_project_group has_access_to_group?(requested_project_group)
end
def has_access_to_group?(requested_group)
return true if requested_group.id == group_id
requested_group
.ancestors .ancestors
.where(id: group_id) .where(id: group_id)
.exists? .exists?
......
...@@ -50,6 +50,14 @@ class GroupPolicy < BasePolicy ...@@ -50,6 +50,14 @@ class GroupPolicy < BasePolicy
@subject.dependency_proxy_feature_available? @subject.dependency_proxy_feature_available?
end end
condition(:dependency_proxy_access_allowed) do
if Feature.enabled?(:dependency_proxy_for_private_groups, default_enabled: true)
access_level >= GroupMember::REPORTER || valid_dependency_proxy_deploy_token
else
can?(:read_group)
end
end
desc "Deploy token with read_package_registry scope" desc "Deploy token with read_package_registry scope"
condition(:read_package_registry_deploy_token) do condition(:read_package_registry_deploy_token) do
@user.is_a?(DeployToken) && @user.groups.include?(@subject) && @user.read_package_registry @user.is_a?(DeployToken) && @user.groups.include?(@subject) && @user.read_package_registry
...@@ -212,7 +220,7 @@ class GroupPolicy < BasePolicy ...@@ -212,7 +220,7 @@ class GroupPolicy < BasePolicy
enable :read_group enable :read_group
end end
rule { can?(:read_group) & dependency_proxy_available } rule { dependency_proxy_access_allowed & dependency_proxy_available }
.enable :read_dependency_proxy .enable :read_dependency_proxy
rule { developer & dependency_proxy_available } rule { developer & dependency_proxy_available }
...@@ -260,6 +268,10 @@ class GroupPolicy < BasePolicy ...@@ -260,6 +268,10 @@ class GroupPolicy < BasePolicy
def resource_access_token_creation_allowed? def resource_access_token_creation_allowed?
resource_access_token_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed? resource_access_token_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed?
end end
def valid_dependency_proxy_deploy_token
@user.is_a?(DeployToken) && @user&.valid_for_dependency_proxy? && @user&.has_access_to_group?(@subject)
end
end end
GroupPolicy.prepend_mod_with('GroupPolicy') GroupPolicy.prepend_mod_with('GroupPolicy')
...@@ -8,10 +8,7 @@ module Auth ...@@ -8,10 +8,7 @@ module Auth
def execute(authentication_abilities:) def execute(authentication_abilities:)
return error('dependency proxy not enabled', 404) unless ::Gitlab.config.dependency_proxy.enabled return error('dependency proxy not enabled', 404) unless ::Gitlab.config.dependency_proxy.enabled
return error('access forbidden', 403) unless valid_user_actor?
# Because app/controllers/concerns/dependency_proxy/auth.rb consumes this
# JWT only as `User.find`, we currently only allow User (not DeployToken, etc)
return error('access forbidden', 403) unless current_user
{ token: authorized_token.encoded } { token: authorized_token.encoded }
end end
...@@ -36,11 +33,24 @@ module Auth ...@@ -36,11 +33,24 @@ module Auth
private private
def valid_user_actor?
current_user || valid_deploy_token?
end
def valid_deploy_token?
deploy_token && deploy_token.valid_for_dependency_proxy?
end
def authorized_token def authorized_token
JSONWebToken::HMACToken.new(self.class.secret).tap do |token| JSONWebToken::HMACToken.new(self.class.secret).tap do |token|
token['user_id'] = current_user.id token['user_id'] = current_user.id if current_user
token['deploy_token'] = deploy_token.token if deploy_token
token.expire_time = self.class.token_expire_at token.expire_time = self.class.token_expire_at
end end
end end
def deploy_token
params[:deploy_token]
end
end end
end end
---
name: dependency_proxy_deploy_tokens
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/64363
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/334565
milestone: '14.2'
type: development
group: group::package
default_enabled: false
...@@ -89,6 +89,7 @@ You can authenticate using: ...@@ -89,6 +89,7 @@ You can authenticate using:
- Your GitLab username and password. - Your GitLab username and password.
- A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `read_registry` and `write_registry`. - A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `read_registry` and `write_registry`.
- A [group deploy token](../../../user/project/deploy_tokens/index.md#group-deploy-token) with the scope set to `read_registry` and `write_registry`.
#### Authenticate within CI/CD #### Authenticate within CI/CD
...@@ -123,7 +124,7 @@ Proxy manually without including the port: ...@@ -123,7 +124,7 @@ Proxy manually without including the port:
docker pull gitlab.example.com:443/my-group/dependency_proxy/containers/alpine:latest docker pull gitlab.example.com:443/my-group/dependency_proxy/containers/alpine:latest
``` ```
You can also use [custom CI/CD variables](../../../ci/variables/index.md#custom-cicd-variables) to store and access your personal access token or other valid credentials. You can also use [custom CI/CD variables](../../../ci/variables/index.md#custom-cicd-variables) to store and access your personal access token or deploy token.
### Store a Docker image in Dependency Proxy cache ### Store a Docker image in Dependency Proxy cache
......
...@@ -30,16 +30,31 @@ RSpec.describe Groups::DependencyProxyAuthController do ...@@ -30,16 +30,31 @@ RSpec.describe Groups::DependencyProxyAuthController do
end end
context 'with valid JWT' do context 'with valid JWT' do
let_it_be(:user) { create(:user) } context 'user' do
let_it_be(:user) { create(:user) }
let(:jwt) { build_jwt(user) } let(:jwt) { build_jwt(user) }
let(:token_header) { "Bearer #{jwt.encoded}" } let(:token_header) { "Bearer #{jwt.encoded}" }
before do before do
request.headers['HTTP_AUTHORIZATION'] = token_header request.headers['HTTP_AUTHORIZATION'] = token_header
end
it { is_expected.to have_gitlab_http_status(:success) }
end end
it { is_expected.to have_gitlab_http_status(:success) } context 'deploy token' do
let_it_be(:user) { create(:deploy_token) }
let(:jwt) { build_jwt(user) }
let(:token_header) { "Bearer #{jwt.encoded}" }
before do
request.headers['HTTP_AUTHORIZATION'] = token_header
end
it { is_expected.to have_gitlab_http_status(:success) }
end
end end
context 'with invalid JWT' do context 'with invalid JWT' do
...@@ -51,7 +66,7 @@ RSpec.describe Groups::DependencyProxyAuthController do ...@@ -51,7 +66,7 @@ RSpec.describe Groups::DependencyProxyAuthController do
request.headers['HTTP_AUTHORIZATION'] = token_header request.headers['HTTP_AUTHORIZATION'] = token_header
end end
it { is_expected.to have_gitlab_http_status(:not_found) } it { is_expected.to have_gitlab_http_status(:unauthorized) }
end end
context 'token with no user id' do context 'token with no user id' do
...@@ -61,7 +76,7 @@ RSpec.describe Groups::DependencyProxyAuthController do ...@@ -61,7 +76,7 @@ RSpec.describe Groups::DependencyProxyAuthController do
request.headers['HTTP_AUTHORIZATION'] = token_header request.headers['HTTP_AUTHORIZATION'] = token_header
end end
it { is_expected.to have_gitlab_http_status(:not_found) } it { is_expected.to have_gitlab_http_status(:unauthorized) }
end end
context 'expired token' do context 'expired token' do
...@@ -76,6 +91,32 @@ RSpec.describe Groups::DependencyProxyAuthController do ...@@ -76,6 +91,32 @@ RSpec.describe Groups::DependencyProxyAuthController do
it { is_expected.to have_gitlab_http_status(:unauthorized) } it { is_expected.to have_gitlab_http_status(:unauthorized) }
end end
context 'expired deploy token' do
let_it_be(:user) { create(:deploy_token, :expired) }
let(:jwt) { build_jwt(user) }
let(:token_header) { "Bearer #{jwt.encoded}" }
before do
request.headers['HTTP_AUTHORIZATION'] = token_header
end
it { is_expected.to have_gitlab_http_status(:unauthorized) }
end
context 'revoked deploy token' do
let_it_be(:user) { create(:deploy_token, :revoked) }
let(:jwt) { build_jwt(user) }
let(:token_header) { "Bearer #{jwt.encoded}" }
before do
request.headers['HTTP_AUTHORIZATION'] = token_header
end
it { is_expected.to have_gitlab_http_status(:unauthorized) }
end
end end
end end
end end
...@@ -35,9 +35,13 @@ FactoryBot.define do ...@@ -35,9 +35,13 @@ FactoryBot.define do
end end
trait :all_scopes do trait :all_scopes do
write_registry { true} write_registry { true }
read_package_registry { true } read_package_registry { true }
write_package_registry { true } write_package_registry { true }
end end
trait :dependency_proxy_scopes do
write_registry { true }
end
end end
end end
...@@ -22,6 +22,32 @@ RSpec.describe DeployToken do ...@@ -22,6 +22,32 @@ RSpec.describe DeployToken do
it { is_expected.to validate_presence_of(:deploy_token_type) } it { is_expected.to validate_presence_of(:deploy_token_type) }
end end
shared_examples 'invalid group deploy token' do
context 'revoked' do
before do
deploy_token.update_column(:revoked, true)
end
it { is_expected.to eq(false) }
end
context 'expired' do
before do
deploy_token.update!(expires_at: Date.today - 1.month)
end
it { is_expected.to eq(false) }
end
context 'project type' do
before do
deploy_token.update_column(:deploy_token_type, 2)
end
it { is_expected.to eq(false) }
end
end
describe 'deploy_token_type validations' do describe 'deploy_token_type validations' do
context 'when a deploy token is associated to a group' do context 'when a deploy token is associated to a group' do
it 'does not allow setting a project to it' do it 'does not allow setting a project to it' do
...@@ -70,6 +96,50 @@ RSpec.describe DeployToken do ...@@ -70,6 +96,50 @@ RSpec.describe DeployToken do
end end
end end
describe '#valid_for_dependency_proxy?' do
let_it_be_with_reload(:deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
subject { deploy_token.valid_for_dependency_proxy? }
it { is_expected.to eq(true) }
it_behaves_like 'invalid group deploy token'
context 'insufficient scopes' do
before do
deploy_token.update_column(:write_registry, false)
end
it { is_expected.to eq(false) }
end
end
describe '#has_access_to_group?' do
let_it_be(:group) { create(:group) }
let_it_be_with_reload(:deploy_token) { create(:deploy_token, :group) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, group: group, deploy_token: deploy_token) }
let(:test_group) { group }
subject { deploy_token.has_access_to_group?(test_group) }
it { is_expected.to eq(true) }
it_behaves_like 'invalid group deploy token'
context 'for a sub group' do
let(:test_group) { create(:group, parent: group) }
it { is_expected.to eq(true) }
end
context 'for a different group' do
let(:test_group) { create(:group) }
it { is_expected.to eq(false) }
end
end
describe '#scopes' do describe '#scopes' do
context 'with all the scopes' do context 'with all the scopes' do
let_it_be(:deploy_token) { create(:deploy_token, :all_scopes) } let_it_be(:deploy_token) { create(:deploy_token, :all_scopes) }
......
...@@ -3,15 +3,40 @@ ...@@ -3,15 +3,40 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe GroupDeployToken, type: :model do RSpec.describe GroupDeployToken, type: :model do
let(:group) { create(:group) } let_it_be(:group) { create(:group) }
let(:deploy_token) { create(:deploy_token) } let_it_be(:deploy_token) { create(:deploy_token) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, group: group, deploy_token: deploy_token) }
subject(:group_deploy_token) { create(:group_deploy_token, group: group, deploy_token: deploy_token) } describe 'relationships' do
it { is_expected.to belong_to :group }
it { is_expected.to belong_to :deploy_token }
end
it { is_expected.to belong_to :group } describe 'validation' do
it { is_expected.to belong_to :deploy_token } it { is_expected.to validate_presence_of :deploy_token }
it { is_expected.to validate_presence_of :group }
it { is_expected.to validate_uniqueness_of(:deploy_token_id).scoped_to(:group_id) }
end
it { is_expected.to validate_presence_of :deploy_token } describe '#has_access_to_group?' do
it { is_expected.to validate_presence_of :group } subject { group_deploy_token.has_access_to_group?(test_group) }
it { is_expected.to validate_uniqueness_of(:deploy_token_id).scoped_to(:group_id) }
context 'for itself' do
let(:test_group) { group }
it { is_expected.to eq(true) }
end
context 'for a subgroup' do
let(:test_group) { create(:group, parent: group) }
it { is_expected.to eq(true) }
end
context 'for other group' do
let(:test_group) { create(:group) }
it { is_expected.to eq(false) }
end
end
end end
...@@ -224,8 +224,10 @@ RSpec.describe JwtController do ...@@ -224,8 +224,10 @@ RSpec.describe JwtController do
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(:group) { create(:group) } let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :private, group: group) } let_it_be(:project) { create(:project, :private, group: group) }
let_it_be(:group_deploy_token) { create(:deploy_token, :group, groups: [group]) } let_it_be(:group_deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
let_it_be(:project_deploy_token) { create(:deploy_token, :project, projects: [project]) } let_it_be(:gdeploy_token) { create(:group_deploy_token, deploy_token: group_deploy_token, group: group) }
let_it_be(:project_deploy_token) { create(:deploy_token, :project, :dependency_proxy_scopes) }
let_it_be(:pdeploy_token) { create(:project_deploy_token, deploy_token: project_deploy_token, project: project) }
let_it_be(:service_name) { 'dependency_proxy' } let_it_be(:service_name) { 'dependency_proxy' }
let(:headers) { { authorization: credentials(credential_user, credential_password) } } let(:headers) { { authorization: credentials(credential_user, credential_password) } }
...@@ -264,7 +266,7 @@ RSpec.describe JwtController do ...@@ -264,7 +266,7 @@ RSpec.describe JwtController do
let(:credential_user) { group_deploy_token.username } let(:credential_user) { group_deploy_token.username }
let(:credential_password) { group_deploy_token.token } let(:credential_password) { group_deploy_token.token }
it_behaves_like 'returning response status', :forbidden it_behaves_like 'with valid credentials'
end end
context 'with project deploy token' do context 'with project deploy token' do
...@@ -274,6 +276,28 @@ RSpec.describe JwtController do ...@@ -274,6 +276,28 @@ RSpec.describe JwtController do
it_behaves_like 'returning response status', :forbidden it_behaves_like 'returning response status', :forbidden
end end
context 'with revoked group deploy token' do
let(:credential_user) { group_deploy_token.username }
let(:credential_password) { project_deploy_token.token }
before do
group_deploy_token.update_column(:revoked, true)
end
it_behaves_like 'returning response status', :unauthorized
end
context 'with group deploy token with insufficient scopes' do
let(:credential_user) { group_deploy_token.username }
let(:credential_password) { project_deploy_token.token }
before do
group_deploy_token.update_column(:write_registry, false)
end
it_behaves_like 'returning response status', :unauthorized
end
context 'with invalid credentials' do context 'with invalid credentials' do
let(:credential_user) { 'foo' } let(:credential_user) { 'foo' }
let(:credential_password) { 'bar' } let(:credential_password) { 'bar' }
......
...@@ -21,6 +21,12 @@ RSpec.describe Auth::DependencyProxyAuthenticationService do ...@@ -21,6 +21,12 @@ RSpec.describe Auth::DependencyProxyAuthenticationService do
end end
end end
shared_examples 'returning a token' do
it 'returns a token' do
expect(subject[:token]).not_to be_nil
end
end
context 'dependency proxy is not enabled' do context 'dependency proxy is not enabled' do
before do before do
stub_config(dependency_proxy: { enabled: false }) stub_config(dependency_proxy: { enabled: false })
...@@ -35,10 +41,14 @@ RSpec.describe Auth::DependencyProxyAuthenticationService do ...@@ -35,10 +41,14 @@ RSpec.describe Auth::DependencyProxyAuthenticationService do
it_behaves_like 'returning', status: 403, message: 'access forbidden' it_behaves_like 'returning', status: 403, message: 'access forbidden'
end end
context 'with a deploy token as user' do
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
it_behaves_like 'returning a token'
end
context 'with a user' do context 'with a user' do
it 'returns a token' do it_behaves_like 'returning a token'
expect(subject[:token]).not_to be_nil
end
end end
end end
end end
...@@ -14,6 +14,19 @@ RSpec.describe DependencyProxy::AuthTokenService do ...@@ -14,6 +14,19 @@ RSpec.describe DependencyProxy::AuthTokenService do
result = subject result = subject
expect(result['user_id']).to eq(user.id) expect(result['user_id']).to eq(user.id)
expect(result['deploy_token']).to be_nil
end
context 'with a deploy token' do
let_it_be(:deploy_token) { create(:deploy_token) }
let_it_be(:token) { build_jwt(deploy_token) }
it 'returns the deploy token' do
result = subject
expect(result['deploy_token']).to eq(deploy_token.token)
expect(result['user_id']).to be_nil
end
end end
it 'raises an error if the token is expired' do it 'raises an error if the token is expired' do
......
...@@ -34,7 +34,8 @@ module DependencyProxyHelpers ...@@ -34,7 +34,8 @@ module DependencyProxyHelpers
def build_jwt(user = nil, expire_time: nil) def build_jwt(user = nil, expire_time: nil)
JSONWebToken::HMACToken.new(::Auth::DependencyProxyAuthenticationService.secret).tap do |jwt| JSONWebToken::HMACToken.new(::Auth::DependencyProxyAuthenticationService.secret).tap do |jwt|
jwt['user_id'] = user.id if user jwt['user_id'] = user.id if user.is_a?(User)
jwt['deploy_token'] = user.token if user.is_a?(DeployToken)
jwt.expire_time = expire_time || jwt.issued_at + 1.minute jwt.expire_time = expire_time || jwt.issued_at + 1.minute
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