New upstream version 14.8.6+ds1
This commit is contained in:
parent
4f07442896
commit
710edc26c8
75 changed files with 1112 additions and 364 deletions
19
CHANGELOG.md
19
CHANGELOG.md
|
@ -2,6 +2,25 @@
|
||||||
documentation](doc/development/changelog.md) for instructions on adding your own
|
documentation](doc/development/changelog.md) for instructions on adding your own
|
||||||
entry.
|
entry.
|
||||||
|
|
||||||
|
## 14.8.6 (2022-04-29)
|
||||||
|
|
||||||
|
### Security (14 changes)
|
||||||
|
|
||||||
|
- [Update Import/Export merge/push access levels & exclude ci config path](gitlab-org/security/gitlab@abfa8d4c128316b1ba095ff8eda7e86018e47caf) ([merge request](gitlab-org/security/gitlab!2372))
|
||||||
|
- [Prevent maintainers from editing PipelineSchedule](gitlab-org/security/gitlab@761a7777cb480d02b9c3418aa7317eba7c0eaff1) ([merge request](gitlab-org/security/gitlab!2423))
|
||||||
|
- [Add validation to pypi file sha256 values](gitlab-org/security/gitlab@712cc01aee2be4b6a9847746a080f190041367d5) ([merge request](gitlab-org/security/gitlab!2417))
|
||||||
|
- [Conan Token uses PAT rather than ID in payload](gitlab-org/security/gitlab@ba3070c90dd1b575982df22c256b0e3f97a9e919) ([merge request](gitlab-org/security/gitlab!2346))
|
||||||
|
- [[security] Fix markdown API disclosing issue titles of limited projects](gitlab-org/security/gitlab@fd3cb263e8f165a4a1a7894c08ddf254f9cf1e92) ([merge request](gitlab-org/security/gitlab!2405))
|
||||||
|
- [Verify that mentioned user can read TODO's note](gitlab-org/security/gitlab@e54be58cc79011d7c79dae94b993774ab36ef232) ([merge request](gitlab-org/security/gitlab!2398))
|
||||||
|
- [Invalidate markdown cache to clear up stored XSS](gitlab-org/security/gitlab@160cdda98c80e052abbb4bec226ad63fe9c9e403) ([merge request](gitlab-org/security/gitlab!2420))
|
||||||
|
- [Allow rate limiting of deploy tokens](gitlab-org/security/gitlab@78f7ee3d7e1258375ddcea3a20e3798092e89d41) ([merge request](gitlab-org/security/gitlab!2385))
|
||||||
|
- [Add suffix to cache name to add isolation](gitlab-org/security/gitlab@184d49640f5dcc4ac1522c874a7b5e0c16d2e05f) ([merge request](gitlab-org/security/gitlab!2373))
|
||||||
|
- [Disable wiki access with CI_JOB_TOKEN when improper access level](gitlab-org/security/gitlab@db93d134394675a4335c92557a55ac4381ed303f) ([merge request](gitlab-org/security/gitlab!2391))
|
||||||
|
- [Sanitize error input to prevent HTML/CSS injection in messages](gitlab-org/security/gitlab@333dd602091810639912702c80034468ff6f8aa0) ([merge request](gitlab-org/security/gitlab!2378))
|
||||||
|
- [Secure debug trace artifact download](gitlab-org/security/gitlab@266d812ba2e8e9936269323465c867983e3a2ebf) ([merge request](gitlab-org/security/gitlab!2367))
|
||||||
|
- [Use password type for all secret integration properties](gitlab-org/security/gitlab@eda2b8f02b34ead354ef07b9e41be006cf90f51b) ([merge request](gitlab-org/security/gitlab!2411))
|
||||||
|
- [Limit CI job group_name regexp](gitlab-org/security/gitlab@03ab6e9f312fb6fe50a6361f7bc78d527b223b96) ([merge request](gitlab-org/security/gitlab!2381))
|
||||||
|
|
||||||
## 14.8.5 (2022-03-31)
|
## 14.8.5 (2022-03-31)
|
||||||
|
|
||||||
### Security (21 changes)
|
### Security (21 changes)
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
14.8.5
|
14.8.6
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
||||||
14.8.5
|
14.8.6
|
|
@ -29,6 +29,25 @@ class Projects::ApplicationController < ApplicationController
|
||||||
@project = find_routable!(Project, path, request.fullpath, extra_authorization_proc: auth_proc)
|
@project = find_routable!(Project, path, request.fullpath, extra_authorization_proc: auth_proc)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def auth_proc
|
||||||
|
->(project) { !project.pending_delete? }
|
||||||
|
end
|
||||||
|
|
||||||
|
def authorize_read_build_trace!
|
||||||
|
return if can?(current_user, :read_build_trace, build)
|
||||||
|
|
||||||
|
if build.debug_mode?
|
||||||
|
access_denied!(
|
||||||
|
_('You must have developer or higher permissions in the associated project to view job logs when debug trace ' \
|
||||||
|
"is enabled. To disable debug trace, set the 'CI_DEBUG_TRACE' variable to 'false' in your pipeline " \
|
||||||
|
'configuration or CI/CD settings. If you need to view this job log, a project maintainer must add you to ' \
|
||||||
|
'the project with developer permissions or higher.')
|
||||||
|
)
|
||||||
|
else
|
||||||
|
access_denied!(_('The current user is not authorized to access the job log.'))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def build_canonical_path(project)
|
def build_canonical_path(project)
|
||||||
params[:namespace_id] = project.namespace.to_param
|
params[:namespace_id] = project.namespace.to_param
|
||||||
params[:project_id] = project.to_param
|
params[:project_id] = project.to_param
|
||||||
|
|
|
@ -7,6 +7,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
|
||||||
|
|
||||||
layout 'project'
|
layout 'project'
|
||||||
before_action :authorize_read_build!
|
before_action :authorize_read_build!
|
||||||
|
before_action :authorize_read_build_trace!, only: [:download]
|
||||||
before_action :authorize_update_build!, only: [:keep]
|
before_action :authorize_update_build!, only: [:keep]
|
||||||
before_action :authorize_destroy_artifacts!, only: [:destroy]
|
before_action :authorize_destroy_artifacts!, only: [:destroy]
|
||||||
before_action :extract_ref_name_and_path
|
before_action :extract_ref_name_and_path
|
||||||
|
@ -162,4 +163,10 @@ class Projects::ArtifactsController < Projects::ApplicationController
|
||||||
|
|
||||||
render_404 unless @entry.exists?
|
render_404 unless @entry.exists?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authorize_read_build_trace!
|
||||||
|
return unless params[:file_type] == 'trace'
|
||||||
|
|
||||||
|
super
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -171,17 +171,7 @@ class Projects::JobsController < Projects::ApplicationController
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def authorize_read_build_trace!
|
attr_reader :build
|
||||||
return if can?(current_user, :read_build_trace, @build)
|
|
||||||
|
|
||||||
msg = _(
|
|
||||||
"You must have developer or higher permissions in the associated project to view job logs when debug trace is enabled. To disable debug trace, set the 'CI_DEBUG_TRACE' variable to 'false' in your pipeline configuration or CI/CD settings. " \
|
|
||||||
"If you need to view this job log, a project maintainer must add you to the project with developer permissions or higher."
|
|
||||||
)
|
|
||||||
return access_denied!(msg) if @build.debug_mode?
|
|
||||||
|
|
||||||
access_denied!(_('The current user is not authorized to access the job log.'))
|
|
||||||
end
|
|
||||||
|
|
||||||
def authorize_update_build!
|
def authorize_update_build!
|
||||||
return access_denied! unless can?(current_user, :update_build, @build)
|
return access_denied! unless can?(current_user, :update_build, @build)
|
||||||
|
|
|
@ -7,7 +7,8 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
|
||||||
before_action :authorize_play_pipeline_schedule!, only: [:play]
|
before_action :authorize_play_pipeline_schedule!, only: [:play]
|
||||||
before_action :authorize_read_pipeline_schedule!
|
before_action :authorize_read_pipeline_schedule!
|
||||||
before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
|
before_action :authorize_create_pipeline_schedule!, only: [:new, :create]
|
||||||
before_action :authorize_update_pipeline_schedule!, except: [:index, :new, :create, :play]
|
before_action :authorize_update_pipeline_schedule!, only: [:edit, :update]
|
||||||
|
before_action :authorize_take_ownership_pipeline_schedule!, only: [:take_ownership]
|
||||||
before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
|
before_action :authorize_admin_pipeline_schedule!, only: [:destroy]
|
||||||
|
|
||||||
feature_category :continuous_integration
|
feature_category :continuous_integration
|
||||||
|
@ -108,6 +109,10 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
|
||||||
return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule)
|
return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authorize_take_ownership_pipeline_schedule!
|
||||||
|
return access_denied! unless can?(current_user, :take_ownership_pipeline_schedule, schedule)
|
||||||
|
end
|
||||||
|
|
||||||
def authorize_admin_pipeline_schedule!
|
def authorize_admin_pipeline_schedule!
|
||||||
return access_denied! unless can?(current_user, :admin_pipeline_schedule, schedule)
|
return access_denied! unless can?(current_user, :admin_pipeline_schedule, schedule)
|
||||||
end
|
end
|
||||||
|
|
|
@ -895,7 +895,10 @@ module Ci
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
cache
|
type_suffix = pipeline.protected_ref? ? 'protected' : 'non_protected'
|
||||||
|
cache.map do |entry|
|
||||||
|
entry.merge(key: "#{entry[:key]}-#{type_suffix}")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def credentials
|
def credentials
|
||||||
|
|
|
@ -229,7 +229,13 @@ class CommitStatus < Ci::ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def group_name
|
def group_name
|
||||||
name.to_s.sub(%r{([\b\s:]+((\[.*\])|(\d+[\s:\/\\]+\d+)))+\s*\z}, '').strip
|
# [\b\s:] -> whitespace or column
|
||||||
|
# (\[.*\])|(\d+[\s:\/\\]+\d+) -> variables/matrix or parallel-jobs numbers
|
||||||
|
# {1,3} -> number of times that matches the variables/matrix or parallel-jobs numbers
|
||||||
|
# we limit this to 3 because of possible abuse
|
||||||
|
regex = %r{([\b\s:]+((\[.*\])|(\d+[\s:\/\\]+\d+))){1,3}\s*\z}
|
||||||
|
|
||||||
|
name.to_s.sub(regex, '').strip
|
||||||
end
|
end
|
||||||
|
|
||||||
def failed_but_allowed?
|
def failed_but_allowed?
|
||||||
|
|
|
@ -29,10 +29,12 @@ module Integrations
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'password',
|
||||||
name: 'api_key',
|
name: 'api_key',
|
||||||
title: 'API key',
|
title: 'API key',
|
||||||
help: s_('AsanaService|User Personal Access Token. User must have access to the task. All comments are attributed to this user.'),
|
help: s_('AsanaService|User Personal Access Token. User must have access to the task. All comments are attributed to this user.'),
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new API key'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current API key.'),
|
||||||
# Example Personal Access Token from Asana docs
|
# Example Personal Access Token from Asana docs
|
||||||
placeholder: '0/68a9e79b868c6789e79a124c30b0',
|
placeholder: '0/68a9e79b868c6789e79a124c30b0',
|
||||||
required: true
|
required: true
|
||||||
|
|
|
@ -19,8 +19,19 @@ module Integrations
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{ type: 'text', name: 'token', placeholder: '', required: true },
|
{
|
||||||
{ type: 'text', name: 'subdomain', placeholder: '' }
|
type: 'password',
|
||||||
|
name: 'token',
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new token'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
|
||||||
|
placeholder: '',
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'subdomain',
|
||||||
|
placeholder: ''
|
||||||
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -55,10 +55,12 @@ module Integrations
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'password',
|
||||||
name: 'build_key',
|
name: 'build_key',
|
||||||
placeholder: s_('KEY'),
|
|
||||||
help: s_('BambooService|Bamboo build plan key.'),
|
help: s_('BambooService|Bamboo build plan key.'),
|
||||||
|
non_empty_password_title: s_('BambooService|Enter new build key'),
|
||||||
|
non_empty_password_help: s_('BambooService|Leave blank to use your current build key.'),
|
||||||
|
placeholder: s_('KEY'),
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -26,7 +26,13 @@ module Integrations
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{ type: 'text', name: 'token', placeholder: 'XXxxXXxxXXxxXXxxXXxxXXxx' }
|
{
|
||||||
|
type: 'password',
|
||||||
|
name: 'token',
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new token'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
|
||||||
|
placeholder: 'XXxxXXxxXXxxXXxxXXxxXXxx'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -76,10 +76,12 @@ module Integrations
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{ type: 'text',
|
{ type: 'password',
|
||||||
name: 'token',
|
name: 'token',
|
||||||
title: _('Token'),
|
title: _('Token'),
|
||||||
help: s_('ProjectService|The token you get after you create a Buildkite pipeline with a GitLab repository.'),
|
help: s_('ProjectService|The token you get after you create a Buildkite pipeline with a GitLab repository.'),
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new token'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
|
||||||
required: true },
|
required: true },
|
||||||
|
|
||||||
{ type: 'text',
|
{ type: 'text',
|
||||||
|
|
|
@ -27,11 +27,13 @@ module Integrations
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'password',
|
||||||
name: 'token',
|
name: 'token',
|
||||||
title: _('Campfire token'),
|
title: _('Campfire token'),
|
||||||
placeholder: '',
|
|
||||||
help: s_('CampfireService|API authentication token from Campfire.'),
|
help: s_('CampfireService|API authentication token from Campfire.'),
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new token'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
|
||||||
|
placeholder: '',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -96,8 +96,21 @@ module Integrations
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{ type: 'text', name: 'token', help: s_('ProjectService|Token for the Drone project.'), required: true },
|
{
|
||||||
{ type: 'text', name: 'drone_url', title: s_('ProjectService|Drone server URL'), placeholder: 'http://drone.example.com', required: true }
|
type: 'password',
|
||||||
|
name: 'token',
|
||||||
|
help: s_('ProjectService|Token for the Drone project.'),
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new token'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'text',
|
||||||
|
name: 'drone_url',
|
||||||
|
title: s_('ProjectService|Drone server URL'),
|
||||||
|
placeholder: 'http://drone.example.com',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -26,7 +26,15 @@ module Integrations
|
||||||
|
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{ type: 'text', name: 'token', placeholder: s_('FlowdockService|1b609b52537...'), required: true, help: 'Enter your Flowdock token.' }
|
{
|
||||||
|
type: 'password',
|
||||||
|
name: 'token',
|
||||||
|
help: s_('FlowdockService|Enter your Flowdock token.'),
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new token'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
|
||||||
|
placeholder: '1b609b52537...',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
]
|
]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,12 @@ module Integrations
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'password',
|
||||||
name: 'token',
|
name: 'token',
|
||||||
title: _('Token'),
|
title: _('Token'),
|
||||||
help: s_('Enter your Packagist token.'),
|
help: s_('Enter your Packagist token.'),
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new token'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
|
@ -28,9 +28,11 @@ module Integrations
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'password',
|
||||||
name: 'token',
|
name: 'token',
|
||||||
help: s_('PivotalTrackerService|Pivotal Tracker API token. User must have access to the story. All comments are attributed to this user.'),
|
help: s_('PivotalTrackerService|Pivotal Tracker API token. User must have access to the story. All comments are attributed to this user.'),
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new token'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current token.'),
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -22,18 +22,22 @@ module Integrations
|
||||||
def fields
|
def fields
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'password',
|
||||||
name: 'api_key',
|
name: 'api_key',
|
||||||
title: _('API key'),
|
title: _('API key'),
|
||||||
help: s_('PushoverService|Enter your application key.'),
|
help: s_('PushoverService|Enter your application key.'),
|
||||||
|
non_empty_password_title: s_('ProjectService|Enter new API key'),
|
||||||
|
non_empty_password_help: s_('ProjectService|Leave blank to use your current API key.'),
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'text',
|
type: 'password',
|
||||||
name: 'user_key',
|
name: 'user_key',
|
||||||
title: _('User key'),
|
title: _('User key'),
|
||||||
help: s_('PushoverService|Enter your user key.'),
|
help: s_('PushoverService|Enter your user key.'),
|
||||||
|
non_empty_password_title: s_('PushoverService|Enter new user key'),
|
||||||
|
non_empty_password_help: s_('PushoverService|Leave blank to use your current user key.'),
|
||||||
placeholder: '',
|
placeholder: '',
|
||||||
required: true
|
required: true
|
||||||
},
|
},
|
||||||
|
|
|
@ -630,7 +630,8 @@ class Issue < ApplicationRecord
|
||||||
|
|
||||||
# Returns `true` if this Issue is visible to everybody.
|
# Returns `true` if this Issue is visible to everybody.
|
||||||
def publicly_visible?
|
def publicly_visible?
|
||||||
project.public? && !confidential? && !hidden? && !::Gitlab::ExternalAuthorization.enabled?
|
project.public? && project.feature_available?(:issues, nil) &&
|
||||||
|
!confidential? && !hidden? && !::Gitlab::ExternalAuthorization.enabled?
|
||||||
end
|
end
|
||||||
|
|
||||||
def expire_etag_cache
|
def expire_etag_cache
|
||||||
|
|
|
@ -35,6 +35,7 @@ class Packages::PackageFile < ApplicationRecord
|
||||||
validates :file_name, presence: true
|
validates :file_name, presence: true
|
||||||
|
|
||||||
validates :file_name, uniqueness: { scope: :package }, if: -> { package&.pypi? }
|
validates :file_name, uniqueness: { scope: :package }, if: -> { package&.pypi? }
|
||||||
|
validates :file_sha256, format: { with: Gitlab::Regex.sha256_regex }, if: -> { package&.pypi? }, allow_nil: true
|
||||||
|
|
||||||
scope :recent, -> { order(id: :desc) }
|
scope :recent, -> { order(id: :desc) }
|
||||||
scope :limit_recent, ->(limit) { recent.limit(limit) }
|
scope :limit_recent, ->(limit) { recent.limit(limit) }
|
||||||
|
|
|
@ -104,7 +104,7 @@ class ProjectFeature < ApplicationRecord
|
||||||
# that the user has access to the feature. It's important to use this scope with others
|
# that the user has access to the feature. It's important to use this scope with others
|
||||||
# that checks project authorizations first (e.g. `filter_by_feature_visibility`).
|
# that checks project authorizations first (e.g. `filter_by_feature_visibility`).
|
||||||
#
|
#
|
||||||
# This method uses an optimised version of `with_feature_access_level` for
|
# This method uses an optimized version of `with_feature_access_level` for
|
||||||
# logged in users to more efficiently get private projects with the given
|
# logged in users to more efficiently get private projects with the given
|
||||||
# feature.
|
# feature.
|
||||||
def self.with_feature_available_for_user(feature, user)
|
def self.with_feature_available_for_user(feature, user)
|
||||||
|
|
|
@ -15,11 +15,14 @@ module Ci
|
||||||
rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
|
rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
|
||||||
|
|
||||||
rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
|
rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
|
||||||
enable :update_pipeline_schedule
|
|
||||||
enable :admin_pipeline_schedule
|
enable :admin_pipeline_schedule
|
||||||
enable :read_pipeline_schedule_variables
|
enable :read_pipeline_schedule_variables
|
||||||
end
|
end
|
||||||
|
|
||||||
|
rule { admin | (owner_of_schedule & can?(:update_build)) }.policy do
|
||||||
|
enable :update_pipeline_schedule
|
||||||
|
end
|
||||||
|
|
||||||
rule { can?(:admin_pipeline_schedule) & ~owner_of_schedule }.policy do
|
rule { can?(:admin_pipeline_schedule) & ~owner_of_schedule }.policy do
|
||||||
enable :take_ownership_pipeline_schedule
|
enable :take_ownership_pipeline_schedule
|
||||||
end
|
end
|
||||||
|
|
|
@ -352,8 +352,6 @@ class TodoService
|
||||||
end
|
end
|
||||||
|
|
||||||
def reject_users_without_access(users, parent, target)
|
def reject_users_without_access(users, parent, target)
|
||||||
target = target.noteable if target.is_a?(Note)
|
|
||||||
|
|
||||||
if target.respond_to?(:to_ability_name)
|
if target.respond_to?(:to_ability_name)
|
||||||
select_users(users, :"read_#{target.to_ability_name}", target)
|
select_users(users, :"read_#{target.to_ability_name}", target)
|
||||||
else
|
else
|
||||||
|
|
|
@ -31,6 +31,7 @@ can't link to files outside it.
|
||||||
- Subsequent pipelines can use the cache.
|
- Subsequent pipelines can use the cache.
|
||||||
- Subsequent jobs in the same pipeline can use the cache, if the dependencies are identical.
|
- Subsequent jobs in the same pipeline can use the cache, if the dependencies are identical.
|
||||||
- Different projects cannot share the cache.
|
- Different projects cannot share the cache.
|
||||||
|
- Protected and non-protected branches do not share the cache.
|
||||||
|
|
||||||
### Artifacts
|
### Artifacts
|
||||||
|
|
||||||
|
@ -446,6 +447,22 @@ is stored on the machine where GitLab Runner is installed. The location also dep
|
||||||
If you use cache and artifacts to store the same path in your jobs, the cache might
|
If you use cache and artifacts to store the same path in your jobs, the cache might
|
||||||
be overwritten because caches are restored before artifacts.
|
be overwritten because caches are restored before artifacts.
|
||||||
|
|
||||||
|
### Segregation of caches between protected and non-protected branches
|
||||||
|
|
||||||
|
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/330047) in GitLab 14.10.
|
||||||
|
|
||||||
|
A suffix is added to the cache key, with the exception of the [fallback cache key](#use-a-fallback-cache-key).
|
||||||
|
This is done in order to prevent cache poisoning that might occur through manipulation of the cache in a non-protected
|
||||||
|
branch. Any subsequent protected-branch jobs would then potentially use a poisoned cache from the preceding job.
|
||||||
|
|
||||||
|
As an example, assuming that `cache.key` is set to `$CI_COMMIT_REF_SLUG`, and that we have two branches `main`
|
||||||
|
and `feature`, then the following table represents the resulting cache keys:
|
||||||
|
|
||||||
|
| Branch name | Cache key |
|
||||||
|
|-------------|-----------|
|
||||||
|
| `main` | `main-protected` |
|
||||||
|
| `feature` | `feature-non_protected` |
|
||||||
|
|
||||||
### How archiving and extracting works
|
### How archiving and extracting works
|
||||||
|
|
||||||
This example shows two jobs in two consecutive stages:
|
This example shows two jobs in two consecutive stages:
|
||||||
|
|
|
@ -151,7 +151,7 @@ The jobs are ordered by comparing the numbers from left to right. You
|
||||||
usually want the first number to be the index and the second number to be the total.
|
usually want the first number to be the index and the second number to be the total.
|
||||||
|
|
||||||
[This regular expression](https://gitlab.com/gitlab-org/gitlab/-/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99)
|
[This regular expression](https://gitlab.com/gitlab-org/gitlab/-/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99)
|
||||||
evaluates the job names: `([\b\s:]+((\[.*\])|(\d+[\s:\/\\]+\d+)))+\s*\z`.
|
evaluates the job names: `([\b\s:]+((\[.*\])|(\d+[\s:\/\\]+\d+))){1,3}\s*\z`.
|
||||||
One or more `: [...]`, `X Y`, `X/Y`, or `X\Y` sequences are removed from the **end**
|
One or more `: [...]`, `X Y`, `X/Y`, or `X\Y` sequences are removed from the **end**
|
||||||
of job names only. Matching substrings found at the beginning or in the middle of
|
of job names only. Matching substrings found at the beginning or in the middle of
|
||||||
job names are not removed.
|
job names are not removed.
|
||||||
|
|
|
@ -53,6 +53,20 @@ is installed on.
|
||||||
|
|
||||||
![Schedules list](img/pipeline_schedules_list.png)
|
![Schedules list](img/pipeline_schedules_list.png)
|
||||||
|
|
||||||
|
## Edit a pipeline schedule
|
||||||
|
|
||||||
|
> Introduced in GitLab 14.8, only a pipeline schedule owner can edit the schedule.
|
||||||
|
|
||||||
|
The owner of a pipeline schedule can edit it:
|
||||||
|
|
||||||
|
1. On the top bar, select **Menu > Projects** and find your project.
|
||||||
|
1. In the left sidebar, select **CI/CD > Schedules**.
|
||||||
|
1. Next to the schedule, select **Edit** (**{pencil}**) and fill in the form.
|
||||||
|
|
||||||
|
The user must have the Developer role or above for the project. If the user is
|
||||||
|
not the owner of the schedule, they must first take ownership.
|
||||||
|
of the schedule.
|
||||||
|
|
||||||
### Using variables
|
### Using variables
|
||||||
|
|
||||||
You can pass any number of arbitrary variables. They are available in
|
You can pass any number of arbitrary variables. They are available in
|
||||||
|
|
|
@ -93,7 +93,7 @@ module API
|
||||||
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
|
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
|
||||||
end
|
end
|
||||||
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
|
post ':id/pipeline_schedules/:pipeline_schedule_id/take_ownership' do
|
||||||
authorize! :update_pipeline_schedule, pipeline_schedule
|
authorize! :take_ownership_pipeline_schedule, pipeline_schedule
|
||||||
|
|
||||||
if pipeline_schedule.own!(current_user)
|
if pipeline_schedule.own!(current_user)
|
||||||
present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
|
present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails
|
||||||
|
|
|
@ -153,7 +153,7 @@ module API
|
||||||
def token
|
def token
|
||||||
strong_memoize(:token) do
|
strong_memoize(:token) do
|
||||||
token = nil
|
token = nil
|
||||||
token = ::Gitlab::ConanToken.from_personal_access_token(access_token) if access_token
|
token = ::Gitlab::ConanToken.from_personal_access_token(find_personal_access_token.user_id, access_token_from_request) if find_personal_access_token
|
||||||
token = ::Gitlab::ConanToken.from_deploy_token(deploy_token_from_request) if deploy_token_from_request
|
token = ::Gitlab::ConanToken.from_deploy_token(deploy_token_from_request) if deploy_token_from_request
|
||||||
token = ::Gitlab::ConanToken.from_job(find_job_from_token) if find_job_from_token
|
token = ::Gitlab::ConanToken.from_job(find_job_from_token) if find_job_from_token
|
||||||
token
|
token
|
||||||
|
@ -224,9 +224,27 @@ module API
|
||||||
forbidden!
|
forbidden!
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# We override this method from auth_finders because we need to
|
||||||
|
# extract the token from the Conan JWT which is specific to the Conan API
|
||||||
def find_personal_access_token
|
def find_personal_access_token
|
||||||
|
strong_memoize(:find_personal_access_token) do
|
||||||
|
PersonalAccessToken.find_by_token(access_token_from_request)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def access_token_from_request
|
||||||
|
strong_memoize(:access_token_from_request) do
|
||||||
find_personal_access_token_from_conan_jwt ||
|
find_personal_access_token_from_conan_jwt ||
|
||||||
find_personal_access_token_from_http_basic_auth
|
find_password_from_basic_auth
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def find_password_from_basic_auth
|
||||||
|
return unless route_authentication_setting[:basic_auth_personal_access_token]
|
||||||
|
return unless has_basic_credentials?(current_request)
|
||||||
|
|
||||||
|
_username, password = user_name_and_password(current_request)
|
||||||
|
password
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_user_from_job_token
|
def find_user_from_job_token
|
||||||
|
@ -256,7 +274,7 @@ module API
|
||||||
|
|
||||||
return unless token
|
return unless token
|
||||||
|
|
||||||
PersonalAccessToken.find_by_id_and_user_id(token.access_token_id, token.user_id)
|
token.access_token_id
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_deploy_token_from_conan_jwt
|
def find_deploy_token_from_conan_jwt
|
||||||
|
|
|
@ -174,7 +174,7 @@ module API
|
||||||
requires :name, type: String
|
requires :name, type: String
|
||||||
requires :version, type: String
|
requires :version, type: String
|
||||||
optional :md5_digest, type: String
|
optional :md5_digest, type: String
|
||||||
optional :sha256_digest, type: String
|
optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex
|
||||||
end
|
end
|
||||||
|
|
||||||
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
|
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
|
||||||
|
|
|
@ -13,6 +13,10 @@ module Gitlab
|
||||||
@request = request
|
@request = request
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def find_authenticated_requester(request_formats)
|
||||||
|
user(request_formats) || deploy_token_from_request
|
||||||
|
end
|
||||||
|
|
||||||
def user(request_formats)
|
def user(request_formats)
|
||||||
request_formats.each do |format|
|
request_formats.each do |format|
|
||||||
user = find_sessionless_user(format)
|
user = find_sessionless_user(format)
|
||||||
|
@ -80,7 +84,8 @@ module Gitlab
|
||||||
def route_authentication_setting
|
def route_authentication_setting
|
||||||
@route_authentication_setting ||= {
|
@route_authentication_setting ||= {
|
||||||
job_token_allowed: api_request?,
|
job_token_allowed: api_request?,
|
||||||
basic_auth_personal_access_token: api_request? || git_request?
|
basic_auth_personal_access_token: api_request? || git_request?,
|
||||||
|
deploy_token_allowed: api_request? || git_request?
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -6,25 +6,28 @@ module Gitlab
|
||||||
module Chain
|
module Chain
|
||||||
module Helpers
|
module Helpers
|
||||||
def error(message, config_error: false, drop_reason: nil)
|
def error(message, config_error: false, drop_reason: nil)
|
||||||
|
sanitized_message = ActionController::Base.helpers.sanitize(message, tags: [])
|
||||||
|
|
||||||
if config_error
|
if config_error
|
||||||
drop_reason = :config_error
|
drop_reason = :config_error
|
||||||
pipeline.yaml_errors = message
|
pipeline.yaml_errors = sanitized_message
|
||||||
end
|
end
|
||||||
|
|
||||||
pipeline.add_error_message(message)
|
pipeline.add_error_message(sanitized_message)
|
||||||
|
|
||||||
drop_pipeline!(drop_reason)
|
drop_pipeline!(drop_reason)
|
||||||
|
|
||||||
# TODO: consider not to rely on AR errors directly as they can be
|
# TODO: consider not to rely on AR errors directly as they can be
|
||||||
# polluted with other unrelated errors (e.g. state machine)
|
# polluted with other unrelated errors (e.g. state machine)
|
||||||
# https://gitlab.com/gitlab-org/gitlab/-/issues/220823
|
# https://gitlab.com/gitlab-org/gitlab/-/issues/220823
|
||||||
pipeline.errors.add(:base, message)
|
pipeline.errors.add(:base, sanitized_message)
|
||||||
|
|
||||||
pipeline.errors.full_messages
|
pipeline.errors.full_messages
|
||||||
end
|
end
|
||||||
|
|
||||||
def warning(message)
|
def warning(message)
|
||||||
pipeline.add_warning_message(message)
|
sanitized_message = ActionController::Base.helpers.sanitize(message, tags: [])
|
||||||
|
pipeline.add_warning_message(sanitized_message)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -23,7 +23,7 @@ module Gitlab
|
||||||
end
|
end
|
||||||
|
|
||||||
unless allowed_to_write_ref?
|
unless allowed_to_write_ref?
|
||||||
error("You do not have sufficient permission to run a pipeline on '#{command.ref}'. Please select a different branch or contact your administrator for assistance. <a href=https://docs.gitlab.com/ee/ci/pipelines/#pipeline-security-on-protected-branches>Learn more</a>".html_safe)
|
error("You do not have sufficient permission to run a pipeline on '#{command.ref}'. Please select a different branch or contact your administrator for assistance.")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -13,8 +13,8 @@ module Gitlab
|
||||||
attr_reader :access_token_id, :user_id
|
attr_reader :access_token_id, :user_id
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def from_personal_access_token(access_token)
|
def from_personal_access_token(user_id, token)
|
||||||
new(access_token_id: access_token.id, user_id: access_token.user_id)
|
new(access_token_id: token, user_id: user_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def from_job(job)
|
def from_job(job)
|
||||||
|
|
|
@ -31,7 +31,8 @@ module Gitlab
|
||||||
def check_download_access!
|
def check_download_access!
|
||||||
super
|
super
|
||||||
|
|
||||||
raise ForbiddenError, download_forbidden_message if deploy_token && !deploy_token.can?(:download_wiki_code, container)
|
raise ForbiddenError, download_forbidden_message if build_cannot_download?
|
||||||
|
raise ForbiddenError, download_forbidden_message if deploy_token_cannot_download?
|
||||||
end
|
end
|
||||||
|
|
||||||
override :check_change_access!
|
override :check_change_access!
|
||||||
|
@ -52,6 +53,17 @@ module Gitlab
|
||||||
def not_found_message
|
def not_found_message
|
||||||
error_message(:not_found)
|
error_message(:not_found)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
# when accessing via the CI_JOB_TOKEN
|
||||||
|
def build_cannot_download?
|
||||||
|
build_can_download_code? && !user_access.can_do_action?(download_ability)
|
||||||
|
end
|
||||||
|
|
||||||
|
def deploy_token_cannot_download?
|
||||||
|
deploy_token && !deploy_token.can?(download_ability, container)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -638,7 +638,6 @@ included_attributes:
|
||||||
- :build_allow_git_fetch
|
- :build_allow_git_fetch
|
||||||
- :build_coverage_regex
|
- :build_coverage_regex
|
||||||
- :build_timeout
|
- :build_timeout
|
||||||
- :ci_config_path
|
|
||||||
- :delete_error
|
- :delete_error
|
||||||
- :description
|
- :description
|
||||||
- :disable_overriding_approvers_per_merge_request
|
- :disable_overriding_approvers_per_merge_request
|
||||||
|
|
|
@ -87,6 +87,8 @@ module Gitlab
|
||||||
when *BUILD_MODELS then setup_build
|
when *BUILD_MODELS then setup_build
|
||||||
when :issues then setup_issue
|
when :issues then setup_issue
|
||||||
when :'Ci::PipelineSchedule' then setup_pipeline_schedule
|
when :'Ci::PipelineSchedule' then setup_pipeline_schedule
|
||||||
|
when :'ProtectedBranch::MergeAccessLevel' then setup_protected_branch_access_level
|
||||||
|
when :'ProtectedBranch::PushAccessLevel' then setup_protected_branch_access_level
|
||||||
end
|
end
|
||||||
|
|
||||||
update_project_references
|
update_project_references
|
||||||
|
@ -152,6 +154,10 @@ module Gitlab
|
||||||
@relation_hash['active'] = false
|
@relation_hash['active'] = false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setup_protected_branch_access_level
|
||||||
|
@relation_hash['access_level'] = Gitlab::Access::MAINTAINER
|
||||||
|
end
|
||||||
|
|
||||||
def compute_relative_position
|
def compute_relative_position
|
||||||
return unless max_relative_position
|
return unless max_relative_position
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ module Gitlab
|
||||||
# this if the change to the renderer output is a new feature or a
|
# this if the change to the renderer output is a new feature or a
|
||||||
# minor bug fix.
|
# minor bug fix.
|
||||||
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330313
|
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330313
|
||||||
CACHE_COMMONMARK_VERSION = 29
|
CACHE_COMMONMARK_VERSION = 30
|
||||||
CACHE_COMMONMARK_VERSION_START = 10
|
CACHE_COMMONMARK_VERSION_START = 10
|
||||||
|
|
||||||
BaseError = Class.new(StandardError)
|
BaseError = Class.new(StandardError)
|
||||||
|
|
|
@ -73,12 +73,16 @@ module Gitlab
|
||||||
matched: req.env['rack.attack.matched']
|
matched: req.env['rack.attack.matched']
|
||||||
}
|
}
|
||||||
|
|
||||||
if THROTTLES_WITH_USER_INFORMATION.include? req.env['rack.attack.matched'].to_sym
|
discriminator = req.env['rack.attack.match_discriminator'].to_s
|
||||||
user_id = req.env['rack.attack.match_discriminator']
|
discriminator_id = discriminator.split(':').last
|
||||||
user = User.find_by(id: user_id) # rubocop:disable CodeReuse/ActiveRecord
|
|
||||||
|
|
||||||
rack_attack_info[:user_id] = user_id
|
if discriminator.starts_with?('user:')
|
||||||
|
user = User.find_by(id: discriminator_id) # rubocop:disable CodeReuse/ActiveRecord
|
||||||
|
|
||||||
|
rack_attack_info[:user_id] = discriminator_id.to_i
|
||||||
rack_attack_info['meta.user'] = user.username unless user.nil?
|
rack_attack_info['meta.user'] = user.username unless user.nil?
|
||||||
|
elsif discriminator.starts_with?('deploy_token:')
|
||||||
|
rack_attack_info[:deploy_token_id] = discriminator_id.to_i
|
||||||
end
|
end
|
||||||
|
|
||||||
Gitlab::InstrumentationHelper.add_instrumentation_data(rack_attack_info)
|
Gitlab::InstrumentationHelper.add_instrumentation_data(rack_attack_info)
|
||||||
|
|
|
@ -95,7 +95,7 @@ module Gitlab
|
||||||
authenticated_options = Gitlab::Throttle.options(throttle, authenticated: true)
|
authenticated_options = Gitlab::Throttle.options(throttle, authenticated: true)
|
||||||
throttle_or_track(rack_attack, "throttle_authenticated_#{throttle}", authenticated_options) do |req|
|
throttle_or_track(rack_attack, "throttle_authenticated_#{throttle}", authenticated_options) do |req|
|
||||||
if req.throttle?(throttle, authenticated: true)
|
if req.throttle?(throttle, authenticated: true)
|
||||||
req.throttled_user_id([:api])
|
req.throttled_identifer([:api])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -117,7 +117,7 @@ module Gitlab
|
||||||
|
|
||||||
throttle_or_track(rack_attack, 'throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
|
throttle_or_track(rack_attack, 'throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
|
||||||
if req.throttle_authenticated_web?
|
if req.throttle_authenticated_web?
|
||||||
req.throttled_user_id([:api, :rss, :ics])
|
req.throttled_identifer([:api, :rss, :ics])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -129,19 +129,19 @@ module Gitlab
|
||||||
|
|
||||||
throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req|
|
throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req|
|
||||||
if req.throttle_authenticated_protected_paths_api?
|
if req.throttle_authenticated_protected_paths_api?
|
||||||
req.throttled_user_id([:api])
|
req.throttled_identifer([:api])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req|
|
throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req|
|
||||||
if req.throttle_authenticated_protected_paths_web?
|
if req.throttle_authenticated_protected_paths_web?
|
||||||
req.throttled_user_id([:api, :rss, :ics])
|
req.throttled_identifer([:api, :rss, :ics])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
throttle_or_track(rack_attack, 'throttle_authenticated_git_lfs', Gitlab::Throttle.throttle_authenticated_git_lfs_options) do |req|
|
throttle_or_track(rack_attack, 'throttle_authenticated_git_lfs', Gitlab::Throttle.throttle_authenticated_git_lfs_options) do |req|
|
||||||
if req.throttle_authenticated_git_lfs?
|
if req.throttle_authenticated_git_lfs?
|
||||||
req.throttled_user_id([:api])
|
req.throttled_identifer([:api])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -9,18 +9,22 @@ module Gitlab
|
||||||
GROUP_PATH_REGEX = %r{^/api/v\d+/groups/[^/]+/?$}.freeze
|
GROUP_PATH_REGEX = %r{^/api/v\d+/groups/[^/]+/?$}.freeze
|
||||||
|
|
||||||
def unauthenticated?
|
def unauthenticated?
|
||||||
!(authenticated_user_id([:api, :rss, :ics]) || authenticated_runner_id)
|
!(authenticated_identifier([:api, :rss, :ics]) || authenticated_runner_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
def throttled_user_id(request_formats)
|
def throttled_identifer(request_formats)
|
||||||
user_id = authenticated_user_id(request_formats)
|
identifier = authenticated_identifier(request_formats)
|
||||||
|
return unless identifier
|
||||||
|
|
||||||
if Gitlab::RackAttack.user_allowlist.include?(user_id)
|
identifier_type = identifier[:identifier_type]
|
||||||
|
identifier_id = identifier[:identifier_id]
|
||||||
|
|
||||||
|
if identifier_type == :user && Gitlab::RackAttack.user_allowlist.include?(identifier_id)
|
||||||
Gitlab::Instrumentation::Throttle.safelist = 'throttle_user_allowlist'
|
Gitlab::Instrumentation::Throttle.safelist = 'throttle_user_allowlist'
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
user_id
|
"#{identifier_type}:#{identifier_id}"
|
||||||
end
|
end
|
||||||
|
|
||||||
def authenticated_runner_id
|
def authenticated_runner_id
|
||||||
|
@ -169,8 +173,18 @@ module Gitlab
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def authenticated_user_id(request_formats)
|
def authenticated_identifier(request_formats)
|
||||||
request_authenticator.user(request_formats)&.id
|
requester = request_authenticator.find_authenticated_requester(request_formats)
|
||||||
|
|
||||||
|
return unless requester
|
||||||
|
|
||||||
|
identifier_type = if requester.is_a?(DeployToken)
|
||||||
|
:deploy_token
|
||||||
|
else
|
||||||
|
:user
|
||||||
|
end
|
||||||
|
|
||||||
|
{ identifier_type: identifier_type, identifier_id: requester.id }
|
||||||
end
|
end
|
||||||
|
|
||||||
def request_authenticator
|
def request_authenticator
|
||||||
|
|
|
@ -237,6 +237,10 @@ module Gitlab
|
||||||
generic_package_name_regex
|
generic_package_name_regex
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def sha256_regex
|
||||||
|
@sha256_regex ||= /\A[0-9a-f]{64}\z/i.freeze
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def conan_name_regex
|
def conan_name_regex
|
||||||
|
|
|
@ -5446,6 +5446,12 @@ msgstr ""
|
||||||
msgid "BambooService|Bamboo service root URL."
|
msgid "BambooService|Bamboo service root URL."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "BambooService|Enter new build key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "BambooService|Leave blank to use your current build key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "BambooService|Run CI/CD pipelines with Atlassian Bamboo."
|
msgid "BambooService|Run CI/CD pipelines with Atlassian Bamboo."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -15643,7 +15649,7 @@ msgstr ""
|
||||||
msgid "FloC|Federated Learning of Cohorts"
|
msgid "FloC|Federated Learning of Cohorts"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "FlowdockService|1b609b52537..."
|
msgid "FlowdockService|Enter your Flowdock token."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
msgid "FlowdockService|Send event notifications from GitLab to Flowdock flows."
|
msgid "FlowdockService|Send event notifications from GitLab to Flowdock flows."
|
||||||
|
@ -28474,6 +28480,9 @@ msgstr ""
|
||||||
msgid "ProjectService|Leave blank to use your current API key"
|
msgid "ProjectService|Leave blank to use your current API key"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "ProjectService|Leave blank to use your current API key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "ProjectService|Leave blank to use your current password"
|
msgid "ProjectService|Leave blank to use your current password"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -29806,6 +29815,9 @@ msgstr ""
|
||||||
msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
|
msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "PushoverService|Enter new user key"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "PushoverService|Enter your application key."
|
msgid "PushoverService|Enter your application key."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
@ -29821,6 +29833,9 @@ msgstr ""
|
||||||
msgid "PushoverService|Leave blank for all active devices."
|
msgid "PushoverService|Leave blank for all active devices."
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "PushoverService|Leave blank to use your current user key."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
msgid "PushoverService|Low priority"
|
msgid "PushoverService|Low priority"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
|
|
@ -204,6 +204,44 @@ RSpec.describe Projects::ArtifactsController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when downloading a debug trace' do
|
||||||
|
let(:file_type) { 'trace' }
|
||||||
|
let(:job) { create(:ci_build, :success, :trace_artifact, pipeline: pipeline) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
create(:ci_job_variable, key: 'CI_DEBUG_TRACE', value: 'true', job: job)
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user does not have update_build permissions' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.add_guest(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
render_views
|
||||||
|
|
||||||
|
it 'denies the user access' do
|
||||||
|
download_artifact(file_type: file_type)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
|
expect(response.body).to include(
|
||||||
|
'You must have developer or higher permissions in the associated project to view job logs when debug trace is enabled. ' \
|
||||||
|
'To disable debug trace, set the 'CI_DEBUG_TRACE' variable to 'false' in your pipeline configuration or CI/CD settings. ' \
|
||||||
|
'If you need to view this job log, a project maintainer must add you to the project with developer permissions or higher.'
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when the user has update_build permissions' do
|
||||||
|
it 'sends the trace' do
|
||||||
|
download_artifact(file_type: file_type)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:ok)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET browse' do
|
describe 'GET browse' do
|
||||||
|
|
|
@ -13,10 +13,43 @@ RSpec.describe Projects::PipelineSchedulesController do
|
||||||
project.add_developer(user)
|
project.add_developer(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
shared_examples 'access update schedule' do
|
||||||
|
describe 'security' do
|
||||||
|
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||||
|
expect { go }.to be_allowed_for(:admin)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'is denied for admin when admin mode disabled' do
|
||||||
|
expect { go }.to be_denied_for(:admin)
|
||||||
|
end
|
||||||
|
|
||||||
|
it { expect { go }.to be_denied_for(:owner).of(project) }
|
||||||
|
it { expect { go }.to be_denied_for(:maintainer).of(project) }
|
||||||
|
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||||
|
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||||
|
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||||
|
it { expect { go }.to be_denied_for(:user) }
|
||||||
|
it { expect { go }.to be_denied_for(:external) }
|
||||||
|
it { expect { go }.to be_denied_for(:visitor) }
|
||||||
|
|
||||||
|
context 'when user is schedule owner' do
|
||||||
|
it { expect { go }.to be_allowed_for(:owner).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_allowed_for(:maintainer).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:reporter).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:guest).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:user).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:external).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:visitor).own(pipeline_schedule) }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET #index' do
|
describe 'GET #index' do
|
||||||
render_views
|
render_views
|
||||||
|
|
||||||
let(:scope) { nil }
|
let(:scope) { nil }
|
||||||
|
|
||||||
let!(:inactive_pipeline_schedule) do
|
let!(:inactive_pipeline_schedule) do
|
||||||
create(:ci_pipeline_schedule, :inactive, project: project)
|
create(:ci_pipeline_schedule, :inactive, project: project)
|
||||||
end
|
end
|
||||||
|
@ -130,12 +163,15 @@ RSpec.describe Projects::PipelineSchedulesController do
|
||||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||||
expect { go }.to be_allowed_for(:admin)
|
expect { go }.to be_allowed_for(:admin)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'is denied for admin when admin mode disabled' do
|
it 'is denied for admin when admin mode disabled' do
|
||||||
expect { go }.to be_denied_for(:admin)
|
expect { go }.to be_denied_for(:admin)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||||
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
||||||
it { expect { go }.to be_allowed_for(:developer).of(project) }
|
it { expect { go }.to be_allowed_for(:developer).of(project) }
|
||||||
|
|
||||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||||
it { expect { go }.to be_denied_for(:user) }
|
it { expect { go }.to be_denied_for(:user) }
|
||||||
|
@ -284,20 +320,7 @@ RSpec.describe Projects::PipelineSchedulesController do
|
||||||
describe 'security' do
|
describe 'security' do
|
||||||
let(:schedule) { { description: 'updated_desc' } }
|
let(:schedule) { { description: 'updated_desc' } }
|
||||||
|
|
||||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
it_behaves_like 'access update schedule'
|
||||||
expect { go }.to be_allowed_for(:admin)
|
|
||||||
end
|
|
||||||
it 'is denied for admin when admin mode disabled' do
|
|
||||||
expect { go }.to be_denied_for(:admin)
|
|
||||||
end
|
|
||||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
|
||||||
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
|
||||||
it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
|
|
||||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
|
||||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
|
||||||
it { expect { go }.to be_denied_for(:user) }
|
|
||||||
it { expect { go }.to be_denied_for(:external) }
|
|
||||||
it { expect { go }.to be_denied_for(:visitor) }
|
|
||||||
|
|
||||||
context 'when a developer created a pipeline schedule' do
|
context 'when a developer created a pipeline schedule' do
|
||||||
let(:developer_1) { create(:user) }
|
let(:developer_1) { create(:user) }
|
||||||
|
@ -308,8 +331,10 @@ RSpec.describe Projects::PipelineSchedulesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect { go }.to be_allowed_for(developer_1) }
|
it { expect { go }.to be_allowed_for(developer_1) }
|
||||||
|
|
||||||
|
it { expect { go }.to be_denied_for(:owner).of(project) }
|
||||||
|
it { expect { go }.to be_denied_for(:maintainer).of(project) }
|
||||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||||
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when a maintainer created a pipeline schedule' do
|
context 'when a maintainer created a pipeline schedule' do
|
||||||
|
@ -321,16 +346,20 @@ RSpec.describe Projects::PipelineSchedulesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect { go }.to be_allowed_for(maintainer_1) }
|
it { expect { go }.to be_allowed_for(maintainer_1) }
|
||||||
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
|
||||||
|
it { expect { go }.to be_denied_for(:owner).of(project) }
|
||||||
|
it { expect { go }.to be_denied_for(:maintainer).of(project) }
|
||||||
it { expect { go }.to be_denied_for(:developer).of(project) }
|
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def go
|
def go
|
||||||
put :update, params: { namespace_id: project.namespace.to_param,
|
put :update, params: {
|
||||||
|
namespace_id: project.namespace.to_param,
|
||||||
project_id: project,
|
project_id: project,
|
||||||
id: pipeline_schedule,
|
id: pipeline_schedule,
|
||||||
schedule: schedule },
|
schedule: schedule
|
||||||
|
},
|
||||||
as: :html
|
as: :html
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -341,6 +370,7 @@ RSpec.describe Projects::PipelineSchedulesController do
|
||||||
|
|
||||||
before do
|
before do
|
||||||
project.add_maintainer(user)
|
project.add_maintainer(user)
|
||||||
|
pipeline_schedule.update!(owner: user)
|
||||||
sign_in(user)
|
sign_in(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -352,22 +382,7 @@ RSpec.describe Projects::PipelineSchedulesController do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'security' do
|
it_behaves_like 'access update schedule'
|
||||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
|
||||||
expect { go }.to be_allowed_for(:admin)
|
|
||||||
end
|
|
||||||
it 'is denied for admin when admin mode disabled' do
|
|
||||||
expect { go }.to be_denied_for(:admin)
|
|
||||||
end
|
|
||||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
|
||||||
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
|
||||||
it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
|
|
||||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
|
||||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
|
||||||
it { expect { go }.to be_denied_for(:user) }
|
|
||||||
it { expect { go }.to be_denied_for(:external) }
|
|
||||||
it { expect { go }.to be_denied_for(:visitor) }
|
|
||||||
end
|
|
||||||
|
|
||||||
def go
|
def go
|
||||||
get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
get :edit, params: { namespace_id: project.namespace.to_param, project_id: project, id: pipeline_schedule.id }
|
||||||
|
@ -379,17 +394,30 @@ RSpec.describe Projects::PipelineSchedulesController do
|
||||||
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
it 'is allowed for admin when admin mode enabled', :enable_admin_mode do
|
||||||
expect { go }.to be_allowed_for(:admin)
|
expect { go }.to be_allowed_for(:admin)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'is denied for admin when admin mode disabled' do
|
it 'is denied for admin when admin mode disabled' do
|
||||||
expect { go }.to be_denied_for(:admin)
|
expect { go }.to be_denied_for(:admin)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
it { expect { go }.to be_allowed_for(:owner).of(project) }
|
||||||
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
it { expect { go }.to be_allowed_for(:maintainer).of(project) }
|
||||||
it { expect { go }.to be_allowed_for(:developer).of(project).own(pipeline_schedule) }
|
it { expect { go }.to be_denied_for(:developer).of(project) }
|
||||||
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
it { expect { go }.to be_denied_for(:reporter).of(project) }
|
||||||
it { expect { go }.to be_denied_for(:guest).of(project) }
|
it { expect { go }.to be_denied_for(:guest).of(project) }
|
||||||
it { expect { go }.to be_denied_for(:user) }
|
it { expect { go }.to be_denied_for(:user) }
|
||||||
it { expect { go }.to be_denied_for(:external) }
|
it { expect { go }.to be_denied_for(:external) }
|
||||||
it { expect { go }.to be_denied_for(:visitor) }
|
it { expect { go }.to be_denied_for(:visitor) }
|
||||||
|
|
||||||
|
context 'when user is schedule owner' do
|
||||||
|
it { expect { go }.to be_denied_for(:owner).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:maintainer).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:developer).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:reporter).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:guest).of(project).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:user).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:external).own(pipeline_schedule) }
|
||||||
|
it { expect { go }.to be_denied_for(:visitor).own(pipeline_schedule) }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def go
|
def go
|
||||||
|
|
|
@ -9,7 +9,77 @@ RSpec.describe 'Pipeline Schedules', :js do
|
||||||
let(:scope) { nil }
|
let(:scope) { nil }
|
||||||
let!(:user) { create(:user) }
|
let!(:user) { create(:user) }
|
||||||
|
|
||||||
context 'logged in as maintainer' do
|
context 'logged in as the pipeline scheduler owner' do
|
||||||
|
before do
|
||||||
|
stub_feature_flags(bootstrap_confirmation_modals: false)
|
||||||
|
project.add_developer(user)
|
||||||
|
pipeline_schedule.update!(owner: user)
|
||||||
|
gitlab_sign_in(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET /projects/pipeline_schedules' do
|
||||||
|
before do
|
||||||
|
visit_pipelines_schedules
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'edits the pipeline' do
|
||||||
|
page.within('.pipeline-schedule-table-row') do
|
||||||
|
click_link 'Edit'
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(page).to have_content('Edit Pipeline Schedule')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'PATCH /projects/pipelines_schedules/:id/edit' do
|
||||||
|
before do
|
||||||
|
edit_pipeline_schedule
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'displays existing properties' do
|
||||||
|
description = find_field('schedule_description').value
|
||||||
|
expect(description).to eq('pipeline schedule')
|
||||||
|
expect(page).to have_button('master')
|
||||||
|
expect(page).to have_button('UTC')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'edits the scheduled pipeline' do
|
||||||
|
fill_in 'schedule_description', with: 'my brand new description'
|
||||||
|
|
||||||
|
save_pipeline_schedule
|
||||||
|
|
||||||
|
expect(page).to have_content('my brand new description')
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ref is nil' do
|
||||||
|
before do
|
||||||
|
pipeline_schedule.update_attribute(:ref, nil)
|
||||||
|
edit_pipeline_schedule
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows the pipeline schedule with default ref' do
|
||||||
|
page.within('.js-target-branch-dropdown') do
|
||||||
|
expect(first('.dropdown-toggle-text').text).to eq('master')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when ref is empty' do
|
||||||
|
before do
|
||||||
|
pipeline_schedule.update_attribute(:ref, '')
|
||||||
|
edit_pipeline_schedule
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'shows the pipeline schedule with default ref' do
|
||||||
|
page.within('.js-target-branch-dropdown') do
|
||||||
|
expect(first('.dropdown-toggle-text').text).to eq('master')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'logged in as a project maintainer' do
|
||||||
before do
|
before do
|
||||||
stub_feature_flags(bootstrap_confirmation_modals: false)
|
stub_feature_flags(bootstrap_confirmation_modals: false)
|
||||||
project.add_maintainer(user)
|
project.add_maintainer(user)
|
||||||
|
@ -46,14 +116,6 @@ RSpec.describe 'Pipeline Schedules', :js do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'edits the pipeline' do
|
|
||||||
page.within('.pipeline-schedule-table-row') do
|
|
||||||
click_link 'Edit'
|
|
||||||
end
|
|
||||||
|
|
||||||
expect(page).to have_content('Edit Pipeline Schedule')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'deletes the pipeline' do
|
it 'deletes the pipeline' do
|
||||||
accept_confirm { click_link 'Delete' }
|
accept_confirm { click_link 'Delete' }
|
||||||
|
|
||||||
|
@ -108,53 +170,6 @@ RSpec.describe 'Pipeline Schedules', :js do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'PATCH /projects/pipelines_schedules/:id/edit' do
|
|
||||||
before do
|
|
||||||
edit_pipeline_schedule
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'displays existing properties' do
|
|
||||||
description = find_field('schedule_description').value
|
|
||||||
expect(description).to eq('pipeline schedule')
|
|
||||||
expect(page).to have_button('master')
|
|
||||||
expect(page).to have_button('UTC')
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'edits the scheduled pipeline' do
|
|
||||||
fill_in 'schedule_description', with: 'my brand new description'
|
|
||||||
|
|
||||||
save_pipeline_schedule
|
|
||||||
|
|
||||||
expect(page).to have_content('my brand new description')
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when ref is nil' do
|
|
||||||
before do
|
|
||||||
pipeline_schedule.update_attribute(:ref, nil)
|
|
||||||
edit_pipeline_schedule
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'shows the pipeline schedule with default ref' do
|
|
||||||
page.within('.js-target-branch-dropdown') do
|
|
||||||
expect(first('.dropdown-toggle-text').text).to eq('master')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when ref is empty' do
|
|
||||||
before do
|
|
||||||
pipeline_schedule.update_attribute(:ref, '')
|
|
||||||
edit_pipeline_schedule
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'shows the pipeline schedule with default ref' do
|
|
||||||
page.within('.js-target-branch-dropdown') do
|
|
||||||
expect(first('.dropdown-toggle-text').text).to eq('master')
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when user creates a new pipeline schedule with variables' do
|
context 'when user creates a new pipeline schedule with variables' do
|
||||||
before do
|
before do
|
||||||
visit_pipelines_schedules
|
visit_pipelines_schedules
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
"creator_id": 123,
|
"creator_id": 123,
|
||||||
"visibility_level": 10,
|
"visibility_level": 10,
|
||||||
"archived": false,
|
"archived": false,
|
||||||
|
"ci_config_path": "config/path",
|
||||||
"labels": [
|
"labels": [
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
|
|
|
@ -6,5 +6,6 @@
|
||||||
"archived": false,
|
"archived": false,
|
||||||
"deploy_keys": [],
|
"deploy_keys": [],
|
||||||
"hooks": [],
|
"hooks": [],
|
||||||
"shared_runners_enabled": true
|
"shared_runners_enabled": true,
|
||||||
|
"ci_config_path": "config/path"
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,6 +44,38 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#find_authenticated_requester' do
|
||||||
|
let_it_be(:api_user) { create(:user) }
|
||||||
|
let_it_be(:deploy_token_user) { create(:user) }
|
||||||
|
|
||||||
|
it 'returns the deploy token if it exists' do
|
||||||
|
allow_next_instance_of(described_class) do |authenticator|
|
||||||
|
expect(authenticator).to receive(:deploy_token_from_request).and_return(deploy_token_user)
|
||||||
|
allow(authenticator).to receive(:user).and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(subject.find_authenticated_requester([:api])).to eq deploy_token_user
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns the user id if it exists' do
|
||||||
|
allow_next_instance_of(described_class) do |authenticator|
|
||||||
|
allow(authenticator).to receive(:deploy_token_from_request).and_return(deploy_token_user)
|
||||||
|
expect(authenticator).to receive(:user).and_return(api_user)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(subject.find_authenticated_requester([:api])).to eq api_user
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'rerturns nil if no match is found' do
|
||||||
|
allow_next_instance_of(described_class) do |authenticator|
|
||||||
|
expect(authenticator).to receive(:deploy_token_from_request).and_return(nil)
|
||||||
|
expect(authenticator).to receive(:user).and_return(nil)
|
||||||
|
end
|
||||||
|
|
||||||
|
expect(subject.find_authenticated_requester([:api])).to eq nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#find_sessionless_user' do
|
describe '#find_sessionless_user' do
|
||||||
let_it_be(:dependency_proxy_user) { build(:user) }
|
let_it_be(:dependency_proxy_user) { build(:user) }
|
||||||
let_it_be(:access_token_user) { build(:user) }
|
let_it_be(:access_token_user) { build(:user) }
|
||||||
|
@ -348,10 +380,10 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
|
||||||
describe '#route_authentication_setting' do
|
describe '#route_authentication_setting' do
|
||||||
using RSpec::Parameterized::TableSyntax
|
using RSpec::Parameterized::TableSyntax
|
||||||
|
|
||||||
where(:script_name, :expected_job_token_allowed, :expected_basic_auth_personal_access_token) do
|
where(:script_name, :expected_job_token_allowed, :expected_basic_auth_personal_access_token, :expected_deploy_token_allowed) do
|
||||||
'/api/endpoint' | true | true
|
'/api/endpoint' | true | true | true
|
||||||
'/namespace/project.git' | false | true
|
'/namespace/project.git' | false | true | true
|
||||||
'/web/endpoint' | false | false
|
'/web/endpoint' | false | false | false
|
||||||
end
|
end
|
||||||
|
|
||||||
with_them do
|
with_them do
|
||||||
|
@ -362,7 +394,8 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
|
||||||
it 'returns correct settings' do
|
it 'returns correct settings' do
|
||||||
expect(subject.send(:route_authentication_setting)).to eql({
|
expect(subject.send(:route_authentication_setting)).to eql({
|
||||||
job_token_allowed: expected_job_token_allowed,
|
job_token_allowed: expected_job_token_allowed,
|
||||||
basic_auth_personal_access_token: expected_basic_auth_personal_access_token
|
basic_auth_personal_access_token: expected_basic_auth_personal_access_token,
|
||||||
|
deploy_token_allowed: expected_deploy_token_allowed
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,6 +22,19 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
|
||||||
let(:command) { double(save_incompleted: true) }
|
let(:command) { double(save_incompleted: true) }
|
||||||
let(:message) { 'message' }
|
let(:message) { 'message' }
|
||||||
|
|
||||||
|
describe '.warning' do
|
||||||
|
context 'when the warning includes malicious HTML' do
|
||||||
|
let(:message) { '<div>gimme your password</div>' }
|
||||||
|
let(:sanitized_message) { 'gimme your password' }
|
||||||
|
|
||||||
|
it 'sanitizes' do
|
||||||
|
subject.warning(message)
|
||||||
|
|
||||||
|
expect(pipeline.warning_messages[0].content).to include(sanitized_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '.error' do
|
describe '.error' do
|
||||||
shared_examples 'error function' do
|
shared_examples 'error function' do
|
||||||
specify do
|
specify do
|
||||||
|
@ -36,6 +49,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when the error includes malicious HTML' do
|
||||||
|
let(:message) { '<div>gimme your password</div>' }
|
||||||
|
let(:sanitized_message) { 'gimme your password' }
|
||||||
|
|
||||||
|
it 'sanitizes the error and removes the HTML tags' do
|
||||||
|
subject.error(message, config_error: true, drop_reason: :config_error)
|
||||||
|
|
||||||
|
expect(pipeline.yaml_errors).to eq(sanitized_message)
|
||||||
|
expect(pipeline.errors[:base]).to include(sanitized_message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when given a drop reason' do
|
context 'when given a drop reason' do
|
||||||
context 'when config error is true' do
|
context 'when config error is true' do
|
||||||
context 'sets the yaml error and overrides the drop reason' do
|
context 'sets the yaml error and overrides the drop reason' do
|
||||||
|
|
|
@ -25,13 +25,17 @@ RSpec.describe Gitlab::ConanToken do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '.from_personal_access_token' do
|
describe '.from_personal_access_token' do
|
||||||
it 'sets access token id and user id' do
|
it 'sets access token and user id and does not use the token id' do
|
||||||
access_token = double(id: 123, user_id: 456)
|
personal_access_token = double(id: 999, token: 123, user_id: 456)
|
||||||
|
|
||||||
token = described_class.from_personal_access_token(access_token)
|
token = described_class.from_personal_access_token(
|
||||||
|
personal_access_token.user_id,
|
||||||
|
personal_access_token.token
|
||||||
|
)
|
||||||
|
|
||||||
expect(token.access_token_id).to eq(123)
|
expect(token.access_token_id).not_to eq(personal_access_token.id)
|
||||||
expect(token.user_id).to eq(456)
|
expect(token.access_token_id).to eq(personal_access_token.token)
|
||||||
|
expect(token.user_id).to eq(personal_access_token.user_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -4,8 +4,9 @@ require 'spec_helper'
|
||||||
|
|
||||||
RSpec.describe Gitlab::GitAccessWiki do
|
RSpec.describe Gitlab::GitAccessWiki do
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
let_it_be(:project) { create(:project, :wiki_repo) }
|
let_it_be_with_reload(:project) { create(:project, :wiki_repo) }
|
||||||
let_it_be(:wiki) { create(:project_wiki, project: project) }
|
|
||||||
|
let(:wiki) { create(:project_wiki, project: project) }
|
||||||
|
|
||||||
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
|
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
|
||||||
let(:authentication_abilities) { %i[read_project download_code push_code] }
|
let(:authentication_abilities) { %i[read_project download_code push_code] }
|
||||||
|
@ -17,6 +18,61 @@ RSpec.describe Gitlab::GitAccessWiki do
|
||||||
redirected_path: redirected_path)
|
redirected_path: redirected_path)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'wiki access by level' do
|
||||||
|
where(:project_visibility, :project_member?, :wiki_access_level, :wiki_repo?, :expected_behavior) do
|
||||||
|
[
|
||||||
|
# Private project - is a project member
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, true, ProjectFeature::ENABLED, true, :no_error],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, true, ProjectFeature::PRIVATE, true, :no_error],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, true, ProjectFeature::DISABLED, true, :forbidden_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, true, ProjectFeature::ENABLED, false, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, true, ProjectFeature::DISABLED, false, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, true, ProjectFeature::PRIVATE, false, :not_found_wiki],
|
||||||
|
# Private project - is NOT a project member
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, false, ProjectFeature::ENABLED, true, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, false, ProjectFeature::PRIVATE, true, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, false, ProjectFeature::DISABLED, true, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, false, ProjectFeature::ENABLED, false, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, false, ProjectFeature::DISABLED, false, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PRIVATE, false, ProjectFeature::PRIVATE, false, :not_found_wiki],
|
||||||
|
# Public project - is a project member
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, true, ProjectFeature::ENABLED, true, :no_error],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, true, ProjectFeature::PRIVATE, true, :no_error],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, true, ProjectFeature::DISABLED, true, :forbidden_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, true, ProjectFeature::ENABLED, false, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, true, ProjectFeature::DISABLED, false, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, true, ProjectFeature::PRIVATE, false, :not_found_wiki],
|
||||||
|
# Public project - is NOT a project member
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, false, ProjectFeature::ENABLED, true, :no_error],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, false, ProjectFeature::PRIVATE, true, :forbidden_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, false, ProjectFeature::DISABLED, true, :forbidden_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, false, ProjectFeature::ENABLED, false, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, false, ProjectFeature::DISABLED, false, :not_found_wiki],
|
||||||
|
[Gitlab::VisibilityLevel::PUBLIC, false, ProjectFeature::PRIVATE, false, :not_found_wiki]
|
||||||
|
]
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
before do
|
||||||
|
project.update!(visibility_level: project_visibility)
|
||||||
|
project.add_developer(user) if project_member?
|
||||||
|
project.project_feature.update_attribute(:wiki_access_level, wiki_access_level)
|
||||||
|
allow(wiki.repository).to receive(:exists?).and_return(wiki_repo?)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'provides access by level' do
|
||||||
|
case expected_behavior
|
||||||
|
when :no_error
|
||||||
|
expect { subject }.not_to raise_error
|
||||||
|
when :forbidden_wiki
|
||||||
|
expect { subject }.to raise_wiki_forbidden
|
||||||
|
when :not_found_wiki
|
||||||
|
expect { subject }.to raise_wiki_not_found
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#push_access_check' do
|
describe '#push_access_check' do
|
||||||
subject { access.check('git-receive-pack', changes) }
|
subject { access.check('git-receive-pack', changes) }
|
||||||
|
|
||||||
|
@ -28,56 +84,26 @@ RSpec.describe Gitlab::GitAccessWiki do
|
||||||
it { expect { subject }.not_to raise_error }
|
it { expect { subject }.not_to raise_error }
|
||||||
|
|
||||||
context 'when in a read-only GitLab instance' do
|
context 'when in a read-only GitLab instance' do
|
||||||
let(:message) { "You can't push code to a read-only GitLab instance." }
|
|
||||||
|
|
||||||
before do
|
before do
|
||||||
allow(Gitlab::Database).to receive(:read_only?) { true }
|
allow(Gitlab::Database).to receive(:read_only?) { true }
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'forbidden git access'
|
it_behaves_like 'forbidden git access' do
|
||||||
|
let(:message) { "You can't push code to a read-only GitLab instance." }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'the user cannot :create_wiki' do
|
context 'the user cannot :create_wiki' do
|
||||||
it_behaves_like 'not-found git access' do
|
it { expect { subject }.to raise_wiki_not_found }
|
||||||
let(:message) { 'The wiki you were looking for could not be found.' }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#check_download_access!' do
|
describe '#check_download_access!' do
|
||||||
subject { access.check('git-upload-pack', Gitlab::GitAccess::ANY) }
|
subject { access.check('git-upload-pack', Gitlab::GitAccess::ANY) }
|
||||||
|
|
||||||
context 'the user can :download_wiki_code' do
|
context 'when actor is a user' do
|
||||||
before do
|
it_behaves_like 'wiki access by level'
|
||||||
project.add_developer(user)
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when wiki feature is disabled' do
|
|
||||||
before do
|
|
||||||
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'forbidden git access' do
|
|
||||||
let(:message) { include('wiki') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when the repository does not exist' do
|
|
||||||
before do
|
|
||||||
allow(wiki.repository).to receive(:exists?).and_return(false)
|
|
||||||
end
|
|
||||||
|
|
||||||
it_behaves_like 'not-found git access' do
|
|
||||||
let(:message) { include('for this wiki') }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'the user cannot :download_wiki_code' do
|
|
||||||
it_behaves_like 'not-found git access' do
|
|
||||||
let(:message) { include('wiki') }
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when the actor is a deploy token' do
|
context 'when the actor is a deploy token' do
|
||||||
|
@ -99,10 +125,40 @@ RSpec.describe Gitlab::GitAccessWiki do
|
||||||
context 'when the wiki is disabled' do
|
context 'when the wiki is disabled' do
|
||||||
let(:wiki_access_level) { ProjectFeature::DISABLED }
|
let(:wiki_access_level) { ProjectFeature::DISABLED }
|
||||||
|
|
||||||
it_behaves_like 'forbidden git access' do
|
it { expect { subject }.to raise_wiki_forbidden }
|
||||||
let(:message) { 'You are not allowed to download files from this wiki.' }
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'when actor is a user provided by build via CI_JOB_TOKEN' do
|
||||||
|
let(:protocol) { 'http' }
|
||||||
|
let(:authentication_abilities) { [:build_download_code] }
|
||||||
|
let(:auth_result_type) { :build }
|
||||||
|
|
||||||
|
before do
|
||||||
|
project.project_feature.update_attribute(:wiki_access_level, wiki_access_level)
|
||||||
|
end
|
||||||
|
|
||||||
|
subject { access.check('git-upload-pack', changes) }
|
||||||
|
|
||||||
|
it_behaves_like 'wiki access by level'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Matchers.define :raise_wiki_not_found do
|
||||||
|
match do |actual|
|
||||||
|
expect { actual.call }.to raise_error(Gitlab::GitAccess::NotFoundError, include('wiki'))
|
||||||
|
end
|
||||||
|
def supports_block_expectations?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec::Matchers.define :raise_wiki_forbidden do
|
||||||
|
match do |actual|
|
||||||
|
expect { subject }.to raise_error(Gitlab::GitAccess::ForbiddenError, include('wiki'))
|
||||||
|
end
|
||||||
|
def supports_block_expectations?
|
||||||
|
true
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -401,4 +401,22 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_
|
||||||
expect(created_object.value).to be_nil
|
expect(created_object.value).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'merge request access level object' do
|
||||||
|
let(:relation_sym) { :'ProtectedBranch::MergeAccessLevel' }
|
||||||
|
let(:relation_hash) { { 'access_level' => 30, 'created_at' => '2022-03-29T09:53:13.457Z', 'updated_at' => '2022-03-29T09:54:13.457Z' } }
|
||||||
|
|
||||||
|
it 'sets access level to maintainer' do
|
||||||
|
expect(created_object.access_level).to equal(Gitlab::Access::MAINTAINER)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'push access level object' do
|
||||||
|
let(:relation_sym) { :'ProtectedBranch::PushAccessLevel' }
|
||||||
|
let(:relation_hash) { { 'access_level' => 30, 'created_at' => '2022-03-29T09:53:13.457Z', 'updated_at' => '2022-03-29T09:54:13.457Z' } }
|
||||||
|
|
||||||
|
it 'sets access level to maintainer' do
|
||||||
|
expect(created_object.access_level).to equal(Gitlab::Access::MAINTAINER)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -111,6 +111,10 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not import ci config path' do
|
||||||
|
expect(@project.ci_config_path).to be_nil
|
||||||
|
end
|
||||||
|
|
||||||
it 'creates a valid pipeline note' do
|
it 'creates a valid pipeline note' do
|
||||||
expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty
|
expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty
|
||||||
end
|
end
|
||||||
|
|
|
@ -91,7 +91,8 @@ RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when matched throttle requires user information' do
|
context 'matching user or deploy token authenticated information' do
|
||||||
|
context 'when matching for user' do
|
||||||
context 'when user not found' do
|
context 'when user not found' do
|
||||||
let(:event) do
|
let(:event) do
|
||||||
ActiveSupport::Notifications::Event.new(
|
ActiveSupport::Notifications::Event.new(
|
||||||
|
@ -103,7 +104,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
|
||||||
env: {
|
env: {
|
||||||
'rack.attack.match_type' => match_type,
|
'rack.attack.match_type' => match_type,
|
||||||
'rack.attack.matched' => 'throttle_authenticated_api',
|
'rack.attack.matched' => 'throttle_authenticated_api',
|
||||||
'rack.attack.match_discriminator' => 'not_exist_user_id'
|
'rack.attack.match_discriminator' => "user:#{non_existing_record_id}"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -118,7 +119,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
|
||||||
request_method: 'GET',
|
request_method: 'GET',
|
||||||
path: '/api/v4/internal/authorized_keys',
|
path: '/api/v4/internal/authorized_keys',
|
||||||
matched: 'throttle_authenticated_api',
|
matched: 'throttle_authenticated_api',
|
||||||
user_id: 'not_exist_user_id'
|
user_id: non_existing_record_id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
subscriber.send(match_type, event)
|
subscriber.send(match_type, event)
|
||||||
|
@ -137,7 +138,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
|
||||||
env: {
|
env: {
|
||||||
'rack.attack.match_type' => match_type,
|
'rack.attack.match_type' => match_type,
|
||||||
'rack.attack.matched' => 'throttle_authenticated_api',
|
'rack.attack.matched' => 'throttle_authenticated_api',
|
||||||
'rack.attack.match_discriminator' => user.id
|
'rack.attack.match_discriminator' => "user:#{user.id}"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -160,6 +161,43 @@ RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when matching for deploy token' do
|
||||||
|
context 'when deploy token found' do
|
||||||
|
let(:deploy_token) { create(:deploy_token) }
|
||||||
|
let(:event) do
|
||||||
|
ActiveSupport::Notifications::Event.new(
|
||||||
|
event_name, Time.current, Time.current + 2.seconds, '1', request: double(
|
||||||
|
:request,
|
||||||
|
ip: '1.2.3.4',
|
||||||
|
request_method: 'GET',
|
||||||
|
fullpath: '/api/v4/internal/authorized_keys',
|
||||||
|
env: {
|
||||||
|
'rack.attack.match_type' => match_type,
|
||||||
|
'rack.attack.matched' => 'throttle_authenticated_api',
|
||||||
|
'rack.attack.match_discriminator' => "deploy_token:#{deploy_token.id}"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'logs request information and user meta' do
|
||||||
|
expect(Gitlab::AuthLogger).to receive(:error).with(
|
||||||
|
include(
|
||||||
|
message: 'Rack_Attack',
|
||||||
|
env: match_type,
|
||||||
|
remote_ip: '1.2.3.4',
|
||||||
|
request_method: 'GET',
|
||||||
|
path: '/api/v4/internal/authorized_keys',
|
||||||
|
matched: 'throttle_authenticated_api',
|
||||||
|
deploy_token_id: deploy_token.id
|
||||||
|
)
|
||||||
|
)
|
||||||
|
subscriber.send(match_type, event)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe '#throttle' do
|
describe '#throttle' do
|
||||||
|
|
|
@ -990,4 +990,19 @@ RSpec.describe Gitlab::Regex do
|
||||||
it { is_expected.not_to match('../../../../../1.2.3') }
|
it { is_expected.not_to match('../../../../../1.2.3') }
|
||||||
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
|
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '.sha256_regex' do
|
||||||
|
subject { described_class.sha256_regex }
|
||||||
|
|
||||||
|
it { is_expected.to match('a' * 64) }
|
||||||
|
it { is_expected.to match('abcdefABCDEF1234567890abcdefABCDEF1234567890abcdefABCDEF12345678') }
|
||||||
|
it { is_expected.not_to match('a' * 63) }
|
||||||
|
it { is_expected.not_to match('a' * 65) }
|
||||||
|
it { is_expected.not_to match('a' * 63 + 'g') }
|
||||||
|
it { is_expected.not_to match('a' * 63 + '{') }
|
||||||
|
it { is_expected.not_to match('a' * 63 + '%') }
|
||||||
|
it { is_expected.not_to match('a' * 63 + '*') }
|
||||||
|
it { is_expected.not_to match('a' * 63 + '#') }
|
||||||
|
it { is_expected.not_to match('') }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1048,7 +1048,27 @@ RSpec.describe Ci::Build do
|
||||||
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
|
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to match([a_hash_including(key: "key-1"), a_hash_including(key: "key2-1")]) }
|
it { is_expected.to match([a_hash_including(key: 'key-1-non_protected'), a_hash_including(key: 'key2-1-non_protected')]) }
|
||||||
|
|
||||||
|
context 'when pipeline is on a protected ref' do
|
||||||
|
before do
|
||||||
|
allow(build.pipeline).to receive(:protected_ref?).and_return(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
is_expected.to all(a_hash_including(key: a_string_matching(/-protected$/)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when pipeline is not on a protected ref' do
|
||||||
|
before do
|
||||||
|
allow(build.pipeline).to receive(:protected_ref?).and_return(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it do
|
||||||
|
is_expected.to all(a_hash_including(key: a_string_matching(/-non_protected$/)))
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when project has jobs_cache_index' do
|
context 'when project has jobs_cache_index' do
|
||||||
|
@ -1056,7 +1076,7 @@ RSpec.describe Ci::Build do
|
||||||
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
|
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to be_an(Array).and all(include(key: "key-1")) }
|
it { is_expected.to be_an(Array).and all(include(key: a_string_matching(/^key-1-(?>protected|non_protected)/))) }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when project does not have jobs_cache_index' do
|
context 'when project does not have jobs_cache_index' do
|
||||||
|
@ -1064,7 +1084,9 @@ RSpec.describe Ci::Build do
|
||||||
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil)
|
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
it { is_expected.to eq(options[:cache]) }
|
it do
|
||||||
|
is_expected.to eq(options[:cache].map { |entry| entry.merge(key: "#{entry[:key]}-non_protected") })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -618,6 +618,7 @@ RSpec.describe CommitStatus do
|
||||||
'rspec:windows 10000 20000' | 'rspec:windows'
|
'rspec:windows 10000 20000' | 'rspec:windows'
|
||||||
'rspec:windows 0 : / 1' | 'rspec:windows'
|
'rspec:windows 0 : / 1' | 'rspec:windows'
|
||||||
'rspec:windows 0 : / 1 name' | 'rspec:windows 0 : / 1 name'
|
'rspec:windows 0 : / 1 name' | 'rspec:windows 0 : / 1 name'
|
||||||
|
'rspec [inception: [something, other thing], value]' | 'rspec'
|
||||||
'0 1 name ruby' | '0 1 name ruby'
|
'0 1 name ruby' | '0 1 name ruby'
|
||||||
'0 :/ 1 name ruby' | '0 :/ 1 name ruby'
|
'0 :/ 1 name ruby' | '0 :/ 1 name ruby'
|
||||||
'rspec: [aws]' | 'rspec'
|
'rspec: [aws]' | 'rspec'
|
||||||
|
|
37
spec/models/integrations/every_integration_spec.rb
Normal file
37
spec/models/integrations/every_integration_spec.rb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
RSpec.describe 'Every integration' do
|
||||||
|
all_integration_names = Integration.available_integration_names
|
||||||
|
|
||||||
|
all_integration_names.each do |integration_name|
|
||||||
|
describe integration_name do
|
||||||
|
let(:integration_class) { Integration.integration_name_to_model(integration_name) }
|
||||||
|
let(:integration) { integration_class.new }
|
||||||
|
let(:secret_name_pattern) { %r/token|key|password|passphrase|secret/.freeze }
|
||||||
|
|
||||||
|
context 'secret fields', :aggregate_failures do
|
||||||
|
it "uses type: 'password' for all secret fields" do
|
||||||
|
integration.fields.each do |field|
|
||||||
|
next unless secret_name_pattern.match?(field[:name])
|
||||||
|
|
||||||
|
expect(field[:type]).to eq('password'),
|
||||||
|
"Field '#{field[:name]}' should use type 'password'"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'defines non-empty titles and help texts for all secret fields' do
|
||||||
|
integration.fields.each do |field|
|
||||||
|
next unless field[:type] == 'password'
|
||||||
|
|
||||||
|
expect(field[:non_empty_password_title]).to be_present,
|
||||||
|
"Field '#{field[:name]}' should define :non_empty_password_title"
|
||||||
|
expect(field[:non_empty_password_help]).to be_present,
|
||||||
|
"Field '#{field[:name]}' should define :non_empty_password_help"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -724,14 +724,15 @@ RSpec.describe Issue do
|
||||||
|
|
||||||
describe '#participants' do
|
describe '#participants' do
|
||||||
context 'using a public project' do
|
context 'using a public project' do
|
||||||
let_it_be(:issue) { create(:issue, project: reusable_project) }
|
let_it_be(:public_project) { create(:project, :public) }
|
||||||
|
let_it_be(:issue) { create(:issue, project: public_project) }
|
||||||
|
|
||||||
let!(:note1) do
|
let!(:note1) do
|
||||||
create(:note_on_issue, noteable: issue, project: reusable_project, note: 'a')
|
create(:note_on_issue, noteable: issue, project: public_project, note: 'a')
|
||||||
end
|
end
|
||||||
|
|
||||||
let!(:note2) do
|
let!(:note2) do
|
||||||
create(:note_on_issue, noteable: issue, project: reusable_project, note: 'b')
|
create(:note_on_issue, noteable: issue, project: public_project, note: 'b')
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes the issue author' do
|
it 'includes the issue author' do
|
||||||
|
@ -801,6 +802,7 @@ RSpec.describe Issue do
|
||||||
context 'without a user' do
|
context 'without a user' do
|
||||||
let(:user) { nil }
|
let(:user) { nil }
|
||||||
|
|
||||||
|
context 'with issue available as public' do
|
||||||
before do
|
before do
|
||||||
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PUBLIC)
|
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PUBLIC)
|
||||||
end
|
end
|
||||||
|
@ -818,6 +820,20 @@ RSpec.describe Issue do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with issues available only to team members in a public project' do
|
||||||
|
let(:public_project) { create(:project, :public) }
|
||||||
|
let(:issue) { build(:issue, project: public_project) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
public_project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns false' do
|
||||||
|
is_expected.to be_falsey
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'with a user' do
|
context 'with a user' do
|
||||||
shared_examples 'issue readable by user' do
|
shared_examples 'issue readable by user' do
|
||||||
it { is_expected.to eq(true) }
|
it { is_expected.to eq(true) }
|
||||||
|
|
|
@ -23,6 +23,41 @@ RSpec.describe Packages::PackageFile, type: :model do
|
||||||
|
|
||||||
describe 'validations' do
|
describe 'validations' do
|
||||||
it { is_expected.to validate_presence_of(:package) }
|
it { is_expected.to validate_presence_of(:package) }
|
||||||
|
|
||||||
|
context 'with pypi package' do
|
||||||
|
let_it_be(:package) { create(:pypi_package) }
|
||||||
|
|
||||||
|
let(:package_file) { package.package_files.first }
|
||||||
|
let(:status) { :default }
|
||||||
|
let(:file_name) { 'foo' }
|
||||||
|
let(:file) { fixture_file_upload('spec/fixtures/dk.png') }
|
||||||
|
let(:params) { { file: file, file_name: file_name, status: status } }
|
||||||
|
|
||||||
|
subject { package.package_files.create!(params) }
|
||||||
|
|
||||||
|
context 'file_sha256' do
|
||||||
|
where(:sha256_value, :expected_success) do
|
||||||
|
'a' * 64 | true
|
||||||
|
nil | true
|
||||||
|
'a' * 63 | false
|
||||||
|
'a' * 65 | false
|
||||||
|
'a' * 63 + '%' | false
|
||||||
|
'' | false
|
||||||
|
end
|
||||||
|
|
||||||
|
with_them do
|
||||||
|
let(:params) { super().merge({ file_sha256: sha256_value }) }
|
||||||
|
|
||||||
|
it 'does not allow invalid sha256 characters' do
|
||||||
|
if expected_success
|
||||||
|
expect { subject }.not_to raise_error
|
||||||
|
else
|
||||||
|
expect { subject }.to raise_error(ActiveRecord::RecordInvalid, "Validation failed: File sha256 is invalid")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with package filenames' do
|
context 'with package filenames' do
|
||||||
|
|
|
@ -84,11 +84,14 @@ RSpec.describe Ci::PipelineSchedulePolicy, :models do
|
||||||
project.add_maintainer(user)
|
project.add_maintainer(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'includes abilities to do all operations on pipeline schedule' do
|
it 'allows for playing and destroying a pipeline schedule' do
|
||||||
expect(policy).to be_allowed :play_pipeline_schedule
|
expect(policy).to be_allowed :play_pipeline_schedule
|
||||||
expect(policy).to be_allowed :update_pipeline_schedule
|
|
||||||
expect(policy).to be_allowed :admin_pipeline_schedule
|
expect(policy).to be_allowed :admin_pipeline_schedule
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not allow for updating of an existing schedule' do
|
||||||
|
expect(policy).not_to be_allowed :update_pipeline_schedule
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'rules for non-owner of schedule' do
|
describe 'rules for non-owner of schedule' do
|
||||||
|
|
|
@ -291,12 +291,34 @@ RSpec.describe API::Ci::PipelineSchedules do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'authenticated user with invalid permissions' do
|
context 'authenticated user with invalid permissions' do
|
||||||
|
context 'as a project maintainer' do
|
||||||
|
before do
|
||||||
|
project.add_maintainer(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not update pipeline_schedule' do
|
||||||
|
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as a project owner' do
|
||||||
|
it 'does not update pipeline_schedule' do
|
||||||
|
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", project.owner)
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'with no special role' do
|
||||||
it 'does not update pipeline_schedule' do
|
it 'does not update pipeline_schedule' do
|
||||||
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
|
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:not_found)
|
expect(response).to have_gitlab_http_status(:not_found)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'unauthenticated user' do
|
context 'unauthenticated user' do
|
||||||
it 'does not update pipeline_schedule' do
|
it 'does not update pipeline_schedule' do
|
||||||
|
@ -312,16 +334,21 @@ RSpec.describe API::Ci::PipelineSchedules do
|
||||||
create(:ci_pipeline_schedule, project: project, owner: developer)
|
create(:ci_pipeline_schedule, project: project, owner: developer)
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'authenticated user with valid permissions' do
|
let(:project_maintainer) do
|
||||||
|
create(:user).tap { |u| project.add_maintainer(u) }
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'as an authenticated user with valid permissions' do
|
||||||
it 'updates owner' do
|
it 'updates owner' do
|
||||||
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer)
|
expect { post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", project_maintainer) }
|
||||||
|
.to change { pipeline_schedule.reload.owner }.from(developer).to(project_maintainer)
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:created)
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
expect(response).to match_response_schema('pipeline_schedule')
|
expect(response).to match_response_schema('pipeline_schedule')
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'authenticated user with invalid permissions' do
|
context 'as an authenticated user with invalid permissions' do
|
||||||
it 'does not update owner' do
|
it 'does not update owner' do
|
||||||
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
|
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", user)
|
||||||
|
|
||||||
|
@ -329,13 +356,23 @@ RSpec.describe API::Ci::PipelineSchedules do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'unauthenticated user' do
|
context 'as an unauthenticated user' do
|
||||||
it 'does not update owner' do
|
it 'does not update owner' do
|
||||||
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
|
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'as the existing owner of the schedule' do
|
||||||
|
it 'rejects the request and leaves the schedule unchanged' do
|
||||||
|
expect do
|
||||||
|
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership", developer)
|
||||||
|
end.not_to change { pipeline_schedule.reload.owner }
|
||||||
|
|
||||||
|
expect(response).to have_gitlab_http_status(:forbidden)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
|
describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do
|
||||||
|
|
|
@ -191,7 +191,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:expected_cache) do
|
let(:expected_cache) do
|
||||||
[{ 'key' => 'cache_key',
|
[{ 'key' => a_string_matching(/^cache_key-(?>protected|non_protected)$/),
|
||||||
'untracked' => false,
|
'untracked' => false,
|
||||||
'paths' => ['vendor/*'],
|
'paths' => ['vendor/*'],
|
||||||
'policy' => 'pull-push',
|
'policy' => 'pull-push',
|
||||||
|
@ -225,7 +225,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
|
||||||
'alias' => nil, 'command' => nil, 'ports' => [], 'variables' => [{ 'key' => 'MYSQL_ROOT_PASSWORD', 'value' => 'root123.' }] }])
|
'alias' => nil, 'command' => nil, 'ports' => [], 'variables' => [{ 'key' => 'MYSQL_ROOT_PASSWORD', 'value' => 'root123.' }] }])
|
||||||
expect(json_response['steps']).to eq(expected_steps)
|
expect(json_response['steps']).to eq(expected_steps)
|
||||||
expect(json_response['artifacts']).to eq(expected_artifacts)
|
expect(json_response['artifacts']).to eq(expected_artifacts)
|
||||||
expect(json_response['cache']).to eq(expected_cache)
|
expect(json_response['cache']).to match(expected_cache)
|
||||||
expect(json_response['variables']).to include(*expected_variables)
|
expect(json_response['variables']).to include(*expected_variables)
|
||||||
expect(json_response['features']).to match(expected_features)
|
expect(json_response['features']).to match(expected_features)
|
||||||
end
|
end
|
||||||
|
|
|
@ -156,6 +156,46 @@ RSpec.describe API::Markdown do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with a public project and issues only for team members' do
|
||||||
|
let(:public_project) do
|
||||||
|
create(:project, :public).tap do |project|
|
||||||
|
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PRIVATE)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:issue) { create(:issue, project: public_project, title: 'Team only title') }
|
||||||
|
let(:text) { "#{issue.to_reference}" }
|
||||||
|
let(:params) { { text: text, gfm: true, project: public_project.full_path } }
|
||||||
|
|
||||||
|
shared_examples 'user without proper access' do
|
||||||
|
it 'does not render the title' do
|
||||||
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
|
expect(json_response["html"]).not_to include('Team only title')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when not logged in' do
|
||||||
|
let(:user) { }
|
||||||
|
|
||||||
|
it_behaves_like 'user without proper access'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when logged in as user without access' do
|
||||||
|
let(:user) { create(:user) }
|
||||||
|
|
||||||
|
it_behaves_like 'user without proper access'
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when logged in as author' do
|
||||||
|
let(:user) { issue.author }
|
||||||
|
|
||||||
|
it 'renders the title or link' do
|
||||||
|
expect(response).to have_gitlab_http_status(:created)
|
||||||
|
expect(json_response["html"]).to include('Team only title')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -136,7 +136,7 @@ RSpec.describe API::PypiPackages do
|
||||||
let(:url) { "/projects/#{project.id}/packages/pypi" }
|
let(:url) { "/projects/#{project.id}/packages/pypi" }
|
||||||
let(:headers) { {} }
|
let(:headers) { {} }
|
||||||
let(:requires_python) { '>=3.7' }
|
let(:requires_python) { '>=3.7' }
|
||||||
let(:base_params) { { requires_python: requires_python, version: '1.0.0', name: 'sample-project', sha256_digest: '123' } }
|
let(:base_params) { { requires_python: requires_python, version: '1.0.0', name: 'sample-project', sha256_digest: '1' * 64 } }
|
||||||
let(:params) { base_params.merge(content: temp_file(file_name)) }
|
let(:params) { base_params.merge(content: temp_file(file_name)) }
|
||||||
let(:send_rewritten_field) { true }
|
let(:send_rewritten_field) { true }
|
||||||
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
|
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
|
||||||
|
@ -213,6 +213,19 @@ RSpec.describe API::PypiPackages do
|
||||||
it_behaves_like 'returning response status', :bad_request
|
it_behaves_like 'returning response status', :bad_request
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'with an invalid sha256' do
|
||||||
|
let(:token) { personal_access_token.token }
|
||||||
|
let(:user_headers) { basic_auth_header(user.username, token) }
|
||||||
|
let(:headers) { user_headers.merge(workhorse_headers) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
params[:sha256_digest] = 'a' * 63 + '%'
|
||||||
|
project.add_developer(user)
|
||||||
|
end
|
||||||
|
|
||||||
|
it_behaves_like 'returning response status', :bad_request
|
||||||
|
end
|
||||||
|
|
||||||
it_behaves_like 'deploy token for package uploads'
|
it_behaves_like 'deploy token for package uploads'
|
||||||
|
|
||||||
it_behaves_like 'job token for package uploads'
|
it_behaves_like 'job token for package uploads'
|
||||||
|
|
|
@ -93,28 +93,28 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { [api(api_partial_url, personal_access_token: token), {}] }
|
let(:request_args) { [api(api_partial_url, personal_access_token: token), {}] }
|
||||||
let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token), {}] }
|
let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token), {}] }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in the headers' do
|
context 'with the token in the headers' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in the OAuth headers' do
|
context 'with the token in the OAuth headers' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in basic auth' do
|
context 'with the token in basic auth' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, basic_auth_headers(user, token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, basic_auth_headers(user, token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, basic_auth_headers(other_user, other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, basic_auth_headers(other_user, other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a read_api scope' do
|
context 'with a read_api scope' do
|
||||||
|
@ -127,14 +127,14 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in the OAuth headers' do
|
context 'with the token in the OAuth headers' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -155,14 +155,14 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { [api(api_partial_url, oauth_access_token: token), {}] }
|
let(:request_args) { [api(api_partial_url, oauth_access_token: token), {}] }
|
||||||
let(:other_user_request_args) { [api(api_partial_url, oauth_access_token: other_user_token), {}] }
|
let(:other_user_request_args) { [api(api_partial_url, oauth_access_token: other_user_token), {}] }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in the headers' do
|
context 'with the token in the headers' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with a read_api scope' do
|
context 'with a read_api scope' do
|
||||||
|
@ -171,7 +171,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(read_token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(read_token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_read_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, oauth_token_headers(other_user_read_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -184,7 +184,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { [rss_url(user), params: nil] }
|
let(:request_args) { [rss_url(user), params: nil] }
|
||||||
let(:other_user_request_args) { [rss_url(other_user), params: nil] }
|
let(:other_user_request_args) { [rss_url(other_user), params: nil] }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -288,14 +288,14 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { [api(api_partial_url, personal_access_token: token), {}] }
|
let(:request_args) { [api(api_partial_url, personal_access_token: token), {}] }
|
||||||
let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token), {}] }
|
let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token), {}] }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in the headers' do
|
context 'with the token in the headers' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -444,14 +444,14 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { [api(api_partial_url, personal_access_token: token), {}] }
|
let(:request_args) { [api(api_partial_url, personal_access_token: token), {}] }
|
||||||
let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token), {}] }
|
let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token), {}] }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in the headers' do
|
context 'with the token in the headers' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'precedence over authenticated api throttle' do
|
context 'precedence over authenticated api throttle' do
|
||||||
|
@ -512,6 +512,16 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'authenticated via deploy token headers' do
|
||||||
|
let(:deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true, projects: [project]) }
|
||||||
|
let(:other_deploy_token) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
|
||||||
|
|
||||||
|
let(:request_args) { [api(api_partial_url), { headers: deploy_token_headers(deploy_token) }] }
|
||||||
|
let(:other_user_request_args) { [api(api_partial_url), { headers: deploy_token_headers(other_deploy_token) }] }
|
||||||
|
|
||||||
|
it_behaves_like 'rate-limited deploy-token-authenticated requests'
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -558,7 +568,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'getting a blob' do
|
context 'getting a blob' do
|
||||||
|
@ -568,7 +578,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:path) { "/v2/#{blob.group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
|
let(:path) { "/v2/#{blob.group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
|
||||||
let(:other_path) { "/v2/#{other_blob.group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
|
let(:other_path) { "/v2/#{other_blob.group.path}/dependency_proxy/containers/alpine/blobs/sha256:a0d0a0d46f8b52473982a3c466318f479767577551a53ffc9074c9fa7035982e" }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -598,7 +608,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { [git_lfs_url, { headers: basic_auth_headers(user, token) }] }
|
let(:request_args) { [git_lfs_url, { headers: basic_auth_headers(user, token) }] }
|
||||||
let(:other_user_request_args) { [git_lfs_url, { headers: basic_auth_headers(other_user, other_user_token) }] }
|
let(:other_user_request_args) { [git_lfs_url, { headers: basic_auth_headers(other_user, other_user_token) }] }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'precedence over authenticated web throttle' do
|
context 'precedence over authenticated web throttle' do
|
||||||
|
@ -786,14 +796,14 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { [api(api_partial_url, personal_access_token: token), {}] }
|
let(:request_args) { [api(api_partial_url, personal_access_token: token), {}] }
|
||||||
let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token), {}] }
|
let(:other_user_request_args) { [api(api_partial_url, personal_access_token: other_user_token), {}] }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in the headers' do
|
context 'with the token in the headers' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'precedence over authenticated api throttle' do
|
context 'precedence over authenticated api throttle' do
|
||||||
|
@ -993,14 +1003,14 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
|
||||||
let(:request_args) { [api(path, personal_access_token: token), {}] }
|
let(:request_args) { [api(path, personal_access_token: token), {}] }
|
||||||
let(:other_user_request_args) { [api(path, personal_access_token: other_user_token), {}] }
|
let(:other_user_request_args) { [api(path, personal_access_token: other_user_token), {}] }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'with the token in the headers' do
|
context 'with the token in the headers' do
|
||||||
let(:request_args) { api_get_args_with_token_headers(path, personal_access_token_headers(token)) }
|
let(:request_args) { api_get_args_with_token_headers(path, personal_access_token_headers(token)) }
|
||||||
let(:other_user_request_args) { api_get_args_with_token_headers(path, personal_access_token_headers(other_user_token)) }
|
let(:other_user_request_args) { api_get_args_with_token_headers(path, personal_access_token_headers(other_user_token)) }
|
||||||
|
|
||||||
it_behaves_like 'rate-limited token-authenticated requests'
|
it_behaves_like 'rate-limited user based token-authenticated requests'
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'precedence over authenticated api throttle' do
|
context 'precedence over authenticated api throttle' do
|
||||||
|
|
|
@ -33,7 +33,7 @@ RSpec.describe Ci::CreatePipelineService do
|
||||||
|
|
||||||
it 'uses the provided key' do
|
it 'uses the provided key' do
|
||||||
expected = {
|
expected = {
|
||||||
key: 'a-key',
|
key: a_string_matching(/^a-key-(?>protected|non_protected)$/),
|
||||||
paths: ['logs/', 'binaries/'],
|
paths: ['logs/', 'binaries/'],
|
||||||
policy: 'pull-push',
|
policy: 'pull-push',
|
||||||
untracked: true,
|
untracked: true,
|
||||||
|
|
|
@ -7,6 +7,9 @@ RSpec.describe Packages::Pypi::CreatePackageService do
|
||||||
let_it_be(:project) { create(:project) }
|
let_it_be(:project) { create(:project) }
|
||||||
let_it_be(:user) { create(:user) }
|
let_it_be(:user) { create(:user) }
|
||||||
|
|
||||||
|
let(:sha256) { '1' * 64 }
|
||||||
|
let(:md5) { '567' }
|
||||||
|
|
||||||
let(:requires_python) { '>=2.7' }
|
let(:requires_python) { '>=2.7' }
|
||||||
let(:params) do
|
let(:params) do
|
||||||
{
|
{
|
||||||
|
@ -14,8 +17,8 @@ RSpec.describe Packages::Pypi::CreatePackageService do
|
||||||
version: '1.0',
|
version: '1.0',
|
||||||
content: temp_file('foo.tgz'),
|
content: temp_file('foo.tgz'),
|
||||||
requires_python: requires_python,
|
requires_python: requires_python,
|
||||||
sha256_digest: '123',
|
sha256_digest: sha256,
|
||||||
md5_digest: '567'
|
md5_digest: md5
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -34,8 +37,8 @@ RSpec.describe Packages::Pypi::CreatePackageService do
|
||||||
expect(created_package.pypi_metadatum.required_python).to eq '>=2.7'
|
expect(created_package.pypi_metadatum.required_python).to eq '>=2.7'
|
||||||
expect(created_package.package_files.size).to eq 1
|
expect(created_package.package_files.size).to eq 1
|
||||||
expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
|
expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
|
||||||
expect(created_package.package_files.first.file_sha256).to eq '123'
|
expect(created_package.package_files.first.file_sha256).to eq sha256
|
||||||
expect(created_package.package_files.first.file_md5).to eq '567'
|
expect(created_package.package_files.first.file_md5).to eq md5
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -62,8 +65,8 @@ RSpec.describe Packages::Pypi::CreatePackageService do
|
||||||
context 'with an existing file' do
|
context 'with an existing file' do
|
||||||
before do
|
before do
|
||||||
params[:content] = temp_file('foo.tgz')
|
params[:content] = temp_file('foo.tgz')
|
||||||
params[:sha256_digest] = 'abc'
|
params[:sha256_digest] = sha256
|
||||||
params[:md5_digest] = 'def'
|
params[:md5_digest] = md5
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'throws an error' do
|
it 'throws an error' do
|
||||||
|
@ -89,8 +92,8 @@ RSpec.describe Packages::Pypi::CreatePackageService do
|
||||||
expect(created_package.pypi_metadatum.required_python).to eq '>=2.7'
|
expect(created_package.pypi_metadatum.required_python).to eq '>=2.7'
|
||||||
expect(created_package.package_files.size).to eq 1
|
expect(created_package.package_files.size).to eq 1
|
||||||
expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
|
expect(created_package.package_files.first.file_name).to eq 'foo.tgz'
|
||||||
expect(created_package.package_files.first.file_sha256).to eq 'abc'
|
expect(created_package.package_files.first.file_sha256).to eq sha256
|
||||||
expect(created_package.package_files.first.file_md5).to eq 'def'
|
expect(created_package.package_files.first.file_md5).to eq md5
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -391,6 +391,7 @@ RSpec.describe TodoService do
|
||||||
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
|
let!(:second_todo) { create(:todo, :assigned, user: john_doe, project: project, target: issue, author: author) }
|
||||||
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
|
let(:confidential_issue) { create(:issue, :confidential, project: project, author: author, assignees: [assignee]) }
|
||||||
let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
|
let(:note) { create(:note, project: project, noteable: issue, author: john_doe, note: mentions) }
|
||||||
|
let(:confidential_note) { create(:note, :confidential, project: project, noteable: issue, author: john_doe, note: mentions) }
|
||||||
let(:addressed_note) { create(:note, project: project, noteable: issue, author: john_doe, note: directly_addressed) }
|
let(:addressed_note) { create(:note, project: project, noteable: issue, author: john_doe, note: directly_addressed) }
|
||||||
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
|
let(:note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: mentions) }
|
||||||
let(:addressed_note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: directly_addressed) }
|
let(:addressed_note_on_commit) { create(:note_on_commit, project: project, author: john_doe, note: directly_addressed) }
|
||||||
|
@ -468,6 +469,17 @@ RSpec.describe TodoService do
|
||||||
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
|
should_create_todo(user: john_doe, target: confidential_issue, author: john_doe, action: Todo::DIRECTLY_ADDRESSED, note: addressed_note_on_confidential_issue)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'does not create todo if user can not read confidential note' do
|
||||||
|
service.new_note(confidential_note, john_doe)
|
||||||
|
|
||||||
|
should_not_create_todo(user: non_member, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
|
||||||
|
should_not_create_todo(user: guest, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
|
||||||
|
should_create_todo(user: member, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
|
||||||
|
should_create_todo(user: author, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
|
||||||
|
should_create_todo(user: assignee, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
|
||||||
|
should_create_todo(user: john_doe, target: issue, author: john_doe, action: Todo::MENTIONED, note: confidential_note)
|
||||||
|
end
|
||||||
|
|
||||||
context 'commits' do
|
context 'commits' do
|
||||||
let(:base_commit_todo_attrs) { { target_id: nil, target_type: 'Commit', author: john_doe } }
|
let(:base_commit_todo_attrs) { { target_id: nil, target_type: 'Commit', author: john_doe } }
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
module PackagesManagerApiSpecHelpers
|
module PackagesManagerApiSpecHelpers
|
||||||
def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil)
|
def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil)
|
||||||
JSONWebToken::HMACToken.new(secret).tap do |jwt|
|
JSONWebToken::HMACToken.new(secret).tap do |jwt|
|
||||||
jwt['access_token'] = personal_access_token.id
|
jwt['access_token'] = personal_access_token.token
|
||||||
jwt['user_id'] = user_id || personal_access_token.user_id
|
jwt['user_id'] = user_id || personal_access_token.user_id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,11 +10,11 @@ module RackAttackSpecHelpers
|
||||||
end
|
end
|
||||||
|
|
||||||
def private_token_headers(user)
|
def private_token_headers(user)
|
||||||
{ 'HTTP_PRIVATE_TOKEN' => user.private_token }
|
{ Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => user.private_token }
|
||||||
end
|
end
|
||||||
|
|
||||||
def personal_access_token_headers(personal_access_token)
|
def personal_access_token_headers(personal_access_token)
|
||||||
{ 'HTTP_PRIVATE_TOKEN' => personal_access_token.token }
|
{ Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => personal_access_token.token }
|
||||||
end
|
end
|
||||||
|
|
||||||
def oauth_token_headers(oauth_access_token)
|
def oauth_token_headers(oauth_access_token)
|
||||||
|
@ -26,6 +26,10 @@ module RackAttackSpecHelpers
|
||||||
{ 'AUTHORIZATION' => "Basic #{encoded_login}" }
|
{ 'AUTHORIZATION' => "Basic #{encoded_login}" }
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def deploy_token_headers(deploy_token)
|
||||||
|
basic_auth_headers(deploy_token, deploy_token)
|
||||||
|
end
|
||||||
|
|
||||||
def expect_rejection(name = nil, &block)
|
def expect_rejection(name = nil, &block)
|
||||||
yield
|
yield
|
||||||
|
|
||||||
|
|
|
@ -62,15 +62,8 @@ RSpec.shared_examples 'conan authenticate endpoint' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'responds with 401 Unauthorized when an invalid access token ID is provided' do
|
it 'responds with 401 Unauthorized when an invalid access token is provided' do
|
||||||
jwt = build_jwt(double(id: 12345), user_id: personal_access_token.user_id)
|
jwt = build_jwt(double(token: 12345), user_id: user.id)
|
||||||
get api(url), headers: build_token_auth_header(jwt.encoded)
|
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'responds with 401 Unauthorized when invalid user is provided' do
|
|
||||||
jwt = build_jwt(personal_access_token, user_id: 12345)
|
|
||||||
get api(url), headers: build_token_auth_header(jwt.encoded)
|
get api(url), headers: build_token_auth_header(jwt.encoded)
|
||||||
|
|
||||||
expect(response).to have_gitlab_http_status(:unauthorized)
|
expect(response).to have_gitlab_http_status(:unauthorized)
|
||||||
|
@ -102,7 +95,7 @@ RSpec.shared_examples 'conan authenticate endpoint' do
|
||||||
|
|
||||||
payload = JSONWebToken::HMACToken.decode(
|
payload = JSONWebToken::HMACToken.decode(
|
||||||
response.body, jwt_secret).first
|
response.body, jwt_secret).first
|
||||||
expect(payload['access_token']).to eq(personal_access_token.id)
|
expect(payload['access_token']).to eq(personal_access_token.token)
|
||||||
expect(payload['user_id']).to eq(personal_access_token.user_id)
|
expect(payload['user_id']).to eq(personal_access_token.user_id)
|
||||||
|
|
||||||
duration = payload['exp'] - payload['iat']
|
duration = payload['exp'] - payload['iat']
|
||||||
|
|
|
@ -8,7 +8,50 @@
|
||||||
# * requests_per_period
|
# * requests_per_period
|
||||||
# * period_in_seconds
|
# * period_in_seconds
|
||||||
# * period
|
# * period
|
||||||
RSpec.shared_examples 'rate-limited token-authenticated requests' do
|
RSpec.shared_examples 'rate-limited user based token-authenticated requests' do
|
||||||
|
context 'when the throttle is enabled' do
|
||||||
|
before do
|
||||||
|
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
|
||||||
|
stub_application_setting(settings_to_set)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not reject requests if the user is in the allowlist' do
|
||||||
|
stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', user.id.to_s)
|
||||||
|
Gitlab::RackAttack.configure_user_allowlist
|
||||||
|
|
||||||
|
expect(Gitlab::Instrumentation::Throttle).to receive(:safelist=).with('throttle_user_allowlist').at_least(:once)
|
||||||
|
|
||||||
|
(requests_per_period + 1).times do
|
||||||
|
make_request(request_args)
|
||||||
|
expect(response).not_to have_gitlab_http_status(:too_many_requests)
|
||||||
|
end
|
||||||
|
|
||||||
|
stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', nil)
|
||||||
|
Gitlab::RackAttack.configure_user_allowlist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
include_examples 'rate-limited token requests' do
|
||||||
|
let(:log_data) do
|
||||||
|
{
|
||||||
|
user_id: user.id,
|
||||||
|
'meta.user' => user.username
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'rate-limited deploy-token-authenticated requests' do
|
||||||
|
include_examples 'rate-limited token requests' do
|
||||||
|
let(:log_data) do
|
||||||
|
{
|
||||||
|
deploy_token_id: deploy_token.id
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
RSpec.shared_examples 'rate-limited token requests' do
|
||||||
let(:throttle_types) do
|
let(:throttle_types) do
|
||||||
{
|
{
|
||||||
"throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
|
"throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
|
||||||
|
@ -51,18 +94,6 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
|
||||||
expect_rejection { make_request(request_args) }
|
expect_rejection { make_request(request_args) }
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'does not reject requests if the user is in the allowlist' do
|
|
||||||
stub_env('GITLAB_THROTTLE_USER_ALLOWLIST', user.id.to_s)
|
|
||||||
Gitlab::RackAttack.configure_user_allowlist
|
|
||||||
|
|
||||||
expect(Gitlab::Instrumentation::Throttle).to receive(:safelist=).with('throttle_user_allowlist').at_least(:once)
|
|
||||||
|
|
||||||
(requests_per_period + 1).times do
|
|
||||||
make_request(request_args)
|
|
||||||
expect(response).not_to have_gitlab_http_status(:too_many_requests)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'allows requests after throttling and then waiting for the next period' do
|
it 'allows requests after throttling and then waiting for the next period' do
|
||||||
requests_per_period.times do
|
requests_per_period.times do
|
||||||
make_request(request_args)
|
make_request(request_args)
|
||||||
|
@ -81,7 +112,7 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'counts requests from different users separately, even from the same IP' do
|
it 'counts requests from different requesters separately, even from the same IP' do
|
||||||
requests_per_period.times do
|
requests_per_period.times do
|
||||||
make_request(request_args)
|
make_request(request_args)
|
||||||
expect(response).not_to have_gitlab_http_status(:too_many_requests)
|
expect(response).not_to have_gitlab_http_status(:too_many_requests)
|
||||||
|
@ -92,7 +123,7 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
|
||||||
expect(response).not_to have_gitlab_http_status(:too_many_requests)
|
expect(response).not_to have_gitlab_http_status(:too_many_requests)
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'counts all requests from the same user, even via different IPs' do
|
it 'counts all requests from the same requesters, even via different IPs' do
|
||||||
requests_per_period.times do
|
requests_per_period.times do
|
||||||
make_request(request_args)
|
make_request(request_args)
|
||||||
expect(response).not_to have_gitlab_http_status(:too_many_requests)
|
expect(response).not_to have_gitlab_http_status(:too_many_requests)
|
||||||
|
@ -122,10 +153,8 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
|
||||||
remote_ip: '127.0.0.1',
|
remote_ip: '127.0.0.1',
|
||||||
request_method: request_method,
|
request_method: request_method,
|
||||||
path: request_args.first,
|
path: request_args.first,
|
||||||
user_id: user.id,
|
|
||||||
'meta.user' => user.username,
|
|
||||||
matched: throttle_types[throttle_setting_prefix]
|
matched: throttle_types[throttle_setting_prefix]
|
||||||
})
|
}.merge(log_data))
|
||||||
|
|
||||||
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
|
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue