Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
b41e09c9ce
commit
eada495948
|
@ -2441,31 +2441,6 @@ Gitlab/FeatureAvailableUsage:
|
|||
- 'lib/api/helpers/related_resources_helpers.rb'
|
||||
- 'spec/models/concerns/featurable_spec.rb'
|
||||
|
||||
# WIP See https://gitlab.com/gitlab-org/gitlab/-/issues/327490
|
||||
Style/RegexpLiteralMixedPreserve:
|
||||
Exclude:
|
||||
- 'qa/qa/page/project/settings/advanced.rb'
|
||||
- 'qa/spec/service/docker_run/gitlab_runner_spec.rb'
|
||||
- 'rubocop/cop/gitlab/duplicate_spec_location.rb'
|
||||
- 'spec/features/clusters/cluster_health_dashboard_spec.rb'
|
||||
- 'spec/features/markdown/metrics_spec.rb'
|
||||
- 'spec/features/search/user_searches_for_code_spec.rb'
|
||||
- 'spec/features/snippets/embedded_snippet_spec.rb'
|
||||
- 'spec/helpers/diff_helper_spec.rb'
|
||||
- 'spec/helpers/releases_helper_spec.rb'
|
||||
- 'spec/lib/gitlab/ci/reports/test_case_spec.rb'
|
||||
- 'spec/lib/gitlab/consul/internal_spec.rb'
|
||||
- 'spec/lib/gitlab/import_export/shared_spec.rb'
|
||||
- 'spec/lib/gitlab/utils/usage_data_spec.rb'
|
||||
- 'spec/presenters/ci/build_runner_presenter_spec.rb'
|
||||
- 'spec/requests/api/projects_spec.rb'
|
||||
- 'spec/services/jira/requests/projects/list_service_spec.rb'
|
||||
- 'spec/support/capybara.rb'
|
||||
- 'spec/support/helpers/grafana_api_helpers.rb'
|
||||
- 'spec/support/helpers/query_recorder.rb'
|
||||
- 'spec/support/helpers/require_migration.rb'
|
||||
- 'spec/views/layouts/_head.html.haml_spec.rb'
|
||||
|
||||
# WIP see: https://gitlab.com/gitlab-org/gitlab/-/issues/335808
|
||||
Database/MultipleDatabases:
|
||||
Exclude:
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -223,7 +223,7 @@ gem 're2', '~> 1.2.0'
|
|||
gem 'version_sorter', '~> 2.2.4'
|
||||
|
||||
# Export Ruby Regex to Javascript
|
||||
gem 'js_regex', '~> 3.4'
|
||||
gem 'js_regex', '~> 3.7'
|
||||
|
||||
# User agent parsing
|
||||
gem 'device_detector'
|
||||
|
@ -523,7 +523,7 @@ gem 'valid_email', '~> 0.1'
|
|||
|
||||
# JSON
|
||||
gem 'json', '~> 2.3.0'
|
||||
gem 'json_schemer', '~> 0.2.12'
|
||||
gem 'json_schemer', '~> 0.2.18'
|
||||
gem 'oj', '~> 3.10.6'
|
||||
gem 'multi_json', '~> 1.14.1'
|
||||
gem 'yajl-ruby', '~> 1.4.1', require: 'yajl'
|
||||
|
|
34
Gemfile.lock
34
Gemfile.lock
|
@ -179,7 +179,8 @@ GEM
|
|||
mime-types (>= 1.16)
|
||||
ssrf_filter (~> 1.0)
|
||||
cbor (0.5.9.6)
|
||||
character_set (1.4.0)
|
||||
character_set (1.4.1)
|
||||
sorted_set (~> 1.0)
|
||||
charlock_holmes (0.7.7)
|
||||
chef-config (16.10.17)
|
||||
addressable
|
||||
|
@ -307,8 +308,8 @@ GEM
|
|||
dry-inflector (~> 0.1, >= 0.1.2)
|
||||
dry-logic (~> 1.0, >= 1.0.2)
|
||||
e2mmap (0.1.0)
|
||||
ecma-re-validator (0.2.1)
|
||||
regexp_parser (~> 1.2)
|
||||
ecma-re-validator (0.3.0)
|
||||
regexp_parser (~> 2.0)
|
||||
ed25519 (1.2.4)
|
||||
elasticsearch (6.8.2)
|
||||
elasticsearch-api (= 6.8.2)
|
||||
|
@ -604,7 +605,7 @@ GEM
|
|||
temple (>= 0.8.2)
|
||||
thor
|
||||
tilt
|
||||
hana (1.3.6)
|
||||
hana (1.3.7)
|
||||
hangouts-chat (0.0.5)
|
||||
hashdiff (1.0.1)
|
||||
hashie (4.1.0)
|
||||
|
@ -652,19 +653,19 @@ GEM
|
|||
multipart-post
|
||||
oauth (~> 0.5, >= 0.5.0)
|
||||
jmespath (1.4.0)
|
||||
js_regex (3.4.0)
|
||||
js_regex (3.7.0)
|
||||
character_set (~> 1.4)
|
||||
regexp_parser (~> 1.5)
|
||||
regexp_property_values (~> 0.3)
|
||||
regexp_parser (~> 2.1)
|
||||
regexp_property_values (~> 1.0)
|
||||
json (2.3.0)
|
||||
json-jwt (1.13.0)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
json_schemer (0.2.12)
|
||||
ecma-re-validator (~> 0.2)
|
||||
json_schemer (0.2.18)
|
||||
ecma-re-validator (~> 0.3)
|
||||
hana (~> 1.3)
|
||||
regexp_parser (~> 1.5)
|
||||
regexp_parser (~> 2.0)
|
||||
uri_template (~> 0.7)
|
||||
jsonpath (1.1.0)
|
||||
multi_json
|
||||
|
@ -1010,6 +1011,7 @@ GEM
|
|||
ffi (>= 1.0.6)
|
||||
msgpack (>= 0.4.3)
|
||||
optimist (>= 3.0.0)
|
||||
rbtree (0.4.4)
|
||||
rchardet (1.8.0)
|
||||
rdoc (6.3.2)
|
||||
re2 (1.2.0)
|
||||
|
@ -1035,8 +1037,8 @@ GEM
|
|||
redis-store (>= 1.2, < 2)
|
||||
redis-store (1.8.1)
|
||||
redis (>= 4, < 5)
|
||||
regexp_parser (1.8.2)
|
||||
regexp_property_values (0.3.5)
|
||||
regexp_parser (2.1.1)
|
||||
regexp_property_values (1.0.0)
|
||||
representable (3.0.4)
|
||||
declarative (< 0.1.0)
|
||||
declarative-option (< 0.2.0)
|
||||
|
@ -1174,6 +1176,7 @@ GEM
|
|||
rubyzip (>= 1.2.2)
|
||||
sentry-raven (3.1.2)
|
||||
faraday (>= 1.0)
|
||||
set (1.0.1)
|
||||
settingslogic (2.0.9)
|
||||
sexp_processor (4.15.1)
|
||||
shellany (0.0.1)
|
||||
|
@ -1218,6 +1221,9 @@ GEM
|
|||
thor (~> 1.0)
|
||||
tilt (~> 2.0)
|
||||
yard (~> 0.9, >= 0.9.24)
|
||||
sorted_set (1.0.3)
|
||||
rbtree
|
||||
set (~> 1.0)
|
||||
spamcheck (0.1.0)
|
||||
grpc (~> 1.0)
|
||||
spring (2.1.1)
|
||||
|
@ -1509,9 +1515,9 @@ DEPENDENCIES
|
|||
invisible_captcha (~> 1.1.0)
|
||||
ipaddress (~> 0.8.3)
|
||||
jira-ruby (~> 2.1.4)
|
||||
js_regex (~> 3.4)
|
||||
js_regex (~> 3.7)
|
||||
json (~> 2.3.0)
|
||||
json_schemer (~> 0.2.12)
|
||||
json_schemer (~> 0.2.18)
|
||||
jwt (~> 2.1.0)
|
||||
kaminari (~> 1.0)
|
||||
kas-grpc (~> 0.0.2)
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import initFilePickers from '~/file_pickers';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initFilePickers);
|
||||
initFilePickers();
|
||||
|
|
|
@ -1,19 +1,17 @@
|
|||
import initSettingsPanels from '~/settings_panels';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Initialize expandable settings panels
|
||||
initSettingsPanels();
|
||||
// Initialize expandable settings panels
|
||||
initSettingsPanels();
|
||||
|
||||
const domainCard = document.querySelector('.js-domain-cert-show');
|
||||
const domainForm = document.querySelector('.js-domain-cert-inputs');
|
||||
const domainReplaceButton = document.querySelector('.js-domain-cert-replace-btn');
|
||||
const domainSubmitButton = document.querySelector('.js-serverless-domain-submit');
|
||||
const domainCard = document.querySelector('.js-domain-cert-show');
|
||||
const domainForm = document.querySelector('.js-domain-cert-inputs');
|
||||
const domainReplaceButton = document.querySelector('.js-domain-cert-replace-btn');
|
||||
const domainSubmitButton = document.querySelector('.js-serverless-domain-submit');
|
||||
|
||||
if (domainReplaceButton && domainCard && domainForm) {
|
||||
domainReplaceButton.addEventListener('click', () => {
|
||||
domainCard.classList.add('hidden');
|
||||
domainForm.classList.remove('hidden');
|
||||
domainSubmitButton.removeAttribute('disabled');
|
||||
});
|
||||
}
|
||||
});
|
||||
if (domainReplaceButton && domainCard && domainForm) {
|
||||
domainReplaceButton.addEventListener('click', () => {
|
||||
domainCard.classList.add('hidden');
|
||||
domainForm.classList.remove('hidden');
|
||||
domainSubmitButton.removeAttribute('disabled');
|
||||
});
|
||||
}
|
||||
|
|
|
@ -5,6 +5,4 @@ import Translate from '~/vue_shared/translate';
|
|||
|
||||
Vue.use(Translate);
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
mountBadgeSettings(GROUP_BADGE);
|
||||
});
|
||||
mountBadgeSettings(GROUP_BADGE);
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
import mountImportProjectsTable from '~/import_entities/import_projects';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const mountElement = document.getElementById('import-projects-mount-element');
|
||||
const mountElement = document.getElementById('import-projects-mount-element');
|
||||
|
||||
mountImportProjectsTable(mountElement);
|
||||
});
|
||||
mountImportProjectsTable(mountElement);
|
||||
|
|
|
@ -5,9 +5,7 @@ import initCompareSelector from '~/projects/compare';
|
|||
|
||||
initCompareSelector();
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
new Diff(); // eslint-disable-line no-new
|
||||
const paddingTop = 16;
|
||||
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
|
||||
GpgBadges.fetch();
|
||||
});
|
||||
new Diff(); // eslint-disable-line no-new
|
||||
const paddingTop = 16;
|
||||
initChangesDropdown(document.querySelector('.navbar-gitlab').offsetHeight - paddingTop);
|
||||
GpgBadges.fetch();
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import initCycleAnalytics from '~/cycle_analytics';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initCycleAnalytics);
|
||||
initCycleAnalytics();
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import initContributorsGraphs from '~/contributors';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initContributorsGraphs);
|
||||
initContributorsGraphs();
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import mountJiraImportApp from '~/jira_import';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', mountJiraImportApp);
|
||||
mountJiraImportApp();
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import initForm from 'ee_else_ce/pages/projects/issues/form';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initForm);
|
||||
initForm();
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import initTerminal from '~/terminal/';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initTerminal);
|
||||
initTerminal();
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import initForm from '~/pages/projects/pages_domains/form';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initForm);
|
||||
initForm();
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
import initForm from '~/pages/projects/pages_domains/form';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initForm);
|
||||
initForm();
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import Vue from 'vue';
|
||||
import PipelineSchedulesCallout from '../shared/components/pipeline_schedules_callout.vue';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
function initPipelineSchedules() {
|
||||
const el = document.getElementById('pipeline-schedules-callout');
|
||||
|
||||
if (!el) {
|
||||
|
@ -21,4 +21,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
|||
return createElement(PipelineSchedulesCallout);
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initPipelineSchedules();
|
||||
|
|
|
@ -1,43 +0,0 @@
|
|||
# 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
|
|||
private
|
||||
|
||||
def verify_dependency_proxy_enabled!
|
||||
render_404 unless group.dependency_proxy_feature_available?
|
||||
render_404 unless group&.dependency_proxy_feature_available?
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
module Groups
|
||||
class DependencyProxiesController < Groups::ApplicationController
|
||||
include DependencyProxy::GroupAccess
|
||||
include ::DependencyProxy::GroupAccess
|
||||
|
||||
before_action :authorize_admin_dependency_proxy!, only: :update
|
||||
before_action :dependency_proxy
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
# 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
|
|
@ -1,8 +1,6 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Groups::DependencyProxyAuthController < ApplicationController
|
||||
include DependencyProxy::Auth
|
||||
|
||||
class Groups::DependencyProxyAuthController < ::Groups::DependencyProxy::ApplicationController
|
||||
feature_category :dependency_proxy
|
||||
|
||||
def authenticate
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Groups::DependencyProxyForContainersController < Groups::ApplicationController
|
||||
include DependencyProxy::Auth
|
||||
class Groups::DependencyProxyForContainersController < ::Groups::DependencyProxy::ApplicationController
|
||||
include Gitlab::Utils::StrongMemoize
|
||||
include DependencyProxy::GroupAccess
|
||||
include SendFileUpload
|
||||
include ::PackagesHelper # for event tracking
|
||||
|
||||
before_action :ensure_group
|
||||
before_action :ensure_token_granted!
|
||||
before_action :ensure_feature_enabled!
|
||||
|
||||
|
@ -24,7 +25,7 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
|
|||
content_type = result[:manifest].content_type
|
||||
|
||||
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(
|
||||
result[:manifest].file,
|
||||
proxy: true,
|
||||
|
@ -42,7 +43,7 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
|
|||
|
||||
if result[:status] == :success
|
||||
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)
|
||||
else
|
||||
head result[:http_status]
|
||||
|
@ -51,6 +52,12 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
|
|||
|
||||
private
|
||||
|
||||
def group
|
||||
strong_memoize(:group) do
|
||||
Group.find_by_full_path(params[:group_id], follow_redirects: request.get?)
|
||||
end
|
||||
end
|
||||
|
||||
def image
|
||||
params[:image]
|
||||
end
|
||||
|
@ -71,6 +78,10 @@ class Groups::DependencyProxyForContainersController < Groups::ApplicationContro
|
|||
group.dependency_proxy_setting || group.create_dependency_proxy_setting
|
||||
end
|
||||
|
||||
def ensure_group
|
||||
render_404 unless group
|
||||
end
|
||||
|
||||
def ensure_feature_enabled!
|
||||
render_404 unless dependency_proxy.enabled
|
||||
end
|
||||
|
|
|
@ -128,7 +128,6 @@ module Ci
|
|||
end
|
||||
|
||||
scope :eager_load_job_artifacts, -> { includes(:job_artifacts) }
|
||||
scope :eager_load_job_artifacts_archive, -> { includes(:job_artifacts_archive) }
|
||||
scope :eager_load_tags, -> { includes(:tags) }
|
||||
|
||||
scope :eager_load_everything, -> do
|
||||
|
|
|
@ -10,6 +10,7 @@ class DeployToken < ApplicationRecord
|
|||
AVAILABLE_SCOPES = %i(read_repository read_registry write_registry
|
||||
read_package_registry write_package_registry).freeze
|
||||
GITLAB_DEPLOY_TOKEN_NAME = 'gitlab-deploy-token'
|
||||
REQUIRED_DEPENDENCY_PROXY_SCOPES = %i[read_registry write_registry].freeze
|
||||
|
||||
default_value_for(:expires_at) { Forever.date }
|
||||
|
||||
|
@ -46,6 +47,12 @@ class DeployToken < ApplicationRecord
|
|||
active.find_by(name: GITLAB_DEPLOY_TOKEN_NAME)
|
||||
end
|
||||
|
||||
def valid_for_dependency_proxy?
|
||||
group_type? &&
|
||||
active? &&
|
||||
REQUIRED_DEPENDENCY_PROXY_SCOPES.all? { |scope| scope.in?(scopes) }
|
||||
end
|
||||
|
||||
def revoke!
|
||||
update!(revoked: true)
|
||||
end
|
||||
|
@ -73,6 +80,14 @@ class DeployToken < ApplicationRecord
|
|||
holder.has_access_to?(requested_project)
|
||||
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
|
||||
# to a single project or group, later we're going to
|
||||
# extend that to be for multiple projects and namespaces.
|
||||
|
|
|
@ -11,9 +11,14 @@ class GroupDeployToken < ApplicationRecord
|
|||
def has_access_to?(requested_project)
|
||||
requested_project_group = 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
|
||||
.where(id: group_id)
|
||||
.exists?
|
||||
|
|
|
@ -9,10 +9,6 @@ class Label < ApplicationRecord
|
|||
include Sortable
|
||||
include FromUnion
|
||||
include Presentable
|
||||
include IgnorableColumns
|
||||
|
||||
# TODO: Project#create_labels can remove column exception when this column is dropped from all envs
|
||||
ignore_column :remove_on_close, remove_with: '14.1', remove_after: '2021-06-22'
|
||||
|
||||
cache_markdown_field :description, pipeline: :single_line
|
||||
|
||||
|
|
|
@ -1427,8 +1427,7 @@ class Project < ApplicationRecord
|
|||
# rubocop: disable CodeReuse/ServiceClass
|
||||
def create_labels
|
||||
Label.templates.each do |label|
|
||||
# TODO: remove_on_close exception can be removed after the column is dropped from all envs
|
||||
params = label.attributes.except('id', 'template', 'created_at', 'updated_at', 'type', 'remove_on_close')
|
||||
params = label.attributes.except('id', 'template', 'created_at', 'updated_at', 'type')
|
||||
Labels::FindOrCreateService.new(nil, self, params).execute(skip_authorization: true)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -20,7 +20,6 @@ module Terraform
|
|||
foreign_key: :terraform_state_id,
|
||||
inverse_of: :terraform_state
|
||||
|
||||
scope :versioning_not_enabled, -> { where(versioning_enabled: false) }
|
||||
scope :ordered_by_name, -> { order(:name) }
|
||||
scope :with_name, -> (name) { where(name: name) }
|
||||
|
||||
|
|
|
@ -50,6 +50,14 @@ class GroupPolicy < BasePolicy
|
|||
@subject.dependency_proxy_feature_available?
|
||||
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"
|
||||
condition(:read_package_registry_deploy_token) do
|
||||
@user.is_a?(DeployToken) && @user.groups.include?(@subject) && @user.read_package_registry
|
||||
|
@ -212,7 +220,7 @@ class GroupPolicy < BasePolicy
|
|||
enable :read_group
|
||||
end
|
||||
|
||||
rule { can?(:read_group) & dependency_proxy_available }
|
||||
rule { dependency_proxy_access_allowed & dependency_proxy_available }
|
||||
.enable :read_dependency_proxy
|
||||
|
||||
rule { developer & dependency_proxy_available }
|
||||
|
@ -260,6 +268,10 @@ class GroupPolicy < BasePolicy
|
|||
def resource_access_token_creation_allowed?
|
||||
resource_access_token_feature_available? && group.root_ancestor.namespace_settings.resource_access_token_creation_allowed?
|
||||
end
|
||||
|
||||
def valid_dependency_proxy_deploy_token
|
||||
@user.is_a?(DeployToken) && @user&.valid_for_dependency_proxy? && @user&.has_access_to_group?(@subject)
|
||||
end
|
||||
end
|
||||
|
||||
GroupPolicy.prepend_mod_with('GroupPolicy')
|
||||
|
|
|
@ -8,10 +8,7 @@ module Auth
|
|||
|
||||
def execute(authentication_abilities:)
|
||||
return error('dependency proxy not enabled', 404) unless ::Gitlab.config.dependency_proxy.enabled
|
||||
|
||||
# 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
|
||||
return error('access forbidden', 403) unless valid_user_actor?
|
||||
|
||||
{ token: authorized_token.encoded }
|
||||
end
|
||||
|
@ -36,11 +33,24 @@ module Auth
|
|||
|
||||
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
|
||||
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
|
||||
end
|
||||
end
|
||||
|
||||
def deploy_token
|
||||
params[:deploy_token]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
= f.label s_('ProjectCreationLevel|Default project creation protection'), class: 'label-bold'
|
||||
= f.select :default_project_creation, options_for_select(Gitlab::Access.project_creation_options, @application_setting.default_project_creation), {}, class: 'form-control'
|
||||
= render_if_exists 'admin/application_settings/default_project_deletion_protection_setting', form: f
|
||||
= render_if_exists 'admin/application_settings/default_delayed_project_deletion_setting', form: f
|
||||
= render_if_exists 'admin/application_settings/default_project_deletion_adjourned_period_setting', form: f
|
||||
.form-group.visibility-level-setting
|
||||
= f.label :default_project_visibility, class: 'label-bold'
|
||||
|
|
|
@ -1,16 +1,3 @@
|
|||
- if group_sidebar_link?(:kubernetes)
|
||||
= nav_link(controller: [:clusters]) do
|
||||
= link_to group_clusters_path(@group) do
|
||||
.nav-icon-container
|
||||
= sprite_icon('cloud-gear')
|
||||
%span.nav-item-name
|
||||
= _('Kubernetes')
|
||||
%ul.sidebar-sub-level-items.is-fly-out-only
|
||||
= nav_link(controller: [:clusters], html_options: { class: "fly-out-top-item" } ) do
|
||||
= link_to group_clusters_path(@group), title: _('Kubernetes'), class: 'shortcuts-kubernetes' do
|
||||
%strong.fly-out-top-item-name
|
||||
= _('Kubernetes')
|
||||
|
||||
= render 'groups/sidebar/packages'
|
||||
|
||||
= render 'layouts/nav/sidebar/analytics_links', links: group_analytics_navbar_links(@group, current_user)
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331082
|
|||
milestone: '14.0'
|
||||
type: development
|
||||
group: group::dynamic analysis
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
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
|
|
@ -96,7 +96,7 @@ Example response:
|
|||
```
|
||||
|
||||
Users on GitLab [Premium or Ultimate](https://about.gitlab.com/pricing/) may also see
|
||||
the `file_template_project_id`, `deletion_adjourned_period`, or the `geo_node_allowed_ips` parameters:
|
||||
the `file_template_project_id`, `delayed_project_deletion`, `deletion_adjourned_period`, or the `geo_node_allowed_ips` parameters:
|
||||
|
||||
```json
|
||||
{
|
||||
|
@ -104,6 +104,7 @@ the `file_template_project_id`, `deletion_adjourned_period`, or the `geo_node_al
|
|||
"signup_enabled" : true,
|
||||
"file_template_project_id": 1,
|
||||
"geo_node_allowed_ips": "0.0.0.0/0, ::/0",
|
||||
"delayed_project_deletion": false,
|
||||
"deletion_adjourned_period": 7,
|
||||
...
|
||||
}
|
||||
|
@ -200,6 +201,7 @@ these parameters:
|
|||
- `file_template_project_id`
|
||||
- `geo_node_allowed_ips`
|
||||
- `geo_status_timeout`
|
||||
- `delayed_project_delection`
|
||||
- `deletion_adjourned_period`
|
||||
|
||||
Example responses: **(PREMIUM SELF)**
|
||||
|
@ -250,6 +252,7 @@ listed in the descriptions of the relevant settings.
|
|||
| `default_project_visibility` | string | no | What visibility level new projects receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
||||
| `default_projects_limit` | integer | no | Project limit per user. Default is `100000`. |
|
||||
| `default_snippet_visibility` | string | no | What visibility level new snippets receive. Can take `private`, `internal` and `public` as a parameter. Default is `private`. |
|
||||
| `delayed_project_deletion` | boolean | no | **(PREMIUM SELF)** Enable delayed project deletion by default in new groups. Default is `false`. |
|
||||
| `deletion_adjourned_period` | integer | no | **(PREMIUM SELF)** The number of days to wait before deleting a project or group that is marked for deletion. Value must be between 0 and 90.
|
||||
| `diff_max_patch_bytes` | integer | no | Maximum [diff patch size](../user/admin_area/diff_limits.md), in bytes. |
|
||||
| `diff_max_files` | integer | no | Maximum [files in a diff](../user/admin_area/diff_limits.md). |
|
||||
|
|
|
@ -71,6 +71,18 @@ To ensure only Administrator users can delete projects:
|
|||
1. Check the **Default project deletion protection** checkbox.
|
||||
1. Click **Save changes**.
|
||||
|
||||
## Default delayed project deletion **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/255449) in GitLab 14.2.
|
||||
|
||||
Projects in a group (but not a personal namespace) can be deleted after a delayed period, by
|
||||
[configuring in Group Settings](../../group/index.md#enable-delayed-project-removal).
|
||||
|
||||
To enable delayed project deletion by default in new groups:
|
||||
|
||||
1. Check the **Default delayed project deletion** checkbox.
|
||||
1. Click **Save changes**.
|
||||
|
||||
## Default deletion delay **(PREMIUM SELF)**
|
||||
|
||||
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32935) in GitLab 12.6.
|
||||
|
|
|
@ -1063,6 +1063,9 @@ follows:
|
|||
|
||||
Both methods are equivalent in functionality. Use whichever is feasible.
|
||||
|
||||
In [GitLab 14.2 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/324990), site profile
|
||||
validation happens in a CI job using the [GitLab Runner](../../../ci/runners/index.md).
|
||||
|
||||
#### Create a site profile
|
||||
|
||||
To create a site profile:
|
||||
|
|
|
@ -89,6 +89,7 @@ You can authenticate using:
|
|||
|
||||
- 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 [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
|
||||
|
||||
|
@ -123,7 +124,7 @@ Proxy manually without including the port:
|
|||
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
|
||||
|
||||
|
|
|
@ -94,3 +94,48 @@ the following table.
|
|||
You may enable or disable project access token creation for all projects in a group in **Group > Settings > General > Permissions, LFS, 2FA > Allow project access token creation**.
|
||||
Even when creation is disabled, you can still use and revoke existing project access tokens.
|
||||
This setting is available only on top-level groups.
|
||||
|
||||
## Group access token workaround **(FREE SELF)**
|
||||
|
||||
NOTE:
|
||||
This section describes a workaround and is subject to change.
|
||||
|
||||
Group access tokens let you use a single token to:
|
||||
|
||||
- Perform actions at the group level.
|
||||
- Manage the projects within the group.
|
||||
|
||||
We don't support group access tokens in the GitLab UI, though GitLab self-managed
|
||||
administrators can create them using the [Rails console](../../../administration/operations/rails_console.md).
|
||||
|
||||
<div class="video-fallback">
|
||||
For a demo of the group access token workaround, see <a href="https://www.youtube.com/watch?v=W2fg1P1xmU0">Demo: Group Level Access Tokens</a>.
|
||||
</div>
|
||||
<figure class="video-container">
|
||||
<iframe src="https://www.youtube.com/embed/W2fg1P1xmU0" frameborder="0" allowfullscreen="true"> </iframe>
|
||||
</figure>
|
||||
|
||||
### Create a group access token
|
||||
|
||||
To create a group access token, run the following in a Rails console:
|
||||
|
||||
```ruby
|
||||
admin = User.find(1) # group admin
|
||||
group = Group.find(109) # the group you want to create a token for
|
||||
bot = Users::CreateService.new(admin, { name: 'group_token', username: "group_#{group.id}_bot", email: "group_#{group.id}_bot@example.com", user_type: :project_bot }).execute # create the group bot user
|
||||
# for further group access tokens, the username should be group_#{group.id}_bot#{bot_count}, e.g. group_109_bot2, and their email should be group_109_bot2@example.com
|
||||
bot.confirm # confirm the bot
|
||||
group.add_user(bot, :maintainer) # add the bot to the group at the desired access level
|
||||
token = bot.personal_access_tokens.create(scopes:[:api, :write_repository], name: 'group_token') # give it a PAT
|
||||
gtoken = token.token # get the token value
|
||||
```
|
||||
|
||||
### Revoke a group access token
|
||||
|
||||
To revoke a group access token, run the following in a Rails console:
|
||||
|
||||
```ruby
|
||||
bot = User.find_by(username: 'group_109_bot') # the owner of the token you want to revoke
|
||||
token = bot.personal_access_tokens.last # the token you want to revoke
|
||||
token.revoke!
|
||||
```
|
||||
|
|
|
@ -214,6 +214,8 @@ module API
|
|||
|
||||
update_project_feature_usage_for(user_project)
|
||||
|
||||
next [] unless user_project.repo_exists?
|
||||
|
||||
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
|
||||
|
||||
present paginate(branches), with: ::API::Github::Entities::Branch, project: user_project
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Sidebars
|
||||
module Groups
|
||||
module Menus
|
||||
class KubernetesMenu < ::Sidebars::Menu
|
||||
override :link
|
||||
def link
|
||||
group_clusters_path(context.group)
|
||||
end
|
||||
|
||||
override :title
|
||||
def title
|
||||
_('Kubernetes')
|
||||
end
|
||||
|
||||
override :sprite_icon
|
||||
def sprite_icon
|
||||
'cloud-gear'
|
||||
end
|
||||
|
||||
override :render?
|
||||
def render?
|
||||
can?(context.current_user, :read_cluster, context.group)
|
||||
end
|
||||
|
||||
override :extra_container_html_options
|
||||
def extra_container_html_options
|
||||
{
|
||||
class: 'shortcuts-kubernetes'
|
||||
}
|
||||
end
|
||||
|
||||
override :active_routes
|
||||
def active_routes
|
||||
{ controller: :clusters }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -11,6 +11,7 @@ module Sidebars
|
|||
add_menu(Sidebars::Groups::Menus::IssuesMenu.new(context))
|
||||
add_menu(Sidebars::Groups::Menus::MergeRequestsMenu.new(context))
|
||||
add_menu(Sidebars::Groups::Menus::CiCdMenu.new(context))
|
||||
add_menu(Sidebars::Groups::Menus::KubernetesMenu.new(context))
|
||||
end
|
||||
|
||||
override :render_raw_menus_partial
|
||||
|
|
|
@ -10555,6 +10555,9 @@ msgstr ""
|
|||
msgid "Default classification label"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default delayed project deletion"
|
||||
msgstr ""
|
||||
|
||||
msgid "Default deletion delay"
|
||||
msgstr ""
|
||||
|
||||
|
@ -11735,6 +11738,9 @@ msgstr ""
|
|||
msgid "Documents reindexed: %{processed_documents} (%{percentage}%%)"
|
||||
msgstr ""
|
||||
|
||||
msgid "Does not apply to projects in personal namespaces, which are deleted immediately on request."
|
||||
msgstr ""
|
||||
|
||||
msgid "Domain"
|
||||
msgstr ""
|
||||
|
||||
|
@ -12260,6 +12266,9 @@ msgstr ""
|
|||
msgid "Enable container expiration and retention policies for projects created earlier than GitLab 12.7."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable delayed project deletion by default for newly-created groups."
|
||||
msgstr ""
|
||||
|
||||
msgid "Enable error tracking"
|
||||
msgstr ""
|
||||
|
||||
|
@ -15944,7 +15953,7 @@ msgstr ""
|
|||
msgid "GroupSettings|Disable group mentions"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Enable delayed project removal"
|
||||
msgid "GroupSettings|Enable delayed project deletion"
|
||||
msgstr ""
|
||||
|
||||
msgid "GroupSettings|Export group"
|
||||
|
@ -22577,9 +22586,6 @@ msgstr ""
|
|||
msgid "Not all data has been processed yet, the accuracy of the chart for the selected timeframe is limited."
|
||||
msgstr ""
|
||||
|
||||
msgid "Not applicable to personal namespaced projects, which are deleted immediately on request."
|
||||
msgstr ""
|
||||
|
||||
msgid "Not available"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -30,16 +30,31 @@ RSpec.describe Groups::DependencyProxyAuthController do
|
|||
end
|
||||
|
||||
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(:token_header) { "Bearer #{jwt.encoded}" }
|
||||
let(:jwt) { build_jwt(user) }
|
||||
let(:token_header) { "Bearer #{jwt.encoded}" }
|
||||
|
||||
before do
|
||||
request.headers['HTTP_AUTHORIZATION'] = token_header
|
||||
before do
|
||||
request.headers['HTTP_AUTHORIZATION'] = token_header
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:success) }
|
||||
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
|
||||
|
||||
context 'with invalid JWT' do
|
||||
|
@ -51,7 +66,7 @@ RSpec.describe Groups::DependencyProxyAuthController do
|
|||
request.headers['HTTP_AUTHORIZATION'] = token_header
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'token with no user id' do
|
||||
|
@ -61,7 +76,7 @@ RSpec.describe Groups::DependencyProxyAuthController do
|
|||
request.headers['HTTP_AUTHORIZATION'] = token_header
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'expired token' do
|
||||
|
@ -76,6 +91,32 @@ RSpec.describe Groups::DependencyProxyAuthController do
|
|||
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
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
|
||||
|
|
|
@ -7,8 +7,8 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
include DependencyProxyHelpers
|
||||
|
||||
let_it_be(:user) { create(:user) }
|
||||
let_it_be_with_reload(:group) { create(:group, :private) }
|
||||
|
||||
let(:group) { create(:group) }
|
||||
let(:token_response) { { status: :success, token: 'abcd1234' } }
|
||||
let(:jwt) { build_jwt(user) }
|
||||
let(:token_header) { "Bearer #{jwt.encoded}" }
|
||||
|
@ -20,6 +20,8 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
end
|
||||
|
||||
context 'feature flag disabled' do
|
||||
let_it_be(:group) { create(:group) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_for_private_groups: false)
|
||||
end
|
||||
|
@ -35,13 +37,12 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
stub_feature_flags(dependency_proxy_for_private_groups: false)
|
||||
end
|
||||
|
||||
it 'redirects', :aggregate_failures do
|
||||
it 'returns not found' do
|
||||
group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
|
||||
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:redirect)
|
||||
expect(response.location).to end_with(new_user_session_path)
|
||||
expect(response).to have_gitlab_http_status(:not_found)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -53,21 +54,95 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
request.headers['HTTP_AUTHORIZATION'] = token_header
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'with valid user that does not have access' do
|
||||
let(:group) { create(:group, :private) }
|
||||
|
||||
before do
|
||||
user = double('bad_user', id: 999)
|
||||
token_header = "Bearer #{build_jwt(user).encoded}"
|
||||
request.headers['HTTP_AUTHORIZATION'] = token_header
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'deploy tokens with dependency_proxy_deploy_tokens disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
context 'with deploy token from a different group,' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'with revoked deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :revoked, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'with expired deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :expired, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'with deploy token with insufficient scopes' do
|
||||
let_it_be(:user) { create(:deploy_token, :group) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when a group is not found' do
|
||||
before do
|
||||
expect(Group).to receive(:find_by_full_path).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'deploy tokens with dependency_proxy_deploy_tokens enabled' do
|
||||
context 'with deploy token from a different group,' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'with revoked deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :revoked, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'with expired deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :expired, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:unauthorized) }
|
||||
end
|
||||
|
||||
context 'with deploy token with insufficient scopes' do
|
||||
let_it_be(:user) { create(:deploy_token, :group) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'when a group is not found' do
|
||||
before do
|
||||
expect(Group).to receive(:find_by_full_path).and_return(nil)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user is not found' do
|
||||
before do
|
||||
allow(User).to receive(:find).and_return(nil)
|
||||
|
@ -115,6 +190,25 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
|
||||
subject { get_manifest }
|
||||
|
||||
shared_examples 'a successful manifest pull' do
|
||||
it 'sends a file' do
|
||||
expect(controller).to receive(:send_file).with(manifest.file.path, type: manifest.content_type)
|
||||
|
||||
subject
|
||||
end
|
||||
|
||||
it 'returns Content-Disposition: attachment', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.headers['Docker-Content-Digest']).to eq(manifest.digest)
|
||||
expect(response.headers['Content-Length']).to eq(manifest.size)
|
||||
expect(response.headers['Docker-Distribution-Api-Version']).to eq(DependencyProxy::DISTRIBUTION_API_VERSION)
|
||||
expect(response.headers['Etag']).to eq("\"#{manifest.digest}\"")
|
||||
expect(response.headers['Content-Disposition']).to match(/^attachment/)
|
||||
end
|
||||
end
|
||||
|
||||
context 'feature enabled' do
|
||||
before do
|
||||
enable_dependency_proxy
|
||||
|
@ -123,14 +217,6 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
it_behaves_like 'without a token'
|
||||
it_behaves_like 'without permission'
|
||||
it_behaves_like 'feature flag disabled with private group'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest'
|
||||
|
||||
context 'with a cache entry' do
|
||||
let(:pull_response) { { status: :success, manifest: manifest, from_cache: true } }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest_from_cache'
|
||||
end
|
||||
|
||||
context 'remote token request fails' do
|
||||
let(:token_response) do
|
||||
|
@ -141,6 +227,10 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
}
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'proxies status from the remote token request', :aggregate_failures do
|
||||
subject
|
||||
|
||||
|
@ -158,6 +248,10 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
}
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'proxies status from the remote manifest request', :aggregate_failures do
|
||||
subject
|
||||
|
||||
|
@ -166,21 +260,58 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
end
|
||||
end
|
||||
|
||||
it 'sends a file' do
|
||||
expect(controller).to receive(:send_file).with(manifest.file.path, type: manifest.content_type)
|
||||
context 'a valid user' do
|
||||
before do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
subject
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest'
|
||||
|
||||
context 'with a cache entry' do
|
||||
let(:pull_response) { { status: :success, manifest: manifest, from_cache: true } }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_manifest_from_cache'
|
||||
end
|
||||
|
||||
context 'with dependency_proxy_deploy_tokens feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
end
|
||||
end
|
||||
|
||||
it 'returns Content-Disposition: attachment' do
|
||||
subject
|
||||
context 'a valid deploy token with dependency_proxy_deploy_tokens feature flag disabled' do
|
||||
let_it_be(:user) { create(:deploy_token, :dependency_proxy_scopes, :group) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(response.headers['Docker-Content-Digest']).to eq(manifest.digest)
|
||||
expect(response.headers['Content-Length']).to eq(manifest.size)
|
||||
expect(response.headers['Docker-Distribution-Api-Version']).to eq(DependencyProxy::DISTRIBUTION_API_VERSION)
|
||||
expect(response.headers['Etag']).to eq("\"#{manifest.digest}\"")
|
||||
expect(response.headers['Content-Disposition']).to match(/^attachment/)
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'a valid deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :dependency_proxy_scopes, :group) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
|
||||
context 'pulling from a subgroup' do
|
||||
let_it_be_with_reload(:parent_group) { create(:group) }
|
||||
let_it_be_with_reload(:group) { create(:group, parent: parent_group) }
|
||||
|
||||
before do
|
||||
parent_group.create_dependency_proxy_setting!(enabled: true)
|
||||
group_deploy_token.update_column(:group_id, parent_group.id)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful manifest pull'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -203,42 +334,7 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
end
|
||||
end
|
||||
|
||||
subject { get_blob }
|
||||
|
||||
context 'feature enabled' do
|
||||
before do
|
||||
enable_dependency_proxy
|
||||
end
|
||||
|
||||
it_behaves_like 'without a token'
|
||||
it_behaves_like 'without permission'
|
||||
it_behaves_like 'feature flag disabled with private group'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob'
|
||||
|
||||
context 'with a cache entry' do
|
||||
let(:blob_response) { { status: :success, blob: blob, from_cache: true } }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
|
||||
end
|
||||
|
||||
context 'remote blob request fails' do
|
||||
let(:blob_response) do
|
||||
{
|
||||
status: :error,
|
||||
http_status: 400,
|
||||
message: ''
|
||||
}
|
||||
end
|
||||
|
||||
it 'proxies status from the remote blob request', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(response.body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples 'a successful blob pull' do
|
||||
it 'sends a file' do
|
||||
expect(controller).to receive(:send_file).with(blob.file.path, {})
|
||||
|
||||
|
@ -253,6 +349,93 @@ RSpec.describe Groups::DependencyProxyForContainersController do
|
|||
end
|
||||
end
|
||||
|
||||
subject { get_blob }
|
||||
|
||||
context 'feature enabled' do
|
||||
before do
|
||||
enable_dependency_proxy
|
||||
end
|
||||
|
||||
it_behaves_like 'without a token'
|
||||
it_behaves_like 'without permission'
|
||||
it_behaves_like 'feature flag disabled with private group'
|
||||
|
||||
context 'remote blob request fails' do
|
||||
let(:blob_response) do
|
||||
{
|
||||
status: :error,
|
||||
http_status: 400,
|
||||
message: ''
|
||||
}
|
||||
end
|
||||
|
||||
before do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
it 'proxies status from the remote blob request', :aggregate_failures do
|
||||
subject
|
||||
|
||||
expect(response).to have_gitlab_http_status(:bad_request)
|
||||
expect(response.body).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'a valid user' do
|
||||
before do
|
||||
group.add_reporter(user)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful blob pull'
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob'
|
||||
|
||||
context 'with a cache entry' do
|
||||
let(:blob_response) { { status: :success, blob: blob, from_cache: true } }
|
||||
|
||||
it_behaves_like 'returning response status', :success
|
||||
it_behaves_like 'a package tracking event', described_class.name, 'pull_blob_from_cache'
|
||||
end
|
||||
|
||||
context 'with dependency_proxy_deploy_tokens feature flag disabled' do
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful blob pull'
|
||||
end
|
||||
end
|
||||
|
||||
context 'a valid deploy token with dependency_proxy_deploy_tokens feature flag disabled' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
before do
|
||||
stub_feature_flags(dependency_proxy_deploy_tokens: false)
|
||||
end
|
||||
|
||||
it { is_expected.to have_gitlab_http_status(:not_found) }
|
||||
end
|
||||
|
||||
context 'a valid deploy token' do
|
||||
let_it_be(:user) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: user, group: group) }
|
||||
|
||||
it_behaves_like 'a successful blob pull'
|
||||
|
||||
context 'pulling from a subgroup' do
|
||||
let_it_be_with_reload(:parent_group) { create(:group) }
|
||||
let_it_be_with_reload(:group) { create(:group, parent: parent_group) }
|
||||
|
||||
before do
|
||||
parent_group.create_dependency_proxy_setting!(enabled: true)
|
||||
group_deploy_token.update_column(:group_id, parent_group.id)
|
||||
end
|
||||
|
||||
it_behaves_like 'a successful blob pull'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it_behaves_like 'not found when disabled'
|
||||
|
||||
def get_blob
|
||||
|
|
|
@ -35,9 +35,13 @@ FactoryBot.define do
|
|||
end
|
||||
|
||||
trait :all_scopes do
|
||||
write_registry { true}
|
||||
write_registry { true }
|
||||
read_package_registry { true }
|
||||
write_package_registry { true }
|
||||
end
|
||||
|
||||
trait :dependency_proxy_scopes do
|
||||
write_registry { true }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require 'spec_helper'
|
||||
|
||||
RSpec.describe Sidebars::Groups::Menus::KubernetesMenu do
|
||||
let_it_be(:owner) { create(:user) }
|
||||
let_it_be(:group) do
|
||||
build(:group, :private).tap do |g|
|
||||
g.add_owner(owner)
|
||||
end
|
||||
end
|
||||
|
||||
let(:user) { owner }
|
||||
let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group) }
|
||||
let(:menu) { described_class.new(context) }
|
||||
|
||||
describe '#render?' do
|
||||
context 'when user can read clusters' do
|
||||
it 'returns true' do
|
||||
expect(menu.render?).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
context 'when user cannot read clusters rules' do
|
||||
let(:user) { nil }
|
||||
|
||||
it 'returns false' do
|
||||
expect(menu.render?).to eq false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,6 +22,32 @@ RSpec.describe DeployToken do
|
|||
it { is_expected.to validate_presence_of(:deploy_token_type) }
|
||||
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
|
||||
context 'when a deploy token is associated to a group' do
|
||||
it 'does not allow setting a project to it' do
|
||||
|
@ -70,6 +96,50 @@ RSpec.describe DeployToken do
|
|||
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
|
||||
context 'with all the scopes' do
|
||||
let_it_be(:deploy_token) { create(:deploy_token, :all_scopes) }
|
||||
|
|
|
@ -3,15 +3,40 @@
|
|||
require 'spec_helper'
|
||||
|
||||
RSpec.describe GroupDeployToken, type: :model do
|
||||
let(:group) { create(:group) }
|
||||
let(:deploy_token) { create(:deploy_token) }
|
||||
let_it_be(:group) { create(:group) }
|
||||
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 }
|
||||
it { is_expected.to belong_to :deploy_token }
|
||||
describe 'validation' do
|
||||
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 }
|
||||
it { is_expected.to validate_presence_of :group }
|
||||
it { is_expected.to validate_uniqueness_of(:deploy_token_id).scoped_to(:group_id) }
|
||||
describe '#has_access_to_group?' do
|
||||
subject { group_deploy_token.has_access_to_group?(test_group) }
|
||||
|
||||
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
|
||||
|
|
|
@ -472,6 +472,17 @@ RSpec.describe API::V3::Github do
|
|||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
end
|
||||
|
||||
context 'when the project has no repository', :aggregate_failures do
|
||||
let_it_be(:project) { create(:project, creator: user) }
|
||||
|
||||
it 'returns an empty collection response' do
|
||||
jira_get v3_api("/repos/#{project.namespace.path}/#{project.path}/branches", user)
|
||||
|
||||
expect(response).to have_gitlab_http_status(:ok)
|
||||
expect(json_response).to be_empty
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'unauthenticated' do
|
||||
|
|
|
@ -224,8 +224,10 @@ RSpec.describe JwtController do
|
|||
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
|
||||
let_it_be(:group) { create(: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(:project_deploy_token) { create(:deploy_token, :project, projects: [project]) }
|
||||
let_it_be(:group_deploy_token) { create(:deploy_token, :group, :dependency_proxy_scopes) }
|
||||
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(:headers) { { authorization: credentials(credential_user, credential_password) } }
|
||||
|
@ -264,7 +266,7 @@ RSpec.describe JwtController do
|
|||
let(:credential_user) { group_deploy_token.username }
|
||||
let(:credential_password) { group_deploy_token.token }
|
||||
|
||||
it_behaves_like 'returning response status', :forbidden
|
||||
it_behaves_like 'with valid credentials'
|
||||
end
|
||||
|
||||
context 'with project deploy token' do
|
||||
|
@ -274,6 +276,28 @@ RSpec.describe JwtController do
|
|||
it_behaves_like 'returning response status', :forbidden
|
||||
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
|
||||
let(:credential_user) { 'foo' }
|
||||
let(:credential_password) { 'bar' }
|
||||
|
|
|
@ -21,6 +21,12 @@ RSpec.describe Auth::DependencyProxyAuthenticationService do
|
|||
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
|
||||
before do
|
||||
stub_config(dependency_proxy: { enabled: false })
|
||||
|
@ -35,10 +41,14 @@ RSpec.describe Auth::DependencyProxyAuthenticationService do
|
|||
it_behaves_like 'returning', status: 403, message: 'access forbidden'
|
||||
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
|
||||
it 'returns a token' do
|
||||
expect(subject[:token]).not_to be_nil
|
||||
end
|
||||
it_behaves_like 'returning a token'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,6 +14,19 @@ RSpec.describe DependencyProxy::AuthTokenService do
|
|||
result = subject
|
||||
|
||||
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
|
||||
|
||||
it 'raises an error if the token is expired' do
|
||||
|
|
|
@ -34,7 +34,8 @@ module DependencyProxyHelpers
|
|||
|
||||
def build_jwt(user = nil, expire_time: nil)
|
||||
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
|
||||
end
|
||||
end
|
||||
|
|
|
@ -100,4 +100,12 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
|
|||
expect(rendered).to have_link('Runners', href: group_runners_path(group))
|
||||
end
|
||||
end
|
||||
|
||||
describe 'Kubernetes menu' do
|
||||
it 'has a link to the group cluster list path' do
|
||||
render
|
||||
|
||||
expect(rendered).to have_link('Kubernetes', href: group_clusters_path(group))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,4 +16,4 @@ OptionParser.new do |opts|
|
|||
end
|
||||
end.parse!
|
||||
|
||||
Tooling::ParallelRSpecRunner.run(options)
|
||||
Tooling::ParallelRSpecRunner.run(**options)
|
||||
|
|
Loading…
Reference in New Issue