diff options
82 files changed, 1280 insertions, 440 deletions
diff --git a/.gitlab/ci/preflight.gitlab-ci.yml b/.gitlab/ci/preflight.gitlab-ci.yml index 05b05fde53b..464eac56a95 100644 --- a/.gitlab/ci/preflight.gitlab-ci.yml +++ b/.gitlab/ci/preflight.gitlab-ci.yml @@ -1,14 +1,59 @@ +.preflight-job-base: + stage: preflight + extends: + - .default-retry + needs: [] + +.qa-preflight-job: + image: ${REGISTRY_HOST}/${REGISTRY_GROUP}/gitlab-build-images/debian-bullseye-ruby-${RUBY_VERSION}:bundler-2.3-chrome-${CHROME_VERSION}-docker-${DOCKER_VERSION} + extends: + - .preflight-job-base + - .qa-cache + variables: + USE_BUNDLE_INSTALL: "false" + SETUP_DB: "false" + before_script: + - !reference [.default-before_script, before_script] + - cd qa && bundle install + rails-production-environment: extends: + - .preflight-job-base - .default-before_script - .production - .ruby-cache - .setup:rules:rails-production-environment - .use-pg12 - stage: preflight variables: BUNDLE_WITHOUT: "development:test" BUNDLE_WITH: "production" needs: [] script: - bundle exec rails runner --environment=production 'puts Rails.env' + +no-ee-check: + extends: + - .preflight-job-base + - .setup:rules:no-ee-check + script: + - scripts/no-dir-check ee + +no-jh-check: + extends: + - .preflight-job-base + - .setup:rules:no-jh-check + script: + - scripts/no-dir-check jh + +qa:selectors: + extends: + - .qa-preflight-job + - .qa:rules:ee-and-foss + script: + - bundle exec bin/qa Test::Sanity::Selectors + +qa:selectors-as-if-foss: + extends: + - qa:selectors + - .qa:rules:as-if-foss + - .as-if-foss diff --git a/.gitlab/ci/qa.gitlab-ci.yml b/.gitlab/ci/qa.gitlab-ci.yml index 95ae215d134..97f8820a511 100644 --- a/.gitlab/ci/qa.gitlab-ci.yml +++ b/.gitlab/ci/qa.gitlab-ci.yml @@ -25,13 +25,6 @@ qa:internal-as-if-foss: - .qa:rules:internal-as-if-foss - .as-if-foss -qa:selectors: - extends: - - .qa-job-base - - .qa:rules:ee-and-foss - script: - - bundle exec bin/qa Test::Sanity::Selectors - qa:master-auto-quarantine-dequarantine: extends: - .qa-job-base @@ -50,12 +43,6 @@ qa:nightly-auto-quarantine-dequarantine: - bundle exec confiner -r .confiner/nightly.yml allow_failure: true -qa:selectors-as-if-foss: - extends: - - qa:selectors - - .qa:rules:as-if-foss - - .as-if-foss - qa:update-qa-cache: extends: - .qa-job-base diff --git a/.gitlab/ci/setup.gitlab-ci.yml b/.gitlab/ci/setup.gitlab-ci.yml index 298d5c4ae08..76c7af2753e 100644 --- a/.gitlab/ci/setup.gitlab-ci.yml +++ b/.gitlab/ci/setup.gitlab-ci.yml @@ -51,22 +51,6 @@ gitlab_git_test: script: - spec/support/prepare-gitlab-git-test-for-commit --check-for-changes -no-ee-check: - extends: - - .predictive-job - - .setup:rules:no-ee-check - stage: test - script: - - scripts/no-dir-check ee - -no-jh-check: - extends: - - .predictive-job - - .setup:rules:no-jh-check - stage: test - script: - - scripts/no-dir-check jh - verify-ruby-3.0: extends: - .absolutely-predictive-job diff --git a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue index c15cf492e06..ad727f01bab 100644 --- a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue +++ b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue @@ -459,12 +459,20 @@ export default { @sort="handleSort" > <template #nav-actions> - <gl-button :href="rssPath" icon="rss"> - {{ $options.i18n.rssLabel }} - </gl-button> - <gl-button :href="calendarPath" icon="calendar"> - {{ $options.i18n.calendarLabel }} - </gl-button> + <gl-button + v-gl-tooltip + :href="rssPath" + icon="rss" + :title="$options.i18n.rssLabel" + class="has-tooltip btn-icon" + /> + <gl-button + v-gl-tooltip + :href="calendarPath" + icon="calendar" + :title="$options.i18n.calendarLabel" + class="has-tooltip btn-icon" + /> </template> <template #timeframe="{ issuable = {} }"> diff --git a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue index 96477b9f476..7e6654140a9 100644 --- a/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue +++ b/app/assets/javascripts/pages/admin/application_settings/general/components/signup_form.vue @@ -207,6 +207,10 @@ export default { emailConfirmationSettingsOffHelpText: s__( 'ApplicationSettings|New users can sign up without confirming their email address.', ), + emailConfirmationSettingsSoftLabel: s__('ApplicationSettings|Soft'), + emailConfirmationSettingsSoftHelpText: s__( + 'ApplicationSettings|Send a confirmation email during sign up. New users can log in immediately, but must confirm their email within three days.', + ), emailConfirmationSettingsHardLabel: s__('ApplicationSettings|Hard'), emailConfirmationSettingsHardHelpText: s__( 'ApplicationSettings|Send a confirmation email during sign up. New users must confirm their email address before they can log in.', @@ -286,16 +290,23 @@ export default { v-model="form.emailConfirmationSetting" name="application_setting[email_confirmation_setting]" > - <gl-form-radio value="hard"> - {{ $options.i18n.emailConfirmationSettingsHardLabel }} - - <template #help> {{ $options.i18n.emailConfirmationSettingsHardHelpText }} </template> - </gl-form-radio> <gl-form-radio value="off"> {{ $options.i18n.emailConfirmationSettingsOffLabel }} <template #help> {{ $options.i18n.emailConfirmationSettingsOffHelpText }} </template> </gl-form-radio> + + <gl-form-radio value="soft"> + {{ $options.i18n.emailConfirmationSettingsSoftLabel }} + + <template #help> {{ $options.i18n.emailConfirmationSettingsSoftHelpText }} </template> + </gl-form-radio> + + <gl-form-radio value="hard"> + {{ $options.i18n.emailConfirmationSettingsHardLabel }} + + <template #help> {{ $options.i18n.emailConfirmationSettingsHardHelpText }} </template> + </gl-form-radio> </gl-form-radio-group> </gl-form-group> diff --git a/app/assets/javascripts/sentry/constants.js b/app/assets/javascripts/sentry/legacy_constants.js index 6bf1e2d3740..d04011dab2f 100644 --- a/app/assets/javascripts/sentry/constants.js +++ b/app/assets/javascripts/sentry/legacy_constants.js @@ -1,6 +1,6 @@ import { __ } from '~/locale'; -// TODO: Remove in favor of https://gitlab.com/gitlab-org/gitlab/issues/35144 +// https://docs.sentry.io/platforms/javascript/configuration/filtering/#decluttering-sentry export const IGNORE_ERRORS = [ // Random plugins/extensions 'top.GLOBALS', diff --git a/app/assets/javascripts/sentry/legacy_sentry_config.js b/app/assets/javascripts/sentry/legacy_sentry_config.js index 50a943886db..ae9ae327544 100644 --- a/app/assets/javascripts/sentry/legacy_sentry_config.js +++ b/app/assets/javascripts/sentry/legacy_sentry_config.js @@ -1,7 +1,7 @@ import * as Sentry5 from 'sentrybrowser5'; import $ from 'jquery'; import { __ } from '~/locale'; -import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from './constants'; +import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from './legacy_constants'; const SentryConfig = { IGNORE_ERRORS, diff --git a/app/assets/javascripts/sentry/sentry_config.js b/app/assets/javascripts/sentry/sentry_config.js index ed8a55b7d44..80f087691f4 100644 --- a/app/assets/javascripts/sentry/sentry_config.js +++ b/app/assets/javascripts/sentry/sentry_config.js @@ -1,5 +1,4 @@ import * as Sentry from 'sentrybrowser7'; -import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from './constants'; const SentryConfig = { init(options = {}) { @@ -17,9 +16,6 @@ const SentryConfig = { release, allowUrls, environment, - ignoreErrors: IGNORE_ERRORS, - denyUrls: DENY_URLS, - sampleRate: SAMPLE_RATE, }); Sentry.setTags(tags); diff --git a/app/controllers/concerns/confirm_email_warning.rb b/app/controllers/concerns/confirm_email_warning.rb index ec5140bf223..8b7371cbc17 100644 --- a/app/controllers/concerns/confirm_email_warning.rb +++ b/app/controllers/concerns/confirm_email_warning.rb @@ -10,7 +10,7 @@ module ConfirmEmailWarning protected def show_confirm_warning? - html_request? && request.get? && Feature.enabled?(:soft_email_confirmation) + html_request? && request.get? && Gitlab::CurrentSettings.email_confirmation_setting_soft? end def set_confirm_warning diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb index 4d1cbd8becc..e94138c4d9b 100644 --- a/app/controllers/confirmations_controller.rb +++ b/app/controllers/confirmations_controller.rb @@ -20,7 +20,7 @@ class ConfirmationsController < Devise::ConfirmationsController protected def after_resending_confirmation_instructions_path_for(resource) - return users_almost_there_path unless Feature.enabled?(:soft_email_confirmation) + return users_almost_there_path unless Gitlab::CurrentSettings.email_confirmation_setting_soft? stored_location_for(resource) || dashboard_projects_path end diff --git a/app/controllers/registrations/welcome_controller.rb b/app/controllers/registrations/welcome_controller.rb index 78c00a81a93..87fcb499d21 100644 --- a/app/controllers/registrations/welcome_controller.rb +++ b/app/controllers/registrations/welcome_controller.rb @@ -50,7 +50,7 @@ module Registrations def requires_confirmation?(user) return false if user.confirmed? - return false if Feature.enabled?(:soft_email_confirmation) + return false unless Gitlab::CurrentSettings.email_confirmation_setting_hard? true end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 92a00618ef5..b4eee3549a0 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -129,9 +129,9 @@ class RegistrationsController < Devise::RegistrationsController def after_inactive_sign_up_path_for(resource) Gitlab::AppLogger.info(user_created_message) return new_user_session_path(anchor: 'login-pane') if resource.blocked_pending_approval? - return dashboard_projects_path if Feature.enabled?(:soft_email_confirmation) + return dashboard_projects_path if Gitlab::CurrentSettings.email_confirmation_setting_soft? - # when email confirmation is enabled, path to redirect is saved + # when email_confirmation_setting is set to `hard`, path to redirect is saved # after user confirms and comes back, he will be redirected store_location_for(:redirect, after_sign_up_path) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index 939e86409db..16c57b572a5 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -835,6 +835,33 @@ class ApplicationSetting < MainClusterwide::ApplicationRecord false end + # Overriding the enum check for `email_confirmation_setting` as the feature flag is being removed and is taking a + # release M, M.N+1 strategy as noted in: + # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107302#note_1286005956 + def email_confirmation_setting_off? + if Feature.enabled?(:soft_email_confirmation) + false + else + super + end + end + + def email_confirmation_setting_soft? + if Feature.enabled?(:soft_email_confirmation) + true + else + super + end + end + + def email_confirmation_setting_hard? + if Feature.enabled?(:soft_email_confirmation) + false + else + super + end + end + private def parsed_grafana_url diff --git a/app/models/project.rb b/app/models/project.rb index dba14f92e4d..f07090d6131 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -264,6 +264,7 @@ class Project < ApplicationRecord has_one :project_setting, inverse_of: :project, autosave: true has_one :alerting_setting, inverse_of: :project, class_name: 'Alerting::ProjectAlertingSetting' has_one :service_desk_setting, class_name: 'ServiceDeskSetting' + has_one :service_desk_custom_email_verification, class_name: 'ServiceDesk::CustomEmailVerification' # Merge requests for target project should be removed with it has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent diff --git a/app/models/service_desk.rb b/app/models/service_desk.rb new file mode 100644 index 00000000000..cb9c924c01f --- /dev/null +++ b/app/models/service_desk.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +module ServiceDesk + def self.table_name_prefix + 'service_desk_' + end +end diff --git a/app/models/service_desk/custom_email_verification.rb b/app/models/service_desk/custom_email_verification.rb new file mode 100644 index 00000000000..b3b9390bb82 --- /dev/null +++ b/app/models/service_desk/custom_email_verification.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +module ServiceDesk + class CustomEmailVerification < ApplicationRecord + enum state: { + running: 0, + verified: 1, + error: 2 + }, _default: 'running' + + enum error: { + incorrect_token: 0, + incorrect_from: 1, + mail_not_received_within_timeframe: 2, + invalid_credentials: 3, + smtp_host_issue: 4 + } + + TIMEFRAME = 30.minutes + + attr_encrypted :token, + mode: :per_attribute_iv, + algorithm: 'aes-256-gcm', + key: Settings.attr_encrypted_db_key_base_32, + encode: false, + encode_iv: false + + belongs_to :project + belongs_to :triggerer, class_name: 'User', optional: true + + validates :project, presence: true + validates :state, presence: true + + delegate :service_desk_setting, to: :project + + class << self + def generate_token + SecureRandom.alphanumeric(12) + end + end + + def accepted_until + return unless running? + return unless triggered_at.present? + + TIMEFRAME.since(triggered_at) + end + + def in_timeframe? + return false unless running? + + !!accepted_until&.future? + end + end +end diff --git a/app/models/service_desk_setting.rb b/app/models/service_desk_setting.rb index 5152746abb4..69afb445734 100644 --- a/app/models/service_desk_setting.rb +++ b/app/models/service_desk_setting.rb @@ -3,6 +3,8 @@ class ServiceDeskSetting < ApplicationRecord include Gitlab::Utils::StrongMemoize + CUSTOM_EMAIL_VERIFICATION_SUBADDRESS = '+verify' + attribute :custom_email_enabled, default: false attr_encrypted :custom_email_smtp_password, mode: :per_attribute_iv, @@ -12,6 +14,7 @@ class ServiceDeskSetting < ApplicationRecord encode_iv: false belongs_to :project + validates :project_id, presence: true validate :valid_issue_template validate :valid_project_key @@ -32,21 +35,25 @@ class ServiceDeskSetting < ApplicationRecord validates :custom_email, presence: true, devise_email: true, - if: :custom_email_enabled? + if: :needs_custom_email_smtp_credentials? validates :custom_email_smtp_address, presence: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }, - if: :custom_email_enabled? + if: :needs_custom_email_smtp_credentials? validates :custom_email_smtp_username, presence: true, - if: :custom_email_enabled? + if: :needs_custom_email_smtp_credentials? validates :custom_email_smtp_port, presence: true, numericality: { only_integer: true, greater_than: 0 }, - if: :custom_email_enabled? + if: :needs_custom_email_smtp_credentials? scope :with_project_key, ->(key) { where(project_key: key) } + def custom_email_verification + project&.service_desk_custom_email_verification + end + def custom_email_delivery_options { user_name: custom_email_smtp_username, @@ -57,6 +64,12 @@ class ServiceDeskSetting < ApplicationRecord } end + def custom_email_address_for_verification + return unless custom_email.present? + + custom_email.sub("@", "#{CUSTOM_EMAIL_VERIFICATION_SUBADDRESS}@") + end + def issue_template_content strong_memoize(:issue_template_content) do next unless issue_template_key.present? @@ -102,6 +115,10 @@ class ServiceDeskSetting < ApplicationRecord setting.project.full_path_slug == project_slug end end + + def needs_custom_email_smtp_credentials? + custom_email_enabled? || custom_email_verification.present? + end end ServiceDeskSetting.prepend_mod diff --git a/app/models/user.rb b/app/models/user.rb index 9f9c71cc83f..8c34c2eb516 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2129,7 +2129,15 @@ class User < ApplicationRecord end def confirmation_required_on_sign_in? - !confirmed? && !confirmation_period_valid? + return false if confirmed? + + if ::Gitlab::CurrentSettings.email_confirmation_setting_off? + false + elsif ::Gitlab::CurrentSettings.email_confirmation_setting_soft? + !in_confirmation_period? + elsif ::Gitlab::CurrentSettings.email_confirmation_setting_hard? + true + end end def impersonated? @@ -2210,10 +2218,13 @@ class User < ApplicationRecord # override from Devise::Confirmable def confirmation_period_valid? - return false if Feature.disabled?(:soft_email_confirmation) + return super if ::Gitlab::CurrentSettings.email_confirmation_setting_soft? - super + # Following devise logic for method, we want to return `true` + # See: https://github.com/heartcombo/devise/blob/main/lib/devise/models/confirmable.rb#L191-L218 + true end + alias_method :in_confirmation_period?, :confirmation_period_valid? # This is copied from Devise::Models::TwoFactorAuthenticatable#consume_otp! # diff --git a/app/services/bulk_imports/archive_extraction_service.rb b/app/services/bulk_imports/archive_extraction_service.rb index caa40d98a76..fec8fd0e1f5 100644 --- a/app/services/bulk_imports/archive_extraction_service.rb +++ b/app/services/bulk_imports/archive_extraction_service.rb @@ -33,7 +33,6 @@ module BulkImports validate_symlink extract_archive - remove_symlinks tmpdir end @@ -60,15 +59,5 @@ module BulkImports def extract_archive untar_xf(archive: filepath, dir: tmpdir) end - - def extracted_files - Dir.glob(File.join(tmpdir, '**', '*')) - end - - def remove_symlinks - extracted_files.each do |path| - FileUtils.rm(path) if symlink?(path) - end - end end end diff --git a/app/services/ci/job_artifacts/create_service.rb b/app/services/ci/job_artifacts/create_service.rb index 247695a9304..30d310dec7f 100644 --- a/app/services/ci/job_artifacts/create_service.rb +++ b/app/services/ci/job_artifacts/create_service.rb @@ -113,7 +113,13 @@ module Ci end def accessibility(params) - params[:accessibility] || 'public' + accessibility = params[:accessibility] + + return :public if Feature.disabled?(:non_public_artifacts, type: :development) + + return accessibility if accessibility.present? + + job.artifacts_public? ? :public : :private end def parse_artifact(artifact) diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml index 0305a9487ca..b64617f3f11 100644 --- a/app/views/admin/application_settings/_visibility_and_access.html.haml +++ b/app/views/admin/application_settings/_visibility_and_access.html.haml @@ -38,6 +38,8 @@ = render_if_exists 'admin/application_settings/ldap_access_setting', form: f + = render_if_exists 'admin/application_settings/saml_group_locks_setting', form: f + .form-group{ data: { testid: 'project-export' } } = f.label :project_export, s_('AdminSettings|Project export'), class: 'label-bold' = f.gitlab_ui_checkbox_component :project_export_enabled, s_('AdminSettings|Enabled') diff --git a/app/views/shared/issuable/_feed_buttons.html.haml b/app/views/shared/issuable/_feed_buttons.html.haml index 94b7fe14721..e0f676021a1 100644 --- a/app/views/shared/issuable/_feed_buttons.html.haml +++ b/app/views/shared/issuable/_feed_buttons.html.haml @@ -1,8 +1,8 @@ - show_calendar_button = local_assigns.fetch(:show_calendar_button, true) -= render Pajamas::ButtonComponent.new(href: safe_params.merge(rss_url_options), icon: 'rss', button_options: { class: 'has-tooltip', 'aria-label': _('Subscribe to RSS feed'), data: { container: 'body', testid: 'rss-feed-link' } }) do - = _('Subscribe to RSS feed') += render Pajamas::ButtonComponent.new(href: safe_params.merge(rss_url_options), button_options: { class: 'has-tooltip btn-icon', title: _('Subscribe to RSS feed'), 'aria-label': _('Subscribe to RSS feed'), data: { container: 'body', testid: 'rss-feed-link' } }) do + = sprite_icon('rss') - if show_calendar_button - = render Pajamas::ButtonComponent.new(href: safe_params.merge(calendar_url_options), icon: 'calendar', button_options: { class: 'has-tooltip', 'aria-label': _('Subscribe to calendar'), data: { container: 'body' } }) do - = _('Subscribe to calendar') + = render Pajamas::ButtonComponent.new(href: safe_params.merge(calendar_url_options), button_options: { class: 'has-tooltip btn-icon', title: _('Subscribe to calendar'), 'aria-label': _('Subscribe to calendar'), data: { container: 'body' } }) do + = sprite_icon('calendar') diff --git a/db/docs/service_desk_custom_email_verifications.yml b/db/docs/service_desk_custom_email_verifications.yml new file mode 100644 index 00000000000..cec5db374d9 --- /dev/null +++ b/db/docs/service_desk_custom_email_verifications.yml @@ -0,0 +1,11 @@ +--- +table_name: service_desk_custom_email_verifications +classes: +- ServiceDesk::CustomEmailVerification +feature_categories: +- service_desk +description: Holds the verification state and additional information for custom email + addresses for Service Desk +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/112938 +milestone: '15.10' +gitlab_schema: gitlab_main diff --git a/db/migrate/20230118135145_add_service_desk_custom_email_verifications.rb b/db/migrate/20230118135145_add_service_desk_custom_email_verifications.rb new file mode 100644 index 00000000000..32f7f3392e6 --- /dev/null +++ b/db/migrate/20230118135145_add_service_desk_custom_email_verifications.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class AddServiceDeskCustomEmailVerifications < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def up + create_table(:service_desk_custom_email_verifications, id: false, primary_key: :project_id) do |t| + t.references :project, index: false, foreign_key: { on_delete: :cascade }, null: false + t.references :triggerer, index: true, foreign_key: { to_table: :users, on_delete: :nullify } + t.timestamps_with_timezone + t.datetime_with_timezone :triggered_at + t.integer :state, limit: 2, null: false, default: 0 + t.integer :error, limit: 2 + t.binary :encrypted_token + t.binary :encrypted_token_iv + end + + execute "ALTER TABLE service_desk_custom_email_verifications ADD PRIMARY KEY (project_id);" + end + + def down + drop_table :service_desk_custom_email_verifications + end +end diff --git a/db/migrate/20230224161346_add_saml_group_lock_to_application_settings.rb b/db/migrate/20230224161346_add_saml_group_lock_to_application_settings.rb new file mode 100644 index 00000000000..003dd5c5b61 --- /dev/null +++ b/db/migrate/20230224161346_add_saml_group_lock_to_application_settings.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddSamlGroupLockToApplicationSettings < Gitlab::Database::Migration[2.1] + def change + add_column :application_settings, :lock_memberships_to_saml, :boolean, default: false, null: false + end +end diff --git a/db/post_migrate/20230303105806_queue_delete_orphaned_packages_dependencies.rb b/db/post_migrate/20230303105806_queue_delete_orphaned_packages_dependencies.rb new file mode 100644 index 00000000000..8c741cf9868 --- /dev/null +++ b/db/post_migrate/20230303105806_queue_delete_orphaned_packages_dependencies.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class QueueDeleteOrphanedPackagesDependencies < Gitlab::Database::Migration[2.1] + restrict_gitlab_migration gitlab_schema: :gitlab_main + + MIGRATION = 'DeleteOrphanedPackagesDependencies' + DELAY_INTERVAL = 2.minutes + BATCH_SIZE = 6000 + SUB_BATCH_SIZE = 100 + + disable_ddl_transaction! + def up + queue_batched_background_migration( + MIGRATION, + :packages_dependencies, + :id, + job_interval: DELAY_INTERVAL, + batch_size: BATCH_SIZE, + sub_batch_size: SUB_BATCH_SIZE + ) + end + + def down + delete_batched_background_migration(MIGRATION, :packages_dependencies, :id, []) + end +end diff --git a/db/post_migrate/20230310111859_recreate_user_type_migration_indexes.rb b/db/post_migrate/20230310111859_recreate_user_type_migration_indexes.rb new file mode 100644 index 00000000000..539ce99a319 --- /dev/null +++ b/db/post_migrate/20230310111859_recreate_user_type_migration_indexes.rb @@ -0,0 +1,24 @@ +# frozen_string_literal: true + +class RecreateUserTypeMigrationIndexes < Gitlab::Database::Migration[2.1] + disable_ddl_transaction! + + INCORRECT_BILLABLE_INDEX = 'index_users_for_active_billable_users_migration' + BILLABLE_INDEX = 'migrate_index_users_for_active_billable_users' + + def up + # Temporary index to migrate human user_type. See https://gitlab.com/gitlab-org/gitlab/-/issues/386474 + add_concurrent_index :users, :id, name: BILLABLE_INDEX, + where: "state = 'active' AND ((user_type IS NULL OR user_type = 0) OR (user_type = ANY (ARRAY[0, 6, 4, 13]))) " \ + "AND ((user_type IS NULL OR user_type = 0) OR (user_type = ANY (ARRAY[0, 4, 5])))" + + remove_concurrent_index_by_name :users, INCORRECT_BILLABLE_INDEX + end + + def down + add_concurrent_index :users, :id, name: INCORRECT_BILLABLE_INDEX, + where: "state = 'active' AND ((user_type IS NULL OR user_type = 0) OR (user_type = ANY (ARRAY[6, 4, 13]))) " \ + "AND ((user_type IS NULL OR user_type = 0) OR (user_type = ANY (ARRAY[4, 5])))" + remove_concurrent_index_by_name :users, BILLABLE_INDEX + end +end diff --git a/db/schema_migrations/20230118135145 b/db/schema_migrations/20230118135145 new file mode 100644 index 00000000000..15c566a4058 --- /dev/null +++ b/db/schema_migrations/20230118135145 @@ -0,0 +1 @@ +d6fdfc530a49b230aa041d4629a0484462abacb824f6bbf23d9740068e3ca781
\ No newline at end of file diff --git a/db/schema_migrations/20230224161346 b/db/schema_migrations/20230224161346 new file mode 100644 index 00000000000..1c939bdafaf --- /dev/null +++ b/db/schema_migrations/20230224161346 @@ -0,0 +1 @@ +191d7be803e9e3a2a5292bbcd562c34a67c07b73da2c429ac2f115b28d04f00c
\ No newline at end of file diff --git a/db/schema_migrations/20230303105806 b/db/schema_migrations/20230303105806 new file mode 100644 index 00000000000..46bf8fb2b2b --- /dev/null +++ b/db/schema_migrations/20230303105806 @@ -0,0 +1 @@ +5f2176abfc462e65c9ef2b9b28c9feb60cac868aa491d4d4207a8904deb60f18
\ No newline at end of file diff --git a/db/schema_migrations/20230310111859 b/db/schema_migrations/20230310111859 new file mode 100644 index 00000000000..0bc9268a311 --- /dev/null +++ b/db/schema_migrations/20230310111859 @@ -0,0 +1 @@ +d1accdc2bbe9aa5266df98a893176fba94148f9754d2c0b2de04e9d8d66d8eba
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index e073c4f4711..5126a322387 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -11748,6 +11748,7 @@ CREATE TABLE application_settings ( projects_api_rate_limit_unauthenticated integer DEFAULT 400 NOT NULL, deny_all_requests_except_allowed boolean DEFAULT false NOT NULL, product_analytics_data_collector_host text, + lock_memberships_to_saml boolean DEFAULT false NOT NULL, CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)), CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)), CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)), @@ -22180,6 +22181,18 @@ CREATE TABLE serverless_domain_cluster ( certificate text ); +CREATE TABLE service_desk_custom_email_verifications ( + project_id bigint NOT NULL, + triggerer_id bigint, + created_at timestamp with time zone NOT NULL, + updated_at timestamp with time zone NOT NULL, + triggered_at timestamp with time zone, + state smallint DEFAULT 0 NOT NULL, + error smallint, + encrypted_token bytea, + encrypted_token_iv bytea +); + CREATE TABLE service_desk_settings ( project_id bigint NOT NULL, issue_template_key character varying(255), @@ -27610,6 +27623,9 @@ ALTER TABLE ONLY sprints ALTER TABLE ONLY serverless_domain_cluster ADD CONSTRAINT serverless_domain_cluster_pkey PRIMARY KEY (uuid); +ALTER TABLE ONLY service_desk_custom_email_verifications + ADD CONSTRAINT service_desk_custom_email_verifications_pkey PRIMARY KEY (project_id); + ALTER TABLE ONLY service_desk_settings ADD CONSTRAINT service_desk_settings_pkey PRIMARY KEY (project_id); @@ -31836,6 +31852,8 @@ CREATE INDEX index_serverless_domain_cluster_on_creator_id ON serverless_domain_ CREATE INDEX index_serverless_domain_cluster_on_pages_domain_id ON serverless_domain_cluster USING btree (pages_domain_id); +CREATE INDEX index_service_desk_custom_email_verifications_on_triggerer_id ON service_desk_custom_email_verifications USING btree (triggerer_id); + CREATE INDEX index_service_desk_enabled_projects_on_id_creator_id_created_at ON projects USING btree (id, creator_id, created_at) WHERE (service_desk_enabled = true); CREATE INDEX index_service_desk_settings_on_file_template_project_id ON service_desk_settings USING btree (file_template_project_id); @@ -32154,8 +32172,6 @@ CREATE UNIQUE INDEX index_user_synced_attributes_metadata_on_user_id ON user_syn CREATE INDEX index_users_for_active_billable_users ON users USING btree (id) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[6, 4, 13]))) AND ((user_type IS NULL) OR (user_type = ANY (ARRAY[4, 5])))); -CREATE INDEX index_users_for_active_billable_users_migration ON users USING btree (id) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = 0) OR (user_type = ANY (ARRAY[6, 4, 13]))) AND ((user_type IS NULL) OR (user_type = 0) OR (user_type = ANY (ARRAY[4, 5])))); - CREATE INDEX index_users_on_accepted_term_id ON users USING btree (accepted_term_id); CREATE INDEX index_users_on_admin ON users USING btree (admin); @@ -32484,6 +32500,8 @@ CREATE UNIQUE INDEX merge_request_user_mentions_on_mr_id_index ON merge_request_ CREATE INDEX merge_requests_state_id_temp_index ON merge_requests USING btree (id) WHERE (state_id = ANY (ARRAY[2, 3])); +CREATE INDEX migrate_index_users_for_active_billable_users ON users USING btree (id) WHERE (((state)::text = 'active'::text) AND ((user_type IS NULL) OR (user_type = 0) OR (user_type = ANY (ARRAY[0, 6, 4, 13]))) AND ((user_type IS NULL) OR (user_type = 0) OR (user_type = ANY (ARRAY[0, 4, 5])))); + CREATE INDEX note_mentions_temp_index ON notes USING btree (id, noteable_type) WHERE (note ~~ '%@%'::text); CREATE UNIQUE INDEX one_canonical_wiki_page_slug_per_metadata ON wiki_page_slugs USING btree (wiki_page_meta_id) WHERE (canonical = true); @@ -35109,6 +35127,9 @@ ALTER TABLE ONLY diff_note_positions ALTER TABLE ONLY analytics_cycle_analytics_aggregations ADD CONSTRAINT fk_rails_13c8374c7a FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE; +ALTER TABLE ONLY service_desk_custom_email_verifications + ADD CONSTRAINT fk_rails_14dcaf4c92 FOREIGN KEY (triggerer_id) REFERENCES users(id) ON DELETE SET NULL; + ALTER TABLE ONLY namespaces_storage_limit_exclusions ADD CONSTRAINT fk_rails_14e8f7b0e0 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE; @@ -36462,6 +36483,9 @@ ALTER TABLE ONLY dast_scanner_profiles_tags ALTER TABLE ONLY vulnerability_feedback ADD CONSTRAINT fk_rails_debd54e456 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; +ALTER TABLE ONLY service_desk_custom_email_verifications + ADD CONSTRAINT fk_rails_debe4c4acc FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE; + ALTER TABLE ONLY packages_debian_project_distributions ADD CONSTRAINT fk_rails_df44271a30 FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE RESTRICT; diff --git a/doc/administration/geo/secondary_proxy/index.md b/doc/administration/geo/secondary_proxy/index.md index addf894f0a5..35ab1d8252c 100644 --- a/doc/administration/geo/secondary_proxy/index.md +++ b/doc/administration/geo/secondary_proxy/index.md @@ -165,6 +165,7 @@ It does not cover all data types. | LFS objects (using Git) | **{check-circle}** Yes | | Pages | **{dotted-circle}** No <sup>2</sup> | | Advanced search (using the web UI) | **{dotted-circle}** No | +| Container registry | **{dotted-circle}** No | 1. Git reads are served from the local secondary while pushes get proxied to the primary. Selective sync or cases where repositories don't exist locally on the Geo secondary throw a "not found" error. diff --git a/doc/administration/operations/index.md b/doc/administration/operations/index.md index f6ab46b9fbf..b02aab738ea 100644 --- a/doc/administration/operations/index.md +++ b/doc/administration/operations/index.md @@ -8,6 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w Keep your GitLab instance up and running smoothly. +- [Upgrading GitLab](../../update/index.md). - [Rake tasks](../../raketasks/index.md): Tasks for common administration and operational processes such as [cleaning up unneeded items from GitLab instance](../../raketasks/cleanup.md), integrity checks, and more. diff --git a/doc/administration/postgresql/multiple_databases.md b/doc/administration/postgresql/multiple_databases.md index 9ac4448a004..188578a739c 100644 --- a/doc/administration/postgresql/multiple_databases.md +++ b/doc/administration/postgresql/multiple_databases.md @@ -1,6 +1,6 @@ --- stage: Data Stores -group: Pods +group: Tenant Scale info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md index 1ffda1c0d79..50acac6bc2a 100644 --- a/doc/api/pipeline_triggers.md +++ b/doc/api/pipeline_triggers.md @@ -155,7 +155,7 @@ or a [CI/CD job token](../ci/jobs/ci_job_token.md) for authentication. With a CI/CD job token, the [triggered pipeline is a multi-project pipeline](../ci/pipelines/downstream_pipelines.md#trigger-a-multi-project-pipeline-by-using-the-api). The job that authenticates the request becomes associated with the upstream pipeline, -which is visible on the [pipeline graph](../ci/pipelines/downstream_pipelines.md#view-multi-project-pipelines-in-pipeline-graphs). +which is visible on the pipeline graph. If you use a trigger token in a job, the job is not associated with the upstream pipeline. diff --git a/doc/ci/pipelines/downstream_pipelines.md b/doc/ci/pipelines/downstream_pipelines.md index 8031ce751ec..1e4654e69fe 100644 --- a/doc/ci/pipelines/downstream_pipelines.md +++ b/doc/ci/pipelines/downstream_pipelines.md @@ -315,18 +315,32 @@ trigger_pipeline: > Hover behavior for pipeline cards [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/197140/) in GitLab 13.2. In the [pipeline graph view](index.md#view-full-pipeline-graph), downstream pipelines display -as a list of cards on the right of the graph. Hover over the pipeline's card to view -which job triggered the downstream pipeline. +as a list of cards on the right of the graph. From this view, you can: -### Retry a downstream pipeline +- Select a trigger job to see the triggered downstream pipeline's jobs. +- Select **Expand jobs** **{chevron-lg-right}** on a pipeline card to expand the view + with the downstream pipeline's jobs. You can view one downstream pipeline at a time. +- Hover over a pipeline card to have the job that triggered the downstream pipeline highlighted. + +### Retry failed and canceled jobs in a downstream pipeline > - Retry from graph view [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/354974) in GitLab 15.0 [with a flag](../../administration/feature_flags.md) named `downstream_retry_action`. Disabled by default. > - Retry from graph view [generally available and feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/357406) in GitLab 15.1. -To retry a completed downstream pipeline, select **Retry** (**{retry}**): +To retry failed and canceled jobs, select **Retry** (**{retry}**): - From the downstream pipeline's details page. -- On the pipeline's card in the [pipeline graph view](index.md#view-full-pipeline-graph). +- On the pipeline's card in the pipeline graph view. + +### Recreate a downstream pipeline + +> Retry trigger job from graph view [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/367547) in GitLab 15.10 [with a flag](../../administration/feature_flags.md) named `ci_recreate_downstream_pipeline`. Disabled by default. + +You can recreate a downstream pipeline by retrying its corresponding trigger job. The newly created downstream pipeline replaces the current downstream pipeline in the pipeline graph. + +To recreate a downstream pipeline: + +- Select **Run again** (**{retry}**) on the trigger job's card in the pipeline graph view. ### Cancel a downstream pipeline @@ -336,7 +350,7 @@ To retry a completed downstream pipeline, select **Retry** (**{retry}**): To cancel a downstream pipeline that is still running, select **Cancel** (**{cancel}**): - From the downstream pipeline's details page. -- On the pipeline's card in the [pipeline graph view](index.md#view-full-pipeline-graph). +- On the pipeline's card in the pipeline graph view. ### Mirror the status of a downstream pipeline in the trigger job @@ -371,13 +385,9 @@ trigger_job: After you trigger a multi-project pipeline, the downstream pipeline displays to the right of the [pipeline graph](index.md#visualize-pipelines). -![Multi-project pipeline graph](img/multi_project_pipeline_graph_v14_3.png) - In [pipeline mini graphs](index.md#pipeline-mini-graphs), the downstream pipeline displays to the right of the mini graph. -![Multi-project pipeline mini graph](img/pipeline_mini_graph_v15_0.png) - ## Fetch artifacts from an upstream pipeline Use [`needs:project`](../yaml/index.md#needsproject) to fetch artifacts from an diff --git a/doc/ci/pipelines/img/multi_project_pipeline_graph_v14_3.png b/doc/ci/pipelines/img/multi_project_pipeline_graph_v14_3.png Binary files differdeleted file mode 100644 index aadf8bb0979..00000000000 --- a/doc/ci/pipelines/img/multi_project_pipeline_graph_v14_3.png +++ /dev/null diff --git a/doc/ci/pipelines/img/pipeline_mini_graph_v15_0.png b/doc/ci/pipelines/img/pipeline_mini_graph_v15_0.png Binary files differdeleted file mode 100644 index 48a0ca9d84f..00000000000 --- a/doc/ci/pipelines/img/pipeline_mini_graph_v15_0.png +++ /dev/null diff --git a/doc/ci/pipelines/index.md b/doc/ci/pipelines/index.md index fa04cb6cb92..d2038dbcb62 100644 --- a/doc/ci/pipelines/index.md +++ b/doc/ci/pipelines/index.md @@ -423,8 +423,7 @@ You can group the jobs by: - [Job dependencies](#view-job-dependencies-in-the-pipeline-graph), which arranges jobs based on their [`needs`](../yaml/index.md#needs) dependencies. -[Multi-project pipeline graphs](downstream_pipelines.md#view-multi-project-pipelines-in-pipeline-graphs) help -you visualize the entire pipeline, including all cross-project inter-dependencies. +Multi-project pipeline graphs help you visualize the entire pipeline, including all cross-project inter-dependencies. If a stage contains more than 100 jobs, only the first 100 jobs are listed in the pipeline graph. The remaining jobs still run as usual. To see the jobs: diff --git a/doc/ci/yaml/index.md b/doc/ci/yaml/index.md index 02350cf0d21..a1a4089a48b 100644 --- a/doc/ci/yaml/index.md +++ b/doc/ci/yaml/index.md @@ -961,10 +961,8 @@ job: #### `artifacts:public` -> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49775) in GitLab 13.8 -> - It's [deployed behind a feature flag](../../user/feature_flags.md), disabled by default. -> - It's disabled on GitLab.com. -> - It's recommended for production use. +> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/223273) in GitLab 13.8 [with a flag](../../user/feature_flags.md) named `non_public_artifacts`, disabled by default. +> - [Updated](https://gitlab.com/gitlab-org/gitlab/-/issues/322454) in GitLab 15.10. Artifacts created with `artifacts:public` before 15.10 are not guaranteed to remain private after this update. FLAG: On self-managed GitLab, by default this feature is not available. To make it available, diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md index 36b38e2e16b..d24875e559a 100644 --- a/doc/development/contributing/style_guides.md +++ b/doc/development/contributing/style_guides.md @@ -30,7 +30,9 @@ We were using Overcommit prior to Lefthook, so you may want to uninstall it firs ### Install Lefthook -1. Install the `lefthook` Ruby gem: +1. You can install lefthook in [different ways](https://github.com/evilmartians/lefthook/blob/master/docs/install.md#install-lefthook). + If you do not choose to install it globally (e.g. via Homebrew or package managers), and only want to use it for the GitLab project, + you can install the Ruby gem via: ```shell bundle install @@ -39,12 +41,18 @@ We were using Overcommit prior to Lefthook, so you may want to uninstall it firs 1. Install Lefthook managed Git hooks: ```shell + # If installed globally + lefthook install + # Or if installed via ruby gem bundle exec lefthook install ``` 1. Test Lefthook is working by running the Lefthook `pre-push` Git hook: ```shell + # If installed globally + lefthook run pre-push + # Or if installed via ruby gem bundle exec lefthook run pre-push ``` @@ -57,6 +65,18 @@ Lefthook is configured with a combination of: - Project configuration in [`lefthook.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lefthook.yml). - Any [local configuration](https://github.com/evilmartians/lefthook/blob/master/README.md#local-config). +### Lefthook auto-fixing files + +We have a custom lefthook target to run all the linters with auto-fix capabilities, +but just on the files which changed in your branch. + +```shell +# If installed globally +lefthook run auto-fix +# Or if installed via ruby gem +bundle exec lefthook run auto-fix +``` + ### Disable Lefthook temporarily To disable Lefthook temporarily, you can set the `LEFTHOOK` environment variable to `0`. For instance: diff --git a/doc/development/database/multiple_databases.md b/doc/development/database/multiple_databases.md index d22e3209096..c1b30a4cbbe 100644 --- a/doc/development/database/multiple_databases.md +++ b/doc/development/database/multiple_databases.md @@ -1,6 +1,6 @@ --- stage: Data Stores -group: Pods +group: Tenant Scale info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments --- diff --git a/doc/development/gemfile.md b/doc/development/gemfile.md index bfe8b7c5205..fbdba2ecb23 100644 --- a/doc/development/gemfile.md +++ b/doc/development/gemfile.md @@ -60,6 +60,8 @@ This means that new dependencies should, at a minimum, meet the following criter - There are no issues open that we know may impact the availability or performance of GitLab. - The project is tested using some form of test automation. The test suite must be passing using the Ruby version currently used by GitLab. +- CI builds for all supported platforms must succeed using the new dependency. For more information, see + how to [build a package for testing](build_test_package.md#building-a-package-for-testing). - If the project uses a C extension, consider requesting an additional review from a C or MRI domain expert. C extensions can greatly impact GitLab stability and performance. diff --git a/doc/integration/advanced_search/elasticsearch.md b/doc/integration/advanced_search/elasticsearch.md index f8021436c27..0b97bfd945a 100644 --- a/doc/integration/advanced_search/elasticsearch.md +++ b/doc/integration/advanced_search/elasticsearch.md @@ -662,6 +662,7 @@ For basic guidance on choosing a cluster configuration you may refer to [Elastic - Generally, you want to use at least a 2-node cluster configuration with one replica, which allows you to have resilience. If your storage usage is growing quickly, you may want to plan horizontal scaling (adding more nodes) beforehand. - It's not recommended to use HDD storage with the search cluster, because it takes a hit on performance. It's better to use SSD storage (NVMe or SATA SSD drives for example). +- You should not use [coordinating-only nodes](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html#coordinating-only-node) with large instances. Coordinating-only nodes are smaller than [data nodes](https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-node.html#data-node), which can impact performance and advanced search migrations. - You can use the [GitLab Performance Tool](https://gitlab.com/gitlab-org/quality/performance) to benchmark search performance with different search cluster sizes and configurations. - `Heap size` should be set to no more than 50% of your physical RAM. Additionally, it shouldn't be set to more than the threshold for zero-based compressed oops. The exact threshold varies, but 26 GB is safe on most systems, but can also be as large as 30 GB on some systems. See [Heap size settings](https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html#heap-size-settings) and [Setting JVM options](https://www.elastic.co/guide/en/elasticsearch/reference/current/jvm-options.html) for more details. - Number of CPUs (CPU cores) per node usually corresponds to the `Number of Elasticsearch shards` setting described below. diff --git a/doc/update/index.md b/doc/update/index.md index a7e1fd23b98..9c3d5840e75 100644 --- a/doc/update/index.md +++ b/doc/update/index.md @@ -196,11 +196,11 @@ accordingly, while also consulting the NOTE: When not explicitly specified, upgrade GitLab to the latest available patch -release rather than the first patch release, for example `13.8.8` instead of `13.8.0`. -This includes versions you must stop at on the upgrade path as there may +release of the `major`.`minor` release rather than the first patch release, for example `13.8.8` instead of `13.8.0`. +This includes `major`.`minor` versions you must stop at on the upgrade path as there may be fixes for issues relating to the upgrade process. Specifically around a [major version](#upgrading-to-a-new-major-version), -crucial database schema and migration patches are included in the latest patch releases. +crucial database schema and migration patches may be included in the latest patch releases. ## Upgrading between editions @@ -237,7 +237,7 @@ possible. ## Version-specific upgrading instructions -Each month, major, minor, or patch releases of GitLab are published along with a +Each month, major or minor as well as possibly patch releases of GitLab are published along with a [release post](https://about.gitlab.com/releases/categories/releases/). You should read the release posts for all versions you're passing over. At the end of major and minor release posts, there are three sections to look for specifically: @@ -267,7 +267,6 @@ and [Helm Chart deployments](https://docs.gitlab.com/charts/). They come with ap ### 15.9.0 - There is a [database migration bug in GitLab 15.9.x](#user-profile-data-loss-bug-in-159x) that can cause data to be lost from the user profile fields. This bug affects all currently available 15.9.x releases. Until a bug fix is released, you should upgrade to 15.6.x, 15.7.x, or 15.8.x first. -- This version removes `SanitizeConfidentialTodos` background migration [added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/87908/diffs) in 15.6, which removed any user inaccessible to-do items. Make sure that this migration is finished before upgrading to 15.9. - As part of the [CI Partitioning effort](../architecture/blueprints/ci_data_decay/pipeline_partitioning.md), a [new Foreign Key](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107547) was added to `ci_builds_needs`. On GitLab instances with large CI tables, adding this constraint can take longer than usual. Make sure that this migration is finished before upgrading to 15.9. - Praefect's metadata verifier's [invalid metadata deletion behavior](../administration/gitaly/praefect.md#enable-deletions) is now enabled by default. diff --git a/doc/user/admin_area/settings/sign_up_restrictions.md b/doc/user/admin_area/settings/sign_up_restrictions.md index c44901b1ad7..3bf52bfe001 100644 --- a/doc/user/admin_area/settings/sign_up_restrictions.md +++ b/doc/user/admin_area/settings/sign_up_restrictions.md @@ -51,17 +51,26 @@ signing up using OmniAuth or LDAP, set `block_auto_created_users` to `true` in t [OmniAuth configuration](../../../integration/omniauth.md#configure-common-settings) or [LDAP configuration](../../../administration/auth/ldap/index.md#basic-configuration-settings). -## Require email confirmation +## Confirm user email + +> - Soft email confirmation [introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47003) in GitLab 12.2 [with a flag](../../../operations/feature_flags.md) named `soft_email_confirmation`. +> - Soft email confirmation [changed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107302/diffs) from a feature flag to an application setting in GitLab 15.9. You can send confirmation emails during sign up and require that users confirm their email address before they are allowed to sign in. -To enforce confirmation of the email address used for new sign ups: +For example, to enforce confirmation of the email address used for new sign ups: 1. On the top bar, select **Main menu > Admin**. 1. On the left sidebar, select **Settings > General**, and expand **Sign-up restrictions**. 1. Under **Email confirmation settings**, select **Hard**. +The following settings are available: + +- **Hard** - Send a confirmation email during sign up. New users must confirm their email address before they can log in. +- **Soft** - Send a confirmation email during sign up. New users can log in immediately, but must confirm their email in three days. Unconfirmed accounts are deleted. +- **Off** - New users can sign up without confirming their email address. + ## User cap > - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4315) in GitLab 13.7. @@ -95,22 +104,6 @@ New user sign ups are subject to the user cap restriction. New users sign ups are not subject to the user cap restriction. Users in pending approval state are automatically approved in a background job. -## Soft email confirmation - -> - [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/issues/47003) in GitLab 12.2. -> - It's [deployed behind a feature flag](../../../user/feature_flags.md), disabled by default. -> - It's enabled on GitLab.com. -> - It's recommended for production use. -> - To use it in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-soft-email-confirmation). - -WARNING: -This feature might not be available to you. Check the **version history** note above for details. - -The soft email confirmation improves the sign-up experience for new users by allowing -them to sign in without an immediate confirmation when an email confirmation is required. -GitLab shows the user a reminder to confirm their email address, and the user can't -create or update pipelines until their email address is confirmed. - ## Minimum password length limit > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/20661) in GitLab 12.6 @@ -171,25 +164,6 @@ semicolon, comma, or a new line. ![Domain Denylist](img/domain_denylist_v14_1.png) -### Enable or disable soft email confirmation - -Soft email confirmation is under development but ready for production use. -It is deployed behind a feature flag that is **disabled by default**. -[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) -can opt to disable it. - -To enable it: - -```ruby -Feature.enable(:soft_email_confirmation) -``` - -To disable it: - -```ruby -Feature.disable(:soft_email_confirmation) -``` - ## Set up LDAP user filter You can limit GitLab access to a subset of the LDAP users on your LDAP server. diff --git a/doc/user/group/saml_sso/group_sync.md b/doc/user/group/saml_sso/group_sync.md index ff6e906b144..7c9b6effc43 100644 --- a/doc/user/group/saml_sso/group_sync.md +++ b/doc/user/group/saml_sso/group_sync.md @@ -106,6 +106,30 @@ Users granted: SAML group membership is evaluated each time a user signs in. +### Global SAML group memberships lock **(PREMIUM SELF)** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/386390) in GitLab 15.10. + +GitLab administrators can use the global SAML group memberships lock to prevent group members from inviting new members to subgroups that have their membership synchronized with SAML Group Links. + +Global group memberships lock only applies to subgroups of a top-level group where SAML Group Links synchronization is configured. No user can modify the +membership of a top-level group configured for SAML Group Links synchronization. + +When global group memberships lock is enabled: + +- Only an administrator can manage memberships of any group including access levels. +- Users cannot: + - Share a project with other groups. + - Invite members to a project created in a group. + +To enable global group memberships lock: + +1. [Configure SAML](../../../integration/saml.md) for your self-managed GitLab instance. +1. On the top bar, select **Main menu > Admin**. +1. On the left sidebar, select **Settings > General**. +1. Expand the **Visibility and access controls** section. +1. Ensure the **Lock memberships to SAML synchronization** checkbox is selected. + ### Automatic member removal After a group sync, users who are not members of a mapped SAML group are removed from the group. diff --git a/doc/user/packages/npm_registry/index.md b/doc/user/packages/npm_registry/index.md index 7577a9446c8..9839f308a3a 100644 --- a/doc/user/packages/npm_registry/index.md +++ b/doc/user/packages/npm_registry/index.md @@ -266,19 +266,28 @@ The GitLab npm repository supports the following commands for the npm CLI (`npm` ### `404 Not Found` errors are happening on `npm install` or `yarn` -Using `CI_JOB_TOKEN` to install npm packages with dependencies in another project gives you 404 Not Found errors. A fix for this problem is proposed in [issue 352962](https://gitlab.com/gitlab-org/gitlab/-/issues/352962). +Using `CI_JOB_TOKEN` to install npm packages with dependencies in another project gives you 404 Not Found errors. You need to authenticate with a token that has access to the package and all its dependencies. -As a workaround, you can: +If the package and its dependencies are in separate projects but in the same group, you can use a +[group deploy token](../../project/deploy_tokens/index.md#create-a-deploy-token): -1. Create a [personal access token](../../profile/personal_access_tokens.md). -1. Authenticate at both the instance level and project level for each package: +```ini +//gitlab.example.com/api/v4/packages/npm/:_authToken=<group-token> +@group-scope:registry=https://gitlab.example.com/api/v4/packages/npm/ +``` - ```ini - @foo:registry=https://gitlab.example.com/api/v4/packages/npm/ - //gitlab.example.com/api/v4/packages/npm/:_authToken=${MY_TOKEN} - //gitlab.example.com/api/v4/projects/<your_project_id_a>/packages/npm/:_authToken=${MY_TOKEN} - //gitlab.example.com/api/v4/projects/<your_project_id_b>/packages/npm/:_authToken=${MY_TOKEN} - ``` +If the package and its dependencies are spread across multiple groups, you can use a [personal access token](../../profile/personal_access_tokens.md) +from a user that has access to all the groups or individual projects: + +```ini +//gitlab.example.com/api/v4/packages/npm/:_authToken=<personal-access-token> +@group-1:registry=https://gitlab.example.com/api/v4/packages/npm/ +@group-2:registry=https://gitlab.example.com/api/v4/packages/npm/ +``` + +WARNING: +Personal access tokens must be treated carefully. Read our [token security considerations](../../../security/token_overview.md#security-considerations) +for guidance on managing personal access tokens (for example, setting a short expiry and using minimal scopes). ### `npm publish` targets default npm registry (`registry.npmjs.org`) diff --git a/doc/user/search/advanced_search.md b/doc/user/search/advanced_search.md index 5273ea47f58..464c44a6f14 100644 --- a/doc/user/search/advanced_search.md +++ b/doc/user/search/advanced_search.md @@ -55,6 +55,8 @@ Advanced search uses [Elasticsearch syntax](https://www.elastic.co/guide/en/elas ### Refining user search +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/388409) in GitLab 15.10. + In user search, a [fuzzy query](https://www.elastic.co/guide/en/elasticsearch/reference/7.2/query-dsl-fuzzy-query.html) is used by default. You can refine your search with [Elasticsearch syntax](#syntax). ### Code search diff --git a/lefthook.yml b/lefthook.yml index 6a80713450f..d5c0230f12b 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -96,6 +96,7 @@ pre-push: "merge_conflicts": skip: true # This is disabled by default. You can enable this check by adding skip: false in lefhook-local.yml https://github.com/evilmartians/lefthook/blob/master/docs/configuration.md#skip runner: bash + pre-commit: parallel: true commands: @@ -103,3 +104,27 @@ pre-commit: tags: secrets files: git diff --name-only --diff-filter=d --staged run: 'if command -v gitleaks > /dev/null 2>&1; then gitleaks protect --no-banner --staged --redact --verbose; else echo "WARNING: gitleaks is not installed. Please install it. See https://github.com/zricethezav/gitleaks#installing."; fi' + +auto-fix: + parallel: true + commands: + frontend: + tags: frontend style + files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD + glob: '*.{js,vue}' + run: 'yarn run lint:eslint:fix {files} && yarn run prettier --write --list-different {files}' + jsonlint: + tags: style + files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD + glob: '*.{json}' + run: scripts/lint-json --format --verbose {files} + prettier-graphql: + tags: frontend style + files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD + glob: '*.{graphql}' + run: yarn run prettier --write --list-different {files} + rubocop: + tags: backend style + files: git diff --name-only --diff-filter=d $(git merge-base origin/master HEAD)..HEAD + glob: '*.{rb,rake}' + run: REVEAL_RUBOCOP_TODO=0 bundle exec rubocop --parallel --autocorrect --force-exclusion {files} diff --git a/lib/gitlab/background_migration/delete_orphaned_packages_dependencies.rb b/lib/gitlab/background_migration/delete_orphaned_packages_dependencies.rb new file mode 100644 index 00000000000..a795300fa9d --- /dev/null +++ b/lib/gitlab/background_migration/delete_orphaned_packages_dependencies.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Deletes orphaned packages_dependencies records that have no packages_dependency_links + class DeleteOrphanedPackagesDependencies < BatchedMigrationJob + operation_name :delete_all + feature_category :package_registry + + scope_to ->(relation) { + relation.where( + <<~SQL.squish + NOT EXISTS ( + SELECT 1 + FROM packages_dependency_links + WHERE packages_dependency_links.dependency_id = packages_dependencies.id + ) + SQL + ) + } + + def perform + each_sub_batch(&:delete_all) + end + end + end +end diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 64ef3dd4830..d681f39f00b 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -90,6 +90,7 @@ module Gitlab def untar_with_options(archive:, dir:, options:) execute_cmd(%W(tar -#{options} #{archive} -C #{dir})) execute_cmd(%W(chmod -R #{UNTAR_MASK} #{dir})) + remove_symlinks(dir) end # rubocop:disable Gitlab/ModuleWithInstanceVariables @@ -120,6 +121,19 @@ module Gitlab FileUtils.copy_entry(source, destination) true end + + def remove_symlinks(dir) + ignore_file_names = %w[. ..] + + # Using File::FNM_DOTMATCH to also delete symlinks starting with "." + Dir.glob("#{dir}/**/*", File::FNM_DOTMATCH) + .reject { |f| ignore_file_names.include?(File.basename(f)) } + .each do |filepath| + FileUtils.rm(filepath) if File.lstat(filepath).symlink? + end + + true + end end end end diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb index 1878b5b1a30..d2593289c23 100644 --- a/lib/gitlab/import_export/file_importer.rb +++ b/lib/gitlab/import_export/file_importer.rb @@ -8,7 +8,6 @@ module Gitlab ImporterError = Class.new(StandardError) MAX_RETRIES = 8 - IGNORED_FILENAMES = %w(. ..).freeze def self.import(*args, **kwargs) new(*args, **kwargs).import @@ -24,7 +23,7 @@ module Gitlab mkdir_p(@shared.export_path) mkdir_p(@shared.archive_path) - remove_symlinks + remove_symlinks(@shared.export_path) copy_archive wait_for_archived_file do @@ -36,7 +35,7 @@ module Gitlab false ensure remove_import_file - remove_symlinks + remove_symlinks(@shared.export_path) end private @@ -86,22 +85,10 @@ module Gitlab end end - def remove_symlinks - extracted_files.each do |path| - FileUtils.rm(path) if File.lstat(path).symlink? - end - - true - end - def remove_import_file FileUtils.rm_rf(@archive_file) end - def extracted_files - Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| IGNORED_FILENAMES.include?(File.basename(f)) } - end - def validate_decompressed_archive_size raise ImporterError, _('Decompressed archive size validation failed.') unless size_validator.valid? end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 53072c90b9a..6edcc18f239 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5099,12 +5099,18 @@ msgstr "" msgid "ApplicationSettings|See %{linkStart}password policy guidelines%{linkEnd}." msgstr "" +msgid "ApplicationSettings|Send a confirmation email during sign up. New users can log in immediately, but must confirm their email within three days." +msgstr "" + msgid "ApplicationSettings|Send a confirmation email during sign up. New users must confirm their email address before they can log in." msgstr "" msgid "ApplicationSettings|Sign-up enabled" msgstr "" +msgid "ApplicationSettings|Soft" +msgstr "" + msgid "ApplicationSettings|Text shown after a user signs up. Markdown enabled." msgstr "" @@ -8716,12 +8722,6 @@ msgstr "" msgid "Checkout|Exp %{expirationMonth}/%{expirationYear}" msgstr "" -msgid "Checkout|Failed to confirm your order! Please try again." -msgstr "" - -msgid "Checkout|Failed to confirm your order: %{message}. Please try again." -msgstr "" - msgid "Checkout|Failed to load countries. Please try again." msgstr "" @@ -19488,6 +19488,9 @@ msgstr "" msgid "Given epic is already related to this epic." msgstr "" +msgid "Global SAML group membership lock" +msgstr "" + msgid "Global Search is disabled for this scope" msgstr "" @@ -21557,6 +21560,9 @@ msgstr "" msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via SAML Group Links synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored. %{link_start}Learn more.%{link_end}" msgstr "" @@ -25902,6 +25908,9 @@ msgstr "" msgid "Lock memberships to LDAP synchronization" msgstr "" +msgid "Lock memberships to SAML Group Links synchronization" +msgstr "" + msgid "Lock merge request" msgstr "" @@ -37891,6 +37900,9 @@ msgstr "" msgid "SAML for %{group_name}" msgstr "" +msgid "SAML group membership settings" +msgstr "" + msgid "SAML single sign-on" msgstr "" @@ -38930,6 +38942,9 @@ msgstr "" msgid "SecurityOrchestration|No rules defined - policy will not run." msgstr "" +msgid "SecurityOrchestration|No tags available" +msgstr "" + msgid "SecurityOrchestration|Non-existing tags have been detected in the policy yaml. As a result, rule mode has been disabled. To enable rule mode, remove those non-existing tags from the policy yaml." msgstr "" @@ -39023,7 +39038,7 @@ msgstr "" msgid "SecurityOrchestration|Scan to be performed on every pipeline on the %{branches}" msgstr "" -msgid "SecurityOrchestration|Scan will automatically choose a runner to run on because there are no tags exist on runners" +msgid "SecurityOrchestration|Scan will automatically choose a runner to run on because there are no tags exist on runners. You can %{linkStart}create a new tag in settings%{linkEnd}." msgstr "" msgid "SecurityOrchestration|Security Approvals" diff --git a/spec/controllers/concerns/confirm_email_warning_spec.rb b/spec/controllers/concerns/confirm_email_warning_spec.rb index 334c156e1ae..fca99d37000 100644 --- a/spec/controllers/concerns/confirm_email_warning_spec.rb +++ b/spec/controllers/concerns/confirm_email_warning_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' RSpec.describe ConfirmEmailWarning do before do - stub_feature_flags(soft_email_confirmation: true) + stub_application_setting_enum('email_confirmation_setting', 'soft') end controller(ApplicationController) do diff --git a/spec/controllers/confirmations_controller_spec.rb b/spec/controllers/confirmations_controller_spec.rb index b32cc892e8a..0e88811550a 100644 --- a/spec/controllers/confirmations_controller_spec.rb +++ b/spec/controllers/confirmations_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe ConfirmationsController do +RSpec.describe ConfirmationsController, feature_category: :system_access do include DeviseHelpers before do @@ -148,51 +148,69 @@ RSpec.describe ConfirmationsController do end end - context 'when reCAPTCHA is disabled' do + context "when `email_confirmation_setting` is set to `soft`" do before do - stub_application_setting(recaptcha_enabled: false) + stub_application_setting_enum('email_confirmation_setting', 'soft') end - it 'successfully sends password reset when reCAPTCHA is not solved' do - perform_request + context 'when reCAPTCHA is disabled' do + before do + stub_application_setting(recaptcha_enabled: false) + end - expect(response).to redirect_to(dashboard_projects_path) - end - end + it 'successfully sends password reset when reCAPTCHA is not solved' do + perform_request - context 'when reCAPTCHA is enabled' do - before do - stub_application_setting(recaptcha_enabled: true) + expect(response).to redirect_to(dashboard_projects_path) + end end - context 'when the reCAPTCHA is not solved' do + context 'when reCAPTCHA is enabled' do before do - Recaptcha.configuration.skip_verify_env.delete('test') + stub_application_setting(recaptcha_enabled: true) end - it 'displays an error' do - perform_request + context 'when the reCAPTCHA is not solved' do + before do + Recaptcha.configuration.skip_verify_env.delete('test') + end + + it 'displays an error' do + perform_request + + expect(response).to render_template(:new) + expect(flash[:alert]).to include _('There was an error with the reCAPTCHA.') + end + + it 'sets gon variables' do + Gon.clear - expect(response).to render_template(:new) - expect(flash[:alert]).to include _('There was an error with the reCAPTCHA. Please solve the reCAPTCHA again.') + perform_request + + expect(response).to render_template(:new) + expect(Gon.all_variables).not_to be_empty + end end - it 'sets gon variables' do - Gon.clear + it 'successfully sends password reset when reCAPTCHA is solved' do + Recaptcha.configuration.skip_verify_env << 'test' perform_request - expect(response).to render_template(:new) - expect(Gon.all_variables).not_to be_empty + expect(response).to redirect_to(dashboard_projects_path) end end + end - it 'successfully sends password reset when reCAPTCHA is solved' do - Recaptcha.configuration.skip_verify_env << 'test' + context "when `email_confirmation_setting` is not set to `soft`" do + before do + stub_feature_flags(soft_email_confirmation: false) + end + it 'redirects to the users_almost_there path' do perform_request - expect(response).to redirect_to(dashboard_projects_path) + expect(response).to redirect_to(users_almost_there_path) end end end diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb index ab3f3fd397d..1f78314c372 100644 --- a/spec/controllers/omniauth_callbacks_controller_spec.rb +++ b/spec/controllers/omniauth_callbacks_controller_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe OmniauthCallbacksController, type: :controller do +RSpec.describe OmniauthCallbacksController, type: :controller, feature_category: :system_access do include LoginHelpers describe 'omniauth' do @@ -202,20 +202,30 @@ RSpec.describe OmniauthCallbacksController, type: :controller do end end - context 'when user with 2FA is unconfirmed' do + context 'when a user has 2FA enabled' do render_views let(:user) { create(:omniauth_user, :two_factor, extern_uid: 'my-uid', provider: provider) } - before do - user.update_column(:confirmed_at, nil) - end + context 'when a user is unconfirmed' do + before do + stub_application_setting_enum('email_confirmation_setting', 'hard') - it 'redirects to login page' do - post provider + user.update!(confirmed_at: nil) + end - expect(response).to redirect_to(new_user_session_path) - expect(flash[:alert]).to match(/You have to confirm your email address before continuing./) + it 'redirects to login page' do + post provider + + expect(response).to redirect_to(new_user_session_path) + expect(flash[:alert]).to match(/You have to confirm your email address before continuing./) + end + end + + context 'when a user is confirmed' do + it 'returns 200 response' do + expect(response).to have_gitlab_http_status(:ok) + end end end diff --git a/spec/controllers/registrations/welcome_controller_spec.rb b/spec/controllers/registrations/welcome_controller_spec.rb index af7891a17cb..3c631362119 100644 --- a/spec/controllers/registrations/welcome_controller_spec.rb +++ b/spec/controllers/registrations/welcome_controller_spec.rb @@ -57,6 +57,32 @@ RSpec.describe Registrations::WelcomeController, feature_category: :system_acces expect(subject).not_to redirect_to(profile_two_factor_auth_path) end end + + context 'when welcome step is completed' do + before do + user.update!(setup_for_company: true) + end + + context 'when user is confirmed' do + before do + sign_in(user) + end + + it { is_expected.to redirect_to dashboard_projects_path } + end + + context 'when user is not confirmed' do + before do + stub_application_setting_enum('email_confirmation_setting', 'hard') + + sign_in(user) + + user.update!(confirmed_at: nil) + end + + it { is_expected.to redirect_to user_session_path } + end + end end describe '#update' do diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb index 54b8f0c6f3b..92329b10426 100644 --- a/spec/controllers/registrations_controller_spec.rb +++ b/spec/controllers/registrations_controller_spec.rb @@ -75,7 +75,7 @@ RSpec.describe RegistrationsController, feature_category: :user_profile do end context 'email confirmation' do - context 'when `email_confirmation_setting` is set to `hard`' do + context 'when email confirmation setting is set to `hard`' do before do stub_application_setting_enum('email_confirmation_setting', 'hard') end @@ -122,7 +122,7 @@ RSpec.describe RegistrationsController, feature_category: :user_profile do end context 'email confirmation' do - context 'when `email_confirmation_setting` is set to `hard`' do + context 'when email confirmation setting is set to `hard`' do before do stub_application_setting_enum('email_confirmation_setting', 'hard') stub_feature_flags(identity_verification: false) @@ -157,7 +157,7 @@ RSpec.describe RegistrationsController, feature_category: :user_profile do stub_feature_flags(identity_verification: false) end - context 'when `email_confirmation_setting` is set to `off`' do + context 'when email confirmation setting is set to `off`' do it 'signs the user in' do stub_application_setting_enum('email_confirmation_setting', 'off') @@ -166,103 +166,97 @@ RSpec.describe RegistrationsController, feature_category: :user_profile do end end - context 'when `email_confirmation_setting` is set to `hard`' do + context 'when email confirmation setting is set to `hard`' do before do stub_application_setting_enum('email_confirmation_setting', 'hard') + allow(User).to receive(:allow_unconfirmed_access_for).and_return 0 end - context 'when soft email confirmation is not enabled' do - before do - stub_feature_flags(soft_email_confirmation: false) - allow(User).to receive(:allow_unconfirmed_access_for).and_return 0 - end + it 'does not authenticate the user and sends a confirmation email' do + expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions) + expect(controller.current_user).to be_nil + end - it 'does not authenticate the user and sends a confirmation email' do - expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions) - expect(controller.current_user).to be_nil - end + it 'tracks an almost there redirect' do + post_create - it 'tracks an almost there redirect' do - post_create + expect_snowplow_event( + category: described_class.name, + action: 'render', + user: User.find_by(email: base_user_params[:email]) + ) + end - expect_snowplow_event( - category: described_class.name, - action: 'render', - user: User.find_by(email: base_user_params[:email]) - ) - end + context 'when registration is triggered from an accepted invite' do + context 'when it is part from the initial invite email', :snowplow do + let_it_be(:member) { create(:project_member, :invited, invite_email: user_params.dig(:user, :email)) } - context 'when registration is triggered from an accepted invite' do - context 'when it is part from the initial invite email', :snowplow do - let_it_be(:member) { create(:project_member, :invited, invite_email: user_params.dig(:user, :email)) } + let(:originating_member_id) { member.id } + let(:session_params) do + { + invite_email: user_params.dig(:user, :email), + originating_member_id: originating_member_id + } + end - let(:originating_member_id) { member.id } - let(:session_params) do - { - invite_email: user_params.dig(:user, :email), - originating_member_id: originating_member_id - } + context 'when member exists from the session key value' do + it 'tracks the invite acceptance' do + subject + + expect_snowplow_event( + category: 'RegistrationsController', + action: 'accepted', + label: 'invite_email', + property: member.id.to_s, + user: member.reload.user + ) + + expect_snowplow_event( + category: 'RegistrationsController', + action: 'create_user', + label: 'invited', + user: member.reload.user + ) end + end - context 'when member exists from the session key value' do - it 'tracks the invite acceptance' do - subject - - expect_snowplow_event( - category: 'RegistrationsController', - action: 'accepted', - label: 'invite_email', - property: member.id.to_s, - user: member.reload.user - ) - - expect_snowplow_event( - category: 'RegistrationsController', - action: 'create_user', - label: 'invited', - user: member.reload.user - ) - end - end + context 'when member does not exist from the session key value' do + let(:originating_member_id) { nil } + + it 'does not track invite acceptance' do + subject - context 'when member does not exist from the session key value' do - let(:originating_member_id) { nil } - - it 'does not track invite acceptance' do - subject - - expect_no_snowplow_event( - category: 'RegistrationsController', - action: 'accepted', - label: 'invite_email' - ) - - expect_snowplow_event( - category: 'RegistrationsController', - action: 'create_user', - label: 'signup', - user: member.reload.user - ) - end + expect_no_snowplow_event( + category: 'RegistrationsController', + action: 'accepted', + label: 'invite_email' + ) + + expect_snowplow_event( + category: 'RegistrationsController', + action: 'create_user', + label: 'signup', + user: member.reload.user + ) end end + end - context 'when invite email matches email used on registration' do - let(:session_params) { { invite_email: user_params.dig(:user, :email) } } + context 'when invite email matches email used on registration' do + let(:session_params) { { invite_email: user_params.dig(:user, :email) } } - it 'signs the user in without sending a confirmation email', :aggregate_failures do - expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions) - expect(controller.current_user).to be_confirmed - end + it 'signs the user in without sending a confirmation email', :aggregate_failures do + expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions) + expect(controller.current_user).to be_confirmed end + end - context 'when invite email does not match the email used on registration' do - let(:session_params) { { invite_email: 'bogus@email.com' } } + context 'when invite email does not match the email used on registration' do + let(:session_params) { { invite_email: 'bogus@email.com' } } - it 'does not authenticate the user and sends a confirmation email', :aggregate_failures do - expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions) - expect(controller.current_user).to be_nil - end + it 'does not authenticate the user and sends a confirmation email', :aggregate_failures do + expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions) + expect(controller.current_user).to be_nil end end end @@ -286,45 +280,45 @@ RSpec.describe RegistrationsController, feature_category: :user_profile do expect(controller.current_user).to be_nil end end + end - context 'when soft email confirmation is enabled' do - before do - stub_feature_flags(soft_email_confirmation: true) - allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days - end + context 'when email confirmation setting is set to `soft`' do + before do + stub_application_setting_enum('email_confirmation_setting', 'soft') + allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days + end - it 'authenticates the user and sends a confirmation email' do - expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions) - expect(controller.current_user).to be_present - expect(response).to redirect_to(users_sign_up_welcome_path) - end + it 'authenticates the user and sends a confirmation email' do + expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions) + expect(controller.current_user).to be_present + expect(response).to redirect_to(users_sign_up_welcome_path) + end - it 'does not track an almost there redirect' do - post_create + it 'does not track an almost there redirect' do + post_create - expect_no_snowplow_event( - category: described_class.name, - action: 'render', - user: User.find_by(email: base_user_params[:email]) - ) - end + expect_no_snowplow_event( + category: described_class.name, + action: 'render', + user: User.find_by(email: base_user_params[:email]) + ) + end - context 'when invite email matches email used on registration' do - let(:session_params) { { invite_email: user_params.dig(:user, :email) } } + context 'when invite email matches email used on registration' do + let(:session_params) { { invite_email: user_params.dig(:user, :email) } } - it 'signs the user in without sending a confirmation email', :aggregate_failures do - expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions) - expect(controller.current_user).to be_confirmed - end + it 'signs the user in without sending a confirmation email', :aggregate_failures do + expect { subject }.not_to have_enqueued_mail(DeviseMailer, :confirmation_instructions) + expect(controller.current_user).to be_confirmed end + end - context 'when invite email does not match the email used on registration' do - let(:session_params) { { invite_email: 'bogus@email.com' } } + context 'when invite email does not match the email used on registration' do + let(:session_params) { { invite_email: 'bogus@email.com' } } - it 'authenticates the user and sends a confirmation email without confirming', :aggregate_failures do - expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions) - expect(controller.current_user).not_to be_confirmed - end + it 'authenticates the user and sends a confirmation email without confirming', :aggregate_failures do + expect { subject }.to have_enqueued_mail(DeviseMailer, :confirmation_instructions) + expect(controller.current_user).not_to be_confirmed end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 224f460488b..c44eef0ab37 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -694,7 +694,7 @@ FactoryBot.define do end end - trait :non_public_artifacts do + trait :with_private_artifacts_config do options do { artifacts: { public: false } @@ -702,6 +702,14 @@ FactoryBot.define do end end + trait :with_public_artifacts_config do + options do + { + artifacts: { public: true } + } + end + end + trait :non_playable do status { 'created' } self.when { 'manual' } diff --git a/spec/factories/service_desk/custom_email_verification.rb b/spec/factories/service_desk/custom_email_verification.rb new file mode 100644 index 00000000000..614be5da71e --- /dev/null +++ b/spec/factories/service_desk/custom_email_verification.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :service_desk_custom_email_verification, class: '::ServiceDesk::CustomEmailVerification' do + project + state { "running" } + end +end diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb index 1091bea1ce3..cb7e933e472 100644 --- a/spec/features/invites_spec.rb +++ b/spec/features/invites_spec.rb @@ -244,9 +244,8 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate context 'the user sign-up using a different email address' do let(:invite_email) { build_stubbed(:user).email } - context 'when soft email confirmation is not enabled' do + context 'when email confirmation is not set to `soft`' do before do - stub_feature_flags(soft_email_confirmation: false) allow(User).to receive(:allow_unconfirmed_access_for).and_return 0 stub_feature_flags(identity_verification: false) end @@ -261,9 +260,9 @@ RSpec.describe 'Group or Project invitations', :aggregate_failures, feature_cate end end - context 'when soft email confirmation is enabled' do + context 'when email confirmation setting is set to `soft`' do before do - stub_feature_flags(soft_email_confirmation: true) + stub_application_setting_enum('email_confirmation_setting', 'soft') allow(User).to receive(:allow_unconfirmed_access_for).and_return 2.days end diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb index 3da7bd1a780..37b5d80ed61 100644 --- a/spec/features/users/login_spec.rb +++ b/spec/features/users/login_spec.rb @@ -109,6 +109,10 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end context 'within the grace period' do + before do + stub_application_setting_enum('email_confirmation_setting', 'soft') + end + it 'allows to login' do expect(authentication_metrics).to increment(:user_authenticated_counter) @@ -137,11 +141,9 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ end context 'when resending the confirmation email' do - it 'redirects to the "almost there" page' do - stub_feature_flags(soft_email_confirmation: false) - - user = create(:user) + let_it_be(:user) { create(:user) } + it 'redirects to the "almost there" page' do visit new_user_confirmation_path fill_in 'user_email', with: user.email click_button 'Resend' @@ -971,8 +973,7 @@ RSpec.describe 'Login', :clean_gitlab_redis_sessions, feature_category: :system_ let(:alert_message) { "To continue, you need to select the link in the confirmation email we sent to verify your email address. If you didn't get our email, select Resend confirmation email" } before do - stub_application_setting_enum('email_confirmation_setting', 'hard') - stub_feature_flags(soft_email_confirmation: true) + stub_application_setting_enum('email_confirmation_setting', 'soft') stub_feature_flags(identity_verification: false) allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period end diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb index 6333af28f85..a762198d3c3 100644 --- a/spec/features/users/signup_spec.rb +++ b/spec/features/users/signup_spec.rb @@ -200,9 +200,8 @@ RSpec.describe 'Signup', feature_category: :user_profile do stub_application_setting_enum('email_confirmation_setting', 'hard') end - context 'when soft email confirmation is not enabled' do + context 'when email confirmation setting is not `soft`' do before do - stub_feature_flags(soft_email_confirmation: false) stub_feature_flags(identity_verification: false) end @@ -221,9 +220,9 @@ RSpec.describe 'Signup', feature_category: :user_profile do end end - context 'when soft email confirmation is enabled' do + context 'when email confirmation setting is `soft`' do before do - stub_feature_flags(soft_email_confirmation: true) + stub_application_setting_enum('email_confirmation_setting', 'soft') end it 'creates the user account and sends a confirmation email' do diff --git a/spec/frontend/sentry/sentry_config_spec.js b/spec/frontend/sentry/sentry_config_spec.js index 44acbee9b38..34c5221ef0d 100644 --- a/spec/frontend/sentry/sentry_config_spec.js +++ b/spec/frontend/sentry/sentry_config_spec.js @@ -1,5 +1,4 @@ import * as Sentry from 'sentrybrowser7'; -import { IGNORE_ERRORS, DENY_URLS, SAMPLE_RATE } from '~/sentry/constants'; import SentryConfig from '~/sentry/sentry_config'; @@ -62,11 +61,8 @@ describe('SentryConfig', () => { expect(Sentry.init).toHaveBeenCalledWith({ dsn: options.dsn, release: options.release, - sampleRate: SAMPLE_RATE, allowUrls: options.allowUrls, environment: options.environment, - ignoreErrors: IGNORE_ERRORS, - denyUrls: DENY_URLS, }); }); @@ -82,11 +78,8 @@ describe('SentryConfig', () => { expect(Sentry.init).toHaveBeenCalledWith({ dsn: options.dsn, release: options.release, - sampleRate: SAMPLE_RATE, allowUrls: options.allowUrls, environment: 'development', - ignoreErrors: IGNORE_ERRORS, - denyUrls: DENY_URLS, }); }); }); diff --git a/spec/lib/gitlab/background_migration/delete_orphaned_packages_dependencies_spec.rb b/spec/lib/gitlab/background_migration/delete_orphaned_packages_dependencies_spec.rb new file mode 100644 index 00000000000..0d82717c7de --- /dev/null +++ b/spec/lib/gitlab/background_migration/delete_orphaned_packages_dependencies_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::BackgroundMigration::DeleteOrphanedPackagesDependencies, schema: 20230303105806, + feature_category: :package_registry do + let!(:migration_attrs) do + { + start_id: 1, + end_id: 1000, + batch_table: :packages_dependencies, + batch_column: :id, + sub_batch_size: 500, + pause_ms: 0, + connection: ApplicationRecord.connection + } + end + + let!(:migration) { described_class.new(**migration_attrs) } + + let(:packages_dependencies) { table(:packages_dependencies) } + + let!(:namespace) { table(:namespaces).create!(name: 'project', path: 'project', type: 'Project') } + let!(:project) do + table(:projects).create!(name: 'project', path: 'project', project_namespace_id: namespace.id, + namespace_id: namespace.id) + end + + let!(:package) do + table(:packages_packages).create!(name: 'test', version: '1.2.3', package_type: 2, project_id: project.id) + end + + let!(:orphan_dependency_1) { packages_dependencies.create!(name: 'dependency 1', version_pattern: '~0.0.1') } + let!(:orphan_dependency_2) { packages_dependencies.create!(name: 'dependency 2', version_pattern: '~0.0.2') } + let!(:orphan_dependency_3) { packages_dependencies.create!(name: 'dependency 3', version_pattern: '~0.0.3') } + let!(:linked_dependency) do + packages_dependencies.create!(name: 'dependency 4', version_pattern: '~0.0.4').tap do |dependency| + table(:packages_dependency_links).create!(package_id: package.id, dependency_id: dependency.id, + dependency_type: 'dependencies') + end + end + + subject(:perform_migration) { migration.perform } + + it 'executes 3 queries' do + queries = ActiveRecord::QueryRecorder.new do + perform_migration + end + + expect(queries.count).to eq(3) + end + + it 'deletes only orphaned dependencies' do + expect { perform_migration }.to change { packages_dependencies.count }.by(-3) + expect(packages_dependencies.all).to eq([linked_dependency]) + end +end diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 73f29f6debe..ebd2db63fe5 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -722,6 +722,7 @@ project: - upstream_project_subscriptions - downstream_project_subscriptions - service_desk_setting +- service_desk_custom_email_verification - security_setting - import_failures - container_expiration_policy @@ -963,6 +964,8 @@ bulk_import_export: - group service_desk_setting: - file_template_project +service_desk_custom_email_verification: + - triggerer approvals: - user - merge_request diff --git a/spec/lib/gitlab/import_export/command_line_util_spec.rb b/spec/lib/gitlab/import_export/command_line_util_spec.rb index f47f1ab58a8..91cfab1688a 100644 --- a/spec/lib/gitlab/import_export/command_line_util_spec.rb +++ b/spec/lib/gitlab/import_export/command_line_util_spec.rb @@ -2,13 +2,14 @@ require 'spec_helper' -RSpec.describe Gitlab::ImportExport::CommandLineUtil do +RSpec.describe Gitlab::ImportExport::CommandLineUtil, feature_category: :importers do include ExportFileHelper let(:path) { "#{Dir.tmpdir}/symlink_test" } let(:archive) { 'spec/fixtures/symlink_export.tar.gz' } let(:shared) { Gitlab::ImportExport::Shared.new(nil) } let(:tmpdir) { Dir.mktmpdir } + let(:archive_dir) { Dir.mktmpdir } subject do Class.new do @@ -25,20 +26,38 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do before do FileUtils.mkdir_p(path) - subject.untar_zxf(archive: archive, dir: path) end after do FileUtils.rm_rf(path) + FileUtils.rm_rf(archive_dir) FileUtils.remove_entry(tmpdir) end - it 'has the right mask for project.json' do - expect(file_permissions("#{path}/project.json")).to eq(0755) # originally 777 - end - - it 'has the right mask for uploads' do - expect(file_permissions("#{path}/uploads")).to eq(0755) # originally 555 + shared_examples 'deletes symlinks' do |compression, decompression| + it 'deletes the symlinks', :aggregate_failures do + Dir.mkdir("#{tmpdir}/.git") + Dir.mkdir("#{tmpdir}/folder") + FileUtils.touch("#{tmpdir}/file.txt") + FileUtils.touch("#{tmpdir}/folder/file.txt") + FileUtils.touch("#{tmpdir}/.gitignore") + FileUtils.touch("#{tmpdir}/.git/config") + File.symlink('file.txt', "#{tmpdir}/.symlink") + File.symlink('file.txt', "#{tmpdir}/.git/.symlink") + File.symlink('file.txt', "#{tmpdir}/folder/.symlink") + archive = File.join(archive_dir, 'archive') + subject.public_send(compression, archive: archive, dir: tmpdir) + + subject.public_send(decompression, archive: archive, dir: archive_dir) + + expect(File.exist?("#{archive_dir}/file.txt")).to eq(true) + expect(File.exist?("#{archive_dir}/folder/file.txt")).to eq(true) + expect(File.exist?("#{archive_dir}/.gitignore")).to eq(true) + expect(File.exist?("#{archive_dir}/.git/config")).to eq(true) + expect(File.exist?("#{archive_dir}/.symlink")).to eq(false) + expect(File.exist?("#{archive_dir}/.git/.symlink")).to eq(false) + expect(File.exist?("#{archive_dir}/folder/.symlink")).to eq(false) + end end describe '#download_or_copy_upload' do @@ -228,12 +247,6 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do end describe '#tar_cf' do - let(:archive_dir) { Dir.mktmpdir } - - after do - FileUtils.remove_entry(archive_dir) - end - it 'archives a folder without compression' do archive_file = File.join(archive_dir, 'archive.tar') @@ -256,12 +269,24 @@ RSpec.describe Gitlab::ImportExport::CommandLineUtil do end end - describe '#untar_xf' do - let(:archive_dir) { Dir.mktmpdir } + describe '#untar_zxf' do + it_behaves_like 'deletes symlinks', :tar_czf, :untar_zxf - after do - FileUtils.remove_entry(archive_dir) + it 'has the right mask for project.json' do + subject.untar_zxf(archive: archive, dir: path) + + expect(file_permissions("#{path}/project.json")).to eq(0755) # originally 777 + end + + it 'has the right mask for uploads' do + subject.untar_zxf(archive: archive, dir: path) + + expect(file_permissions("#{path}/uploads")).to eq(0755) # originally 555 end + end + + describe '#untar_xf' do + it_behaves_like 'deletes symlinks', :tar_cf, :untar_xf it 'extracts archive without decompression' do filename = 'archive.tar.gz' diff --git a/spec/migrations/20230303105806_queue_delete_orphaned_packages_dependencies_spec.rb b/spec/migrations/20230303105806_queue_delete_orphaned_packages_dependencies_spec.rb new file mode 100644 index 00000000000..7fe90a08763 --- /dev/null +++ b/spec/migrations/20230303105806_queue_delete_orphaned_packages_dependencies_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' +require_migration! + +RSpec.describe QueueDeleteOrphanedPackagesDependencies, feature_category: :package_registry do + let!(:batched_migration) { described_class::MIGRATION } + + it 'schedules a new batched migration' do + reversible_migration do |migration| + migration.before -> { + expect(batched_migration).not_to have_scheduled_batched_migration + } + + migration.after -> { + expect(batched_migration).to have_scheduled_batched_migration( + table_name: :packages_dependencies, + column_name: :id, + interval: described_class::DELAY_INTERVAL, + batch_size: described_class::BATCH_SIZE, + sub_batch_size: described_class::SUB_BATCH_SIZE + ) + } + end + end +end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb index 29540507faf..113b07c320b 100644 --- a/spec/models/application_setting_spec.rb +++ b/spec/models/application_setting_spec.rb @@ -1525,4 +1525,50 @@ RSpec.describe ApplicationSetting, feature_category: :shared, type: :model do expect(setting.personal_access_tokens_disabled?).to eq(false) end end + + describe 'email_confirmation_setting prefixes' do + before do + described_class.create_from_defaults + end + + context 'when feature flag `soft_email_confirmation` is not enabled' do + before do + stub_feature_flags(soft_email_confirmation: false) + end + + where(:email_confirmation_setting, :off, :soft, :hard) do + 'off' | true | false | false + 'soft' | false | true | false + 'hard' | false | false | true + end + + with_them do + it 'returns the correct value when prefixed' do + stub_application_setting_enum('email_confirmation_setting', email_confirmation_setting) + + expect(described_class.last.email_confirmation_setting_off?).to be off + expect(described_class.last.email_confirmation_setting_soft?).to be soft + expect(described_class.last.email_confirmation_setting_hard?).to be hard + end + end + + it 'calls super' do + expect(described_class.last.email_confirmation_setting_off?).to be true + expect(described_class.last.email_confirmation_setting_soft?).to be false + expect(described_class.last.email_confirmation_setting_hard?).to be false + end + end + + context 'when feature flag `soft_email_confirmation` is enabled' do + before do + stub_feature_flags(soft_email_confirmation: true) + end + + it 'returns correct value when enum is prefixed' do + expect(described_class.last.email_confirmation_setting_off?).to be false + expect(described_class.last.email_confirmation_setting_soft?).to be true + expect(described_class.last.email_confirmation_setting_hard?).to be false + end + end + end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index abd29a12b47..b00f654f1be 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1040,7 +1040,7 @@ RSpec.describe Ci::Build, feature_category: :continuous_integration, factory_def end context 'non public artifacts' do - let(:build) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) } + let(:build) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline) } it { is_expected.to be_falsey } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 03aa6d60796..e7390c99b64 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -89,6 +89,7 @@ RSpec.describe Project, factory_default: :keep, feature_category: :projects do it { is_expected.to have_one(:alerting_setting).class_name('Alerting::ProjectAlertingSetting') } it { is_expected.to have_one(:mock_ci_integration) } it { is_expected.to have_one(:mock_monitoring_integration) } + it { is_expected.to have_one(:service_desk_custom_email_verification).class_name('ServiceDesk::CustomEmailVerification') } it { is_expected.to have_many(:commit_statuses) } it { is_expected.to have_many(:ci_pipelines) } it { is_expected.to have_many(:ci_refs) } diff --git a/spec/models/service_desk/custom_email_verification_spec.rb b/spec/models/service_desk/custom_email_verification_spec.rb new file mode 100644 index 00000000000..f0a6028f21d --- /dev/null +++ b/spec/models/service_desk/custom_email_verification_spec.rb @@ -0,0 +1,109 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe ServiceDesk::CustomEmailVerification, feature_category: :service_desk do + let(:user) { build_stubbed(:user) } + let(:project) { build_stubbed(:project) } + let(:verification) { build_stubbed(:service_desk_custom_email_verification, project: project) } + let(:token) { 'XXXXXXXXXXXX' } + + describe '.generate_token' do + it 'matches expected output' do + expect(described_class.generate_token).to match(/\A\p{Alnum}{12}\z/) + end + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:state) } + end + + describe '#accepted_until' do + context 'when no custom email is set up' do + it 'returns nil' do + expect(subject.accepted_until).to be_nil + end + end + + context 'when custom email is set up' do + subject { verification.accepted_until } + + it { is_expected.to be_nil } + + context 'when verification process started' do + let(:triggered_at) { 2.minutes.ago } + + before do + verification.assign_attributes( + state: "running", + triggered_at: triggered_at, + triggerer: user, + token: token + ) + end + + it { is_expected.to eq(described_class::TIMEFRAME.since(triggered_at)) } + end + end + end + + describe '#in_timeframe?' do + context 'when no custom email is set up' do + it 'returns false' do + expect(subject).not_to be_in_timeframe + end + end + + context 'when custom email is set up' do + it { is_expected.not_to be_in_timeframe } + + context 'when verification process started' do + let(:triggered_at) { 1.second.ago } + + before do + subject.assign_attributes( + state: "running", + triggered_at: triggered_at, + triggerer: user, + token: token + ) + end + + it { is_expected.to be_in_timeframe } + + context 'and timeframe was missed' do + let(:triggered_at) { (described_class::TIMEFRAME + 1).ago } + + before do + subject.triggered_at = triggered_at + end + + it { is_expected.not_to be_in_timeframe } + end + end + end + end + + describe 'encrypted #token' do + subject { build_stubbed(:service_desk_custom_email_verification, token: token) } + + it 'saves and retrieves the encrypted token and iv correctly' do + expect(subject.encrypted_token).not_to be_nil + expect(subject.encrypted_token_iv).not_to be_nil + + expect(subject.token).to eq(token) + end + end + + describe 'associations' do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:triggerer) } + + it 'can access service desk setting from project' do + setting = build_stubbed(:service_desk_setting, project: project) + + expect(verification.service_desk_setting).to eq(setting) + end + end +end diff --git a/spec/models/service_desk_setting_spec.rb b/spec/models/service_desk_setting_spec.rb index 32c36375a3d..b99494e6736 100644 --- a/spec/models/service_desk_setting_spec.rb +++ b/spec/models/service_desk_setting_spec.rb @@ -3,6 +3,9 @@ require 'spec_helper' RSpec.describe ServiceDeskSetting, feature_category: :service_desk do + let(:verification) { build(:service_desk_custom_email_verification) } + let(:project) { build(:project) } + describe 'validations' do subject(:service_desk_setting) { create(:service_desk_setting) } @@ -23,6 +26,8 @@ RSpec.describe ServiceDeskSetting, feature_category: :service_desk do context 'when custom_email_enabled is true' do before do + # Test without ServiceDesk::CustomEmailVerification for simplicity + # See dedicated simplified tests below subject.custom_email_enabled = true end @@ -55,7 +60,18 @@ RSpec.describe ServiceDeskSetting, feature_category: :service_desk do it { is_expected.not_to allow_value('/example').for(:custom_email_smtp_address) } end - describe '.valid_issue_template' do + context 'when custom email verification is present/was triggered' do + before do + subject.project.service_desk_custom_email_verification = verification + end + + it { is_expected.to validate_presence_of(:custom_email) } + it { is_expected.to validate_presence_of(:custom_email_smtp_username) } + it { is_expected.to validate_presence_of(:custom_email_smtp_port) } + it { is_expected.to validate_presence_of(:custom_email_smtp_address) } + end + + describe '#valid_issue_template' do let_it_be(:project) { create(:project, :custom_repo, files: { '.gitlab/issue_templates/service_desk.md' => 'template' }) } it 'is not valid if template does not exist' do @@ -73,7 +89,20 @@ RSpec.describe ServiceDeskSetting, feature_category: :service_desk do end end - describe '.valid_project_key' do + describe '#custom_email_address_for_verification' do + it 'returns nil' do + expect(subject.custom_email_address_for_verification).to be_nil + end + + context 'when custom_email exists' do + it 'returns correct verification address' do + subject.custom_email = 'support@example.com' + expect(subject.custom_email_address_for_verification).to eq('support+verify@example.com') + end + end + end + + describe '#valid_project_key' do # Creates two projects with same full path slug # group1/test/one and group1/test-one will both have 'group-test-one' slug let_it_be(:group) { create(:group) } @@ -109,15 +138,15 @@ RSpec.describe ServiceDeskSetting, feature_category: :service_desk do end end - describe 'encrypted password' do + describe 'encrypted #custom_email_smtp_password' do let_it_be(:settings) do create( :service_desk_setting, custom_email_enabled: true, - custom_email: 'supersupport@example.com', + custom_email: 'support@example.com', custom_email_smtp_address: 'smtp.example.com', custom_email_smtp_port: 587, - custom_email_smtp_username: 'supersupport@example.com', + custom_email_smtp_username: 'support@example.com', custom_email_smtp_password: 'supersecret' ) end @@ -131,6 +160,24 @@ RSpec.describe ServiceDeskSetting, feature_category: :service_desk do end describe 'associations' do + let(:custom_email_settings) do + build_stubbed( + :service_desk_setting, + custom_email: 'support@example.com', + custom_email_smtp_address: 'smtp.example.com', + custom_email_smtp_port: 587, + custom_email_smtp_username: 'support@example.com', + custom_email_smtp_password: 'supersecret' + ) + end + it { is_expected.to belong_to(:project) } + + it 'can access custom email verification from project' do + project.service_desk_custom_email_verification = verification + custom_email_settings.project = project + + expect(custom_email_settings.custom_email_verification).to eq(verification) + end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 7c3eee31f7b..eab0771fe83 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -7102,42 +7102,104 @@ RSpec.describe User, feature_category: :user_profile do context 'when user is confirmed' do let(:user) { create(:user) } - it 'is falsey' do - expect(user.confirmed?).to be_truthy - expect(subject).to be_falsey + it 'is false' do + expect(user.confirmed?).to be(true) + expect(subject).to be(false) end end context 'when user is not confirmed' do let_it_be(:user) { build_stubbed(:user, :unconfirmed, confirmation_sent_at: Time.current) } - it 'is truthy when soft_email_confirmation feature is disabled' do - stub_feature_flags(soft_email_confirmation: false) - expect(subject).to be_truthy + context 'when email confirmation setting is set to `off`' do + before do + stub_application_setting_enum('email_confirmation_setting', 'off') + end + + it { is_expected.to be(false) } end - context 'when soft_email_confirmation feature is enabled' do + context 'when email confirmation setting is set to `soft`' do before do - stub_feature_flags(soft_email_confirmation: true) + stub_application_setting_enum('email_confirmation_setting', 'soft') end - it 'is falsey when confirmation period is valid' do - expect(subject).to be_falsey + context 'when confirmation period is valid' do + it { is_expected.to be(false) } end - it 'is truthy when confirmation period is expired' do - travel_to(User.allow_unconfirmed_access_for.from_now + 1.day) do - expect(subject).to be_truthy + context 'when confirmation period is expired' do + before do + travel_to(User.allow_unconfirmed_access_for.from_now + 1.day) end + + it { is_expected.to be(true) } end context 'when user has no confirmation email sent' do let(:user) { build(:user, :unconfirmed, confirmation_sent_at: nil) } - it 'is truthy' do - expect(subject).to be_truthy - end + it { is_expected.to be(true) } + end + end + + context 'when email confirmation setting is set to `hard`' do + before do + stub_application_setting_enum('email_confirmation_setting', 'hard') + end + + it { is_expected.to be(true) } + end + end + end + + describe '#confirmation_period_valid?' do + subject { user.send(:confirmation_period_valid?) } + + let_it_be(:user) { create(:user) } + + context 'when email confirmation setting is set to `off`' do + before do + stub_feature_flags(soft_email_confirmation: false) + end + + it { is_expected.to be(true) } + end + + context 'when email confirmation setting is set to `soft`' do + before do + stub_application_setting_enum('email_confirmation_setting', 'soft') + end + + context 'when within confirmation window' do + before do + user.update!(confirmation_sent_at: Date.today) + end + + it { is_expected.to be(true) } + end + + context 'when outside confirmation window' do + before do + user.update!(confirmation_sent_at: Date.today - described_class.confirm_within - 7.days) end + + it { is_expected.to be(false) } + end + end + + context 'when email confirmation setting is set to `hard`' do + before do + stub_feature_flags(soft_email_confirmation: false) + stub_application_setting_enum('email_confirmation_setting', 'hard') + end + + it { is_expected.to be(true) } + end + + describe '#in_confirmation_period?' do + it 'is expected to be an alias' do + expect(user.method(:in_confirmation_period?).original_name).to eq(:confirmation_period_valid?) end end end diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb index 4ecc7e9ac5e..3d6d95bb122 100644 --- a/spec/policies/global_policy_spec.rb +++ b/spec/policies/global_policy_spec.rb @@ -292,6 +292,7 @@ RSpec.describe GlobalPolicy, feature_category: :shared do context 'inactive user' do before do + stub_application_setting_enum('email_confirmation_setting', 'soft') current_user.update!(confirmed_at: nil, confirmation_sent_at: 5.days.ago) end @@ -412,6 +413,7 @@ RSpec.describe GlobalPolicy, feature_category: :shared do describe 'inactive user' do before do + stub_application_setting_enum('email_confirmation_setting', 'soft') current_user.update!(confirmed_at: nil) end @@ -516,6 +518,7 @@ RSpec.describe GlobalPolicy, feature_category: :shared do describe 'inactive user' do before do + stub_application_setting_enum('email_confirmation_setting', 'soft') current_user.update!(confirmed_at: nil) end diff --git a/spec/requests/api/ci/job_artifacts_spec.rb b/spec/requests/api/ci/job_artifacts_spec.rb index ee390773f29..7cea744cdb9 100644 --- a/spec/requests/api/ci/job_artifacts_spec.rb +++ b/spec/requests/api/ci/job_artifacts_spec.rb @@ -190,7 +190,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do end context 'when project is public with artifacts that are non public' do - let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) } + let(:job) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline) } it 'rejects access to artifacts' do project.update_column(:visibility_level, @@ -439,7 +439,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do context 'when public project guest and artifacts are non public' do let(:api_user) { guest } - let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) } + let(:job) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline) } before do project.update_column(:visibility_level, @@ -644,7 +644,7 @@ RSpec.describe API::Ci::JobArtifacts, feature_category: :build_artifacts do end context 'when project is public with non public artifacts' do - let(:job) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline, user: api_user) } + let(:job) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline, user: api_user) } let(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC } let(:public_builds) { true } diff --git a/spec/requests/api/oauth_tokens_spec.rb b/spec/requests/api/oauth_tokens_spec.rb index 2d3215543d2..19a943477d2 100644 --- a/spec/requests/api/oauth_tokens_spec.rb +++ b/spec/requests/api/oauth_tokens_spec.rb @@ -124,6 +124,8 @@ RSpec.describe 'OAuth tokens', feature_category: :system_access do context 'when user account is not confirmed' do before do + stub_application_setting_enum('email_confirmation_setting', 'soft') + user.update!(confirmed_at: nil) request_oauth_token(user, client_basic_auth_header(client)) diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index 916798c669c..ea3826f903a 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -285,7 +285,7 @@ RSpec.describe BuildDetailsEntity do end context 'when the build has non public archive type artifacts' do - let(:build) { create(:ci_build, :artifacts, :non_public_artifacts, pipeline: pipeline) } + let(:build) { create(:ci_build, :artifacts, :with_private_artifacts_config, pipeline: pipeline) } it 'does not expose non public artifacts' do expect(subject.keys).not_to include(:artifact) diff --git a/spec/services/ci/job_artifacts/create_service_spec.rb b/spec/services/ci/job_artifacts/create_service_spec.rb index 1ef06ec7dcd..69f760e28ca 100644 --- a/spec/services/ci/job_artifacts/create_service_spec.rb +++ b/spec/services/ci/job_artifacts/create_service_spec.rb @@ -33,6 +33,66 @@ RSpec.describe Ci::JobArtifacts::CreateService, feature_category: :build_artifac describe '#execute' do subject { service.execute(artifacts_file, params, metadata_file: metadata_file) } + def expect_accessibility_be(accessibility) + if accessibility == :public + expect(job.job_artifacts).to all be_public_accessibility + else + expect(job.job_artifacts).to all be_private_accessibility + end + end + + shared_examples 'job does not have public artifacts in the CI config' do |expected_artifacts_count, accessibility| + it "sets accessibility by default to #{accessibility}" do + expect { subject }.to change { Ci::JobArtifact.count }.by(expected_artifacts_count) + + expect_accessibility_be(accessibility) + end + end + + shared_examples 'job artifact set as private in the CI config' do |expected_artifacts_count, accessibility| + let!(:job) { create(:ci_build, :with_private_artifacts_config, project: project) } + + it "sets accessibility to #{accessibility}" do + expect { subject }.to change { Ci::JobArtifact.count }.by(expected_artifacts_count) + + expect_accessibility_be(accessibility) + end + end + + shared_examples 'job artifact set as public in the CI config' do |expected_artifacts_count, accessibility| + let!(:job) { create(:ci_build, :with_public_artifacts_config, project: project) } + + it "sets accessibility to #{accessibility}" do + expect { subject }.to change { Ci::JobArtifact.count }.by(expected_artifacts_count) + + expect_accessibility_be(accessibility) + end + end + + shared_examples 'when accessibility level passed as private' do |expected_artifacts_count, accessibility| + before do + params.merge!('accessibility' => 'private') + end + + it 'sets accessibility to private level' do + expect { subject }.to change { Ci::JobArtifact.count }.by(expected_artifacts_count) + + expect_accessibility_be(accessibility) + end + end + + shared_examples 'when accessibility passed as public' do |expected_artifacts_count| + before do + params.merge!('accessibility' => 'public') + end + + it 'sets accessibility level to public' do + expect { subject }.to change { Ci::JobArtifact.count }.by(expected_artifacts_count) + + expect(job.job_artifacts).to all be_public_accessibility + end + end + context 'when artifacts file is uploaded' do it 'logs the created artifact' do expect(Gitlab::Ci::Artifacts::Logger) @@ -61,37 +121,19 @@ RSpec.describe Ci::JobArtifacts::CreateService, feature_category: :build_artifac expect(new_artifact.locked).to eq(job.pipeline.locked) end - it 'sets accessibility level by default to public' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_public_accessibility - end - - context 'when accessibility level passed as private' do + context 'when non_public_artifacts feature flag is disabled' do before do - params.merge!('accessibility' => 'private') + stub_feature_flags(non_public_artifacts: false) end - it 'sets accessibility level to private' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_private_accessibility + context 'when accessibility level not passed to the service' do + it_behaves_like 'job does not have public artifacts in the CI config', 1, :public + it_behaves_like 'job artifact set as private in the CI config', 1, :public + it_behaves_like 'job artifact set as public in the CI config', 1, :public end - end - context 'when accessibility passed as public' do - before do - params.merge!('accessibility' => 'public') - end - - it 'sets accessibility to public level' do - expect { subject }.to change { Ci::JobArtifact.count }.by(1) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_public_accessibility - end + it_behaves_like 'when accessibility level passed as private', 1, :public + it_behaves_like 'when accessibility passed as public', 1 end context 'when accessibility passed as invalid value' do @@ -104,6 +146,16 @@ RSpec.describe Ci::JobArtifacts::CreateService, feature_category: :build_artifac end end + context 'when accessibility level not passed to the service' do + it_behaves_like 'job does not have public artifacts in the CI config', 1, :public + it_behaves_like 'job artifact set as private in the CI config', 1, :private + it_behaves_like 'job artifact set as public in the CI config', 1, :public + end + + it_behaves_like 'when accessibility level passed as private', 1, :private + + it_behaves_like 'when accessibility passed as public', 1 + context 'when metadata file is also uploaded' do let(:metadata_file) do file_to_upload('spec/fixtures/ci_build_artifacts_metadata.gz', sha256: artifacts_sha256) @@ -125,13 +177,16 @@ RSpec.describe Ci::JobArtifacts::CreateService, feature_category: :build_artifac expect(new_artifact.locked).to eq(job.pipeline.locked) end - it 'sets accessibility by default to public' do - expect { subject }.to change { Ci::JobArtifact.count }.by(2) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_public_accessibility + context 'when accessibility level not passed to the service' do + it_behaves_like 'job does not have public artifacts in the CI config', 2, :public + it_behaves_like 'job artifact set as private in the CI config', 2, :private + it_behaves_like 'job artifact set as public in the CI config', 2, :public end + it_behaves_like 'when accessibility level passed as private', 2, :privatge + + it_behaves_like 'when accessibility passed as public', 2 + it 'logs the created artifact and metadata' do expect(Gitlab::Ci::Artifacts::Logger) .to receive(:log_created) @@ -140,32 +195,6 @@ RSpec.describe Ci::JobArtifacts::CreateService, feature_category: :build_artifac subject end - context 'when accessibility level passed as private' do - before do - params.merge!('accessibility' => 'private') - end - - it 'sets accessibility to private level' do - expect { subject }.to change { Ci::JobArtifact.count }.by(2) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_private_accessibility - end - end - - context 'when accessibility passed as public' do - before do - params.merge!('accessibility' => 'public') - end - - it 'sets accessibility level to public' do - expect { subject }.to change { Ci::JobArtifact.count }.by(2) - - new_artifact = job.job_artifacts.last - expect(new_artifact).to be_public_accessibility - end - end - it 'sets expiration date according to application settings' do expected_expire_at = 1.day.from_now diff --git a/spec/support/shared_examples/features/secure_oauth_authorizations_shared_examples.rb b/spec/support/shared_examples/features/secure_oauth_authorizations_shared_examples.rb index 028e075c87a..231406289b4 100644 --- a/spec/support/shared_examples/features/secure_oauth_authorizations_shared_examples.rb +++ b/spec/support/shared_examples/features/secure_oauth_authorizations_shared_examples.rb @@ -10,7 +10,7 @@ RSpec.shared_examples 'Secure OAuth Authorizations' do end context 'when user is unconfirmed' do - let(:user) { create(:user, confirmed_at: nil) } + let(:user) { create(:user, :unconfirmed) } it 'displays an error' do expect(page).to have_text I18n.t('doorkeeper.errors.messages.unconfirmed_email') |