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 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)

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) @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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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?

View file

@ -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

View file

@ -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

View file

@ -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
}, },
{ {

View file

@ -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

View file

@ -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',

View file

@ -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
}, },
{ {

View file

@ -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

View file

@ -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

View file

@ -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
}, },

View file

@ -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
}, },
{ {

View file

@ -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
}, },

View file

@ -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

View file

@ -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) }

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 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)

View file

@ -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

View file

@ -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

View file

@ -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:

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. 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.

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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 ""

View file

@ -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 &#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 end
describe 'GET browse' do describe 'GET browse' do

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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"
} }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

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('../../../../../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

View file

@ -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

View file

@ -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'

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 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) }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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'

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(: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

View file

@ -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,

View file

@ -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

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!(: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 } }

View file

@ -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

View file

@ -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

View file

@ -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']

View file

@ -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