New upstream version 14.8.6+ds1

This commit is contained in:
Pirate Praveen 2022-05-03 16:02:30 +05:30
parent 4f07442896
commit 710edc26c8
75 changed files with 1112 additions and 364 deletions

View file

@ -2,6 +2,25 @@
documentation](doc/development/changelog.md) for instructions on adding your own
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)
### Security (21 changes)

View file

@ -1 +1 @@
14.8.5
14.8.6

View file

@ -1 +1 @@
14.8.5
14.8.6

View file

@ -29,6 +29,25 @@ class Projects::ApplicationController < ApplicationController
@project = find_routable!(Project, path, request.fullpath, extra_authorization_proc: auth_proc)
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)
params[:namespace_id] = project.namespace.to_param
params[:project_id] = project.to_param

View file

@ -7,6 +7,7 @@ class Projects::ArtifactsController < Projects::ApplicationController
layout 'project'
before_action :authorize_read_build!
before_action :authorize_read_build_trace!, only: [:download]
before_action :authorize_update_build!, only: [:keep]
before_action :authorize_destroy_artifacts!, only: [:destroy]
before_action :extract_ref_name_and_path
@ -162,4 +163,10 @@ class Projects::ArtifactsController < Projects::ApplicationController
render_404 unless @entry.exists?
end
def authorize_read_build_trace!
return unless params[:file_type] == 'trace'
super
end
end

View file

@ -171,17 +171,7 @@ class Projects::JobsController < Projects::ApplicationController
private
def authorize_read_build_trace!
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
attr_reader :build
def authorize_update_build!
return access_denied! unless can?(current_user, :update_build, @build)

View file

@ -7,7 +7,8 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
before_action :authorize_play_pipeline_schedule!, only: [:play]
before_action :authorize_read_pipeline_schedule!
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]
feature_category :continuous_integration
@ -108,6 +109,10 @@ class Projects::PipelineSchedulesController < Projects::ApplicationController
return access_denied! unless can?(current_user, :update_pipeline_schedule, schedule)
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!
return access_denied! unless can?(current_user, :admin_pipeline_schedule, schedule)
end

View file

@ -895,7 +895,10 @@ module Ci
end
end
cache
type_suffix = pipeline.protected_ref? ? 'protected' : 'non_protected'
cache.map do |entry|
entry.merge(key: "#{entry[:key]}-#{type_suffix}")
end
end
def credentials

View file

@ -229,7 +229,13 @@ class CommitStatus < Ci::ApplicationRecord
end
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
def failed_but_allowed?

View file

@ -29,10 +29,12 @@ module Integrations
def fields
[
{
type: 'text',
type: 'password',
name: '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.'),
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
placeholder: '0/68a9e79b868c6789e79a124c30b0',
required: true

View file

@ -19,8 +19,19 @@ module Integrations
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

View file

@ -55,10 +55,12 @@ module Integrations
required: true
},
{
type: 'text',
type: 'password',
name: 'build_key',
placeholder: s_('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
},
{

View file

@ -26,7 +26,13 @@ module Integrations
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

View file

@ -76,10 +76,12 @@ module Integrations
def fields
[
{ type: 'text',
{ type: 'password',
name: 'token',
title: _('Token'),
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 },
{ type: 'text',

View file

@ -27,11 +27,13 @@ module Integrations
def fields
[
{
type: 'text',
type: 'password',
name: 'token',
title: _('Campfire token'),
placeholder: '',
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
},
{

View file

@ -96,8 +96,21 @@ module Integrations
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

View file

@ -26,7 +26,15 @@ module Integrations
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

View file

@ -36,10 +36,12 @@ module Integrations
required: true
},
{
type: 'text',
type: 'password',
name: 'token',
title: _('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: '',
required: true
},

View file

@ -28,9 +28,11 @@ module Integrations
def fields
[
{
type: 'text',
type: 'password',
name: 'token',
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
},
{

View file

@ -22,18 +22,22 @@ module Integrations
def fields
[
{
type: 'text',
type: 'password',
name: 'api_key',
title: _('API 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: '',
required: true
},
{
type: 'text',
type: 'password',
name: 'user_key',
title: _('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: '',
required: true
},

View file

@ -630,7 +630,8 @@ class Issue < ApplicationRecord
# Returns `true` if this Issue is visible to everybody.
def publicly_visible?
project.public? && !confidential? && !hidden? && !::Gitlab::ExternalAuthorization.enabled?
project.public? && project.feature_available?(:issues, nil) &&
!confidential? && !hidden? && !::Gitlab::ExternalAuthorization.enabled?
end
def expire_etag_cache

View file

@ -35,6 +35,7 @@ class Packages::PackageFile < ApplicationRecord
validates :file_name, presence: true
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 :limit_recent, ->(limit) { recent.limit(limit) }

View file

@ -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 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
# feature.
def self.with_feature_available_for_user(feature, user)

View file

@ -15,11 +15,14 @@ module Ci
rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
enable :update_pipeline_schedule
enable :admin_pipeline_schedule
enable :read_pipeline_schedule_variables
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
enable :take_ownership_pipeline_schedule
end

View file

@ -352,8 +352,6 @@ class TodoService
end
def reject_users_without_access(users, parent, target)
target = target.noteable if target.is_a?(Note)
if target.respond_to?(:to_ability_name)
select_users(users, :"read_#{target.to_ability_name}", target)
else

View file

@ -31,6 +31,7 @@ can't link to files outside it.
- Subsequent pipelines can use the cache.
- Subsequent jobs in the same pipeline can use the cache, if the dependencies are identical.
- Different projects cannot share the cache.
- Protected and non-protected branches do not share the cache.
### 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
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
This example shows two jobs in two consecutive stages:

View file

@ -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.
[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**
of job names only. Matching substrings found at the beginning or in the middle of
job names are not removed.

View file

@ -53,6 +53,20 @@ is installed on.
![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
You can pass any number of arbitrary variables. They are available in

View file

@ -93,7 +93,7 @@ module API
requires :pipeline_schedule_id, type: Integer, desc: 'The pipeline schedule id'
end
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)
present pipeline_schedule, with: Entities::Ci::PipelineScheduleDetails

View file

@ -153,7 +153,7 @@ module API
def token
strong_memoize(:token) do
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_job(find_job_from_token) if find_job_from_token
token
@ -224,9 +224,27 @@ module API
forbidden!
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
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_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
def find_user_from_job_token
@ -256,7 +274,7 @@ module API
return unless token
PersonalAccessToken.find_by_id_and_user_id(token.access_token_id, token.user_id)
token.access_token_id
end
def find_deploy_token_from_conan_jwt

View file

@ -174,7 +174,7 @@ module API
requires :name, type: String
requires :version, type: String
optional :md5_digest, type: String
optional :sha256_digest, type: String
optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex
end
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth

View file

@ -13,6 +13,10 @@ module Gitlab
@request = request
end
def find_authenticated_requester(request_formats)
user(request_formats) || deploy_token_from_request
end
def user(request_formats)
request_formats.each do |format|
user = find_sessionless_user(format)
@ -80,7 +84,8 @@ module Gitlab
def route_authentication_setting
@route_authentication_setting ||= {
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

View file

@ -6,25 +6,28 @@ module Gitlab
module Chain
module Helpers
def error(message, config_error: false, drop_reason: nil)
sanitized_message = ActionController::Base.helpers.sanitize(message, tags: [])
if config_error
drop_reason = :config_error
pipeline.yaml_errors = message
pipeline.yaml_errors = sanitized_message
end
pipeline.add_error_message(message)
pipeline.add_error_message(sanitized_message)
drop_pipeline!(drop_reason)
# TODO: consider not to rely on AR errors directly as they can be
# polluted with other unrelated errors (e.g. state machine)
# https://gitlab.com/gitlab-org/gitlab/-/issues/220823
pipeline.errors.add(:base, message)
pipeline.errors.add(:base, sanitized_message)
pipeline.errors.full_messages
end
def warning(message)
pipeline.add_warning_message(message)
sanitized_message = ActionController::Base.helpers.sanitize(message, tags: [])
pipeline.add_warning_message(sanitized_message)
end
private

View file

@ -23,7 +23,7 @@ module Gitlab
end
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

View file

@ -13,8 +13,8 @@ module Gitlab
attr_reader :access_token_id, :user_id
class << self
def from_personal_access_token(access_token)
new(access_token_id: access_token.id, user_id: access_token.user_id)
def from_personal_access_token(user_id, token)
new(access_token_id: token, user_id: user_id)
end
def from_job(job)

View file

@ -31,7 +31,8 @@ module Gitlab
def check_download_access!
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
override :check_change_access!
@ -52,6 +53,17 @@ module Gitlab
def not_found_message
error_message(:not_found)
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

View file

@ -638,7 +638,6 @@ included_attributes:
- :build_allow_git_fetch
- :build_coverage_regex
- :build_timeout
- :ci_config_path
- :delete_error
- :description
- :disable_overriding_approvers_per_merge_request

View file

@ -87,6 +87,8 @@ module Gitlab
when *BUILD_MODELS then setup_build
when :issues then setup_issue
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
update_project_references
@ -152,6 +154,10 @@ module Gitlab
@relation_hash['active'] = false
end
def setup_protected_branch_access_level
@relation_hash['access_level'] = Gitlab::Access::MAINTAINER
end
def compute_relative_position
return unless max_relative_position

View file

@ -11,7 +11,7 @@ module Gitlab
# this if the change to the renderer output is a new feature or a
# minor bug fix.
# See: https://gitlab.com/gitlab-org/gitlab/-/issues/330313
CACHE_COMMONMARK_VERSION = 29
CACHE_COMMONMARK_VERSION = 30
CACHE_COMMONMARK_VERSION_START = 10
BaseError = Class.new(StandardError)

View file

@ -73,12 +73,16 @@ module Gitlab
matched: req.env['rack.attack.matched']
}
if THROTTLES_WITH_USER_INFORMATION.include? req.env['rack.attack.matched'].to_sym
user_id = req.env['rack.attack.match_discriminator']
user = User.find_by(id: user_id) # rubocop:disable CodeReuse/ActiveRecord
discriminator = req.env['rack.attack.match_discriminator'].to_s
discriminator_id = discriminator.split(':').last
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?
elsif discriminator.starts_with?('deploy_token:')
rack_attack_info[:deploy_token_id] = discriminator_id.to_i
end
Gitlab::InstrumentationHelper.add_instrumentation_data(rack_attack_info)

View file

@ -95,7 +95,7 @@ module Gitlab
authenticated_options = Gitlab::Throttle.options(throttle, authenticated: true)
throttle_or_track(rack_attack, "throttle_authenticated_#{throttle}", authenticated_options) do |req|
if req.throttle?(throttle, authenticated: true)
req.throttled_user_id([:api])
req.throttled_identifer([:api])
end
end
end
@ -117,7 +117,7 @@ module Gitlab
throttle_or_track(rack_attack, 'throttle_authenticated_web', Gitlab::Throttle.authenticated_web_options) do |req|
if req.throttle_authenticated_web?
req.throttled_user_id([:api, :rss, :ics])
req.throttled_identifer([:api, :rss, :ics])
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|
if req.throttle_authenticated_protected_paths_api?
req.throttled_user_id([:api])
req.throttled_identifer([:api])
end
end
throttle_or_track(rack_attack, 'throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req|
if req.throttle_authenticated_protected_paths_web?
req.throttled_user_id([:api, :rss, :ics])
req.throttled_identifer([:api, :rss, :ics])
end
end
throttle_or_track(rack_attack, 'throttle_authenticated_git_lfs', Gitlab::Throttle.throttle_authenticated_git_lfs_options) do |req|
if req.throttle_authenticated_git_lfs?
req.throttled_user_id([:api])
req.throttled_identifer([:api])
end
end

View file

@ -9,18 +9,22 @@ module Gitlab
GROUP_PATH_REGEX = %r{^/api/v\d+/groups/[^/]+/?$}.freeze
def unauthenticated?
!(authenticated_user_id([:api, :rss, :ics]) || authenticated_runner_id)
!(authenticated_identifier([:api, :rss, :ics]) || authenticated_runner_id)
end
def throttled_user_id(request_formats)
user_id = authenticated_user_id(request_formats)
def throttled_identifer(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'
return
end
user_id
"#{identifier_type}:#{identifier_id}"
end
def authenticated_runner_id
@ -169,8 +173,18 @@ module Gitlab
private
def authenticated_user_id(request_formats)
request_authenticator.user(request_formats)&.id
def authenticated_identifier(request_formats)
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
def request_authenticator

View file

@ -237,6 +237,10 @@ module Gitlab
generic_package_name_regex
end
def sha256_regex
@sha256_regex ||= /\A[0-9a-f]{64}\z/i.freeze
end
private
def conan_name_regex

View file

@ -5446,6 +5446,12 @@ msgstr ""
msgid "BambooService|Bamboo service root URL."
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."
msgstr ""
@ -15643,7 +15649,7 @@ msgstr ""
msgid "FloC|Federated Learning of Cohorts"
msgstr ""
msgid "FlowdockService|1b609b52537..."
msgid "FlowdockService|Enter your Flowdock token."
msgstr ""
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"
msgstr ""
msgid "ProjectService|Leave blank to use your current API key."
msgstr ""
msgid "ProjectService|Leave blank to use your current password"
msgstr ""
@ -29806,6 +29815,9 @@ msgstr ""
msgid "PushoverService|%{user_name} pushed new branch \"%{ref}\"."
msgstr ""
msgid "PushoverService|Enter new user key"
msgstr ""
msgid "PushoverService|Enter your application key."
msgstr ""
@ -29821,6 +29833,9 @@ msgstr ""
msgid "PushoverService|Leave blank for all active devices."
msgstr ""
msgid "PushoverService|Leave blank to use your current user key."
msgstr ""
msgid "PushoverService|Low priority"
msgstr ""

View file

@ -204,6 +204,44 @@ RSpec.describe Projects::ArtifactsController do
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 &#39;CI_DEBUG_TRACE&#39; variable to &#39;false&#39; 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
describe 'GET browse' do

View file

@ -13,10 +13,43 @@ RSpec.describe Projects::PipelineSchedulesController do
project.add_developer(user)
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
render_views
let(:scope) { nil }
let!(:inactive_pipeline_schedule) do
create(:ci_pipeline_schedule, :inactive, project: project)
end
@ -130,12 +163,15 @@ RSpec.describe Projects::PipelineSchedulesController 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_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) }
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) }
@ -284,20 +320,7 @@ RSpec.describe Projects::PipelineSchedulesController do
describe 'security' do
let(:schedule) { { description: 'updated_desc' } }
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) }
it_behaves_like 'access update schedule'
context 'when a developer created a pipeline schedule' do
let(:developer_1) { create(:user) }
@ -308,8 +331,10 @@ RSpec.describe Projects::PipelineSchedulesController do
end
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_allowed_for(:maintainer).of(project) }
end
context 'when a maintainer created a pipeline schedule' do
@ -321,16 +346,20 @@ RSpec.describe Projects::PipelineSchedulesController do
end
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) }
end
end
def go
put :update, params: { namespace_id: project.namespace.to_param,
put :update, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: pipeline_schedule,
schedule: schedule },
schedule: schedule
},
as: :html
end
end
@ -341,6 +370,7 @@ RSpec.describe Projects::PipelineSchedulesController do
before do
project.add_maintainer(user)
pipeline_schedule.update!(owner: user)
sign_in(user)
end
@ -352,22 +382,7 @@ RSpec.describe Projects::PipelineSchedulesController do
end
end
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_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
it_behaves_like 'access update schedule'
def go
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
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(: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_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
def go

View file

@ -9,7 +9,77 @@ RSpec.describe 'Pipeline Schedules', :js do
let(:scope) { nil }
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
stub_feature_flags(bootstrap_confirmation_modals: false)
project.add_maintainer(user)
@ -46,14 +116,6 @@ RSpec.describe 'Pipeline Schedules', :js do
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
accept_confirm { click_link 'Delete' }
@ -108,53 +170,6 @@ RSpec.describe 'Pipeline Schedules', :js do
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
before do
visit_pipelines_schedules

View file

@ -4,6 +4,7 @@
"creator_id": 123,
"visibility_level": 10,
"archived": false,
"ci_config_path": "config/path",
"labels": [
{
"id": 2,

View file

@ -6,5 +6,6 @@
"archived": false,
"deploy_keys": [],
"hooks": [],
"shared_runners_enabled": true
"shared_runners_enabled": true,
"ci_config_path": "config/path"
}

View file

@ -44,6 +44,38 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
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
let_it_be(:dependency_proxy_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
using RSpec::Parameterized::TableSyntax
where(:script_name, :expected_job_token_allowed, :expected_basic_auth_personal_access_token) do
'/api/endpoint' | true | true
'/namespace/project.git' | false | true
'/web/endpoint' | false | false
where(:script_name, :expected_job_token_allowed, :expected_basic_auth_personal_access_token, :expected_deploy_token_allowed) do
'/api/endpoint' | true | true | true
'/namespace/project.git' | false | true | true
'/web/endpoint' | false | false | false
end
with_them do
@ -362,7 +394,8 @@ RSpec.describe Gitlab::Auth::RequestAuthenticator do
it 'returns correct settings' do
expect(subject.send(:route_authentication_setting)).to eql({
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

View file

@ -22,6 +22,19 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
let(:command) { double(save_incompleted: true) }
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
shared_examples 'error function' do
specify do
@ -36,6 +49,18 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Helpers do
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 config error is true' do
context 'sets the yaml error and overrides the drop reason' do

View file

@ -25,13 +25,17 @@ RSpec.describe Gitlab::ConanToken do
end
describe '.from_personal_access_token' do
it 'sets access token id and user id' do
access_token = double(id: 123, user_id: 456)
it 'sets access token and user id and does not use the token id' do
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.user_id).to eq(456)
expect(token.access_token_id).not_to eq(personal_access_token.id)
expect(token.access_token_id).to eq(personal_access_token.token)
expect(token.user_id).to eq(personal_access_token.user_id)
end
end

View file

@ -4,8 +4,9 @@ require 'spec_helper'
RSpec.describe Gitlab::GitAccessWiki do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :wiki_repo) }
let_it_be(:wiki) { create(:project_wiki, project: project) }
let_it_be_with_reload(:project) { create(:project, :wiki_repo) }
let(:wiki) { create(:project_wiki, project: project) }
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
let(:authentication_abilities) { %i[read_project download_code push_code] }
@ -17,6 +18,61 @@ RSpec.describe Gitlab::GitAccessWiki do
redirected_path: redirected_path)
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
subject { access.check('git-receive-pack', changes) }
@ -28,56 +84,26 @@ RSpec.describe Gitlab::GitAccessWiki do
it { expect { subject }.not_to raise_error }
context 'when in a read-only GitLab instance' do
let(:message) { "You can't push code to a read-only GitLab instance." }
before do
allow(Gitlab::Database).to receive(:read_only?) { true }
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
context 'the user cannot :create_wiki' do
it_behaves_like 'not-found git access' do
let(:message) { 'The wiki you were looking for could not be found.' }
end
it { expect { subject }.to raise_wiki_not_found }
end
end
describe '#check_download_access!' do
subject { access.check('git-upload-pack', Gitlab::GitAccess::ANY) }
context 'the user can :download_wiki_code' do
before do
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
context 'when actor is a user' do
it_behaves_like 'wiki access by level'
end
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
let(:wiki_access_level) { ProjectFeature::DISABLED }
it_behaves_like 'forbidden git access' do
let(:message) { 'You are not allowed to download files from this wiki.' }
end
end
it { expect { subject }.to raise_wiki_forbidden }
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

View file

@ -401,4 +401,22 @@ RSpec.describe Gitlab::ImportExport::Project::RelationFactory, :use_clean_rails_
expect(created_object.value).to be_nil
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

View file

@ -111,6 +111,10 @@ RSpec.describe Gitlab::ImportExport::Project::TreeRestorer do
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
expect(Ci::Pipeline.find_by_sha('sha-notes').notes).not_to be_empty
end

View file

@ -91,7 +91,8 @@ RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
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
let(:event) do
ActiveSupport::Notifications::Event.new(
@ -103,7 +104,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
env: {
'rack.attack.match_type' => match_type,
'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',
path: '/api/v4/internal/authorized_keys',
matched: 'throttle_authenticated_api',
user_id: 'not_exist_user_id'
user_id: non_existing_record_id
)
)
subscriber.send(match_type, event)
@ -137,7 +138,7 @@ RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do
env: {
'rack.attack.match_type' => match_type,
'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
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
describe '#throttle' do

View file

@ -990,4 +990,19 @@ RSpec.describe Gitlab::Regex do
it { is_expected.not_to match('../../../../../1.2.3') }
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
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

View file

@ -1048,7 +1048,27 @@ RSpec.describe Ci::Build do
allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1)
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
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)
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
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)
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

View file

@ -618,6 +618,7 @@ RSpec.describe CommitStatus do
'rspec:windows 10000 20000' | 'rspec:windows'
'rspec:windows 0 : / 1' | 'rspec:windows'
'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'
'rspec: [aws]' | 'rspec'

View 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

View file

@ -724,14 +724,15 @@ RSpec.describe Issue do
describe '#participants' 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
create(:note_on_issue, noteable: issue, project: reusable_project, note: 'a')
create(:note_on_issue, noteable: issue, project: public_project, note: 'a')
end
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
it 'includes the issue author' do
@ -801,6 +802,7 @@ RSpec.describe Issue do
context 'without a user' do
let(:user) { nil }
context 'with issue available as public' do
before do
project.project_feature.update_attribute(:issues_access_level, ProjectFeature::PUBLIC)
end
@ -818,6 +820,20 @@ RSpec.describe Issue do
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
shared_examples 'issue readable by user' do
it { is_expected.to eq(true) }

View file

@ -23,6 +23,41 @@ RSpec.describe Packages::PackageFile, type: :model do
describe 'validations' do
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
context 'with package filenames' do

View file

@ -84,11 +84,14 @@ RSpec.describe Ci::PipelineSchedulePolicy, :models do
project.add_maintainer(user)
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 :update_pipeline_schedule
expect(policy).to be_allowed :admin_pipeline_schedule
end
it 'does not allow for updating of an existing schedule' do
expect(policy).not_to be_allowed :update_pipeline_schedule
end
end
describe 'rules for non-owner of schedule' do

View file

@ -291,12 +291,34 @@ RSpec.describe API::Ci::PipelineSchedules do
end
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
put api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}", user)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'unauthenticated user' 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)
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
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 match_response_schema('pipeline_schedule')
end
end
context 'authenticated user with invalid permissions' do
context 'as an authenticated user with invalid permissions' do
it 'does not update owner' do
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
context 'unauthenticated user' do
context 'as an unauthenticated user' do
it 'does not update owner' do
post api("/projects/#{project.id}/pipeline_schedules/#{pipeline_schedule.id}/take_ownership")
expect(response).to have_gitlab_http_status(:unauthorized)
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
describe 'DELETE /projects/:id/pipeline_schedules/:pipeline_schedule_id' do

View file

@ -191,7 +191,7 @@ RSpec.describe API::Ci::Runner, :clean_gitlab_redis_shared_state do
end
let(:expected_cache) do
[{ 'key' => 'cache_key',
[{ 'key' => a_string_matching(/^cache_key-(?>protected|non_protected)$/),
'untracked' => false,
'paths' => ['vendor/*'],
'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.' }] }])
expect(json_response['steps']).to eq(expected_steps)
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['features']).to match(expected_features)
end

View file

@ -156,6 +156,46 @@ RSpec.describe API::Markdown do
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

View file

@ -136,7 +136,7 @@ RSpec.describe API::PypiPackages do
let(:url) { "/projects/#{project.id}/packages/pypi" }
let(:headers) { {} }
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(:send_rewritten_field) { true }
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
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 'job token for package uploads'

View file

@ -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(: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
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(: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
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(: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
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(: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
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(: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
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(: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
@ -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(: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
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(: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
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(: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
@ -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(: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
@ -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(: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
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(: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
@ -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(: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
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(: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
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
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
@ -558,7 +568,7 @@ RSpec.describe 'Rack Attack global throttles', :use_clean_rails_memory_store_cac
end
end
it_behaves_like 'rate-limited token-authenticated requests'
it_behaves_like 'rate-limited user based token-authenticated requests'
end
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(: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
@ -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(: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
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(: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
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(: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
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(: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
context 'with the token in the headers' do
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)) }
it_behaves_like 'rate-limited token-authenticated requests'
it_behaves_like 'rate-limited user based token-authenticated requests'
end
context 'precedence over authenticated api throttle' do

View file

@ -33,7 +33,7 @@ RSpec.describe Ci::CreatePipelineService do
it 'uses the provided key' do
expected = {
key: 'a-key',
key: a_string_matching(/^a-key-(?>protected|non_protected)$/),
paths: ['logs/', 'binaries/'],
policy: 'pull-push',
untracked: true,

View file

@ -7,6 +7,9 @@ RSpec.describe Packages::Pypi::CreatePackageService do
let_it_be(:project) { create(:project) }
let_it_be(:user) { create(:user) }
let(:sha256) { '1' * 64 }
let(:md5) { '567' }
let(:requires_python) { '>=2.7' }
let(:params) do
{
@ -14,8 +17,8 @@ RSpec.describe Packages::Pypi::CreatePackageService do
version: '1.0',
content: temp_file('foo.tgz'),
requires_python: requires_python,
sha256_digest: '123',
md5_digest: '567'
sha256_digest: sha256,
md5_digest: md5
}
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.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_sha256).to eq '123'
expect(created_package.package_files.first.file_md5).to eq '567'
expect(created_package.package_files.first.file_sha256).to eq sha256
expect(created_package.package_files.first.file_md5).to eq md5
end
end
@ -62,8 +65,8 @@ RSpec.describe Packages::Pypi::CreatePackageService do
context 'with an existing file' do
before do
params[:content] = temp_file('foo.tgz')
params[:sha256_digest] = 'abc'
params[:md5_digest] = 'def'
params[:sha256_digest] = sha256
params[:md5_digest] = md5
end
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.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_sha256).to eq 'abc'
expect(created_package.package_files.first.file_md5).to eq 'def'
expect(created_package.package_files.first.file_sha256).to eq sha256
expect(created_package.package_files.first.file_md5).to eq md5
end
end
end

View file

@ -391,6 +391,7 @@ RSpec.describe TodoService do
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(: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(: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) }
@ -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)
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
let(:base_commit_todo_attrs) { { target_id: nil, target_type: 'Commit', author: john_doe } }

View file

@ -3,7 +3,7 @@
module PackagesManagerApiSpecHelpers
def build_jwt(personal_access_token, secret: jwt_secret, user_id: nil)
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
end
end

View file

@ -10,11 +10,11 @@ module RackAttackSpecHelpers
end
def private_token_headers(user)
{ 'HTTP_PRIVATE_TOKEN' => user.private_token }
{ Gitlab::Auth::AuthFinders::PRIVATE_TOKEN_HEADER => user.private_token }
end
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
def oauth_token_headers(oauth_access_token)
@ -26,6 +26,10 @@ module RackAttackSpecHelpers
{ 'AUTHORIZATION' => "Basic #{encoded_login}" }
end
def deploy_token_headers(deploy_token)
basic_auth_headers(deploy_token, deploy_token)
end
def expect_rejection(name = nil, &block)
yield

View file

@ -62,15 +62,8 @@ RSpec.shared_examples 'conan authenticate endpoint' do
end
end
it 'responds with 401 Unauthorized when an invalid access token ID is provided' do
jwt = build_jwt(double(id: 12345), user_id: personal_access_token.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)
it 'responds with 401 Unauthorized when an invalid access token is provided' do
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)
@ -102,7 +95,7 @@ RSpec.shared_examples 'conan authenticate endpoint' do
payload = JSONWebToken::HMACToken.decode(
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)
duration = payload['exp'] - payload['iat']

View file

@ -8,7 +8,50 @@
# * requests_per_period
# * period_in_seconds
# * 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
{
"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) }
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
requests_per_period.times do
make_request(request_args)
@ -81,7 +112,7 @@ RSpec.shared_examples 'rate-limited token-authenticated requests' do
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
make_request(request_args)
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)
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
make_request(request_args)
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',
request_method: request_method,
path: request_args.first,
user_id: user.id,
'meta.user' => user.username,
matched: throttle_types[throttle_setting_prefix]
})
}.merge(log_data))
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once