summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop_todo/rspec/verified_doubles.yml1
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/api.js4
-rw-r--r--app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue4
-rw-r--r--app/assets/stylesheets/utilities.scss13
-rw-r--r--app/controllers/admin/applications_controller.rb11
-rw-r--r--app/controllers/concerns/harbor/access.rb2
-rw-r--r--app/controllers/groups/settings/applications_controller.rb12
-rw-r--r--app/controllers/oauth/applications_controller.rb11
-rw-r--r--app/helpers/jira_connect_helper.rb14
-rw-r--r--app/models/jira_connect_installation.rb6
-rw-r--r--app/services/issues/base_service.rb3
-rw-r--r--app/services/issues/relative_position_rebalancing_service.rb2
-rw-r--r--app/views/jira_connect/subscriptions/index.html.haml2
-rw-r--r--app/views/shared/doorkeeper/applications/_show.html.haml9
-rw-r--r--config/feature_flags/development/hash_oauth_secrets.yml8
-rw-r--r--config/feature_flags/development/rebalance_issues.yml8
-rw-r--r--config/initializers/doorkeeper.rb4
-rw-r--r--config/initializers/zz_metrics.rb2
-rw-r--r--config/metrics/counts_all/20210216180242_web_ide_commits.yml5
-rw-r--r--config/metrics/counts_all/20210216180244_web_ide_views.yml5
-rw-r--r--config/metrics/counts_all/20210216180246_web_ide_merge_requests.yml5
-rw-r--r--config/metrics/counts_all/20210216180248_web_ide_previews.yml5
-rw-r--r--config/metrics/counts_all/20210216180250_web_ide_terminals.yml5
-rw-r--r--config/metrics/counts_all/20210216180252_web_ide_pipelines.yml5
-rw-r--r--config/metrics/counts_all/20220122022215_web_ide_previews_success.yml5
-rw-r--r--db/migrate/20220831182105_add_constraints_view.rb32
-rw-r--r--db/schema_migrations/202208311821051
-rw-r--r--db/structure.sql15
-rw-r--r--doc/api/discussions.md32
-rw-r--r--doc/development/documentation/index.md4
-rw-r--r--doc/development/import_project.md16
-rw-r--r--doc/integration/oauth_provider.md10
-rw-r--r--doc/user/compliance/license_compliance/img/denied_licenses_v15_3.pngbin115891 -> 39570 bytes
-rw-r--r--doc/user/group/epics/epic_boards.md36
-rw-r--r--doc/user/project/issues/img/related_issue_block_v15_3.pngbin28910 -> 10699 bytes
-rw-r--r--doc/user/project/issues/img/related_issues_add_v15_3.pngbin24947 -> 9286 bytes
-rw-r--r--doc/user/project/issues/managing_issues.md7
-rw-r--r--lib/gitlab/ci/pipeline/chain/assign_partition.rb7
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb4
-rw-r--r--lib/gitlab/database/gitlab_schemas.yml1
-rw-r--r--lib/gitlab/database/partitioning/convert_table_to_first_list_partition.rb214
-rw-r--r--lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb48
-rw-r--r--lib/gitlab/database/postgres_constraint.rb29
-rw-r--r--lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512.rb28
-rw-r--r--lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512.rb38
-rw-r--r--lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512.rb30
-rw-r--r--lib/gitlab/github_import/importer/protected_branches_importer.rb2
-rw-r--r--lib/gitlab/import_export/base/relation_factory.rb2
-rw-r--r--lib/gitlab/import_export/base/relation_object_saver.rb13
-rw-r--r--lib/gitlab/import_export/project/import_export.yml48
-rw-r--r--lib/gitlab/import_export/project/object_builder.rb14
-rw-r--r--lib/gitlab/import_export/project/relation_tree_restorer.rb2
-rw-r--r--lib/gitlab/usage/metrics/instrumentations/redis_metric.rb10
-rw-r--r--lib/gitlab/usage_data_counters.rb4
-rw-r--r--lib/gitlab/usage_data_counters/base_counter.rb4
-rw-r--r--lib/sidebars/projects/menus/packages_registries_menu.rb2
-rw-r--r--locale/gitlab.pot6
-rw-r--r--qa/qa/support/matchers/eventually_matcher.rb1
-rw-r--r--spec/controllers/admin/applications_controller_spec.rb84
-rw-r--r--spec/controllers/groups/settings/applications_controller_spec.rb84
-rw-r--r--spec/controllers/oauth/applications_controller_spec.rb27
-rw-r--r--spec/fixtures/lib/gitlab/import_export/group/project.json33
-rw-r--r--spec/fixtures/lib/gitlab/import_export/group/tree/project/issues.ndjson2
-rw-r--r--spec/fixtures/security_reports/deprecated/gl-sast-report.json2
-rw-r--r--spec/fixtures/security_reports/feature-branch/gl-sast-report.json2
-rw-r--r--spec/fixtures/security_reports/feature-branch/gl-secret-detection-report.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-common-scanning-report-names.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-common-scanning-report-without-top-level-scanner.json45
-rw-r--r--spec/fixtures/security_reports/master/gl-common-scanning-report.json403
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-missing-scanner.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report-bandit.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report-gosec.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report-minimal.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-bandit.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-gosec.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-sast-report.json2
-rw-r--r--spec/fixtures/security_reports/master/gl-secret-detection-report.json2
-rw-r--r--spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js4
-rw-r--r--spec/helpers/jira_connect_helper_spec.rb32
-rw-r--r--spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb51
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb19
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/command_spec.rb38
-rw-r--r--spec/lib/gitlab/database/partitioning/convert_table_to_first_list_partition_spec.rb246
-rw-r--r--spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb70
-rw-r--r--spec/lib/gitlab/database/postgres_constraint_spec.rb123
-rw-r--r--spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb45
-rw-r--r--spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb (renamed from spec/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512_spec.rb)2
-rw-r--r--spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb7
-rw-r--r--spec/lib/gitlab/import_export/all_models.yml14
-rw-r--r--spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb25
-rw-r--r--spec/lib/gitlab/import_export/safe_model_attributes.yml14
-rw-r--r--spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb14
-rw-r--r--spec/models/ci/runner_spec.rb141
-rw-r--r--spec/models/jira_connect_installation_spec.rb26
-rw-r--r--spec/policies/project_policy_spec.rb35
-rw-r--r--spec/services/ci/create_pipeline_service/partitioning_spec.rb41
-rw-r--r--spec/services/issues/relative_position_rebalancing_service_spec.rb15
-rw-r--r--spec/services/issues/update_service_spec.rb32
-rw-r--r--spec/support/helpers/usage_data_helpers.rb4
-rw-r--r--spec/support/shared_contexts/policies/project_policy_shared_context.rb13
-rw-r--r--spec/support/shared_examples/features/manage_applications_shared_examples.rb92
-rw-r--r--spec/support/shared_examples/policies/project_policy_shared_examples.rb150
-rw-r--r--spec/support/shared_examples/requests/applications_controller_shared_examples.rb6
103 files changed, 2207 insertions, 565 deletions
diff --git a/.rubocop_todo/rspec/verified_doubles.yml b/.rubocop_todo/rspec/verified_doubles.yml
index 0fa36d1941c..a225dbf21ae 100644
--- a/.rubocop_todo/rspec/verified_doubles.yml
+++ b/.rubocop_todo/rspec/verified_doubles.yml
@@ -448,7 +448,6 @@ RSpec/VerifiedDoubles:
- spec/lib/gitlab/ci/config/external/file/project_spec.rb
- spec/lib/gitlab/ci/config/external/rules_spec.rb
- spec/lib/gitlab/ci/parsers/test/junit_spec.rb
- - spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
- spec/lib/gitlab/ci/pipeline/chain/evaluate_workflow_rules_spec.rb
- spec/lib/gitlab/ci/pipeline/chain/helpers_spec.rb
- spec/lib/gitlab/ci/pipeline/chain/limit/deployments_spec.rb
diff --git a/app/assets/javascripts/jira_connect/subscriptions/api.js b/app/assets/javascripts/jira_connect/subscriptions/api.js
index 8b32a67c23b..c79d7002111 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/api.js
+++ b/app/assets/javascripts/jira_connect/subscriptions/api.js
@@ -92,6 +92,6 @@ export const fetchOAuthApplicationId = () => {
return axiosInstance.get(JIRA_CONNECT_OAUTH_APPLICATION_ID_PATH);
};
-export const fetchOAuthToken = (oauthTokenURL, data = {}) => {
- return axiosInstance.post(oauthTokenURL, data);
+export const fetchOAuthToken = (oauthTokenPath, data = {}) => {
+ return axiosInstance.post(oauthTokenPath, data);
};
diff --git a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
index 8df906dd292..4cf3a1a0279 100644
--- a/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
+++ b/app/assets/javascripts/jira_connect/subscriptions/components/sign_in_oauth_button.vue
@@ -159,9 +159,9 @@ export default {
async getOAuthToken(code) {
const {
oauth_token_payload: oauthTokenPayload,
- oauth_token_url: oauthTokenURL,
+ oauth_token_path: oauthTokenPath,
} = this.oauthMetadata;
- const { data } = await fetchOAuthToken(oauthTokenURL, {
+ const { data } = await fetchOAuthToken(oauthTokenPath, {
...oauthTokenPayload,
code,
code_verifier: this.codeVerifier,
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index e5b6c9811b5..bdb8f758137 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -285,19 +285,6 @@ $gl-line-height-42: px-to-rem(42px);
padding-right: $gl-spacing-scale-10;
}
-// TODO: will be moved to @gitlab/ui as part of https://gitlab.com/gitlab-org/gitlab/-/issues/349008
-.gl-sm-mt-6 {
- @include media-breakpoint-up(sm) {
- margin-top: $gl-spacing-scale-6;
- }
-}
-
-.gl-sm-mt-6\! {
- @include media-breakpoint-up(sm) {
- margin-top: $gl-spacing-scale-6 !important;
- }
-}
-
/*
All of the following (up until the "End gitlab-ui#1709" comment) will be moved
to @gitlab/ui by https://gitlab.com/gitlab-org/gitlab-ui/-/issues/1709
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index b0d7c8cb8f2..d66b3cb4366 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -14,7 +14,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
end
def show
- @created = get_created_session
+ @created = get_created_session if Feature.disabled?('hash_oauth_secrets')
end
def new
@@ -30,9 +30,14 @@ class Admin::ApplicationsController < Admin::ApplicationController
if @application.persisted?
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- set_created_session
+ if Feature.enabled?('hash_oauth_secrets')
+ @created = true
+ render :show
+ else
+ set_created_session
- redirect_to admin_application_url(@application)
+ redirect_to admin_application_url(@application)
+ end
else
render :new
end
diff --git a/app/controllers/concerns/harbor/access.rb b/app/controllers/concerns/harbor/access.rb
index 70de72f15fc..211566aeda7 100644
--- a/app/controllers/concerns/harbor/access.rb
+++ b/app/controllers/concerns/harbor/access.rb
@@ -17,7 +17,7 @@ module Harbor
private
def harbor_registry_enabled!
- render_404 unless Feature.enabled?(:harbor_registry_integration)
+ render_404 unless Feature.enabled?(:harbor_registry_integration, defined?(group) ? group : project)
end
def authorize_read_harbor_registry!
diff --git a/app/controllers/groups/settings/applications_controller.rb b/app/controllers/groups/settings/applications_controller.rb
index bfe61696e0f..3557d485422 100644
--- a/app/controllers/groups/settings/applications_controller.rb
+++ b/app/controllers/groups/settings/applications_controller.rb
@@ -16,7 +16,7 @@ module Groups
end
def show
- @created = get_created_session
+ @created = get_created_session if Feature.disabled?('hash_oauth_secrets')
end
def edit
@@ -28,9 +28,15 @@ module Groups
if @application.persisted?
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- set_created_session
+ if Feature.enabled?('hash_oauth_secrets')
- redirect_to group_settings_application_url(@group, @application)
+ @created = true
+ render :show
+ else
+ set_created_session
+
+ redirect_to group_settings_application_url(@group, @application)
+ end
else
set_index_vars
render :index
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index a996bad3fac..ff466fd5fbb 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -25,7 +25,7 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
end
def show
- @created = get_created_session
+ @created = get_created_session if Feature.disabled?('hash_oauth_secrets')
end
def create
@@ -34,9 +34,14 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
if @application.persisted?
flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create])
- set_created_session
+ if Feature.enabled?('hash_oauth_secrets')
+ @created = true
+ render :show
+ else
+ set_created_session
- redirect_to oauth_application_url(@application)
+ redirect_to oauth_application_url(@application)
+ end
else
set_index_vars
render :index
diff --git a/app/helpers/jira_connect_helper.rb b/app/helpers/jira_connect_helper.rb
index 4ddfb0224d1..0971fdae8dd 100644
--- a/app/helpers/jira_connect_helper.rb
+++ b/app/helpers/jira_connect_helper.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
module JiraConnectHelper
- def jira_connect_app_data(subscriptions)
+ def jira_connect_app_data(subscriptions, installation)
skip_groups = subscriptions.map(&:namespace_id)
{
@@ -11,14 +11,16 @@ module JiraConnectHelper
subscriptions_path: jira_connect_subscriptions_path(format: :json),
users_path: current_user ? nil : jira_connect_users_path, # users_path is used to determine if user is signed in
gitlab_user_path: current_user ? user_path(current_user) : nil,
- oauth_metadata: Feature.enabled?(:jira_connect_oauth, current_user) ? jira_connect_oauth_data.to_json : nil
+ oauth_metadata: Feature.enabled?(:jira_connect_oauth, current_user) ? jira_connect_oauth_data(installation).to_json : nil
}
end
private
- def jira_connect_oauth_data
- oauth_authorize_url = oauth_authorization_url(
+ def jira_connect_oauth_data(installation)
+ oauth_instance_url = installation.oauth_authorization_url
+
+ oauth_authorize_path = oauth_authorization_path(
client_id: Gitlab::CurrentSettings.jira_connect_application_key,
response_type: 'code',
scope: 'api',
@@ -27,8 +29,8 @@ module JiraConnectHelper
)
{
- oauth_authorize_url: oauth_authorize_url,
- oauth_token_url: oauth_token_url,
+ oauth_authorize_url: Gitlab::Utils.append_path(oauth_instance_url, oauth_authorize_path),
+ oauth_token_path: oauth_token_path,
state: oauth_state,
oauth_token_payload: {
grant_type: :authorization_code,
diff --git a/app/models/jira_connect_installation.rb b/app/models/jira_connect_installation.rb
index 8befe9a9230..0a2d3ba0749 100644
--- a/app/models/jira_connect_installation.rb
+++ b/app/models/jira_connect_installation.rb
@@ -24,4 +24,10 @@ class JiraConnectInstallation < ApplicationRecord
def client
Atlassian::JiraConnect::Client.new(base_url, shared_secret)
end
+
+ def oauth_authorization_url
+ return Gitlab.config.gitlab.url if instance_url.blank? || Feature.disabled?(:jira_connect_oauth_self_managed)
+
+ instance_url
+ end
end
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 61a95e49228..d75e74f3b19 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -28,9 +28,6 @@ module Issues
return if issue.relative_position.nil?
return if NO_REBALANCING_NEEDED.cover?(issue.relative_position)
- gates = [issue.project, issue.project.group].compact
- return unless gates.any? { |gate| Feature.enabled?(:rebalance_issues, gate) }
-
Issues::RebalancingWorker.perform_async(nil, *issue.project.self_or_root_group_ids)
end
diff --git a/app/services/issues/relative_position_rebalancing_service.rb b/app/services/issues/relative_position_rebalancing_service.rb
index 23bb409f3cd..b5c10430e83 100644
--- a/app/services/issues/relative_position_rebalancing_service.rb
+++ b/app/services/issues/relative_position_rebalancing_service.rb
@@ -16,8 +16,6 @@ module Issues
end
def execute
- return unless Feature.enabled?(:rebalance_issues, root_namespace)
-
# Given can_start_rebalance? and track_new_running_rebalance are not atomic
# it can happen that we end up with more than Rebalancing::State::MAX_NUMBER_OF_CONCURRENT_REBALANCES running.
# Considering the number of allowed Rebalancing::State::MAX_NUMBER_OF_CONCURRENT_REBALANCES is small we should be ok,
diff --git a/app/views/jira_connect/subscriptions/index.html.haml b/app/views/jira_connect/subscriptions/index.html.haml
index d4ced15b869..f66aa0840aa 100644
--- a/app/views/jira_connect/subscriptions/index.html.haml
+++ b/app/views/jira_connect/subscriptions/index.html.haml
@@ -1,4 +1,4 @@
-.js-jira-connect-app{ data: jira_connect_app_data(@subscriptions) }
+.js-jira-connect-app{ data: jira_connect_app_data(@subscriptions, @current_jira_installation) }
= webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
= webpack_bundle_tag 'jira_connect_app'
diff --git a/app/views/shared/doorkeeper/applications/_show.html.haml b/app/views/shared/doorkeeper/applications/_show.html.haml
index f533b5b5a4d..562b1aee4ca 100644
--- a/app/views/shared/doorkeeper/applications/_show.html.haml
+++ b/app/views/shared/doorkeeper/applications/_show.html.haml
@@ -15,7 +15,14 @@
%td
= _('Secret')
%td
- = clipboard_button(clipboard_text: @application.secret, button_text: _('Copy'), title: _("Copy secret"), class: "btn btn-default btn-md gl-button")
+ - if Feature.enabled?('hash_oauth_secrets')
+ - if @application.plaintext_secret
+ = clipboard_button(clipboard_text: @application.plaintext_secret, button_text: _('Copy'), title: _("Copy secret"), class: "btn btn-default btn-md gl-button")
+ %span= _('This is the only time the secret is accessible. Copy the secret and store it securely.')
+ - else
+ = _('The secret is only available when you first create the application.')
+ - else
+ = clipboard_button(clipboard_text: @application.secret, button_text: _('Copy'), title: _("Copy secret"), class: "btn btn-default btn-md gl-button")
%tr
%td
= _('Callback URL')
diff --git a/config/feature_flags/development/hash_oauth_secrets.yml b/config/feature_flags/development/hash_oauth_secrets.yml
new file mode 100644
index 00000000000..7730d319bab
--- /dev/null
+++ b/config/feature_flags/development/hash_oauth_secrets.yml
@@ -0,0 +1,8 @@
+---
+name: hash_oauth_secrets
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/96252
+rollout_issue_url:
+milestone: '15.4'
+type: development
+group: group::authentication and authorization
+default_enabled: false
diff --git a/config/feature_flags/development/rebalance_issues.yml b/config/feature_flags/development/rebalance_issues.yml
deleted file mode 100644
index 5651b02b073..00000000000
--- a/config/feature_flags/development/rebalance_issues.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: rebalance_issues
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40124
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/239344
-milestone: '13.4'
-type: development
-group: group::project management
-default_enabled: true
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index 867f3fd47cc..918b2767c4d 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -90,7 +90,9 @@ Doorkeeper.configure do
# Check out the wiki for more information on customization
access_token_methods :from_access_token_param, :from_bearer_authorization, :from_bearer_param
- hash_token_secrets using: '::Gitlab::DoorkeeperSecretStoring::Pbkdf2Sha512', fallback: :plain
+ hash_token_secrets using: '::Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512', fallback: :plain
+
+ hash_application_secrets using: '::Gitlab::DoorkeeperSecretStoring::Secret::Pbkdf2Sha512', fallback: :plain
# Specify what grant flows are enabled in array of Strings. The valid
# strings and the flows they enable are:
diff --git a/config/initializers/zz_metrics.rb b/config/initializers/zz_metrics.rb
index 3a729e34135..940d8eed61f 100644
--- a/config/initializers/zz_metrics.rb
+++ b/config/initializers/zz_metrics.rb
@@ -40,6 +40,8 @@ if Gitlab::Metrics.enabled? && !Rails.env.test? && !(Rails.env.development? && d
if Gitlab::Runtime.puma?
Gitlab::Metrics::RequestsRackMiddleware.initialize_metrics
Gitlab::Metrics::GlobalSearchSlis.initialize_slis!
+ elsif Gitlab.ee? && Gitlab::Runtime.sidekiq?
+ Gitlab::Metrics::GlobalSearchIndexingSlis.initialize_slis!
end
GC::Profiler.enable
diff --git a/config/metrics/counts_all/20210216180242_web_ide_commits.yml b/config/metrics/counts_all/20210216180242_web_ide_commits.yml
index 3d1d416a7c2..f86b5bd5f84 100644
--- a/config/metrics/counts_all/20210216180242_web_ide_commits.yml
+++ b/config/metrics/counts_all/20210216180242_web_ide_commits.yml
@@ -10,6 +10,11 @@ value_type: number
status: active
time_frame: all
data_source: redis
+instrumentation_class: RedisMetric
+options:
+ prefix: web_ide
+ event: commits_count
+ include_usage_prefix: false
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216180244_web_ide_views.yml b/config/metrics/counts_all/20210216180244_web_ide_views.yml
index 7bc32b3dbc9..63149b86e0f 100644
--- a/config/metrics/counts_all/20210216180244_web_ide_views.yml
+++ b/config/metrics/counts_all/20210216180244_web_ide_views.yml
@@ -10,6 +10,11 @@ value_type: number
status: active
time_frame: all
data_source: redis
+instrumentation_class: RedisMetric
+options:
+ prefix: web_ide
+ event: views_count
+ include_usage_prefix: false
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216180246_web_ide_merge_requests.yml b/config/metrics/counts_all/20210216180246_web_ide_merge_requests.yml
index eb02d98dc85..f620447e615 100644
--- a/config/metrics/counts_all/20210216180246_web_ide_merge_requests.yml
+++ b/config/metrics/counts_all/20210216180246_web_ide_merge_requests.yml
@@ -10,6 +10,11 @@ value_type: number
status: active
time_frame: all
data_source: redis
+instrumentation_class: RedisMetric
+options:
+ prefix: web_ide
+ event: merge_requests_count
+ include_usage_prefix: false
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216180248_web_ide_previews.yml b/config/metrics/counts_all/20210216180248_web_ide_previews.yml
index 4d581fc7f7e..c785e95e105 100644
--- a/config/metrics/counts_all/20210216180248_web_ide_previews.yml
+++ b/config/metrics/counts_all/20210216180248_web_ide_previews.yml
@@ -10,6 +10,11 @@ value_type: number
status: active
time_frame: all
data_source: redis
+instrumentation_class: RedisMetric
+options:
+ prefix: web_ide
+ event: previews_count
+ include_usage_prefix: false
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216180250_web_ide_terminals.yml b/config/metrics/counts_all/20210216180250_web_ide_terminals.yml
index e8c1f425639..cd64a877341 100644
--- a/config/metrics/counts_all/20210216180250_web_ide_terminals.yml
+++ b/config/metrics/counts_all/20210216180250_web_ide_terminals.yml
@@ -10,6 +10,11 @@ value_type: number
status: active
time_frame: all
data_source: redis
+instrumentation_class: RedisMetric
+options:
+ prefix: web_ide
+ event: terminals_count
+ include_usage_prefix: false
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20210216180252_web_ide_pipelines.yml b/config/metrics/counts_all/20210216180252_web_ide_pipelines.yml
index ae891775bf9..bfd0b69401e 100644
--- a/config/metrics/counts_all/20210216180252_web_ide_pipelines.yml
+++ b/config/metrics/counts_all/20210216180252_web_ide_pipelines.yml
@@ -10,6 +10,11 @@ value_type: number
status: active
time_frame: all
data_source: redis
+instrumentation_class: RedisMetric
+options:
+ prefix: web_ide
+ event: pipelines_count
+ include_usage_prefix: false
distribution:
- ce
- ee
diff --git a/config/metrics/counts_all/20220122022215_web_ide_previews_success.yml b/config/metrics/counts_all/20220122022215_web_ide_previews_success.yml
index 203201e9174..e2d617dba16 100644
--- a/config/metrics/counts_all/20220122022215_web_ide_previews_success.yml
+++ b/config/metrics/counts_all/20220122022215_web_ide_previews_success.yml
@@ -10,6 +10,11 @@ value_type: number
status: active
time_frame: all
data_source: redis
+instrumentation_class: RedisMetric
+options:
+ prefix: web_ide
+ event: previews_success_count
+ include_usage_prefix: false
distribution:
- ce
- ee
diff --git a/db/migrate/20220831182105_add_constraints_view.rb b/db/migrate/20220831182105_add_constraints_view.rb
new file mode 100644
index 00000000000..03c183b6e9f
--- /dev/null
+++ b/db/migrate/20220831182105_add_constraints_view.rb
@@ -0,0 +1,32 @@
+# frozen_string_literal: true
+
+class AddConstraintsView < Gitlab::Database::Migration[2.0]
+ def up
+ execute(<<~SQL)
+ CREATE OR REPLACE VIEW postgres_constraints
+ AS
+ SELECT
+ pg_constraint.oid AS oid,
+ pg_constraint.conname AS name,
+ pg_constraint.contype AS constraint_type,
+ pg_constraint.convalidated AS constraint_valid,
+ (SELECT array_agg(attname ORDER BY ordering)
+ FROM unnest(pg_constraint.conkey) WITH ORDINALITY attnums(attnum, ordering)
+ INNER JOIN pg_attribute ON pg_attribute.attnum = attnums.attnum AND pg_attribute.attrelid = pg_class.oid
+ ) AS column_names,
+ pg_namespace.nspname::text || '.'::text || pg_class.relname::text AS table_identifier,
+ -- pg_constraint reports a 0 oid rather than null if the constraint is not a partition child constraint.
+ nullif(pg_constraint.conparentid, 0) AS parent_constraint_oid,
+ pg_get_constraintdef(pg_constraint.oid) AS definition
+ FROM pg_constraint
+ INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid
+ INNER JOIN pg_namespace ON pg_class.relnamespace = pg_namespace.oid;
+ SQL
+ end
+
+ def down
+ execute(<<~SQL)
+ DROP VIEW postgres_constraints;
+ SQL
+ end
+end
diff --git a/db/schema_migrations/20220831182105 b/db/schema_migrations/20220831182105
new file mode 100644
index 00000000000..6f4b0f46ff1
--- /dev/null
+++ b/db/schema_migrations/20220831182105
@@ -0,0 +1 @@
+80828666cac381dde65dc208764b6e1c7fe703b63c708410f72afdd33886fc60 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index f536bd170eb..d03a291ea6b 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -19307,6 +19307,21 @@ CREATE VIEW postgres_autovacuum_activity AS
COMMENT ON VIEW postgres_autovacuum_activity IS 'Contains information about PostgreSQL backends currently performing autovacuum operations on the tables indicated here.';
+CREATE VIEW postgres_constraints AS
+ SELECT pg_constraint.oid,
+ pg_constraint.conname AS name,
+ pg_constraint.contype AS constraint_type,
+ pg_constraint.convalidated AS constraint_valid,
+ ( SELECT array_agg(pg_attribute.attname ORDER BY attnums.ordering) AS array_agg
+ FROM (unnest(pg_constraint.conkey) WITH ORDINALITY attnums(attnum, ordering)
+ JOIN pg_attribute ON (((pg_attribute.attnum = attnums.attnum) AND (pg_attribute.attrelid = pg_class.oid))))) AS column_names,
+ (((pg_namespace.nspname)::text || '.'::text) || (pg_class.relname)::text) AS table_identifier,
+ NULLIF(pg_constraint.conparentid, (0)::oid) AS parent_constraint_oid,
+ pg_get_constraintdef(pg_constraint.oid) AS definition
+ FROM ((pg_constraint
+ JOIN pg_class ON ((pg_constraint.conrelid = pg_class.oid)))
+ JOIN pg_namespace ON ((pg_class.relnamespace = pg_namespace.oid)));
+
CREATE VIEW postgres_foreign_keys AS
SELECT pg_constraint.oid,
pg_constraint.conname AS name,
diff --git a/doc/api/discussions.md b/doc/api/discussions.md
index a5610adad79..60ce10590e1 100644
--- a/doc/api/discussions.md
+++ b/doc/api/discussions.md
@@ -1109,7 +1109,7 @@ GET /projects/:id/repository/commits/:commit_id/discussions
| Attribute | Type | Required | Description |
| ------------------- | ---------------- | ---------- | ------------ |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
-| `commit_id` | integer | yes | The ID of a commit |
+| `commit_id` | string | yes | The SHA of a commit |
```json
[
@@ -1237,7 +1237,7 @@ Diff comments contain also position:
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>"\
- "https://gitlab.example.com/api/v4/projects/5/repository/commits/11/discussions"
+ "https://gitlab.example.com/api/v4/projects/5/repository/commits/<commit_id>/discussions"
```
### Get single commit discussion item
@@ -1253,12 +1253,12 @@ Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
-| `commit_id` | integer | yes | The ID of a commit |
-| `discussion_id` | integer | yes | The ID of a discussion item |
+| `commit_id` | string | yes | The SHA of a commit |
+| `discussion_id` | string | yes | The ID of a discussion item |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>"\
- "https://gitlab.example.com/api/v4/projects/5/repository/commits/11/discussions/<discussion_id>"
+ "https://gitlab.example.com/api/v4/projects/5/repository/commits/<commit_id>/discussions/<discussion_id>"
```
### Create new commit thread
@@ -1294,7 +1294,7 @@ Parameters:
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>"\
- "https://gitlab.example.com/api/v4/projects/5/repository/commits/11/discussions?body=comment"
+ "https://gitlab.example.com/api/v4/projects/5/repository/commits/<commit_id>/discussions?body=comment"
```
The rules for creating the API request are the same as when
@@ -1314,15 +1314,15 @@ Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
-| `commit_id` | integer | yes | The ID of a commit |
-| `discussion_id` | integer | yes | The ID of a thread |
+| `commit_id` | string | yes | The SHA of a commit |
+| `discussion_id` | string | yes | The ID of a thread |
| `note_id` | integer | yes | The ID of a thread note |
| `body` | string | yes | The content of the note/reply |
| `created_at` | string | no | Date time string, ISO 8601 formatted, such `2016-03-11T03:45:40Z` (requires administrator or project/group owner rights) |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>"\
- "https://gitlab.example.com/api/v4/projects/5/repository/commits/11/discussions/<discussion_id>/notes?body=comment
+ "https://gitlab.example.com/api/v4/projects/5/repository/commits/<commit_id>/discussions/<discussion_id>/notes?body=comment
```
### Modify an existing commit thread note
@@ -1338,21 +1338,21 @@ Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
-| `commit_id` | integer | yes | The ID of a commit |
-| `discussion_id` | integer | yes | The ID of a thread |
+| `commit_id` | string | yes | The SHA of a commit |
+| `discussion_id` | string | yes | The ID of a thread |
| `note_id` | integer | yes | The ID of a thread note |
| `body` | string | no | The content of a note |
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>"\
- "https://gitlab.example.com/api/v4/projects/5/repository/commits/11/discussions/<discussion_id>/notes/1108?body=comment"
+ "https://gitlab.example.com/api/v4/projects/5/repository/commits/<commit_id>/discussions/<discussion_id>/notes/1108?body=comment"
```
Resolving a note:
```shell
curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>"\
- "https://gitlab.example.com/api/v4/projects/5/repository/commits/11/discussions/<discussion_id>/notes/1108?resolved=true"
+ "https://gitlab.example.com/api/v4/projects/5/repository/commits/<commit_id>/discussions/<discussion_id>/notes/1108?resolved=true"
```
### Delete a commit thread note
@@ -1368,11 +1368,11 @@ Parameters:
| Attribute | Type | Required | Description |
| ------------------- | -------------- | -------- | ----------- |
| `id` | integer/string | yes | The ID or [URL-encoded path of the project](index.md#namespaced-path-encoding) |
-| `commit_id` | integer | yes | The ID of a commit |
-| `discussion_id` | integer | yes | The ID of a thread |
+| `commit_id` | string | yes | The SHA of a commit |
+| `discussion_id` | string | yes | The ID of a thread |
| `note_id` | integer | yes | The ID of a thread note |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>"\
- "https://gitlab.example.com/api/v4/projects/5/repository/commits/11/discussions/636"
+ "https://gitlab.example.com/api/v4/projects/5/repository/commits/<commit_id>/discussions/<discussion_id>/notes/636"
```
diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md
index f4e74973fd7..73c1874f09e 100644
--- a/doc/development/documentation/index.md
+++ b/doc/development/documentation/index.md
@@ -240,9 +240,9 @@ Every GitLab instance includes documentation at `/help` (`https://gitlab.example
that matches the version of the instance. For example, <https://gitlab.com/help>.
The documentation available online at <https://docs.gitlab.com> is deployed every
-four hours from the default branch of [GitLab, Omnibus, Runner, and Charts](#source-files-and-rendered-web-locations).
+hour from the default branch of [GitLab, Omnibus, Runner, and Charts](#source-files-and-rendered-web-locations).
After a merge request that updates documentation is merged, it is available online
-in 4 hours or less.
+in an hour or less.
However, it's only available at `/help` on self-managed instances in the next released
version. The date an update is merged can impact which self-managed release the update
diff --git a/doc/development/import_project.md b/doc/development/import_project.md
index 7c55d2e2668..1f3bf860257 100644
--- a/doc/development/import_project.md
+++ b/doc/development/import_project.md
@@ -149,26 +149,10 @@ You might see an error like `N is out of range for ActiveModel::Type::Integer wi
where `N` is the integer exceeding the 4-byte integer limit. If that's the case, you
are likely hitting the issue with rebalancing of `relative_position` field of the issues.
-The feature flag to enable the rebalance automatically was enabled on GitLab.com.
-We intend to enable it by default on self-managed instances when the issue
-[Rebalance issues FF rollout](https://gitlab.com/gitlab-org/gitlab/-/issues/343368)
-is implemented.
-
-If the feature is not enabled by default on your GitLab version, run the following
-commands in the [Rails console](../administration/operations/rails_console.md) as
-a workaround. Replace the ID with the ID of your project you were trying to import:
-
```ruby
-# Check if the feature is enabled on your instance. If it is, rebalance should work automatically on your instance
-Feature.enabled?(:rebalance_issues,Project.find(ID).root_namespace)
-
# Check the current maximum value of relative_position
Issue.where(project_id: Project.find(ID).root_namespace.all_projects).maximum(:relative_position)
-# Enable `rebalance_issues` feauture and check that it was successfully enabled
-Feature.enable(:rebalance_issues,Project.find(ID).root_namespace)
-Feature.enabled?(:rebalance_issues,Project.find(ID).root_namespace)
-
# Run the rebalancing process and check if the maximum value of relative_position has changed
Issues::RelativePositionRebalancingService.new(Project.find(ID).root_namespace.all_projects).execute
Issue.where(project_id: Project.find(ID).root_namespace.all_projects).maximum(:relative_position)
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index 55a14cb4859..21184f7b678 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -127,3 +127,13 @@ application can perform. Available scopes are depicted in the following table.
| `email` | Grants read-only access to the user's primary email address using [OpenID Connect](openid_connect_provider.md). |
At any time you can revoke any access by clicking **Revoke**.
+
+## Hashed OAuth application secrets
+
+> Introduced in GitLab 15.4 [with a flag](../administration/feature_flags.md) named `hash_oauth_secrets`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default, this feature is not available. To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `hash_oauth_secrets`.
+On GitLab.com, this feature is not available.
+
+By default, OAuth application secrets are stored as plain text in the database. When enabled, OAuth application secrets are stored in the database in hashed format and are only available to users immediately after creating OAuth applications.
diff --git a/doc/user/compliance/license_compliance/img/denied_licenses_v15_3.png b/doc/user/compliance/license_compliance/img/denied_licenses_v15_3.png
index db29cc1cf13..4ed84047133 100644
--- a/doc/user/compliance/license_compliance/img/denied_licenses_v15_3.png
+++ b/doc/user/compliance/license_compliance/img/denied_licenses_v15_3.png
Binary files differ
diff --git a/doc/user/group/epics/epic_boards.md b/doc/user/group/epics/epic_boards.md
index 25cb88e8a7c..a8bbb0575b3 100644
--- a/doc/user/group/epics/epic_boards.md
+++ b/doc/user/group/epics/epic_boards.md
@@ -155,6 +155,42 @@ into another list. Learn about possible effects in [Dragging epics between lists
To move a list, select its top bar, and drag it horizontally.
You can't move the **Open** and **Closed** lists, but you can hide them when editing an epic board.
+#### Move an epic to the start of the list
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367473) in GitLab 15.4.
+
+When you have many epics, it's inconvenient to manually drag an epic from the bottom of a board list all
+the way to the top. You can move epics to the top of the list with a menu shortcut.
+
+Your epic is moved to the top of the list even if other epics are hidden by a filter.
+
+Prerequisites:
+
+- You must at least have the Reporter role for a group.
+
+To move an epic to the start of the list:
+
+1. In an epic board, hover over the card of the epic you want to move.
+1. Select the vertical ellipsis (**{ellipsis_v}**), then **Move to start of list**.
+
+#### Move an epic to the end of the list
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367473) in GitLab 15.4.
+
+When you have many epics, it's inconvenient to manually drag an epic from the top of a board list all
+the way to the bottom. You can move epics to the bottom of the list with a menu shortcut.
+
+Your epic is moved to the bottom of the list even if other epics are hidden by a filter.
+
+Prerequisites:
+
+- You must at least have the Reporter role for a group.
+
+To move an epic to the end of the list:
+
+1. In an epic board, hover over the card of the epic you want to move.
+1. Select the vertical ellipsis (**{ellipsis_v}**), then **Move to end of list**.
+
#### Dragging epics between lists
When you drag epics between lists, the result is different depending on the source list
diff --git a/doc/user/project/issues/img/related_issue_block_v15_3.png b/doc/user/project/issues/img/related_issue_block_v15_3.png
index 827ddeabf10..942f7a33fe0 100644
--- a/doc/user/project/issues/img/related_issue_block_v15_3.png
+++ b/doc/user/project/issues/img/related_issue_block_v15_3.png
Binary files differ
diff --git a/doc/user/project/issues/img/related_issues_add_v15_3.png b/doc/user/project/issues/img/related_issues_add_v15_3.png
index 7c6edf61427..28739c0b909 100644
--- a/doc/user/project/issues/img/related_issues_add_v15_3.png
+++ b/doc/user/project/issues/img/related_issues_add_v15_3.png
Binary files differ
diff --git a/doc/user/project/issues/managing_issues.md b/doc/user/project/issues/managing_issues.md
index 4647631343b..b4edb238479 100644
--- a/doc/user/project/issues/managing_issues.md
+++ b/doc/user/project/issues/managing_issues.md
@@ -686,6 +686,7 @@ Up to five similar issues, sorted by most recently updated, are displayed below
> - Health status of closed issues [can't be edited](https://gitlab.com/gitlab-org/gitlab/-/issues/220867) in GitLab 13.4 and later.
> - Issue health status visible in issue lists [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/45141) in GitLab 13.6.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/213567) in GitLab 13.7.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/218618) in GitLab 15.4: health status is visible on issue cards in issue boards.
To help you track issue statuses, you can assign a status to each issue.
This status marks issues as progressing as planned or needing attention to keep on schedule.
@@ -704,7 +705,11 @@ To edit health status of an issue:
- Needs attention (amber)
- At risk (red)
-You can then see the issue's status in the issues list and the epic tree.
+You can see the issue’s health status in:
+
+- Issues list
+- Epic tree
+- Issue cards in issue boards
After an issue is closed, its health status can't be edited and the **Edit** button becomes disabled
until the issue is reopened.
diff --git a/lib/gitlab/ci/pipeline/chain/assign_partition.rb b/lib/gitlab/ci/pipeline/chain/assign_partition.rb
index f946041db31..4b8efe13d44 100644
--- a/lib/gitlab/ci/pipeline/chain/assign_partition.rb
+++ b/lib/gitlab/ci/pipeline/chain/assign_partition.rb
@@ -17,9 +17,12 @@ module Gitlab
private
- # TODO handle parent-child pipelines
def find_partition_id
- ::Ci::Pipeline.current_partition_value
+ if @command.creates_child_pipeline?
+ @command.parent_pipeline_partition_id
+ else
+ ::Ci::Pipeline.current_partition_value
+ end
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 2419b039f24..14c320f77bf 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -80,6 +80,10 @@ module Gitlab
bridge&.parent_pipeline
end
+ def parent_pipeline_partition_id
+ parent_pipeline.partition_id if creates_child_pipeline?
+ end
+
def creates_child_pipeline?
bridge&.triggers_child_pipeline?
end
diff --git a/lib/gitlab/database/gitlab_schemas.yml b/lib/gitlab/database/gitlab_schemas.yml
index 7d1addd1a3d..6d290a14896 100644
--- a/lib/gitlab/database/gitlab_schemas.yml
+++ b/lib/gitlab/database/gitlab_schemas.yml
@@ -403,6 +403,7 @@ plans: :gitlab_main
pool_repositories: :gitlab_main
postgres_async_indexes: :gitlab_shared
postgres_autovacuum_activity: :gitlab_shared
+postgres_constraints: :gitlab_shared
postgres_foreign_keys: :gitlab_shared
postgres_index_bloat_estimates: :gitlab_shared
postgres_indexes: :gitlab_shared
diff --git a/lib/gitlab/database/partitioning/convert_table_to_first_list_partition.rb b/lib/gitlab/database/partitioning/convert_table_to_first_list_partition.rb
new file mode 100644
index 00000000000..f45cf02ec9b
--- /dev/null
+++ b/lib/gitlab/database/partitioning/convert_table_to_first_list_partition.rb
@@ -0,0 +1,214 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ module Partitioning
+ class ConvertTableToFirstListPartition
+ UnableToPartition = Class.new(StandardError)
+
+ include Gitlab::Database::MigrationHelpers
+
+ SQL_STATEMENT_SEPARATOR = ";\n\n"
+
+ attr_reader :partitioning_column, :table_name, :parent_table_name, :zero_partition_value
+
+ def initialize(migration_context:, table_name:, parent_table_name:, partitioning_column:, zero_partition_value:)
+ @migration_context = migration_context
+ @connection = migration_context.connection
+ @table_name = table_name
+ @parent_table_name = parent_table_name
+ @partitioning_column = partitioning_column
+ @zero_partition_value = zero_partition_value
+ end
+
+ def prepare_for_partitioning
+ assert_existing_constraints_partitionable
+
+ add_partitioning_check_constraint
+ end
+
+ def revert_preparation_for_partitioning
+ migration_context.remove_check_constraint(table_name, partitioning_constraint.name)
+ end
+
+ def partition
+ assert_existing_constraints_partitionable
+ assert_partitioning_constraint_present
+ create_parent_table
+ attach_foreign_keys_to_parent
+
+ migration_context.with_lock_retries(raise_on_exhaustion: true) do
+ migration_context.execute(sql_to_convert_table)
+ end
+ end
+
+ def revert_partitioning
+ migration_context.with_lock_retries(raise_on_exhaustion: true) do
+ migration_context.execute(<<~SQL)
+ ALTER TABLE #{connection.quote_table_name(parent_table_name)}
+ DETACH PARTITION #{connection.quote_table_name(table_name)};
+ SQL
+
+ alter_sequences_sql = alter_sequence_statements(old_table: parent_table_name, new_table: table_name)
+ .join(SQL_STATEMENT_SEPARATOR)
+
+ migration_context.execute(alter_sequences_sql)
+
+ # This takes locks for all the foreign keys that the parent table had.
+ # However, those same locks were taken while detaching the partition, and we can't avoid that.
+ # If we dropped the foreign key before detaching the partition to avoid this locking,
+ # the drop would cascade to the child partitions and drop their foreign keys as well
+ migration_context.drop_table(parent_table_name)
+ end
+
+ add_partitioning_check_constraint
+ end
+
+ private
+
+ attr_reader :connection, :migration_context
+
+ delegate :quote_table_name, :quote_column_name, to: :connection
+
+ def sql_to_convert_table
+ # The critical statement here is the attach_table_to_parent statement.
+ # The following statements could be run in a later transaction,
+ # but they acquire the same locks so it's much faster to incude them
+ # here.
+ [
+ attach_table_to_parent_statement,
+ alter_sequence_statements(old_table: table_name, new_table: parent_table_name),
+ remove_constraint_statement
+ ].flatten.join(SQL_STATEMENT_SEPARATOR)
+ end
+
+ def table_identifier
+ "#{connection.current_schema}.#{table_name}"
+ end
+
+ def assert_existing_constraints_partitionable
+ violating_constraints = Gitlab::Database::PostgresConstraint
+ .by_table_identifier(table_identifier)
+ .primary_or_unique_constraints
+ .not_including_column(partitioning_column)
+ .to_a
+
+ return if violating_constraints.empty?
+
+ violation_messages = violating_constraints.map { |c| "#{c.name} on (#{c.column_names.join(', ')})" }
+
+ raise UnableToPartition, <<~MSG
+ Constraints on #{table_name} are incompatible with partitioning on #{partitioning_column}
+
+ All primary key and unique constraints must include the partitioning column.
+ Violations:
+ #{violation_messages.join("\n")}
+ MSG
+ end
+
+ def partitioning_constraint
+ constraints_on_column = Gitlab::Database::PostgresConstraint
+ .by_table_identifier(table_identifier)
+ .check_constraints
+ .valid
+ .including_column(partitioning_column)
+
+ constraints_on_column.to_a.find do |constraint|
+ constraint.definition == "CHECK ((#{partitioning_column} = #{zero_partition_value}))"
+ end
+ end
+
+ def assert_partitioning_constraint_present
+ return if partitioning_constraint
+
+ raise UnableToPartition, <<~MSG
+ Table #{table_name} is not ready for partitioning.
+ Before partitioning, a check constraint must enforce that (#{partitioning_column} = #{zero_partition_value})
+ MSG
+ end
+
+ def add_partitioning_check_constraint
+ return if partitioning_constraint.present?
+
+ check_body = "#{partitioning_column} = #{connection.quote(zero_partition_value)}"
+ # Any constraint name would work. The constraint is found based on its definition before partitioning
+ migration_context.add_check_constraint(table_name, check_body, 'partitioning_constraint')
+
+ raise UnableToPartition, 'Error adding partitioning constraint' unless partitioning_constraint.present?
+ end
+
+ def create_parent_table
+ migration_context.execute(<<~SQL)
+ CREATE TABLE IF NOT EXISTS #{quote_table_name(parent_table_name)} (
+ LIKE #{quote_table_name(table_name)} INCLUDING ALL
+ ) PARTITION BY LIST(#{quote_column_name(partitioning_column)})
+ SQL
+ end
+
+ def attach_foreign_keys_to_parent
+ migration_context.foreign_keys(table_name).each do |fk|
+ # At this point no other connection knows about the parent table.
+ # Thus the only contended lock in the following transaction is on fk.to_table.
+ # So a deadlock is impossible.
+
+ # If we're rerunning this migration after a failure to acquire a lock, the foreign key might already exist.
+ # Don't try to recreate it in that case
+ if migration_context.foreign_keys(parent_table_name)
+ .any? { |p_fk| p_fk.options[:name] == fk.options[:name] }
+ next
+ end
+
+ migration_context.with_lock_retries(raise_on_exhaustion: true) do
+ migration_context.add_foreign_key(parent_table_name, fk.to_table, **fk.options)
+ end
+ end
+ end
+
+ def attach_table_to_parent_statement
+ <<~SQL
+ ALTER TABLE #{quote_table_name(parent_table_name)}
+ ATTACH PARTITION #{table_name}
+ FOR VALUES IN (#{zero_partition_value})
+ SQL
+ end
+
+ def alter_sequence_statements(old_table:, new_table:)
+ sequences_owned_by(old_table).map do |seq_info|
+ seq_name, column_name = seq_info.values_at(:name, :column_name)
+ <<~SQL.chomp
+ ALTER SEQUENCE #{quote_table_name(seq_name)} OWNED BY #{quote_table_name(new_table)}.#{quote_column_name(column_name)}
+ SQL
+ end
+ end
+
+ def remove_constraint_statement
+ <<~SQL
+ ALTER TABLE #{quote_table_name(parent_table_name)}
+ DROP CONSTRAINT #{quote_table_name(partitioning_constraint.name)}
+ SQL
+ end
+
+ # TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/373887
+ def sequences_owned_by(table_name)
+ sequence_data = connection.exec_query(<<~SQL, nil, [table_name])
+ SELECT seq_pg_class.relname AS seq_name,
+ dep_pg_class.relname AS table_name,
+ pg_attribute.attname AS col_name
+ FROM pg_class seq_pg_class
+ INNER JOIN pg_depend ON seq_pg_class.oid = pg_depend.objid
+ INNER JOIN pg_class dep_pg_class ON pg_depend.refobjid = dep_pg_class.oid
+ INNER JOIN pg_attribute ON dep_pg_class.oid = pg_attribute.attrelid
+ AND pg_depend.refobjsubid = pg_attribute.attnum
+ WHERE seq_pg_class.relkind = 'S'
+ AND dep_pg_class.relname = $1
+ SQL
+
+ sequence_data.map do |seq_info|
+ name, column_name = seq_info.values_at('seq_name', 'col_name')
+ { name: name, column_name: column_name }
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb b/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
index a541ecf5316..695a5d7ec77 100644
--- a/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
+++ b/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers.rb
@@ -251,6 +251,54 @@ module Gitlab
create_sync_trigger(source_table_name, trigger_name, function_name)
end
+ def prepare_constraint_for_list_partitioning(table_name:, partitioning_column:, parent_table_name:, initial_partitioning_value:)
+ validate_not_in_transaction!(:prepare_constraint_for_list_partitioning)
+
+ Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
+ .new(migration_context: self,
+ table_name: table_name,
+ parent_table_name: parent_table_name,
+ partitioning_column: partitioning_column,
+ zero_partition_value: initial_partitioning_value
+ ).prepare_for_partitioning
+ end
+
+ def revert_preparing_constraint_for_list_partitioning(table_name:, partitioning_column:, parent_table_name:, initial_partitioning_value:)
+ validate_not_in_transaction!(:revert_preparing_constraint_for_list_partitioning)
+
+ Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
+ .new(migration_context: self,
+ table_name: table_name,
+ parent_table_name: parent_table_name,
+ partitioning_column: partitioning_column,
+ zero_partition_value: initial_partitioning_value
+ ).revert_preparation_for_partitioning
+ end
+
+ def convert_table_to_first_list_partition(table_name:, partitioning_column:, parent_table_name:, initial_partitioning_value:)
+ validate_not_in_transaction!(:convert_table_to_first_list_partition)
+
+ Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
+ .new(migration_context: self,
+ table_name: table_name,
+ parent_table_name: parent_table_name,
+ partitioning_column: partitioning_column,
+ zero_partition_value: initial_partitioning_value
+ ).partition
+ end
+
+ def revert_converting_table_to_first_list_partition(table_name:, partitioning_column:, parent_table_name:, initial_partitioning_value:)
+ validate_not_in_transaction!(:revert_converting_table_to_first_list_partition)
+
+ Gitlab::Database::Partitioning::ConvertTableToFirstListPartition
+ .new(migration_context: self,
+ table_name: table_name,
+ parent_table_name: parent_table_name,
+ partitioning_column: partitioning_column,
+ zero_partition_value: initial_partitioning_value
+ ).revert_partitioning
+ end
+
private
def assert_table_is_allowed(table_name)
diff --git a/lib/gitlab/database/postgres_constraint.rb b/lib/gitlab/database/postgres_constraint.rb
new file mode 100644
index 00000000000..fa590914332
--- /dev/null
+++ b/lib/gitlab/database/postgres_constraint.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Database
+ # Backed by the postgres_constraints view
+ class PostgresConstraint < SharedModel
+ IDENTIFIER_REGEX = /^\w+\.\w+$/.freeze
+ self.primary_key = :oid
+
+ scope :check_constraints, -> { where(constraint_type: 'c') }
+ scope :primary_key_constraints, -> { where(constraint_type: 'p') }
+ scope :unique_constraints, -> { where(constraint_type: 'u') }
+ scope :primary_or_unique_constraints, -> { where(constraint_type: %w[u p]) }
+
+ scope :including_column, ->(column) { where("? = ANY(column_names)", column) }
+ scope :not_including_column, ->(column) { where.not("? = ANY(column_names)", column) }
+
+ scope :valid, -> { where(constraint_valid: true) }
+
+ scope :by_table_identifier, ->(identifier) do
+ unless identifier =~ IDENTIFIER_REGEX
+ raise ArgumentError, "Table name is not fully qualified with a schema: #{identifier}"
+ end
+
+ where(table_identifier: identifier)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512.rb b/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512.rb
deleted file mode 100644
index 4bfb5f9e64c..00000000000
--- a/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module DoorkeeperSecretStoring
- class Pbkdf2Sha512 < ::Doorkeeper::SecretStoring::Base
- STRETCHES = 20_000
- # An empty salt is used because we need to look tokens up solely by
- # their hashed value. Additionally, tokens are always cryptographically
- # pseudo-random and unique, therefore salting provides no
- # additional security.
- SALT = ''
-
- def self.transform_secret(plain_secret)
- return plain_secret unless Feature.enabled?(:hash_oauth_tokens)
-
- Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512.digest(plain_secret, STRETCHES, SALT)
- end
-
- ##
- # Determines whether this strategy supports restoring
- # secrets from the database. This allows detecting users
- # trying to use a non-restorable strategy with +reuse_access_tokens+.
- def self.allows_restoring_secrets?
- false
- end
- end
- end
-end
diff --git a/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512.rb b/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512.rb
new file mode 100644
index 00000000000..e0884557496
--- /dev/null
+++ b/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+module Gitlab
+ module DoorkeeperSecretStoring
+ module Secret
+ class Pbkdf2Sha512 < ::Doorkeeper::SecretStoring::Base
+ STRETCHES = 20_000
+ # An empty salt is used because we need to look tokens up solely by
+ # their hashed value. Additionally, tokens are always cryptographically
+ # pseudo-random and unique, therefore salting provides no
+ # additional security.
+ SALT = ''
+
+ def self.transform_secret(plain_secret, stored_as_hash = false)
+ return plain_secret if Feature.disabled?(:hash_oauth_secrets) && !stored_as_hash
+
+ Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512.digest(plain_secret, STRETCHES, SALT)
+ end
+
+ ##
+ # Determines whether this strategy supports restoring
+ # secrets from the database. This allows detecting users
+ # trying to use a non-restorable strategy with +reuse_access_tokens+.
+ def self.allows_restoring_secrets?
+ false
+ end
+
+ ##
+ # Securely compare the given +input+ value with a +stored+ value
+ # processed by +transform_secret+.
+ def self.secret_matches?(input, stored)
+ stored_as_hash = stored.starts_with?('$pbkdf2-')
+ transformed_input = transform_secret(input, stored_as_hash)
+ ActiveSupport::SecurityUtils.secure_compare transformed_input, stored
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512.rb b/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512.rb
new file mode 100644
index 00000000000..f9e6d4076f3
--- /dev/null
+++ b/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module DoorkeeperSecretStoring
+ module Token
+ class Pbkdf2Sha512 < ::Doorkeeper::SecretStoring::Base
+ STRETCHES = 20_000
+ # An empty salt is used because we need to look tokens up solely by
+ # their hashed value. Additionally, tokens are always cryptographically
+ # pseudo-random and unique, therefore salting provides no
+ # additional security.
+ SALT = ''
+
+ def self.transform_secret(plain_secret)
+ return plain_secret unless Feature.enabled?(:hash_oauth_tokens)
+
+ Devise::Pbkdf2Encryptable::Encryptors::Pbkdf2Sha512.digest(plain_secret, STRETCHES, SALT)
+ end
+
+ ##
+ # Determines whether this strategy supports restoring
+ # secrets from the database. This allows detecting users
+ # trying to use a non-restorable strategy with +reuse_access_tokens+.
+ def self.allows_restoring_secrets?
+ false
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer/protected_branches_importer.rb b/lib/gitlab/github_import/importer/protected_branches_importer.rb
index be3ac3d17c1..b5be823d5ab 100644
--- a/lib/gitlab/github_import/importer/protected_branches_importer.rb
+++ b/lib/gitlab/github_import/importer/protected_branches_importer.rb
@@ -11,7 +11,7 @@ module Gitlab
def each_object_to_import
repo = project.import_source
- protected_branches = client.branches(repo).select { |branch| branch.protection.enabled }
+ protected_branches = client.branches(repo).select { |branch| branch.protection&.enabled }
protected_branches.each do |protected_branch|
object = client.branch_protection(repo, protected_branch.name)
next if object.nil? || already_imported?(object)
diff --git a/lib/gitlab/import_export/base/relation_factory.rb b/lib/gitlab/import_export/base/relation_factory.rb
index 1cbfcbdb595..bbec473d29d 100644
--- a/lib/gitlab/import_export/base/relation_factory.rb
+++ b/lib/gitlab/import_export/base/relation_factory.rb
@@ -31,6 +31,8 @@ module Gitlab
TOKEN_RESET_MODELS = %i[Project Namespace Group Ci::Trigger Ci::Build Ci::Runner ProjectHook ErrorTracking::ProjectErrorTrackingSetting].freeze
+ attr_reader :relation_name, :importable
+
def self.create(*args, **kwargs)
new(*args, **kwargs).create
end
diff --git a/lib/gitlab/import_export/base/relation_object_saver.rb b/lib/gitlab/import_export/base/relation_object_saver.rb
index ea989487ebd..3c473449ec0 100644
--- a/lib/gitlab/import_export/base/relation_object_saver.rb
+++ b/lib/gitlab/import_export/base/relation_object_saver.rb
@@ -58,8 +58,19 @@ module Gitlab
records.each_slice(BATCH_SIZE) do |batch|
valid_records, invalid_records = batch.partition { |record| record.valid? }
- invalid_subrelations << invalid_records
relation_object.public_send(relation_name) << valid_records
+
+ # Attempt to save some of the invalid subrelations, as they might be valid after all.
+ # For example, a merge request `Approval` validates presence of merge_request_id.
+ # It is not present at a time of calling `#valid?` above, since it's indeed missing.
+ # However, when saving such subrelation against already persisted merge request
+ # such validation won't fail (e.g. `merge_request.approvals << Approval.new(user_id: 1)`),
+ # as we're operating on a merge request that has `id` present.
+ invalid_records.each do |invalid_record|
+ relation_object.public_send(relation_name) << invalid_record
+
+ invalid_subrelations << invalid_record unless invalid_record.persisted?
+ end
end
end
end
diff --git a/lib/gitlab/import_export/project/import_export.yml b/lib/gitlab/import_export/project/import_export.yml
index 270a5c5c258..33e4823f192 100644
--- a/lib/gitlab/import_export/project/import_export.yml
+++ b/lib/gitlab/import_export/project/import_export.yml
@@ -1020,6 +1020,35 @@ excluded_attributes:
- :merge_request_id
- :epic_id
- :source_merge_request_id
+ iteration:
+ - :id
+ - :title
+ - :title_html
+ - :project_id
+ - :description_html
+ - :cached_markdown_version
+ - :iterations_cadence_id
+ - :sequence
+ resource_iteration_events:
+ - :id
+ - :issue_id
+ - :merge_request_id
+ - :iteration_id
+ iterations_cadence:
+ - :id
+ - :last_run_date
+ - :duration_in_weeks
+ - :iterations_in_advance
+ - :automatic
+ - :group_id
+ - :created_at
+ - :updated_at
+ - :start_date
+ - :active
+ - :roll_over
+ - :description
+ - :sequence
+
methods:
notes:
- :type
@@ -1093,6 +1122,11 @@ ee:
- epic_issue:
- :epic
- :issuable_sla
+ - iteration:
+ - :iterations_cadence
+ - resource_iteration_events:
+ - iteration:
+ - :iterations_cadence
- protected_branches:
- :unprotect_access_levels
- protected_environments:
@@ -1151,12 +1185,22 @@ ee:
- :auto_fix_dependency_scanning
- :auto_fix_sast
project:
- - :requirements_enabled
- - :requirements_access_level
+ - :requirements_enabled
+ - :requirements_access_level
resource_iteration_events:
- :user_id
- :action
- :created_at
+ iteration:
+ - :iid
+ - :created_at
+ - :updated_at
+ - :start_date
+ - :due_date
+ - :group_id
+ - :description
+ iterations_cadence:
+ - :title
preloads:
issues:
diff --git a/lib/gitlab/import_export/project/object_builder.rb b/lib/gitlab/import_export/project/object_builder.rb
index bf60d115a25..50a67a746f8 100644
--- a/lib/gitlab/import_export/project/object_builder.rb
+++ b/lib/gitlab/import_export/project/object_builder.rb
@@ -21,7 +21,7 @@ module Gitlab
end
def find
- return if epic? && group.nil?
+ return if group_relation_without_group?
return find_diff_commit_user if diff_commit_user?
return find_diff_commit if diff_commit?
@@ -60,7 +60,7 @@ module Gitlab
def prepare_attributes
attributes.dup.tap do |atts|
- atts.delete('group') unless epic?
+ atts.delete('group') unless epic? || iteration?
if label?
atts['type'] = 'ProjectLabel' # Always create project labels
@@ -141,6 +141,10 @@ module Gitlab
klass == MergeRequestDiffCommit
end
+ def iteration?
+ klass == Iteration
+ end
+
# If an existing group milestone used the IID
# claim the IID back and set the group milestone to use one available
# This is necessary to fix situations like the following:
@@ -157,7 +161,13 @@ module Gitlab
milestone.ensure_project_iid!
milestone.save!
end
+
+ def group_relation_without_group?
+ (epic? || iteration?) && group.nil?
+ end
end
end
end
end
+
+Gitlab::ImportExport::Project::ObjectBuilder.prepend_mod
diff --git a/lib/gitlab/import_export/project/relation_tree_restorer.rb b/lib/gitlab/import_export/project/relation_tree_restorer.rb
index 6e9548f393a..47196db6f8a 100644
--- a/lib/gitlab/import_export/project/relation_tree_restorer.rb
+++ b/lib/gitlab/import_export/project/relation_tree_restorer.rb
@@ -5,7 +5,7 @@ module Gitlab
module Project
class RelationTreeRestorer < ImportExport::Group::RelationTreeRestorer
# Relations which cannot be saved at project level (and have a group assigned)
- GROUP_MODELS = [GroupLabel, Milestone, Epic].freeze
+ GROUP_MODELS = [GroupLabel, Milestone, Epic, Iteration].freeze
private
diff --git a/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb b/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb
index c9449f10cc2..26d963e2407 100644
--- a/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb
+++ b/lib/gitlab/usage/metrics/instrumentations/redis_metric.rb
@@ -16,6 +16,8 @@ module Gitlab
class RedisMetric < BaseMetric
include Gitlab::UsageDataCounters::RedisCounter
+ USAGE_PREFIX = "USAGE_"
+
def initialize(time_frame:, options: {})
super
@@ -31,6 +33,10 @@ module Gitlab
options[:prefix]
end
+ def include_usage_prefix?
+ options.fetch(:include_usage_prefix, true)
+ end
+
def value
redis_usage_data do
total_count(redis_key)
@@ -44,7 +50,9 @@ module Gitlab
private
def redis_key
- "USAGE_#{prefix}_#{metric_event}".upcase
+ key = "#{prefix}_#{metric_event}".upcase
+ key.prepend(USAGE_PREFIX) if include_usage_prefix?
+ key
end
end
end
diff --git a/lib/gitlab/usage_data_counters.rb b/lib/gitlab/usage_data_counters.rb
index 27376b9d231..fa6bde8c19c 100644
--- a/lib/gitlab/usage_data_counters.rb
+++ b/lib/gitlab/usage_data_counters.rb
@@ -4,7 +4,6 @@ module Gitlab
module UsageDataCounters
COUNTERS = [
WikiPageCounter,
- WebIdeCounter,
NoteCounter,
SnippetCounter,
SearchCounter,
@@ -20,7 +19,8 @@ module Gitlab
].freeze
COUNTERS_MIGRATED_TO_INSTRUMENTATION_CLASSES = [
- PackageEventCounter
+ PackageEventCounter,
+ WebIdeCounter
].freeze
UsageDataCounterError = Class.new(StandardError)
diff --git a/lib/gitlab/usage_data_counters/base_counter.rb b/lib/gitlab/usage_data_counters/base_counter.rb
index 4ab310a2519..5d2ab5eaf74 100644
--- a/lib/gitlab/usage_data_counters/base_counter.rb
+++ b/lib/gitlab/usage_data_counters/base_counter.rb
@@ -10,7 +10,9 @@ module Gitlab::UsageDataCounters
def redis_key(event)
require_known_event(event)
- "USAGE_#{prefix}_#{event}".upcase
+ usage_prefix = Gitlab::Usage::Metrics::Instrumentations::RedisMetric::USAGE_PREFIX
+
+ "#{usage_prefix}#{prefix}_#{event}".upcase
end
def count(event)
diff --git a/lib/sidebars/projects/menus/packages_registries_menu.rb b/lib/sidebars/projects/menus/packages_registries_menu.rb
index 30110f1a97f..2ddffe42899 100644
--- a/lib/sidebars/projects/menus/packages_registries_menu.rb
+++ b/lib/sidebars/projects/menus/packages_registries_menu.rb
@@ -66,7 +66,7 @@ module Sidebars
end
def harbor_registry__menu_item
- if Feature.disabled?(:harbor_registry_integration) || context.project.harbor_integration.nil?
+ if Feature.disabled?(:harbor_registry_integration, context.project) || context.project.harbor_integration.nil?
return ::Sidebars::NilMenuItem.new(item_id: :harbor_registry)
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 87ff69fa9eb..9d4acf41ff1 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -39991,6 +39991,9 @@ msgstr ""
msgid "The scan has been created."
msgstr ""
+msgid "The secret is only available when you first create the application."
+msgstr ""
+
msgid "The snippet can be accessed without any authentication."
msgstr ""
@@ -40726,6 +40729,9 @@ msgstr ""
msgid "This is the number of %{billable_users_link_start}billable users%{link_end} on your installation, and this is the minimum number you need to purchase when you renew your license."
msgstr ""
+msgid "This is the only time the secret is accessible. Copy the secret and store it securely."
+msgstr ""
+
msgid "This is your current session"
msgstr ""
diff --git a/qa/qa/support/matchers/eventually_matcher.rb b/qa/qa/support/matchers/eventually_matcher.rb
index 01d07585f57..3f451f89246 100644
--- a/qa/qa/support/matchers/eventually_matcher.rb
+++ b/qa/qa/support/matchers/eventually_matcher.rb
@@ -21,6 +21,7 @@ module QA
eq
be
include
+ match
be_truthy
be_falsey
be_empty
diff --git a/spec/controllers/admin/applications_controller_spec.rb b/spec/controllers/admin/applications_controller_spec.rb
index 6c423097e70..bf7707f177c 100644
--- a/spec/controllers/admin/applications_controller_spec.rb
+++ b/spec/controllers/admin/applications_controller_spec.rb
@@ -39,17 +39,43 @@ RSpec.describe Admin::ApplicationsController do
end
describe 'POST #create' do
- it 'creates the application' do
- create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
+ context 'with hash_oauth_secrets flag off' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: false)
+ end
- expect do
- post :create, params: { doorkeeper_application: create_params }
- end.to change { Doorkeeper::Application.count }.by(1)
+ it 'creates the application' do
+ create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
+
+ expect do
+ post :create, params: { doorkeeper_application: create_params }
+ end.to change { Doorkeeper::Application.count }.by(1)
- application = Doorkeeper::Application.last
+ application = Doorkeeper::Application.last
- expect(response).to redirect_to(admin_application_path(application))
- expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ expect(response).to redirect_to(admin_application_path(application))
+ expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ end
+ end
+
+ context 'with hash_oauth_secrets flag on' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: true)
+ end
+
+ it 'creates the application' do
+ create_params = attributes_for(:application, trusted: true, confidential: false, scopes: ['api'])
+
+ expect do
+ post :create, params: { doorkeeper_application: create_params }
+ end.to change { Doorkeeper::Application.count }.by(1)
+
+ application = Doorkeeper::Application.last
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template :show
+ expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ end
end
it 'renders the application form on errors' do
@@ -62,17 +88,43 @@ RSpec.describe Admin::ApplicationsController do
end
context 'when the params are for a confidential application' do
- it 'creates a confidential application' do
- create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
+ context 'with hash_oauth_secrets flag off' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: false)
+ end
- expect do
- post :create, params: { doorkeeper_application: create_params }
- end.to change { Doorkeeper::Application.count }.by(1)
+ it 'creates a confidential application' do
+ create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
- application = Doorkeeper::Application.last
+ expect do
+ post :create, params: { doorkeeper_application: create_params }
+ end.to change { Doorkeeper::Application.count }.by(1)
- expect(response).to redirect_to(admin_application_path(application))
- expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ application = Doorkeeper::Application.last
+
+ expect(response).to redirect_to(admin_application_path(application))
+ expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ end
+ end
+
+ context 'with hash_oauth_secrets flag on' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: true)
+ end
+
+ it 'creates a confidential application' do
+ create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
+
+ expect do
+ post :create, params: { doorkeeper_application: create_params }
+ end.to change { Doorkeeper::Application.count }.by(1)
+
+ application = Doorkeeper::Application.last
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template :show
+ expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ end
end
end
diff --git a/spec/controllers/groups/settings/applications_controller_spec.rb b/spec/controllers/groups/settings/applications_controller_spec.rb
index 0804a5536e0..b9457770ed6 100644
--- a/spec/controllers/groups/settings/applications_controller_spec.rb
+++ b/spec/controllers/groups/settings/applications_controller_spec.rb
@@ -71,17 +71,43 @@ RSpec.describe Groups::Settings::ApplicationsController do
group.add_owner(user)
end
- it 'creates the application' do
- create_params = attributes_for(:application, trusted: false, confidential: false, scopes: ['api'])
+ context 'with hash_oauth_secrets flag on' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: true)
+ end
- expect do
- post :create, params: { group_id: group, doorkeeper_application: create_params }
- end.to change { Doorkeeper::Application.count }.by(1)
+ it 'creates the application' do
+ create_params = attributes_for(:application, trusted: false, confidential: false, scopes: ['api'])
+
+ expect do
+ post :create, params: { group_id: group, doorkeeper_application: create_params }
+ end.to change { Doorkeeper::Application.count }.by(1)
- application = Doorkeeper::Application.last
+ application = Doorkeeper::Application.last
- expect(response).to redirect_to(group_settings_application_path(group, application))
- expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template :show
+ expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ end
+ end
+
+ context 'with hash_oauth_secrets flag off' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: false)
+ end
+
+ it 'creates the application' do
+ create_params = attributes_for(:application, trusted: false, confidential: false, scopes: ['api'])
+
+ expect do
+ post :create, params: { group_id: group, doorkeeper_application: create_params }
+ end.to change { Doorkeeper::Application.count }.by(1)
+
+ application = Doorkeeper::Application.last
+
+ expect(response).to redirect_to(group_settings_application_path(group, application))
+ expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ end
end
it 'renders the application form on errors' do
@@ -94,17 +120,43 @@ RSpec.describe Groups::Settings::ApplicationsController do
end
context 'when the params are for a confidential application' do
- it 'creates a confidential application' do
- create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
+ context 'with hash_oauth_secrets flag off' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: false)
+ end
- expect do
- post :create, params: { group_id: group, doorkeeper_application: create_params }
- end.to change { Doorkeeper::Application.count }.by(1)
+ it 'creates a confidential application' do
+ create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
- application = Doorkeeper::Application.last
+ expect do
+ post :create, params: { group_id: group, doorkeeper_application: create_params }
+ end.to change { Doorkeeper::Application.count }.by(1)
- expect(response).to redirect_to(group_settings_application_path(group, application))
- expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ application = Doorkeeper::Application.last
+
+ expect(response).to redirect_to(group_settings_application_path(group, application))
+ expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ end
+ end
+
+ context 'with hash_oauth_secrets flag on' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: true)
+ end
+
+ it 'creates a confidential application' do
+ create_params = attributes_for(:application, confidential: true, scopes: ['read_user'])
+
+ expect do
+ post :create, params: { group_id: group, doorkeeper_application: create_params }
+ end.to change { Doorkeeper::Application.count }.by(1)
+
+ application = Doorkeeper::Application.last
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template :show
+ expect(application).to have_attributes(create_params.except(:uid, :owner_type))
+ end
end
end
diff --git a/spec/controllers/oauth/applications_controller_spec.rb b/spec/controllers/oauth/applications_controller_spec.rb
index 5bf3b4c48bf..9b16dc9a463 100644
--- a/spec/controllers/oauth/applications_controller_spec.rb
+++ b/spec/controllers/oauth/applications_controller_spec.rb
@@ -113,11 +113,30 @@ RSpec.describe Oauth::ApplicationsController do
subject { post :create, params: oauth_params }
- it 'creates an application' do
- subject
+ context 'when hash_oauth_tokens flag set' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: true)
+ end
- expect(response).to have_gitlab_http_status(:found)
- expect(response).to redirect_to(oauth_application_path(Doorkeeper::Application.last))
+ it 'creates an application' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to render_template :show
+ end
+ end
+
+ context 'when hash_oauth_tokens flag not set' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: false)
+ end
+
+ it 'creates an application' do
+ subject
+
+ expect(response).to have_gitlab_http_status(:found)
+ expect(response).to redirect_to(oauth_application_path(Doorkeeper::Application.last))
+ end
end
it 'redirects back to profile page if OAuth applications are disabled' do
diff --git a/spec/fixtures/lib/gitlab/import_export/group/project.json b/spec/fixtures/lib/gitlab/import_export/group/project.json
index e8e1e53a86a..671ff92087b 100644
--- a/spec/fixtures/lib/gitlab/import_export/group/project.json
+++ b/spec/fixtures/lib/gitlab/import_export/group/project.json
@@ -205,6 +205,18 @@
"iid": 1,
"group_id": 100
},
+ "iteration": {
+ "created_at": "2022-08-15T12:55:42.607Z",
+ "updated_at": "2022-08-15T12:56:19.269Z",
+ "start_date": "2022-08-15",
+ "due_date": "2022-08-21",
+ "group_id": 260,
+ "iid": 5,
+ "description": "iteration description",
+ "iterations_cadence": {
+ "title": "iterations cadence"
+ }
+ },
"epic_issue": {
"id": 78,
"relative_position": 1073740323,
@@ -239,7 +251,26 @@
"due_date_sourcing_epic_id": null,
"milestone_id": null
}
- }
+ },
+ "resource_iteration_events": [
+ {
+ "user_id": 1,
+ "created_at": "2022-08-17T13:04:02.495Z",
+ "action": "add",
+ "iteration": {
+ "created_at": "2022-08-15T12:55:42.607Z",
+ "updated_at": "2022-08-15T12:56:19.269Z",
+ "start_date": "2022-08-15",
+ "due_date": "2022-08-21",
+ "group_id": 260,
+ "iid": 5,
+ "description": "iteration description",
+ "iterations_cadence": {
+ "title": "iterations cadence"
+ }
+ }
+ }
+ ]
}
],
"snippets": [
diff --git a/spec/fixtures/lib/gitlab/import_export/group/tree/project/issues.ndjson b/spec/fixtures/lib/gitlab/import_export/group/tree/project/issues.ndjson
index 4759e97228f..9596986dca0 100644
--- a/spec/fixtures/lib/gitlab/import_export/group/tree/project/issues.ndjson
+++ b/spec/fixtures/lib/gitlab/import_export/group/tree/project/issues.ndjson
@@ -1,3 +1,3 @@
{"id":1,"title":"Fugiat est minima quae maxime non similique.","assignee_id":null,"project_id":8,"author_id":1,"created_at":"2017-07-07T18:13:01.138Z","updated_at":"2017-08-15T18:37:40.807Z","branch_name":null,"description":"Quam totam fuga numquam in eveniet.","state":"opened","iid":1,"updated_by_id":1,"confidential":false,"due_date":null,"moved_to_id":null,"lock_version":null,"time_estimate":0,"closed_at":null,"last_edited_at":null,"last_edited_by_id":null,"group_milestone_id":null,"milestone":{"id":1,"title":"Project milestone","project_id":8,"description":"Project-level milestone","due_date":null,"created_at":"2016-06-14T15:02:04.415Z","updated_at":"2016-06-14T15:02:04.415Z","state":"active","iid":1,"group_id":null},"label_links":[{"id":11,"label_id":6,"target_id":1,"target_type":"Issue","created_at":"2017-08-15T18:37:40.795Z","updated_at":"2017-08-15T18:37:40.795Z","label":{"id":6,"title":"group label","color":"#A8D695","project_id":null,"created_at":"2017-08-15T18:37:19.698Z","updated_at":"2017-08-15T18:37:19.698Z","template":false,"description":"","group_id":5,"type":"GroupLabel","priorities":[]}},{"id":11,"label_id":2,"target_id":1,"target_type":"Issue","created_at":"2017-08-15T18:37:40.795Z","updated_at":"2017-08-15T18:37:40.795Z","label":{"id":6,"title":"A project label","color":"#A8D695","project_id":null,"created_at":"2017-08-15T18:37:19.698Z","updated_at":"2017-08-15T18:37:19.698Z","template":false,"description":"","group_id":5,"type":"ProjectLabel","priorities":[]}}]}
{"id":2,"title":"Fugiat est minima quae maxime non similique.","assignee_id":null,"project_id":8,"author_id":1,"created_at":"2017-07-07T18:13:01.138Z","updated_at":"2017-08-15T18:37:40.807Z","branch_name":null,"description":"Quam totam fuga numquam in eveniet.","state":"closed","iid":2,"updated_by_id":1,"confidential":false,"due_date":null,"moved_to_id":null,"lock_version":null,"time_estimate":0,"closed_at":null,"last_edited_at":null,"last_edited_by_id":null,"group_milestone_id":null,"milestone":{"id":2,"title":"A group milestone","description":"Group-level milestone","due_date":null,"created_at":"2016-06-14T15:02:04.415Z","updated_at":"2016-06-14T15:02:04.415Z","state":"active","iid":1,"group_id":100},"label_links":[{"id":11,"label_id":2,"target_id":1,"target_type":"Issue","created_at":"2017-08-15T18:37:40.795Z","updated_at":"2017-08-15T18:37:40.795Z","label":{"id":2,"title":"A project label","color":"#A8D695","project_id":null,"created_at":"2017-08-15T18:37:19.698Z","updated_at":"2017-08-15T18:37:19.698Z","template":false,"description":"","group_id":5,"type":"ProjectLabel","priorities":[]}}]}
-{"id":3,"title":"Issue with Epic","author_id":1,"project_id":8,"created_at":"2019-12-08T19:41:11.233Z","updated_at":"2019-12-08T19:41:53.194Z","position":0,"branch_name":null,"description":"Donec at nulla vitae sem molestie rutrum ut at sem.","state":"opened","iid":3,"updated_by_id":null,"confidential":false,"due_date":null,"moved_to_id":null,"issue_assignees":[],"notes":[],"milestone":{"id":2,"title":"A group milestone","description":"Group-level milestone","due_date":null,"created_at":"2016-06-14T15:02:04.415Z","updated_at":"2016-06-14T15:02:04.415Z","state":"active","iid":1,"group_id":100},"epic_issue":{"id":78,"relative_position":1073740323,"epic":{"id":1,"group_id":5,"author_id":1,"assignee_id":null,"iid":1,"updated_by_id":null,"last_edited_by_id":null,"lock_version":0,"start_date":null,"end_date":null,"last_edited_at":null,"created_at":"2019-12-08T19:37:07.098Z","updated_at":"2019-12-08T19:43:11.568Z","title":"An epic","description":null,"start_date_sourcing_milestone_id":null,"due_date_sourcing_milestone_id":null,"start_date_fixed":null,"due_date_fixed":null,"start_date_is_fixed":null,"due_date_is_fixed":null,"closed_by_id":null,"closed_at":null,"parent_id":null,"relative_position":null,"state_id":"opened","start_date_sourcing_epic_id":null,"due_date_sourcing_epic_id":null,"milestone_id":null}}}
+{"id":3,"title":"Issue with Epic","author_id":1,"project_id":8,"created_at":"2019-12-08T19:41:11.233Z","updated_at":"2019-12-08T19:41:53.194Z","position":0,"branch_name":null,"description":"Donec at nulla vitae sem molestie rutrum ut at sem.","state":"opened","iid":3,"updated_by_id":null,"confidential":false,"due_date":null,"moved_to_id":null,"issue_assignees":[],"notes":[],"milestone":{"id":2,"title":"A group milestone","description":"Group-level milestone","due_date":null,"created_at":"2016-06-14T15:02:04.415Z","updated_at":"2016-06-14T15:02:04.415Z","state":"active","iid":1,"group_id":100},"iteration":{"created_at":"2022-08-15T12:55:42.607Z","updated_at":"2022-08-15T12:56:19.269Z","start_date":"2022-08-15","due_date":"2022-08-21","group_id":260,"iid":5,"description":"iteration description","iterations_cadence":{"title":"iterations cadence"}},"epic_issue":{"id":78,"relative_position":1073740323,"epic":{"id":1,"group_id":5,"author_id":1,"assignee_id":null,"iid":1,"updated_by_id":null,"last_edited_by_id":null,"lock_version":0,"start_date":null,"end_date":null,"last_edited_at":null,"created_at":"2019-12-08T19:37:07.098Z","updated_at":"2019-12-08T19:43:11.568Z","title":"An epic","description":null,"start_date_sourcing_milestone_id":null,"due_date_sourcing_milestone_id":null,"start_date_fixed":null,"due_date_fixed":null,"start_date_is_fixed":null,"due_date_is_fixed":null,"closed_by_id":null,"closed_at":null,"parent_id":null,"relative_position":null,"state_id":"opened","start_date_sourcing_epic_id":null,"due_date_sourcing_epic_id":null,"milestone_id":null}},"resource_iteration_events":[{"user_id":1,"created_at":"2022-08-17T13:04:02.495Z","action":"add","iteration":{"created_at":"2022-08-15T12:55:42.607Z","updated_at":"2022-08-15T12:56:19.269Z","start_date":"2022-08-15","due_date":"2022-08-21","group_id":260,"iid":5,"description":"iteration description","iterations_cadence":{"title":"iterations cadence"}}}]}
diff --git a/spec/fixtures/security_reports/deprecated/gl-sast-report.json b/spec/fixtures/security_reports/deprecated/gl-sast-report.json
index 2f7e47281e2..c5b0148fe3e 100644
--- a/spec/fixtures/security_reports/deprecated/gl-sast-report.json
+++ b/spec/fixtures/security_reports/deprecated/gl-sast-report.json
@@ -961,4 +961,4 @@
"url": "https://cwe.mitre.org/data/definitions/120.html",
"tool": "flawfinder"
}
-]
+] \ No newline at end of file
diff --git a/spec/fixtures/security_reports/feature-branch/gl-sast-report.json b/spec/fixtures/security_reports/feature-branch/gl-sast-report.json
index f93233e0ebb..51761583c70 100644
--- a/spec/fixtures/security_reports/feature-branch/gl-sast-report.json
+++ b/spec/fixtures/security_reports/feature-branch/gl-sast-report.json
@@ -174,4 +174,4 @@
"start_time": "placeholder-value",
"end_time": "placeholder-value"
}
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/feature-branch/gl-secret-detection-report.json b/spec/fixtures/security_reports/feature-branch/gl-secret-detection-report.json
index 538364f84a2..4862a504cec 100644
--- a/spec/fixtures/security_reports/feature-branch/gl-secret-detection-report.json
+++ b/spec/fixtures/security_reports/feature-branch/gl-secret-detection-report.json
@@ -2,4 +2,4 @@
"version": "14.1.2",
"vulnerabilities": [],
"remediations": []
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-common-scanning-report-names.json b/spec/fixtures/security_reports/master/gl-common-scanning-report-names.json
index 3cfb3e51ef7..ef2ff7443d3 100644
--- a/spec/fixtures/security_reports/master/gl-common-scanning-report-names.json
+++ b/spec/fixtures/security_reports/master/gl-common-scanning-report-names.json
@@ -165,4 +165,4 @@
"end_time": "placeholder-value",
"status": "success"
}
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-common-scanning-report-without-top-level-scanner.json b/spec/fixtures/security_reports/master/gl-common-scanning-report-without-top-level-scanner.json
index 7f092bf5f68..417dc960aff 100644
--- a/spec/fixtures/security_reports/master/gl-common-scanning-report-without-top-level-scanner.json
+++ b/spec/fixtures/security_reports/master/gl-common-scanning-report-without-top-level-scanner.json
@@ -1,5 +1,6 @@
{
- "vulnerabilities": [{
+ "vulnerabilities": [
+ {
"category": "dependency_scanning",
"name": "Vulnerability for remediation testing 1",
"message": "This vulnerability should have ONE remediation",
@@ -12,24 +13,32 @@
"name": "Gemnasium"
},
"location": {},
- "identifiers": [{
- "type": "GitLab",
- "name": "Foo vulnerability",
- "value": "foo"
- }],
- "links": [{
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2137"
- }],
+ "identifiers": [
+ {
+ "type": "GitLab",
+ "name": "Foo vulnerability",
+ "value": "foo"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2137"
+ }
+ ],
"details": {
"commit": {
- "name": [{
- "lang": "en",
- "value": "The Commit"
- }],
- "description": [{
- "lang": "en",
- "value": "Commit where the vulnerability was identified"
- }],
+ "name": [
+ {
+ "lang": "en",
+ "value": "The Commit"
+ }
+ ],
+ "description": [
+ {
+ "lang": "en",
+ "value": "Commit where the vulnerability was identified"
+ }
+ ],
"type": "commit",
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
}
@@ -38,4 +47,4 @@
],
"dependency_files": [],
"version": "14.0.2"
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-common-scanning-report.json b/spec/fixtures/security_reports/master/gl-common-scanning-report.json
index 2065af15267..1295b44d4df 100644
--- a/spec/fixtures/security_reports/master/gl-common-scanning-report.json
+++ b/spec/fixtures/security_reports/master/gl-common-scanning-report.json
@@ -1,5 +1,6 @@
{
- "vulnerabilities": [{
+ "vulnerabilities": [
+ {
"category": "dependency_scanning",
"name": "Vulnerability for remediation testing 1",
"message": "This vulnerability should have ONE remediation",
@@ -12,24 +13,32 @@
"name": "Gemnasium"
},
"location": {},
- "identifiers": [{
- "type": "GitLab",
- "name": "Foo vulnerability",
- "value": "foo"
- }],
- "links": [{
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2137"
- }],
+ "identifiers": [
+ {
+ "type": "GitLab",
+ "name": "Foo vulnerability",
+ "value": "foo"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2137"
+ }
+ ],
"details": {
"commit": {
- "name": [{
- "lang": "en",
- "value": "The Commit"
- }],
- "description": [{
- "lang": "en",
- "value": "Commit where the vulnerability was identified"
- }],
+ "name": [
+ {
+ "lang": "en",
+ "value": "The Commit"
+ }
+ ],
+ "description": [
+ {
+ "lang": "en",
+ "value": "Commit where the vulnerability was identified"
+ }
+ ],
"type": "commit",
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
}
@@ -48,24 +57,32 @@
"name": "Gemnasium"
},
"location": {},
- "identifiers": [{
- "type": "GitLab",
- "name": "Foo vulnerability",
- "value": "foo"
- }],
- "links": [{
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2138"
- }],
+ "identifiers": [
+ {
+ "type": "GitLab",
+ "name": "Foo vulnerability",
+ "value": "foo"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2138"
+ }
+ ],
"details": {
"commit": {
- "name": [{
- "lang": "en",
- "value": "The Commit"
- }],
- "description": [{
- "lang": "en",
- "value": "Commit where the vulnerability was identified"
- }],
+ "name": [
+ {
+ "lang": "en",
+ "value": "The Commit"
+ }
+ ],
+ "description": [
+ {
+ "lang": "en",
+ "value": "Commit where the vulnerability was identified"
+ }
+ ],
"type": "commit",
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
}
@@ -84,24 +101,32 @@
"name": "Gemnasium"
},
"location": {},
- "identifiers": [{
- "type": "GitLab",
- "name": "Foo vulnerability",
- "value": "foo"
- }],
- "links": [{
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2139"
- }],
+ "identifiers": [
+ {
+ "type": "GitLab",
+ "name": "Foo vulnerability",
+ "value": "foo"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2139"
+ }
+ ],
"details": {
"commit": {
- "name": [{
- "lang": "en",
- "value": "The Commit"
- }],
- "description": [{
- "lang": "en",
- "value": "Commit where the vulnerability was identified"
- }],
+ "name": [
+ {
+ "lang": "en",
+ "value": "The Commit"
+ }
+ ],
+ "description": [
+ {
+ "lang": "en",
+ "value": "Commit where the vulnerability was identified"
+ }
+ ],
"type": "commit",
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
}
@@ -120,24 +145,32 @@
"name": "Gemnasium"
},
"location": {},
- "identifiers": [{
- "type": "GitLab",
- "name": "Foo vulnerability",
- "value": "foo"
- }],
- "links": [{
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2140"
- }],
+ "identifiers": [
+ {
+ "type": "GitLab",
+ "name": "Foo vulnerability",
+ "value": "foo"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-2140"
+ }
+ ],
"details": {
"commit": {
- "name": [{
- "lang": "en",
- "value": "The Commit"
- }],
- "description": [{
- "lang": "en",
- "value": "Commit where the vulnerability was identified"
- }],
+ "name": [
+ {
+ "lang": "en",
+ "value": "The Commit"
+ }
+ ],
+ "description": [
+ {
+ "lang": "en",
+ "value": "Commit where the vulnerability was identified"
+ }
+ ],
"type": "commit",
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
}
@@ -162,30 +195,37 @@
},
"summary": "The Origin header was changed to an invalid value of http://peachapisecurity.com and the response contained an Access-Control-Allow-Origin header which included this invalid Origin, indicating that the CORS configuration on the server is overly permissive.\n\n\n",
"request": {
- "headers": [{
- "name": "Host",
- "value": "127.0.0.1:7777"
- }],
+ "headers": [
+ {
+ "name": "Host",
+ "value": "127.0.0.1:7777"
+ }
+ ],
"method": "GET",
"url": "http://127.0.0.1:7777/api/users",
"body": ""
},
"response": {
- "headers": [{
- "name": "Server",
- "value": "TwistedWeb/20.3.0"
- }],
+ "headers": [
+ {
+ "name": "Server",
+ "value": "TwistedWeb/20.3.0"
+ }
+ ],
"reason_phrase": "OK",
"status_code": 200,
"body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]"
},
- "supporting_messages": [{
+ "supporting_messages": [
+ {
"name": "Origional",
"request": {
- "headers": [{
- "name": "Host",
- "value": "127.0.0.1:7777"
- }],
+ "headers": [
+ {
+ "name": "Host",
+ "value": "127.0.0.1:7777"
+ }
+ ],
"method": "GET",
"url": "http://127.0.0.1:7777/api/users",
"body": ""
@@ -194,19 +234,23 @@
{
"name": "Recorded",
"request": {
- "headers": [{
- "name": "Host",
- "value": "127.0.0.1:7777"
- }],
+ "headers": [
+ {
+ "name": "Host",
+ "value": "127.0.0.1:7777"
+ }
+ ],
"method": "GET",
"url": "http://127.0.0.1:7777/api/users",
"body": ""
},
"response": {
- "headers": [{
- "name": "Server",
- "value": "TwistedWeb/20.3.0"
- }],
+ "headers": [
+ {
+ "name": "Server",
+ "value": "TwistedWeb/20.3.0"
+ }
+ ],
"reason_phrase": "OK",
"status_code": 200,
"body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]"
@@ -215,24 +259,32 @@
]
},
"location": {},
- "identifiers": [{
- "type": "GitLab",
- "name": "Foo vulnerability",
- "value": "foo"
- }],
- "links": [{
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020"
- }],
+ "identifiers": [
+ {
+ "type": "GitLab",
+ "name": "Foo vulnerability",
+ "value": "foo"
+ }
+ ],
+ "links": [
+ {
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1020"
+ }
+ ],
"details": {
"commit": {
- "name": [{
- "lang": "en",
- "value": "The Commit"
- }],
- "description": [{
- "lang": "en",
- "value": "Commit where the vulnerability was identified"
- }],
+ "name": [
+ {
+ "lang": "en",
+ "value": "The Commit"
+ }
+ ],
+ "description": [
+ {
+ "lang": "en",
+ "value": "Commit where the vulnerability was identified"
+ }
+ ],
"type": "commit",
"value": "41df7b7eb3be2b5be2c406c2f6d28cd6631eeb19"
}
@@ -258,30 +310,37 @@
},
"summary": "The Origin header was changed to an invalid value of http://peachapisecurity.com and the response contained an Access-Control-Allow-Origin header which included this invalid Origin, indicating that the CORS configuration on the server is overly permissive.\n\n\n",
"request": {
- "headers": [{
- "name": "Host",
- "value": "127.0.0.1:7777"
- }],
+ "headers": [
+ {
+ "name": "Host",
+ "value": "127.0.0.1:7777"
+ }
+ ],
"method": "GET",
"url": "http://127.0.0.1:7777/api/users",
"body": ""
},
"response": {
- "headers": [{
- "name": "Server",
- "value": "TwistedWeb/20.3.0"
- }],
+ "headers": [
+ {
+ "name": "Server",
+ "value": "TwistedWeb/20.3.0"
+ }
+ ],
"reason_phrase": "OK",
"status_code": 200,
"body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]"
},
- "supporting_messages": [{
+ "supporting_messages": [
+ {
"name": "Origional",
"request": {
- "headers": [{
- "name": "Host",
- "value": "127.0.0.1:7777"
- }],
+ "headers": [
+ {
+ "name": "Host",
+ "value": "127.0.0.1:7777"
+ }
+ ],
"method": "GET",
"url": "http://127.0.0.1:7777/api/users",
"body": ""
@@ -290,19 +349,23 @@
{
"name": "Recorded",
"request": {
- "headers": [{
- "name": "Host",
- "value": "127.0.0.1:7777"
- }],
+ "headers": [
+ {
+ "name": "Host",
+ "value": "127.0.0.1:7777"
+ }
+ ],
"method": "GET",
"url": "http://127.0.0.1:7777/api/users",
"body": ""
},
"response": {
- "headers": [{
- "name": "Server",
- "value": "TwistedWeb/20.3.0"
- }],
+ "headers": [
+ {
+ "name": "Server",
+ "value": "TwistedWeb/20.3.0"
+ }
+ ],
"reason_phrase": "OK",
"status_code": 200,
"body": "[{\"user_id\":1,\"user\":\"admin\",\"first\":\"Joe\",\"last\":\"Smith\",\"password\":\"Password!\"}]"
@@ -311,15 +374,19 @@
]
},
"location": {},
- "identifiers": [{
- "type": "GitLab",
- "name": "Bar vulnerability",
- "value": "bar"
- }],
- "links": [{
- "name": "CVE-1030",
- "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030"
- }]
+ "identifiers": [
+ {
+ "type": "GitLab",
+ "name": "Bar vulnerability",
+ "value": "bar"
+ }
+ ],
+ "links": [
+ {
+ "name": "CVE-1030",
+ "url": "https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-1030"
+ }
+ ]
},
{
"category": "dependency_scanning",
@@ -338,57 +405,73 @@
"links": []
}
],
- "remediations": [{
- "fixes": [{
- "cve": "CVE-2137"
- }],
+ "remediations": [
+ {
+ "fixes": [
+ {
+ "cve": "CVE-2137"
+ }
+ ],
"summary": "this remediates CVE-2137",
"diff": "dG90YWxseSBsZWdpdCBkaWZm"
},
{
- "fixes": [{
- "cve": "CVE-2138"
- }],
+ "fixes": [
+ {
+ "cve": "CVE-2138"
+ }
+ ],
"summary": "this remediates CVE-2138",
"diff": "dG90YWxseSBsZWdpdCBkaWZm"
},
{
- "fixes": [{
- "cve": "CVE-2139"
- }, {
- "cve": "CVE-2140"
- }],
+ "fixes": [
+ {
+ "cve": "CVE-2139"
+ },
+ {
+ "cve": "CVE-2140"
+ }
+ ],
"summary": "this remediates CVE-2139 and CVE-2140",
"diff": "dG90YWxseSBsZWdpdGltYXRlIGRpZmYsIDEwLzEwIHdvdWxkIGFwcGx5"
},
{
- "fixes": [{
- "cve": "CVE-1020"
- }],
+ "fixes": [
+ {
+ "cve": "CVE-1020"
+ }
+ ],
"summary": "",
"diff": ""
},
{
- "fixes": [{
- "cve": "CVE",
- "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3"
- }],
+ "fixes": [
+ {
+ "cve": "CVE",
+ "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3"
+ }
+ ],
"summary": "",
"diff": ""
},
{
- "fixes": [{
- "cve": "CVE",
- "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3"
- }],
+ "fixes": [
+ {
+ "cve": "CVE",
+ "id": "bb2fbeb1b71ea360ce3f86f001d4e84823c3ffe1a1f7d41ba7466b14cfa953d3"
+ }
+ ],
"summary": "",
"diff": ""
},
{
- "fixes": [{
- "id": "2134",
- "cve": "CVE-1"
- }],
+ "fixes": [
+ {
+ "id": "2134",
+ "cve": "CVE-1"
+ }
+ ],
"summary": "",
"diff": ""
}
@@ -419,4 +502,4 @@
"status": "success"
},
"version": "14.0.2"
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-sast-missing-scanner.json b/spec/fixtures/security_reports/master/gl-sast-missing-scanner.json
index ab3ee348263..fcfd9b831f4 100644
--- a/spec/fixtures/security_reports/master/gl-sast-missing-scanner.json
+++ b/spec/fixtures/security_reports/master/gl-sast-missing-scanner.json
@@ -799,4 +799,4 @@
"url": "https://cwe.mitre.org/data/definitions/120.html"
}
]
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-sast-report-bandit.json b/spec/fixtures/security_reports/master/gl-sast-report-bandit.json
index a80833354ed..d0346479b85 100644
--- a/spec/fixtures/security_reports/master/gl-sast-report-bandit.json
+++ b/spec/fixtures/security_reports/master/gl-sast-report-bandit.json
@@ -40,4 +40,4 @@
"end_time": "2022-03-11T00:21:50",
"status": "success"
}
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-sast-report-gosec.json b/spec/fixtures/security_reports/master/gl-sast-report-gosec.json
index 42986ea1045..4c385326c8c 100644
--- a/spec/fixtures/security_reports/master/gl-sast-report-gosec.json
+++ b/spec/fixtures/security_reports/master/gl-sast-report-gosec.json
@@ -65,4 +65,4 @@
"end_time": "2022-03-15T20:33:17",
"status": "success"
}
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-sast-report-minimal.json b/spec/fixtures/security_reports/master/gl-sast-report-minimal.json
index 60a67453c9b..5e9273d43b1 100644
--- a/spec/fixtures/security_reports/master/gl-sast-report-minimal.json
+++ b/spec/fixtures/security_reports/master/gl-sast-report-minimal.json
@@ -65,4 +65,4 @@
"start_time": "placeholder-value",
"end_time": "placeholder-value"
}
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-bandit.json b/spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-bandit.json
index 2a60a75366e..037b9fb8d3e 100644
--- a/spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-bandit.json
+++ b/spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-bandit.json
@@ -68,4 +68,4 @@
"end_time": "2022-03-11T18:48:22",
"status": "success"
}
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-gosec.json b/spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-gosec.json
index 3d8c65d5823..f01d26a69c9 100644
--- a/spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-gosec.json
+++ b/spec/fixtures/security_reports/master/gl-sast-report-semgrep-for-gosec.json
@@ -67,4 +67,4 @@
"end_time": "2022-03-15T20:37:05",
"status": "success"
}
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-sast-report.json b/spec/fixtures/security_reports/master/gl-sast-report.json
index 63504e6fccc..1aa8db1a65f 100644
--- a/spec/fixtures/security_reports/master/gl-sast-report.json
+++ b/spec/fixtures/security_reports/master/gl-sast-report.json
@@ -197,4 +197,4 @@
"start_time": "placeholder-value",
"end_time": "placeholder-value"
}
-}
+} \ No newline at end of file
diff --git a/spec/fixtures/security_reports/master/gl-secret-detection-report.json b/spec/fixtures/security_reports/master/gl-secret-detection-report.json
index 9b0b2a19beb..21d4f3f1798 100644
--- a/spec/fixtures/security_reports/master/gl-secret-detection-report.json
+++ b/spec/fixtures/security_reports/master/gl-secret-detection-report.json
@@ -30,4 +30,4 @@
}
],
"remediations": []
-}
+} \ No newline at end of file
diff --git a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
index 653143a6243..01317eb5dba 100644
--- a/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
+++ b/spec/frontend/jira_connect/subscriptions/components/sign_in_oauth_button_spec.js
@@ -30,7 +30,7 @@ describe('SignInOauthButton', () => {
let store;
const mockOauthMetadata = {
oauth_authorize_url: 'https://gitlab.com/mockOauth',
- oauth_token_url: 'https://gitlab.com/mockOauthToken',
+ oauth_token_path: 'https://gitlab.com/mockOauthToken',
oauth_token_payload: {
client_id: '543678901',
},
@@ -197,7 +197,7 @@ describe('SignInOauthButton', () => {
});
it('executes POST request to Oauth token endpoint', () => {
- expect(fetchOAuthToken).toHaveBeenCalledWith(mockOauthMetadata.oauth_token_url, {
+ expect(fetchOAuthToken).toHaveBeenCalledWith(mockOauthMetadata.oauth_token_path, {
code: '1234',
code_verifier: 'mock-verifier',
client_id: mockOauthMetadata.oauth_token_payload.client_id,
diff --git a/spec/helpers/jira_connect_helper_spec.rb b/spec/helpers/jira_connect_helper_spec.rb
index 4d2fc3d9ee6..97e37023c2d 100644
--- a/spec/helpers/jira_connect_helper_spec.rb
+++ b/spec/helpers/jira_connect_helper_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe JiraConnectHelper do
describe '#jira_connect_app_data' do
+ let_it_be(:installation) { create(:jira_connect_installation) }
let_it_be(:subscription) { create(:jira_connect_subscription) }
let(:user) { create(:user) }
@@ -13,11 +14,12 @@ RSpec.describe JiraConnectHelper do
stub_application_setting(jira_connect_application_key: client_id)
end
- subject { helper.jira_connect_app_data([subscription]) }
+ subject { helper.jira_connect_app_data([subscription], installation) }
context 'user is not logged in' do
before do
allow(view).to receive(:current_user).and_return(nil)
+ allow(Gitlab).to receive_message_chain('config.gitlab.url') { 'http://test.host' }
end
it 'includes Jira Connect app attributes' do
@@ -36,14 +38,14 @@ RSpec.describe JiraConnectHelper do
end
context 'with oauth_metadata' do
- let(:oauth_metadata) { helper.jira_connect_app_data([subscription])[:oauth_metadata] }
+ let(:oauth_metadata) { helper.jira_connect_app_data([subscription], installation)[:oauth_metadata] }
subject(:parsed_oauth_metadata) { Gitlab::Json.parse(oauth_metadata).deep_symbolize_keys }
it 'assigns oauth_metadata' do
expect(parsed_oauth_metadata).to include(
oauth_authorize_url: start_with('http://test.host/oauth/authorize?'),
- oauth_token_url: 'http://test.host/oauth/token',
+ oauth_token_path: '/oauth/token',
state: %r/[a-z0-9.]{32}/,
oauth_token_payload: hash_including(
grant_type: 'authorization_code',
@@ -74,6 +76,30 @@ RSpec.describe JiraConnectHelper do
expect(oauth_metadata).to be_nil
end
end
+
+ context 'with self-managed instance' do
+ let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'https://gitlab.example.com') }
+
+ it 'points urls to the self-managed instance' do
+ expect(parsed_oauth_metadata).to include(
+ oauth_authorize_url: start_with('https://gitlab.example.com/oauth/authorize?'),
+ oauth_token_path: '/oauth/token'
+ )
+ end
+
+ context 'and jira_connect_oauth_self_managed feature is disabled' do
+ before do
+ stub_feature_flags(jira_connect_oauth_self_managed: false)
+ end
+
+ it 'does not point urls to the self-managed instance' do
+ expect(parsed_oauth_metadata).not_to include(
+ oauth_authorize_url: start_with('https://gitlab.example.com/oauth/authorize?'),
+ oauth_token_path: 'https://gitlab.example.com/oauth/token'
+ )
+ end
+ end
+ end
end
it 'passes group as "skip_groups" param' do
diff --git a/spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb b/spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb
index 3f02356b41e..e780cde4ae2 100644
--- a/spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb
+++ b/spec/lib/bulk_imports/projects/pipelines/merge_requests_pipeline_spec.rb
@@ -4,6 +4,7 @@ require 'spec_helper'
RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do
let_it_be(:user) { create(:user) }
+ let_it_be(:another_user) { create(:user) }
let_it_be(:group) { create(:group) }
let_it_be(:project) { create(:project, :repository, group: group) }
let_it_be(:bulk_import) { create(:bulk_import, user: user) }
@@ -85,6 +86,9 @@ RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do
describe '#run' do
before do
group.add_owner(user)
+ group.add_maintainer(another_user)
+
+ ::BulkImports::UsersMapper.new(context: context).cache_source_user_id(42, another_user.id)
allow_next_instance_of(BulkImports::Common::Extractors::NdjsonExtractor) do |extractor|
allow(extractor).to receive(:remove_tmp_dir)
@@ -293,5 +297,52 @@ RSpec.describe BulkImports::Projects::Pipelines::MergeRequestsPipeline do
expect(imported_mr.milestone.title).to eq(attributes.dig('milestone', 'title'))
end
end
+
+ context 'user assignments' do
+ let(:attributes) do
+ {
+ key => [
+ {
+ 'user_id' => 22,
+ 'created_at' => '2020-01-07T11:21:21.235Z'
+ },
+ {
+ 'user_id' => 42,
+ 'created_at' => '2020-01-08T12:21:21.235Z'
+ }
+ ]
+ }
+ end
+
+ context 'assignees' do
+ let(:key) { 'merge_request_assignees' }
+
+ it 'imports mr assignees' do
+ assignees = imported_mr.merge_request_assignees
+
+ expect(assignees.pluck(:user_id)).to contain_exactly(user.id, another_user.id)
+ end
+ end
+
+ context 'approvals' do
+ let(:key) { 'approvals' }
+
+ it 'imports mr approvals' do
+ approvals = imported_mr.approvals
+
+ expect(approvals.pluck(:user_id)).to contain_exactly(user.id, another_user.id)
+ end
+ end
+
+ context 'reviewers' do
+ let(:key) { 'merge_request_reviewers' }
+
+ it 'imports mr reviewers' do
+ reviewers = imported_mr.merge_request_reviewers
+
+ expect(reviewers.pluck(:user_id)).to contain_exactly(user.id, another_user.id)
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
index 7215c65d41b..15df5b2f68c 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
@@ -24,5 +24,24 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::AssignPartition do
it 'assigns partition_id to pipeline' do
expect { subject }.to change(pipeline, :partition_id).to(current_partition_id)
end
+
+ context 'with parent-child pipelines' do
+ let(:bridge) do
+ instance_double(Ci::Bridge,
+ triggers_child_pipeline?: true,
+ parent_pipeline: instance_double(Ci::Pipeline, partition_id: 125))
+ end
+
+ let(:command) do
+ Gitlab::Ci::Pipeline::Chain::Command.new(
+ project: project,
+ current_user: user,
+ bridge: bridge)
+ end
+
+ it 'assigns partition_id to pipeline' do
+ expect { subject }.to change(pipeline, :partition_id).to(125)
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
index de43e759193..6e8b6e40928 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/command_spec.rb
@@ -302,13 +302,13 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
context 'when bridge is present' do
context 'when bridge triggers a child pipeline' do
- let(:bridge) { double(:bridge, triggers_child_pipeline?: true) }
+ let(:bridge) { instance_double(Ci::Bridge, triggers_child_pipeline?: true) }
it { is_expected.to be_truthy }
end
context 'when bridge triggers a multi-project pipeline' do
- let(:bridge) { double(:bridge, triggers_child_pipeline?: false) }
+ let(:bridge) { instance_double(Ci::Bridge, triggers_child_pipeline?: false) }
it { is_expected.to be_falsey }
end
@@ -321,6 +321,38 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
end
end
+ describe '#parent_pipeline_partition_id' do
+ let(:command) { described_class.new(bridge: bridge) }
+
+ subject { command.parent_pipeline_partition_id }
+
+ context 'when bridge is present' do
+ context 'when bridge triggers a child pipeline' do
+ let(:pipeline) { instance_double(Ci::Pipeline, partition_id: 123) }
+
+ let(:bridge) do
+ instance_double(Ci::Bridge,
+ triggers_child_pipeline?: true,
+ parent_pipeline: pipeline)
+ end
+
+ it { is_expected.to eq(123) }
+ end
+
+ context 'when bridge triggers a multi-project pipeline' do
+ let(:bridge) { instance_double(Ci::Bridge, triggers_child_pipeline?: false) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ context 'when bridge is not present' do
+ let(:bridge) { nil }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
describe '#increment_pipeline_failure_reason_counter' do
let(:command) { described_class.new }
let(:reason) { :size_limit_exceeded }
@@ -345,7 +377,7 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Command do
describe '#observe_step_duration' do
context 'when ci_pipeline_creation_step_duration_tracking is enabled' do
it 'adds the duration to the step duration histogram' do
- histogram = double(:histogram)
+ histogram = instance_double(Prometheus::Client::Histogram)
duration = 1.hour
expect(::Gitlab::Ci::Pipeline::Metrics).to receive(:pipeline_creation_step_duration_histogram)
diff --git a/spec/lib/gitlab/database/partitioning/convert_table_to_first_list_partition_spec.rb b/spec/lib/gitlab/database/partitioning/convert_table_to_first_list_partition_spec.rb
new file mode 100644
index 00000000000..af7d751a404
--- /dev/null
+++ b/spec/lib/gitlab/database/partitioning/convert_table_to_first_list_partition_spec.rb
@@ -0,0 +1,246 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::Partitioning::ConvertTableToFirstListPartition do
+ include Gitlab::Database::DynamicModelHelpers
+ include Database::TableSchemaHelpers
+
+ let(:migration_context) { Gitlab::Database::Migration[2.0].new }
+
+ let(:connection) { migration_context.connection }
+ let(:table_name) { '_test_table_to_partition' }
+ let(:table_identifier) { "#{connection.current_schema}.#{table_name}" }
+ let(:partitioning_column) { :partition_number }
+ let(:partitioning_default) { 1 }
+ let(:referenced_table_name) { '_test_referenced_table' }
+ let(:other_referenced_table_name) { '_test_other_referenced_table' }
+ let(:parent_table_name) { "#{table_name}_parent" }
+
+ let(:model) { define_batchable_model(table_name, connection: connection) }
+
+ let(:parent_model) { define_batchable_model(parent_table_name, connection: connection) }
+
+ let(:converter) do
+ described_class.new(
+ migration_context: migration_context,
+ table_name: table_name,
+ partitioning_column: partitioning_column,
+ parent_table_name: parent_table_name,
+ zero_partition_value: partitioning_default
+ )
+ end
+
+ before do
+ # Suppress printing migration progress
+ allow(migration_context).to receive(:puts)
+ allow(migration_context.connection).to receive(:transaction_open?).and_return(false)
+
+ connection.execute(<<~SQL)
+ create table #{referenced_table_name} (
+ id bigserial primary key not null
+ )
+ SQL
+
+ connection.execute(<<~SQL)
+ create table #{other_referenced_table_name} (
+ id bigserial primary key not null
+ )
+ SQL
+
+ connection.execute(<<~SQL)
+ insert into #{referenced_table_name} default values;
+ insert into #{other_referenced_table_name} default values;
+ SQL
+
+ connection.execute(<<~SQL)
+ create table #{table_name} (
+ id bigserial not null,
+ #{partitioning_column} bigint not null default #{partitioning_default},
+ referenced_id bigint not null references #{referenced_table_name} (id) on delete cascade,
+ other_referenced_id bigint not null references #{other_referenced_table_name} (id) on delete set null,
+ primary key (id, #{partitioning_column})
+ )
+ SQL
+
+ connection.execute(<<~SQL)
+ insert into #{table_name} (referenced_id, other_referenced_id)
+ select #{referenced_table_name}.id, #{other_referenced_table_name}.id
+ from #{referenced_table_name}, #{other_referenced_table_name};
+ SQL
+ end
+
+ describe "#prepare_for_partitioning" do
+ subject(:prepare) { converter.prepare_for_partitioning }
+
+ it 'adds a check constraint' do
+ expect { prepare }.to change {
+ Gitlab::Database::PostgresConstraint
+ .check_constraints
+ .by_table_identifier(table_identifier)
+ .count
+ }.from(0).to(1)
+ end
+ end
+
+ describe '#revert_prepare_for_partitioning' do
+ before do
+ converter.prepare_for_partitioning
+ end
+
+ subject(:revert_prepare) { converter.revert_preparation_for_partitioning }
+
+ it 'removes a check constraint' do
+ expect { revert_prepare }.to change {
+ Gitlab::Database::PostgresConstraint
+ .check_constraints
+ .by_table_identifier("#{connection.current_schema}.#{table_name}")
+ .count
+ }.from(1).to(0)
+ end
+ end
+
+ describe "#convert_to_zero_partition" do
+ subject(:partition) { converter.partition }
+
+ before do
+ converter.prepare_for_partitioning
+ end
+
+ context 'when the primary key is incorrect' do
+ before do
+ connection.execute(<<~SQL)
+ alter table #{table_name} drop constraint #{table_name}_pkey;
+ alter table #{table_name} add constraint #{table_name}_pkey PRIMARY KEY (id);
+ SQL
+ end
+
+ it 'throws a reasonable error message' do
+ expect { partition }.to raise_error(described_class::UnableToPartition, /#{partitioning_column}/)
+ end
+ end
+
+ context 'when there is not a supporting check constraint' do
+ before do
+ connection.execute(<<~SQL)
+ alter table #{table_name} drop constraint partitioning_constraint;
+ SQL
+ end
+
+ it 'throws a reasonable error message' do
+ expect { partition }.to raise_error(described_class::UnableToPartition, /constraint /)
+ end
+ end
+
+ it 'migrates the table to a partitioned table' do
+ fks_before = migration_context.foreign_keys(table_name)
+
+ partition
+
+ expect(Gitlab::Database::PostgresPartition.for_parent_table(parent_table_name).count).to eq(1)
+ expect(migration_context.foreign_keys(parent_table_name).map(&:options)).to match_array(fks_before.map(&:options))
+
+ connection.execute(<<~SQL)
+ insert into #{table_name} (referenced_id, other_referenced_id) select #{referenced_table_name}.id, #{other_referenced_table_name}.id from #{referenced_table_name}, #{other_referenced_table_name};
+ SQL
+
+ # Create a second partition
+ connection.execute(<<~SQL)
+ create table #{table_name}2 partition of #{parent_table_name} FOR VALUES IN (2)
+ SQL
+
+ parent_model.create!(partitioning_column => 2, :referenced_id => 1, :other_referenced_id => 1)
+ expect(parent_model.pluck(:id)).to match_array([1, 2, 3])
+ end
+
+ context 'when an error occurs during the conversion' do
+ def fail_first_time
+ # We can't directly use a boolean here, as we need something that will be passed by-reference to the proc
+ fault_status = { faulted: false }
+ proc do |m, *args, **kwargs|
+ next m.call(*args, **kwargs) if fault_status[:faulted]
+
+ fault_status[:faulted] = true
+ raise 'fault!'
+ end
+ end
+
+ def fail_sql_matching(regex)
+ proc do
+ allow(migration_context.connection).to receive(:execute).and_call_original
+ allow(migration_context.connection).to receive(:execute).with(regex).and_wrap_original(&fail_first_time)
+ end
+ end
+
+ def fail_adding_fk(from_table, to_table)
+ proc do
+ allow(migration_context.connection).to receive(:add_foreign_key).and_call_original
+ expect(migration_context.connection).to receive(:add_foreign_key).with(from_table, to_table, any_args)
+ .and_wrap_original(&fail_first_time)
+ end
+ end
+
+ where(:case_name, :fault) do
+ [
+ ["creating parent table", lazy { fail_sql_matching(/CREATE/i) }],
+ ["adding the first foreign key", lazy { fail_adding_fk(parent_table_name, referenced_table_name) }],
+ ["adding the second foreign key", lazy { fail_adding_fk(parent_table_name, other_referenced_table_name) }],
+ ["attaching table", lazy { fail_sql_matching(/ATTACH/i) }]
+ ]
+ end
+
+ before do
+ # Set up the fault that we'd like to inject
+ fault.call
+ end
+
+ with_them do
+ it 'recovers from a fault', :aggregate_failures do
+ expect { converter.partition }.to raise_error(/fault/)
+ expect(Gitlab::Database::PostgresPartition.for_parent_table(parent_table_name).count).to eq(0)
+
+ expect { converter.partition }.not_to raise_error
+ expect(Gitlab::Database::PostgresPartition.for_parent_table(parent_table_name).count).to eq(1)
+ end
+ end
+ end
+ end
+
+ describe '#revert_conversion_to_zero_partition' do
+ before do
+ converter.prepare_for_partitioning
+ converter.partition
+ end
+
+ subject(:revert_conversion) { converter.revert_partitioning }
+
+ it 'detaches the partition' do
+ expect { revert_conversion }.to change {
+ Gitlab::Database::PostgresPartition
+ .for_parent_table(parent_table_name).count
+ }.from(1).to(0)
+ end
+
+ it 'does not drop the child partition' do
+ expect { revert_conversion }.not_to change { table_oid(table_name) }
+ end
+
+ it 'removes the parent table' do
+ expect { revert_conversion }.to change { table_oid(parent_table_name).present? }.from(true).to(false)
+ end
+
+ it 're-adds the check constraint' do
+ expect { revert_conversion }.to change {
+ Gitlab::Database::PostgresConstraint
+ .check_constraints
+ .by_table_identifier(table_identifier)
+ .count
+ }.by(1)
+ end
+
+ it 'moves sequences back to the original table' do
+ expect { revert_conversion }.to change { converter.send(:sequences_owned_by, table_name).count }.from(0)
+ .and change { converter.send(:sequences_owned_by, parent_table_name).count }.to(0)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
index 1026b4370a5..8bb9ad2737a 100644
--- a/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
+++ b/spec/lib/gitlab/database/partitioning_migration_helpers/table_management_helpers_spec.rb
@@ -41,6 +41,76 @@ RSpec.describe Gitlab::Database::PartitioningMigrationHelpers::TableManagementHe
allow(migration).to receive(:assert_table_is_allowed)
end
+ context 'list partitioning conversion helpers' do
+ shared_examples_for 'delegates to ConvertTableToFirstListPartition' do
+ it 'throws an error if in a transaction' do
+ allow(migration).to receive(:transaction_open?).and_return(true)
+ expect { migrate }.to raise_error(/cannot be run inside a transaction/)
+ end
+
+ it 'delegates to a method on ConvertTableToFirstListPartition' do
+ expect_next_instance_of(Gitlab::Database::Partitioning::ConvertTableToFirstListPartition,
+ migration_context: migration,
+ table_name: source_table,
+ parent_table_name: partitioned_table,
+ partitioning_column: partition_column,
+ zero_partition_value: min_date) do |converter|
+ expect(converter).to receive(expected_method)
+ end
+
+ migrate
+ end
+ end
+
+ describe '#convert_table_to_first_list_partition' do
+ it_behaves_like 'delegates to ConvertTableToFirstListPartition' do
+ let(:expected_method) { :partition }
+ let(:migrate) do
+ migration.convert_table_to_first_list_partition(table_name: source_table,
+ partitioning_column: partition_column,
+ parent_table_name: partitioned_table,
+ initial_partitioning_value: min_date)
+ end
+ end
+ end
+
+ describe '#revert_converting_table_to_first_list_partition' do
+ it_behaves_like 'delegates to ConvertTableToFirstListPartition' do
+ let(:expected_method) { :revert_partitioning }
+ let(:migrate) do
+ migration.revert_converting_table_to_first_list_partition(table_name: source_table,
+ partitioning_column: partition_column,
+ parent_table_name: partitioned_table,
+ initial_partitioning_value: min_date)
+ end
+ end
+ end
+
+ describe '#prepare_constraint_for_list_partitioning' do
+ it_behaves_like 'delegates to ConvertTableToFirstListPartition' do
+ let(:expected_method) { :prepare_for_partitioning }
+ let(:migrate) do
+ migration.prepare_constraint_for_list_partitioning(table_name: source_table,
+ partitioning_column: partition_column,
+ parent_table_name: partitioned_table,
+ initial_partitioning_value: min_date)
+ end
+ end
+ end
+
+ describe '#revert_preparing_constraint_for_list_partitioning' do
+ it_behaves_like 'delegates to ConvertTableToFirstListPartition' do
+ let(:expected_method) { :revert_preparation_for_partitioning }
+ let(:migrate) do
+ migration.revert_preparing_constraint_for_list_partitioning(table_name: source_table,
+ partitioning_column: partition_column,
+ parent_table_name: partitioned_table,
+ initial_partitioning_value: min_date)
+ end
+ end
+ end
+ end
+
describe '#partition_table_by_date' do
let(:partition_column) { 'created_at' }
let(:old_primary_key) { 'id' }
diff --git a/spec/lib/gitlab/database/postgres_constraint_spec.rb b/spec/lib/gitlab/database/postgres_constraint_spec.rb
new file mode 100644
index 00000000000..75084a69115
--- /dev/null
+++ b/spec/lib/gitlab/database/postgres_constraint_spec.rb
@@ -0,0 +1,123 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::Database::PostgresConstraint, type: :model do
+ # PostgresConstraint does not `behaves_like 'a postgres model'` because it does not correspond 1-1 with a single entry
+ # in pg_class
+ let(:schema) { ActiveRecord::Base.connection.current_schema }
+ let(:table_name) { '_test_table' }
+ let(:table_identifier) { "#{schema}.#{table_name}" }
+ let(:referenced_name) { '_test_referenced' }
+ let(:check_constraint_a_positive) { 'check_constraint_a_positive' }
+ let(:check_constraint_a_gt_b) { 'check_constraint_a_gt_b' }
+ let(:invalid_constraint_a) { 'check_constraint_b_positive_invalid' }
+ let(:unique_constraint_a) { "#{table_name}_a_key" }
+
+ before do
+ ActiveRecord::Base.connection.execute(<<~SQL)
+ create table #{referenced_name} (
+ id bigserial primary key not null
+ );
+
+ create table #{table_name} (
+ id bigserial not null,
+ referenced_id bigint not null references #{referenced_name}(id),
+ a integer unique,
+ b integer,
+ primary key (id, referenced_id),
+ constraint #{check_constraint_a_positive} check (a > 0),
+ constraint #{check_constraint_a_gt_b} check (a > b)
+ );
+
+ alter table #{table_name} add constraint #{invalid_constraint_a} CHECK (a > 1) NOT VALID;
+ SQL
+ end
+
+ describe '#by_table_identifier' do
+ subject(:constraints_for_table) { described_class.by_table_identifier(table_identifier) }
+
+ it 'includes all constraints on the table' do
+ all_constraints_for_table = described_class.all.to_a.select { |c| c.table_identifier == table_identifier }
+ expect(all_constraints_for_table.map(&:oid)).to match_array(constraints_for_table.pluck(:oid))
+ end
+
+ it 'throws an error if the format is incorrect' do
+ expect { described_class.by_table_identifier('not-an-identifier') }.to raise_error(ArgumentError)
+ end
+ end
+
+ describe '#check_constraints' do
+ subject(:check_constraints) { described_class.check_constraints.by_table_identifier(table_identifier) }
+
+ it 'finds check constraints for the table' do
+ expect(check_constraints.map(&:name)).to contain_exactly(check_constraint_a_positive,
+ check_constraint_a_gt_b,
+ invalid_constraint_a)
+ end
+
+ it 'includes columns for the check constraints', :aggregate_failures do
+ expect(check_constraints.find_by(name: check_constraint_a_positive).column_names).to contain_exactly('a')
+ expect(check_constraints.find_by(name: check_constraint_a_gt_b).column_names).to contain_exactly('a', 'b')
+ end
+ end
+
+ describe "#valid" do
+ subject(:valid_constraint_names) { described_class.valid.by_table_identifier(table_identifier).pluck(:name) }
+
+ let(:all_constraint_names) { described_class.by_table_identifier(table_identifier).pluck(:name) }
+
+ it 'excludes invalid constraints' do
+ expect(valid_constraint_names).not_to include(invalid_constraint_a)
+ expect(valid_constraint_names).to match_array(all_constraint_names - [invalid_constraint_a])
+ end
+ end
+
+ describe '#primary_key_constraints' do
+ subject(:pk_constraints) { described_class.primary_key_constraints.by_table_identifier(table_identifier) }
+
+ it 'finds the primary key constraint for the table' do
+ expect(pk_constraints.count).to eq(1)
+ expect(pk_constraints.first.constraint_type).to eq('p')
+ end
+
+ it 'finds the columns in the primary key constraint' do
+ constraint = pk_constraints.first
+ expect(constraint.column_names).to contain_exactly('id', 'referenced_id')
+ end
+ end
+
+ describe '#unique_constraints' do
+ subject(:unique_constraints) { described_class.unique_constraints.by_table_identifier(table_identifier) }
+
+ it 'finds the unique constraints for the table' do
+ expect(unique_constraints.pluck(:name)).to contain_exactly(unique_constraint_a)
+ end
+ end
+
+ describe '#primary_or_unique_constraints' do
+ subject(:pk_or_unique_constraints) do
+ described_class.primary_or_unique_constraints.by_table_identifier(table_identifier)
+ end
+
+ it 'finds primary and unique constraints' do
+ expect(pk_or_unique_constraints.pluck(:name)).to contain_exactly("#{table_name}_pkey", unique_constraint_a)
+ end
+ end
+
+ describe '#including_column' do
+ it 'only matches constraints on the given column' do
+ constraints_on_a = described_class.by_table_identifier(table_identifier).including_column('a').map(&:name)
+ expect(constraints_on_a).to contain_exactly(check_constraint_a_positive, check_constraint_a_gt_b,
+ unique_constraint_a, invalid_constraint_a)
+ end
+ end
+
+ describe '#not_including_column' do
+ it 'only matches constraints not including the given column' do
+ constraints_not_on_a = described_class.by_table_identifier(table_identifier).not_including_column('a').map(&:name)
+
+ expect(constraints_not_on_a).to contain_exactly("#{table_name}_pkey", "#{table_name}_referenced_id_fkey")
+ end
+ end
+end
diff --git a/spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb b/spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb
new file mode 100644
index 00000000000..df17d92bb0c
--- /dev/null
+++ b/spec/lib/gitlab/doorkeeper_secret_storing/secret/pbkdf2_sha512_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::DoorkeeperSecretStoring::Secret::Pbkdf2Sha512 do
+ describe '.transform_secret' do
+ let(:plaintext_secret) { 'CzOBzBfU9F-HvsqfTaTXF4ivuuxYZuv3BoAK4pnvmyw' }
+
+ it 'generates a PBKDF2+SHA512 hashed value in the correct format' do
+ expect(described_class.transform_secret(plaintext_secret))
+ .to eq("$pbkdf2-sha512$20000$$.c0G5XJVEew1TyeJk5TrkvB0VyOaTmDzPrsdNRED9vVeZlSyuG3G90F0ow23zUCiWKAVwmNnR/ceh.nJG3MdpQ") # rubocop:disable Layout/LineLength
+ end
+
+ context 'when hash_oauth_secrets is disabled' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: false)
+ end
+
+ it 'returns a plaintext secret' do
+ expect(described_class.transform_secret(plaintext_secret)).to eq(plaintext_secret)
+ end
+ end
+ end
+
+ describe 'STRETCHES' do
+ it 'is 20_000' do
+ expect(described_class::STRETCHES).to eq(20_000)
+ end
+ end
+
+ describe 'SALT' do
+ it 'is empty' do
+ expect(described_class::SALT).to be_empty
+ end
+ end
+
+ describe '.secret_matches?' do
+ it "match by hashing the input if the stored value is hashed" do
+ stub_feature_flags(hash_oauth_secrets: false)
+ plain_secret = 'plain_secret'
+ stored_value = '$pbkdf2-sha512$20000$$/BwQRdwSpL16xkQhstavh7nvA5avCP7.4n9LLKe9AupgJDeA7M5xOAvG3N3E5XbRyGWWBbbr.BsojPVWzd1Sqg' # rubocop:disable Layout/LineLength
+ expect(described_class.secret_matches?(plain_secret, stored_value)).to be true
+ end
+ end
+end
diff --git a/spec/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512_spec.rb b/spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb
index e953733c997..c73744cd481 100644
--- a/spec/lib/gitlab/doorkeeper_secret_storing/pbkdf2_sha512_spec.rb
+++ b/spec/lib/gitlab/doorkeeper_secret_storing/token/pbkdf2_sha512_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Gitlab::DoorkeeperSecretStoring::Pbkdf2Sha512 do
+RSpec.describe Gitlab::DoorkeeperSecretStoring::Token::Pbkdf2Sha512 do
describe '.transform_secret' do
let(:plaintext_token) { 'CzOBzBfU9F-HvsqfTaTXF4ivuuxYZuv3BoAK4pnvmyw' }
diff --git a/spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb b/spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb
index 9266b1b0585..4e9208be985 100644
--- a/spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/protected_branches_importer_spec.rb
@@ -15,7 +15,8 @@ RSpec.describe Gitlab::GithubImport::Importer::ProtectedBranchesImporter do
[
branch.new(name: 'main', protection: protection.new(enabled: false)),
- branch.new(name: 'staging', protection: protection.new(enabled: true))
+ branch.new(name: 'staging', protection: protection.new(enabled: true)),
+ branch.new(name: 'development', protection: nil) # when user has no admin right for this repo
]
end
@@ -154,12 +155,14 @@ RSpec.describe Gitlab::GithubImport::Importer::ProtectedBranchesImporter do
let(:protection_struct) { Struct.new(:enabled, keyword_init: true) }
let(:protected_branch) { branch_struct.new(name: 'main', protection: protection_struct.new(enabled: true)) }
let(:unprotected_branch) { branch_struct.new(name: 'staging', protection: protection_struct.new(enabled: false)) }
+ # when user has no admin rights on repo
+ let(:unknown_protection_branch) { branch_struct.new(name: 'development', protection: nil) }
let(:page_counter) { instance_double(Gitlab::GithubImport::PageCounter) }
before do
allow(client).to receive(:branches).with(project.import_source)
- .and_return([protected_branch, unprotected_branch])
+ .and_return([protected_branch, unprotected_branch, unknown_protection_branch])
allow(client).to receive(:branch_protection)
.with(project.import_source, protected_branch.name).once
.and_return(github_protection_rule)
diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml
index e429fc93d65..b9c9e02625a 100644
--- a/spec/lib/gitlab/import_export/all_models.yml
+++ b/spec/lib/gitlab/import_export/all_models.yml
@@ -831,3 +831,17 @@ resource_state_events:
- merge_request
- source_merge_request
- epic
+iteration:
+ - group
+ - iterations_cadence
+ - issues
+ - labels
+ - merge_requests
+resource_iteration_events:
+ - user
+ - issue
+ - merge_request
+ - iteration
+iterations_cadence:
+ - group
+ - iterations
diff --git a/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb b/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb
index 9f1b15aa049..4ee825c71b6 100644
--- a/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb
+++ b/spec/lib/gitlab/import_export/base/relation_object_saver_spec.rb
@@ -79,14 +79,14 @@ RSpec.describe Gitlab::ImportExport::Base::RelationObjectSaver do
let(:relation_definition) { { 'notes' => {} } }
it 'saves valid subrelations and logs invalid subrelation' do
- expect(relation_object.notes).to receive(:<<).and_call_original
+ expect(relation_object.notes).to receive(:<<).twice.and_call_original
expect(Gitlab::Import::Logger)
.to receive(:info)
.with(
message: '[Project/Group Import] Invalid subrelation',
project_id: project.id,
relation_key: 'issues',
- error_messages: "Noteable can't be blank and Project does not match noteable project"
+ error_messages: "Project does not match noteable project"
)
saver.execute
@@ -94,9 +94,28 @@ RSpec.describe Gitlab::ImportExport::Base::RelationObjectSaver do
issue = project.issues.last
import_failure = project.import_failures.last
+ expect(invalid_note.persisted?).to eq(false)
expect(issue.notes.count).to eq(5)
expect(import_failure.source).to eq('RelationObjectSaver#save!')
- expect(import_failure.exception_message).to eq("Noteable can't be blank and Project does not match noteable project")
+ expect(import_failure.exception_message).to eq('Project does not match noteable project')
+ end
+
+ context 'when invalid subrelation can still be persisted' do
+ let(:relation_key) { 'merge_requests' }
+ let(:relation_definition) { { 'approvals' => {} } }
+ let(:approval_1) { build(:approval, merge_request_id: nil, user: create(:user)) }
+ let(:approval_2) { build(:approval, merge_request_id: nil, user: create(:user)) }
+ let(:relation_object) { build(:merge_request, source_project: project, target_project: project, approvals: [approval_1, approval_2]) }
+
+ it 'saves the subrelation' do
+ expect(approval_1.valid?).to eq(false)
+ expect(Gitlab::Import::Logger).not_to receive(:info)
+
+ saver.execute
+
+ expect(project.merge_requests.first.approvals.count).to eq(2)
+ expect(project.merge_requests.first.approvals.first.persisted?).to eq(true)
+ end
end
context 'when importable is group' do
diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml
index 352255afc8d..e591cbd05a0 100644
--- a/spec/lib/gitlab/import_export/safe_model_attributes.yml
+++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml
@@ -930,3 +930,17 @@ ResourceStateEvent:
- source_commit
- close_after_error_tracking_resolve
- close_auto_resolve_prometheus_alert
+Iteration:
+ - created_at
+ - updated_at
+ - start_date
+ - due_date
+ - group_id
+ - iid
+ - description
+ResourceIterationEvent:
+ - user_id
+ - created_at
+ - action
+Iterations::Cadence:
+ - title
diff --git a/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb b/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb
index e228a0a7d72..80ae5c6fd21 100644
--- a/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb
+++ b/spec/lib/gitlab/usage/metrics/instrumentations/redis_metric_spec.rb
@@ -44,4 +44,18 @@ RSpec.describe Gitlab::Usage::Metrics::Instrumentations::RedisMetric, :clean_git
end
end
end
+
+ context "with usage prefix disabled" do
+ let(:expected_value) { 3 }
+
+ before do
+ 3.times do
+ Gitlab::UsageDataCounters::WebIdeCounter.increment_merge_requests_count
+ end
+ end
+
+ it_behaves_like 'a correct instrumented metric value', {
+ options: { event: 'merge_requests_count', prefix: 'web_ide', include_usage_prefix: false }
+ }
+ end
end
diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb
index 7226c9db15d..181351222c1 100644
--- a/spec/models/ci/runner_spec.rb
+++ b/spec/models/ci/runner_spec.rb
@@ -266,13 +266,13 @@ RSpec.describe Ci::Runner do
end
shared_examples '.belonging_to_parent_group_of_project' do
- let!(:group1) { create(:group) }
- let!(:project1) { create(:project, group: group1) }
- let!(:runner1) { create(:ci_runner, :group, groups: [group1]) }
+ let_it_be(:group1) { create(:group) }
+ let_it_be(:project1) { create(:project, group: group1) }
+ let_it_be(:runner1) { create(:ci_runner, :group, groups: [group1]) }
- let!(:group2) { create(:group) }
- let!(:project2) { create(:project, group: group2) }
- let!(:runner2) { create(:ci_runner, :group, groups: [group2]) }
+ let_it_be(:group2) { create(:group) }
+ let_it_be(:project2) { create(:project, group: group2) }
+ let_it_be(:runner2) { create(:ci_runner, :group, groups: [group2]) }
let(:project_id) { project1.id }
@@ -495,8 +495,8 @@ RSpec.describe Ci::Runner do
describe '.active' do
subject { described_class.active(active_value) }
- let!(:runner1) { create(:ci_runner, :instance, active: false) }
- let!(:runner2) { create(:ci_runner, :instance) }
+ let_it_be(:runner1) { create(:ci_runner, :instance, active: false) }
+ let_it_be(:runner2) { create(:ci_runner, :instance) }
context 'with active_value set to false' do
let(:active_value) { false }
@@ -544,7 +544,7 @@ RSpec.describe Ci::Runner do
end
describe '#stale?', :clean_gitlab_redis_cache do
- let(:runner) { create(:ci_runner, :instance) }
+ let(:runner) { build(:ci_runner, :instance) }
subject { runner.stale? }
@@ -619,7 +619,7 @@ RSpec.describe Ci::Runner do
end
describe '#online?', :clean_gitlab_redis_cache do
- let(:runner) { create(:ci_runner, :instance) }
+ let(:runner) { build(:ci_runner, :instance) }
subject { runner.online? }
@@ -1162,13 +1162,13 @@ RSpec.describe Ci::Runner do
end
describe '.assignable_for' do
- let(:project) { create(:project) }
- let(:group) { create(:group) }
- let(:another_project) { create(:project) }
- let!(:unlocked_project_runner) { create(:ci_runner, :project, projects: [project]) }
- let!(:locked_project_runner) { create(:ci_runner, :project, locked: true, projects: [project]) }
- let!(:group_runner) { create(:ci_runner, :group, groups: [group]) }
- let!(:instance_runner) { create(:ci_runner, :instance) }
+ let_it_be(:project) { create(:project) }
+ let_it_be(:group) { create(:group) }
+ let_it_be(:another_project) { create(:project) }
+ let_it_be(:unlocked_project_runner) { create(:ci_runner, :project, projects: [project]) }
+ let_it_be(:locked_project_runner) { create(:ci_runner, :project, locked: true, projects: [project]) }
+ let_it_be(:group_runner) { create(:ci_runner, :group, groups: [group]) }
+ let_it_be(:instance_runner) { create(:ci_runner, :instance) }
context 'with already assigned project' do
subject { described_class.assignable_for(project) }
@@ -1186,78 +1186,74 @@ RSpec.describe Ci::Runner do
end
end
- describe '#owner_project' do
+ context 'Project-related queries' do
let_it_be(:project1) { create(:project) }
let_it_be(:project2) { create(:project) }
- subject(:owner_project) { project_runner.owner_project }
+ describe '#owner_project' do
+ subject(:owner_project) { project_runner.owner_project }
- context 'with project1 as first project associated with runner' do
- let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project1, project2]) }
+ context 'with project1 as first project associated with runner' do
+ let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project1, project2]) }
- it { is_expected.to eq project1 }
- end
+ it { is_expected.to eq project1 }
+ end
- context 'with project2 as first project associated with runner' do
- let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project2, project1]) }
+ context 'with project2 as first project associated with runner' do
+ let_it_be(:project_runner) { create(:ci_runner, :project, projects: [project2, project1]) }
- it { is_expected.to eq project2 }
+ it { is_expected.to eq project2 }
+ end
end
- end
- describe "belongs_to_one_project?" do
- it "returns false if there are two projects runner assigned to" do
- project1 = create(:project)
- project2 = create(:project)
- runner = create(:ci_runner, :project, projects: [project1, project2])
+ describe "belongs_to_one_project?" do
+ it "returns false if there are two projects runner is assigned to" do
+ runner = create(:ci_runner, :project, projects: [project1, project2])
- expect(runner.belongs_to_one_project?).to be_falsey
- end
+ expect(runner.belongs_to_one_project?).to be_falsey
+ end
- it "returns true" do
- project = create(:project)
- runner = create(:ci_runner, :project, projects: [project])
+ it "returns true if there is only one project runner is assigned to" do
+ runner = create(:ci_runner, :project, projects: [project1])
- expect(runner.belongs_to_one_project?).to be_truthy
+ expect(runner.belongs_to_one_project?).to be_truthy
+ end
end
- end
- describe '#belongs_to_more_than_one_project?' do
- context 'project runner' do
- let(:project1) { create(:project) }
- let(:project2) { create(:project) }
+ describe '#belongs_to_more_than_one_project?' do
+ context 'project runner' do
+ context 'two projects assigned to runner' do
+ let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
- context 'two projects assigned to runner' do
- let(:runner) { create(:ci_runner, :project, projects: [project1, project2]) }
+ it 'returns true' do
+ expect(runner.belongs_to_more_than_one_project?).to be_truthy
+ end
+ end
+
+ context 'one project assigned to runner' do
+ let(:runner) { create(:ci_runner, :project, projects: [project1]) }
- it 'returns true' do
- expect(runner.belongs_to_more_than_one_project?).to be_truthy
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
end
end
- context 'one project assigned to runner' do
- let(:runner) { create(:ci_runner, :project, projects: [project1]) }
+ context 'group runner' do
+ let(:group) { create(:group) }
+ let(:runner) { create(:ci_runner, :group, groups: [group]) }
it 'returns false' do
expect(runner.belongs_to_more_than_one_project?).to be_falsey
end
end
- end
-
- context 'group runner' do
- let(:group) { create(:group) }
- let(:runner) { create(:ci_runner, :group, groups: [group]) }
-
- it 'returns false' do
- expect(runner.belongs_to_more_than_one_project?).to be_falsey
- end
- end
- context 'shared runner' do
- let(:runner) { create(:ci_runner, :instance) }
+ context 'shared runner' do
+ let(:runner) { create(:ci_runner, :instance) }
- it 'returns false' do
- expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ it 'returns false' do
+ expect(runner.belongs_to_more_than_one_project?).to be_falsey
+ end
end
end
end
@@ -1318,7 +1314,7 @@ RSpec.describe Ci::Runner do
end
describe '.search' do
- let(:runner) { create(:ci_runner, token: '123abc', description: 'test runner') }
+ let_it_be(:runner) { create(:ci_runner, token: '123abc', description: 'test runner') }
it 'returns runners with a matching token' do
expect(described_class.search(runner.token)).to eq([runner])
@@ -1346,8 +1342,9 @@ RSpec.describe Ci::Runner do
end
describe '#pick_build!' do
+ let_it_be(:runner) { create(:ci_runner) }
+
let(:build) { create(:ci_build) }
- let(:runner) { create(:ci_runner) }
context 'runner can pick the build' do
it 'calls #tick_runner_queue' do
@@ -1384,26 +1381,26 @@ RSpec.describe Ci::Runner do
end
describe '.order_by' do
+ let_it_be(:runner1) { create(:ci_runner, created_at: 1.year.ago, contacted_at: 1.year.ago) }
+ let_it_be(:runner2) { create(:ci_runner, created_at: 1.month.ago, contacted_at: 1.month.ago) }
+
+ before do
+ runner1.update!(token_expires_at: 1.year.from_now)
+ end
+
it 'supports ordering by the contact date' do
- runner1 = create(:ci_runner, contacted_at: 1.year.ago)
- runner2 = create(:ci_runner, contacted_at: 1.month.ago)
runners = described_class.order_by('contacted_asc')
expect(runners).to eq([runner1, runner2])
end
it 'supports ordering by the creation date' do
- runner1 = create(:ci_runner, created_at: 1.year.ago)
- runner2 = create(:ci_runner, created_at: 1.month.ago)
runners = described_class.order_by('created_asc')
expect(runners).to eq([runner2, runner1])
end
it 'supports ordering by the token expiration' do
- runner1 = create(:ci_runner)
- runner1.update!(token_expires_at: 1.year.from_now)
- runner2 = create(:ci_runner)
runner3 = create(:ci_runner)
runner3.update!(token_expires_at: 1.month.from_now)
diff --git a/spec/models/jira_connect_installation_spec.rb b/spec/models/jira_connect_installation_spec.rb
index 3d1095845aa..9c1f7c678a9 100644
--- a/spec/models/jira_connect_installation_spec.rb
+++ b/spec/models/jira_connect_installation_spec.rb
@@ -45,4 +45,30 @@ RSpec.describe JiraConnectInstallation do
expect(subject).to contain_exactly(subscription.installation)
end
end
+
+ describe '#oauth_authorization_url' do
+ let_it_be(:installation) { create(:jira_connect_installation) }
+
+ subject { installation.oauth_authorization_url }
+
+ before do
+ allow(Gitlab).to receive_message_chain('config.gitlab.url') { 'http://test.host' }
+ end
+
+ it { is_expected.to eq('http://test.host') }
+
+ context 'with instance_url' do
+ let_it_be(:installation) { create(:jira_connect_installation, instance_url: 'https://gitlab.example.com') }
+
+ it { is_expected.to eq('https://gitlab.example.com') }
+
+ context 'and jira_connect_oauth_self_managed feature is disabled' do
+ before do
+ stub_feature_flags(jira_connect_oauth_self_managed: false)
+ end
+
+ it { is_expected.to eq('http://test.host') }
+ end
+ end
+ end
end
diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb
index 5b032c53352..fefd9f71408 100644
--- a/spec/policies/project_policy_spec.rb
+++ b/spec/policies/project_policy_spec.rb
@@ -2729,6 +2729,41 @@ RSpec.describe ProjectPolicy do
end
end
+ describe 'read_milestone' do
+ context 'when project is public' do
+ let(:project) { public_project_in_group }
+
+ context 'and issues and merge requests are private' do
+ before do
+ project.project_feature.update!(
+ issues_access_level: ProjectFeature::PRIVATE,
+ merge_requests_access_level: ProjectFeature::PRIVATE
+ )
+ end
+
+ context 'when user is an inherited member from the group' do
+ context 'and user is a guest' do
+ let(:current_user) { inherited_guest }
+
+ it { is_expected.to be_allowed(:read_milestone) }
+ end
+
+ context 'and user is a reporter' do
+ let(:current_user) { inherited_reporter }
+
+ it { is_expected.to be_allowed(:read_milestone) }
+ end
+
+ context 'and user is a developer' do
+ let(:current_user) { inherited_developer }
+
+ it { is_expected.to be_allowed(:read_milestone) }
+ end
+ end
+ end
+ end
+ end
+
private
def project_subject(project_type)
diff --git a/spec/services/ci/create_pipeline_service/partitioning_spec.rb b/spec/services/ci/create_pipeline_service/partitioning_spec.rb
index cb4f32f591f..43fbb74ede4 100644
--- a/spec/services/ci/create_pipeline_service/partitioning_spec.rb
+++ b/spec/services/ci/create_pipeline_service/partitioning_spec.rb
@@ -96,6 +96,47 @@ RSpec.describe Ci::CreatePipelineService, :yaml_processor_feature_flag_corectnes
end
end
+ context 'with parent child pipelines' do
+ before do
+ allow(Ci::Pipeline)
+ .to receive(:current_partition_value)
+ .and_return(current_partition_id, 301, 302)
+
+ allow_next_found_instance_of(Ci::Bridge) do |bridge|
+ allow(bridge).to receive(:yaml_for_downstream).and_return(child_config)
+ end
+ end
+
+ let(:config) do
+ <<-YAML
+ test:
+ trigger:
+ include: child.yml
+ YAML
+ end
+
+ let(:child_config) do
+ <<-YAML
+ test:
+ script: make test
+ YAML
+ end
+
+ it 'assigns partition values to child pipelines', :aggregate_failures, :sidekiq_inline do
+ expect(pipeline).to be_created_successfully
+ expect(pipeline.child_pipelines).to all be_created_successfully
+
+ child_partition_ids = pipeline.child_pipelines.map(&:partition_id).uniq
+ child_jobs = CommitStatus.where(commit_id: pipeline.child_pipelines)
+
+ expect(pipeline.partition_id).to eq(current_partition_id)
+ expect(child_partition_ids).to eq([current_partition_id])
+
+ expect(child_jobs).to all be_a(Ci::Build)
+ expect(child_jobs.pluck(:partition_id).uniq).to eq([current_partition_id])
+ end
+ end
+
def find_metadata(name)
pipeline
.processables
diff --git a/spec/services/issues/relative_position_rebalancing_service_spec.rb b/spec/services/issues/relative_position_rebalancing_service_spec.rb
index 20064bd7e4b..37a94e1d6a2 100644
--- a/spec/services/issues/relative_position_rebalancing_service_spec.rb
+++ b/spec/services/issues/relative_position_rebalancing_service_spec.rb
@@ -72,22 +72,8 @@ RSpec.describe Issues::RelativePositionRebalancingService, :clean_gitlab_redis_s
end.not_to change { issues_in_position_order.map(&:id) }
end
- it 'does nothing if the feature flag is disabled' do
- stub_feature_flags(rebalance_issues: false)
- issue = project.issues.first
- issue.project
- issue.project.group
- old_pos = issue.relative_position
-
- # fetching root namespace in the initializer triggers 2 queries:
- # for fetching a random project from collection and fetching the root namespace.
- expect { service.execute }.not_to exceed_query_limit(2)
- expect(old_pos).to eq(issue.reload.relative_position)
- end
-
it 'acts if the flag is enabled for the root namespace' do
issue = create(:issue, project: project, author: user, relative_position: max_pos)
- stub_feature_flags(rebalance_issues: project.root_namespace)
expect { service.execute }.to change { issue.reload.relative_position }
end
@@ -95,7 +81,6 @@ RSpec.describe Issues::RelativePositionRebalancingService, :clean_gitlab_redis_s
it 'acts if the flag is enabled for the group' do
issue = create(:issue, project: project, author: user, relative_position: max_pos)
project.update!(group: create(:group))
- stub_feature_flags(rebalance_issues: issue.project.group)
expect { service.execute }.to change { issue.reload.relative_position }
end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 68d145259b5..8a2e9ed74f7 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -304,38 +304,6 @@ RSpec.describe Issues::UpdateService, :mailer do
end
end
- it 'does not rebalance even if needed if the flag is disabled' do
- stub_feature_flags(rebalance_issues: false)
-
- range = described_class::NO_REBALANCING_NEEDED
- issue1 = create(:issue, project: project, relative_position: range.first - 100)
- issue2 = create(:issue, project: project, relative_position: range.first)
- issue.update!(relative_position: RelativePositioning::START_POSITION)
-
- opts[:move_between_ids] = [issue1.id, issue2.id]
-
- expect(Issues::RebalancingWorker).not_to receive(:perform_async)
-
- update_issue(opts)
- expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
- end
-
- it 'rebalances if needed if the flag is enabled for the project' do
- stub_feature_flags(rebalance_issues: project)
-
- range = described_class::NO_REBALANCING_NEEDED
- issue1 = create(:issue, project: project, relative_position: range.first - 100)
- issue2 = create(:issue, project: project, relative_position: range.first)
- issue.update!(relative_position: RelativePositioning::START_POSITION)
-
- opts[:move_between_ids] = [issue1.id, issue2.id]
-
- expect(Issues::RebalancingWorker).to receive(:perform_async).with(nil, nil, project.root_namespace.id)
-
- update_issue(opts)
- expect(issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
- end
-
it 'rebalances if needed on the left' do
range = described_class::NO_REBALANCING_NEEDED
issue1 = create(:issue, project: project, relative_position: range.first - 100)
diff --git a/spec/support/helpers/usage_data_helpers.rb b/spec/support/helpers/usage_data_helpers.rb
index 7b60561432c..d8c9c5b7556 100644
--- a/spec/support/helpers/usage_data_helpers.rb
+++ b/spec/support/helpers/usage_data_helpers.rb
@@ -11,10 +11,6 @@ module UsageDataHelpers
wiki_pages_create
wiki_pages_update
wiki_pages_delete
- web_ide_views
- web_ide_commits
- web_ide_merge_requests
- web_ide_previews
navbar_searches
cycle_analytics_views
productivity_analytics_views
diff --git a/spec/support/shared_contexts/policies/project_policy_shared_context.rb b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
index 1d4731d9b39..fc7255a4a20 100644
--- a/spec/support/shared_contexts/policies/project_policy_shared_context.rb
+++ b/spec/support/shared_contexts/policies/project_policy_shared_context.rb
@@ -6,13 +6,19 @@ RSpec.shared_context 'ProjectPolicy context' do
let_it_be(:reporter) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:maintainer) { create(:user) }
+ let_it_be(:inherited_guest) { create(:user) }
+ let_it_be(:inherited_reporter) { create(:user) }
+ let_it_be(:inherited_developer) { create(:user) }
+ let_it_be(:inherited_maintainer) { create(:user) }
let_it_be(:owner) { create(:user) }
let_it_be(:admin) { create(:admin) }
let_it_be(:non_member) { create(:user) }
+ let_it_be_with_refind(:group) { create(:group, :public) }
let_it_be_with_refind(:private_project) { create(:project, :private, namespace: owner.namespace) }
let_it_be_with_refind(:internal_project) { create(:project, :internal, namespace: owner.namespace) }
let_it_be_with_refind(:public_project) { create(:project, :public, namespace: owner.namespace) }
- let_it_be_with_refind(:public_project_in_group) { create(:project, :public, namespace: create(:group, :public)) }
+ let_it_be_with_refind(:public_project_in_group) { create(:project, :public, namespace: group) }
+ let_it_be_with_refind(:private_project_in_group) { create(:project, :private, namespace: group) }
let(:base_guest_permissions) do
%i[
@@ -95,6 +101,11 @@ RSpec.shared_context 'ProjectPolicy context' do
let(:owner_permissions) { base_owner_permissions + additional_owner_permissions }
before_all do
+ group.add_guest(inherited_guest)
+ group.add_reporter(inherited_reporter)
+ group.add_developer(inherited_developer)
+ group.add_maintainer(inherited_maintainer)
+
[private_project, internal_project, public_project, public_project_in_group].each do |project|
project.add_guest(guest)
project.add_reporter(reporter)
diff --git a/spec/support/shared_examples/features/manage_applications_shared_examples.rb b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
index 442264e7ae4..b59f3f1e27b 100644
--- a/spec/support/shared_examples/features/manage_applications_shared_examples.rb
+++ b/spec/support/shared_examples/features/manage_applications_shared_examples.rb
@@ -5,39 +5,87 @@ RSpec.shared_examples 'manage applications' do
let_it_be(:application_name_changed) { "#{application_name} changed" }
let_it_be(:application_redirect_uri) { 'https://foo.bar' }
- it 'allows user to manage applications', :js do
- visit new_application_path
+ context 'when hash_oauth_secrets flag set' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: true)
+ end
+
+ it 'allows user to manage applications', :js do
+ visit new_application_path
- expect(page).to have_content 'Add new application'
+ expect(page).to have_content 'Add new application'
- fill_in :doorkeeper_application_name, with: application_name
- fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri
- check :doorkeeper_application_scopes_read_user
- click_on 'Save application'
+ fill_in :doorkeeper_application_name, with: application_name
+ fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri
+ check :doorkeeper_application_scopes_read_user
+ click_on 'Save application'
- validate_application(application_name, 'Yes')
- expect(page).to have_link('Continue', href: index_path)
+ validate_application(application_name, 'Yes')
+ expect(page).to have_content _('This is the only time the secret is accessible. Copy the secret and store it securely')
+ expect(page).to have_link('Continue', href: index_path)
- application = Doorkeeper::Application.find_by(name: application_name)
- expect(page).to have_css("button[title=\"Copy secret\"][data-clipboard-text=\"#{application.secret}\"]", text: 'Copy')
+ expect(page).to have_css("button[title=\"Copy secret\"]", text: 'Copy')
- click_on 'Edit'
+ click_on 'Edit'
- application_name_changed = "#{application_name} changed"
+ application_name_changed = "#{application_name} changed"
- fill_in :doorkeeper_application_name, with: application_name_changed
- uncheck :doorkeeper_application_confidential
- click_on 'Save application'
+ fill_in :doorkeeper_application_name, with: application_name_changed
+ uncheck :doorkeeper_application_confidential
+ click_on 'Save application'
+
+ validate_application(application_name_changed, 'No')
+ expect(page).not_to have_link('Continue')
+ expect(page).to have_content _('The secret is only available when you first create the application')
+
+ visit_applications_path
+
+ page.within '.oauth-applications' do
+ click_on 'Destroy'
+ end
+ expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
+ end
+ end
+
+ context 'when hash_oauth_secrets flag not set' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: false)
+ end
+
+ it 'allows user to manage applications', :js do
+ visit new_application_path
+
+ expect(page).to have_content 'Add new application'
+
+ fill_in :doorkeeper_application_name, with: application_name
+ fill_in :doorkeeper_application_redirect_uri, with: application_redirect_uri
+ check :doorkeeper_application_scopes_read_user
+ click_on 'Save application'
+
+ validate_application(application_name, 'Yes')
+ expect(page).to have_link('Continue', href: index_path)
+
+ application = Doorkeeper::Application.find_by(name: application_name)
+ expect(page).to have_css("button[title=\"Copy secret\"][data-clipboard-text=\"#{application.secret}\"]", text: 'Copy')
+
+ click_on 'Edit'
+
+ application_name_changed = "#{application_name} changed"
+
+ fill_in :doorkeeper_application_name, with: application_name_changed
+ uncheck :doorkeeper_application_confidential
+ click_on 'Save application'
- validate_application(application_name_changed, 'No')
- expect(page).not_to have_link('Continue')
+ validate_application(application_name_changed, 'No')
+ expect(page).not_to have_link('Continue')
- visit_applications_path
+ visit_applications_path
- page.within '.oauth-applications' do
- click_on 'Destroy'
+ page.within '.oauth-applications' do
+ click_on 'Destroy'
+ end
+ expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
end
- expect(page.find('.oauth-applications')).not_to have_content 'test_changed'
end
context 'when scopes are blank' do
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 c4083df47e2..cfcc3615e13 100644
--- a/spec/support/shared_examples/policies/project_policy_shared_examples.rb
+++ b/spec/support/shared_examples/policies/project_policy_shared_examples.rb
@@ -107,70 +107,88 @@ RSpec.shared_examples 'deploy token does not get confused with user' do
end
RSpec.shared_examples 'project policies as guest' do
- context 'abilities for public projects' do
- let(:project) { public_project }
- let(:current_user) { guest }
-
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(*public_permissions)
- expect_disallowed(*developer_permissions)
- expect_disallowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
+ let(:reporter_public_build_permissions) do
+ reporter_permissions - [:read_build, :read_pipeline]
end
- context 'abilities for non-public projects' do
- let(:project) { private_project }
- let(:current_user) { guest }
+ context 'as a direct project member' do
+ context 'abilities for public projects' do
+ let(:project) { public_project }
+ let(:current_user) { guest }
- let(:reporter_public_build_permissions) do
- reporter_permissions - [:read_build, :read_pipeline]
+ specify do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*public_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
end
- it do
- expect_allowed(*guest_permissions)
- expect_disallowed(*reporter_public_build_permissions)
- expect_disallowed(*team_member_reporter_permissions)
- expect_disallowed(*developer_permissions)
- expect_disallowed(*maintainer_permissions)
- expect_disallowed(*owner_permissions)
- end
+ context 'abilities for non-public projects' do
+ let(:project) { private_project }
+ let(:current_user) { guest }
- it_behaves_like 'deploy token does not get confused with user' do
- let(:user_id) { guest.id }
- end
+ specify do
+ expect_allowed(*guest_permissions)
+ expect_disallowed(*reporter_public_build_permissions)
+ expect_disallowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
- it_behaves_like 'archived project policies' do
- let(:regular_abilities) { guest_permissions }
- end
+ it_behaves_like 'deploy token does not get confused with user' do
+ let(:user_id) { guest.id }
+ end
- context 'public builds enabled' do
- it do
- expect_allowed(*guest_permissions)
- expect_allowed(:read_build, :read_pipeline)
+ it_behaves_like 'archived project policies' do
+ let(:regular_abilities) { guest_permissions }
end
- end
- context 'when public builds disabled' do
- before do
- project.update!(public_builds: false)
+ context 'public builds enabled' do
+ specify do
+ expect_allowed(*guest_permissions)
+ expect_allowed(:read_build, :read_pipeline)
+ end
end
- it do
- expect_allowed(*guest_permissions)
- expect_disallowed(:read_build, :read_pipeline)
+ context 'when public builds disabled' do
+ before do
+ project.update!(public_builds: false)
+ end
+
+ specify do
+ expect_allowed(*guest_permissions)
+ expect_disallowed(:read_build, :read_pipeline)
+ end
end
- end
- context 'when builds are disabled' do
- before do
- project.project_feature.update!(builds_access_level: ProjectFeature::DISABLED)
+ context 'when builds are disabled' do
+ before do
+ project.project_feature.update!(builds_access_level: ProjectFeature::DISABLED)
+ end
+
+ specify do
+ expect_disallowed(:read_build)
+ expect_allowed(:read_pipeline)
+ end
end
+ end
+ end
- it do
- expect_disallowed(:read_build)
- expect_allowed(:read_pipeline)
+ context 'as an inherited member from the group' do
+ context 'abilities for private projects' do
+ let(:project) { private_project_in_group }
+ let(:current_user) { inherited_guest }
+
+ specify do
+ expect_allowed(*guest_permissions)
+ expect_disallowed(*reporter_public_build_permissions)
+ expect_disallowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
end
end
end
@@ -181,7 +199,7 @@ RSpec.shared_examples 'project policies as reporter' do
let(:project) { private_project }
let(:current_user) { reporter }
- it do
+ specify do
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*team_member_reporter_permissions)
@@ -198,6 +216,22 @@ RSpec.shared_examples 'project policies as reporter' do
let(:regular_abilities) { reporter_permissions }
end
end
+
+ context 'as an inherited member from the group' do
+ context 'abilities for private projects' do
+ let(:project) { private_project_in_group }
+ let(:current_user) { inherited_reporter }
+
+ specify do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_disallowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+ end
+ end
end
RSpec.shared_examples 'project policies as developer' do
@@ -205,7 +239,7 @@ RSpec.shared_examples 'project policies as developer' do
let(:project) { private_project }
let(:current_user) { developer }
- it do
+ specify do
expect_allowed(*guest_permissions)
expect_allowed(*reporter_permissions)
expect_allowed(*team_member_reporter_permissions)
@@ -222,6 +256,22 @@ RSpec.shared_examples 'project policies as developer' do
let(:regular_abilities) { developer_permissions }
end
end
+
+ context 'as an inherited member from the group' do
+ context 'abilities for private projects' do
+ let(:project) { private_project_in_group }
+ let(:current_user) { inherited_developer }
+
+ specify do
+ expect_allowed(*guest_permissions)
+ expect_allowed(*reporter_permissions)
+ expect_allowed(*team_member_reporter_permissions)
+ expect_allowed(*developer_permissions)
+ expect_disallowed(*maintainer_permissions)
+ expect_disallowed(*owner_permissions)
+ end
+ end
+ end
end
RSpec.shared_examples 'project policies as maintainer' do
diff --git a/spec/support/shared_examples/requests/applications_controller_shared_examples.rb b/spec/support/shared_examples/requests/applications_controller_shared_examples.rb
index 8f852d42c2c..642930dd982 100644
--- a/spec/support/shared_examples/requests/applications_controller_shared_examples.rb
+++ b/spec/support/shared_examples/requests/applications_controller_shared_examples.rb
@@ -11,6 +11,7 @@ RSpec.shared_examples 'applications controller - GET #show' do
context 'when application is viewed after being created' do
before do
create_application
+ stub_feature_flags(hash_oauth_secrets: false)
end
it 'sets `@created` instance variable to `true`' do
@@ -21,6 +22,10 @@ RSpec.shared_examples 'applications controller - GET #show' do
end
context 'when application is reviewed' do
+ before do
+ stub_feature_flags(hash_oauth_secrets: false)
+ end
+
it 'sets `@created` instance variable to `false`' do
get show_path
@@ -32,6 +37,7 @@ end
RSpec.shared_examples 'applications controller - POST #create' do
it "sets `#{OauthApplications::CREATED_SESSION_KEY}` session key to `true`" do
+ stub_feature_flags(hash_oauth_secrets: false)
create_application
expect(session[OauthApplications::CREATED_SESSION_KEY]).to eq(true)