diff options
author | GitLab Release Tools Bot <delivery-team+release-tools@gitlab.com> | 2023-08-31 19:41:43 +0300 |
---|---|---|
committer | GitLab Release Tools Bot <delivery-team+release-tools@gitlab.com> | 2023-08-31 19:41:43 +0300 |
commit | f0a2a6e328ab823ae6ba9eda7b4198b6ef7954c7 (patch) | |
tree | bae088e8db79a417b623f75df78e84ff11c36720 | |
parent | 4432289851dcfc0bc030323f581866103fd12f66 (diff) | |
parent | 098480e7f30e1ca20e9095390f48edea76c069ed (diff) |
Merge remote-tracking branch 'dev/16-2-stable' into 16-2-stable
50 files changed, 619 insertions, 222 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index a9ea213484e..a8bdf9eb469 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,28 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 16.2.5 (2023-08-31) + +### Fixed (1 change) + +- [Geo: Resync direct upload object stored artifacts](gitlab-org/security/gitlab@2b89dcd8d4e238ee081b5a886a43f2d7d390e853) **GitLab Enterprise Edition** + +### Security (13 changes) + +- [Add authorization checks to import status endpoint](gitlab-org/security/gitlab@4ace6aaeaa836d0545576857080b6a01163d40b6) ([merge request](gitlab-org/security/gitlab!3514)) +- [Update commonmarker to 0.23.10](gitlab-org/security/gitlab@41ae8c446666e478addfff8c2d450103435c1ac1) ([merge request](gitlab-org/security/gitlab!3508)) +- [Remove DAST secret variables when URL is updated](gitlab-org/security/gitlab@ab9b3384bfdf15698285e99d1f31c7d8b3ec7db5) ([merge request](gitlab-org/security/gitlab!3499)) +- [Maintainer can leak sentry token by changing the configured URL](gitlab-org/security/gitlab@8c423fdd1afceedf34a5d7c11f9be96b7d273b95) ([merge request](gitlab-org/security/gitlab!3517)) +- [Service account users are external by default](gitlab-org/security/gitlab@9abbd558d4307c4bcb62a5fea2bffa2e59ded4fa) ([merge request](gitlab-org/security/gitlab!3502)) +- [Additional permission check when editing label](gitlab-org/security/gitlab@416b3a3d448c21b96c4cd6dda42da2e561f8040d) ([merge request](gitlab-org/security/gitlab!3505)) +- [Fix ReDOS in bulk_imports endpoint params](gitlab-org/security/gitlab@90dbac471eff8d1d867db979be5aaf7f8660e64c) ([merge request](gitlab-org/security/gitlab!3511)) +- [Prevent namespace level banned users from accessing API](gitlab-org/security/gitlab@76ce2605f091d7c2d10ed3dd00cf8c7e37e26b5a) ([merge request](gitlab-org/security/gitlab!3484)) +- [Requires write_model_experiments on mlflow api](gitlab-org/security/gitlab@a385fb7b6422e6d41c8197655947fc6d3f0d65c8) ([merge request](gitlab-org/security/gitlab!3480)) +- [Check prohibit_outer_forks in fork relationship api](gitlab-org/security/gitlab@d8ee7ec151440088bb34b5d2c20b490986bba654) ([merge request](gitlab-org/security/gitlab!3477)) +- [Remove GCP private key from streaming audit events UI](gitlab-org/security/gitlab@36b15be1d8643172d4f54063fb6430068d57e6f8) ([merge request](gitlab-org/security/gitlab!3487)) +- [Prevent traversal for `path` parameter in refs/switch endpoint](gitlab-org/security/gitlab@89cd4dae070fcf20df467639934accb41f5c46da) ([merge request](gitlab-org/security/gitlab!3475)) +- [Gitaly keyset pager when pagination none only with tree view](gitlab-org/security/gitlab@498f72aed3d0e70f7af5335ee3fb11f6cfc21986) ([merge request](gitlab-org/security/gitlab!3481)) + ## 16.2.4 (2023-08-11) ### Fixed (2 changes) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index b045f481e1e..d4e90579bc6 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -16.2.4
\ No newline at end of file +16.2.5
\ No newline at end of file diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index b045f481e1e..d4e90579bc6 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -16.2.4
\ No newline at end of file +16.2.5
\ No newline at end of file @@ -192,7 +192,7 @@ gem 'typhoeus', '~> 1.4.0' # Used with Elasticsearch to support http keep-alive gem 'html-pipeline', '~> 2.14.3' gem 'deckar01-task_list', '2.3.2' gem 'gitlab-markup', '~> 1.9.0', require: 'github/markup' -gem 'commonmarker', '~> 0.23.9' +gem 'commonmarker', '~> 0.23.10' gem 'kramdown', '~> 2.3.1' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~> 6.3.2' diff --git a/Gemfile.checksum b/Gemfile.checksum index 333116430ec..f8647701463 100644 --- a/Gemfile.checksum +++ b/Gemfile.checksum @@ -84,7 +84,7 @@ {"name":"coderay","version":"1.1.3","platform":"ruby","checksum":"dc530018a4684512f8f38143cd2a096c9f02a1fc2459edcfe534787a7fc77d4b"}, {"name":"coercible","version":"1.0.0","platform":"ruby","checksum":"5081ad24352cc8435ce5472bc2faa30260c7ea7f2102cc6a9f167c4d9bffaadc"}, {"name":"colored2","version":"3.1.2","platform":"ruby","checksum":"b13c2bd7eeae2cf7356a62501d398e72fde78780bd26aec6a979578293c28b4a"}, -{"name":"commonmarker","version":"0.23.9","platform":"ruby","checksum":"2e739c85a6961531cb6f5ba5169f2c7f64471b7e700c64b048ec22a5b230811c"}, +{"name":"commonmarker","version":"0.23.10","platform":"ruby","checksum":"fdd312ae2bb4071b2f3085d4d7533cb9f8d9057a2eaa0760228a65bc3ed565d1"}, {"name":"concurrent-ruby","version":"1.2.2","platform":"ruby","checksum":"3879119b8b75e3b62616acc256c64a134d0b0a7a9a3fcba5a233025bcde22c4f"}, {"name":"connection_pool","version":"2.3.0","platform":"ruby","checksum":"677985be912f33c90f98f229aaa0c0ddb2ef8776f21929a36eeeb25251c944da"}, {"name":"cork","version":"0.3.0","platform":"ruby","checksum":"a0a0ac50e262f8514d1abe0a14e95e71c98b24e3378690e5d044daf0013ad4bc"}, diff --git a/Gemfile.lock b/Gemfile.lock index 3add4cdfda3..c43e9782ee7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -358,7 +358,7 @@ GEM coercible (1.0.0) descendants_tracker (~> 0.0.1) colored2 (3.1.2) - commonmarker (0.23.9) + commonmarker (0.23.10) concurrent-ruby (1.2.2) connection_pool (2.3.0) cork (0.3.0) @@ -1761,7 +1761,7 @@ DEPENDENCIES circuitbox (= 2.0.0) click_house-client! cloud_profiler_agent (~> 0.0.0)! - commonmarker (~> 0.23.9) + commonmarker (~> 0.23.10) concurrent-ruby (~> 1.1) connection_pool (~> 2.0) countries (~> 4.0.0) @@ -1 +1 @@ -16.2.4
\ No newline at end of file +16.2.5
\ No newline at end of file diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index 2d821676677..61f9333bf95 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -4,7 +4,8 @@ class Groups::LabelsController < Groups::ApplicationController include ToggleSubscriptionAction before_action :label, only: [:edit, :update, :destroy] - before_action :authorize_admin_labels!, only: [:new, :create, :edit, :update, :destroy] + before_action :authorize_group_for_admin_labels!, only: [:new, :create, :edit, :update, :destroy] + before_action :authorize_label_for_admin_label!, only: [:edit, :update, :destroy] before_action :save_previous_label_path, only: [:edit] respond_to :html @@ -70,10 +71,14 @@ class Groups::LabelsController < Groups::ApplicationController protected - def authorize_admin_labels! + def authorize_group_for_admin_labels! return render_404 unless can?(current_user, :admin_label, @group) end + def authorize_label_for_admin_label! + return render_404 unless can?(current_user, :admin_label, @label) + end + def authorize_read_labels! return render_404 unless can?(current_user, :read_label, @group) end diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 4c2bd2a9d42..278d306301a 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -15,6 +15,8 @@ class Projects::RefsController < Projects::ApplicationController urgency :low, [:switch, :logs_tree] def switch + Gitlab::PathTraversal.check_path_traversal!(@id) + respond_to do |format| format.html do new_path = @@ -40,6 +42,8 @@ class Projects::RefsController < Projects::ApplicationController redirect_to new_path end end + rescue Gitlab::PathTraversal::PathTraversalAttackError + head :bad_request end def logs_tree diff --git a/app/graphql/resolvers/group_issues_resolver.rb b/app/graphql/resolvers/group_issues_resolver.rb index 43f01395896..7bbc662c6c8 100644 --- a/app/graphql/resolvers/group_issues_resolver.rb +++ b/app/graphql/resolvers/group_issues_resolver.rb @@ -9,6 +9,11 @@ module Resolvers include GroupIssuableResolver + before_connection_authorization do |nodes, _| + projects = nodes.map(&:project) + ActiveRecord::Associations::Preloader.new(records: projects, associations: :namespace).call + end + def ready?(**args) if args.dig(:not, :release_tag).present? raise ::Gitlab::Graphql::Errors::ArgumentError, 'releaseTag filter is not allowed when parent is a group.' diff --git a/app/graphql/resolvers/issues_resolver.rb b/app/graphql/resolvers/issues_resolver.rb index 17e3e159a5b..589366ba26d 100644 --- a/app/graphql/resolvers/issues_resolver.rb +++ b/app/graphql/resolvers/issues_resolver.rb @@ -23,6 +23,7 @@ module Resolvers projects = nodes.map(&:project) ::Preloaders::UserMaxAccessLevelInProjectsPreloader.new(projects, current_user).execute ::Preloaders::GroupPolicyPreloader.new(projects.filter_map(&:group), current_user).execute + ActiveRecord::Associations::Preloader.new(records: projects, associations: :namespace).call end def ready?(**args) diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb index 4f50a112141..644673e249e 100644 --- a/app/models/bulk_imports/entity.rb +++ b/app/models/bulk_imports/entity.rb @@ -41,19 +41,15 @@ class BulkImports::Entity < ApplicationRecord validates :project, absence: true, if: :group validates :group, absence: true, if: :project validates :source_type, presence: true - validates :source_full_path, presence: true, format: { - with: Gitlab::Regex.bulk_import_source_full_path_regex, - message: Gitlab::Regex.bulk_import_source_full_path_regex_message - } - + validates :source_full_path, presence: true validates :destination_name, presence: true, if: -> { group || project } validates :destination_namespace, exclusion: [nil], if: :group validates :destination_namespace, presence: true, if: :project? validate :validate_parent_is_a_group, if: :parent validate :validate_imported_entity_type - validate :validate_destination_namespace_ascendency, if: :group_entity? + validate :validate_source_full_path_format enum source_type: { group_entity: 0, project_entity: 1 } @@ -236,4 +232,15 @@ class BulkImports::Entity < ApplicationRecord ) end end + + def validate_source_full_path_format + validator = group? ? NamespacePathValidator : ProjectPathValidator + + return if validator.valid_path?(source_full_path) + + errors.add( + :source_full_path, + Gitlab::Regex.bulk_import_source_full_path_regex_message + ) + end end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index ad6155258ab..7470f21c6c4 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -561,6 +561,7 @@ class ProjectPolicy < BasePolicy enable :destroy_upload enable :admin_incident_management_timeline_event_tag enable :stop_environment + enable :read_import_error end rule { public_project & metrics_dashboard_allowed }.policy do diff --git a/app/services/error_tracking/list_projects_service.rb b/app/services/error_tracking/list_projects_service.rb index 35a8179d54d..1539e24df9d 100644 --- a/app/services/error_tracking/list_projects_service.rb +++ b/app/services/error_tracking/list_projects_service.rb @@ -20,22 +20,20 @@ module ErrorTracking def project_error_tracking_setting (super || project.build_error_tracking_setting).tap do |setting| - url_changed = !setting.api_url&.start_with?(params[:api_host]) - setting.api_url = ErrorTracking::ProjectErrorTrackingSetting.build_api_url_from( api_host: params[:api_host], organization_slug: 'org', project_slug: 'proj' ) - setting.token = token(setting, url_changed) + setting.token = token(setting) setting.enabled = true end end strong_memoize_attr :project_error_tracking_setting - def token(setting, url_changed) - return if url_changed && masked_token? + def token(setting) + return if setting.api_url_changed? && masked_token? # Use param token if not masked, otherwise use database token return params[:token] unless masked_token? diff --git a/doc/administration/audit_event_streaming/index.md b/doc/administration/audit_event_streaming/index.md index 622d29fa9a7..b92f85dd7ed 100644 --- a/doc/administration/audit_event_streaming/index.md +++ b/doc/administration/audit_event_streaming/index.md @@ -92,7 +92,7 @@ To add Google Cloud Logging streaming destinations to a top-level group: 1. Select **Secure > Audit events**. 1. On the main area, select **Streams** tab. 1. Select **Add streaming destination** and select **Google Cloud Logging** to show the section for adding destinations. -1. Enter the Google Project ID, Google Client Email, Log ID, and Google Private Key to add. +1. Enter the Google project ID, Google client email, log ID, and Google private key to add. 1. Select **Add** to add the new streaming destination. ## List streaming destinations @@ -200,7 +200,8 @@ To update the streaming destinations for an instance: ### Google Cloud Logging streaming -> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124384) in GitLab 16.2. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/124384) in GitLab 16.2. +> - Button to add private key [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/419675) in GitLab 16.3. Prerequisites: @@ -212,7 +213,8 @@ To update Google Cloud Logging streaming destinations to a top-level group: 1. Select **Secure > Audit events**. 1. On the main area, select **Streams** tab. 1. Select the Google Cloud Logging stream to expand. -1. Enter the Google Project ID, Google Client Email, Log ID, and Google Private Key to update. +1. Enter the Google project ID, Google client email, and log ID to update. +1. Select **Add a new private key** and enter a Google private key to update the private key. 1. Select **Save** to update the streaming destination. ## Delete streaming destinations diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 425a2b7e980..67075efbfb9 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -15920,7 +15920,6 @@ Stores Google Cloud Logging configurations associated with IAM service accounts, | <a id="googlecloudloggingconfigurationtypegroup"></a>`group` | [`Group!`](#group) | Group the configuration belongs to. | | <a id="googlecloudloggingconfigurationtypeid"></a>`id` | [`ID!`](#id) | ID of the configuration. | | <a id="googlecloudloggingconfigurationtypelogidname"></a>`logIdName` | [`String!`](#string) | Log ID. | -| <a id="googlecloudloggingconfigurationtypeprivatekey"></a>`privateKey` | [`String!`](#string) | Private key. | ### `GpgSignature` diff --git a/doc/user/application_security/dast/proxy-based.md b/doc/user/application_security/dast/proxy-based.md index 0eec04bfeff..5fb759876a5 100644 --- a/doc/user/application_security/dast/proxy-based.md +++ b/doc/user/application_security/dast/proxy-based.md @@ -644,6 +644,9 @@ NOTE: If a site profile is linked to a security policy, you cannot edit the profile from this page. See [Scan execution policies](../policies/scan-execution-policies.md) for more information. +NOTE: +If a site profile's Target URL or Authenticated URL is updated, the request headers and password fields associated with that profile are cleared. + When a validated site profile's file, header, or meta tag is edited, the site's [validation status](#site-profile-validation) is revoked. diff --git a/lib/api/entities/project_import_status.rb b/lib/api/entities/project_import_status.rb index 59388aacafd..a7e7cd9ff73 100644 --- a/lib/api/entities/project_import_status.rb +++ b/lib/api/entities/project_import_status.rb @@ -17,8 +17,15 @@ module API project.import_state&.relation_hard_failures(limit: 100) || [] end - expose :import_error, documentation: { type: 'string', example: 'Error message' } do |project, _options| - project.import_state&.last_error + expose :import_error, documentation: { type: 'string', example: 'Error message' } do |project, options| + next unless options[:current_user] + next unless project.import_state&.last_error + + if Ability.allowed?(options[:current_user], :read_import_error, project) + project.import_state&.last_error + else + _("Ask a maintainer to check the import status for more details.") + end end expose :stats, documentation: { type: 'object' } do |project, _options| diff --git a/lib/api/ml/mlflow/api_helpers.rb b/lib/api/ml/mlflow/api_helpers.rb index 7f4a895235c..cfe4cc6b5ae 100644 --- a/lib/api/ml/mlflow/api_helpers.rb +++ b/lib/api/ml/mlflow/api_helpers.rb @@ -4,6 +4,14 @@ module API module Ml module Mlflow module ApiHelpers + def check_api_read! + not_found! unless can?(current_user, :read_model_experiments, user_project) + end + + def check_api_write! + unauthorized! unless can?(current_user, :write_model_experiments, user_project) + end + def resource_not_found! render_structured_api_error!({ error_code: 'RESOURCE_DOES_NOT_EXIST' }, 404) end diff --git a/lib/api/ml/mlflow/entrypoint.rb b/lib/api/ml/mlflow/entrypoint.rb index 048234eccd1..d8474946a75 100644 --- a/lib/api/ml/mlflow/entrypoint.rb +++ b/lib/api/ml/mlflow/entrypoint.rb @@ -26,7 +26,8 @@ module API authenticate! - not_found! unless can?(current_user, :read_model_experiments, user_project) + check_api_read! + check_api_write! unless request.get? || request.head? end rescue_from ActiveRecord::ActiveRecordError do |e| diff --git a/lib/api/project_import.rb b/lib/api/project_import.rb index 6639b3ec346..c28d0ae2def 100644 --- a/lib/api/project_import.rb +++ b/lib/api/project_import.rb @@ -111,7 +111,7 @@ module API ).execute if response.success? - present(response.payload, with: Entities::ProjectImportStatus) + present(response.payload, with: Entities::ProjectImportStatus, current_user: current_user) else render_api_error!(response.message, response.http_status) end @@ -134,7 +134,7 @@ module API end route_setting :skip_authentication, true get ':id/import' do - present user_project, with: Entities::ProjectImportStatus + present user_project, with: Entities::ProjectImportStatus, current_user: current_user end params do @@ -182,7 +182,7 @@ module API ).execute if response.success? - present(response.payload, with: Entities::ProjectImportStatus) + present(response.payload, with: Entities::ProjectImportStatus, current_user: current_user) else render_api_error!(response.message, response.http_status) end @@ -241,7 +241,7 @@ module API ).execute if response.success? - present(response.payload, with: Entities::ProjectImportStatus) + present(response.payload, with: Entities::ProjectImportStatus, current_user: current_user) else render_api_error!(response.message, response.http_status) end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 468f284f136..7198917a097 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -689,6 +689,7 @@ module API desc 'Mark this project as forked from another' do success code: 201, model: Entities::Project failure [ + { code: 401, message: 'Unauthorized' }, { code: 403, message: 'Unauthenticated' }, { code: 404, message: 'Not found' } ] @@ -706,7 +707,11 @@ module API authorize! :fork_project, fork_from_project - result = ::Projects::ForkService.new(fork_from_project, current_user).execute(user_project) + service = ::Projects::ForkService.new(fork_from_project, current_user) + + unauthorized!('Target Namespace') unless service.valid_fork_target?(user_project.namespace) + + result = service.execute(user_project) if result present_project user_project.reset, with: Entities::Project, current_user: current_user diff --git a/lib/api/validations/validators/bulk_imports.rb b/lib/api/validations/validators/bulk_imports.rb index f8ad5ed6d14..67dc084cc12 100644 --- a/lib/api/validations/validators/bulk_imports.rb +++ b/lib/api/validations/validators/bulk_imports.rb @@ -32,8 +32,7 @@ module API class DestinationNamespacePath < Grape::Validations::Validators::Base def validate_param!(attr_name, params) return if params[attr_name].blank? - - return if params[attr_name] =~ Gitlab::Regex.bulk_import_destination_namespace_path_regex + return if NamespacePathValidator.valid_path?(params[attr_name]) raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], @@ -44,7 +43,10 @@ module API class SourceFullPath < Grape::Validations::Validators::Base def validate_param!(attr_name, params) - return if params[attr_name] =~ Gitlab::Regex.bulk_import_source_full_path_regex + full_path = params[attr_name] + + return if params['source_type'] == 'group_entity' && NamespacePathValidator.valid_path?(full_path) + return if params['source_type'] == 'project_entity' && ProjectPathValidator.valid_path?(full_path) raise Grape::Exceptions::Validation.new( params: [@scope.full_name(attr_name)], diff --git a/lib/gitlab/pagination/gitaly_keyset_pager.rb b/lib/gitlab/pagination/gitaly_keyset_pager.rb index 6235874132f..82d6fc64d89 100644 --- a/lib/gitlab/pagination/gitaly_keyset_pager.rb +++ b/lib/gitlab/pagination/gitaly_keyset_pager.rb @@ -15,7 +15,7 @@ module Gitlab # It is expected that the given finder will respond to `execute` method with `gitaly_pagination:` option # and supports pagination via gitaly. def paginate(finder) - return finder.execute(gitaly_pagination: false) if no_pagination? + return finder.execute(gitaly_pagination: false) if no_pagination?(finder) return paginate_via_gitaly(finder) if keyset_pagination_enabled?(finder) return paginate_first_page_via_gitaly(finder) if paginate_first_page?(finder) @@ -28,8 +28,8 @@ module Gitlab private - def no_pagination? - params[:pagination] == 'none' + def no_pagination?(finder) + params[:pagination] == 'none' && finder.is_a?(::Repositories::TreeFinder) end def keyset_pagination_enabled?(finder) diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 4e666dbaf77..031b49629c3 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -267,28 +267,6 @@ module Gitlab end module BulkImports - def bulk_import_destination_namespace_path_regex - # This regexp validates the string conforms to rules for a destination_namespace path: - # i.e does not start with a non-alphanumeric character, - # contains only alphanumeric characters, forward slashes, periods, and underscores, - # does not end with a period or forward slash, and has a relative path structure - # with no http protocol chars or leading or trailing forward slashes - # eg 'source/full/path' or 'destination_namespace' not 'https://example.com/destination/namespace/path' - # the regex also allows for an empty string ('') to be accepted as this is allowed in - # a bulk_import POST request - @bulk_import_destination_namespace_path_regex ||= %r/((\A\z)|(\A[0-9a-z]*(-_.)?[0-9a-z])(\/?[0-9a-z]*[-_.]?[0-9a-z])+\z)/i - end - - def bulk_import_source_full_path_regex - # This regexp validates the string conforms to rules for a source_full_path path: - # i.e does not start with a non-alphanumeric character except for periods or underscores, - # contains only alphanumeric characters, forward slashes, periods, and underscores, - # does not end with a period or forward slash, and has a relative path structure - # with no http protocol chars or leading or trailing forward slashes - # eg 'source/full/path' or 'destination_namespace' not 'https://example.com/source/full/path' - @bulk_import_source_full_path_regex ||= %r/\A([.]?)[^\W](\/?([-_.+]*)*[0-9a-z][-_]*)+\z/i - end - def bulk_import_source_full_path_regex_message bulk_import_destination_namespace_path_regex_message end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 50694b8fc5c..f59b086afb1 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -6365,6 +6365,9 @@ msgstr "" msgid "AsanaService|User Personal Access Token. User must have access to the task. All comments are attributed to this user." msgstr "" +msgid "Ask a maintainer to check the import status for more details." +msgstr "" + msgid "Ask again later" msgstr "" @@ -6575,6 +6578,9 @@ msgstr "" msgid "AuditStreams|Active" msgstr "" +msgid "AuditStreams|Add a new private key" +msgstr "" + msgid "AuditStreams|Add an HTTP endpoint to manage audit logs in third-party systems." msgstr "" @@ -6689,6 +6695,9 @@ msgstr "" msgid "AuditStreams|This is great for keeping everything one place." msgstr "" +msgid "AuditStreams|Use the Google Cloud console to view the private key. To change the private key, replace it with a new private key." +msgstr "" + msgid "AuditStreams|Value" msgstr "" @@ -14382,6 +14391,9 @@ msgstr "" msgid "DastProfiles|Minimum = 1 second, Maximum = 3600 seconds" msgstr "" +msgid "DastProfiles|Modifying the URL will clear any previously entered values for the additional request headers and password fields." +msgstr "" + msgid "DastProfiles|Monitors all HTTP requests sent to the target to find potential vulnerabilities." msgstr "" diff --git a/spec/controllers/groups/labels_controller_spec.rb b/spec/controllers/groups/labels_controller_spec.rb index 916b2cf10dd..3ade85eee9d 100644 --- a/spec/controllers/groups/labels_controller_spec.rb +++ b/spec/controllers/groups/labels_controller_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' RSpec.describe Groups::LabelsController, feature_category: :team_planning do let_it_be(:group) { create(:group) } let_it_be(:user) { create(:user) } + let_it_be(:another_user) { create(:user) } let_it_be(:project) { create(:project, namespace: group) } before do @@ -66,6 +67,46 @@ RSpec.describe Groups::LabelsController, feature_category: :team_planning do end end + shared_examples 'when current_user does not have ability to modify the label' do + before do + sign_in(another_user) + end + + it 'responds with status 404' do + group_request + + expect(response).to have_gitlab_http_status(:not_found) + end + + # No matter what permissions you have in a sub-group, you need the proper + # permissions in the group in order to modify a group label + # See https://gitlab.com/gitlab-org/gitlab/-/issues/387531 + context 'when trying to edit a parent group label from inside a subgroup' do + it 'responds with status 404' do + sub_group.add_owner(another_user) + sub_group_request + + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + describe 'GET #edit' do + let_it_be(:label) { create(:group_label, group: group) } + + it 'shows the edit page' do + get :edit, params: { group_id: group.to_param, id: label.to_param } + + expect(response).to have_gitlab_http_status(:ok) + end + + it_behaves_like 'when current_user does not have ability to modify the label' do + let_it_be(:sub_group) { create(:group, parent: group) } + let(:group_request) { get :edit, params: { group_id: group.to_param, id: label.to_param } } + let(:sub_group_request) { get :edit, params: { group_id: sub_group.to_param, id: label.to_param } } + end + end + describe 'POST #toggle_subscription' do it 'allows user to toggle subscription on group labels' do label = create(:group_label, group: group) @@ -99,19 +140,20 @@ RSpec.describe Groups::LabelsController, feature_category: :team_planning do end end - context 'when current_user does not have ability to destroy the label' do - let(:another_user) { create(:user) } - - before do - sign_in(another_user) - end - - it 'responds with status 404' do - label = create(:group_label, group: group) - delete :destroy, params: { group_id: group.to_param, id: label.to_param } + it_behaves_like 'when current_user does not have ability to modify the label' do + let_it_be(:label) { create(:group_label, group: group) } + let_it_be(:sub_group) { create(:group, parent: group) } + let(:group_request) { delete :destroy, params: { group_id: group.to_param, id: label.to_param } } + let(:sub_group_request) { delete :destroy, params: { group_id: sub_group.to_param, id: label.to_param } } + end + end - expect(response).to have_gitlab_http_status(:not_found) - end + describe 'PUT #update' do + it_behaves_like 'when current_user does not have ability to modify the label' do + let_it_be(:label) { create(:group_label, group: group) } + let_it_be(:sub_group) { create(:group, parent: group) } + let(:group_request) { put :update, params: { group_id: group.to_param, id: label.to_param, label: { title: 'Test' } } } + let(:sub_group_request) { put :update, params: { group_id: sub_group.to_param, id: label.to_param, label: { title: 'Test' } } } end end end diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb index 0b1d0b75de7..7ea0e678a41 100644 --- a/spec/controllers/projects/refs_controller_spec.rb +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -12,40 +12,70 @@ RSpec.describe Projects::RefsController, feature_category: :source_code_manageme end describe 'GET #switch' do - using RSpec::Parameterized::TableSyntax + context 'with normal parameters' do + using RSpec::Parameterized::TableSyntax - let(:id) { 'master' } - let(:params) do - { destination: destination, namespace_id: project.namespace.to_param, project_id: project, id: id, - ref_type: ref_type } - end + let(:id) { 'master' } + let(:id_and_path) { "#{id}/#{path}" } + + let(:params) do + { destination: destination, namespace_id: project.namespace.to_param, project_id: project, id: id, + ref_type: ref_type, path: path } + end + + subject { get :switch, params: params } + + where(:destination, :ref_type, :path, :redirected_to) do + 'tree' | nil | nil | lazy { project_tree_path(project, id) } + 'tree' | 'heads' | nil | lazy { project_tree_path(project, id) } + 'tree' | nil | 'foo/bar' | lazy { project_tree_path(project, id_and_path) } + 'blob' | nil | nil | lazy { project_blob_path(project, id) } + 'blob' | 'heads' | nil | lazy { project_blob_path(project, id) } + 'blob' | nil | 'foo/bar' | lazy { project_blob_path(project, id_and_path) } + 'graph' | nil | nil | lazy { project_network_path(project, id) } + 'graph' | 'heads' | nil | lazy { project_network_path(project, id, ref_type: 'heads') } + 'graph' | nil | 'foo/bar' | lazy { project_network_path(project, id_and_path) } + 'graphs' | nil | nil | lazy { project_graph_path(project, id) } + 'graphs' | 'heads' | nil | lazy { project_graph_path(project, id, ref_type: 'heads') } + 'graphs' | nil | 'foo/bar' | lazy { project_graph_path(project, id_and_path) } + 'find_file' | nil | nil | lazy { project_find_file_path(project, id) } + 'find_file' | 'heads' | nil | lazy { project_find_file_path(project, id) } + 'find_file' | nil | 'foo/bar' | lazy { project_find_file_path(project, id_and_path) } + 'graphs_commits' | nil | nil | lazy { commits_project_graph_path(project, id) } + 'graphs_commits' | 'heads' | nil | lazy { commits_project_graph_path(project, id) } + 'graphs_commits' | nil | 'foo/bar' | lazy { commits_project_graph_path(project, id_and_path) } + 'badges' | nil | nil | lazy { project_settings_ci_cd_path(project, ref: id) } + 'badges' | 'heads' | nil | lazy { project_settings_ci_cd_path(project, ref: id) } + 'badges' | nil | 'foo/bar' | lazy { project_settings_ci_cd_path(project, ref: id_and_path) } + 'commits' | nil | nil | lazy { project_commits_path(project, id) } + 'commits' | 'heads' | nil | lazy { project_commits_path(project, id, ref_type: 'heads') } + 'commits' | nil | 'foo/bar' | lazy { project_commits_path(project, id_and_path) } + nil | nil | nil | lazy { project_commits_path(project, id) } + nil | 'heads' | nil | lazy { project_commits_path(project, id, ref_type: 'heads') } + nil | nil | 'foo/bar' | lazy { project_commits_path(project, id_and_path) } + end - subject { get :switch, params: params } - - where(:destination, :ref_type, :redirected_to) do - 'tree' | nil | lazy { project_tree_path(project, id) } - 'tree' | 'heads' | lazy { project_tree_path(project, id) } - 'blob' | nil | lazy { project_blob_path(project, id) } - 'blob' | 'heads' | lazy { project_blob_path(project, id) } - 'graph' | nil | lazy { project_network_path(project, id) } - 'graph' | 'heads' | lazy { project_network_path(project, id, ref_type: 'heads') } - 'graphs' | nil | lazy { project_graph_path(project, id) } - 'graphs' | 'heads' | lazy { project_graph_path(project, id, ref_type: 'heads') } - 'find_file' | nil | lazy { project_find_file_path(project, id) } - 'find_file' | 'heads' | lazy { project_find_file_path(project, id) } - 'graphs_commits' | nil | lazy { commits_project_graph_path(project, id) } - 'graphs_commits' | 'heads' | lazy { commits_project_graph_path(project, id) } - 'badges' | nil | lazy { project_settings_ci_cd_path(project, ref: id) } - 'badges' | 'heads' | lazy { project_settings_ci_cd_path(project, ref: id) } - 'commits' | nil | lazy { project_commits_path(project, id) } - 'commits' | 'heads' | lazy { project_commits_path(project, id, ref_type: 'heads') } - nil | nil | lazy { project_commits_path(project, id) } - nil | 'heads' | lazy { project_commits_path(project, id, ref_type: 'heads') } + with_them do + it 'redirects to destination' do + expect(subject).to redirect_to(redirected_to) + end + end end - with_them do - it 'redirects to destination' do - expect(subject).to redirect_to(redirected_to) + context 'with bad path parameter' do + it 'returns 400 bad request' do + params = { + destination: 'tree', + namespace_id: project.namespace.to_param, + project_id: project, + id: 'master', + ref_type: nil, + path: '../bad_path_redirect' + } + + get :switch, params: params + + expect(response).to have_gitlab_http_status(:bad_request) end end end diff --git a/spec/factories/bulk_import/entities.rb b/spec/factories/bulk_import/entities.rb index a662d42c7c9..04407947f14 100644 --- a/spec/factories/bulk_import/entities.rb +++ b/spec/factories/bulk_import/entities.rb @@ -18,6 +18,7 @@ FactoryBot.define do trait(:project_entity) do source_type { :project_entity } + sequence(:source_full_path) { |n| "root/source-path-#{n}" } end trait :created do diff --git a/spec/helpers/nav/new_dropdown_helper_spec.rb b/spec/helpers/nav/new_dropdown_helper_spec.rb index 26dadd3b4f1..4ec120d152b 100644 --- a/spec/helpers/nav/new_dropdown_helper_spec.rb +++ b/spec/helpers/nav/new_dropdown_helper_spec.rb @@ -22,6 +22,7 @@ RSpec.describe Nav::NewDropdownHelper, feature_category: :navigation do allow(helper).to receive(:can?).and_return(false) allow(user).to receive(:can_create_project?) { with_can_create_project } allow(user).to receive(:can_create_group?) { with_can_create_group } + allow(user).to receive(:can?).and_call_original allow(user).to receive(:can?).with(:create_snippet) { with_can_create_snippet } end diff --git a/spec/lib/api/entities/project_import_status_spec.rb b/spec/lib/api/entities/project_import_status_spec.rb index 37a18718950..5d7f06dc78e 100644 --- a/spec/lib/api/entities/project_import_status_spec.rb +++ b/spec/lib/api/entities/project_import_status_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe API::Entities::ProjectImportStatus, :aggregate_failures do +RSpec.describe API::Entities::ProjectImportStatus, :aggregate_failures, feature_category: :importers do describe '#as_json' do subject { entity.as_json } @@ -67,14 +67,36 @@ RSpec.describe API::Entities::ProjectImportStatus, :aggregate_failures do context 'when import has failed' do let(:project) { create(:project, :import_failed, import_type: 'import_type', import_correlation_id: correlation_id, import_last_error: 'error') } - let(:entity) { described_class.new(project) } + let(:current_user) { create(:user) } + let(:options) { { current_user: current_user } } + let(:entity) { described_class.new(project, options) } + + context 'when user has access to read import status' do + before do + project.add_maintainer(current_user) + end + + it 'includes basic fields with import error' do + expect(subject[:import_status]).to eq('failed') + expect(subject[:import_type]).to eq('import_type') + expect(subject[:correlation_id]).to eq(correlation_id) + expect(subject[:import_error]).to eq('error') + expect(subject[:failed_relations]).to eq([]) + end + end - it 'includes basic fields with import error' do - expect(subject[:import_status]).to eq('failed') - expect(subject[:import_type]).to eq('import_type') - expect(subject[:correlation_id]).to eq(correlation_id) - expect(subject[:import_error]).to eq('error') - expect(subject[:failed_relations]).to eq([]) + context 'when user does not have access to read import status' do + before do + project.add_reporter(current_user) + end + + it 'includes basic fields with import error' do + expect(subject[:import_status]).to eq('failed') + expect(subject[:import_type]).to eq('import_type') + expect(subject[:correlation_id]).to eq(correlation_id) + expect(subject[:import_error]).to eq('Ask a maintainer to check the import status for more details.') + expect(subject[:failed_relations]).to eq([]) + end end end diff --git a/spec/lib/api/validations/validators/bulk_imports/destination_namespace_path_spec.rb b/spec/lib/api/validations/validators/bulk_imports/destination_namespace_path_spec.rb new file mode 100644 index 00000000000..39432c724a0 --- /dev/null +++ b/spec/lib/api/validations/validators/bulk_imports/destination_namespace_path_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Validations::Validators::BulkImports::DestinationNamespacePath, feature_category: :importers do + include ApiValidatorsHelpers + + subject do + described_class.new(['test'], {}, false, scope.new) + end + + context 'when destination namespace param is valid' do + it 'raises a validation error', :aggregate_failures do + expect_validation_error('test' => '?gitlab') + expect_validation_error('test' => "Users's something") + expect_validation_error('test' => '/source') + expect_validation_error('test' => 'http:') + expect_validation_error('test' => 'https:') + expect_validation_error('test' => 'example.com/?stuff=true') + expect_validation_error('test' => 'example.com:5000/?stuff=true') + expect_validation_error('test' => 'http://gitlab.example/gitlab-org/manage/import/gitlab-migration-test') + expect_validation_error('test' => 'good_for_me!') + expect_validation_error('test' => 'good_for+you') + expect_validation_error('test' => 'source/') + expect_validation_error('test' => '.source/full./path') + end + end + + context 'when destination namespace param is invalid' do + it 'does not raise a validation error', :aggregate_failures do + expect_no_validation_error('') + expect_no_validation_error('test' => '') + expect_no_validation_error('test' => 'source') + expect_no_validation_error('test' => 'source/full') + expect_no_validation_error('test' => 'source/full/path') + expect_no_validation_error('test' => 'sou_rce/fu-ll/pa.th') + expect_no_validation_error('test' => 'domain_namespace') + expect_no_validation_error('test' => 'gitlab-migration-test') + expect_no_validation_error('test' => '1-project-path') + expect_no_validation_error('test' => 'e-project-path') + end + end +end diff --git a/spec/lib/api/validations/validators/bulk_imports/source_full_path_validator_spec.rb b/spec/lib/api/validations/validators/bulk_imports/source_full_path_validator_spec.rb new file mode 100644 index 00000000000..f0d2121b0d6 --- /dev/null +++ b/spec/lib/api/validations/validators/bulk_imports/source_full_path_validator_spec.rb @@ -0,0 +1,168 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe API::Validations::Validators::BulkImports::SourceFullPath, feature_category: :importers do + include ApiValidatorsHelpers + using RSpec::Parameterized::TableSyntax + + subject do + described_class.new(['test'], {}, false, scope.new) + end + + let(:source_type_params) { { 'source_type' => source_type } } + + context 'when source_type is group_entity' do + let(:source_type) { 'group_entity' } + + context 'when source_full_path param is invalid' do + where(:invalid_param) do + [ + '', + '?gitlab', + "Users's something", + '/source', + 'http:', + 'https:', + 'example.com/?stuff=true', + 'example.com:5000/?stuff=true', + 'http://gitlab.example/gitlab-org/manage/import/gitlab-migration-test', + 'good_for_me!', + 'good_for+you', + 'source/', + '.source/full/path.' + ] + end + + with_them do + it 'raises a validation error' do + params = source_type_params.merge('test' => invalid_param) + + expect_validation_error(params) + end + end + end + + context 'when source_full_path param is valid' do + where(:valid_param) do + [ + 'source', + 'source/full', + 'source/full/path', + 'sou_rce/fu-ll/pa.th', + 'source/full/path---', + 'source/full/..path', + 'domain_namespace', + 'gitlab-migration-test', + '1-project-path', + 'e-project-path' + ] + end + + with_them do + it 'does not raise a validation error' do + params = source_type_params.merge('test' => valid_param) + + expect_no_validation_error(params) + end + end + end + end + + context 'when source_type is project_entity' do + let(:source_type) { 'project_entity' } + + context 'when source_full_path param is invalid' do + where(:invalid_param) do + [ + '', + '?gitlab', + "Users's something", + '/source', + 'http:', + 'https:', + 'example.com/?stuff=true', + 'example.com:5000/?stuff=true', + 'http://gitlab.example/gitlab-org/manage/import/gitlab-migration-test', + 'good_for_me!', + 'good_for+you', + 'source/', + 'source', + '.source/full./path', + 'domain_namespace', + 'gitlab-migration-test', + '1-project-path', + 'e-project-path' + ] + end + + with_them do + it 'raises a validation error' do + params = source_type_params.merge('test' => invalid_param) + + expect_validation_error(params) + end + end + end + + context 'when source_full_path param is valid' do + where(:valid_param) do + [ + 'source/full', + 'source/full/path', + 'sou_rce/fu-ll/pa.th', + 'source/full/path---', + 'source/full/..path' + ] + end + + with_them do + it 'does not raise a validation error' do + params = source_type_params.merge('test' => valid_param) + + expect_no_validation_error(params) + end + end + end + end + + context 'when source_type is invalid' do + let(:source_type) { '' } + + context 'when source_full_path param is invalid' do + where(:invalid_param) do + [ + '', + '?gitlab', + "Users's something", + '/source', + 'http:', + 'https:', + 'example.com/?stuff=true', + 'example.com:5000/?stuff=true', + 'http://gitlab.example/gitlab-org/manage/import/gitlab-migration-test', + 'good_for_me!', + 'good_for+you', + 'source/', + '.source/full./path', + 'source', + 'source/full', + 'source/full/path', + 'sou_rce/fu-ll/pa.th', + 'domain_namespace', + 'gitlab-migration-test', + '1-project-path', + 'e-project-path' + ] + end + + with_them do + it 'raises a validation error' do + params = source_type_params.merge('test' => invalid_param) + + expect_validation_error(params) + end + end + end + end +end diff --git a/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb index 50640e1e4ba..c91b031de30 100644 --- a/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb +++ b/spec/lib/bulk_imports/common/pipelines/lfs_objects_pipeline_spec.rb @@ -7,7 +7,7 @@ RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline, feature_categ let_it_be(:oid) { 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855' } let(:tmpdir) { Dir.mktmpdir } - let(:entity) { create(:bulk_import_entity, :project_entity, project: portable, source_full_path: 'test', source_xid: nil) } + let(:entity) { create(:bulk_import_entity, :project_entity, project: portable, source_xid: nil) } let(:tracker) { create(:bulk_import_tracker, entity: entity) } let(:context) { BulkImports::Pipeline::Context.new(tracker) } let(:lfs_dir_path) { tmpdir } @@ -53,7 +53,7 @@ RSpec.describe BulkImports::Common::Pipelines::LfsObjectsPipeline, feature_categ .to receive(:new) .with( configuration: context.configuration, - relative_url: "/#{entity.pluralized_name}/test/export_relations/download?relation=lfs_objects", + relative_url: "/#{entity.pluralized_name}/#{CGI.escape(entity.source_full_path)}/export_relations/download?relation=lfs_objects", tmpdir: tmpdir, filename: 'lfs_objects.tar.gz') .and_return(download_service) diff --git a/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb b/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb index 09c8c7b92c2..675ae935c1c 100644 --- a/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb +++ b/spec/lib/bulk_imports/common/pipelines/uploads_pipeline_spec.rb @@ -79,7 +79,7 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category .to receive(:new) .with( configuration: context.configuration, - relative_url: "/#{entity.pluralized_name}/test/export_relations/download?relation=uploads", + relative_url: "/#{entity.pluralized_name}/#{CGI.escape(entity.source_full_path)}/export_relations/download?relation=uploads", tmpdir: tmpdir, filename: 'uploads.tar.gz') .and_return(download_service) @@ -178,14 +178,14 @@ RSpec.describe BulkImports::Common::Pipelines::UploadsPipeline, feature_category context 'when importing to group' do let(:portable) { group } - let(:entity) { create(:bulk_import_entity, :group_entity, group: group, source_full_path: 'test', source_xid: nil) } + let(:entity) { create(:bulk_import_entity, :group_entity, group: group, source_xid: nil) } include_examples 'uploads import' end context 'when importing to project' do let(:portable) { project } - let(:entity) { create(:bulk_import_entity, :project_entity, project: project, source_full_path: 'test', source_xid: nil) } + let(:entity) { create(:bulk_import_entity, :project_entity, project: project, source_xid: nil) } include_examples 'uploads import' end diff --git a/spec/lib/bulk_imports/projects/pipelines/design_bundle_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/design_bundle_pipeline_spec.rb index 87efad92131..e65339ffdd0 100644 --- a/spec/lib/bulk_imports/projects/pipelines/design_bundle_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/design_bundle_pipeline_spec.rb @@ -9,7 +9,7 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline, feature_c let(:tmpdir) { Dir.mktmpdir } let(:design_bundle_path) { File.join(tmpdir, 'design.bundle') } let(:entity) do - create(:bulk_import_entity, :project_entity, project: portable, source_full_path: 'test', source_xid: nil) + create(:bulk_import_entity, :project_entity, project: portable, source_xid: nil) end let(:tracker) { create(:bulk_import_tracker, entity: entity) } @@ -52,7 +52,8 @@ RSpec.describe BulkImports::Projects::Pipelines::DesignBundlePipeline, feature_c .to receive(:new) .with( configuration: context.configuration, - relative_url: "/#{entity.pluralized_name}/test/export_relations/download?relation=design", + relative_url: "/#{entity.pluralized_name}/#{CGI.escape(entity.source_full_path)}" \ + '/export_relations/download?relation=design', tmpdir: tmpdir, filename: 'design.tar.gz') .and_return(download_service) diff --git a/spec/lib/bulk_imports/projects/pipelines/repository_bundle_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/repository_bundle_pipeline_spec.rb index 5aae8c959fa..2865215823a 100644 --- a/spec/lib/bulk_imports/projects/pipelines/repository_bundle_pipeline_spec.rb +++ b/spec/lib/bulk_imports/projects/pipelines/repository_bundle_pipeline_spec.rb @@ -9,7 +9,7 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline, featu let(:tmpdir) { Dir.mktmpdir } let(:bundle_path) { File.join(tmpdir, 'repository.bundle') } let(:entity) do - create(:bulk_import_entity, :project_entity, project: portable, source_full_path: 'test', source_xid: nil) + create(:bulk_import_entity, :project_entity, project: portable, source_xid: nil) end let(:tracker) { create(:bulk_import_tracker, entity: entity) } @@ -62,7 +62,8 @@ RSpec.describe BulkImports::Projects::Pipelines::RepositoryBundlePipeline, featu .to receive(:new) .with( configuration: context.configuration, - relative_url: "/#{entity.pluralized_name}/test/export_relations/download?relation=repository", + relative_url: "/#{entity.pluralized_name}/#{CGI.escape(entity.source_full_path)}" \ + '/export_relations/download?relation=repository', tmpdir: tmpdir, filename: 'repository.tar.gz') .and_return(download_service) diff --git a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb index 2e87c582040..cb3f1fe86dc 100644 --- a/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb +++ b/spec/lib/gitlab/pagination/gitaly_keyset_pager_spec.rb @@ -127,17 +127,27 @@ RSpec.describe Gitlab::Pagination::GitalyKeysetPager do end end - context 'with "none" pagination option' do + context "with 'none' pagination option" do let(:expected_result) { double(:result) } let(:query) { { pagination: 'none' } } - it 'uses offset pagination' do - expect(finder).to receive(:execute).with(gitaly_pagination: false).and_return(expected_result) - expect(Kaminari).not_to receive(:paginate_array) - expect(Gitlab::Pagination::OffsetPagination).not_to receive(:new) + context "with a finder that is not a TreeFinder" do + it_behaves_like 'offset pagination' + end + + context "with a finder that is a TreeFinder" do + before do + allow(finder).to receive(:is_a?).with(::Repositories::TreeFinder).and_return(true) + end - actual_result = pager.paginate(finder) - expect(actual_result).to eq(expected_result) + it "doesn't uses offset pagination" do + expect(finder).to receive(:execute).with(gitaly_pagination: false).and_return(expected_result) + expect(Kaminari).not_to receive(:paginate_array) + expect(Gitlab::Pagination::OffsetPagination).not_to receive(:new) + + actual_result = pager.paginate(finder) + expect(actual_result).to eq(expected_result) + end end end end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index 5e58282ff92..f85395fb491 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -86,36 +86,6 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do } end - describe '.bulk_import_destination_namespace_path_regex' do - subject { described_class.bulk_import_destination_namespace_path_regex } - - it { is_expected.not_to match('?gitlab') } - it { is_expected.not_to match("Users's something") } - it { is_expected.not_to match('/source') } - it { is_expected.not_to match('http:') } - it { is_expected.not_to match('https:') } - it { is_expected.not_to match('example.com/?stuff=true') } - it { is_expected.not_to match('example.com:5000/?stuff=true') } - it { is_expected.not_to match('http://gitlab.example/gitlab-org/manage/import/gitlab-migration-test') } - it { is_expected.not_to match('_good_for_me!') } - it { is_expected.not_to match('good_for+you') } - it { is_expected.not_to match('source/') } - it { is_expected.not_to match('.source/full./path') } - it { is_expected.not_to match('.source/.full/.path') } - it { is_expected.not_to match('_source') } - it { is_expected.not_to match('.source') } - - it { is_expected.to match('source') } - it { is_expected.to match('source/full') } - it { is_expected.to match('source/full/path') } - it { is_expected.to match('sou_rce/fu-ll/pa.th') } - it { is_expected.to match('domain_namespace') } - it { is_expected.to match('gitlab-migration-test') } - it { is_expected.to match('1-project-path') } - it { is_expected.to match('e-project-path') } - it { is_expected.to match('') } # it is possible to pass an empty string for destination_namespace in bulk_import POST request - end - describe '.bulk_import_source_full_path_regex_message' do subject { described_class.bulk_import_source_full_path_regex_message } @@ -129,44 +99,6 @@ RSpec.describe Gitlab::Regex, feature_category: :tooling do } end - describe '.bulk_import_source_full_path_regex' do - subject { described_class.bulk_import_source_full_path_regex } - - it { is_expected.not_to match("Users's something") } - it { is_expected.not_to match('/source') } - it { is_expected.not_to match('http:') } - it { is_expected.not_to match('https:') } - it { is_expected.not_to match('example.com/?stuff=true') } - it { is_expected.not_to match('example.com:5000/?stuff=true') } - it { is_expected.not_to match('http://gitlab.example/gitlab-org/manage/import/gitlab-migration-test') } - it { is_expected.not_to match('source/') } - it { is_expected.not_to match('') } - it { is_expected.not_to match('.source/full./path') } - it { is_expected.not_to match('?gitlab') } - it { is_expected.not_to match('_good_for_me!') } - it { is_expected.not_to match('group/@*%_my_other-project-----') } - it { is_expected.not_to match('_foog-for-me!') } - it { is_expected.not_to match('.source/full/path.') } - - it { is_expected.to match('good_for+you') } - it { is_expected.to match('source') } - it { is_expected.to match('.source') } - it { is_expected.to match('_source') } - it { is_expected.to match('source/full') } - it { is_expected.to match('source/full/path') } - it { is_expected.to match('domain_namespace') } - it { is_expected.to match('gitlab-migration-test') } - it { is_expected.to match('source/full/path-') } - it { is_expected.to match('.source/full/path') } - it { is_expected.to match('.source/.full/.path') } - it { is_expected.to match('source/full/.path') } - it { is_expected.to match('source/full/..path') } - it { is_expected.to match('source/full/---1path') } - it { is_expected.to match('source/full/-___path') } - it { is_expected.to match('source/full/path---') } - it { is_expected.to match('group/__my_other-project-----') } - end - describe '.group_path_regex' do subject { described_class.group_path_regex } diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb index eee5396bdbf..a1e1d2caa90 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_bulk_imports_entities_metric_spec.rb @@ -5,17 +5,17 @@ require 'spec_helper' RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountBulkImportsEntitiesMetric, feature_category: :importers do let_it_be(:user) { create(:user) } let_it_be(:bulk_import_projects) do - create_list(:bulk_import_entity, 2, source_type: 'project_entity', created_at: 3.weeks.ago, status: 2) - create(:bulk_import_entity, source_type: 'project_entity', created_at: 3.weeks.ago, status: 0) + create_list(:bulk_import_entity, 2, :project_entity, created_at: 3.weeks.ago, status: 2) + create(:bulk_import_entity, :project_entity, created_at: 3.weeks.ago, status: 0) end let_it_be(:bulk_import_groups) do - create_list(:bulk_import_entity, 2, source_type: 'group_entity', created_at: 3.weeks.ago, status: 2) - create(:bulk_import_entity, source_type: 'group_entity', created_at: 3.weeks.ago, status: 0) + create_list(:bulk_import_entity, 2, :group_entity, created_at: 3.weeks.ago, status: 2) + create(:bulk_import_entity, :group_entity, created_at: 3.weeks.ago, status: 0) end let_it_be(:old_bulk_import_project) do - create(:bulk_import_entity, source_type: 'project_entity', created_at: 2.months.ago, status: 2) + create(:bulk_import_entity, :project_entity, created_at: 2.months.ago, status: 2) end context 'with no source_type' do diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_total_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_total_metric_spec.rb index bd432b614e7..bf3cd810e23 100644 --- a/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_total_metric_spec.rb +++ b/spec/lib/gitlab/usage/metrics/instrumentations/count_imported_projects_total_metric_spec.rb @@ -15,15 +15,15 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::CountImportedProjectsTo let_it_be(:old_import) { create(:project, import_type: 'gitea', creator_id: user.id, created_at: 2.months.ago) } let_it_be(:bulk_import_projects) do - create_list(:bulk_import_entity, 3, source_type: 'project_entity', created_at: 3.weeks.ago) + create_list(:bulk_import_entity, 3, :project_entity, created_at: 3.weeks.ago) end let_it_be(:bulk_import_groups) do - create_list(:bulk_import_entity, 3, source_type: 'group_entity', created_at: 3.weeks.ago) + create_list(:bulk_import_entity, 3, :group_entity, created_at: 3.weeks.ago) end let_it_be(:old_bulk_import_project) do - create(:bulk_import_entity, source_type: 'project_entity', created_at: 2.months.ago) + create(:bulk_import_entity, :project_entity, created_at: 2.months.ago) end before do diff --git a/spec/models/bulk_imports/entity_spec.rb b/spec/models/bulk_imports/entity_spec.rb index 7179ed7cb42..da1eb12e9b8 100644 --- a/spec/models/bulk_imports/entity_spec.rb +++ b/spec/models/bulk_imports/entity_spec.rb @@ -24,11 +24,20 @@ RSpec.describe BulkImports::Entity, type: :model, feature_category: :importers d it { is_expected.to define_enum_for(:source_type).with_values(%i[group_entity project_entity]) } - context 'when formatting with regexes' do + context 'when source_type is group_entity' do + subject { build(:bulk_import_entity, :group_entity) } + it { is_expected.to allow_values('source', 'source/path', 'source/full/path').for(:source_full_path) } it { is_expected.not_to allow_values('/source', 'http://source/path', 'sou rce/full/path', '').for(:source_full_path) } end + context 'when source_type is project_entity' do + subject { build(:bulk_import_entity, :project_entity) } + + it { is_expected.to allow_values('source/path', 'source/full/path').for(:source_full_path) } + it { is_expected.not_to allow_values('/source', 'source', 'http://source/path', 'sou rce/full/path', '').for(:source_full_path) } + end + context 'when associated with a group and project' do it 'is invalid' do entity = build(:bulk_import_entity, group: build(:group), project: build(:project)) diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 602b7148d0e..2f0d351063b 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -578,6 +578,11 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do expect(described_class.new(maintainer, project)).to be_allowed(:admin_incident_management_timeline_event_tag) expect(described_class.new(owner, project)).to be_allowed(:admin_incident_management_timeline_event_tag) end + + it 'allows to read import error' do + expect(described_class.new(maintainer, project)).to be_allowed(:read_import_error) + expect(described_class.new(owner, project)).to be_allowed(:read_import_error) + end end context 'when user is a developer/guest/reporter' do @@ -586,6 +591,12 @@ RSpec.describe ProjectPolicy, feature_category: :system_access do expect(described_class.new(guest, project)).to be_disallowed(:admin_incident_management_timeline_event_tag) expect(described_class.new(reporter, project)).to be_disallowed(:admin_incident_management_timeline_event_tag) end + + it 'disallows reading the import error' do + expect(described_class.new(developer, project)).to be_disallowed(:read_import_error) + expect(described_class.new(guest, project)).to be_disallowed(:read_import_error) + expect(described_class.new(reporter, project)).to be_disallowed(:read_import_error) + end end context 'when user is not a member of the project' do diff --git a/spec/requests/api/bulk_imports_spec.rb b/spec/requests/api/bulk_imports_spec.rb index b159d4ad445..fdbfbf052d0 100644 --- a/spec/requests/api/bulk_imports_spec.rb +++ b/spec/requests/api/bulk_imports_spec.rb @@ -248,6 +248,20 @@ RSpec.describe API::BulkImports, feature_category: :importers do end end + context 'when the destination_namespace is invalid' do + it 'returns invalid error' do + params[:entities][0][:destination_namespace] = 'dest?nation-namespace' + + request + expect(response).to have_gitlab_http_status(:bad_request) + expect(json_response['error']).to include('entities[0][destination_namespace] must have a relative path ' \ + 'structure with no HTTP protocol characters, or leading or ' \ + 'trailing forward slashes. Path segments must not start or end ' \ + 'with a special character, and must not contain consecutive ' \ + 'special characters.') + end + end + context 'when the destination_slug is invalid' do it 'returns invalid error when restricting special characters is disabled' do Feature.disable(:restrict_special_characters_in_namespace_path) diff --git a/spec/requests/api/ml/mlflow/experiments_spec.rb b/spec/requests/api/ml/mlflow/experiments_spec.rb index fc2e814752c..409b4529699 100644 --- a/spec/requests/api/ml/mlflow/experiments_spec.rb +++ b/spec/requests/api/ml/mlflow/experiments_spec.rb @@ -179,7 +179,7 @@ RSpec.describe API::Ml::Mlflow::Experiments, feature_category: :mlops do end it_behaves_like 'MLflow|shared error cases' - it_behaves_like 'MLflow|Requires api scope' + it_behaves_like 'MLflow|Requires api scope and write permission' end end @@ -203,7 +203,7 @@ RSpec.describe API::Ml::Mlflow::Experiments, feature_category: :mlops do end it_behaves_like 'MLflow|shared error cases' - it_behaves_like 'MLflow|Requires api scope' + it_behaves_like 'MLflow|Requires api scope and write permission' it_behaves_like 'MLflow|Bad Request on missing required', [:key, :value] end end diff --git a/spec/requests/api/ml/mlflow/runs_spec.rb b/spec/requests/api/ml/mlflow/runs_spec.rb index a85fe4d867a..88fd1bada1c 100644 --- a/spec/requests/api/ml/mlflow/runs_spec.rb +++ b/spec/requests/api/ml/mlflow/runs_spec.rb @@ -124,7 +124,7 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do end it_behaves_like 'MLflow|shared error cases' - it_behaves_like 'MLflow|Requires api scope' + it_behaves_like 'MLflow|Requires api scope and write permission' end end @@ -204,7 +204,7 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do end it_behaves_like 'MLflow|shared error cases' - it_behaves_like 'MLflow|Requires api scope' + it_behaves_like 'MLflow|Requires api scope and write permission' it_behaves_like 'MLflow|run_id param error cases' end end @@ -222,7 +222,7 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do describe 'Error Cases' do it_behaves_like 'MLflow|shared error cases' - it_behaves_like 'MLflow|Requires api scope' + it_behaves_like 'MLflow|Requires api scope and write permission' it_behaves_like 'MLflow|run_id param error cases' it_behaves_like 'MLflow|Bad Request on missing required', [:key, :value, :timestamp] end @@ -247,7 +247,7 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do end it_behaves_like 'MLflow|shared error cases' - it_behaves_like 'MLflow|Requires api scope' + it_behaves_like 'MLflow|Requires api scope and write permission' it_behaves_like 'MLflow|run_id param error cases' it_behaves_like 'MLflow|Bad Request on missing required', [:key, :value] end @@ -272,7 +272,7 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do end it_behaves_like 'MLflow|shared error cases' - it_behaves_like 'MLflow|Requires api scope' + it_behaves_like 'MLflow|Requires api scope and write permission' it_behaves_like 'MLflow|run_id param error cases' it_behaves_like 'MLflow|Bad Request on missing required', [:key, :value] end @@ -342,7 +342,7 @@ RSpec.describe API::Ml::Mlflow::Runs, feature_category: :mlops do end it_behaves_like 'MLflow|shared error cases' - it_behaves_like 'MLflow|Requires api scope' + it_behaves_like 'MLflow|Requires api scope and write permission' it_behaves_like 'MLflow|run_id param error cases' end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index f5d1bbbc7e8..ef0959689e3 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -3270,16 +3270,41 @@ RSpec.describe API::Projects, :aggregate_failures, feature_category: :groups_and project_fork_target.add_maintainer(user) end - it 'allows project to be forked from an existing project' do - expect(project_fork_target).not_to be_forked + context 'and user is a reporter of target group' do + let_it_be_with_reload(:target_group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + let_it_be_with_reload(:project_fork_target) { create(:project, namespace: target_group) } - post api(path, user) - project_fork_target.reload + before do + target_group.add_reporter(user) + end - expect(response).to have_gitlab_http_status(:created) - expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) - expect(project_fork_target.fork_network_member).to be_present - expect(project_fork_target).to be_forked + it 'fails as target namespace is unauthorized' do + post api(path, user) + + expect(response).to have_gitlab_http_status(:unauthorized) + expect(json_response['message']).to eq "401 Unauthorized - Target Namespace" + end + end + + context 'and user is a developer of target group' do + let_it_be_with_reload(:target_group) { create(:group, project_creation_level: ::Gitlab::Access::DEVELOPER_MAINTAINER_PROJECT_ACCESS) } + let_it_be_with_reload(:project_fork_target) { create(:project, namespace: target_group) } + + before do + target_group.add_developer(user) + end + + it 'allows project to be forked from an existing project' do + expect(project_fork_target).not_to be_forked + + post api(path, user) + project_fork_target.reload + + expect(response).to have_gitlab_http_status(:created) + expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) + expect(project_fork_target.fork_network_member).to be_present + expect(project_fork_target).to be_forked + end end it 'fails without permission from forked_from project' do diff --git a/spec/services/error_tracking/list_projects_service_spec.rb b/spec/services/error_tracking/list_projects_service_spec.rb index 8408adcc21d..d91808edc8d 100644 --- a/spec/services/error_tracking/list_projects_service_spec.rb +++ b/spec/services/error_tracking/list_projects_service_spec.rb @@ -6,7 +6,7 @@ RSpec.describe ErrorTracking::ListProjectsService, feature_category: :integratio let_it_be(:user) { create(:user) } let_it_be(:project, reload: true) { create(:project) } - let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/sentry-org/sentry-project' } + let(:sentry_url) { 'https://sentrytest.gitlab.com/api/0/projects/org/proj/' } let(:token) { 'test-token' } let(:new_api_host) { 'https://gitlab.com/' } let(:new_token) { 'new-token' } @@ -66,6 +66,20 @@ RSpec.describe ErrorTracking::ListProjectsService, feature_category: :integratio end end + context 'with the similar api host' do + let(:api_host) { 'https://sentrytest.gitlab.co' } + + it 'returns an error' do + expect(result[:message]).to start_with('Token is a required field') + expect(error_tracking_setting).not_to be_valid + expect(error_tracking_setting).not_to receive(:list_sentry_projects) + end + + it 'resets the token' do + expect { subject.execute }.to change { error_tracking_setting.token }.from(token).to(nil) + end + end + context 'with a new api host' do let(:api_host) { new_api_host } diff --git a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb index cbd0ffbab21..f2052f4202d 100644 --- a/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb +++ b/spec/support/shared_examples/features/search/redacted_search_results_shared_examples.rb @@ -254,7 +254,7 @@ RSpec.shared_examples 'a redacted search results' do end context 'with :with_api_entity_associations' do - it_behaves_like "redaction limits N+1 queries", limit: 14 + it_behaves_like "redaction limits N+1 queries", limit: 15 end end diff --git a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb index f2c38d70508..00e50b07909 100644 --- a/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb +++ b/spec/support/shared_examples/requests/api/ml/mlflow/mlflow_shared_examples.rb @@ -8,12 +8,25 @@ RSpec.shared_examples 'MLflow|Not Found - Resource Does Not Exist' do end end -RSpec.shared_examples 'MLflow|Requires api scope' do +RSpec.shared_examples 'MLflow|Requires api scope and write permission' do context 'when user has access but token has wrong scope' do let(:access_token) { tokens[:read] } it { is_expected.to have_gitlab_http_status(:forbidden) } end + + context 'when user has access but is not allowed to write' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?) + .with(current_user, :write_model_experiments, project) + .and_return(false) + end + + it "is Unauthorized" do + is_expected.to have_gitlab_http_status(:unauthorized) + end + end end RSpec.shared_examples 'MLflow|Requires read_api scope' do |