summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-02-14 15:09:21 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-02-14 15:09:21 +0000
commita2b7b398c7855bccee5d2f0f9a021b2efea0838e (patch)
tree7185da81b374a95d76659803f89628873f28c91e
parentcacc3815006ab7d3828ebe8903f95154b27a6e21 (diff)
downloadgitlab-ce-a2b7b398c7855bccee5d2f0f9a021b2efea0838e.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--app/assets/javascripts/boards/components/board_list.vue2
-rw-r--r--app/assets/javascripts/merge_requests/components/compare_dropdown.vue17
-rw-r--r--app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js4
-rw-r--r--app/assets/javascripts/repository/mixins/highlight_mixin.js106
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss7
-rw-r--r--app/assets/stylesheets/utilities.scss4
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb36
-rw-r--r--app/models/user.rb33
-rw-r--r--app/views/layouts/header/_sign_in_register_button.html.haml7
-rw-r--r--config/feature_flags/development/ci_includes_count_duplicates.yml8
-rw-r--r--config/feature_flags/development/webauthn_without_totp.yml8
-rw-r--r--data/deprecations/15-9-database-single-database-connection-conf.yml17
-rw-r--r--data/deprecations/15-9-insecure-ci-job-token.yml31
-rw-r--r--data/deprecations/15-9-trigger-job-status.yml42
-rw-r--r--doc/administration/get_started.md2
-rw-r--r--doc/administration/gitaly/index.md2
-rw-r--r--doc/development/elasticsearch.md44
-rw-r--r--doc/development/service_ping/metrics_lifecycle.md2
-rw-r--r--doc/update/deprecations.md53
-rw-r--r--doc/update/plan_your_upgrade.md2
-rw-r--r--doc/user/admin_area/settings/email.md23
-rw-r--r--doc/user/admin_area/settings/usage_statistics.md2
-rw-r--r--doc/user/search/exact_code_search.md14
-rw-r--r--lib/bulk_imports/clients/graphql.rb9
-rw-r--r--lib/gitlab/ci/config/external/context.rb5
-rw-r--r--lib/gitlab/ci/config/external/mapper/verifier.rb6
-rw-r--r--locale/gitlab.pot9
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb133
-rw-r--r--spec/features/webauthn_spec.rb276
-rw-r--r--spec/frontend/boards/board_list_spec.js12
-rw-r--r--spec/frontend/repository/mixins/highlight_mixin_spec.js106
-rw-r--r--spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js4
-rw-r--r--spec/lib/bulk_imports/clients/graphql_spec.rb58
-rw-r--r--spec/lib/gitlab/ci/config/external/context_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/config/external/mapper_spec.rb15
-rw-r--r--spec/models/user_spec.rb1
37 files changed, 923 insertions, 244 deletions
diff --git a/app/assets/javascripts/boards/components/board_list.vue b/app/assets/javascripts/boards/components/board_list.vue
index e86dae536b5..3e15f639ffe 100644
--- a/app/assets/javascripts/boards/components/board_list.vue
+++ b/app/assets/javascripts/boards/components/board_list.vue
@@ -362,7 +362,7 @@ export default {
:data-board="list.id"
:data-board-type="list.listType"
:class="{
- 'bg-danger-100 gl-rounded-bottom-left-base gl-rounded-bottom-right-base': boardItemsSizeExceedsMax,
+ 'gl-bg-red-100 gl-rounded-bottom-left-base gl-rounded-bottom-right-base': boardItemsSizeExceedsMax,
'gl-overflow-hidden': disableScrollingWhenMutationInProgress,
'gl-overflow-y-auto': !disableScrollingWhenMutationInProgress,
}"
diff --git a/app/assets/javascripts/merge_requests/components/compare_dropdown.vue b/app/assets/javascripts/merge_requests/components/compare_dropdown.vue
index d70cf26ec6e..1c0239c2153 100644
--- a/app/assets/javascripts/merge_requests/components/compare_dropdown.vue
+++ b/app/assets/javascripts/merge_requests/components/compare_dropdown.vue
@@ -1,5 +1,5 @@
<script>
-import { GlListbox, GlButton, GlIcon } from '@gitlab/ui';
+import { GlListbox } from '@gitlab/ui';
import { debounce } from 'lodash';
import { createAlert } from '~/flash';
import { __ } from '~/locale';
@@ -8,8 +8,6 @@ import axios from '~/lib/utils/axios_utils';
export default {
components: {
GlListbox,
- GlButton,
- GlIcon,
},
props: {
staticData: {
@@ -141,17 +139,6 @@ export default {
@shown="fetchData"
@search="searchData"
@select="selectItem"
- >
- <template #toggle>
- <gl-button
- class="gl-w-full gl-align-items-flex-start! gl-justify-content-start! mr-compare-dropdown"
- :class="toggleClass"
- :data-qa-selector="qaSelector"
- >
- {{ current.text || dropdownHeader }}
- <gl-icon name="chevron-down" class="gl-new-dropdown-chevron gl-float-right" />
- </gl-button>
- </template>
- </gl-listbox>
+ />
</div>
</template>
diff --git a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
index 868fa204a55..2718765ee23 100644
--- a/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
+++ b/app/assets/javascripts/pages/projects/merge_requests/creations/new/index.js
@@ -34,7 +34,7 @@ if (mrNewCompareNode) {
},
toggleClass: {
project: 'js-source-project',
- branch: 'js-source-branch',
+ branch: 'js-source-branch gl-font-monospace',
},
branchQaSelector: 'source_branch_dropdown',
},
@@ -68,7 +68,7 @@ if (mrNewCompareNode) {
},
toggleClass: {
project: 'js-target-project',
- branch: 'js-target-branch',
+ branch: 'js-target-branch gl-font-monospace',
},
},
render(h) {
diff --git a/app/assets/javascripts/repository/mixins/highlight_mixin.js b/app/assets/javascripts/repository/mixins/highlight_mixin.js
new file mode 100644
index 00000000000..95d0c55bb04
--- /dev/null
+++ b/app/assets/javascripts/repository/mixins/highlight_mixin.js
@@ -0,0 +1,106 @@
+import { nextTick } from 'vue';
+import {
+ LEGACY_FALLBACKS,
+ EVENT_ACTION,
+ EVENT_LABEL_FALLBACK,
+ LINES_PER_CHUNK,
+} from '~/vue_shared/components/source_viewer/constants';
+import { splitIntoChunks } from '~/vue_shared/components/source_viewer/workers/highlight_utils';
+import LineHighlighter from '~/blob/line_highlighter';
+import languageLoader from '~/content_editor/services/highlight_js_language_loader';
+import Tracking from '~/tracking';
+import { TEXT_FILE_TYPE } from '../constants';
+
+/*
+ * This mixin is intended to be used as an interface between our highlight worker and Vue components
+ */
+export default {
+ mixins: [Tracking.mixin()],
+ inject: {
+ highlightWorker: { default: null },
+ },
+ data() {
+ return {
+ chunks: [],
+ };
+ },
+ methods: {
+ trackEvent(label, language) {
+ this.track(EVENT_ACTION, { label, property: language });
+ },
+ isUnsupportedLanguage(language) {
+ const supportedLanguages = Object.keys(languageLoader);
+ const isUnsupportedLanguage = !supportedLanguages.includes(language);
+
+ return LEGACY_FALLBACKS.includes(language) || isUnsupportedLanguage;
+ },
+ handleUnsupportedLanguage(language) {
+ this.trackEvent(EVENT_LABEL_FALLBACK, language);
+ this?.onError();
+ },
+ initHighlightWorker({ rawTextBlob, language, simpleViewer }) {
+ if (simpleViewer?.fileType !== TEXT_FILE_TYPE) return;
+
+ if (this.isUnsupportedLanguage(language)) {
+ this.handleUnsupportedLanguage(language);
+ return;
+ }
+
+ /*
+ * We want to start rendering content as soon as possible, but highlighting large amounts of
+ * content can take long, so we render the content in phases:
+ *
+ * 1. `splitIntoChunks` with the first 70 lines of raw text.
+ * This ensures that we start rendering raw content in the DOM as soon as we can so that
+ * the user can see content as fast as possible (improves perceived performance and LCP).
+ * 2. `instructWorker` to start highlighting the first 70 lines.
+ * This ensures that we display highlighted** content to the user as fast as possible
+ * (improves perceived performance and makes the first 70 lines look nice).
+ * 3. `instructWorker` to start highlighting all the content.
+ * This is the longest task. It ensures that we highlight all content, since the first 70
+ * lines are already rendered, this can happen in the background.
+ */
+
+ // Render the first 70 lines (raw text) ASAP, this improves perceived performance and LCP.
+ const firstSeventyLines = rawTextBlob.split(/\r?\n/).slice(0, LINES_PER_CHUNK).join('\n');
+
+ this.chunks = splitIntoChunks(language, firstSeventyLines);
+
+ this.highlightWorker.onmessage = this.handleWorkerMessage;
+
+ // Instruct the worker to highlight the first 70 lines ASAP, this improves perceived performance.
+ this.instructWorker(firstSeventyLines, language);
+
+ // Instruct the worker to start highlighting all lines in the background.
+ this.instructWorker(rawTextBlob, language);
+ },
+ handleWorkerMessage({ data }) {
+ this.chunks = data;
+ this.highlightHash(); // highlight the line if a line number hash is present in the URL
+ },
+ instructWorker(content, language) {
+ this.highlightWorker.postMessage({ content, language });
+ },
+ async highlightHash() {
+ const { hash } = this.$route;
+ if (!hash) return;
+
+ // Make the chunk containing the line number visible
+ const lineNumber = hash.substring(hash.indexOf('L') + 1).split('-')[0];
+ const chunkToHighlight = this.chunks.find(
+ (chunk) =>
+ chunk.startingFrom <= lineNumber && chunk.startingFrom + chunk.totalLines >= lineNumber,
+ );
+
+ if (chunkToHighlight) {
+ chunkToHighlight.isHighlighted = true;
+ }
+
+ // Line numbers in the DOM needs to update first based on changes made to `chunks`.
+ await nextTick();
+
+ const lineHighlighter = new LineHighlighter({ scrollBehavior: 'auto' });
+ lineHighlighter.highlightHash(hash);
+ },
+ },
+};
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index a128efc1e69..5b8b850ba35 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -382,12 +382,5 @@ $comparison-empty-state-height: 62px;
.gl-button-text {
@include gl-w-full;
- @include gl-text-left;
- }
-}
-
-.merge-request-branch-select {
- .gl-button-text {
- @include gl-font-monospace;
}
}
diff --git a/app/assets/stylesheets/utilities.scss b/app/assets/stylesheets/utilities.scss
index 7d98a780e55..dbcafadd13a 100644
--- a/app/assets/stylesheets/utilities.scss
+++ b/app/assets/stylesheets/utilities.scss
@@ -7,10 +7,6 @@
@each $variant, $range in $color-ranges {
@each $suffix, $color in $range {
- #{'.bg-#{$variant}-#{$suffix}'} {
- background-color: $color;
- }
-
#{'.text-#{$variant}-#{$suffix}'} {
color: $color;
}
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index c36f03d3e69..aded295bfab 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -3,7 +3,7 @@
class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
skip_before_action :check_two_factor_requirement
before_action :ensure_verified_primary_email, only: [:show, :create]
- before_action :validate_current_password, only: [:create, :codes, :destroy], if: :current_password_required?
+ before_action :validate_current_password, only: [:create, :codes, :destroy, :create_webauthn], if: :current_password_required?
before_action :update_current_user_otp!, only: [:show]
helper_method :current_password_required?
@@ -21,8 +21,13 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create
otp_validation_result =
::Users::ValidateManualOtpService.new(current_user).execute(params[:pin_code])
+ validated = (otp_validation_result[:status] == :success)
- if otp_validation_result[:status] == :success
+ if validated && current_user.otp_backup_codes? && Feature.enabled?(:webauthn_without_totp)
+ ActiveSession.destroy_all_but_current(current_user, session)
+ Users::UpdateService.new(current_user, user: current_user, otp_required_for_login: true).execute!
+ redirect_to profile_two_factor_auth_path, notice: _("Your Time-based OTP device was registered!")
+ elsif validated
ActiveSession.destroy_all_but_current(current_user, session)
Users::UpdateService.new(current_user, user: current_user, otp_required_for_login: true).execute! do |user|
@@ -64,10 +69,27 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create_webauthn
@webauthn_registration = Webauthn::RegisterService.new(current_user, device_registration_params, session[:challenge]).execute
+
+ notice = _("Your WebAuthn device was registered!")
if @webauthn_registration.persisted?
session.delete(:challenge)
- redirect_to profile_two_factor_auth_path, notice: s_("Your WebAuthn device was registered!")
+ if Feature.enabled?(:webauthn_without_totp)
+
+ if current_user.otp_backup_codes?
+ redirect_to profile_two_factor_auth_path, notice: notice
+ else
+
+ Users::UpdateService.new(current_user, user: current_user).execute! do |user|
+ @codes = current_user.generate_otp_backup_codes!
+ end
+ helpers.dismiss_two_factor_auth_recovery_settings_check
+ flash[:notice] = notice
+ render 'create'
+ end
+ else
+ redirect_to profile_two_factor_auth_path, notice: notice
+ end
else
@qr_code = build_qr_code
@@ -119,11 +141,17 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
def validate_current_password
+ return if Feature.disabled?(:webauthn_without_totp) && params[:action] == 'create_webauthn'
return if current_user.valid_password?(params[:current_password])
current_user.increment_failed_attempts!
- @error = { message: _('You must provide a valid current password') }
+ error_message = { message: _('You must provide a valid current password.') }
+ if params[:action] == 'create_webauthn'
+ @webauthn_error = error_message
+ else
+ @error = error_message
+ end
setup_show_page
diff --git a/app/models/user.rb b/app/models/user.rb
index 293726b5cc4..602f6e348f6 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1035,19 +1035,32 @@ class User < ApplicationRecord
def disable_two_factor!
transaction do
- update(
- otp_required_for_login: false,
- encrypted_otp_secret: nil,
- encrypted_otp_secret_iv: nil,
- encrypted_otp_secret_salt: nil,
- otp_grace_period_started_at: nil,
- otp_backup_codes: nil
- )
- self.u2f_registrations.destroy_all # rubocop: disable Cop/DestroyAll
- self.webauthn_registrations.destroy_all # rubocop: disable Cop/DestroyAll
+ self.u2f_registrations.destroy_all # rubocop:disable Cop/DestroyAll
+ self.disable_webauthn!
+ self.disable_two_factor_otp!
+ self.reset_backup_codes!
end
end
+ def disable_two_factor_otp!
+ update(
+ otp_required_for_login: false,
+ encrypted_otp_secret: nil,
+ encrypted_otp_secret_iv: nil,
+ encrypted_otp_secret_salt: nil,
+ otp_grace_period_started_at: nil,
+ otp_secret_expires_at: nil
+ )
+ end
+
+ def disable_webauthn!
+ self.webauthn_registrations.destroy_all # rubocop:disable Cop/DestroyAll
+ end
+
+ def reset_backup_codes!
+ update(otp_backup_codes: nil)
+ end
+
def two_factor_enabled?
two_factor_otp_enabled? || two_factor_webauthn_u2f_enabled?
end
diff --git a/app/views/layouts/header/_sign_in_register_button.html.haml b/app/views/layouts/header/_sign_in_register_button.html.haml
deleted file mode 100644
index cadb7cfe683..00000000000
--- a/app/views/layouts/header/_sign_in_register_button.html.haml
+++ /dev/null
@@ -1,7 +0,0 @@
-- top_class = local_assigns.fetch(:class, nil)
-
-%li.nav-item{ class: top_class }
- %div
- - sign_in_text = allow_signup? ? _('Sign in / Register') : _('Sign in')
- = render Pajamas::ButtonComponent.new(href: new_session_path(:user, redirect_to_referer: 'yes'), button_options: { class: 'btn-sign-in'}) do
- = sign_in_text
diff --git a/config/feature_flags/development/ci_includes_count_duplicates.yml b/config/feature_flags/development/ci_includes_count_duplicates.yml
new file mode 100644
index 00000000000..5e33edddc03
--- /dev/null
+++ b/config/feature_flags/development/ci_includes_count_duplicates.yml
@@ -0,0 +1,8 @@
+---
+name: ci_includes_count_duplicates
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/111726
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/391517
+milestone: '15.9'
+type: development
+group: group::pipeline authoring
+default_enabled: false
diff --git a/config/feature_flags/development/webauthn_without_totp.yml b/config/feature_flags/development/webauthn_without_totp.yml
new file mode 100644
index 00000000000..3820c5edfb9
--- /dev/null
+++ b/config/feature_flags/development/webauthn_without_totp.yml
@@ -0,0 +1,8 @@
+---
+name: webauthn_without_totp
+introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/107438"
+rollout_issue_url: "https://gitlab.com/gitlab-org/gitlab/-/issues/386270"
+milestone: '15.9'
+type: development
+group: group::authentication and authorization
+default_enabled: false
diff --git a/data/deprecations/15-9-database-single-database-connection-conf.yml b/data/deprecations/15-9-database-single-database-connection-conf.yml
new file mode 100644
index 00000000000..c7d59e860ca
--- /dev/null
+++ b/data/deprecations/15-9-database-single-database-connection-conf.yml
@@ -0,0 +1,17 @@
+- title: "Single database connection is deprecated"
+ announcement_milestone: "15.9"
+ removal_milestone: "17.0"
+ breaking_change: true
+ reporter: tkuah
+ stage: Enablement
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/387898
+ body: |
+ Previously, [GitLab's database](https://docs.gitlab.com/omnibus/settings/database.html)
+ configuration had a single `main:` section. This is being deprecated. The new
+ configuration has both a `main:` and a `ci:` section.
+
+ This deprecation affects users compiling GitLab from source, who will need
+ to [add the `ci:` section](https://docs.gitlab.com/ee/install/installation.html#configure-gitlab-db-settings).
+ Omnibus, the Helm chart, and Operator will handle this configuration
+ automatically from GitLab 16.0 onwards.
+ documentation_url: https://docs.gitlab.com/ee/install/installation.html#configure-gitlab-db-settings
diff --git a/data/deprecations/15-9-insecure-ci-job-token.yml b/data/deprecations/15-9-insecure-ci-job-token.yml
new file mode 100644
index 00000000000..eb40224335c
--- /dev/null
+++ b/data/deprecations/15-9-insecure-ci-job-token.yml
@@ -0,0 +1,31 @@
+#
+# REQUIRED FIELDS
+#
+- title: "Default CI/CD job token (`CI_JOB_TOKEN`) scope changed" # (required) Clearly explain the change, or planned change. For example, "The `confidential` field for a `Note` is deprecated" or "CI/CD job names will be limited to 250 characters."
+ announcement_milestone: "15.9" # (required) The milestone when this feature was first announced as deprecated.
+ removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
+ breaking_change: true # (required) If this deprecation is a breaking change, set this value to true
+ reporter: jheimbuck_gl # (required) GitLab username of the person reporting the deprecation
+ stage: Verify # (required) String value of the stage that the feature was created in. e.g., Growth
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/335465 # (required) Link to the deprecation issue in GitLab
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ In GitLab 14.4 we introduced the ability to limit the "outbound" scope of the CI/CD job token (`CI_JOB_TOKEN`) to make it more secure. You can prevent job tokens from your project's pipelines from being used to access other projects. If needed, you can list specific projects that you want to access with your project's job tokens.
+
+ In 15.9 we extended this functionality with a better solution, an "inbound" scope limit. You can prevent the job tokens from _other_ projects from being used to access your project. With this feature, you can optionally list specific projects that you want to allow to access your project with _their_ job token.
+
+ In 16.0, this inbound scope limit will be the only option available for all projects, and the outbound limit setting will be removed. To prepare for this change, you can enable the ["inbound" CI/CD job token limit](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#configure-the-job-token-scope-limit) feature now, and list any projects that need to access your project.
+#
+# OPTIONAL END OF SUPPORT FIELDS
+#
+# If an End of Support period applies, the announcement should be shared with GitLab Support
+# in the `#spt_managers` channel in Slack, and mention `@gitlab-com/support` in this MR.
+#
+ end_of_support_milestone: # (optional) Use "XX.YY" format. The milestone when support for this feature will end.
+ end_of_support_date: # (optional) The date of the milestone release when support for this feature will end.
+#
+# OTHER OPTIONAL FIELDS
+#
+ tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
+ documentation_url: "https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#configure-the-job-token-scope-limit" # (optional) This is a link to the current documentation page
+ image_url: # (optional) This is a link to a thumbnail image depicting the feature
+ video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
diff --git a/data/deprecations/15-9-trigger-job-status.yml b/data/deprecations/15-9-trigger-job-status.yml
new file mode 100644
index 00000000000..66681de7f56
--- /dev/null
+++ b/data/deprecations/15-9-trigger-job-status.yml
@@ -0,0 +1,42 @@
+# This is a template for announcing a feature deprecation or other important planned change.
+#
+# Please refer to the deprecation guidelines to confirm your understanding of GitLab's definitions.
+# https://docs.gitlab.com/ee/development/deprecation_guidelines/#terminology
+#
+# Deprecations and other future breaking changes must be announced at least
+# three releases prior to removal.
+#
+# Breaking changes must happen in a major release.
+#
+# See the OPTIONAL END OF SUPPORT FIELDS section below if an End of Support period also applies.
+#
+# For more information please refer to the handbook documentation here:
+# https://about.gitlab.com/handbook/marketing/blog/release-posts/#deprecations-and-other-planned-breaking-change-announcements
+#
+# Please delete this line and above before submitting your merge request.
+#
+# REQUIRED FIELDS
+#
+- title: "Trigger jobs can mirror downstream pipeline status exactly" # (required) Clearly explain the change, or planned change. For example, "The `confidential` field for a `Note` is deprecated" or "CI/CD job names will be limited to 250 characters."
+ announcement_milestone: "15.9" # (required) The milestone when this feature was first announced as deprecated.
+ removal_milestone: "16.0" # (required) The milestone when this feature is planned to be removed
+ breaking_change: true # (required) Change to false if this is not a breaking change.
+ reporter: dhershkovitch # (required) GitLab username of the person reporting the change
+ stage: verify # (required) String value of the stage that the feature was created in. e.g., Growth
+ issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/285493 # (required) Link to the deprecation issue in GitLab
+ body: | # (required) Do not modify this line, instead modify the lines below.
+ In some cases, like when a downstream pipeline had the `passed with warnings` status, trigger jobs that were using [`strategy: depend`](https://docs.gitlab.com/ee/ci/yaml/index.html#strategydepend) did not mirror the status of the downstream pipeline exactly. In GitLab 16.0 trigger jobs will show the exact same status as the the downstream pipeline. If your pipeline relied on this behavior, you should update your pipeline to handle the more accurate status.
+#
+# OPTIONAL END OF SUPPORT FIELDS
+#
+# If an End of Support period applies, the announcement should be shared with GitLab Support
+# in the `#spt_managers` channel in Slack, and mention `@gitlab-com/support` in this MR.
+#
+ end_of_support_milestone: # (optional) Use "XX.YY" format. The milestone when support for this feature will end.
+ #
+ # OTHER OPTIONAL FIELDS
+ #
+ tiers: # (optional - may be required in the future) An array of tiers that the feature is available in currently. e.g., [Free, Silver, Gold, Core, Premium, Ultimate]
+ documentation_url: # (optional) This is a link to the current documentation page
+ image_url: # (optional) This is a link to a thumbnail image depicting the feature
+ video_url: # (optional) Use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg
diff --git a/doc/administration/get_started.md b/doc/administration/get_started.md
index f27adfcb100..d9191440a24 100644
--- a/doc/administration/get_started.md
+++ b/doc/administration/get_started.md
@@ -1,5 +1,5 @@
---
-info: For assistance with this TAM Onboarding page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
+info: For assistance with this CSM Onboarding page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments-to-other-projects-and-subjects.
stage: none
group: unassigned
---
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index 4b6558c5bfa..405c56284cf 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -60,7 +60,7 @@ If you have:
- A sharded Gitaly instance.
- Gitaly Cluster.
-Contact your Technical Account Manager or customer support if you have any questions.
+Contact your [Customer Success Manager](https://about.gitlab.com/job-families/sales/customer-success-management/) or customer support if you have any questions.
### Known issues
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 961a47e005b..935964a9a90 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -276,6 +276,50 @@ class MigrationName < Elastic::Migration
end
```
+#### `Elastic::MigrationRemoveFieldsHelper`
+
+Removes specified fields from an index.
+
+Requires the `index_name`, `document_type` methods. If there is one field to remove, add the `field_to_remove` method, otherwise add `fields_to_remove` with an array of fields.
+
+Checks in batches if any documents that match `document_type` have the fields specified in Elasticsearch. If documents exist, uses a Painless script to perform `update_by_query`.
+
+```ruby
+class MigrationName < Elastic::Migration
+ include Elastic::MigrationRemoveFieldsHelper
+
+ batched!
+ throttle_delay 1.minute
+
+ private
+
+ def index_name
+ User.__elasticsearch__.index_name
+ end
+
+ def document_type
+ 'user'
+ end
+
+ def fields_to_remove
+ %w[two_factor_enabled has_projects]
+ end
+end
+```
+
+The default batch size is `10_000`. You can override this value by specifying `BATCH_SIZE`:
+
+```ruby
+class MigrationName < Elastic::Migration
+ include Elastic::MigrationRemoveFieldsHelper
+
+ batched!
+ BATCH_SIZE = 100
+
+ ...
+end
+```
+
#### `Elastic::MigrationObsolete`
Marks a migration as obsolete when it's no longer required.
diff --git a/doc/development/service_ping/metrics_lifecycle.md b/doc/development/service_ping/metrics_lifecycle.md
index e8ff27c6cf5..8a8ceae1f4c 100644
--- a/doc/development/service_ping/metrics_lifecycle.md
+++ b/doc/development/service_ping/metrics_lifecycle.md
@@ -122,7 +122,7 @@ To remove a metric:
update the attributes of the metric's YAML definition:
- Set the `status:` to `removed`.
- - Set `removed_by_url:` to the URL of the issue removing the metric
+ - Set `removed_by_url:` to the URL of the MR removing the metric
- Set `milestone_removed:` to the number of the
milestone in which the metric was removed.
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index e8ee1823f87..7c4b38446d9 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -94,6 +94,24 @@ When using the native HashiCorp Vault integration, CI/CD jobs will fail when no
<div class="deprecation removal-160 breaking-change">
+### Default CI/CD job token (`CI_JOB_TOKEN`) scope changed
+
+Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+In GitLab 14.4 we introduced the ability to limit the "outbound" scope of the CI/CD job token (`CI_JOB_TOKEN`) to make it more secure. You can prevent job tokens from your project's pipelines from being used to access other projects. If needed, you can list specific projects that you want to access with your project's job tokens.
+
+In 15.9 we extended this functionality with a better solution, an "inbound" scope limit. You can prevent the job tokens from _other_ projects from being used to access your project. With this feature, you can optionally list specific projects that you want to allow to access your project with _their_ job token.
+
+In 16.0, this inbound scope limit will be the only option available for all projects, and the outbound limit setting will be removed. To prepare for this change, you can enable the ["inbound" CI/CD job token limit](https://docs.gitlab.com/ee/ci/jobs/ci_job_token.html#configure-the-job-token-scope-limit) feature now, and list any projects that need to access your project.
+
+</div>
+
+<div class="deprecation removal-160 breaking-change">
+
### Development dependencies reported for PHP and Python
Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
@@ -245,6 +263,27 @@ We recommend replacing this with an alternative [compliance solution](https://do
</div>
+<div class="deprecation removal-170 breaking-change">
+
+### Single database connection is deprecated
+
+Planned removal: GitLab <span class="removal-milestone">17.0</span> <span class="removal-date"></span>
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+Previously, [GitLab's database](https://docs.gitlab.com/omnibus/settings/database.html)
+configuration had a single `main:` section. This is being deprecated. The new
+configuration has both a `main:` and a `ci:` section.
+
+This deprecation affects users compiling GitLab from source, who will need
+to [add the `ci:` section](https://docs.gitlab.com/ee/install/installation.html#configure-gitlab-db-settings).
+Omnibus, the Helm chart, and Operator will handle this configuration
+automatically from GitLab 16.0 onwards.
+
+</div>
+
<div class="deprecation removal-160 breaking-change">
### Support for Praefect custom metrics endpoint configuration
@@ -280,6 +319,20 @@ Review the details carefully before upgrading.
We will be transitioning to a new IID as a result of moving requirements to a [work item type](https://docs.gitlab.com/ee/development/work_items.html#work-items-and-work-item-types). Users should begin using the new IID as support for the legacy IID and existing formatting will end in GitLab 17.0. The legacy requirement IID remains available until its removal in GitLab 17.0.
</div>
+
+<div class="deprecation removal-160 breaking-change">
+
+### Trigger jobs can mirror downstream pipeline status exactly
+
+Planned removal: GitLab <span class="removal-milestone">16.0</span> <span class="removal-date"></span>
+
+WARNING:
+This is a [breaking change](https://docs.gitlab.com/ee/development/deprecation_guidelines/).
+Review the details carefully before upgrading.
+
+In some cases, like when a downstream pipeline had the `passed with warnings` status, trigger jobs that were using [`strategy: depend`](https://docs.gitlab.com/ee/ci/yaml/index.html#strategydepend) did not mirror the status of the downstream pipeline exactly. In GitLab 16.0 trigger jobs will show the exact same status as the the downstream pipeline. If your pipeline relied on this behavior, you should update your pipeline to handle the more accurate status.
+
+</div>
</div>
<div class="announcement-milestone">
diff --git a/doc/update/plan_your_upgrade.md b/doc/update/plan_your_upgrade.md
index 667ed37af02..5b4ecb96747 100644
--- a/doc/update/plan_your_upgrade.md
+++ b/doc/update/plan_your_upgrade.md
@@ -192,7 +192,7 @@ If anything doesn't go as planned:
you installed GitLab using the Helm Charts.
- For support:
- [Contact GitLab Support](https://support.gitlab.com) and,
- if you have one, your Technical Account Manager.
+ if you have one, your [Customer Success Managers](https://about.gitlab.com/job-families/sales/customer-success-management/).
- If [the situation qualifies](https://about.gitlab.com/support/#definitions-of-support-impact)
and [your plan includes emergency support](https://about.gitlab.com/support/#priority-support),
create an emergency ticket.
diff --git a/doc/user/admin_area/settings/email.md b/doc/user/admin_area/settings/email.md
index 6d2a3c2cdae..3cdb045d19d 100644
--- a/doc/user/admin_area/settings/email.md
+++ b/doc/user/admin_area/settings/email.md
@@ -84,6 +84,29 @@ To disable these notifications:
1. Clear the **Enable user deactivation emails** checkbox.
1. Select **Save changes**.
+### Custom additional text in deactivation emails **(FREE SELF)**
+
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/355964) in GitLab 15.9
+[with a flag](../../../administration/feature_flags.md) named `deactivation_email_additional_text`.
+Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, ask an
+administrator to [enable the feature flag](../../../administration/feature_flags.md) named
+`deactivation_email_additional_text`. On GitLab.com, this feature is unavailable.
+
+You can add additional text at the bottom of the email that GitLab sends to users when their account
+is deactivated. This email text is separate from the [custom additional text](#custom-additional-text)
+setting.
+
+To add additional text to deactivation emails:
+
+1. On the top bar, select **Main menu > Admin**.
+1. On the left sidebar, select **Settings > Preferences** (`/admin/application_settings/preferences`).
+1. Expand **Email**.
+1. Enter your text in the **Additional text for deactivation email** field.
+1. Select **Save changes**.
+
<!-- ## Troubleshooting
Include any troubleshooting steps that you can foresee. If you know beforehand what issues
diff --git a/doc/user/admin_area/settings/usage_statistics.md b/doc/user/admin_area/settings/usage_statistics.md
index 8b9f09d9df5..212769ed89b 100644
--- a/doc/user/admin_area/settings/usage_statistics.md
+++ b/doc/user/admin_area/settings/usage_statistics.md
@@ -27,7 +27,7 @@ There are several other benefits to enabling Service Ping:
- Analyze the users' activities over time of your GitLab installation.
- A [DevOps Score](../analytics/dev_ops_reports.md#devops-score) to give you an overview of your entire instance's adoption of concurrent DevOps from planning to monitoring.
-- More proactive support (assuming that our TAMs and support organization used the data to deliver more value).
+- More proactive support (assuming that our [Customer Success Managers (CSMs)](https://about.gitlab.com/job-families/sales/customer-success-management/) and support organization used the data to deliver more value).
- Insight and advice into how to get the most value out of your investment in GitLab.
- Reports that show how you compare against other similar organizations (anonymized), with specific advice and recommendations on how to improve your DevOps processes.
- Participation in our [Registration Features Program](#registration-features-program) to receive free paid features.
diff --git a/doc/user/search/exact_code_search.md b/doc/user/search/exact_code_search.md
index 97f58b973cb..14dca6d4a12 100644
--- a/doc/user/search/exact_code_search.md
+++ b/doc/user/search/exact_code_search.md
@@ -10,17 +10,9 @@ type: reference
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/105049) in GitLab 15.9 [with a flag](../../administration/feature_flags.md) named `index_code_with_zoekt` and `search_code_with_zoekt` which enables indexing and searching respectively. Both are disabled by default.
WARNING:
-Exact code search is in [**Alpha**](../../policy/alpha-beta-support.md#alpha-features).
-For the Exact code search feature roadmap, see [epic 9404](https://gitlab.com/groups/gitlab-org/-/epics/9404).
-
-This feature will initially only be rolled out to
-specific customers on GitLab.com that request
-access.
-
-On self-managed GitLab it should be possible to enable this, but no
-documentation is provided as it requires executing commands from the Rails
-console as well advanced configuration of
-[Zoekt](https://github.com/sourcegraph/zoekt) servers.
+Exact Code Search is an MVC. For the Exact Code Search feature roadmap, see [epic 9404](https://gitlab.com/groups/gitlab-org/-/epics/9404).
+When this feature reaches the [**Alpha**](../../policy/alpha-beta-support.md#alpha-features) version, GitLab will dogfood it, and roll it out only to specific customers on GitLab.com who request access to it.
+On self-managed GitLab, this feature is available and can be enabled. However, GitLab does not provide support or documentation at this development stage.
## Usage
diff --git a/lib/bulk_imports/clients/graphql.rb b/lib/bulk_imports/clients/graphql.rb
index b8c80886a60..e1e78f52801 100644
--- a/lib/bulk_imports/clients/graphql.rb
+++ b/lib/bulk_imports/clients/graphql.rb
@@ -16,8 +16,15 @@ module BulkImports
}.to_json
)
+ unless response.success?
+ raise ::BulkImports::NetworkError.new(
+ "Unsuccessful response #{response.code} from #{response.request.path.path}",
+ response: response
+ )
+ end
+
::Gitlab::Json.parse(response.body)
- rescue *Gitlab::HTTP::HTTP_ERRORS => e
+ rescue *Gitlab::HTTP::HTTP_ERRORS, JSON::ParserError => e
raise ::BulkImports::NetworkError, e
end
end
diff --git a/lib/gitlab/ci/config/external/context.rb b/lib/gitlab/ci/config/external/context.rb
index 138e79db331..6eef279d3de 100644
--- a/lib/gitlab/ci/config/external/context.rb
+++ b/lib/gitlab/ci/config/external/context.rb
@@ -10,6 +10,7 @@ module Gitlab
TimeoutError = Class.new(StandardError)
MAX_INCLUDES = 100
+ NEW_MAX_INCLUDES = 150 # Update to MAX_INCLUDES when FF ci_includes_count_duplicates is removed
include ::Gitlab::Utils::StrongMemoize
@@ -27,10 +28,10 @@ module Gitlab
@user = user
@parent_pipeline = parent_pipeline
@variables = variables || Ci::Variables::Collection.new
- @expandset = Set.new
+ @expandset = Feature.enabled?(:ci_includes_count_duplicates, project) ? [] : Set.new
@execution_deadline = 0
@logger = logger || Gitlab::Ci::Pipeline::Logger.new(project: project)
- @max_includes = MAX_INCLUDES
+ @max_includes = Feature.enabled?(:ci_includes_count_duplicates, project) ? NEW_MAX_INCLUDES : MAX_INCLUDES
yield self if block_given?
end
diff --git a/lib/gitlab/ci/config/external/mapper/verifier.rb b/lib/gitlab/ci/config/external/mapper/verifier.rb
index 6c161e95154..83ef58bb46f 100644
--- a/lib/gitlab/ci/config/external/mapper/verifier.rb
+++ b/lib/gitlab/ci/config/external/mapper/verifier.rb
@@ -28,7 +28,11 @@ module Gitlab
file.validate_content! if file.valid?
file.load_and_validate_expanded_hash! if file.valid?
- context.expandset.add(file)
+ if ::Feature.enabled?(:ci_includes_count_duplicates, context.project)
+ context.expandset << file
+ else
+ context.expandset.add(file)
+ end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 45b6d07ae5d..c3d86ffbc57 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -39873,9 +39873,6 @@ msgstr ""
msgid "Sign in"
msgstr ""
-msgid "Sign in / Register"
-msgstr ""
-
msgid "Sign in as a user with the matching email address, add the email to this account, or sign-up for a new account using the matching email."
msgstr ""
@@ -49124,6 +49121,9 @@ msgstr ""
msgid "You must provide a valid current password"
msgstr ""
+msgid "You must provide a valid current password."
+msgstr ""
+
msgid "You must provide at least one filter argument for this query"
msgstr ""
@@ -49363,6 +49363,9 @@ msgstr ""
msgid "Your SSH keys (%{count})"
msgstr ""
+msgid "Your Time-based OTP device was registered!"
+msgstr ""
+
msgid "Your To-Do List"
msgstr ""
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index 1dd564427d3..7d7cdededdb 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe Profiles::TwoFactorAuthsController do
+RSpec.describe Profiles::TwoFactorAuthsController, feature_category: :authentication_and_authorization do
before do
# `user` should be defined within the action-specific describe blocks
sign_in(user)
@@ -31,7 +31,7 @@ RSpec.describe Profiles::TwoFactorAuthsController do
shared_examples 'user must enter a valid current password' do
let(:current_password) { '123' }
- let(:error_message) { { message: _('You must provide a valid current password') } }
+ let(:error_message) { { message: _('You must provide a valid current password.') } }
it 'requires the current password', :aggregate_failures do
go
@@ -154,7 +154,7 @@ RSpec.describe Profiles::TwoFactorAuthsController do
context 'with valid pin' do
before do
- expect(user).to receive(:validate_and_consume_otp!).with(pin).and_return(true)
+ allow(user).to receive(:validate_and_consume_otp!).with(pin).and_return(true)
end
it 'enables 2fa for the user' do
@@ -187,6 +187,64 @@ RSpec.describe Profiles::TwoFactorAuthsController do
it 'renders create' do
go
expect(response).to render_template(:create)
+ expect(user.otp_backup_codes?).to be_eql(true)
+ end
+
+ it 'do not create new backup codes if exists' do
+ expect(user).to receive(:otp_backup_codes?).and_return(true)
+ go
+ expect(response).to redirect_to(profile_two_factor_auth_path)
+ end
+
+ it 'calls to delete other sessions when backup codes already exist' do
+ expect(user).to receive(:otp_backup_codes?).and_return(true)
+ expect(ActiveSession).to receive(:destroy_all_but_current)
+ go
+ end
+
+ context 'when webauthn_without_totp flag is disabled' do
+ before do
+ stub_feature_flags(webauthn_without_totp: false)
+ expect(user).to receive(:validate_and_consume_otp!).with(pin).and_return(true)
+ end
+
+ it 'enables 2fa for the user' do
+ go
+
+ user.reload
+ expect(user).to be_two_factor_enabled
+ end
+
+ it 'presents plaintext codes for the user to save' do
+ expect(user).to receive(:generate_otp_backup_codes!).and_return(%w(a b c))
+
+ go
+
+ expect(assigns[:codes]).to match_array %w(a b c)
+ end
+
+ it 'calls to delete other sessions' do
+ expect(ActiveSession).to receive(:destroy_all_but_current)
+
+ go
+ end
+
+ it 'dismisses the `TWO_FACTOR_AUTH_RECOVERY_SETTINGS_CHECK` callout' do
+ expect(controller.helpers).to receive(:dismiss_two_factor_auth_recovery_settings_check)
+
+ go
+ end
+
+ it 'renders create' do
+ go
+ expect(response).to render_template(:create)
+ end
+
+ it 'renders create even if backup code already exists' do
+ expect(user).to receive(:otp_backup_codes?).and_return(true)
+ go
+ expect(response).to render_template(:create)
+ end
end
end
@@ -254,6 +312,75 @@ RSpec.describe Profiles::TwoFactorAuthsController do
end
end
+ describe 'POST create_webauthn' do
+ let_it_be_with_reload(:user) { create(:user) }
+ let(:client) { WebAuthn::FakeClient.new('http://localhost', encoding: :base64) }
+ let(:credential) { create_credential(client: client, rp_id: request.host) }
+
+ let(:params) { { device_registration: { name: 'touch id', device_response: device_response } } } # rubocop:disable Rails/SaveBang
+
+ let(:params_with_password) { { device_registration: { name: 'touch id', device_response: device_response }, current_password: user.password } } # rubocop:disable Rails/SaveBang
+
+ before do
+ session[:challenge] = challenge
+ end
+
+ def go
+ post :create_webauthn, params: params
+ end
+
+ def challenge
+ @_challenge ||= begin
+ options_for_create = WebAuthn::Credential.options_for_create(
+ user: { id: user.webauthn_xid, name: user.username },
+ authenticator_selection: { user_verification: 'discouraged' },
+ rp: { name: 'GitLab' }
+ )
+ options_for_create.challenge
+ end
+ end
+
+ def device_response
+ client.create(challenge: challenge).to_json # rubocop:disable Rails/SaveBang
+ end
+
+ it 'update failed_attempts when proper password is not given' do
+ go
+ expect(user.failed_attempts).to be_eql(1)
+ end
+
+ context "when valid password is given" do
+ it "registers and render OTP backup codes" do
+ post :create_webauthn, params: params_with_password
+ expect(user.otp_backup_codes).not_to be_empty
+ expect(flash[:notice]).to match(/Your WebAuthn device was registered!/)
+ end
+
+ it 'registers and redirects back if user is already having backup codes' do
+ expect(user).to receive(:otp_backup_codes?).and_return(true)
+ post :create_webauthn, params: params_with_password
+ expect(response).to redirect_to(profile_two_factor_auth_path)
+ expect(flash[:notice]).to match(/Your WebAuthn device was registered!/)
+ end
+ end
+
+ context "when the feature flag 'webauthn_without_totp' is disabled" do
+ before do
+ stub_feature_flags(webauthn_without_totp: false)
+ session[:challenge] = challenge
+ end
+
+ let(:params) { { device_registration: { name: 'touch id', device_response: device_response } } } # rubocop:disable Rails/SaveBang
+
+ it "does not validate the current_password" do
+ go
+
+ expect(flash[:notice]).to match(/Your WebAuthn device was registered!/)
+ expect(response).to redirect_to(profile_two_factor_auth_path)
+ end
+ end
+ end
+
describe 'DELETE destroy' do
def go
delete :destroy, params: { current_password: current_password }
diff --git a/spec/features/webauthn_spec.rb b/spec/features/webauthn_spec.rb
index e2f16f4a017..859793d1353 100644
--- a/spec/features/webauthn_spec.rb
+++ b/spec/features/webauthn_spec.rb
@@ -10,57 +10,62 @@ RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_categor
WebAuthn.configuration.origin = app_id
end
- it_behaves_like 'hardware device for 2fa', 'WebAuthn'
-
- describe 'registration' do
- let(:user) { create(:user) }
-
+ context 'when the webauth_without_totp feature flag is disabled' do
before do
- gitlab_sign_in(user)
- user.update_attribute(:otp_required_for_login, true)
+ stub_feature_flags(webauthn_without_totp: false)
end
- describe 'when 2FA via OTP is enabled' do
- it 'allows registering more than one device' do
- visit profile_account_path
+ it_behaves_like 'hardware device for 2fa', 'WebAuthn'
+
+ describe 'registration' do
+ let(:user) { create(:user) }
+
+ before do
+ gitlab_sign_in(user)
+ user.update_attribute(:otp_required_for_login, true)
+ end
+
+ describe 'when 2FA via OTP is enabled' do
+ it 'allows registering more than one device' do
+ visit profile_account_path
- # First device
+ # First device
+ manage_two_factor_authentication
+ first_device = register_webauthn_device
+ expect(page).to have_content('Your WebAuthn device was registered')
+
+ # Second device
+ second_device = register_webauthn_device(name: 'My other device')
+ expect(page).to have_content('Your WebAuthn device was registered')
+
+ expect(page).to have_content(first_device.name)
+ expect(page).to have_content(second_device.name)
+ expect(WebauthnRegistration.count).to eq(2)
+ end
+ end
+
+ it 'allows the same device to be registered for multiple users' do
+ # First user
+ visit profile_account_path
manage_two_factor_authentication
- first_device = register_webauthn_device
+ webauthn_device = register_webauthn_device
expect(page).to have_content('Your WebAuthn device was registered')
+ gitlab_sign_out
- # Second device
- second_device = register_webauthn_device(name: 'My other device')
+ # Second user
+ user = gitlab_sign_in(:user)
+ user.update_attribute(:otp_required_for_login, true)
+ visit profile_account_path
+ manage_two_factor_authentication
+ register_webauthn_device(webauthn_device, name: 'My other device')
expect(page).to have_content('Your WebAuthn device was registered')
- expect(page).to have_content(first_device.name)
- expect(page).to have_content(second_device.name)
expect(WebauthnRegistration.count).to eq(2)
end
- end
-
- it 'allows the same device to be registered for multiple users' do
- # First user
- visit profile_account_path
- manage_two_factor_authentication
- webauthn_device = register_webauthn_device
- expect(page).to have_content('Your WebAuthn device was registered')
- gitlab_sign_out
-
- # Second user
- user = gitlab_sign_in(:user)
- user.update_attribute(:otp_required_for_login, true)
- visit profile_account_path
- manage_two_factor_authentication
- register_webauthn_device(webauthn_device, name: 'My other device')
- expect(page).to have_content('Your WebAuthn device was registered')
-
- expect(WebauthnRegistration.count).to eq(2)
- end
- context 'when there are form errors' do
- let(:mock_register_js) do
- <<~JS
+ context 'when there are form errors' do
+ let(:mock_register_js) do
+ <<~JS
const mockResponse = {
type: 'public-key',
id: '',
@@ -72,135 +77,136 @@ RSpec.describe 'Using WebAuthn Devices for Authentication', :js, feature_categor
getClientExtensionResults: () => {},
};
navigator.credentials.create = function(_) {return Promise.resolve(mockResponse);}
- JS
- end
+ JS
+ end
- it "doesn't register the device if there are errors" do
- visit profile_account_path
- manage_two_factor_authentication
+ it "doesn't register the device if there are errors" do
+ visit profile_account_path
+ manage_two_factor_authentication
- # Have the "webauthn device" respond with bad data
- page.execute_script(mock_register_js)
- click_on 'Set up new device'
- expect(page).to have_content('Your device was successfully set up')
- click_on 'Register device'
+ # Have the "webauthn device" respond with bad data
+ page.execute_script(mock_register_js)
+ click_on 'Set up new device'
+ expect(page).to have_content('Your device was successfully set up')
+ click_on 'Register device'
- expect(WebauthnRegistration.count).to eq(0)
- expect(page).to have_content('The form contains the following error')
- expect(page).to have_content('did not send a valid JSON response')
- end
+ expect(WebauthnRegistration.count).to eq(0)
+ expect(page).to have_content('The form contains the following error')
+ expect(page).to have_content('did not send a valid JSON response')
+ end
- it 'allows retrying registration' do
- visit profile_account_path
- manage_two_factor_authentication
+ it 'allows retrying registration' do
+ visit profile_account_path
+ manage_two_factor_authentication
- # Failed registration
- page.execute_script(mock_register_js)
- click_on 'Set up new device'
- expect(page).to have_content('Your device was successfully set up')
- click_on 'Register device'
- expect(page).to have_content('The form contains the following error')
+ # Failed registration
+ page.execute_script(mock_register_js)
+ click_on 'Set up new device'
+ expect(page).to have_content('Your device was successfully set up')
+ click_on 'Register device'
+ expect(page).to have_content('The form contains the following error')
- # Successful registration
- register_webauthn_device
+ # Successful registration
+ register_webauthn_device
- expect(page).to have_content('Your WebAuthn device was registered')
- expect(WebauthnRegistration.count).to eq(1)
+ expect(page).to have_content('Your WebAuthn device was registered')
+ expect(WebauthnRegistration.count).to eq(1)
+ end
end
end
- end
- describe 'authentication' do
- let(:otp_required_for_login) { true }
- let(:user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) }
- let!(:webauthn_device) do
- add_webauthn_device(app_id, user)
- end
+ describe 'authentication' do
+ let(:otp_required_for_login) { true }
+ let(:user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) }
+ let!(:webauthn_device) do
+ add_webauthn_device(app_id, user)
+ end
- describe 'when 2FA via OTP is disabled' do
- let(:otp_required_for_login) { false }
+ describe 'when 2FA via OTP is disabled' do
+ let(:otp_required_for_login) { false }
- it 'allows logging in with the WebAuthn device' do
- gitlab_sign_in(user)
+ it 'allows logging in with the WebAuthn device' do
+ gitlab_sign_in(user)
- webauthn_device.respond_to_webauthn_authentication
+ webauthn_device.respond_to_webauthn_authentication
- expect(page).to have_css('.sign-out-link', visible: false)
+ expect(page).to have_css('.sign-out-link', visible: false)
+ end
end
- end
- describe 'when 2FA via OTP is enabled' do
- it 'allows logging in with the WebAuthn device' do
- gitlab_sign_in(user)
+ describe 'when 2FA via OTP is enabled' do
+ it 'allows logging in with the WebAuthn device' do
+ gitlab_sign_in(user)
- webauthn_device.respond_to_webauthn_authentication
+ webauthn_device.respond_to_webauthn_authentication
- expect(page).to have_css('.sign-out-link', visible: false)
+ expect(page).to have_css('.sign-out-link', visible: false)
+ end
end
- end
- describe 'when a given WebAuthn device has already been registered by another user' do
- describe 'but not the current user' do
- let(:other_user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) }
+ describe 'when a given WebAuthn device has already been registered by another user' do
+ describe 'but not the current user' do
+ let(:other_user) { create(:user, webauthn_xid: WebAuthn.generate_user_id, otp_required_for_login: otp_required_for_login) }
- it 'does not allow logging in with that particular device' do
- # Register other user with a different WebAuthn device
- other_device = add_webauthn_device(app_id, other_user)
+ it 'does not allow logging in with that particular device' do
+ # Register other user with a different WebAuthn device
+ other_device = add_webauthn_device(app_id, other_user)
- # Try authenticating user with the old WebAuthn device
- gitlab_sign_in(user)
- other_device.respond_to_webauthn_authentication
- expect(page).to have_content('Authentication via WebAuthn device failed')
+ # Try authenticating user with the old WebAuthn device
+ gitlab_sign_in(user)
+ other_device.respond_to_webauthn_authentication
+ expect(page).to have_content('Authentication via WebAuthn device failed')
+ end
end
- end
-
- describe "and also the current user" do
- # TODO Uncomment once WebAuthn::FakeClient supports passing credential options
- # (especially allow_credentials, as this is needed to specify which credential the
- # fake client should use. Currently, the first credential is always used).
- # There is an issue open for this: https://github.com/cedarcode/webauthn-ruby/issues/259
- it "allows logging in with that particular device" do
- pending("support for passing credential options in FakeClient")
- # Register current user with the same WebAuthn device
- current_user = gitlab_sign_in(:user)
- visit profile_account_path
- manage_two_factor_authentication
- register_webauthn_device(webauthn_device)
- gitlab_sign_out
- # Try authenticating user with the same WebAuthn device
- gitlab_sign_in(current_user)
- webauthn_device.respond_to_webauthn_authentication
-
- expect(page).to have_css('.sign-out-link', visible: false)
+ describe "and also the current user" do
+ # TODO Uncomment once WebAuthn::FakeClient supports passing credential options
+ # (especially allow_credentials, as this is needed to specify which credential the
+ # fake client should use. Currently, the first credential is always used).
+ # There is an issue open for this: https://github.com/cedarcode/webauthn-ruby/issues/259
+ it "allows logging in with that particular device" do
+ pending("support for passing credential options in FakeClient")
+ # Register current user with the same WebAuthn device
+ current_user = gitlab_sign_in(:user)
+ visit profile_account_path
+ manage_two_factor_authentication
+ register_webauthn_device(webauthn_device)
+ gitlab_sign_out
+
+ # Try authenticating user with the same WebAuthn device
+ gitlab_sign_in(current_user)
+ webauthn_device.respond_to_webauthn_authentication
+
+ expect(page).to have_css('.sign-out-link', visible: false)
+ end
end
end
- end
- describe 'when a given WebAuthn device has not been registered' do
- it 'does not allow logging in with that particular device' do
- unregistered_device = FakeWebauthnDevice.new(page, 'My device')
- gitlab_sign_in(user)
- unregistered_device.respond_to_webauthn_authentication
+ describe 'when a given WebAuthn device has not been registered' do
+ it 'does not allow logging in with that particular device' do
+ unregistered_device = FakeWebauthnDevice.new(page, 'My device')
+ gitlab_sign_in(user)
+ unregistered_device.respond_to_webauthn_authentication
- expect(page).to have_content('Authentication via WebAuthn device failed')
+ expect(page).to have_content('Authentication via WebAuthn device failed')
+ end
end
- end
- describe 'when more than one device has been registered by the same user' do
- it 'allows logging in with either device' do
- first_device = add_webauthn_device(app_id, user)
- second_device = add_webauthn_device(app_id, user)
+ describe 'when more than one device has been registered by the same user' do
+ it 'allows logging in with either device' do
+ first_device = add_webauthn_device(app_id, user)
+ second_device = add_webauthn_device(app_id, user)
- # Authenticate as both devices
- [first_device, second_device].each do |device|
- gitlab_sign_in(user)
- # register_webauthn_device(device)
- device.respond_to_webauthn_authentication
+ # Authenticate as both devices
+ [first_device, second_device].each do |device|
+ gitlab_sign_in(user)
+ # register_webauthn_device(device)
+ device.respond_to_webauthn_authentication
- expect(page).to have_css('.sign-out-link', visible: false)
+ expect(page).to have_css('.sign-out-link', visible: false)
- gitlab_sign_out
+ gitlab_sign_out
+ end
end
end
end
diff --git a/spec/frontend/boards/board_list_spec.js b/spec/frontend/boards/board_list_spec.js
index 07c4be60770..543186a8009 100644
--- a/spec/frontend/boards/board_list_spec.js
+++ b/spec/frontend/boards/board_list_spec.js
@@ -159,11 +159,11 @@ describe('Board list component', () => {
});
describe('when issue count exceeds max issue count', () => {
- it('sets background to bg-danger-100', async () => {
+ it('sets background to gl-bg-red-100', async () => {
wrapper.setProps({ list: { issuesCount: 4, maxIssueCount: 3 } });
await nextTick();
- const block = wrapper.find('.bg-danger-100');
+ const block = wrapper.find('.gl-bg-red-100');
expect(block.exists()).toBe(true);
expect(block.attributes('class')).toContain(
@@ -173,18 +173,18 @@ describe('Board list component', () => {
});
describe('when list issue count does NOT exceed list max issue count', () => {
- it('does not sets background to bg-danger-100', () => {
+ it('does not sets background to gl-bg-red-100', () => {
wrapper.setProps({ list: { issuesCount: 2, maxIssueCount: 3 } });
- expect(wrapper.find('.bg-danger-100').exists()).toBe(false);
+ expect(wrapper.find('.gl-bg-red-100').exists()).toBe(false);
});
});
describe('when list max issue count is 0', () => {
- it('does not sets background to bg-danger-100', () => {
+ it('does not sets background to gl-bg-red-100', () => {
wrapper.setProps({ list: { maxIssueCount: 0 } });
- expect(wrapper.find('.bg-danger-100').exists()).toBe(false);
+ expect(wrapper.find('.gl-bg-red-100').exists()).toBe(false);
});
});
});
diff --git a/spec/frontend/repository/mixins/highlight_mixin_spec.js b/spec/frontend/repository/mixins/highlight_mixin_spec.js
new file mode 100644
index 00000000000..7c48fe440d2
--- /dev/null
+++ b/spec/frontend/repository/mixins/highlight_mixin_spec.js
@@ -0,0 +1,106 @@
+import { shallowMount } from '@vue/test-utils';
+import { splitIntoChunks } from '~/vue_shared/components/source_viewer/workers/highlight_utils';
+import highlightMixin from '~/repository/mixins/highlight_mixin';
+import LineHighlighter from '~/blob/line_highlighter';
+import Tracking from '~/tracking';
+import { TEXT_FILE_TYPE } from '~/repository/constants';
+import {
+ EVENT_ACTION,
+ EVENT_LABEL_FALLBACK,
+ LINES_PER_CHUNK,
+} from '~/vue_shared/components/source_viewer/constants';
+
+const lineHighlighter = new LineHighlighter();
+jest.mock('~/blob/line_highlighter', () => jest.fn().mockReturnValue({ highlightHash: jest.fn() }));
+jest.mock('~/vue_shared/components/source_viewer/workers/highlight_utils', () => ({
+ splitIntoChunks: jest.fn().mockResolvedValue([]),
+}));
+
+const workerMock = { postMessage: jest.fn() };
+const onErrorMock = jest.fn();
+
+describe('HighlightMixin', () => {
+ let wrapper;
+ const hash = '#L50';
+ const contentArray = Array.from({ length: 140 }, () => 'newline'); // simulate 140 lines of code
+ const rawTextBlob = contentArray.join('\n');
+ const languageMock = 'javascript';
+
+ const createComponent = ({ fileType = TEXT_FILE_TYPE, language = languageMock } = {}) => {
+ const simpleViewer = { fileType };
+
+ const dummyComponent = {
+ mixins: [highlightMixin],
+ inject: { highlightWorker: { default: workerMock } },
+ template: '<div>{{chunks[0]?.highlightedContent}}</div>',
+ created() {
+ this.initHighlightWorker({ rawTextBlob, simpleViewer, language });
+ },
+ methods: { onError: onErrorMock },
+ };
+
+ wrapper = shallowMount(dummyComponent, { mocks: { $route: { hash } } });
+ };
+
+ beforeEach(() => createComponent());
+
+ afterEach(() => wrapper.destroy());
+
+ describe('initHighlightWorker', () => {
+ const firstSeventyLines = contentArray.slice(0, LINES_PER_CHUNK).join('\n');
+
+ it('does not instruct worker if file is not a text file', () => {
+ workerMock.postMessage.mockClear();
+ createComponent({ fileType: 'markdown' });
+
+ expect(workerMock.postMessage).not.toHaveBeenCalled();
+ });
+
+ it('tracks event if a language is not supported and does not instruct worker', () => {
+ const unsupportedLanguage = 'some_unsupported_language';
+ const eventData = { label: EVENT_LABEL_FALLBACK, property: unsupportedLanguage };
+
+ jest.spyOn(Tracking, 'event');
+ workerMock.postMessage.mockClear();
+ createComponent({ language: unsupportedLanguage });
+
+ expect(Tracking.event).toHaveBeenCalledWith(undefined, EVENT_ACTION, eventData);
+ expect(onErrorMock).toHaveBeenCalled();
+ expect(workerMock.postMessage).not.toHaveBeenCalled();
+ });
+
+ it('generates a chunk for the first 70 lines of raw text', () => {
+ expect(splitIntoChunks).toHaveBeenCalledWith(languageMock, firstSeventyLines);
+ });
+
+ it('calls postMessage on the worker', () => {
+ expect(workerMock.postMessage.mock.calls.length).toBe(2);
+
+ // first call instructs worker to highlight the first 70 lines
+ expect(workerMock.postMessage.mock.calls[0][0]).toMatchObject({
+ content: firstSeventyLines,
+ language: languageMock,
+ });
+
+ // second call instructs worker to highlight all of the lines
+ expect(workerMock.postMessage.mock.calls[1][0]).toMatchObject({
+ content: rawTextBlob,
+ language: languageMock,
+ });
+ });
+ });
+
+ describe('worker message handling', () => {
+ const CHUNK_MOCK = { startingFrom: 0, totalLines: 70, highlightedContent: 'some content' };
+
+ beforeEach(() => workerMock.onmessage({ data: [CHUNK_MOCK] }));
+
+ it('updates the chunks data', () => {
+ expect(wrapper.text()).toBe(CHUNK_MOCK.highlightedContent);
+ });
+
+ it('highlights hash', () => {
+ expect(lineHighlighter.highlightHash).toHaveBeenCalledWith(hash);
+ });
+ });
+});
diff --git a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js
index 2c5bb86d8a5..c1495e8264a 100644
--- a/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js
+++ b/spec/frontend/vue_shared/components/date_time_picker/date_time_picker_input_spec.js
@@ -56,11 +56,11 @@ describe('DateTimePickerInput', () => {
it('input event is emitted when focus is lost', () => {
createComponent();
- jest.spyOn(wrapper.vm, '$emit');
+
const input = wrapper.find('input');
input.setValue(inputValue);
input.trigger('blur');
- expect(wrapper.vm.$emit).toHaveBeenCalledWith('input', inputValue);
+ expect(wrapper.emitted('input')[0][0]).toEqual(inputValue);
});
});
diff --git a/spec/lib/bulk_imports/clients/graphql_spec.rb b/spec/lib/bulk_imports/clients/graphql_spec.rb
index 3ce84dbfe2f..58e6992698c 100644
--- a/spec/lib/bulk_imports/clients/graphql_spec.rb
+++ b/spec/lib/bulk_imports/clients/graphql_spec.rb
@@ -2,7 +2,7 @@
require 'spec_helper'
-RSpec.describe BulkImports::Clients::Graphql do
+RSpec.describe BulkImports::Clients::Graphql, feature_category: :importers do
let_it_be(:config) { create(:bulk_import_configuration) }
subject { described_class.new(url: config.url, token: config.access_token) }
@@ -11,30 +11,60 @@ RSpec.describe BulkImports::Clients::Graphql do
let(:query) { '{ metadata { version } }' }
let(:graphql_client_double) { double }
let(:response_double) { double }
+ let(:version) { '14.0.0' }
before do
stub_const('BulkImports::MINIMUM_COMPATIBLE_MAJOR_VERSION', version)
- allow(graphql_client_double).to receive(:execute)
- allow(subject).to receive(:client).and_return(graphql_client_double)
- allow(graphql_client_double).to receive(:execute).with(query).and_return(response_double)
- allow(response_double).to receive_message_chain(:data, :metadata, :version).and_return(version)
end
- context 'when source instance is compatible' do
- let(:version) { '14.0.0' }
+ describe 'source instance validation' do
+ before do
+ allow(graphql_client_double).to receive(:execute)
+ allow(subject).to receive(:client).and_return(graphql_client_double)
+ allow(graphql_client_double).to receive(:execute).with(query).and_return(response_double)
+ allow(response_double).to receive_message_chain(:data, :metadata, :version).and_return(version)
+ end
- it 'marks source instance as compatible' do
- subject.execute('test')
+ context 'when source instance is compatible' do
+ it 'marks source instance as compatible' do
+ subject.execute('test')
- expect(subject.instance_variable_get(:@compatible_instance_version)).to eq(true)
+ expect(subject.instance_variable_get(:@compatible_instance_version)).to eq(true)
+ end
+ end
+
+ context 'when source instance is incompatible' do
+ let(:version) { '13.0.0' }
+
+ it 'raises an error' do
+ expect { subject.execute('test') }.to raise_error(::BulkImports::Error, "Unsupported GitLab version. Source instance must run GitLab version #{BulkImport::MIN_MAJOR_VERSION} or later.")
+ end
end
end
- context 'when source instance is incompatible' do
- let(:version) { '13.0.0' }
+ describe 'network errors' do
+ before do
+ allow(Gitlab::HTTP)
+ .to receive(:post)
+ .and_return(response_double)
+ end
+
+ context 'when response cannot be parsed' do
+ let(:response_double) { instance_double(HTTParty::Response, body: 'invalid', success?: true) }
+
+ it 'raises network error' do
+ expect { subject.execute('test') }.to raise_error(BulkImports::NetworkError, /unexpected character/)
+ end
+ end
+
+ context 'when response is unsuccessful' do
+ let(:response_double) { instance_double(HTTParty::Response, success?: false, code: 503) }
+
+ it 'raises network error' do
+ allow(response_double).to receive_message_chain(:request, :path, :path).and_return('foo/bar')
- it 'raises an error' do
- expect { subject.execute('test') }.to raise_error(::BulkImports::Error, "Unsupported GitLab version. Source instance must run GitLab version #{BulkImport::MIN_MAJOR_VERSION} or later.")
+ expect { subject.execute('test') }.to raise_error(BulkImports::NetworkError, 'Unsuccessful response 503 from foo/bar')
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/context_spec.rb b/spec/lib/gitlab/ci/config/external/context_spec.rb
index 45efb16434b..1fd3cf3c99f 100644
--- a/spec/lib/gitlab/ci/config/external/context_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/context_spec.rb
@@ -14,7 +14,8 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin
describe 'attributes' do
context 'with values' do
it { is_expected.to have_attributes(**attributes) }
- it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.expandset).to eq([]) }
+ it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::NEW_MAX_INCLUDES) }
it { expect(subject.execution_deadline).to eq(0) }
it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
@@ -25,11 +26,39 @@ RSpec.describe Gitlab::Ci::Config::External::Context, feature_category: :pipelin
let(:attributes) { { project: nil, user: nil, sha: nil } }
it { is_expected.to have_attributes(**attributes) }
- it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.expandset).to eq([]) }
+ it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::NEW_MAX_INCLUDES) }
it { expect(subject.execution_deadline).to eq(0) }
it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
end
+
+ context 'when FF ci_includes_count_duplicates is disabled' do
+ before do
+ stub_feature_flags(ci_includes_count_duplicates: false)
+ end
+
+ context 'with values' do
+ it { is_expected.to have_attributes(**attributes) }
+ it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::MAX_INCLUDES) }
+ it { expect(subject.execution_deadline).to eq(0) }
+ it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
+ it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
+ it { expect(subject.variables_hash).to include('a' => 'b') }
+ end
+
+ context 'without values' do
+ let(:attributes) { { project: nil, user: nil, sha: nil } }
+
+ it { is_expected.to have_attributes(**attributes) }
+ it { expect(subject.expandset).to eq(Set.new) }
+ it { expect(subject.max_includes).to eq(Gitlab::Ci::Config::External::Context::MAX_INCLUDES) }
+ it { expect(subject.execution_deadline).to eq(0) }
+ it { expect(subject.variables).to be_instance_of(Gitlab::Ci::Variables::Collection) }
+ it { expect(subject.variables_hash).to be_instance_of(ActiveSupport::HashWithIndifferentAccess) }
+ end
+ end
end
describe '#set_deadline' do
diff --git a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
index 7bdd519cb74..a219666f24e 100644
--- a/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper/verifier_spec.rb
@@ -150,7 +150,7 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
end
end
- context 'when max_includes is exceeded' do
+ context 'when total file count exceeds max_includes' do
context 'when files are nested' do
let(:files) do
[
@@ -158,11 +158,8 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
]
end
- before do
- allow(context).to receive(:max_includes).and_return(1)
- end
-
it 'raises Processor::IncludeError' do
+ allow(context).to receive(:max_includes).and_return(1)
expect { process }.to raise_error(Gitlab::Ci::Config::External::Processor::IncludeError)
end
end
@@ -175,13 +172,36 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper::Verifier, feature_category:
]
end
- before do
+ it 'raises Mapper::TooManyIncludesError' do
allow(context).to receive(:max_includes).and_return(1)
+ expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError)
end
+ end
- it 'raises Mapper::TooManyIncludesError' do
+ context 'when files are duplicates' do
+ let(:files) do
+ [
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context),
+ Gitlab::Ci::Config::External::File::Local.new({ local: 'myfolder/file1.yml' }, context)
+ ]
+ end
+
+ it 'raises error' do
+ allow(context).to receive(:max_includes).and_return(2)
expect { process }.to raise_error(Gitlab::Ci::Config::External::Mapper::TooManyIncludesError)
end
+
+ context 'when FF ci_includes_count_duplicates is disabled' do
+ before do
+ stub_feature_flags(ci_includes_count_duplicates: false)
+ end
+
+ it 'does not raise error' do
+ allow(context).to receive(:max_includes).and_return(2)
+ expect { process }.not_to raise_error
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/config/external/mapper_spec.rb b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
index dc14bf18b98..b3115617084 100644
--- a/spec/lib/gitlab/ci/config/external/mapper_spec.rb
+++ b/spec/lib/gitlab/ci/config/external/mapper_spec.rb
@@ -230,9 +230,20 @@ RSpec.describe Gitlab::Ci::Config::External::Mapper, feature_category: :pipeline
expect { process }.not_to raise_error
end
- it 'has expanset with one' do
+ it 'has expanset with two' do
process
- expect(context.expandset.size).to eq(1)
+ expect(context.expandset.size).to eq(2)
+ end
+
+ context 'when FF ci_includes_count_duplicates is disabled' do
+ before do
+ stub_feature_flags(ci_includes_count_duplicates: false)
+ end
+
+ it 'has expanset with one' do
+ process
+ expect(context.expandset.size).to eq(1)
+ end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 3fb867f8ee8..e87667d9604 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2129,6 +2129,7 @@ RSpec.describe User, feature_category: :user_profile do
expect(user.encrypted_otp_secret_salt).to be_nil
expect(user.otp_backup_codes).to be_nil
expect(user.otp_grace_period_started_at).to be_nil
+ expect(user.otp_secret_expires_at).to be_nil
end
end