From 394d455fd1c2d91f90d0fa9dfcf2c27a1474b638 Mon Sep 17 00:00:00 2001 From: Pirate Praveen Date: Fri, 1 Jul 2022 11:34:44 +0530 Subject: [PATCH] New upstream version 14.10.5+ds1 --- .rubocop_todo/layout/line_length.yml | 1 - CHANGELOG.md | 22 +++ GITALY_SERVER_VERSION | 2 +- Gemfile.lock | 2 +- VERSION | 2 +- .../error_tracking_settings/utils.js | 3 +- .../projects/settings/access_dropdown.js | 2 +- .../projects/error_tracking_controller.rb | 1 + .../settings/operations_controller.rb | 2 +- app/finders/ci/runner_jobs_finder.rb | 12 +- app/finders/packages/conan/package_finder.rb | 2 +- app/graphql/resolvers/ci/config_resolver.rb | 2 +- app/helpers/integrations_helper.rb | 21 --- app/helpers/projects_helper.rb | 1 + app/helpers/timeboxes_helper.rb | 4 +- app/models/ci/project_mirror.rb | 2 + app/models/clusters/applications/runner.rb | 2 +- app/models/concerns/enums/ci/commit_status.rb | 3 +- .../project_error_tracking_setting.rb | 24 +++ app/models/group_group_link.rb | 2 + app/models/user.rb | 14 +- app/policies/project_policy.rb | 28 +++- app/presenters/commit_status_presenter.rb | 3 +- app/serializers/member_user_entity.rb | 14 +- ...ntainer_registry_authentication_service.rb | 6 +- .../projects/operations/update_service.rb | 3 +- .../shared/milestones/_milestone.html.haml | 2 +- .../shared/milestones/_sidebar.html.haml | 2 +- .../initializers/net_http_response_patch.rb | 46 ++++++ data/deprecations/15-0-JTW_v2_update.yml | 28 ---- ...llable_conan_packages_index_to_packages.rb | 25 ++++ ...t_id_to_project_error_tracking_settings.rb | 7 + db/schema_migrations/20220520120637 | 1 + db/schema_migrations/20220525084153 | 1 + db/structure.sql | 5 +- doc/api/runners.md | 3 +- doc/operations/error_tracking.md | 2 +- doc/update/deprecations.md | 12 -- doc/update/index.md | 3 + doc/user/group/index.md | 3 + lib/api/ci/runners.rb | 2 +- lib/api/helpers/label_helpers.rb | 14 +- .../projects/graphql/get_project_query.rb | 12 -- .../project_attributes_transformer.rb | 14 +- lib/error_tracking/sentry_client/event.rb | 1 + lib/gitlab/buffered_io.rb | 6 +- lib/gitlab/ci/status/build/failed.rb | 3 +- lib/gitlab/error_tracking/error_event.rb | 2 +- .../decompressed_archive_size_validator.rb | 20 ++- lib/gitlab/jira/dvcs.rb | 3 +- locale/gitlab.pot | 3 + qa/Gemfile.lock | 2 +- .../error_tracking_controller_spec.rb | 62 +++++--- spec/db/schema_spec.rb | 1 + spec/factories/error_tracking/error.rb | 2 +- .../project_error_tracking_settings.rb | 1 + spec/finders/ci/runner_jobs_finder_spec.rb | 53 ++++++- .../packages/conan/package_finder_spec.rb | 51 +++++-- .../fixtures/api/schemas/entities/member.json | 2 +- .../schemas/entities/member_user_default.json | 35 +++++ ...json => member_user_for_admin_member.json} | 0 .../projects/settings/access_dropdown_spec.js | 17 +++ .../resolvers/ci/config_resolver_spec.rb | 114 +++++++------- spec/helpers/integrations_helper_spec.rb | 15 -- spec/helpers/projects_helper_spec.rb | 1 + spec/helpers/timeboxes_helper_spec.rb | 19 +++ .../net_http_response_patch_spec.rb | 79 ++++++++++ .../pipelines/project_pipeline_spec.rb | 26 +--- .../project_attributes_transformer_spec.rb | 21 ++- spec/lib/gitlab/buffered_io_spec.rb | 74 +++++----- ...ecompressed_archive_size_validator_spec.rb | 59 ++++++++ .../project_error_tracking_setting_spec.rb | 77 +++++++++- spec/models/user_spec.rb | 35 +++++ spec/policies/project_policy_spec.rb | 139 ++++++++++++++++-- spec/requests/api/ci/runners_spec.rb | 17 +++ spec/requests/api/labels_spec.rb | 61 ++++++++ spec/requests/jira_routing_spec.rb | 54 +++++-- spec/serializers/member_user_entity_spec.rb | 70 ++++++++- .../file_decompression_service_spec.rb | 3 +- spec/services/issues/create_service_spec.rb | 2 +- .../project_features_shared_context.rb | 28 ++++ .../sentry_error_tracking_shared_context.rb | 2 +- .../project_policy_shared_examples.rb | 13 +- .../api/conan_packages_shared_examples.rb | 67 ++++++--- ...r_registry_auth_service_shared_examples.rb | 6 +- tooling/overcommit/Gemfile.lock | 2 +- 86 files changed, 1249 insertions(+), 361 deletions(-) create mode 100644 config/initializers/net_http_response_patch.rb delete mode 100644 data/deprecations/15-0-JTW_v2_update.yml create mode 100644 db/migrate/20220520120637_add_installable_conan_packages_index_to_packages.rb create mode 100644 db/migrate/20220525084153_add_sentry_project_id_to_project_error_tracking_settings.rb create mode 100644 db/schema_migrations/20220520120637 create mode 100644 db/schema_migrations/20220525084153 create mode 100644 spec/fixtures/api/schemas/entities/member_user_default.json rename spec/fixtures/api/schemas/entities/{member_user.json => member_user_for_admin_member.json} (100%) create mode 100644 spec/initializers/net_http_response_patch_spec.rb create mode 100644 spec/support/shared_contexts/project_features_shared_context.rb diff --git a/.rubocop_todo/layout/line_length.yml b/.rubocop_todo/layout/line_length.yml index 8a922a1f16..f49d99360a 100644 --- a/.rubocop_todo/layout/line_length.yml +++ b/.rubocop_todo/layout/line_length.yml @@ -1979,7 +1979,6 @@ Layout/LineLength: - '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_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/override_ldap_memberships_spec.rb' - 'ee/spec/features/groups/saml_providers_spec.rb' diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f665427a5..52ae31c7c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ documentation](doc/development/changelog.md) for instructions on adding your own 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) ### Security (7 changes) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index e8ebae9eba..ce21932daf 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -14.10.4 \ No newline at end of file +14.10.5 \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index 9a67aa1b01..067f0ccfad 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -958,7 +958,7 @@ GEM pyu-ruby-sasl (0.0.3.3) raabro (1.1.6) racc (1.6.0) - rack (2.2.3) + rack (2.2.3.1) rack-accept (0.4.5) rack (>= 0.4) rack-attack (6.3.0) diff --git a/VERSION b/VERSION index e8ebae9eba..ce21932daf 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -14.10.4 \ No newline at end of file +14.10.5 \ No newline at end of file diff --git a/app/assets/javascripts/error_tracking_settings/utils.js b/app/assets/javascripts/error_tracking_settings/utils.js index 7ef5f7bbd3..47a42dc374 100644 --- a/app/assets/javascripts/error_tracking_settings/utils.js +++ b/app/assets/javascripts/error_tracking_settings/utils.js @@ -1,4 +1,4 @@ -export const projectKeys = ['name', 'organizationName', 'organizationSlug', 'slug']; +export const projectKeys = ['id', 'name', 'organizationName', 'organizationSlug', 'slug']; export const transformFrontendSettings = ({ apiHost, @@ -9,6 +9,7 @@ export const transformFrontendSettings = ({ }) => { const project = selectedProject ? { + sentry_project_id: selectedProject.id, slug: selectedProject.slug, name: selectedProject.name, organization_name: selectedProject.organizationName, diff --git a/app/assets/javascripts/projects/settings/access_dropdown.js b/app/assets/javascripts/projects/settings/access_dropdown.js index 7fb7a416dc..79dfa166b1 100644 --- a/app/assets/javascripts/projects/settings/access_dropdown.js +++ b/app/assets/javascripts/projects/settings/access_dropdown.js @@ -537,7 +537,7 @@ export default class AccessDropdown { return `
  • - ${key.title} + ${escape(key.title)}

    ${sprintf( __('Owned by %{image_tag}'), diff --git a/app/controllers/projects/error_tracking_controller.rb b/app/controllers/projects/error_tracking_controller.rb index 06383d2613..d2e36ef549 100644 --- a/app/controllers/projects/error_tracking_controller.rb +++ b/app/controllers/projects/error_tracking_controller.rb @@ -4,6 +4,7 @@ class Projects::ErrorTrackingController < Projects::ErrorTracking::BaseControlle respond_to :json before_action :authorize_read_sentry_issue! + before_action :authorize_update_sentry_issue!, only: %i[update] before_action :set_issue_id, only: :details before_action only: [:index] do diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb index 43c72b358d..199173cb64 100644 --- a/app/controllers/projects/settings/operations_controller.rb +++ b/app/controllers/projects/settings/operations_controller.rb @@ -143,7 +143,7 @@ module Projects :integrated, :api_host, :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], diff --git a/app/finders/ci/runner_jobs_finder.rb b/app/finders/ci/runner_jobs_finder.rb index 9dc3c2a242..b659eda664 100644 --- a/app/finders/ci/runner_jobs_finder.rb +++ b/app/finders/ci/runner_jobs_finder.rb @@ -6,19 +6,29 @@ module Ci ALLOWED_INDEXED_COLUMNS = %w[id].freeze - def initialize(runner, params = {}) + def initialize(runner, current_user, params = {}) @runner = runner + @user = current_user @params = params end def execute items = @runner.builds + items = by_permission(items) items = by_status(items) sort_items(items) end 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 def by_status(items) return items unless Ci::HasStatus::AVAILABLE_STATUSES.include?(params[:status]) diff --git a/app/finders/packages/conan/package_finder.rb b/app/finders/packages/conan/package_finder.rb index 8ebdd358ba..210b37635b 100644 --- a/app/finders/packages/conan/package_finder.rb +++ b/app/finders/packages/conan/package_finder.rb @@ -25,7 +25,7 @@ module Packages end 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 diff --git a/app/graphql/resolvers/ci/config_resolver.rb b/app/graphql/resolvers/ci/config_resolver.rb index f9d6065044..ccde39522c 100644 --- a/app/graphql/resolvers/ci/config_resolver.rb +++ b/app/graphql/resolvers/ci/config_resolver.rb @@ -12,7 +12,7 @@ module Resolvers Should not be requested more than once per request. MD - authorize :read_pipeline + authorize :create_pipeline argument :project_path, GraphQL::Types::ID, required: true, diff --git a/app/helpers/integrations_helper.rb b/app/helpers/integrations_helper.rb index b960ed46ba..471ca425f8 100644 --- a/app/helpers/integrations_helper.rb +++ b/app/helpers/integrations_helper.rb @@ -159,27 +159,6 @@ module IntegrationsHelper !Gitlab.com? 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 private diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 21c7a54670..1e427efe9d 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -298,6 +298,7 @@ module ProjectsHelper setting.organization_slug.blank? { + sentry_project_id: setting.sentry_project_id, name: setting.project_name, organization_name: setting.organization_name, organization_slug: setting.organization_slug, diff --git a/app/helpers/timeboxes_helper.rb b/app/helpers/timeboxes_helper.rb index c81fbcbfd1..39993bbfb4 100644 --- a/app/helpers/timeboxes_helper.rb +++ b/app/helpers/timeboxes_helper.rb @@ -153,11 +153,11 @@ module TimeboxesHelper n_("%{releases} release", "%{releases} releases", count) % { releases: count } end - def recent_releases_with_counts(milestone) + def recent_releases_with_counts(milestone, user) total_count = milestone.releases.size 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 [recent_releases, total_count, more_count] end diff --git a/app/models/ci/project_mirror.rb b/app/models/ci/project_mirror.rb index 9000d1791a..15a161d5b7 100644 --- a/app/models/ci/project_mirror.rb +++ b/app/models/ci/project_mirror.rb @@ -4,6 +4,8 @@ module Ci # 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. class ProjectMirror < ApplicationRecord + include FromUnion + belongs_to :project scope :by_namespace_id, -> (namespace_id) { where(namespace_id: namespace_id) } diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb index e62b6fa5fc..74b09f935e 100644 --- a/app/models/clusters/applications/runner.rb +++ b/app/models/clusters/applications/runner.rb @@ -3,7 +3,7 @@ module Clusters module Applications class Runner < ApplicationRecord - VERSION = '0.39.0' + VERSION = '0.39.2' self.table_name = 'clusters_applications_runners' diff --git a/app/models/concerns/enums/ci/commit_status.rb b/app/models/concerns/enums/ci/commit_status.rb index 312b88a4d6..445277a7a7 100644 --- a/app/models/concerns/enums/ci/commit_status.rb +++ b/app/models/concerns/enums/ci/commit_status.rb @@ -35,7 +35,8 @@ module Enums bridge_pipeline_is_child_pipeline: 1_006, # not used anymore, but cannot be deleted because of old data downstream_pipeline_creation_failed: 1_007, 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 diff --git a/app/models/error_tracking/project_error_tracking_setting.rb b/app/models/error_tracking/project_error_tracking_setting.rb index 3ecfb895da..30382a1c20 100644 --- a/app/models/error_tracking/project_error_tracking_setting.rb +++ b/app/models/error_tracking/project_error_tracking_setting.rb @@ -125,17 +125,22 @@ module ErrorTracking def issue_details(opts = {}) with_reactive_cache('issue_details', opts.stringify_keys) do |result| + ensure_issue_belongs_to_project!(result[:issue].project_id) result end end def issue_latest_event(opts = {}) with_reactive_cache('issue_latest_event', opts.stringify_keys) do |result| + ensure_issue_belongs_to_project!(result[:latest_event].project_id) result end end 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 { updated: sentry_client.update_issue(opts) } end @@ -177,6 +182,25 @@ module ErrorTracking 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) 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 diff --git a/app/models/group_group_link.rb b/app/models/group_group_link.rb index b0020f097b..a70110c407 100644 --- a/app/models/group_group_link.rb +++ b/app/models/group_group_link.rb @@ -41,3 +41,5 @@ class GroupGroupLink < ApplicationRecord Gitlab::Access.human_access(self.group_access) end end + +GroupGroupLink.prepend_mod_with('GroupGroupLink') diff --git a/app/models/user.rb b/app/models/user.rb index 26d47de4f0..25ac394793 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1646,6 +1646,14 @@ class User < ApplicationRecord true 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 @ci_owned_runners ||= begin if ci_owned_runners_cross_joins_fix_enabled? @@ -2116,6 +2124,10 @@ class User < ApplicationRecord end # 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 return if notification_email.blank? || temp_oauth_email? @@ -2267,7 +2279,7 @@ class User < ApplicationRecord end 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 .joins(:runner_projects) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 493afd9136..7c40d02b44 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -54,7 +54,13 @@ class ProjectPolicy < BasePolicy desc "Container registry is disabled" 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 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) 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" condition(:read_package_registry_deploy_token) do 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_merge_request enable :read_sentry_issue - enable :update_sentry_issue enable :read_prometheus enable :read_metrics_dashboard_annotation enable :metrics_dashboard @@ -413,6 +428,7 @@ class ProjectPolicy < BasePolicy enable :admin_feature_flags_user_lists enable :update_escalation_status enable :read_secure_files + enable :update_sentry_issue end rule { can?(:developer_access) & user_confirmed? }.policy do @@ -685,6 +701,14 @@ class ProjectPolicy < BasePolicy enable :push_code 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 enable :read_package enable :read_project diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb index fdfcc896bf..675288da35 100644 --- a/app/presenters/commit_status_presenter.rb +++ b/app/presenters/commit_status_presenter.rb @@ -30,7 +30,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated trace_size_exceeded: 'The job log size limit was reached', 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.', - 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 TROUBLESHOOTING_DOC = { diff --git a/app/serializers/member_user_entity.rb b/app/serializers/member_user_entity.rb index b3d8efc914..6a01c5bb29 100644 --- a/app/serializers/member_user_entity.rb +++ b/app/serializers/member_user_entity.rb @@ -16,7 +16,7 @@ class MemberUserEntity < UserEntity user.blocked? 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? end @@ -25,6 +25,18 @@ class MemberUserEntity < UserEntity user.status.emoji 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 MemberUserEntity.prepend_mod_with('MemberUserEntity') diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index 6d6d8641d9..e806bef46f 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -215,15 +215,13 @@ module Auth def deploy_token_can_pull?(requested_project) has_authentication_ability?(:read_container_image) && deploy_token.present? && - deploy_token.has_access_to?(requested_project) && - deploy_token.read_registry? + can?(deploy_token, :read_container_image, requested_project) end def deploy_token_can_push?(requested_project) has_authentication_ability?(:create_container_image) && deploy_token.present? && - deploy_token.has_access_to?(requested_project) && - deploy_token.write_registry? + can?(deploy_token, :create_container_image, requested_project) end ## diff --git a/app/services/projects/operations/update_service.rb b/app/services/projects/operations/update_service.rb index b66435d013..d8686f16dc 100644 --- a/app/services/projects/operations/update_service.rb +++ b/app/services/projects/operations/update_service.rb @@ -90,7 +90,8 @@ module Projects api_url: api_url, enabled: settings[:enabled], 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 diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index 4e06b7902b..4aecc0f6bd 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -14,7 +14,7 @@ - if milestone.due_date || milestone.start_date .text-tertiary.gl-mb-2 = 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 .text-tertiary.gl-mb-2.milestone-release-links = sprite_icon("rocket", size: 12) diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index a1e94172ec..ed5373fa58 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -138,7 +138,7 @@ = milestone.merge_requests.merged.count - 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 .sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } } %strong diff --git a/config/initializers/net_http_response_patch.rb b/config/initializers/net_http_response_patch.rb new file mode 100644 index 0000000000..4ffe227a3f --- /dev/null +++ b/config/initializers/net_http_response_patch.rb @@ -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 diff --git a/data/deprecations/15-0-JTW_v2_update.yml b/data/deprecations/15-0-JTW_v2_update.yml deleted file mode 100644 index 39880d7fbe..0000000000 --- a/data/deprecations/15-0-JTW_v2_update.yml +++ /dev/null @@ -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 diff --git a/db/migrate/20220520120637_add_installable_conan_packages_index_to_packages.rb b/db/migrate/20220520120637_add_installable_conan_packages_index_to_packages.rb new file mode 100644 index 0000000000..b26d1f5429 --- /dev/null +++ b/db/migrate/20220520120637_add_installable_conan_packages_index_to_packages.rb @@ -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 diff --git a/db/migrate/20220525084153_add_sentry_project_id_to_project_error_tracking_settings.rb b/db/migrate/20220525084153_add_sentry_project_id_to_project_error_tracking_settings.rb new file mode 100644 index 0000000000..248dd128be --- /dev/null +++ b/db/migrate/20220525084153_add_sentry_project_id_to_project_error_tracking_settings.rb @@ -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 diff --git a/db/schema_migrations/20220520120637 b/db/schema_migrations/20220520120637 new file mode 100644 index 0000000000..f379ef0d58 --- /dev/null +++ b/db/schema_migrations/20220520120637 @@ -0,0 +1 @@ +1fdb60b1c72b687aa8bede083ac7038097d538dc815e334d74296b1d39c2acb8 \ No newline at end of file diff --git a/db/schema_migrations/20220525084153 b/db/schema_migrations/20220525084153 new file mode 100644 index 0000000000..dbf7eaa0c9 --- /dev/null +++ b/db/schema_migrations/20220525084153 @@ -0,0 +1 @@ +1f03beba0775e2a4eead512819592f590b02b70096cee250dfcdf426440cb5f5 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index eaae14bebb..334d59db23 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -19156,7 +19156,8 @@ CREATE TABLE project_error_tracking_settings ( encrypted_token_iv character varying, project_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 ( @@ -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_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_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)); diff --git a/doc/api/runners.md b/doc/api/runners.md index 304f2494f7..6ecdd620d3 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -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. -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 GET /runners/:id/jobs diff --git a/doc/operations/error_tracking.md b/doc/operations/error_tracking.md index 907c59adac..3e47a86d74 100644 --- a/doc/operations/error_tracking.md +++ b/doc/operations/error_tracking.md @@ -106,7 +106,7 @@ button and a link to the GitLab issue displays within the error detail section. ## 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 diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md index 7e8eaef2b4..f78def780b 100644 --- a/doc/update/deprecations.md +++ b/doc/update/deprecations.md @@ -212,18 +212,6 @@ Any API calls attempting to change the rate limits for `user_email_lookup_limit` ## 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 Configuring the `per_repository` Gitaly election strategy is [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/352612). diff --git a/doc/update/index.md b/doc/update/index.md index 003c0fc02f..b2d31489ed 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -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 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. +- 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 diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 085cd054c1..4d22bb045d 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -640,6 +640,7 @@ To restrict group access by IP address: > - [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 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. @@ -662,6 +663,8 @@ The most popular public email domains cannot be restricted, such as: - `hotmail.com`, `hotmail.co.uk`, `hotmail.fr` - `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)** Use group file templates to share a set of templates for common file diff --git a/lib/api/ci/runners.rb b/lib/api/ci/runners.rb index 3c9e887e75..a4c6f9b682 100644 --- a/lib/api/ci/runners.rb +++ b/lib/api/ci/runners.rb @@ -127,7 +127,7 @@ module API runner = get_runner(params[:id]) 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 end diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb index 02613cbf9b..8572cc89e7 100644 --- a/lib/api/helpers/label_helpers.rb +++ b/lib/api/helpers/label_helpers.rb @@ -82,8 +82,14 @@ module API params.delete(:label_id) params.delete(:name) - label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label) - render_validation_error!(label) unless label.valid? + update_params = declared_params(include_missing: false) + + 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 priority.nil? @@ -97,10 +103,10 @@ module API end def delete_label(parent) - authorize! :admin_label, parent - label = find_label(parent, params_id_or_title, include_ancestor_groups: false) + authorize! :admin_label, label + destroy_conditionally!(label) end diff --git a/lib/bulk_imports/projects/graphql/get_project_query.rb b/lib/bulk_imports/projects/graphql/get_project_query.rb index b3d7f3f468..76475893ac 100644 --- a/lib/bulk_imports/projects/graphql/get_project_query.rb +++ b/lib/bulk_imports/projects/graphql/get_project_query.rb @@ -10,20 +10,8 @@ module BulkImports <<-'GRAPHQL' query($full_path: ID!) { project(fullPath: $full_path) { - description visibility - archived 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 diff --git a/lib/bulk_imports/projects/transformers/project_attributes_transformer.rb b/lib/bulk_imports/projects/transformers/project_attributes_transformer.rb index 24c55d8dbb..38730a7723 100644 --- a/lib/bulk_imports/projects/transformers/project_attributes_transformer.rb +++ b/lib/bulk_imports/projects/transformers/project_attributes_transformer.rb @@ -7,16 +7,18 @@ module BulkImports PROJECT_IMPORT_TYPE = 'gitlab_project_migration' def transform(context, data) + project = {} entity = context.entity visibility = data.delete('visibility') - data['name'] = entity.destination_name - data['path'] = entity.destination_name.parameterize - data['import_type'] = PROJECT_IMPORT_TYPE - data['visibility_level'] = Gitlab::VisibilityLevel.string_options[visibility] if visibility.present? - data['namespace_id'] = Namespace.find_by_full_path(entity.destination_namespace)&.id if entity.destination_namespace.present? + project[:name] = entity.destination_name + project[:path] = entity.destination_name.parameterize + project[:created_at] = data['created_at'] + project[:import_type] = PROJECT_IMPORT_TYPE + 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 diff --git a/lib/error_tracking/sentry_client/event.rb b/lib/error_tracking/sentry_client/event.rb index 5343eb7df5..1db31abeeb 100644 --- a/lib/error_tracking/sentry_client/event.rb +++ b/lib/error_tracking/sentry_client/event.rb @@ -15,6 +15,7 @@ module ErrorTracking stack_trace = parse_stack_trace(event) Gitlab::ErrorTracking::ErrorEvent.new( + project_id: event['projectID'], issue_id: event['groupID'], date_received: event['dateReceived'], stack_trace_entries: stack_trace diff --git a/lib/gitlab/buffered_io.rb b/lib/gitlab/buffered_io.rb index 91d5d8acc5..b76c8191fa 100644 --- a/lib/gitlab/buffered_io.rb +++ b/lib/gitlab/buffered_io.rb @@ -14,6 +14,7 @@ module Gitlab HEADER_READ_TIMEOUT = 20 + # rubocop: disable Style/RedundantBegin # rubocop: disable Style/RedundantReturn # rubocop: disable Cop/LineBreakAfterGuardClauses # rubocop: disable Layout/EmptyLineAfterGuardClause @@ -21,9 +22,7 @@ module Gitlab # Original method: # https://github.com/ruby/ruby/blob/cdb7d699d0641e8f081d590d06d07887ac09961f/lib/net/protocol.rb#L190-L200 override :readuntil - def readuntil(terminator, ignore_eof = false) - start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC) - + def readuntil(terminator, ignore_eof = false, start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)) begin until idx = @rbuf.index(terminator) if (elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - start_time) > HEADER_READ_TIMEOUT @@ -39,6 +38,7 @@ module Gitlab return rbuf_consume(@rbuf.size) end end + # rubocop: disable Style/RedundantBegin # rubocop: enable Style/RedundantReturn # rubocop: enable Cop/LineBreakAfterGuardClauses # rubocop: enable Layout/EmptyLineAfterGuardClause diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb index 5dd28157b1..1a074c1af5 100644 --- a/lib/gitlab/ci/status/build/failed.rb +++ b/lib/gitlab/ci/status/build/failed.rb @@ -35,7 +35,8 @@ module Gitlab trace_size_exceeded: 'log size limit exceeded', builds_disabled: 'project builds are disabled', environment_creation_failure: 'environment creation failure', - deployment_rejected: 'deployment rejected' + deployment_rejected: 'deployment rejected', + ip_restriction_failure: 'IP address restriction failure' }.freeze private_constant :REASONS diff --git a/lib/gitlab/error_tracking/error_event.rb b/lib/gitlab/error_tracking/error_event.rb index d80289f6bc..590fb82883 100644 --- a/lib/gitlab/error_tracking/error_event.rb +++ b/lib/gitlab/error_tracking/error_event.rb @@ -7,7 +7,7 @@ module Gitlab class ErrorEvent 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 'ErrorTracking::BasePolicy' diff --git a/lib/gitlab/import_export/decompressed_archive_size_validator.rb b/lib/gitlab/import_export/decompressed_archive_size_validator.rb index 61b3725696..a185eb4df1 100644 --- a/lib/gitlab/import_export/decompressed_archive_size_validator.rb +++ b/lib/gitlab/import_export/decompressed_archive_size_validator.rb @@ -8,6 +8,8 @@ module Gitlab DEFAULT_MAX_BYTES = 10.gigabytes.freeze TIMEOUT_LIMIT = 210.seconds + ServiceError = Class.new(StandardError) + def initialize(archive_path:, max_bytes: self.class.max_bytes) @archive_path = archive_path @max_bytes = max_bytes @@ -29,6 +31,8 @@ module Gitlab pgrp = nil valid_archive = true + validate_archive_path + Timeout.timeout(TIMEOUT_LIMIT) do stdin, stdout, stderr, wait_thr = Open3.popen3(command, pgroup: true) stdin.close @@ -78,15 +82,29 @@ module Gitlab false 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 "gzip -dc #{@archive_path} | wc -c" end def log_error(error) + archive_size = begin + File.size(@archive_path) + rescue StandardError + nil + end + Gitlab::Import::Logger.info( message: error, import_upload_archive_path: @archive_path, - import_upload_archive_size: File.size(@archive_path) + import_upload_archive_size: archive_size ) end end diff --git a/lib/gitlab/jira/dvcs.rb b/lib/gitlab/jira/dvcs.rb index ddf2cd7670..41a039674b 100644 --- a/lib/gitlab/jira/dvcs.rb +++ b/lib/gitlab/jira/dvcs.rb @@ -38,7 +38,8 @@ module Gitlab # @param [String] namespace def self.restore_full_path(namespace:, project:) 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 "#{namespace}/#{project}" end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index edad17df27..5ecd033296 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -20839,6 +20839,9 @@ msgstr "" msgid "Invited" 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 > General' page and check 'Restrict membership by email domain'." +msgstr "" + msgid "Invocations" msgstr "" diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index c4809a17f6..675d163fe9 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -251,7 +251,7 @@ GEM pry (~> 0.10) public_suffix (4.0.6) racc (1.6.0) - rack (2.2.3) + rack (2.2.3.1) rack-test (1.1.0) rack (>= 1.0, < 3) rainbow (3.0.0) diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb index b4f21e070c..6598d9b173 100644 --- a/spec/controllers/projects/error_tracking_controller_spec.rb +++ b/spec/controllers/projects/error_tracking_controller_spec.rb @@ -297,40 +297,54 @@ RSpec.describe Projects::ErrorTrackingController do put :update, params: issue_params(issue_id: issue_id, status: 'resolved', format: :json) end - before do - expect(ErrorTracking::IssueUpdateService) - .to receive(:new).with(project, user, permitted_params) - .and_return(issue_update_service) - end - describe 'format json' do - context 'update result is successful' do + context 'when user is a reporter' do before do - expect(issue_update_service).to receive(:execute) - .and_return(status: :success, updated: true, closed_issue_iid: non_existing_record_iid) - - update_issue + project.add_reporter(user) end - it 'returns a success' do - expect(response).to have_gitlab_http_status(:ok) - expect(response).to match_response_schema('error_tracking/update_issue') + it 'returns 404 error' do + update_issue + + expect(response).to have_gitlab_http_status(:not_found) end end - context 'update result is erroneous' do - let(:error_message) { 'error message' } - + context 'when user has access to update' do before do - expect(issue_update_service).to receive(:execute) - .and_return(status: :error, message: error_message) - - update_issue + expect(ErrorTracking::IssueUpdateService) + .to receive(:new).with(project, user, permitted_params) + .and_return(issue_update_service) end - it 'returns 400 with message' do - expect(response).to have_gitlab_http_status(:bad_request) - expect(json_response['message']).to eq(error_message) + context 'when update result is successful' do + before do + 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 diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 04f73050ea..bb7298bc12 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -72,6 +72,7 @@ RSpec.describe 'Database schema' do oauth_applications: %w[owner_id], product_analytics_events_experimental: %w[event_id txn_id user_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_statistics: %w[namespace_id], projects: %w[creator_id ci_id mirror_user_id], diff --git a/spec/factories/error_tracking/error.rb b/spec/factories/error_tracking/error.rb index bebdffb361..62d1624412 100644 --- a/spec/factories/error_tracking/error.rb +++ b/spec/factories/error_tracking/error.rb @@ -13,7 +13,7 @@ FactoryBot.define do message { 'message' } culprit { 'culprit' } external_url { 'http://example.com/id' } - project_id { 'project1' } + project_id { '111111' } project_name { 'project name' } project_slug { 'project_name' } short_id { 'ID' } diff --git a/spec/factories/project_error_tracking_settings.rb b/spec/factories/project_error_tracking_settings.rb index ed743d8283..a8ad1af634 100644 --- a/spec/factories/project_error_tracking_settings.rb +++ b/spec/factories/project_error_tracking_settings.rb @@ -9,6 +9,7 @@ FactoryBot.define do project_name { 'Sentry Project' } organization_name { 'Sentry Org' } integrated { false } + sentry_project_id { 10 } trait :disabled do enabled { false } diff --git a/spec/finders/ci/runner_jobs_finder_spec.rb b/spec/finders/ci/runner_jobs_finder_spec.rb index 3569582d70..755b21ec08 100644 --- a/spec/finders/ci/runner_jobs_finder_spec.rb +++ b/spec/finders/ci/runner_jobs_finder_spec.rb @@ -5,12 +5,17 @@ require 'spec_helper' RSpec.describe Ci::RunnerJobsFinder do let(:project) { create(:project) } 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 context 'when params is empty' do - let(:params) { {} } let!(:job) { create(:ci_build, runner: runner, project: project) } let!(:job1) { create(:ci_build, project: project) } @@ -20,6 +25,50 @@ RSpec.describe Ci::RunnerJobsFinder do 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 Ci::HasStatus::AVAILABLE_STATUSES.each do |target_status| context "when status is #{target_status}" do diff --git a/spec/finders/packages/conan/package_finder_spec.rb b/spec/finders/packages/conan/package_finder_spec.rb index b26f890009..6848786818 100644 --- a/spec/finders/packages/conan/package_finder_spec.rb +++ b/spec/finders/packages/conan/package_finder_spec.rb @@ -2,22 +2,53 @@ require 'spec_helper' 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(: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 - let!(:conan_package) { create(:conan_package, project: project) } - let!(:conan_package2) { create(:conan_package, project: project) } + let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" } + 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 - let!(:conan_package3) { create(:conan_package, :error, project: project) } - let!(:non_visible_project) { create(:project, :private) } - let!(:non_visible_conan_package) { create(:conan_package, project: non_visible_project) } - let(:query) { "#{conan_package.name.split('/').first[0, 3]}%" } + where(:visibility, :role, :packages_visible) do + :private | :maintainer | true + :private | :developer | true + :private | :reporter | true + :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 diff --git a/spec/fixtures/api/schemas/entities/member.json b/spec/fixtures/api/schemas/entities/member.json index dec98123e8..88f7d87b26 100644 --- a/spec/fixtures/api/schemas/entities/member.json +++ b/spec/fixtures/api/schemas/entities/member.json @@ -53,7 +53,7 @@ }, "user": { "allOf": [ - { "$ref": "member_user.json" } + { "$ref": "member_user_default.json" } ] }, "state": { "type": "integer" }, diff --git a/spec/fixtures/api/schemas/entities/member_user_default.json b/spec/fixtures/api/schemas/entities/member_user_default.json new file mode 100644 index 0000000000..e0b3dba569 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/member_user_default.json @@ -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" } + } +} diff --git a/spec/fixtures/api/schemas/entities/member_user.json b/spec/fixtures/api/schemas/entities/member_user_for_admin_member.json similarity index 100% rename from spec/fixtures/api/schemas/entities/member_user.json rename to spec/fixtures/api/schemas/entities/member_user_for_admin_member.json diff --git a/spec/frontend/projects/settings/access_dropdown_spec.js b/spec/frontend/projects/settings/access_dropdown_spec.js index 236968a373..b98c7cdf2a 100644 --- a/spec/frontend/projects/settings/access_dropdown_spec.js +++ b/spec/frontend/projects/settings/access_dropdown_spec.js @@ -154,4 +154,21 @@ describe('AccessDropdown', () => { expect(template).not.toContain(user.name); }); }); + + describe('deployKeyRowHtml', () => { + const deployKey = { + id: 1, + title: 'title ', + fullname: 'fullname ', + 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); + }); + }); }); diff --git a/spec/graphql/resolvers/ci/config_resolver_spec.rb b/spec/graphql/resolvers/ci/config_resolver_spec.rb index 3ff6d8f434..570f547292 100644 --- a/spec/graphql/resolvers/ci/config_resolver_spec.rb +++ b/spec/graphql/resolvers/ci/config_resolver_spec.rb @@ -7,24 +7,13 @@ RSpec.describe Resolvers::Ci::ConfigResolver do describe '#resolve' do 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(:content) do File.read(Rails.root.join('spec/support/gitlab_stubs/gitlab_ci_includes.yml')) 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 resolve(described_class, args: { project_path: project.full_path, content: content, sha: sha }, @@ -49,51 +38,76 @@ RSpec.describe Resolvers::Ci::ConfigResolver do end 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 eq(['Invalid configuration format']) - end - end - - context 'with an invalid SHA' do - let_it_be(:sha) { ':' } - + context 'when the user can create a pipeline' do let(:ci_lint) do 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 end - it 'logs the invalid SHA to Sentry' do - expect(Gitlab::ErrorTracking).to receive(:track_and_raise_exception) - .with(GRPC::InvalidArgument, sha: ':') + before do + allow(::Gitlab::Ci::Lint).to receive(:new).and_return(ci_lint) - 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 diff --git a/spec/helpers/integrations_helper_spec.rb b/spec/helpers/integrations_helper_spec.rb index 3bedc1d8ae..874f9d4870 100644 --- a/spec/helpers/integrations_helper_spec.rb +++ b/spec/helpers/integrations_helper_spec.rb @@ -149,19 +149,4 @@ RSpec.describe IntegrationsHelper do 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) { "" } - - it 'escapes issue reference' do - is_expected.not_to include(issue_reference) - is_expected.to include(html_escape(issue_reference)) - end - end - end end diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb index 1cf36fd69c..acb833ed3f 100644 --- a/spec/helpers/projects_helper_spec.rb +++ b/spec/helpers/projects_helper_spec.rb @@ -52,6 +52,7 @@ RSpec.describe ProjectsHelper do context 'api_url present' do let(:json) do { + sentry_project_id: error_tracking_setting.sentry_project_id, name: error_tracking_setting.project_name, organization_name: error_tracking_setting.organization_name, organization_slug: error_tracking_setting.organization_slug, diff --git a/spec/helpers/timeboxes_helper_spec.rb b/spec/helpers/timeboxes_helper_spec.rb index e31f2df737..f9fb40a616 100644 --- a/spec/helpers/timeboxes_helper_spec.rb +++ b/spec/helpers/timeboxes_helper_spec.rb @@ -38,4 +38,23 @@ RSpec.describe TimeboxesHelper do 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 diff --git a/spec/initializers/net_http_response_patch_spec.rb b/spec/initializers/net_http_response_patch_spec.rb new file mode 100644 index 0000000000..3bd0d8c390 --- /dev/null +++ b/spec/initializers/net_http_response_patch_spec.rb @@ -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 diff --git a/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb index c53c084993..567a0a4fcc 100644 --- a/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/project_pipeline_spec.rb @@ -25,18 +25,7 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do let(:project_data) do { 'visibility' => 'private', - 'created_at' => 10.days.ago, - '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 + 'created_at' => '2016-08-12T09:41:03' } end @@ -58,17 +47,8 @@ RSpec.describe BulkImports::Projects::Pipelines::ProjectPipeline do expect(imported_project).not_to be_nil expect(imported_project.group).to eq(group) - expect(imported_project.suggestion_commit_message).to eq('message') - expect(imported_project.archived?).to eq(project_data['archived']) - 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']) + expect(imported_project.visibility).to eq(project_data['visibility']) + expect(imported_project.created_at).to eq(project_data['created_at']) end end diff --git a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb index 822bb9a560..a1d77b9732 100644 --- a/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb +++ b/spec/lib/bulk_imports/projects/transformers/project_attributes_transformer_spec.rb @@ -25,8 +25,8 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer let(:data) do { - 'name' => 'source_name', - 'visibility' => 'private' + 'visibility' => 'private', + 'created_at' => '2016-11-18T09:29:42.634Z' } end @@ -76,8 +76,21 @@ RSpec.describe BulkImports::Projects::Transformers::ProjectAttributesTransformer end end - it 'converts all keys to symbols' do - expect(transformed_data.keys).to contain_exactly(:name, :path, :import_type, :visibility_level, :namespace_id) + context 'when data has extra keys' do + 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 diff --git a/spec/lib/gitlab/buffered_io_spec.rb b/spec/lib/gitlab/buffered_io_spec.rb index f8896abd46..c6939b819e 100644 --- a/spec/lib/gitlab/buffered_io_spec.rb +++ b/spec/lib/gitlab/buffered_io_spec.rb @@ -1,54 +1,50 @@ -# rubocop:disable Style/FrozenStringLiteralComment +# frozen_string_literal: true + require 'spec_helper' RSpec.describe Gitlab::BufferedIo do describe '#readuntil' do - let(:never_ending_tcp_socket) do - Class.new do - 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 + let(:mock_io) { StringIO.new('a') } + let(:start_time) { Process.clock_gettime(Process::CLOCK_MONOTONIC) } before do stub_const('Gitlab::BufferedIo::HEADER_READ_TIMEOUT', 0.1) end subject(:readuntil) do - Gitlab::BufferedIo.new(never_ending_tcp_socket.new).readuntil('a') + Gitlab::BufferedIo.new(mock_io).readuntil('a', false, start_time) 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/) + it 'does not raise a timeout error' do + 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 -# rubocop:enable Style/FrozenStringLiteralComment diff --git a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb index fe3b638d20..dea584e501 100644 --- a/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb +++ b/spec/lib/gitlab/import_export/decompressed_archive_size_validator_spec.rb @@ -86,6 +86,65 @@ RSpec.describe Gitlab::ImportExport::DecompressedArchiveSizeValidator do include_examples 'logs raised exception and terminates validator process group' 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 def create_compressed_file diff --git a/spec/models/error_tracking/project_error_tracking_setting_spec.rb b/spec/models/error_tracking/project_error_tracking_setting_spec.rb index 2939a40a84..15b6b45eab 100644 --- a/spec/models/error_tracking/project_error_tracking_setting_spec.rb +++ b/spec/models/error_tracking/project_error_tracking_setting_spec.rb @@ -10,7 +10,9 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do 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 it { is_expected.to belong_to(:project) } @@ -270,7 +272,7 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do end 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(:result) { subject.issue_details(opts) } let(:opts) { { issue_id: 1 } } @@ -317,12 +319,33 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do 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 let(:result) { subject.update_issue(**opts) } let(:opts) { { issue_id: 1, params: {} } } before do 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 context 'when sentry response is successful' do @@ -344,6 +367,56 @@ RSpec.describe ErrorTracking::ProjectErrorTrackingSetting do expect(result).to eq(error: 'Unexpected Error') 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 describe 'slugs' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index bc425b15c6..6fe4b3a815 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -4045,6 +4045,41 @@ RSpec.describe User do 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 let(:user) { create(:user) } diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index b77ccb8350..5a79315b6e 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1023,25 +1023,117 @@ RSpec.describe ProjectPolicy do subject { described_class.new(deploy_token, project) } - context 'a deploy token with read_package_registry scope' do - let(:deploy_token) { create(:deploy_token, read_package_registry: true) } + context 'private project' do + let(:project) { private_project } - it { is_expected.to be_allowed(:read_package) } - it { is_expected.to be_allowed(:read_project) } - it { is_expected.to be_disallowed(:create_package) } + context 'a deploy token with read_registry scope' do + let(:deploy_token) { create(:deploy_token, read_registry: true, write_registry: false) } - 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 - context 'a deploy token with write_package_registry scope' do - let(:deploy_token) { create(:deploy_token, write_package_registry: true) } + context 'public project' do + let(:project) { public_project } - 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) } + context 'a deploy token with read_registry scope' do + let(:deploy_token) { create(:deploy_token, read_registry: true, write_registry: false) } - 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 @@ -2102,4 +2194,25 @@ RSpec.describe ProjectPolicy do it { is_expected.to be_disallowed(:register_project_runners) } 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 diff --git a/spec/requests/api/ci/runners_spec.rb b/spec/requests/api/ci/runners_spec.rb index d6ebc197ab..587a692108 100644 --- a/spec/requests/api/ci/runners_spec.rb +++ b/spec/requests/api/ci/runners_spec.rb @@ -804,6 +804,23 @@ RSpec.describe API::Ci::Runners do expect(json_response).to be_an(Array) expect(json_response.length).to eq(2) 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 context 'when valid status is provided' do diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index 48f2c45bd9..c1217292d5 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -483,6 +483,29 @@ RSpec.describe API::Labels do let(:request) { api("/projects/#{project.id}/labels", user) } let(:params) { { name: valid_label_title_1 } } 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 describe 'PUT /projects/:id/labels' do @@ -537,6 +560,44 @@ RSpec.describe API::Labels do expect(response).to have_gitlab_http_status(:bad_request) 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 describe 'PUT /projects/:id/labels/promote' do diff --git a/spec/requests/jira_routing_spec.rb b/spec/requests/jira_routing_spec.rb index a627eea33a..e0e170044d 100644 --- a/spec/requests/jira_routing_spec.rb +++ b/spec/requests/jira_routing_spec.rb @@ -25,27 +25,49 @@ RSpec.describe 'Jira referenced paths', type: :request do expect(response).to redirect_to(redirect_path) end - context 'with encoded subgroup path' do - where(:jira_path, :redirect_path) do - '/group/group@sub_group@sub_group_project' | '/group/sub_group/sub_group_project' - '/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' + shared_examples 'redirects to jira path' do + it 'redirects to canonical path with legacy prefix' do + redirects_to_canonical_path "/-/jira#{jira_path}", redirect_path end - with_them do - context 'with legacy prefix' do - 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 + it 'redirects to canonical path' do + redirects_to_canonical_path jira_path, redirect_path 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 where(:jira_path, :redirect_path) do '/-/jira/group/group_project' | '/group/group_project' diff --git a/spec/serializers/member_user_entity_spec.rb b/spec/serializers/member_user_entity_spec.rb index 0e6d4bcc3f..85f29845d6 100644 --- a/spec/serializers/member_user_entity_spec.rb +++ b/spec/serializers/member_user_entity_spec.rb @@ -11,7 +11,7 @@ RSpec.describe MemberUserEntity do let(:entity_hash) { entity.as_json } 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 it 'correctly exposes `avatar_url`' do @@ -27,10 +27,8 @@ RSpec.describe MemberUserEntity do expect(entity_hash[:blocked]).to be(true) end - it 'correctly exposes `two_factor_enabled`' do - allow(user).to receive(:two_factor_enabled?).and_return(true) - - expect(entity_hash[:two_factor_enabled]).to be(true) + it 'does not expose `two_factor_enabled` by default' do + expect(entity_hash[:two_factor_enabled]).to be(nil) end it 'correctly exposes `status.emoji`' do @@ -44,4 +42,66 @@ RSpec.describe MemberUserEntity do it 'correctly exposes `last_activity_on`' do expect(entity_hash[:last_activity_on]).to be(user.last_activity_on) 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 diff --git a/spec/services/bulk_imports/file_decompression_service_spec.rb b/spec/services/bulk_imports/file_decompression_service_spec.rb index 1d6aa79a37..77348428d6 100644 --- a/spec/services/bulk_imports/file_decompression_service_spec.rb +++ b/spec/services/bulk_imports/file_decompression_service_spec.rb @@ -80,7 +80,8 @@ RSpec.describe BulkImports::FileDecompressionService do subject { described_class.new(tmpdir: tmpdir, filename: 'symlink.gz') } 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) end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 6b7b72d83f..724970e27c 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -294,7 +294,7 @@ RSpec.describe Issues::CreateService do context 'user is reporter or above' do before do - project.add_reporter(user) + project.add_developer(user) end it 'assigns the sentry error' do diff --git a/spec/support/shared_contexts/project_features_shared_context.rb b/spec/support/shared_contexts/project_features_shared_context.rb new file mode 100644 index 0000000000..40d9cb29c1 --- /dev/null +++ b/spec/support/shared_contexts/project_features_shared_context.rb @@ -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 diff --git a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb index 3453f954c9..e473ac2149 100644 --- a/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb +++ b/spec/support/shared_contexts/sentry_error_tracking_shared_context.rb @@ -16,6 +16,6 @@ RSpec.shared_context 'sentry error tracking context' do before do 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 diff --git a/spec/support/shared_examples/policies/project_policy_shared_examples.rb b/spec/support/shared_examples/policies/project_policy_shared_examples.rb index 63e4d458ad..c4083df47e 100644 --- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb +++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb @@ -345,16 +345,7 @@ RSpec.shared_examples 'project policies as admin without admin mode' do end RSpec.shared_examples 'package access with repository disabled' do - context 'when repository is disabled' do - 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 + include_context 'repository disabled via project features' - it { is_expected.to be_allowed(:read_package) } - end + it { is_expected.to be_allowed(:read_package) } end diff --git a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb index 135fa4cf5a..e6b0772aec 100644 --- a/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/conan_packages_shared_examples.rb @@ -19,33 +19,66 @@ RSpec.shared_examples 'conan ping endpoint' do end RSpec.shared_examples 'conan search endpoint' do - before do - 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 + using RSpec::Parameterized::TableSyntax subject { json_response['results'] } - context 'returns packages with a matching name' do - let(:params) { { q: package.conan_recipe } } + context 'with a public project' do + 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 - context 'returns packages using a * wildcard' do + context 'with a private project' do let(:params) { { q: "#{package.name[0, 3]}*" } } - it { is_expected.to contain_exactly(package.conan_recipe) } - end + where(:role, :packages_visible) do + :maintainer | true + :developer | true + :reporter | true + :guest | false + :anonymous | false + end - context 'does not return non-matching packages' do - let(:params) { { q: "foo" } } + with_them do + 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 diff --git a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb index 7677e5d8cb..f18869fb38 100644 --- a/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb +++ b/spec/support/shared_examples/services/container_registry_auth_service_shared_examples.rb @@ -142,9 +142,9 @@ RSpec.shared_examples 'logs an auth warning' do |requested_actions| requested_project_path: project.full_path, requested_actions: requested_actions, authorized_actions: [], - user_id: current_user.id, - username: current_user.username - } + user_id: current_user&.id, + username: current_user&.username + }.compact end it do diff --git a/tooling/overcommit/Gemfile.lock b/tooling/overcommit/Gemfile.lock index 13c611439b..a4b6f62a97 100644 --- a/tooling/overcommit/Gemfile.lock +++ b/tooling/overcommit/Gemfile.lock @@ -34,7 +34,7 @@ GEM parallel (1.19.2) parser (2.7.2.0) ast (~> 2.4.1) - rack (2.2.3) + rack (2.2.3.1) rainbow (3.0.0) regexp_parser (1.8.2) rexml (3.2.4)