New upstream version 14.10.5+ds1

This commit is contained in:
Pirate Praveen 2022-07-01 11:34:44 +05:30
parent 13a35f9ff0
commit 394d455fd1
86 changed files with 1249 additions and 361 deletions

View file

@ -1979,7 +1979,6 @@ Layout/LineLength:
- 'ee/spec/features/groups/iterations/user_edits_iteration_spec.rb' - 'ee/spec/features/groups/iterations/user_edits_iteration_spec.rb'
- 'ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb' - 'ee/spec/features/groups/iterations/user_views_iteration_cadence_spec.rb'
- 'ee/spec/features/groups/iterations/user_views_iteration_spec.rb' - 'ee/spec/features/groups/iterations/user_views_iteration_spec.rb'
- 'ee/spec/features/groups/members/manage_groups_spec.rb'
- 'ee/spec/features/groups/members/manage_members_spec.rb' - 'ee/spec/features/groups/members/manage_members_spec.rb'
- 'ee/spec/features/groups/members/override_ldap_memberships_spec.rb' - 'ee/spec/features/groups/members/override_ldap_memberships_spec.rb'
- 'ee/spec/features/groups/saml_providers_spec.rb' - 'ee/spec/features/groups/saml_providers_spec.rb'

View file

@ -2,6 +2,28 @@
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.10.5 (2022-06-30)
### Security (17 changes)
- [Fix group IP restrictions not enforced for container registry requests](gitlab-org/security/gitlab@b146ad7b8c6fba9d3c5bea365ff8afd49949dcb0) ([merge request](gitlab-org/security/gitlab!2552))
- [Update rack gem to version 2.2.3.1](gitlab-org/security/gitlab@09ebb50ceee5a2226c1f70fa1d6c25391d51dda6) ([merge request](gitlab-org/security/gitlab!2554))
- [Gitlab Runner version upgrade](gitlab-org/security/gitlab@c91bfdb4f96e70e377a84b99c4edaa2fdecb8e16) ([merge request](gitlab-org/security/gitlab!2567))
- [Update ProjectAttributesTransformer to use fixed number of attributes](gitlab-org/security/gitlab@6f892fb2a4b84473c3796533551f915c16cf77d9) ([merge request](gitlab-org/security/gitlab!2549))
- [Escape deploy key title to prevent XSS](gitlab-org/security/gitlab@153a7c447e03a509b7f06ac7381f4f9db414c9ea) ([merge request](gitlab-org/security/gitlab!2494))
- [Sanitize ZenTao breadcrumb links](gitlab-org/security/gitlab@530c7be82ae90138898ff99008d994b1c85d8cf1) ([merge request](gitlab-org/security/gitlab!2557))
- [Fix permissions in the project labels API](gitlab-org/security/gitlab@f2c71f64c258bef9f56f4892d11a4dbf20d668e6) ([merge request](gitlab-org/security/gitlab!2534))
- [Security fix sentry issue leaks and access level check](gitlab-org/security/gitlab@c644d94f58e30e1a9d87521b039a347412f0fead) ([merge request](gitlab-org/security/gitlab!2501))
- [Check permissions before exposing user two factor enabled](gitlab-org/security/gitlab@8a623e8a4fdbd3421ac3ae0e37e156b7d3b04970) ([merge request](gitlab-org/security/gitlab!2525))
- [Filter milestone release by user access](gitlab-org/security/gitlab@d7d6431a52808107a71f15d29e856eef2cb313e5) ([merge request](gitlab-org/security/gitlab!2537))
- [Fix the required access level in the Conan packages finder](gitlab-org/security/gitlab@756fb242c4d6acf6cfd95fa39f37410eaf009747) ([merge request](gitlab-org/security/gitlab!2485))
- [Allow inviting only groups with subset of allowed domains to groups](gitlab-org/security/gitlab@ca50492a32a2e367b0bc75dae0f91dc52d23b2ed) ([merge request](gitlab-org/security/gitlab!2512))
- [Fix open redirect vulnerability](gitlab-org/security/gitlab@1450068a44d67af3cbe09fedcc4b1e9b4ea2e586) ([merge request](gitlab-org/security/gitlab!2540))
- [Adds a filter based on user access to Runner jobs endpoint](gitlab-org/security/gitlab@dafaf3e50e8b1a18ff362cbb60e9482c9d60fc33) ([merge request](gitlab-org/security/gitlab!2497))
- [Prevent runners from picking IP restricted jobs](gitlab-org/security/gitlab@0fad0cdde00b68c2a0f19ffa2681b438fcad4097) ([merge request](gitlab-org/security/gitlab!2503))
- [Restrict CI lint access to pipeline creators](gitlab-org/security/gitlab@c5b79e969f10e3604eff16a9edef716e700cd201) ([merge request](gitlab-org/security/gitlab!2515))
- [Catch endless headers when reading HTTP responses](gitlab-org/security/gitlab@65379002bd7a0259c425455c937b110bd96096dc) ([merge request](gitlab-org/security/gitlab!2529))
## 14.10.4 (2022-06-01) ## 14.10.4 (2022-06-01)
### Security (7 changes) ### Security (7 changes)

View file

@ -1 +1 @@
14.10.4 14.10.5

View file

@ -958,7 +958,7 @@ GEM
pyu-ruby-sasl (0.0.3.3) pyu-ruby-sasl (0.0.3.3)
raabro (1.1.6) raabro (1.1.6)
racc (1.6.0) racc (1.6.0)
rack (2.2.3) rack (2.2.3.1)
rack-accept (0.4.5) rack-accept (0.4.5)
rack (>= 0.4) rack (>= 0.4)
rack-attack (6.3.0) rack-attack (6.3.0)

View file

@ -1 +1 @@
14.10.4 14.10.5

View file

@ -1,4 +1,4 @@
export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug']; export const projectKeys = ['id', 'name', 'organizationName', 'organizationSlug', 'slug'];
export const transformFrontendSettings = ({ export const transformFrontendSettings = ({
apiHost, apiHost,
@ -9,6 +9,7 @@ export const transformFrontendSettings = ({
}) => { }) => {
const project = selectedProject const project = selectedProject
? { ? {
sentry_project_id: selectedProject.id,
slug: selectedProject.slug, slug: selectedProject.slug,
name: selectedProject.name, name: selectedProject.name,
organization_name: selectedProject.organizationName, organization_name: selectedProject.organizationName,

View file

@ -537,7 +537,7 @@ export default class AccessDropdown {
return ` return `
<li> <li>
<a href="#" class="${isActiveClass}"> <a href="#" class="${isActiveClass}">
<strong>${key.title}</strong> <strong>${escape(key.title)}</strong>
<p> <p>
${sprintf( ${sprintf(
__('Owned by %{image_tag}'), __('Owned by %{image_tag}'),

View file

@ -4,6 +4,7 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle
respond_to :json respond_to :json
before_action :authorize_read_sentry_issue! before_action :authorize_read_sentry_issue!
before_action :authorize_update_sentry_issue!, only: %i[update]
before_action :set_issue_id, only: :details before_action :set_issue_id, only: :details
before_action only: [:index] do before_action only: [:index] do

View file

@ -143,7 +143,7 @@ module Projects
:integrated, :integrated,
:api_host, :api_host,
:token, :token,
project: [:slug, :name, :organization_slug, :organization_name] project: [:slug, :name, :organization_slug, :organization_name, :sentry_project_id]
], ],
grafana_integration_attributes: [:token, :grafana_url, :enabled], grafana_integration_attributes: [:token, :grafana_url, :enabled],

View file

@ -6,19 +6,29 @@ module Ci
ALLOWED_INDEXED_COLUMNS = %w[id].freeze ALLOWED_INDEXED_COLUMNS = %w[id].freeze
def initialize(runner, params = {}) def initialize(runner, current_user, params = {})
@runner = runner @runner = runner
@user = current_user
@params = params @params = params
end end
def execute def execute
items = @runner.builds items = @runner.builds
items = by_permission(items)
items = by_status(items) items = by_status(items)
sort_items(items) sort_items(items)
end end
private private
# rubocop: disable CodeReuse/ActiveRecord
def by_permission(items)
return items if @user.can_read_all_resources?
items.for_project(@user.authorized_project_mirrors(Gitlab::Access::REPORTER).select(:project_id))
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord # rubocop: disable CodeReuse/ActiveRecord
def by_status(items) def by_status(items)
return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status]) return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status])

View file

@ -25,7 +25,7 @@ module Packages
end end
def projects_visible_to_current_user def projects_visible_to_current_user
::Project.public_or_visible_to_user(current_user) ::Project.public_or_visible_to_user(current_user, ::Gitlab::Access::REPORTER)
end end
end end
end end

View file

@ -12,7 +12,7 @@ module Resolvers
Should not be requested more than once per request. Should not be requested more than once per request.
MD MD
authorize :read_pipeline authorize :create_pipeline
argument :project_path, GraphQL::Types::ID, argument :project_path, GraphQL::Types::ID,
required: true, required: true,

View file

@ -159,27 +159,6 @@ module IntegrationsHelper
!Gitlab.com? !Gitlab.com?
end end
def jira_issue_breadcrumb_link(issue_reference)
link_to '', { class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
icon = image_tag image_path('illustrations/logos/jira.svg'), width: 15, height: 15, class: 'gl-mr-2'
[icon, html_escape(issue_reference)].join.html_safe
end
end
def zentao_issue_breadcrumb_link(issue)
link_to issue[:web_url], { target: '_blank', rel: 'noopener noreferrer', class: 'gl-display-flex gl-align-items-center gl-white-space-nowrap' } do
icon = image_tag image_path('logos/zentao.svg'), width: 15, height: 15, class: 'gl-mr-2'
[icon, html_escape(issue[:id])].join.html_safe
end
end
def zentao_issues_show_data
{
issues_show_path: project_integrations_zentao_issue_path(@project, params[:id], format: :json),
issues_list_path: project_integrations_zentao_issues_path(@project)
}
end
extend self extend self
private private

View file

@ -298,6 +298,7 @@ module ProjectsHelper
setting.organization_slug.blank? setting.organization_slug.blank?
{ {
sentry_project_id: setting.sentry_project_id,
name: setting.project_name, name: setting.project_name,
organization_name: setting.organization_name, organization_name: setting.organization_name,
organization_slug: setting.organization_slug, organization_slug: setting.organization_slug,

View file

@ -153,11 +153,11 @@ module TimeboxesHelper
n_("%{releases} release", "%{releases} releases", count) % { releases: count } n_("%{releases} release", "%{releases} releases", count) % { releases: count }
end end
def recent_releases_with_counts(milestone) def recent_releases_with_counts(milestone, user)
total_count = milestone.releases.size total_count = milestone.releases.size
return [[], 0, 0] if total_count == 0 return [[], 0, 0] if total_count == 0
recent_releases = milestone.releases.recent.to_a recent_releases = milestone.releases.recent.filter { |release| Ability.allowed?(user, :read_release, release) }
more_count = total_count - recent_releases.size more_count = total_count - recent_releases.size
[recent_releases, total_count, more_count] [recent_releases, total_count, more_count]
end end

View file

@ -4,6 +4,8 @@ module Ci
# This model represents a shadow table of the main database's projects table. # This model represents a shadow table of the main database's projects table.
# It allows us to navigate the project and namespace hierarchy on the ci database. # It allows us to navigate the project and namespace hierarchy on the ci database.
class ProjectMirror < ApplicationRecord class ProjectMirror < ApplicationRecord
include FromUnion
belongs_to :project belongs_to :project
scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) } scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) }

View file

@ -3,7 +3,7 @@
module Clusters module Clusters
module Applications module Applications
class Runner < ApplicationRecord class Runner < ApplicationRecord
VERSION = '0.39.0' VERSION = '0.39.2'
self.table_name = 'clusters_applications_runners' self.table_name = 'clusters_applications_runners'

View file

@ -35,7 +35,8 @@ module Enums
bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data
downstream_pipeline_creation_failed: 1_007, downstream_pipeline_creation_failed: 1_007,
secrets_provider_not_found: 1_008, secrets_provider_not_found: 1_008,
reached_max_descendant_pipelines_depth: 1_009 reached_max_descendant_pipelines_depth: 1_009,
ip_restriction_failure: 1_010
} }
end end
end end

View file

@ -125,17 +125,22 @@ module ErrorTracking
def issue_details(opts = {}) def issue_details(opts = {})
with_reactive_cache('issue_details', opts.stringify_keys) do |result| with_reactive_cache('issue_details', opts.stringify_keys) do |result|
ensure_issue_belongs_to_project!(result[:issue].project_id)
result result
end end
end end
def issue_latest_event(opts = {}) def issue_latest_event(opts = {})
with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result| with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result|
ensure_issue_belongs_to_project!(result[:latest_event].project_id)
result result
end end
end end
def update_issue(opts = {}) def update_issue(opts = {})
issue_to_be_updated = sentry_client.issue_details(issue_id: opts[:issue_id])
ensure_issue_belongs_to_project!(issue_to_be_updated.project_id)
handle_exceptions do handle_exceptions do
{ updated: sentry_client.update_issue(opts) } { updated: sentry_client.update_issue(opts) }
end end
@ -177,6 +182,25 @@ module ErrorTracking
private private
def ensure_issue_belongs_to_project!(project_id_from_api)
raise 'The Sentry issue appers to be outside of the configured Sentry project' if Integer(project_id_from_api) != ensure_sentry_project_id!
end
def ensure_sentry_project_id!
return sentry_project_id if sentry_project_id.present?
raise("Couldn't find project: #{organization_name} / #{project_name} on Sentry") if sentry_project.nil?
update!(sentry_project_id: sentry_project.id)
sentry_project_id
end
def sentry_project
strong_memoize(:sentry_project) do
sentry_client.projects.find { |project| project.name == project_name && project.organization_name == organization_name }
end
end
def add_gitlab_issue_details(issue) def add_gitlab_issue_details(issue)
issue.gitlab_commit = match_gitlab_commit(issue.first_release_version) issue.gitlab_commit = match_gitlab_commit(issue.first_release_version)
issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit issue.gitlab_commit_path = project_commit_path(project, issue.gitlab_commit) if issue.gitlab_commit

View file

@ -41,3 +41,5 @@ class GroupGroupLink < ApplicationRecord
Gitlab::Access.human_access(self.group_access) Gitlab::Access.human_access(self.group_access)
end end
end end
GroupGroupLink.prepend_mod_with('GroupGroupLink')

View file

@ -1646,6 +1646,14 @@ class User < ApplicationRecord
true true
end end
def authorized_project_mirrors(level)
projects = Ci::ProjectMirror.by_project_id(ci_project_mirrors_for_project_members(level))
namespace_projects = Ci::ProjectMirror.by_namespace_id(ci_namespace_mirrors_for_group_members(level).select(:namespace_id))
Ci::ProjectMirror.from_union([projects, namespace_projects])
end
def ci_owned_runners def ci_owned_runners
@ci_owned_runners ||= begin @ci_owned_runners ||= begin
if ci_owned_runners_cross_joins_fix_enabled? if ci_owned_runners_cross_joins_fix_enabled?
@ -2116,6 +2124,10 @@ class User < ApplicationRecord
end end
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
def ci_project_mirrors_for_project_members(level)
project_members.where('access_level >= ?', level).pluck(:source_id)
end
def notification_email_verified def notification_email_verified
return if notification_email.blank? || temp_oauth_email? return if notification_email.blank? || temp_oauth_email?
@ -2267,7 +2279,7 @@ class User < ApplicationRecord
end end
def ci_owned_project_runners_from_project_members def ci_owned_project_runners_from_project_members
project_ids = project_members.where('access_level >= ?', Gitlab::Access::MAINTAINER).pluck(:source_id) project_ids = ci_project_mirrors_for_project_members(Gitlab::Access::MAINTAINER)
Ci::Runner Ci::Runner
.joins(:runner_projects) .joins(:runner_projects)

View file

@ -54,7 +54,13 @@ class ProjectPolicy < BasePolicy
desc "Container registry is disabled" desc "Container registry is disabled"
condition(:container_registry_disabled, scope: :subject) do condition(:container_registry_disabled, scope: :subject) do
!access_allowed_to?(:container_registry) if user.is_a?(DeployToken)
(!user.read_registry? && !user.write_registry?) ||
user.revoked? ||
!project.container_registry_enabled?
else
!access_allowed_to?(:container_registry)
end
end end
desc "Container registry is enabled for everyone with access to the project" desc "Container registry is enabled for everyone with access to the project"
@ -83,6 +89,16 @@ class ProjectPolicy < BasePolicy
user.is_a?(DeployKey) && user.can_push_to?(project) user.is_a?(DeployKey) && user.can_push_to?(project)
end end
desc "Deploy token with read_container_image scope"
condition(:read_container_image_deploy_token) do
user.is_a?(DeployToken) && user.has_access_to?(project) && user.read_registry?
end
desc "Deploy token with create_container_image scope"
condition(:create_container_image_deploy_token) do
user.is_a?(DeployToken) && user.has_access_to?(project) && user.write_registry?
end
desc "Deploy token with read_package_registry scope" desc "Deploy token with read_package_registry scope"
condition(:read_package_registry_deploy_token) do condition(:read_package_registry_deploy_token) do
user.is_a?(DeployToken) && user.has_access_to?(project) && user.read_package_registry user.is_a?(DeployToken) && user.has_access_to?(project) && user.read_package_registry
@ -297,7 +313,6 @@ class ProjectPolicy < BasePolicy
enable :read_deployment enable :read_deployment
enable :read_merge_request enable :read_merge_request
enable :read_sentry_issue enable :read_sentry_issue
enable :update_sentry_issue
enable :read_prometheus enable :read_prometheus
enable :read_metrics_dashboard_annotation enable :read_metrics_dashboard_annotation
enable :metrics_dashboard enable :metrics_dashboard
@ -413,6 +428,7 @@ class ProjectPolicy < BasePolicy
enable :admin_feature_flags_user_lists enable :admin_feature_flags_user_lists
enable :update_escalation_status enable :update_escalation_status
enable :read_secure_files enable :read_secure_files
enable :update_sentry_issue
end end
rule { can?(:developer_access) & user_confirmed? }.policy do rule { can?(:developer_access) & user_confirmed? }.policy do
@ -685,6 +701,14 @@ class ProjectPolicy < BasePolicy
enable :push_code enable :push_code
end end
rule { read_container_image_deploy_token }.policy do
enable :read_container_image
end
rule { create_container_image_deploy_token }.policy do
enable :create_container_image
end
rule { read_package_registry_deploy_token }.policy do rule { read_package_registry_deploy_token }.policy do
enable :read_package enable :read_package
enable :read_project enable :read_project

View file

@ -30,7 +30,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
trace_size_exceeded: 'The job log size limit was reached', trace_size_exceeded: 'The job log size limit was reached',
builds_disabled: 'The CI/CD is disabled for this project', builds_disabled: 'The CI/CD is disabled for this project',
environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.', environment_creation_failure: 'This job could not be executed because it would create an environment with an invalid parameter.',
deployment_rejected: 'This deployment job was rejected.' deployment_rejected: 'This deployment job was rejected.',
ip_restriction_failure: "This job could not be executed because group IP address restrictions are enabled, and the runner's IP address is not in the allowed range."
}.freeze }.freeze
TROUBLESHOOTING_DOC = { TROUBLESHOOTING_DOC = {

View file

@ -16,7 +16,7 @@ class MemberUserEntity < UserEntity
user.blocked? user.blocked?
end end
expose :two_factor_enabled do |user| expose :two_factor_enabled, if: -> (user) { current_user_can_manage_members? || current_user?(user) } do |user|
user.two_factor_enabled? user.two_factor_enabled?
end end
@ -25,6 +25,18 @@ class MemberUserEntity < UserEntity
user.status.emoji user.status.emoji
end end
end end
private
def current_user_can_manage_members?
return false unless options[:source]
Ability.allowed?(options[:current_user], :"admin_#{options[:source].to_ability_name}_member", options[:source])
end
def current_user?(user)
options[:current_user] == user
end
end end
MemberUserEntity.prepend_mod_with('MemberUserEntity') MemberUserEntity.prepend_mod_with('MemberUserEntity')

View file

@ -215,15 +215,13 @@ module Auth
def deploy_token_can_pull?(requested_project) def deploy_token_can_pull?(requested_project)
has_authentication_ability?(:read_container_image) && has_authentication_ability?(:read_container_image) &&
deploy_token.present? && deploy_token.present? &&
deploy_token.has_access_to?(requested_project) && can?(deploy_token, :read_container_image, requested_project)
deploy_token.read_registry?
end end
def deploy_token_can_push?(requested_project) def deploy_token_can_push?(requested_project)
has_authentication_ability?(:create_container_image) && has_authentication_ability?(:create_container_image) &&
deploy_token.present? && deploy_token.present? &&
deploy_token.has_access_to?(requested_project) && can?(deploy_token, :create_container_image, requested_project)
deploy_token.write_registry?
end end
## ##

View file

@ -90,7 +90,8 @@ module Projects
api_url: api_url, api_url: api_url,
enabled: settings[:enabled], enabled: settings[:enabled],
project_name: settings.dig(:project, :name), project_name: settings.dig(:project, :name),
organization_name: settings.dig(:project, :organization_name) organization_name: settings.dig(:project, :organization_name),
sentry_project_id: settings.dig(:project, :sentry_project_id)
} }
} }
params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value params[:error_tracking_setting_attributes][:token] = settings[:token] unless /\A\*+\z/.match?(settings[:token]) # Don't update token if we receive masked value

View file

@ -14,7 +14,7 @@
- if milestone.due_date || milestone.start_date - if milestone.due_date || milestone.start_date
.text-tertiary.gl-mb-2 .text-tertiary.gl-mb-2
= milestone_date_range(milestone) = milestone_date_range(milestone)
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone) - recent_releases, total_count, more_count = recent_releases_with_counts(milestone, current_user)
- unless total_count == 0 - unless total_count == 0
.text-tertiary.gl-mb-2.milestone-release-links .text-tertiary.gl-mb-2.milestone-release-links
= sprite_icon("rocket", size: 12) = sprite_icon("rocket", size: 12)

View file

@ -138,7 +138,7 @@
= milestone.merge_requests.merged.count = milestone.merge_requests.merged.count
- if project - if project
- recent_releases, total_count, more_count = recent_releases_with_counts(milestone) - recent_releases, total_count, more_count = recent_releases_with_counts(milestone, current_user)
.block.releases .block.releases
.sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } } .sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } }
%strong %strong

View file

@ -0,0 +1,46 @@
# frozen_string_literal: true
module Net
class HTTPResponse
# rubocop: disable Cop/LineBreakAfterGuardClauses
# rubocop: disable Cop/LineBreakAroundConditionalBlock
# rubocop: disable Layout/EmptyLineAfterGuardClause
# rubocop: disable Style/AndOr
# rubocop: disable Style/CharacterLiteral
# rubocop: disable Style/InfiniteLoop
# Original method:
# https://github.com/ruby/ruby/blob/v2_7_5/lib/net/http/response.rb#L54-L69
#
# Our changes:
# - Pass along the `start_time` to `Gitlab::BufferedIo`, so we can raise a timeout
# if reading the headers takes too long.
# - Limit the regexes to avoid ReDoS attacks.
def self.each_response_header(sock)
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
key = value = nil
while true
line = sock.is_a?(Gitlab::BufferedIo) ? sock.readuntil("\n", true, start_time) : sock.readuntil("\n", true)
line = line.sub(/\s{0,10}\z/, '')
break if line.empty?
if line[0] == ?\s or line[0] == ?\t and value
# :nocov:
value << ' ' unless value.empty?
value << line.strip
# :nocov:
else
yield key, value if key
key, value = line.strip.split(/\s{0,10}:\s{0,10}/, 2)
raise Net::HTTPBadResponse, 'wrong header line format' if value.nil?
end
end
yield key, value if key
end
# rubocop: enable Cop/LineBreakAfterGuardClauses
# rubocop: enable Cop/LineBreakAroundConditionalBlock
# rubocop: enable Layout/EmptyLineAfterGuardClause
# rubocop: enable Style/AndOr
# rubocop: enable Style/CharacterLiteral
# rubocop: enable Style/InfiniteLoop
end
end

View file

@ -1,28 +0,0 @@
# This is a template for a feature deprecation
# A deprecation typically occurs when a feature or capability is planned to be removed in a future release.
# Deprecations should be announced at least two releases prior to removal. Any breaking changes should only be done in major releases.
#
# Below is an example of what a single entry should look like, it's required attributes,
# and what types we expect those attribute values to be.
#
# For more information please refer to the handbook documentation here:
# https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations
#
# Please delete this line and above before submitting your merge request.
- name: "Changes to the `CI_JOB_JWT`" # The name of the feature to be deprecated
announcement_milestone: "14.8" # The milestone when this feature was first announced as deprecated.
announcement_date: "2022-02-22" # The date of the milestone release when this feature was first announced as deprecated. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
removal_milestone: "15.0" # The milestone when this feature is planned to be removed
removal_date: "2022-05-22" # The date of the milestone release when this feature is planned to be removed. This should almost always be the 22nd of a month (YYYY-MM-22), unless you did an out of band blog post.
breaking_change: true # If this deprecation is a breaking change, set this value to true
reporter: dhershkovitch # GitLab username of the person reporting the deprecation
body: | # Do not modify this line, instead modify the lines below.
The `CI_JOB_JWT` will be updated to support a wider variety of cloud providers. It will be changed to match [`CI_JOB_JWT_V2`](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html), but this change may not be backwards compatible for all users, including Hashicorp Vault users. To maintain the current behavior, users can switch to using `CI_JOB_JWT_V1`, or update their configuration in GitLab 15.0 to use the improved `CI_JOB_JWT`.
# The following items are not published on the docs page, but may be used in the future.
stage: # (optional - may be required in the future) String value of the stage that the feature was created in. e.g., Growth
tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
issue_url: # (optional) This is a link to the deprecation issue in GitLab
documentation_url: # (optional) This is a link to the current documentation page
image_url: # (optional) This is a link to a thumbnail image depicting the feature
video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg

View file

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddInstallableConanPackagesIndexToPackages < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'idx_installable_conan_pkgs_on_project_id_id'
# as defined by Packages::Package.package_types
CONAN_PACKAGE_TYPE = 3
# as defined by Packages::Package::INSTALLABLE_STATUSES
DEFAULT_STATUS = 0
HIDDEN_STATUS = 1
def up
where = "package_type = #{CONAN_PACKAGE_TYPE} AND status IN (#{DEFAULT_STATUS}, #{HIDDEN_STATUS})"
add_concurrent_index :packages_packages,
[:project_id, :id],
where: where,
name: INDEX_NAME
end
def down
remove_concurrent_index_by_name :packages_packages, INDEX_NAME
end
end

View file

@ -0,0 +1,7 @@
# frozen_string_literal: true
class AddSentryProjectIdToProjectErrorTrackingSettings < Gitlab::Database::Migration[2.0]
def change
add_column :project_error_tracking_settings, :sentry_project_id, :bigint
end
end

View file

@ -0,0 +1 @@
1fdb60b1c72b687aa8bede083ac7038097d538dc815e334d74296b1d39c2acb8

View file

@ -0,0 +1 @@
1f03beba0775e2a4eead512819592f590b02b70096cee250dfcdf426440cb5f5

View file

@ -19156,7 +19156,8 @@ CREATE TABLE project_error_tracking_settings (
encrypted_token_iv character varying, encrypted_token_iv character varying,
project_name character varying, project_name character varying,
organization_name character varying, organization_name character varying,
integrated boolean DEFAULT true NOT NULL integrated boolean DEFAULT true NOT NULL,
sentry_project_id bigint
); );
CREATE TABLE project_export_jobs ( CREATE TABLE project_export_jobs (
@ -26544,6 +26545,8 @@ CREATE UNIQUE INDEX idx_environment_merge_requests_unique_index ON deployment_me
CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id); CREATE INDEX idx_geo_con_rep_updated_events_on_container_repository_id ON geo_container_repository_updated_events USING btree (container_repository_id);
CREATE INDEX idx_installable_conan_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id) WHERE ((package_type = 3) AND (status = ANY (ARRAY[0, 1])));
CREATE INDEX idx_installable_helm_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id); CREATE INDEX idx_installable_helm_pkgs_on_project_id_id ON packages_packages USING btree (project_id, id);
CREATE INDEX idx_installable_npm_pkgs_on_project_id_name_version_id ON packages_packages USING btree (project_id, name, version, id) WHERE ((package_type = 2) AND (status = 0)); CREATE INDEX idx_installable_npm_pkgs_on_project_id_name_version_id ON packages_packages USING btree (project_id, name, version, id) WHERE ((package_type = 2) AND (status = 0));

View file

@ -359,7 +359,8 @@ and will be removed in [GitLab 16.0](https://gitlab.com/gitlab-org/gitlab/-/issu
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3. > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/15432) in GitLab 10.3.
List jobs that are being processed or were processed by specified runner. List jobs that are being processed or were processed by the specified runner. The list of jobs is limited
to projects where the user has at least the Reporter role.
```plaintext ```plaintext
GET /runners/:id/jobs GET /runners/:id/jobs

View file

@ -106,7 +106,7 @@ button and a link to the GitLab issue displays within the error detail section.
## Taking Action on errors ## Taking Action on errors
You can take action on Sentry Errors from within the GitLab UI. You can take action on Sentry Errors from within the GitLab UI. Marking errors ignored or resolved require at least Developer role.
### Ignoring errors ### Ignoring errors

View file

@ -212,18 +212,6 @@ Any API calls attempting to change the rate limits for `user_email_lookup_limit`
## 14.8 ## 14.8
### Changes to the `CI_JOB_JWT`
WARNING:
This feature will be changed or removed in 15.0
as a [breaking change](https://docs.gitlab.com/ee/development/contributing/#breaking-changes).
Before updating GitLab, review the details carefully to determine if you need to make any
changes to your code, settings, or workflow.
The `CI_JOB_JWT` will be updated to support a wider variety of cloud providers. It will be changed to match [`CI_JOB_JWT_V2`](https://docs.gitlab.com/ee/ci/variables/predefined_variables.html), but this change may not be backwards compatible for all users, including Hashicorp Vault users. To maintain the current behavior, users can switch to using `CI_JOB_JWT_V1`, or update their configuration in GitLab 15.0 to use the improved `CI_JOB_JWT`.
**Planned removal milestone: 15.0 (2022-05-22)**
### Configurable Gitaly `per_repository` election strategy ### Configurable Gitaly `per_repository` election strategy
Configuring the `per_repository` Gitaly election strategy is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/352612). Configuring the `per_repository` Gitaly election strategy is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/352612).

View file

@ -407,6 +407,9 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap
- The upgrade to GitLab 14.10 executes a [concurrent index drop](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84308) of unneeded - The upgrade to GitLab 14.10 executes a [concurrent index drop](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/84308) of unneeded
entries from the `ci_job_artifacts` database table. This could potentially run for multiple minutes, especially if the table has a lot of entries from the `ci_job_artifacts` database table. This could potentially run for multiple minutes, especially if the table has a lot of
traffic and the migration is unable to acquire a lock. It is advised to let this process finish as restarting may result in data loss. traffic and the migration is unable to acquire a lock. It is advised to let this process finish as restarting may result in data loss.
- Unauthenticated requests to the [`ciConfig` GraphQL field](../api/graphql/reference/index.md#queryciconfig) are no longer supported.
Before you upgrade to GitLab 15.1, add an [access token](../api/index.md#authentication) to your requests.
The user creating the token must have [permission](../user/permissions.md) to create pipelines in the project.
### 14.9.0 ### 14.9.0

View file

@ -640,6 +640,7 @@ To restrict group access by IP address:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/7297) in GitLab 12.2.
> - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1. > - Support for specifying multiple email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/33143) in GitLab 13.1.
> - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2. > - Support for restricting access to projects in the group [added](https://gitlab.com/gitlab-org/gitlab/-/issues/14004) in GitLab 14.1.2.
> - Support for restricting group memberships to groups with a subset of the allowed email domains [added](https://gitlab.com/gitlab-org/gitlab/-/issues/354791) in GitLab 15.0.1
You can prevent users with email addresses in specific domains from being added to a group and its projects. You can prevent users with email addresses in specific domains from being added to a group and its projects.
@ -662,6 +663,8 @@ The most popular public email domains cannot be restricted, such as:
- `hotmail.com`, `hotmail.co.uk`, `hotmail.fr` - `hotmail.com`, `hotmail.co.uk`, `hotmail.fr`
- `msn.com`, `live.com`, `outlook.com` - `msn.com`, `live.com`, `outlook.com`
When you share a group, both the source and target namespaces must allow the domains of the members' email addresses.
## Group file templates **(PREMIUM)** ## Group file templates **(PREMIUM)**
Use group file templates to share a set of templates for common file Use group file templates to share a set of templates for common file

View file

@ -127,7 +127,7 @@ module API
runner = get_runner(params[:id]) runner = get_runner(params[:id])
authenticate_list_runners_jobs!(runner) authenticate_list_runners_jobs!(runner)
jobs = ::Ci::RunnerJobsFinder.new(runner, params).execute jobs = ::Ci::RunnerJobsFinder.new(runner, current_user, params).execute
present paginate(jobs), with: Entities::Ci::JobBasicWithProject present paginate(jobs), with: Entities::Ci::JobBasicWithProject
end end

View file

@ -82,8 +82,14 @@ module API
params.delete(:label_id) params.delete(:label_id)
params.delete(:name) params.delete(:name)
label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label) update_params = declared_params(include_missing: false)
render_validation_error!(label) unless label.valid?
if update_params.present?
authorize! :admin_label, label
label = ::Labels::UpdateService.new(update_params).execute(label)
render_validation_error!(label) unless label.valid?
end
if parent.is_a?(Project) && update_priority if parent.is_a?(Project) && update_priority
if priority.nil? if priority.nil?
@ -97,10 +103,10 @@ module API
end end
def delete_label(parent) def delete_label(parent)
authorize! :admin_label, parent
label = find_label(parent, params_id_or_title, include_ancestor_groups: false) label = find_label(parent, params_id_or_title, include_ancestor_groups: false)
authorize! :admin_label, label
destroy_conditionally!(label) destroy_conditionally!(label)
end end

View file

@ -10,20 +10,8 @@ module BulkImports
<<-'GRAPHQL' <<-'GRAPHQL'
query($full_path: ID!) { query($full_path: ID!) {
project(fullPath: $full_path) { project(fullPath: $full_path) {
description
visibility visibility
archived
created_at: createdAt created_at: createdAt
shared_runners_enabled: sharedRunnersEnabled
container_registry_enabled: containerRegistryEnabled
only_allow_merge_if_pipeline_succeeds: onlyAllowMergeIfPipelineSucceeds
only_allow_merge_if_all_discussions_are_resolved: onlyAllowMergeIfAllDiscussionsAreResolved
request_access_enabled: requestAccessEnabled
printing_merge_request_link_enabled: printingMergeRequestLinkEnabled
remove_source_branch_after_merge: removeSourceBranchAfterMerge
autoclose_referenced_issues: autocloseReferencedIssues
suggestion_commit_message: suggestionCommitMessage
wiki_enabled: wikiEnabled
} }
} }
GRAPHQL GRAPHQL

View file

@ -7,16 +7,18 @@ module BulkImports
PROJECT_IMPORT_TYPE = 'gitlab_project_migration' PROJECT_IMPORT_TYPE = 'gitlab_project_migration'
def transform(context, data) def transform(context, data)
project = {}
entity = context.entity entity = context.entity
visibility = data.delete('visibility') visibility = data.delete('visibility')
data['name'] = entity.destination_name project[:name] = entity.destination_name
data['path'] = entity.destination_name.parameterize project[:path] = entity.destination_name.parameterize
data['import_type'] = PROJECT_IMPORT_TYPE project[:created_at] = data['created_at']
data['visibility_level'] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present? project[:import_type] = PROJECT_IMPORT_TYPE
data['namespace_id'] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present? project[:visibility_level] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present?
project[:namespace_id] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present?
data.transform_keys!(&:to_sym) project
end end
end end
end end

View file

@ -15,6 +15,7 @@ module ErrorTracking
stack_trace = parse_stack_trace(event) stack_trace = parse_stack_trace(event)
Gitlab::ErrorTracking::ErrorEvent.new( Gitlab::ErrorTracking::ErrorEvent.new(
project_id: event['projectID'],
issue_id: event['groupID'], issue_id: event['groupID'],
date_received: event['dateReceived'], date_received: event['dateReceived'],
stack_trace_entries: stack_trace stack_trace_entries: stack_trace

View file

@ -14,6 +14,7 @@ module Gitlab
HEADER_READ_TIMEOUT = 20 HEADER_READ_TIMEOUT = 20
# rubocop: disable Style/RedundantBegin
# rubocop: disable Style/RedundantReturn # rubocop: disable Style/RedundantReturn
# rubocop: disable Cop/LineBreakAfterGuardClauses # rubocop: disable Cop/LineBreakAfterGuardClauses
# rubocop: disable Layout/EmptyLineAfterGuardClause # rubocop: disable Layout/EmptyLineAfterGuardClause
@ -21,9 +22,7 @@ module Gitlab
# Original method: # Original method:
# https://github.com/ruby/ruby/blob/cdb7d699d0641e8f081d590d06d07887ac09961f/lib/net/protocol.rb#L190-L200 # https://github.com/ruby/ruby/blob/cdb7d699d0641e8f081d590d06d07887ac09961f/lib/net/protocol.rb#L190-L200
override :readuntil override :readuntil
def readuntil(terminator, ignore_eof = false) def readuntil(terminator, ignore_eof = false, start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC))
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
begin begin
until idx = @rbuf.index(terminator) until idx = @rbuf.index(terminator)
if (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) > HEADER_READ_TIMEOUT if (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) > HEADER_READ_TIMEOUT
@ -39,6 +38,7 @@ module Gitlab
return rbuf_consume(@rbuf.size) return rbuf_consume(@rbuf.size)
end end
end end
# rubocop: disable Style/RedundantBegin
# rubocop: enable Style/RedundantReturn # rubocop: enable Style/RedundantReturn
# rubocop: enable Cop/LineBreakAfterGuardClauses # rubocop: enable Cop/LineBreakAfterGuardClauses
# rubocop: enable Layout/EmptyLineAfterGuardClause # rubocop: enable Layout/EmptyLineAfterGuardClause

View file

@ -35,7 +35,8 @@ module Gitlab
trace_size_exceeded: 'log size limit exceeded', trace_size_exceeded: 'log size limit exceeded',
builds_disabled: 'project builds are disabled', builds_disabled: 'project builds are disabled',
environment_creation_failure: 'environment creation failure', environment_creation_failure: 'environment creation failure',
deployment_rejected: 'deployment rejected' deployment_rejected: 'deployment rejected',
ip_restriction_failure: 'IP address restriction failure'
}.freeze }.freeze
private_constant :REASONS private_constant :REASONS

View file

@ -7,7 +7,7 @@ module Gitlab
class ErrorEvent class ErrorEvent
include ActiveModel::Model include ActiveModel::Model
attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project attr_accessor :issue_id, :date_received, :stack_trace_entries, :gitlab_project, :project_id
def self.declarative_policy_class def self.declarative_policy_class
'ErrorTracking::BasePolicy' 'ErrorTracking::BasePolicy'

View file

@ -8,6 +8,8 @@ module Gitlab
DEFAULT_MAX_BYTES = 10.gigabytes.freeze DEFAULT_MAX_BYTES = 10.gigabytes.freeze
TIMEOUT_LIMIT = 210.seconds TIMEOUT_LIMIT = 210.seconds
ServiceError = Class.new(StandardError)
def initialize(archive_path:, max_bytes: self.class.max_bytes) def initialize(archive_path:, max_bytes: self.class.max_bytes)
@archive_path = archive_path @archive_path = archive_path
@max_bytes = max_bytes @max_bytes = max_bytes
@ -29,6 +31,8 @@ module Gitlab
pgrp = nil pgrp = nil
valid_archive = true valid_archive = true
validate_archive_path
Timeout.timeout(TIMEOUT_LIMIT) do Timeout.timeout(TIMEOUT_LIMIT) do
stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true) stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true)
stdin.close stdin.close
@ -78,15 +82,29 @@ module Gitlab
false false
end end
def validate_archive_path
Gitlab::Utils.check_path_traversal!(@archive_path)
raise(ServiceError, 'Archive path is not a string') unless @archive_path.is_a?(String)
raise(ServiceError, 'Archive path is a symlink') if File.lstat(@archive_path).symlink?
raise(ServiceError, 'Archive path is not a file') unless File.file?(@archive_path)
end
def command def command
"gzip -dc #{@archive_path} | wc -c" "gzip -dc #{@archive_path} | wc -c"
end end
def log_error(error) def log_error(error)
archive_size = begin
File.size(@archive_path)
rescue StandardError
nil
end
Gitlab::Import::Logger.info( Gitlab::Import::Logger.info(
message: error, message: error,
import_upload_archive_path: @archive_path, import_upload_archive_path: @archive_path,
import_upload_archive_size: File.size(@archive_path) import_upload_archive_size: archive_size
) )
end end
end end

View file

@ -38,7 +38,8 @@ module Gitlab
# @param [String] namespace # @param [String] namespace
def self.restore_full_path(namespace:, project:) def self.restore_full_path(namespace:, project:)
if project.include?(ENCODED_SLASH) if project.include?(ENCODED_SLASH)
project.gsub(ENCODED_SLASH, SLASH) # Replace multiple slashes with single ones to make sure the redirect stays on the same host
project.gsub(ENCODED_SLASH, SLASH).gsub(%r{\/{2,}}, '/')
else else
"#{namespace}/#{project}" "#{namespace}/#{project}"
end end

View file

@ -20839,6 +20839,9 @@ msgstr ""
msgid "Invited" msgid "Invited"
msgstr "" msgstr ""
msgid "Invited group allowed email domains must contain a subset of the allowed email domains of the root ancestor group. Go to the group's 'Settings &gt; General' page and check 'Restrict membership by email domain'."
msgstr ""
msgid "Invocations" msgid "Invocations"
msgstr "" msgstr ""

View file

@ -251,7 +251,7 @@ GEM
pry (~> 0.10) pry (~> 0.10)
public_suffix (4.0.6) public_suffix (4.0.6)
racc (1.6.0) racc (1.6.0)
rack (2.2.3) rack (2.2.3.1)
rack-test (1.1.0) rack-test (1.1.0)
rack (>= 1.0, < 3) rack (>= 1.0, < 3)
rainbow (3.0.0) rainbow (3.0.0)

View file

@ -297,40 +297,54 @@ RSpec.describe Projects::ErrorTrackingController do
put :update, params: issue_params(issue_id: issue_id, status: 'resolved', format: :json) put :update, params: issue_params(issue_id: issue_id, status: 'resolved', format: :json)
end end
before do
expect(ErrorTracking::IssueUpdateService)
.to receive(:new).with(project, user, permitted_params)
.and_return(issue_update_service)
end
describe 'format json' do describe 'format json' do
context 'update result is successful' do context 'when user is a reporter' do
before do before do
expect(issue_update_service).to receive(:execute) project.add_reporter(user)
.and_return(status: :success, updated: true, closed_issue_iid: non_existing_record_iid)
update_issue
end end
it 'returns a success' do it 'returns 404 error' do
expect(response).to have_gitlab_http_status(:ok) update_issue
expect(response).to match_response_schema('error_tracking/update_issue')
expect(response).to have_gitlab_http_status(:not_found)
end end
end end
context 'update result is erroneous' do context 'when user has access to update' do
let(:error_message) { 'error message' }
before do before do
expect(issue_update_service).to receive(:execute) expect(ErrorTracking::IssueUpdateService)
.and_return(status: :error, message: error_message) .to receive(:new).with(project, user, permitted_params)
.and_return(issue_update_service)
update_issue
end end
it 'returns 400 with message' do context 'when update result is successful' do
expect(response).to have_gitlab_http_status(:bad_request) before do
expect(json_response['message']).to eq(error_message) expect(issue_update_service).to receive(:execute)
.and_return(status: :success, updated: true, closed_issue_iid: non_existing_record_iid)
update_issue
end
it 'returns a success' do
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('error_tracking/update_issue')
end
end
context 'update result is erroneous' do
let(:error_message) { 'error message' }
before do
expect(issue_update_service).to receive(:execute)
.and_return(status: :error, message: error_message)
update_issue
end
it 'returns 400 with message' do
expect(response).to have_gitlab_http_status(:bad_request)
expect(json_response['message']).to eq(error_message)
end
end end
end end
end end

View file

@ -72,6 +72,7 @@ RSpec.describe 'Database schema' do
oauth_applications: %w[owner_id], oauth_applications: %w[owner_id],
product_analytics_events_experimental: %w[event_id txn_id user_id], product_analytics_events_experimental: %w[event_id txn_id user_id],
project_build_artifacts_size_refreshes: %w[last_job_artifact_id], project_build_artifacts_size_refreshes: %w[last_job_artifact_id],
project_error_tracking_settings: %w[sentry_project_id],
project_group_links: %w[group_id], project_group_links: %w[group_id],
project_statistics: %w[namespace_id], project_statistics: %w[namespace_id],
projects: %w[creator_id ci_id mirror_user_id], projects: %w[creator_id ci_id mirror_user_id],

View file

@ -13,7 +13,7 @@ FactoryBot.define do
message { 'message' } message { 'message' }
culprit { 'culprit' } culprit { 'culprit' }
external_url { 'http://example.com/id' } external_url { 'http://example.com/id' }
project_id { 'project1' } project_id { '111111' }
project_name { 'project name' } project_name { 'project name' }
project_slug { 'project_name' } project_slug { 'project_name' }
short_id { 'ID' } short_id { 'ID' }

View file

@ -9,6 +9,7 @@ FactoryBot.define do
project_name { 'Sentry Project' } project_name { 'Sentry Project' }
organization_name { 'Sentry Org' } organization_name { 'Sentry Org' }
integrated { false } integrated { false }
sentry_project_id { 10 }
trait :disabled do trait :disabled do
enabled { false } enabled { false }

View file

@ -5,12 +5,17 @@ require 'spec_helper'
RSpec.describe Ci::RunnerJobsFinder do RSpec.describe Ci::RunnerJobsFinder do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:runner) { create(:ci_runner, :instance) } let(:runner) { create(:ci_runner, :instance) }
let(:user) { create(:user) }
let(:params) { {} }
subject { described_class.new(runner, params).execute } subject { described_class.new(runner, user, params).execute }
before do
project.add_developer(user)
end
describe '#execute' do describe '#execute' do
context 'when params is empty' do context 'when params is empty' do
let(:params) { {} }
let!(:job) { create(:ci_build, runner: runner, project: project) } let!(:job) { create(:ci_build, runner: runner, project: project) }
let!(:job1) { create(:ci_build, project: project) } let!(:job1) { create(:ci_build, project: project) }
@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do
end end
end end
context 'when the user has guest access' do
it 'does not returns jobs the user does not have permission to see' do
another_project = create(:project)
job = create(:ci_build, runner: runner, project: another_project)
another_project.add_guest(user)
is_expected.not_to match_array(job)
end
end
context 'when the user has permission to read all resources' do
let(:user) { create(:user, :admin) }
it 'returns all the jobs assigned to a runner' do
jobs = create_list(:ci_build, 5, runner: runner, project: project)
is_expected.to match_array(jobs)
end
end
context 'when the user has different access levels in different projects' do
it 'returns only the jobs the user has permission to see' do
guest_project = create(:project)
reporter_project = create(:project)
_guest_jobs = create_list(:ci_build, 2, runner: runner, project: guest_project)
reporter_jobs = create_list(:ci_build, 3, runner: runner, project: reporter_project)
guest_project.add_guest(user)
reporter_project.add_reporter(user)
is_expected.to match_array(reporter_jobs)
end
end
context 'when the user has reporter access level or greater' do
it 'returns jobs assigned to the Runner that the user has accesss to' do
jobs = create_list(:ci_build, 3, runner: runner, project: project)
is_expected.to match_array(jobs)
end
end
context 'when params contains status' do context 'when params contains status' do
Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status| Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status|
context "when status is #{target_status}" do context "when status is #{target_status}" do

View file

@ -2,22 +2,53 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe ::Packages::Conan::PackageFinder do RSpec.describe ::Packages::Conan::PackageFinder do
using RSpec::Parameterized::TableSyntax
let_it_be_with_reload(:project) { create(:project) }
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :public) } let_it_be(:private_project) { create(:project, :private) }
let_it_be(:conan_package) { create(:conan_package, project: project) }
let_it_be(:conan_package2) { create(:conan_package, project: project) }
let_it_be(:errored_package) { create(:conan_package, :error, project: project) }
let_it_be(:private_package) { create(:conan_package, project: private_project) }
describe '#execute' do describe '#execute' do
let!(:conan_package) { create(:conan_package, project: project) } let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" }
let!(:conan_package2) { create(:conan_package, project: project) } let(:finder) { described_class.new(user, query: query) }
subject { described_class.new(user, query: query).execute } subject { finder.execute }
context 'packages that are not installable' do where(:visibility, :role, :packages_visible) do
let!(:conan_package3) { create(:conan_package, :error, project: project) } :private | :maintainer | true
let!(:non_visible_project) { create(:project, :private) } :private | :developer | true
let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) } :private | :reporter | true
let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" } :private | :guest | false
:private | :anonymous | false
it { is_expected.to eq [conan_package, conan_package2] } :internal | :maintainer | true
:internal | :developer | true
:internal | :reporter | true
:internal | :guest | true
:internal | :anonymous | false
:public | :maintainer | true
:public | :developer | true
:public | :reporter | true
:public | :guest | true
:public | :anonymous | true
end
with_them do
let(:expected_packages) { packages_visible ? [conan_package, conan_package2] : [] }
let(:user) { role == :anonymous ? nil : super() }
before do
project.update_column(:visibility_level, Gitlab::VisibilityLevel.string_options[visibility.to_s])
project.add_user(user, role) unless role == :anonymous
end
it { is_expected.to eq(expected_packages) }
end end
end end
end end

View file

@ -53,7 +53,7 @@
}, },
"user": { "user": {
"allOf": [ "allOf": [
{ "$ref": "member_user.json" } { "$ref": "member_user_default.json" }
] ]
}, },
"state": { "type": "integer" }, "state": { "type": "integer" },

View file

@ -0,0 +1,35 @@
{
"type": "object",
"required": [
"id",
"name",
"username",
"created_at",
"last_activity_on",
"avatar_url",
"web_url",
"blocked",
"show_status"
],
"properties": {
"id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"created_at": { "type": ["string"] },
"avatar_url": { "type": ["string", "null"] },
"web_url": { "type": "string" },
"blocked": { "type": "boolean" },
"two_factor_enabled": { "type": "boolean" },
"availability": { "type": ["string", "null"] },
"last_activity_on": { "type": ["string", "null"] },
"status": {
"type": "object",
"required": ["emoji"],
"properties": {
"emoji": { "type": "string" }
},
"additionalProperties": false
},
"show_status": { "type": "boolean" }
}
}

View file

@ -154,4 +154,21 @@ describe('AccessDropdown', () => {
expect(template).not.toContain(user.name); expect(template).not.toContain(user.name);
}); });
}); });
describe('deployKeyRowHtml', () => {
const deployKey = {
id: 1,
title: 'title <script>alert(document.domain)</script>',
fullname: 'fullname <script>alert(document.domain)</script>',
avatar_url: '',
username: '',
};
it('escapes deploy key title and fullname', () => {
const template = dropdown.deployKeyRowHtml(deployKey);
expect(template).not.toContain(deployKey.title);
expect(template).not.toContain(deployKey.fullname);
});
});
}); });

View file

@ -7,24 +7,13 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
describe '#resolve' do describe '#resolve' do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user, namespace: user.namespace) } let_it_be(:project) { create(:project, :repository) }
let_it_be(:sha) { nil } let_it_be(:sha) { nil }
let_it_be(:content) do let_it_be(:content) do
File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml')) File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml'))
end end
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_return(fake_result)
ci_lint_double
end
before do
allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
end
subject(:response) do subject(:response) do
resolve(described_class, resolve(described_class,
args: { project_path: project.full_path, content: content, sha: sha }, args: { project_path: project.full_path, content: content, sha: sha },
@ -49,51 +38,76 @@ RSpec.describe Resolvers::Ci::ConfigResolver do
end end
end end
context 'with a valid .gitlab-ci.yml' do context 'when the user can create a pipeline' do
context 'with a sha' do
let(:sha) { '1231231' }
it_behaves_like 'a valid config file'
end
context 'without a sha' do
it_behaves_like 'a valid config file'
end
end
context 'with an invalid .gitlab-ci.yml' do
let(:content) { 'invalid' }
let(:fake_result) do
Gitlab::Ci::Lint::Result.new(
jobs: [],
merged_yaml: content,
errors: ['Invalid configuration format'],
warnings: []
)
end
it 'responds with errors about invalid syntax' do
expect(response[:status]).to eq(:invalid)
expect(response[:errors]).to eq(['Invalid configuration format'])
end
end
context 'with an invalid SHA' do
let_it_be(:sha) { ':' }
let(:ci_lint) do let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint) ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument) allow(ci_lint_double).to receive(:validate).and_return(fake_result)
ci_lint_double ci_lint_double
end end
it 'logs the invalid SHA to Sentry' do before do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception) allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint)
.with(GRPC::InvalidArgument, sha: ':')
response project.add_developer(user)
end
context 'with a valid .gitlab-ci.yml' do
context 'with a sha' do
let(:sha) { '1231231' }
it_behaves_like 'a valid config file'
end
context 'without a sha' do
it_behaves_like 'a valid config file'
end
end
context 'with an invalid .gitlab-ci.yml' do
let(:content) { 'invalid' }
let(:fake_result) do
Gitlab::Ci::Lint::Result.new(
jobs: [],
merged_yaml: content,
errors: ['Invalid configuration format'],
warnings: []
)
end
it 'responds with errors about invalid syntax' do
expect(response[:status]).to eq(:invalid)
expect(response[:errors]).to match_array(['Invalid configuration format'])
end
end
context 'with an invalid SHA' do
let_it_be(:sha) { ':' }
let(:ci_lint) do
ci_lint_double = instance_double(::Gitlab::Ci::Lint)
allow(ci_lint_double).to receive(:validate).and_raise(GRPC::InvalidArgument)
ci_lint_double
end
it 'logs the invalid SHA to Sentry' do
expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception)
.with(GRPC::InvalidArgument, sha: ':')
response
end
end
end
context 'when the user cannot create a pipeline' do
before do
project.add_guest(user)
end
it 'returns an error stating that the user cannot access the linting' do
expect { response }.to raise_error(::Gitlab::Graphql::Errors::ResourceNotAvailable)
end end
end end
end end

View file

@ -149,19 +149,4 @@ RSpec.describe IntegrationsHelper do
end end
end end
end end
describe '#jira_issue_breadcrumb_link' do
let(:issue_reference) { nil }
subject { helper.jira_issue_breadcrumb_link(issue_reference) }
context 'when issue_reference contains HTML' do
let(:issue_reference) { "<script>alert('XSS')</script>" }
it 'escapes issue reference' do
is_expected.not_to include(issue_reference)
is_expected.to include(html_escape(issue_reference))
end
end
end
end end

View file

@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do
context 'api_url present' do context 'api_url present' do
let(:json) do let(:json) do
{ {
sentry_project_id: error_tracking_setting.sentry_project_id,
name: error_tracking_setting.project_name, name: error_tracking_setting.project_name,
organization_name: error_tracking_setting.organization_name, organization_name: error_tracking_setting.organization_name,
organization_slug: error_tracking_setting.organization_slug, organization_slug: error_tracking_setting.organization_slug,

View file

@ -38,4 +38,23 @@ RSpec.describe TimeboxesHelper do
end end
end end
end end
describe "#recent_releases_with_counts" do
let_it_be(:milestone) { create(:milestone) }
let_it_be(:project) { milestone.project }
let_it_be(:user) { create(:user) }
subject { helper.recent_releases_with_counts(milestone, user) }
before do
project.add_developer(user)
end
it "returns releases with counts" do
_old_releases = create_list(:release, 2, project: project, milestones: [milestone])
recent_public_releases = create_list(:release, 3, project: project, milestones: [milestone], released_at: '2022-01-01T18:00:00Z')
is_expected.to match([match_array(recent_public_releases), 5, 2])
end
end
end end

View file

@ -0,0 +1,79 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'Net::HTTPResponse patch header read timeout' do
describe '.each_response_header' do
let(:server_response) do
<<~EOS
Content-Type: text/html
Header-Two: foo
Hello World
EOS
end
before do
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
end
subject(:each_response_header) { Net::HTTPResponse.each_response_header(socket) { |k, v| } }
context 'with Net::BufferedIO' do
let(:socket) { Net::BufferedIO.new(StringIO.new(server_response)) }
it 'does not forward start time to the socket' do
allow(socket).to receive(:readuntil).and_call_original
expect(socket).to receive(:readuntil).with("\n", true)
each_response_header
end
context 'when the response contains many consecutive spaces' do
before do
expect(socket).to receive(:readuntil).and_return(
"a: #{' ' * 100_000} b",
''
)
end
it 'has no regex backtracking issues' do
Timeout.timeout(1) do
each_response_header
end
end
end
end
context 'with Gitlab::BufferedIo' do
let(:mock_io) { StringIO.new(server_response) }
let(:socket) { Gitlab::BufferedIo.new(mock_io) }
it 'forwards start time to the socket' do
allow(socket).to receive(:readuntil).and_call_original
expect(socket).to receive(:readuntil).with("\n", true, kind_of(Numeric))
each_response_header
end
context 'when the response contains an infinite number of headers' do
before do
read_counter = 0
allow(mock_io).to receive(:read_nonblock) do
read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
sleep 0.01
+"Yet-Another-Header: foo\n"
end
end
it 'raises a timeout error' do
expect { each_response_header }.to raise_error(Gitlab::HTTP::HeaderReadTimeout,
/Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
end
end
end
end

View file

@ -25,18 +25,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
let(:project_data) do let(:project_data) do
{ {
'visibility' => 'private', 'visibility' => 'private',
'created_at' => 10.days.ago, 'created_at' => '2016-08-12T09:41:03'
'archived' => false,
'shared_runners_enabled' => true,
'container_registry_enabled' => true,
'only_allow_merge_if_pipeline_succeeds' => true,
'only_allow_merge_if_all_discussions_are_resolved' => true,
'request_access_enabled' => true,
'printing_merge_request_link_enabled' => true,
'remove_source_branch_after_merge' => true,
'autoclose_referenced_issues' => true,
'suggestion_commit_message' => 'message',
'wiki_enabled' => true
} }
end end
@ -58,17 +47,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do
expect(imported_project).not_to be_nil expect(imported_project).not_to be_nil
expect(imported_project.group).to eq(group) expect(imported_project.group).to eq(group)
expect(imported_project.suggestion_commit_message).to eq('message') expect(imported_project.visibility).to eq(project_data['visibility'])
expect(imported_project.archived?).to eq(project_data['archived']) expect(imported_project.created_at).to eq(project_data['created_at'])
expect(imported_project.shared_runners_enabled?).to eq(project_data['shared_runners_enabled'])
expect(imported_project.container_registry_enabled?).to eq(project_data['container_registry_enabled'])
expect(imported_project.only_allow_merge_if_pipeline_succeeds?).to eq(project_data['only_allow_merge_if_pipeline_succeeds'])
expect(imported_project.only_allow_merge_if_all_discussions_are_resolved?).to eq(project_data['only_allow_merge_if_all_discussions_are_resolved'])
expect(imported_project.request_access_enabled?).to eq(project_data['request_access_enabled'])
expect(imported_project.printing_merge_request_link_enabled?).to eq(project_data['printing_merge_request_link_enabled'])
expect(imported_project.remove_source_branch_after_merge?).to eq(project_data['remove_source_branch_after_merge'])
expect(imported_project.autoclose_referenced_issues?).to eq(project_data['autoclose_referenced_issues'])
expect(imported_project.wiki_enabled?).to eq(project_data['wiki_enabled'])
end end
end end

View file

@ -25,8 +25,8 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
let(:data) do let(:data) do
{ {
'name' => 'source_name', 'visibility' => 'private',
'visibility' => 'private' 'created_at' => '2016-11-18T09:29:42.634Z'
} }
end end
@ -76,8 +76,21 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer
end end
end end
it 'converts all keys to symbols' do context 'when data has extra keys' do
expect(transformed_data.keys).to contain_exactly(:name, :path, :import_type, :visibility_level, :namespace_id) it 'returns a fixed number of keys' do
data = {
'visibility' => 'private',
'created_at' => '2016-11-18T09:29:42.634Z',
'my_key' => 'my_key',
'another_key' => 'another_key',
'last_key' => 'last_key'
}
transformed_data = described_class.new.transform(context, data)
expect(transformed_data.keys)
.to contain_exactly(:created_at, :import_type, :name, :namespace_id, :path, :visibility_level)
end
end end
end end
end end

View file

@ -1,54 +1,50 @@
# rubocop:disable Style/FrozenStringLiteralComment # frozen_string_literal: true
require 'spec_helper' require 'spec_helper'
RSpec.describe Gitlab::BufferedIo do RSpec.describe Gitlab::BufferedIo do
describe '#readuntil' do describe '#readuntil' do
let(:never_ending_tcp_socket) do let(:mock_io) { StringIO.new('a') }
Class.new do let(:start_time) { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
def initialize(*_)
@read_counter = 0
end
def setsockopt(*_); end
def closed?
false
end
def close
true
end
def to_io
StringIO.new('Hello World!')
end
def write_nonblock(data, *_)
data.size
end
def read_nonblock(buffer_size, *_)
sleep 0.01
@read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if @read_counter > 10
'H' * buffer_size
end
end
end
before do before do
stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1) stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1)
end end
subject(:readuntil) do subject(:readuntil) do
Gitlab::BufferedIo.new(never_ending_tcp_socket.new).readuntil('a') Gitlab::BufferedIo.new(mock_io).readuntil('a', false, start_time)
end end
it 'raises a timeout error' do it 'does not raise a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/) expect { readuntil }.not_to raise_error
end
context 'when the response contains infinitely long headers' do
before do
read_counter = 0
allow(mock_io).to receive(:read_nonblock) do |buffer_size, *_|
read_counter += 1
raise 'Test did not raise HeaderReadTimeout' if read_counter > 10
sleep 0.01
'H' * buffer_size
end
end
it 'raises a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
context 'when not passing start_time' do
subject(:readuntil) do
Gitlab::BufferedIo.new(mock_io).readuntil('a', false)
end
it 'raises a timeout error' do
expect { readuntil }.to raise_error(Gitlab::HTTP::HeaderReadTimeout, /Request timed out after reading headers for 0\.[0-9]+ seconds/)
end
end
end end
end end
end end
# rubocop:enable Style/FrozenStringLiteralComment

View file

@ -86,6 +86,65 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do
include_examples 'logs raised exception and terminates validator process group' include_examples 'logs raised exception and terminates validator process group'
end end
end end
context 'archive path validation' do
let(:filesize) { nil }
before do
expect(Gitlab::Import::Logger)
.to receive(:info)
.with(
import_upload_archive_path: filepath,
import_upload_archive_size: filesize,
message: error_message
)
end
context 'when archive path is traversed' do
let(:filepath) { '/foo/../bar' }
let(:error_message) { 'Invalid path' }
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'when archive path is not a string' do
let(:filepath) { 123 }
let(:error_message) { 'Archive path is not a string' }
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'which archive path is a symlink' do
let(:filepath) { File.join(Dir.tmpdir, 'symlink') }
let(:error_message) { 'Archive path is a symlink' }
before do
FileUtils.ln_s(filepath, filepath, force: true)
end
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
context 'when archive path is not a file' do
let(:filepath) { Dir.mktmpdir }
let(:filesize) { File.size(filepath) }
let(:error_message) { 'Archive path is not a file' }
after do
FileUtils.rm_rf(filepath)
end
it 'returns false' do
expect(subject.valid?).to eq(false)
end
end
end
end end
def create_compressed_file def create_compressed_file

View file

@ -10,7 +10,9 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
let(:sentry_client) { instance_double(ErrorTracking::SentryClient) } let(:sentry_client) { instance_double(ErrorTracking::SentryClient) }
subject(:setting) { build(:project_error_tracking_setting, project: project) } let(:sentry_project_id) { 10 }
subject(:setting) { build(:project_error_tracking_setting, project: project, sentry_project_id: sentry_project_id) }
describe 'Associations' do describe 'Associations' do
it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:project) }
@ -270,7 +272,7 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end end
describe '#issue_details' do describe '#issue_details' do
let(:issue) { build(:error_tracking_sentry_detailed_error) } let(:issue) { build(:error_tracking_sentry_detailed_error, project_id: sentry_project_id) }
let(:commit_id) { issue.first_release_version } let(:commit_id) { issue.first_release_version }
let(:result) { subject.issue_details(opts) } let(:result) { subject.issue_details(opts) }
let(:opts) { { issue_id: 1 } } let(:opts) { { issue_id: 1 } }
@ -317,12 +319,33 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
end end
end end
describe '#issue_latest_event' do
let(:error_event) { build(:error_tracking_sentry_error_event, project_id: sentry_project_id) }
let(:result) { subject.issue_latest_event(opts) }
let(:opts) { { issue_id: 1 } }
before do
stub_reactive_cache(subject, error_event, {})
synchronous_reactive_cache(subject)
allow(subject).to receive(:sentry_client).and_return(sentry_client)
allow(sentry_client).to receive(:issue_latest_event).with(opts).and_return(error_event)
end
it 'returns the error event' do
expect(result[:latest_event].project_id).to eq(sentry_project_id)
end
end
describe '#update_issue' do describe '#update_issue' do
let(:result) { subject.update_issue(**opts) } let(:result) { subject.update_issue(**opts) }
let(:opts) { { issue_id: 1, params: {} } } let(:opts) { { issue_id: 1, params: {} } }
before do before do
allow(subject).to receive(:sentry_client).and_return(sentry_client) allow(subject).to receive(:sentry_client).and_return(sentry_client)
allow(sentry_client).to receive(:issue_details)
.with({ issue_id: 1 })
.and_return(Gitlab::ErrorTracking::DetailedError.new(project_id: sentry_project_id))
end end
context 'when sentry response is successful' do context 'when sentry response is successful' do
@ -344,6 +367,56 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do
expect(result).to eq(error: 'Unexpected Error') expect(result).to eq(error: 'Unexpected Error')
end end
end end
context 'when sentry_project_id is not set' do
let(:sentry_projects) do
[
Gitlab::ErrorTracking::Project.new(
id: 1111,
name: 'Some Project',
organization_name: 'Org'
),
Gitlab::ErrorTracking::Project.new(
id: sentry_project_id,
name: setting.project_name,
organization_name: setting.organization_name
)
]
end
context 'when sentry_project_id is not set' do
before do
setting.update!(sentry_project_id: nil)
allow(sentry_client).to receive(:projects).and_return(sentry_projects)
allow(sentry_client).to receive(:update_issue).with(opts).and_return(true)
end
it 'tries to backfill it from sentry API' do
expect(result).to eq(updated: true)
expect(setting.reload.sentry_project_id).to eq(sentry_project_id)
end
context 'when the project cannot be found on sentry' do
before do
sentry_projects.pop
end
it 'raises error' do
expect { result }.to raise_error(/Couldn't find project/)
end
end
end
context 'when mismatching sentry_project_id is detected' do
it 'raises error' do
setting.update!(sentry_project_id: sentry_project_id + 1)
expect { result }.to raise_error(/The Sentry issue appers to be outside/)
end
end
end
end end
describe 'slugs' do describe 'slugs' do

View file

@ -4045,6 +4045,41 @@ RSpec.describe User do
end end
end end
describe '#authorized_project_mirrors' do
it 'returns project mirrors where the user has access equal to or above the given level' do
guest_project = create(:project)
reporter_project = create(:project)
maintainer_project = create(:project)
guest_group = create(:group)
reporter_group = create(:group)
maintainer_group = create(:group)
_guest_group_project = create(:project, group: guest_group)
reporter_group_project = create(:project, group: reporter_group)
maintainer_group_project = create(:project, group: maintainer_group)
user = create(:user)
guest_project.add_guest(user)
reporter_project.add_reporter(user)
maintainer_project.add_maintainer(user)
guest_group.add_guest(user)
reporter_group.add_reporter(user)
maintainer_group.add_maintainer(user)
project_mirrors = user.authorized_project_mirrors(Gitlab::Access::REPORTER)
expect(project_mirrors.pluck(:project_id)).to contain_exactly(
reporter_group_project.id,
maintainer_group_project.id,
reporter_project.id,
maintainer_project.id
)
end
end
shared_context '#ci_owned_runners' do shared_context '#ci_owned_runners' do
let(:user) { create(:user) } let(:user) { create(:user) }

View file

@ -1023,25 +1023,117 @@ RSpec.describe ProjectPolicy do
subject { described_class.new(deploy_token, project) } subject { described_class.new(deploy_token, project) }
context 'a deploy token with read_package_registry scope' do context 'private project' do
let(:deploy_token) { create(:deploy_token, read_package_registry: true) } let(:project) { private_project }
it { is_expected.to be_allowed(:read_package) } context 'a deploy token with read_registry scope' do
it { is_expected.to be_allowed(:read_project) } let(:deploy_token) { create(:deploy_token, read_registry: true, write_registry: false) }
it { is_expected.to be_disallowed(:create_package) }
it_behaves_like 'package access with repository disabled' it { is_expected.to be_allowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
context 'with registry disabled' do
include_context 'registry disabled via project features'
it { is_expected.to be_disallowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
end
end
context 'a deploy token with write_registry scope' do
let(:deploy_token) { create(:deploy_token, read_registry: false, write_registry: true) }
it { is_expected.to be_disallowed(:read_container_image) }
it { is_expected.to be_allowed(:create_container_image) }
context 'with registry disabled' do
include_context 'registry disabled via project features'
it { is_expected.to be_disallowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
end
end
context 'a deploy token with no registry scope' do
let(:deploy_token) { create(:deploy_token, read_registry: false, write_registry: false) }
it { is_expected.to be_disallowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
end
context 'a deploy token with read_package_registry scope' do
let(:deploy_token) { create(:deploy_token, read_repository: false, read_registry: false, read_package_registry: true) }
it { is_expected.to be_allowed(:read_project) }
it { is_expected.to be_allowed(:read_package) }
it { is_expected.to be_disallowed(:create_package) }
it_behaves_like 'package access with repository disabled'
end
context 'a deploy token with write_package_registry scope' do
let(:deploy_token) { create(:deploy_token, read_repository: false, read_registry: false, write_package_registry: true) }
it { is_expected.to be_allowed(:create_package) }
it { is_expected.to be_allowed(:read_package) }
it { is_expected.to be_allowed(:read_project) }
it { is_expected.to be_disallowed(:destroy_package) }
it_behaves_like 'package access with repository disabled'
end
end end
context 'a deploy token with write_package_registry scope' do context 'public project' do
let(:deploy_token) { create(:deploy_token, write_package_registry: true) } let(:project) { public_project }
it { is_expected.to be_allowed(:create_package) } context 'a deploy token with read_registry scope' do
it { is_expected.to be_allowed(:read_package) } let(:deploy_token) { create(:deploy_token, read_registry: true, write_registry: false) }
it { is_expected.to be_allowed(:read_project) }
it { is_expected.to be_disallowed(:destroy_package) }
it_behaves_like 'package access with repository disabled' it { is_expected.to be_allowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
context 'with registry disabled' do
include_context 'registry disabled via project features'
it { is_expected.to be_disallowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
end
context 'with registry private' do
include_context 'registry set to private via project features'
it { is_expected.to be_allowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
end
end
context 'a deploy token with write_registry scope' do
let(:deploy_token) { create(:deploy_token, read_registry: false, write_registry: true) }
it { is_expected.to be_allowed(:read_container_image) }
it { is_expected.to be_allowed(:create_container_image) }
context 'with registry disabled' do
include_context 'registry disabled via project features'
it { is_expected.to be_disallowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
end
context 'with registry private' do
include_context 'registry set to private via project features'
it { is_expected.to be_allowed(:read_container_image) }
it { is_expected.to be_allowed(:create_container_image) }
end
end
context 'a deploy token with no registry scope' do
let(:deploy_token) { create(:deploy_token, read_registry: false, write_registry: false) }
it { is_expected.to be_disallowed(:read_container_image) }
it { is_expected.to be_disallowed(:create_container_image) }
end
end end
end end
@ -2102,4 +2194,25 @@ RSpec.describe ProjectPolicy do
it { is_expected.to be_disallowed(:register_project_runners) } it { is_expected.to be_disallowed(:register_project_runners) }
end end
end end
describe 'update_sentry_issue' do
using RSpec::Parameterized::TableSyntax
where(:role, :allowed) do
:owner | true
:maintainer | true
:developer | true
:reporter | false
:guest | false
end
let(:project) { public_project }
let(:current_user) { public_send(role) }
with_them do
it do
expect(subject.can?(:update_sentry_issue)).to be(allowed)
end
end
end
end end

View file

@ -804,6 +804,23 @@ RSpec.describe API::Ci::Runners do
expect(json_response).to be_an(Array) expect(json_response).to be_an(Array)
expect(json_response.length).to eq(2) expect(json_response.length).to eq(2)
end end
context 'when user does not have authorization to see all jobs' do
it 'shows only jobs it has permission to see' do
create(:ci_build, :running, runner: two_projects_runner, project: project)
create(:ci_build, :running, runner: two_projects_runner, project: project2)
project.add_guest(user2)
project2.add_maintainer(user2)
get api("/runners/#{two_projects_runner.id}/jobs", user2)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to include_pagination_headers
expect(json_response).to be_an(Array)
expect(json_response.length).to eq(1)
end
end
end end
context 'when valid status is provided' do context 'when valid status is provided' do

View file

@ -483,6 +483,29 @@ RSpec.describe API::Labels do
let(:request) { api("/projects/#{project.id}/labels", user) } let(:request) { api("/projects/#{project.id}/labels", user) }
let(:params) { { name: valid_label_title_1 } } let(:params) { { name: valid_label_title_1 } }
end end
context 'with group label' do
let_it_be(:group) { create(:group) }
let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) }
before do
project.update!(group: group)
end
it 'returns 401 if user does not have access' do
delete api("/projects/#{project.id}/labels/#{group_label.id}", user)
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'returns 204 if user has access' do
group.add_developer(user)
delete api("/projects/#{project.id}/labels/#{group_label.id}", user)
expect(response).to have_gitlab_http_status(:no_content)
end
end
end end
describe 'PUT /projects/:id/labels' do describe 'PUT /projects/:id/labels' do
@ -537,6 +560,44 @@ RSpec.describe API::Labels do
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
end end
context 'with group label' do
let_it_be(:group) { create(:group) }
let_it_be(:group_label) { create(:group_label, title: valid_group_label_title_1, group: group) }
before do
project.update!(group: group)
end
it 'allows updating of group label priority' do
put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: { priority: 5 }
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['priority']).to eq(5)
end
it 'returns 401 when updating other fields' do
put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: {
priority: 5,
new_name: 'new label name'
}
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'returns 200 when user has access to the group label' do
group.add_developer(user)
put api("/projects/#{project.id}/labels/#{group_label.id}", user), params: {
priority: 5,
new_name: 'new label name'
}
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['priority']).to eq(5)
expect(json_response['name']).to eq('new label name')
end
end
end end
describe 'PUT /projects/:id/labels/promote' do describe 'PUT /projects/:id/labels/promote' do

View file

@ -25,27 +25,49 @@ RSpec.describe 'Jira referenced paths', type: :request do
expect(response).to redirect_to(redirect_path) expect(response).to redirect_to(redirect_path)
end end
context 'with encoded subgroup path' do shared_examples 'redirects to jira path' do
where(:jira_path, :redirect_path) do it 'redirects to canonical path with legacy prefix' do
'/group/group@sub_group@sub_group_project' | '/group/sub_group/sub_group_project' redirects_to_canonical_path "/-/jira#{jira_path}", redirect_path
'/group@sub_group/group@sub_group@sub_group_project' | '/group/sub_group/sub_group_project'
'/group/group@sub_group@sub_group_project/commit/1234567' | '/group/sub_group/sub_group_project/commit/1234567'
'/group/group@sub_group@sub_group_project/tree/1234567' | '/group/sub_group/sub_group_project/-/tree/1234567'
end end
with_them do it 'redirects to canonical path' do
context 'with legacy prefix' do redirects_to_canonical_path jira_path, redirect_path
it 'redirects to canonical path' do
redirects_to_canonical_path "/-/jira#{jira_path}", redirect_path
end
end
it 'redirects to canonical path' do
redirects_to_canonical_path jira_path, redirect_path
end
end end
end end
let(:jira_path) { '/group/group@sub_group@sub_group_project' }
let(:redirect_path) { '/group/sub_group/sub_group_project' }
it_behaves_like 'redirects to jira path'
context 'contains @ before the first /' do
let(:jira_path) { '/group@sub_group/group@sub_group@sub_group_project' }
let(:redirect_path) { '/group/sub_group/sub_group_project' }
it_behaves_like 'redirects to jira path'
end
context 'including commit path' do
let(:jira_path) { '/group/group@sub_group@sub_group_project/commit/1234567' }
let(:redirect_path) { '/group/sub_group/sub_group_project/commit/1234567' }
it_behaves_like 'redirects to jira path'
end
context 'including tree path' do
let(:jira_path) { '/group/group@sub_group@sub_group_project/tree/1234567' }
let(:redirect_path) { '/group/sub_group/sub_group_project/-/tree/1234567' }
it_behaves_like 'redirects to jira path'
end
context 'malicious path' do
let(:jira_path) { '/group/@@malicious.server' }
let(:redirect_path) { '/malicious.server' }
it_behaves_like 'redirects to jira path'
end
context 'regular paths with legacy prefix' do context 'regular paths with legacy prefix' do
where(:jira_path, :redirect_path) do where(:jira_path, :redirect_path) do
'/-/jira/group/group_project' | '/group/group_project' '/-/jira/group/group_project' | '/group/group_project'

View file

@ -11,7 +11,7 @@ RSpec.describe MemberUserEntity do
let(:entity_hash) { entity.as_json } let(:entity_hash) { entity.as_json }
it 'matches json schema' do it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user') expect(entity.to_json).to match_schema('entities/member_user_default')
end end
it 'correctly exposes `avatar_url`' do it 'correctly exposes `avatar_url`' do
@ -27,10 +27,8 @@ RSpec.describe MemberUserEntity do
expect(entity_hash[:blocked]).to be(true) expect(entity_hash[:blocked]).to be(true)
end end
it 'correctly exposes `two_factor_enabled`' do it 'does not expose `two_factor_enabled` by default' do
allow(user).to receive(:two_factor_enabled?).and_return(true) expect(entity_hash[:two_factor_enabled]).to be(nil)
expect(entity_hash[:two_factor_enabled]).to be(true)
end end
it 'correctly exposes `status.emoji`' do it 'correctly exposes `status.emoji`' do
@ -44,4 +42,66 @@ RSpec.describe MemberUserEntity do
it 'correctly exposes `last_activity_on`' do it 'correctly exposes `last_activity_on`' do
expect(entity_hash[:last_activity_on]).to be(user.last_activity_on) expect(entity_hash[:last_activity_on]).to be(user.last_activity_on)
end end
context 'when options includes a source' do
let(:current_user) { create(:user) }
let(:options) { { current_user: current_user, source: source } }
let(:entity) { described_class.new(user, options) }
shared_examples 'correctly exposes user two_factor_enabled' do
context 'when the current_user has a role lower than minimum manage member role' do
before do
source.add_user(current_user, Gitlab::Access::DEVELOPER)
end
it 'does not expose user two_factor_enabled' do
expect(entity_hash[:two_factor_enabled]).to be(nil)
end
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user_default')
end
end
context 'when the current user has a minimum manage member role or higher' do
before do
source.add_user(current_user, minimum_manage_member_role)
end
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user_for_admin_member')
end
it 'exposes user two_factor_enabled' do
expect(entity_hash[:two_factor_enabled]).to be(false)
end
end
context 'when the current user is self' do
let(:current_user) { user }
it 'exposes user two_factor_enabled' do
expect(entity_hash[:two_factor_enabled]).to be(false)
end
it 'matches json schema' do
expect(entity.to_json).to match_schema('entities/member_user_for_admin_member')
end
end
end
context 'when the source is a group' do
let(:source) { create(:group) }
let(:minimum_manage_member_role) { Gitlab::Access::OWNER }
it_behaves_like 'correctly exposes user two_factor_enabled'
end
context 'when the source is a project' do
let(:source) { create(:project) }
let(:minimum_manage_member_role) { Gitlab::Access::MAINTAINER }
it_behaves_like 'correctly exposes user two_factor_enabled'
end
end
end end

View file

@ -80,7 +80,8 @@ RSpec.describe BulkImports::FileDecompressionService do
subject { described_class.new(tmpdir: tmpdir, filename: 'symlink.gz') } subject { described_class.new(tmpdir: tmpdir, filename: 'symlink.gz') }
it 'raises an error and removes the file' do it 'raises an error and removes the file' do
expect { subject.execute }.to raise_error(described_class::ServiceError, 'Invalid file') expect { subject.execute }
.to raise_error(BulkImports::FileDecompressionService::ServiceError, 'File decompression error')
expect(File.exist?(symlink)).to eq(false) expect(File.exist?(symlink)).to eq(false)
end end

View file

@ -294,7 +294,7 @@ RSpec.describe Issues::CreateService do
context 'user is reporter or above' do context 'user is reporter or above' do
before do before do
project.add_reporter(user) project.add_developer(user)
end end
it 'assigns the sentry error' do it 'assigns the sentry error' do

View file

@ -0,0 +1,28 @@
# frozen_string_literal: true
RSpec.shared_context 'repository disabled via project features' do
before do
project.project_feature.update_columns(
# Disable merge_requests and builds as well, since merge_requests and
# builds cannot have higher visibility than repository.
merge_requests_access_level: ProjectFeature::DISABLED,
builds_access_level: ProjectFeature::DISABLED,
repository_access_level: ProjectFeature::DISABLED)
end
end
RSpec.shared_context 'registry disabled via project features' do
before do
project.project_feature.update_columns(
container_registry_access_level: ProjectFeature::DISABLED
)
end
end
RSpec.shared_context 'registry set to private via project features' do
before do
project.project_feature.update_columns(
container_registry_access_level: ProjectFeature::PRIVATE
)
end
end

View file

@ -16,6 +16,6 @@ RSpec.shared_context 'sentry error tracking context' do
before do before do
expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting) expect(project).to receive(:error_tracking_setting).at_least(:once).and_return(error_tracking_setting)
project.add_reporter(user) project.add_developer(user)
end end
end end

View file

@ -345,16 +345,7 @@ RSpec.shared_examples 'project policies as admin without admin mode' do
end end
RSpec.shared_examples 'package access with repository disabled' do RSpec.shared_examples 'package access with repository disabled' do
context 'when repository is disabled' do include_context 'repository disabled via project features'
before do
project.project_feature.update!(
# Disable merge_requests and builds as well, since merge_requests and
# builds cannot have higher visibility than repository.
merge_requests_access_level: ProjectFeature::DISABLED,
builds_access_level: ProjectFeature::DISABLED,
repository_access_level: ProjectFeature::DISABLED)
end
it { is_expected.to be_allowed(:read_package) } it { is_expected.to be_allowed(:read_package) }
end
end end

View file

@ -19,33 +19,66 @@ RSpec.shared_examples 'conan ping endpoint' do
end end
RSpec.shared_examples 'conan search endpoint' do RSpec.shared_examples 'conan search endpoint' do
before do using RSpec::Parameterized::TableSyntax
project.update_column(:visibility_level, Gitlab::VisibilityLevel::PUBLIC)
# Do not pass the HTTP_AUTHORIZATION header,
# in order to test that this public project's packages
# are visible to anonymous search.
get api(url), params: params
end
subject { json_response['results'] } subject { json_response['results'] }
context 'returns packages with a matching name' do context 'with a public project' do
let(:params) { { q: package.conan_recipe } } before do
project.update!(visibility: 'public')
it { is_expected.to contain_exactly(package.conan_recipe) } # Do not pass the HTTP_AUTHORIZATION header,
# in order to test that this public project's packages
# are visible to anonymous search.
get api(url), params: params
end
context 'returns packages with a matching name' do
let(:params) { { q: package.conan_recipe } }
it { is_expected.to contain_exactly(package.conan_recipe) }
end
context 'returns packages using a * wildcard' do
let(:params) { { q: "#{package.name[0, 3]}*" } }
it { is_expected.to contain_exactly(package.conan_recipe) }
end
context 'does not return non-matching packages' do
let(:params) { { q: "foo" } }
it { is_expected.to be_blank }
end
end end
context 'returns packages using a * wildcard' do context 'with a private project' do
let(:params) { { q: "#{package.name[0, 3]}*" } } let(:params) { { q: "#{package.name[0, 3]}*" } }
it { is_expected.to contain_exactly(package.conan_recipe) } where(:role, :packages_visible) do
end :maintainer | true
:developer | true
:reporter | true
:guest | false
:anonymous | false
end
context 'does not return non-matching packages' do with_them do
let(:params) { { q: "foo" } } before do
project.update!(visibility: 'private')
project.team.truncate
user.project_authorizations.delete_all
project.add_user(user, role) unless role == :anonymous
it { is_expected.to be_blank } get api(url), params: params, headers: headers
end
if params[:packages_visible]
it { is_expected.to contain_exactly(package.conan_recipe) }
else
it { is_expected.to be_blank }
end
end
end end
end end

View file

@ -142,9 +142,9 @@ RSpec.shared_examples 'logs an auth warning' do |requested_actions|
requested_project_path: project.full_path, requested_project_path: project.full_path,
requested_actions: requested_actions, requested_actions: requested_actions,
authorized_actions: [], authorized_actions: [],
user_id: current_user.id, user_id: current_user&.id,
username: current_user.username username: current_user&.username
} }.compact
end end
it do it do

View file

@ -34,7 +34,7 @@ GEM
parallel (1.19.2) parallel (1.19.2)
parser (2.7.2.0) parser (2.7.2.0)
ast (~> 2.4.1) ast (~> 2.4.1)
rack (2.2.3) rack (2.2.3.1)
rainbow (3.0.0) rainbow (3.0.0)
regexp_parser (1.8.2) regexp_parser (1.8.2)
rexml (3.2.4) rexml (3.2.4)