summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-01-12 09:15:13 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2022-01-12 09:15:13 +0000
commit612dd7d31ab927dd79968a6be7cb36599291bace (patch)
tree6af6f616794ec0bd40bec2174d5bc58a4231fb21
parent563d0d3bc956f6d6a9805720dade3b72bd488043 (diff)
downloadgitlab-ce-612dd7d31ab927dd79968a6be7cb36599291bace.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--Gemfile6
-rw-r--r--Gemfile.lock25
-rw-r--r--app/assets/javascripts/admin/users/components/user_actions.vue3
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue2
-rw-r--r--app/assets/javascripts/cycle_analytics/components/stage_table.vue28
-rw-r--r--app/assets/javascripts/integrations/edit/components/integration_form.vue75
-rw-r--r--app/assets/javascripts/related_issues/components/related_issues_list.vue2
-rw-r--r--app/assets/javascripts/runner/components/cells/runner_actions_cell.vue4
-rw-r--r--app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue4
-rw-r--r--app/assets/javascripts/runner/components/runner_update_form.vue8
-rw-r--r--app/assets/javascripts/runner/components/search_tokens/tag_token.vue4
-rw-r--r--app/assets/javascripts/runner/group_runners/group_runners_app.vue4
-rw-r--r--app/assets/javascripts/runner/runner_details/runner_details_app.vue4
-rw-r--r--app/assets/stylesheets/page_bundles/boards.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/issues_list.scss4
-rw-r--r--app/helpers/issues_helper.rb2
-rw-r--r--app/models/deployment.rb1
-rw-r--r--app/models/preloaders/environments/deployment_preloader.rb43
-rw-r--r--app/models/user.rb2
-rw-r--r--app/serializers/analytics_build_entity.rb3
-rw-r--r--app/serializers/analytics_issue_entity.rb3
-rw-r--r--app/serializers/environment_serializer.rb12
-rw-r--r--app/services/ci/process_build_service.rb19
-rw-r--r--app/services/packages/terraform_module/create_package_service.rb2
-rw-r--r--app/views/admin/users/_head.html.haml15
-rw-r--r--config/feature_flags/development/custom_preloader_for_deployments.yml8
-rw-r--r--config/initializers/active_record_transaction_observer.rb11
-rw-r--r--config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml2
-rw-r--r--config/metrics/counts_7d/20210216184805_i_package_composer_deploy_token_weekly.yml2
-rw-r--r--config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml2
-rw-r--r--config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml2
-rw-r--r--db/post_migrate/20211210140000_add_temporary_static_object_token_index.rb15
-rw-r--r--db/post_migrate/20211210140629_encrypt_static_object_token.rb22
-rw-r--r--db/schema_migrations/202112101400001
-rw-r--r--db/schema_migrations/202112101406291
-rw-r--r--db/structure.sql2
-rw-r--r--doc/administration/operations/cleaning_up_redis_sessions.md9
-rw-r--r--doc/api/graphql/reference/index.md8
-rw-r--r--doc/development/snowplow/dictionary.md4
-rw-r--r--doc/security/img/unlock_user_v14_7.pngbin0 -> 31666 bytes
-rw-r--r--doc/security/unlock_user.md18
-rw-r--r--doc/user/application_security/vulnerabilities/index.md2
-rw-r--r--lib/banzai/filter/base_sanitization_filter.rb2
-rw-r--r--lib/feature.rb9
-rw-r--r--lib/gitlab/background_migration/encrypt_static_object_token.rb70
-rw-r--r--lib/gitlab/usage_data_counters/hll_redis_counter.rb2
-rw-r--r--lib/gitlab/utils/sanitize_node_link.rb6
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/controllers/projects/repositories_controller_spec.rb19
-rw-r--r--spec/features/admin/users/user_spec.rb20
-rw-r--r--spec/features/boards/boards_spec.rb2
-rw-r--r--spec/features/groups/issues_spec.rb4
-rw-r--r--spec/frontend/boards/components/board_card_spec.js4
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js8
-rw-r--r--spec/frontend/cycle_analytics/stage_table_spec.js25
-rw-r--r--spec/frontend/runner/components/cells/runner_actions_cell_spec.js10
-rw-r--r--spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js6
-rw-r--r--spec/frontend/runner/components/runner_update_form_spec.js10
-rw-r--r--spec/frontend/runner/components/search_tokens/tag_token_spec.js6
-rw-r--r--spec/frontend/runner/group_runners/group_runners_app_spec.js4
-rw-r--r--spec/frontend/runner/runner_detail/runner_details_app_spec.js4
-rw-r--r--spec/lib/feature_spec.rb26
-rw-r--r--spec/lib/gitlab/background_migration/encrypt_static_object_token_spec.rb56
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb3
-rw-r--r--spec/migrations/20211210140629_encrypt_static_object_token_spec.rb50
-rw-r--r--spec/models/preloaders/environments/deployment_preloader_spec.rb65
-rw-r--r--spec/models/user_spec.rb23
-rw-r--r--spec/serializers/analytics_build_entity_spec.rb4
-rw-r--r--spec/serializers/analytics_issue_entity_spec.rb4
-rw-r--r--spec/serializers/environment_serializer_spec.rb36
-rw-r--r--spec/services/packages/terraform_module/create_package_service_spec.rb2
72 files changed, 707 insertions, 166 deletions
diff --git a/Gemfile b/Gemfile
index 6be591ce6eb..786fdadd157 100644
--- a/Gemfile
+++ b/Gemfile
@@ -170,7 +170,7 @@ gem 'asciidoctor-kroki', '~> 0.5.0', require: false
gem 'rouge', '~> 3.27.0'
gem 'truncato', '~> 0.7.11'
gem 'bootstrap_form', '~> 4.2.0'
-gem 'nokogiri', '~> 1.11.4'
+gem 'nokogiri', '~> 1.12'
gem 'escape_utils', '~> 1.1'
# Calendar rendering
@@ -264,7 +264,7 @@ gem 'ruby-fogbugz', '~> 0.2.1'
gem 'kubeclient', '~> 4.9.2'
# Sanitize user input
-gem 'sanitize', '~> 5.2.1'
+gem 'sanitize', '~> 6.0'
gem 'babosa', '~> 1.0.4'
# Sanitizes SVG input
@@ -277,7 +277,7 @@ gem 'licensee', '~> 9.14.1'
gem 'charlock_holmes', '~> 0.7.7'
# Detect mime content type from content
-gem 'ruby-magic', '~> 0.4'
+gem 'ruby-magic', '~> 0.5'
# Faster blank
gem 'fast_blank'
diff --git a/Gemfile.lock b/Gemfile.lock
index 645a38e155b..69e37a8f430 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -754,7 +754,7 @@ GEM
mini_histogram (0.3.1)
mini_magick (4.10.1)
mini_mime (1.1.1)
- mini_portile2 (2.5.3)
+ mini_portile2 (2.6.1)
minitest (5.11.3)
mixlib-cli (2.1.8)
mixlib-config (3.0.9)
@@ -792,11 +792,9 @@ GEM
netrc (0.11.0)
nio4r (2.5.8)
no_proxy_fix (0.1.2)
- nokogiri (1.11.7)
- mini_portile2 (~> 2.5.0)
+ nokogiri (1.12.5)
+ mini_portile2 (~> 2.6.1)
racc (~> 1.4)
- nokogumbo (2.0.2)
- nokogiri (~> 1.8, >= 1.8.4)
notiffany (0.1.3)
nenv (~> 0.1)
shellany (~> 0.0)
@@ -954,7 +952,7 @@ GEM
puma (>= 2.7)
pyu-ruby-sasl (0.0.3.3)
raabro (1.1.6)
- racc (1.5.2)
+ racc (1.6.0)
rack (2.2.3)
rack-accept (0.4.5)
rack (>= 0.4)
@@ -1126,8 +1124,8 @@ GEM
rubocop-ast (>= 0.7.1)
ruby-fogbugz (0.2.1)
crack (~> 0.4)
- ruby-magic (0.4.0)
- mini_portile2 (~> 2.5.0)
+ ruby-magic (0.5.3)
+ mini_portile2 (~> 2.6)
ruby-prof (1.3.1)
ruby-progressbar (1.11.0)
ruby-saml (1.13.0)
@@ -1144,10 +1142,9 @@ GEM
safe_yaml (1.0.4)
safety_net_attestation (0.4.0)
jwt (~> 2.0)
- sanitize (5.2.1)
+ sanitize (6.0.0)
crass (~> 1.0.2)
- nokogiri (>= 1.8.0)
- nokogumbo (~> 2.0)
+ nokogiri (>= 1.12.0)
sass (3.5.5)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@@ -1549,7 +1546,7 @@ DEPENDENCIES
net-ldap (~> 0.16.3)
net-ntp
net-ssh (~> 6.0)
- nokogiri (~> 1.11.4)
+ nokogiri (~> 1.12)
oauth2 (~> 1.4)
octokit (~> 4.15)
ohai (~> 16.10)
@@ -1617,14 +1614,14 @@ DEPENDENCIES
rspec_junit_formatter
rspec_profiling (~> 0.0.6)
ruby-fogbugz (~> 0.2.1)
- ruby-magic (~> 0.4)
+ ruby-magic (~> 0.5)
ruby-prof (~> 1.3.0)
ruby-progressbar (~> 1.10)
ruby-saml (~> 1.13.0)
ruby_parser (~> 3.15)
rubyzip (~> 2.0.0)
rugged (~> 1.2)
- sanitize (~> 5.2.1)
+ sanitize (~> 6.0)
sassc-rails (~> 2.1.0)
sd_notify (~> 0.1.0)
seed-fu (~> 2.3.7)
diff --git a/app/assets/javascripts/admin/users/components/user_actions.vue b/app/assets/javascripts/admin/users/components/user_actions.vue
index 567d7151847..f5d21ece138 100644
--- a/app/assets/javascripts/admin/users/components/user_actions.vue
+++ b/app/assets/javascripts/admin/users/components/user_actions.vue
@@ -109,12 +109,13 @@ export default {
<div v-if="hasDropdownActions" class="gl-p-2">
<gl-dropdown
data-testid="dropdown-toggle"
- right
:text="$options.i18n.userAdministration"
:text-sr-only="!showButtonLabels"
icon="ellipsis_h"
data-qa-selector="user_actions_dropdown_toggle"
:data-qa-username="user.username"
+ no-caret
+ right
>
<gl-dropdown-section-header>{{
$options.i18n.userAdministration
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 563bed6a6b8..dc821cb9f58 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -72,7 +72,7 @@ export default {
data-qa-selector="board_card"
:class="{
'multi-select': multiSelectVisible,
- 'user-can-drag': isDraggable,
+ 'gl-cursor-grab': isDraggable,
'is-disabled': isDisabled,
'is-active': isActive,
'gl-cursor-not-allowed gl-bg-gray-10': item.isLoading,
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index 19004518edf..6835d83a66c 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -263,7 +263,7 @@ export default {
>
<h3
:class="{
- 'user-can-drag': userCanDrag,
+ 'gl-cursor-grab': userCanDrag,
'gl-py-3 gl-h-full': list.collapsed && !isSwimlanesHeader,
'gl-border-b-0': list.collapsed || isSwimlanesHeader,
'gl-py-2': list.collapsed && isSwimlanesHeader,
diff --git a/app/assets/javascripts/cycle_analytics/components/stage_table.vue b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
index fc4dfafb809..1139403bcf7 100644
--- a/app/assets/javascripts/cycle_analytics/components/stage_table.vue
+++ b/app/assets/javascripts/cycle_analytics/components/stage_table.vue
@@ -89,6 +89,11 @@ export default {
required: false,
default: true,
},
+ includeProjectName: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
if (this.pagination) {
@@ -144,8 +149,13 @@ export default {
isMrLink(url = '') {
return url.includes('/merge_request');
},
- itemId({ url, iid }) {
- return this.isMrLink(url) ? `!${iid}` : `#${iid}`;
+ itemId({ iid, projectPath }, separator = '#') {
+ const prefix = this.includeProjectName ? projectPath : '';
+ return `${prefix}${separator}${iid}`;
+ },
+ itemDisplayName(item) {
+ const separator = this.isMrLink(item.url) ? '!' : '#';
+ return this.itemId(item, separator);
},
itemTitle(item) {
return item.title || item.name;
@@ -201,8 +211,11 @@ export default {
<div data-testid="vsa-stage-event">
<div v-if="item.id" data-testid="vsa-stage-content">
<p class="gl-m-0">
- <gl-link class="gl-text-black-normal pipeline-id" :href="item.url"
- >#{{ item.id }}</gl-link
+ <gl-link
+ data-testid="vsa-stage-event-link"
+ class="gl-text-black-normal pipeline-id"
+ :href="item.url"
+ >{{ itemId(item.id, '#') }}</gl-link
>
<gl-icon :size="16" name="fork" />
<gl-link
@@ -240,7 +253,12 @@ export default {
<gl-link class="gl-text-black-normal" :href="item.url">{{ itemTitle(item) }}</gl-link>
</h5>
<p class="gl-m-0">
- <gl-link class="gl-text-black-normal" :href="item.url">{{ itemId(item) }}</gl-link>
+ <gl-link
+ data-testid="vsa-stage-event-link"
+ class="gl-text-black-normal"
+ :href="item.url"
+ >{{ itemDisplayName(item) }}</gl-link
+ >
<span class="gl-font-lg">&middot;</span>
<span data-testid="vsa-stage-event-date">
{{ s__('OpenedNDaysAgo|Opened') }}
diff --git a/app/assets/javascripts/integrations/edit/components/integration_form.vue b/app/assets/javascripts/integrations/edit/components/integration_form.vue
index 167130cb6f2..ca38c83547b 100644
--- a/app/assets/javascripts/integrations/edit/components/integration_form.vue
+++ b/app/assets/javascripts/integrations/edit/components/integration_form.vue
@@ -216,62 +216,69 @@ export default {
v-bind="propsSource.jiraIssuesProps"
@request-jira-issue-types="onRequestJiraIssueTypes"
/>
- <div v-if="isEditable" class="footer-block row-content-block">
- <template v-if="isInstanceOrGroupLevel">
+
+ <div
+ v-if="isEditable"
+ class="footer-block row-content-block gl-display-flex gl-justify-content-space-between"
+ >
+ <div>
+ <template v-if="isInstanceOrGroupLevel">
+ <gl-button
+ v-gl-modal.confirmSaveIntegration
+ category="primary"
+ variant="confirm"
+ :loading="isSaving"
+ :disabled="disableButtons"
+ data-testid="save-button-instance-group"
+ data-qa-selector="save_changes_button"
+ >
+ {{ __('Save changes') }}
+ </gl-button>
+ <confirmation-modal @submit="onSaveClick" />
+ </template>
<gl-button
- v-gl-modal.confirmSaveIntegration
+ v-else
category="primary"
variant="confirm"
+ type="submit"
:loading="isSaving"
:disabled="disableButtons"
- data-testid="save-button-instance-group"
+ data-testid="save-button"
data-qa-selector="save_changes_button"
+ @click.prevent="onSaveClick"
>
{{ __('Save changes') }}
</gl-button>
- <confirmation-modal @submit="onSaveClick" />
- </template>
- <gl-button
- v-else
- category="primary"
- variant="confirm"
- type="submit"
- :loading="isSaving"
- :disabled="disableButtons"
- data-testid="save-button"
- data-qa-selector="save_changes_button"
- @click.prevent="onSaveClick"
- >
- {{ __('Save changes') }}
- </gl-button>
- <gl-button
- v-if="showTestButton"
- category="secondary"
- variant="confirm"
- :loading="isTesting"
- :disabled="disableButtons"
- data-testid="test-button"
- @click.prevent="onTestClick"
- >
- {{ __('Test settings') }}
- </gl-button>
+ <gl-button
+ v-if="showTestButton"
+ category="secondary"
+ variant="confirm"
+ :loading="isTesting"
+ :disabled="disableButtons"
+ data-testid="test-button"
+ @click.prevent="onTestClick"
+ >
+ {{ __('Test settings') }}
+ </gl-button>
+
+ <gl-button :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button>
+ </div>
<template v-if="showResetButton">
<gl-button
v-gl-modal.confirmResetIntegration
- category="secondary"
- variant="confirm"
+ category="tertiary"
+ variant="danger"
:loading="isResetting"
:disabled="disableButtons"
data-testid="reset-button"
>
{{ __('Reset') }}
</gl-button>
+
<reset-confirmation-modal @reset="onResetClick" />
</template>
-
- <gl-button :href="propsSource.cancelPath">{{ __('Cancel') }}</gl-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/related_issues/components/related_issues_list.vue b/app/assets/javascripts/related_issues/components/related_issues_list.vue
index 58138655241..8b39851405e 100644
--- a/app/assets/javascripts/related_issues/components/related_issues_list.vue
+++ b/app/assets/javascripts/related_issues/components/related_issues_list.vue
@@ -110,7 +110,7 @@ export default {
v-for="issue in relatedIssues"
:key="issue.id"
:class="{
- 'user-can-drag': canReorder,
+ 'gl-cursor-grab': canReorder,
'sortable-row': canReorder,
'card card-slim': canReorder,
}"
diff --git a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue
index 1e9e866d382..82ee41d6def 100644
--- a/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue
+++ b/app/assets/javascripts/runner/components/cells/runner_actions_cell.vue
@@ -1,6 +1,6 @@
<script>
import { GlButton, GlButtonGroup, GlModalDirective, GlTooltipDirective } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { __, s__, sprintf } from '~/locale';
import runnerDeleteMutation from '~/runner/graphql/runner_delete.mutation.graphql';
import runnerActionsUpdateMutation from '~/runner/graphql/runner_actions_update.mutation.graphql';
@@ -139,7 +139,7 @@ export default {
onError(error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
},
diff --git a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
index 3bb15bff8d8..edcbcb2bf69 100644
--- a/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
+++ b/app/assets/javascripts/runner/components/registration/registration_token_reset_dropdown_item.vue
@@ -1,6 +1,6 @@
<script>
import { GlDropdownItem, GlLoadingIcon } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { TYPE_GROUP, TYPE_PROJECT } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import { __, s__ } from '~/locale';
@@ -91,7 +91,7 @@ export default {
},
onError(error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
},
diff --git a/app/assets/javascripts/runner/components/runner_update_form.vue b/app/assets/javascripts/runner/components/runner_update_form.vue
index 9a6fc07f6dd..aeb5869bcce 100644
--- a/app/assets/javascripts/runner/components/runner_update_form.vue
+++ b/app/assets/javascripts/runner/components/runner_update_form.vue
@@ -11,7 +11,7 @@ import {
modelToUpdateMutationVariables,
runnerToModel,
} from 'ee_else_ce/runner/runner_details/runner_update_form_utils';
-import createFlash, { FLASH_TYPES } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/flash';
import { __ } from '~/locale';
import { captureException } from '~/runner/sentry_utils';
import { ACCESS_LEVEL_NOT_PROTECTED, ACCESS_LEVEL_REF_PROTECTED, PROJECT_TYPE } from '../constants';
@@ -75,14 +75,14 @@ export default {
if (errors?.length) {
// Validation errors need not be thrown
- createFlash({ message: errors[0] });
+ createAlert({ message: errors[0] });
return;
}
this.onSuccess();
} catch (error) {
const { message } = error;
- createFlash({ message });
+ createAlert({ message });
this.reportToSentry(error);
} finally {
@@ -90,7 +90,7 @@ export default {
}
},
onSuccess() {
- createFlash({ message: __('Changes saved.'), type: FLASH_TYPES.SUCCESS });
+ createAlert({ message: __('Changes saved.'), variant: VARIANT_SUCCESS });
this.model = runnerToModel(this.runner);
},
reportToSentry(error) {
diff --git a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
index 7461308ab91..59230bb809e 100644
--- a/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
+++ b/app/assets/javascripts/runner/components/search_tokens/tag_token.vue
@@ -1,6 +1,6 @@
<script>
import { GlFilteredSearchSuggestion, GlToken } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { s__ } from '~/locale';
@@ -50,7 +50,7 @@ export default {
try {
this.tags = await this.getTagsOptions(searchTerm);
} catch {
- createFlash({
+ createAlert({
message: s__('Runners|Something went wrong while fetching the tags suggestions'),
});
} finally {
diff --git a/app/assets/javascripts/runner/group_runners/group_runners_app.vue b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
index a58a53a6a0d..f33f28c11e3 100644
--- a/app/assets/javascripts/runner/group_runners/group_runners_app.vue
+++ b/app/assets/javascripts/runner/group_runners/group_runners_app.vue
@@ -1,6 +1,6 @@
<script>
import { GlLink } from '@gitlab/ui';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { fetchPolicies } from '~/lib/graphql';
import { updateHistory } from '~/lib/utils/url_utility';
import { formatNumber, sprintf, s__ } from '~/locale';
@@ -84,7 +84,7 @@ export default {
};
},
error(error) {
- createFlash({ message: I18N_FETCH_ERROR });
+ createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
diff --git a/app/assets/javascripts/runner/runner_details/runner_details_app.vue b/app/assets/javascripts/runner/runner_details/runner_details_app.vue
index 7b5f4a90351..f0a1d781a3a 100644
--- a/app/assets/javascripts/runner/runner_details/runner_details_app.vue
+++ b/app/assets/javascripts/runner/runner_details/runner_details_app.vue
@@ -1,5 +1,5 @@
<script>
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { TYPE_CI_RUNNER } from '~/graphql_shared/constants';
import { convertToGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '../components/runner_header.vue';
@@ -34,7 +34,7 @@ export default {
};
},
error(error) {
- createFlash({ message: I18N_FETCH_ERROR });
+ createAlert({ message: I18N_FETCH_ERROR });
this.reportToSentry(error);
},
diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss
index d4c59a6ab0c..f91ca489bdf 100644
--- a/app/assets/stylesheets/page_bundles/boards.scss
+++ b/app/assets/stylesheets/page_bundles/boards.scss
@@ -1,9 +1,5 @@
@import 'mixins_and_variables_and_functions';
-.user-can-drag {
- cursor: grab;
-}
-
.is-ghost {
opacity: 0.3;
pointer-events: none;
diff --git a/app/assets/stylesheets/page_bundles/issues_list.scss b/app/assets/stylesheets/page_bundles/issues_list.scss
index 8a958bdf0c5..41515a98e0a 100644
--- a/app/assets/stylesheets/page_bundles/issues_list.scss
+++ b/app/assets/stylesheets/page_bundles/issues_list.scss
@@ -35,10 +35,6 @@
}
}
-.user-can-drag {
- cursor: grab;
-}
-
.is-ghost {
opacity: 0.3;
pointer-events: none;
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index b1a5e4068cc..5aa2aca37f3 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -7,7 +7,7 @@ module IssuesHelper
classes = ["issue"]
classes << "closed" if issue.closed?
classes << "today" if issue.new?
- classes << "user-can-drag" if @sort == 'relative_position'
+ classes << "gl-cursor-grab" if @sort == 'relative_position'
classes.join(' ')
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 943abdc7c1c..453c6bce362 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -8,6 +8,7 @@ class Deployment < ApplicationRecord
include Importable
include Gitlab::Utils::StrongMemoize
include FastDestroyAll
+ include FromUnion
StatusUpdateError = Class.new(StandardError)
StatusSyncError = Class.new(StandardError)
diff --git a/app/models/preloaders/environments/deployment_preloader.rb b/app/models/preloaders/environments/deployment_preloader.rb
new file mode 100644
index 00000000000..fcf892698bb
--- /dev/null
+++ b/app/models/preloaders/environments/deployment_preloader.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+module Preloaders
+ module Environments
+ # This class is to batch-load deployments of multiple environments.
+ # The deployments to batch-load are fetched using UNION of N selects in a single query instead of default scoping with `IN (environment_id1, environment_id2 ...)`.
+ # See https://gitlab.com/gitlab-org/gitlab/-/issues/345672#note_761852224 for more details.
+ class DeploymentPreloader
+ attr_reader :environments
+
+ def initialize(environments)
+ @environments = environments
+ end
+
+ def execute_with_union(association_name, association_attributes)
+ load_deployment_association(association_name, association_attributes)
+ end
+
+ private
+
+ def load_deployment_association(association_name, association_attributes)
+ return unless environments.present?
+
+ union_arg = environments.inject([]) do |result, environment|
+ result << environment.association(association_name).scope
+ end
+
+ union_sql = Deployment.from_union(union_arg).to_sql
+
+ deployments = Deployment
+ .from("(#{union_sql}) #{::Deployment.table_name}")
+ .preload(association_attributes)
+
+ deployments_by_environment_id = deployments.index_by(&:environment_id)
+
+ environments.each do |environment|
+ environment.association(association_name).target = deployments_by_environment_id[environment.id]
+ environment.association(association_name).loaded!
+ end
+ end
+ end
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
index 0e0388b52b7..3dacff4a989 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -48,7 +48,7 @@ class User < ApplicationRecord
add_authentication_token_field :incoming_email_token, token_generator: -> { SecureRandom.hex.to_i(16).to_s(36) }
add_authentication_token_field :feed_token
- add_authentication_token_field :static_object_token
+ add_authentication_token_field :static_object_token, encrypted: :optional
default_value_for :admin, false
default_value_for(:external) { Gitlab::CurrentSettings.user_default_external }
diff --git a/app/serializers/analytics_build_entity.rb b/app/serializers/analytics_build_entity.rb
index 99663c8d5eb..5beb945257f 100644
--- a/app/serializers/analytics_build_entity.rb
+++ b/app/serializers/analytics_build_entity.rb
@@ -9,6 +9,9 @@ class AnalyticsBuildEntity < Grape::Entity
expose :ref, as: :branch
expose :short_sha
expose :author, using: UserEntity
+ expose :project_path do |build|
+ build.project.path
+ end
expose :started_at, as: :date do |build|
interval_in_words(build[:started_at])
diff --git a/app/serializers/analytics_issue_entity.rb b/app/serializers/analytics_issue_entity.rb
index 307ce14a921..b3244740ae1 100644
--- a/app/serializers/analytics_issue_entity.rb
+++ b/app/serializers/analytics_issue_entity.rb
@@ -6,6 +6,9 @@ class AnalyticsIssueEntity < Grape::Entity
expose :title
expose :author, using: UserEntity
+ expose :project_path do |object|
+ object[:project_path]
+ end
expose :iid do |object|
object[:iid].to_s
diff --git a/app/serializers/environment_serializer.rb b/app/serializers/environment_serializer.rb
index 2fb1ad52135..11445f93609 100644
--- a/app/serializers/environment_serializer.rb
+++ b/app/serializers/environment_serializer.rb
@@ -52,7 +52,17 @@ class EnvironmentSerializer < BaseSerializer
end
def batch_load(resource)
- resource = resource.preload(environment_associations)
+ if ::Feature.enabled?(:custom_preloader_for_deployments, default_enabled: :yaml)
+ resource = resource.preload(environment_associations.except(:last_deployment, :upcoming_deployment))
+
+ Preloaders::Environments::DeploymentPreloader.new(resource)
+ .execute_with_union(:last_deployment, deployment_associations)
+
+ Preloaders::Environments::DeploymentPreloader.new(resource)
+ .execute_with_union(:upcoming_deployment, deployment_associations)
+ else
+ resource = resource.preload(environment_associations)
+ end
resource.all.to_a.tap do |environments|
environments.each do |environment|
diff --git a/app/services/ci/process_build_service.rb b/app/services/ci/process_build_service.rb
index 5271c0fe93d..e6ec65fcc91 100644
--- a/app/services/ci/process_build_service.rb
+++ b/app/services/ci/process_build_service.rb
@@ -4,14 +4,7 @@ module Ci
class ProcessBuildService < BaseService
def execute(build, current_status)
if valid_statuses_for_build(build).include?(current_status)
- if build.schedulable?
- build.schedule
- elsif build.action?
- build.actionize
- else
- enqueue(build)
- end
-
+ process(build)
true
else
build.skip
@@ -21,6 +14,16 @@ module Ci
private
+ def process(build)
+ if build.schedulable?
+ build.schedule
+ elsif build.action?
+ build.actionize
+ else
+ enqueue(build)
+ end
+ end
+
def enqueue(build)
build.enqueue
end
diff --git a/app/services/packages/terraform_module/create_package_service.rb b/app/services/packages/terraform_module/create_package_service.rb
index 03f749edfa8..d1bc79089a3 100644
--- a/app/services/packages/terraform_module/create_package_service.rb
+++ b/app/services/packages/terraform_module/create_package_service.rb
@@ -7,7 +7,7 @@ module Packages
def execute
return error('Version is empty.', 400) if params[:module_version].blank?
- return error('Package already exists.', 403) if current_package_exists_elsewhere?
+ return error('Access Denied', 403) if current_package_exists_elsewhere?
return error('Package version already exists.', 403) if current_package_version_exists?
return error('File is too large.', 400) if file_size_exceeded?
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
index bafb2085589..ca14d898d79 100644
--- a/app/views/admin/users/_head.html.haml
+++ b/app/views/admin/users/_head.html.haml
@@ -3,23 +3,26 @@
%h3.page-title.gl-m-0
= @user.name
- if @user.blocked_pending_approval?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Pending approval)')
- elsif @user.banned?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Banned)')
- elsif @user.blocked?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Blocked)')
- if @user.internal?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Internal)')
- if @user.admin
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Admin)')
- if @user.deactivated?
- %span.cred
+ %span.gl-text-red-500
= s_('AdminUsers|(Deactivated)')
+ - if @user.access_locked?
+ %span.gl-text-red-500
+ = s_('AdminUsers|(Locked)')
= render_if_exists 'admin/users/auditor_user_badge'
= render_if_exists 'admin/users/gma_user_badge'
diff --git a/config/feature_flags/development/custom_preloader_for_deployments.yml b/config/feature_flags/development/custom_preloader_for_deployments.yml
new file mode 100644
index 00000000000..f8abcb4ba4a
--- /dev/null
+++ b/config/feature_flags/development/custom_preloader_for_deployments.yml
@@ -0,0 +1,8 @@
+---
+name: custom_preloader_for_deployments
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/75767
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/348289
+milestone: '14.7'
+type: development
+group: group::release
+default_enabled: false
diff --git a/config/initializers/active_record_transaction_observer.rb b/config/initializers/active_record_transaction_observer.rb
index fc9b73d656e..b90b3a39ac1 100644
--- a/config/initializers/active_record_transaction_observer.rb
+++ b/config/initializers/active_record_transaction_observer.rb
@@ -2,17 +2,8 @@
return unless Gitlab.com? || Gitlab.dev_or_test_env?
-def feature_flags_available?
- # When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
- active_db_connection = ActiveRecord::Base.connection.active? rescue false
-
- active_db_connection && Feature::FlipperFeature.table_exists?
-rescue ActiveRecord::NoDatabaseError
- false
-end
-
Gitlab::Application.configure do
- if feature_flags_available? && ::Feature.enabled?(:active_record_transactions_tracking, type: :ops, default_enabled: :yaml)
+ if Feature.feature_flags_available? && ::Feature.enabled?(:active_record_transactions_tracking, type: :ops, default_enabled: :yaml)
Gitlab::Database::Transaction::Observer.register!
end
end
diff --git a/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml b/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml
index 976ffa0444a..d27548980b0 100644
--- a/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml
+++ b/config/metrics/counts_28d/20210216175550_ci_pipeline_config_repository.yml
@@ -1,7 +1,7 @@
---
data_category: operational
key_path: usage_activity_by_stage_monthly.verify.ci_pipeline_config_repository
-description: Total Monthly Pipelines from templates in repository
+description: Monthly count of unique users creating pipelines from CI files in the repository
product_section: ops
product_stage: verify
product_group: group::pipeline execution
diff --git a/config/metrics/counts_7d/20210216184805_i_package_composer_deploy_token_weekly.yml b/config/metrics/counts_7d/20210216184805_i_package_composer_deploy_token_weekly.yml
index 627138b5082..4238901ee7f 100644
--- a/config/metrics/counts_7d/20210216184805_i_package_composer_deploy_token_weekly.yml
+++ b/config/metrics/counts_7d/20210216184805_i_package_composer_deploy_token_weekly.yml
@@ -8,7 +8,7 @@ product_stage: package
product_group: group::package
product_category: package registry
value_type: number
-status: deprecated
+status: removed
time_frame: 7d
data_source: redis_hll
instrumentation_class: RedisHLLMetric
diff --git a/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml b/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml
index cf850f26d42..2d904295d18 100644
--- a/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml
+++ b/config/metrics/counts_all/20210216175518_ci_pipeline_config_repository.yml
@@ -1,7 +1,7 @@
---
data_category: optional
key_path: counts.ci_pipeline_config_repository
-description: Total Pipelines from templates in repository
+description: Total Pipelines from CI files in repository
product_section: ops
product_stage: verify
product_group: group::pipeline execution
diff --git a/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml b/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml
index 269acb1105e..c28daf950dd 100644
--- a/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml
+++ b/config/metrics/counts_all/20210216175533_ci_pipeline_config_repository.yml
@@ -1,7 +1,7 @@
---
data_category: optional
key_path: usage_activity_by_stage.verify.ci_pipeline_config_repository
-description: Total Pipelines from templates in repository
+description: Total count of unique users creating pipelines from CI files in the repository
product_section: ops
product_stage: verify
product_group: group::pipeline execution
diff --git a/db/post_migrate/20211210140000_add_temporary_static_object_token_index.rb b/db/post_migrate/20211210140000_add_temporary_static_object_token_index.rb
new file mode 100644
index 00000000000..54997dc4cc4
--- /dev/null
+++ b/db/post_migrate/20211210140000_add_temporary_static_object_token_index.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class AddTemporaryStaticObjectTokenIndex < Gitlab::Database::Migration[1.0]
+ disable_ddl_transaction!
+
+ INDEX_NAME = 'index_users_with_static_object_token'
+
+ def up
+ add_concurrent_index :users, :id, where: "static_object_token IS NOT NULL AND static_object_token_encrypted IS NULL", name: INDEX_NAME
+ end
+
+ def down
+ remove_concurrent_index :users, :id, name: INDEX_NAME
+ end
+end
diff --git a/db/post_migrate/20211210140629_encrypt_static_object_token.rb b/db/post_migrate/20211210140629_encrypt_static_object_token.rb
new file mode 100644
index 00000000000..fe4db9fc14c
--- /dev/null
+++ b/db/post_migrate/20211210140629_encrypt_static_object_token.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+class EncryptStaticObjectToken < Gitlab::Database::Migration[1.0]
+ BATCH_SIZE = 10_000
+ MIGRATION = 'EncryptStaticObjectToken'
+
+ disable_ddl_transaction!
+
+ def up
+ queue_background_migration_jobs_by_range_at_intervals(
+ define_batchable_model('users').where.not(static_object_token: nil).where(static_object_token_encrypted: nil),
+ MIGRATION,
+ 2.minutes,
+ batch_size: BATCH_SIZE,
+ track_jobs: true
+ )
+ end
+
+ def down
+ # no ops
+ end
+end
diff --git a/db/schema_migrations/20211210140000 b/db/schema_migrations/20211210140000
new file mode 100644
index 00000000000..b64d8251d69
--- /dev/null
+++ b/db/schema_migrations/20211210140000
@@ -0,0 +1 @@
+f02c1b7412d2bb6d8a20639704ad55cdbcc14bfccf0509b659c3ef9614bcfa2b \ No newline at end of file
diff --git a/db/schema_migrations/20211210140629 b/db/schema_migrations/20211210140629
new file mode 100644
index 00000000000..ad631461d87
--- /dev/null
+++ b/db/schema_migrations/20211210140629
@@ -0,0 +1 @@
+7940b0f692b62bcabbe98440082e2245fd28caba2c9e052e85e82acea0a98d23 \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 9188caad2c0..ae6c128f293 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -27797,6 +27797,8 @@ CREATE INDEX index_users_star_projects_on_project_id ON users_star_projects USIN
CREATE UNIQUE INDEX index_users_star_projects_on_user_id_and_project_id ON users_star_projects USING btree (user_id, project_id);
+CREATE INDEX index_users_with_static_object_token ON users USING btree (id) WHERE ((static_object_token IS NOT NULL) AND (static_object_token_encrypted IS NULL));
+
CREATE UNIQUE INDEX index_verification_codes_on_phone_and_visitor_id_code ON ONLY verification_codes USING btree (visitor_id_code, phone, created_at);
COMMENT ON INDEX index_verification_codes_on_phone_and_visitor_id_code IS 'JiHu-specific index';
diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md
deleted file mode 100644
index ed5014b65e1..00000000000
--- a/doc/administration/operations/cleaning_up_redis_sessions.md
+++ /dev/null
@@ -1,9 +0,0 @@
----
-redirect_to: 'index.md'
-remove_date: '2021-12-10'
----
-
-This document was moved to [another location](index.md).
-
-<!-- This redirect file can be deleted after 2021-12-10. -->
-<!-- Before deletion, see: https://docs.gitlab.com/ee/development/documentation/#move-or-rename-a-page -->
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index cdc354c0fb8..5fac7a555d5 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -17690,10 +17690,10 @@ The state of the vulnerability.
| Value | Description |
| ----- | ----------- |
-| <a id="vulnerabilitystateconfirmed"></a>`CONFIRMED` | Confirmed vulnerability. |
-| <a id="vulnerabilitystatedetected"></a>`DETECTED` | Detected vulnerability. |
-| <a id="vulnerabilitystatedismissed"></a>`DISMISSED` | Dismissed vulnerability. |
-| <a id="vulnerabilitystateresolved"></a>`RESOLVED` | Resolved vulnerability. |
+| <a id="vulnerabilitystateconfirmed"></a>`CONFIRMED` | For details, see [vulnerability status values](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/index.html#vulnerability-status-values). |
+| <a id="vulnerabilitystatedetected"></a>`DETECTED` | For details, see [vulnerability status values](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/index.html#vulnerability-status-values). |
+| <a id="vulnerabilitystatedismissed"></a>`DISMISSED` | For details, see [vulnerability status values](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/index.html#vulnerability-status-values). |
+| <a id="vulnerabilitystateresolved"></a>`RESOLVED` | For details, see [vulnerability status values](https://docs.gitlab.com/ee/user/application_security/vulnerabilities/index.html#vulnerability-status-values). |
### `WeightWildcardId`
diff --git a/doc/development/snowplow/dictionary.md b/doc/development/snowplow/dictionary.md
deleted file mode 100644
index 02e9ba5ce20..00000000000
--- a/doc/development/snowplow/dictionary.md
+++ /dev/null
@@ -1,4 +0,0 @@
----
-redirect_to: 'https://metrics.gitlab.com/snowplow.html'
-remove_date: '2021-12-28'
----
diff --git a/doc/security/img/unlock_user_v14_7.png b/doc/security/img/unlock_user_v14_7.png
new file mode 100644
index 00000000000..51015d932cb
--- /dev/null
+++ b/doc/security/img/unlock_user_v14_7.png
Binary files differ
diff --git a/doc/security/unlock_user.md b/doc/security/unlock_user.md
index fa2fcbe782a..057d4e87efa 100644
--- a/doc/security/unlock_user.md
+++ b/doc/security/unlock_user.md
@@ -5,9 +5,23 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: howto
---
-# How to unlock a locked user from the command line **(FREE SELF)**
+# Locked users **(FREE SELF)**
-After ten failed login attempts a user gets in a locked state.
+Users are locked after ten failed sign-in attempts. These users remain locked:
+
+- For 10 minutes, after which time they are automatically unlocked.
+- Until an admin unlocks them from the [Admin Area](../user/admin_area/index.md) or the command line in under 10 minutes.
+
+## Unlock a user from the Admin Area
+
+1. On the top bar, select **Menu > Admin**.
+1. On the left sidebar, select **Overview > Users**.
+1. Use the search bar to find the locked user.
+1. From the **User administration** dropdown select **Unlock**.
+
+![Unlock a user from the Admin Area](img/unlock_user_v14_7.png)
+
+## Unlock a user from the command line
To unlock a locked user:
diff --git a/doc/user/application_security/vulnerabilities/index.md b/doc/user/application_security/vulnerabilities/index.md
index 7bdc8cc8479..7fd3c076fe9 100644
--- a/doc/user/application_security/vulnerabilities/index.md
+++ b/doc/user/application_security/vulnerabilities/index.md
@@ -37,7 +37,7 @@ A vulnerability's status can be one of the following:
| Status | Description |
|:----------|:------------|
-| Detected | The default state for a newly discovered vulnerability. |
+| Detected | The default state for a newly discovered vulnerability. Appears as "Needs triage" in the UI. |
| Confirmed | A user has seen this vulnerability and confirmed it to be accurate. |
| Dismissed | A user has seen this vulnerability and dismissed it because it is not accurate or otherwise not to be resolved. |
| Resolved | The vulnerability has been fixed or is no longer present. |
diff --git a/lib/banzai/filter/base_sanitization_filter.rb b/lib/banzai/filter/base_sanitization_filter.rb
index 7ea32c4b1e7..4e350a59fa0 100644
--- a/lib/banzai/filter/base_sanitization_filter.rb
+++ b/lib/banzai/filter/base_sanitization_filter.rb
@@ -42,7 +42,7 @@ module Banzai
# Allow any protocol in `a` elements
# and then remove links with unsafe protocols
allowlist[:protocols].delete('a')
- allowlist[:transformers].push(self.class.method(:remove_unsafe_links))
+ allowlist[:transformers].push(self.class.method(:sanitize_unsafe_links))
# Remove `rel` attribute from `a` elements
allowlist[:transformers].push(self.class.remove_rel)
diff --git a/lib/feature.rb b/lib/feature.rb
index f301f206b46..12b4ef07dd6 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -29,6 +29,15 @@ class Feature
class << self
delegate :group, to: :flipper
+ def feature_flags_available?
+ # When the DBMS is not available, an exception (e.g. PG::ConnectionBad) is raised
+ active_db_connection = ActiveRecord::Base.connection.active? rescue false # rubocop:disable Database/MultipleDatabases
+
+ active_db_connection && Feature::FlipperFeature.table_exists?
+ rescue ActiveRecord::NoDatabaseError
+ false
+ end
+
def all
flipper.features.to_a
end
diff --git a/lib/gitlab/background_migration/encrypt_static_object_token.rb b/lib/gitlab/background_migration/encrypt_static_object_token.rb
new file mode 100644
index 00000000000..80931353e2f
--- /dev/null
+++ b/lib/gitlab/background_migration/encrypt_static_object_token.rb
@@ -0,0 +1,70 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BackgroundMigration
+ # Populates "static_object_token_encrypted" field with encrypted versions
+ # of values from "static_object_token" field
+ class EncryptStaticObjectToken
+ # rubocop:disable Style/Documentation
+ class User < ActiveRecord::Base
+ include ::EachBatch
+ self.table_name = 'users'
+ scope :with_static_object_token, -> { where.not(static_object_token: nil) }
+ scope :without_static_object_token_encrypted, -> { where(static_object_token_encrypted: nil) }
+ end
+ # rubocop:enable Style/Documentation
+
+ BATCH_SIZE = 100
+
+ def perform(start_id, end_id)
+ ranged_query = User
+ .where(id: start_id..end_id)
+ .with_static_object_token
+ .without_static_object_token_encrypted
+
+ ranged_query.each_batch(of: BATCH_SIZE) do |sub_batch|
+ first, last = sub_batch.pluck(Arel.sql('min(id), max(id)')).first
+
+ batch_query = User.unscoped
+ .where(id: first..last)
+ .with_static_object_token
+ .without_static_object_token_encrypted
+
+ user_tokens = batch_query.pluck(:id, :static_object_token)
+
+ user_encrypted_tokens = user_tokens.map do |(id, plaintext_token)|
+ next if plaintext_token.blank?
+
+ [id, Gitlab::CryptoHelper.aes256_gcm_encrypt(plaintext_token)]
+ end
+
+ encrypted_tokens_sql = user_encrypted_tokens.compact.map { |(id, token)| "(#{id}, '#{token}')" }.join(',')
+
+ if user_encrypted_tokens.present?
+ User.connection.execute(<<~SQL)
+ WITH cte(cte_id, cte_token) AS #{::Gitlab::Database::AsWithMaterialized.materialized_if_supported} (
+ SELECT *
+ FROM (VALUES #{encrypted_tokens_sql}) AS t (id, token)
+ )
+ UPDATE #{User.table_name}
+ SET static_object_token_encrypted = cte_token
+ FROM cte
+ WHERE cte_id = id
+ SQL
+ end
+
+ mark_job_as_succeeded(start_id, end_id)
+ end
+ end
+
+ private
+
+ def mark_job_as_succeeded(*arguments)
+ Gitlab::Database::BackgroundMigrationJob.mark_all_as_succeeded(
+ self.class.name.demodulize,
+ arguments
+ )
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data_counters/hll_redis_counter.rb b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
index 8fc8bb5d344..c6e9db6a314 100644
--- a/lib/gitlab/usage_data_counters/hll_redis_counter.rb
+++ b/lib/gitlab/usage_data_counters/hll_redis_counter.rb
@@ -104,7 +104,7 @@ module Gitlab
events_names = events_for_category(category)
event_results = events_names.each_with_object({}) do |event, hash|
- hash["#{event}_weekly"] = unique_events(**weekly_time_range.merge(event_names: [event]))
+ hash["#{event}_weekly"] = unique_events(**weekly_time_range.merge(event_names: [event])) unless event == "i_package_composer_deploy_token"
hash["#{event}_monthly"] = unique_events(**monthly_time_range.merge(event_names: [event]))
end
diff --git a/lib/gitlab/utils/sanitize_node_link.rb b/lib/gitlab/utils/sanitize_node_link.rb
index ab5d18e9c8a..b0dfa087fcf 100644
--- a/lib/gitlab/utils/sanitize_node_link.rb
+++ b/lib/gitlab/utils/sanitize_node_link.rb
@@ -8,6 +8,12 @@ module Gitlab
UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze
ATTRS_TO_SANITIZE = %w(href src data-src data-canonical-src).freeze
+ # sanitize 6.0 requires only a context argument. Do not add any default
+ # arguments to this method.
+ def sanitize_unsafe_links(env)
+ remove_unsafe_links(env)
+ end
+
def remove_unsafe_links(env, remove_invalid_links: true)
node = env[:node]
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a3ce0a4881e..6714eb75fd0 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2648,6 +2648,9 @@ msgstr ""
msgid "AdminUsers|(Internal)"
msgstr ""
+msgid "AdminUsers|(Locked)"
+msgstr ""
+
msgid "AdminUsers|(Pending approval)"
msgstr ""
diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb
index f7cf55d8a95..1370ec9cc0b 100644
--- a/spec/controllers/projects/repositories_controller_spec.rb
+++ b/spec/controllers/projects/repositories_controller_spec.rb
@@ -210,6 +210,25 @@ RSpec.describe Projects::RepositoriesController do
expect(response).to have_gitlab_http_status(:found)
end
end
+
+ context 'when token is migrated' do
+ let(:user) { create(:user, static_object_token: '') }
+ let(:token) { 'Test' }
+
+ it 'calls the action normally' do
+ user.update_column(:static_object_token, token)
+
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master', token: token }, format: 'zip'
+ expect(user.static_object_token).to eq(token)
+ expect(response).to have_gitlab_http_status(:ok)
+
+ user.update_column(:static_object_token_encrypted, Gitlab::CryptoHelper.aes256_gcm_encrypt(token))
+
+ get :archive, params: { namespace_id: project.namespace, project_id: project, id: 'master', token: token }, format: 'zip'
+ expect(user.static_object_token).to eq(token)
+ expect(response).to have_gitlab_http_status(:ok)
+ end
+ end
end
context 'when a token header is present' do
diff --git a/spec/features/admin/users/user_spec.rb b/spec/features/admin/users/user_spec.rb
index ae940fecabe..0d053329627 100644
--- a/spec/features/admin/users/user_spec.rb
+++ b/spec/features/admin/users/user_spec.rb
@@ -125,6 +125,26 @@ RSpec.describe 'Admin::Users::User' do
end
end
+ context 'when a user is locked', time_travel_to: '2020-02-02 10:30:45 -0700' do
+ let_it_be(:locked_user) { create(:user, locked_at: DateTime.parse('2020-02-02 10:30:00 -0700')) }
+
+ before do
+ visit admin_user_path(locked_user)
+ end
+
+ it "displays `(Locked)` next to user's name" do
+ expect(page).to have_content("#{locked_user.name} (Locked)")
+ end
+
+ it 'allows a user to be unlocked from the `User administration dropdown', :js do
+ accept_gl_confirm("Unlock user #{locked_user.name}?", button_text: 'Unlock') do
+ click_action_in_user_dropdown(locked_user.id, 'Unlock')
+ end
+
+ expect(page).not_to have_content("#{locked_user.name} (Locked)")
+ end
+ end
+
describe 'Impersonation' do
let_it_be(:another_user) { create(:user) }
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index 2f21961d1fc..d25cddea902 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -528,7 +528,7 @@ RSpec.describe 'Project issue boards', :js do
end
it 'does not allow dragging' do
- expect(page).not_to have_selector('.user-can-drag')
+ expect(page).not_to have_selector('.gl-cursor-grab')
end
end
diff --git a/spec/features/groups/issues_spec.rb b/spec/features/groups/issues_spec.rb
index 1bac1bcdf5a..3fc1484826c 100644
--- a/spec/features/groups/issues_spec.rb
+++ b/spec/features/groups/issues_spec.rb
@@ -156,10 +156,10 @@ RSpec.describe 'Group issues page' do
expect(page).to have_selector('.manual-ordering')
end
- it 'each issue item has a user-can-drag css applied' do
+ it 'each issue item has a gl-cursor-grab css applied' do
visit issues_group_path(group, sort: 'relative_position')
- expect(page).to have_selector('.issue.user-can-drag', count: 3)
+ expect(page).to have_selector('.issue.gl-cursor-grab', count: 3)
end
it 'issues should be draggable and persist order' do
diff --git a/spec/frontend/boards/components/board_card_spec.js b/spec/frontend/boards/components/board_card_spec.js
index 5742dfdc5d2..3af173aa18c 100644
--- a/spec/frontend/boards/components/board_card_spec.js
+++ b/spec/frontend/boards/components/board_card_spec.js
@@ -167,7 +167,7 @@ describe('Board card', () => {
mountComponent({ item: { ...mockIssue, isLoading: true } });
expect(wrapper.classes()).toContain('is-disabled');
- expect(wrapper.classes()).not.toContain('user-can-drag');
+ expect(wrapper.classes()).not.toContain('gl-cursor-grab');
});
});
@@ -177,7 +177,7 @@ describe('Board card', () => {
mountComponent();
expect(wrapper.classes()).not.toContain('is-disabled');
- expect(wrapper.classes()).toContain('user-can-drag');
+ expect(wrapper.classes()).toContain('gl-cursor-grab');
});
});
});
diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js
index 148d0c5684d..8cc0ad5f30c 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -180,18 +180,18 @@ describe('Board List Header Component', () => {
const canDragList = [ListType.label, ListType.milestone, ListType.iteration, ListType.assignee];
it.each(cannotDragList)(
- 'does not have user-can-drag-class so user cannot drag list',
+ 'does not have gl-cursor-grab class so user cannot drag list',
(listType) => {
createComponent({ listType });
- expect(findTitle().classes()).not.toContain('user-can-drag');
+ expect(findTitle().classes()).not.toContain('gl-cursor-grab');
},
);
- it.each(canDragList)('has user-can-drag-class so user can drag list', (listType) => {
+ it.each(canDragList)('has gl-cursor-grab class so user can drag list', (listType) => {
createComponent({ listType });
- expect(findTitle().classes()).toContain('user-can-drag');
+ expect(findTitle().classes()).toContain('gl-cursor-grab');
});
});
});
diff --git a/spec/frontend/cycle_analytics/stage_table_spec.js b/spec/frontend/cycle_analytics/stage_table_spec.js
index 3158446c37d..69de2044a8b 100644
--- a/spec/frontend/cycle_analytics/stage_table_spec.js
+++ b/spec/frontend/cycle_analytics/stage_table_spec.js
@@ -24,6 +24,7 @@ const findTable = () => wrapper.findComponent(GlTable);
const findTableHead = () => wrapper.find('thead');
const findTableHeadColumns = () => findTableHead().findAll('th');
const findStageEventTitle = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-title');
+const findStageEventLink = (ev) => extendedWrapper(ev).findByTestId('vsa-stage-event-link');
const findStageTime = () => wrapper.findByTestId('vsa-stage-event-time');
const findIcon = (name) => wrapper.findByTestId(`${name}-icon`);
@@ -86,6 +87,15 @@ describe('StageTable', () => {
expect(titles[index]).toBe(ev.title);
});
});
+
+ it('will not display the project name in the record link', () => {
+ const evs = findStageEvents();
+
+ const links = evs.wrappers.map((ev) => findStageEventLink(ev).text());
+ issueEventItems.forEach((ev, index) => {
+ expect(links[index]).toBe(`#${ev.iid}`);
+ });
+ });
});
describe('default event', () => {
@@ -187,6 +197,21 @@ describe('StageTable', () => {
});
});
+ describe('includeProjectName set', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ includeProjectName: true });
+ });
+
+ it('will display the project name in the record link', () => {
+ const evs = findStageEvents();
+
+ const links = evs.wrappers.map((ev) => findStageEventLink(ev).text());
+ issueEventItems.forEach((ev, index) => {
+ expect(links[index]).toBe(`${ev.projectPath}#${ev.iid}`);
+ });
+ });
+ });
+
describe('Pagination', () => {
beforeEach(() => {
wrapper = createComponent();
diff --git a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
index 1ae99abf4aa..ec42782ab15 100644
--- a/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
+++ b/spec/frontend/runner/components/cells/runner_actions_cell_spec.js
@@ -4,7 +4,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { captureException } from '~/runner/sentry_utils';
@@ -200,7 +200,7 @@ describe('RunnerTypeCell', () => {
});
it('error is shown to the user', () => {
- expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createAlert).toHaveBeenCalledTimes(1);
});
});
@@ -229,7 +229,7 @@ describe('RunnerTypeCell', () => {
});
it('error is shown to the user', () => {
- expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createAlert).toHaveBeenCalledTimes(1);
});
});
});
@@ -350,7 +350,7 @@ describe('RunnerTypeCell', () => {
});
it('error is shown to the user', () => {
- expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createAlert).toHaveBeenCalledTimes(1);
});
it('toast notification is not shown', () => {
@@ -382,7 +382,7 @@ describe('RunnerTypeCell', () => {
});
it('error is shown to the user', () => {
- expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createAlert).toHaveBeenCalledTimes(1);
});
});
});
diff --git a/spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js b/spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js
index 0d002c272b4..4e93d6bcaa3 100644
--- a/spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js
+++ b/spec/frontend/runner/components/registration/registration_token_reset_dropdown_item_spec.js
@@ -4,7 +4,7 @@ import { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import RegistrationTokenResetDropdownItem from '~/runner/components/registration/registration_token_reset_dropdown_item.vue';
import { INSTANCE_TYPE, GROUP_TYPE, PROJECT_TYPE } from '~/runner/constants';
import runnersRegistrationTokenResetMutation from '~/runner/graphql/runners_registration_token_reset.mutation.graphql';
@@ -146,7 +146,7 @@ describe('RegistrationTokenResetDropdownItem', () => {
findDropdownItem().trigger('click');
await waitForPromises();
- expect(createFlash).toHaveBeenLastCalledWith({
+ expect(createAlert).toHaveBeenLastCalledWith({
message: `Network error: ${mockErrorMsg}`,
});
expect(captureException).toHaveBeenCalledWith({
@@ -172,7 +172,7 @@ describe('RegistrationTokenResetDropdownItem', () => {
findDropdownItem().trigger('click');
await waitForPromises();
- expect(createFlash).toHaveBeenLastCalledWith({
+ expect(createAlert).toHaveBeenLastCalledWith({
message: `${mockErrorMsg} ${mockErrorMsg2}`,
});
expect(captureException).toHaveBeenCalledWith({
diff --git a/spec/frontend/runner/components/runner_update_form_spec.js b/spec/frontend/runner/components/runner_update_form_spec.js
index 112b990fa45..ebb2e67d1e2 100644
--- a/spec/frontend/runner/components/runner_update_form_spec.js
+++ b/spec/frontend/runner/components/runner_update_form_spec.js
@@ -5,7 +5,7 @@ import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import createFlash, { FLASH_TYPES } from '~/flash';
+import { createAlert, VARIANT_SUCCESS } from '~/flash';
import RunnerUpdateForm from '~/runner/components/runner_update_form.vue';
import {
INSTANCE_TYPE,
@@ -79,9 +79,9 @@ describe('RunnerUpdateForm', () => {
input: expect.objectContaining(submittedRunner),
});
- expect(createFlash).toHaveBeenLastCalledWith({
+ expect(createAlert).toHaveBeenLastCalledWith({
message: expect.stringContaining('saved'),
- type: FLASH_TYPES.SUCCESS,
+ variant: VARIANT_SUCCESS,
});
expect(findSubmitDisabledAttr()).toBeUndefined();
@@ -238,7 +238,7 @@ describe('RunnerUpdateForm', () => {
await submitFormAndWait();
- expect(createFlash).toHaveBeenLastCalledWith({
+ expect(createAlert).toHaveBeenLastCalledWith({
message: `Network error: ${mockErrorMsg}`,
});
expect(captureException).toHaveBeenCalledWith({
@@ -262,7 +262,7 @@ describe('RunnerUpdateForm', () => {
await submitFormAndWait();
- expect(createFlash).toHaveBeenLastCalledWith({
+ expect(createAlert).toHaveBeenLastCalledWith({
message: mockErrorMsg,
});
expect(captureException).not.toHaveBeenCalled();
diff --git a/spec/frontend/runner/components/search_tokens/tag_token_spec.js b/spec/frontend/runner/components/search_tokens/tag_token_spec.js
index 89c06ba2df4..52557ff716d 100644
--- a/spec/frontend/runner/components/search_tokens/tag_token_spec.js
+++ b/spec/frontend/runner/components/search_tokens/tag_token_spec.js
@@ -3,7 +3,7 @@ import { mount } from '@vue/test-utils';
import MockAdapter from 'axios-mock-adapter';
import { nextTick } from 'vue';
import waitForPromises from 'helpers/wait_for_promises';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import axios from '~/lib/utils/axios_utils';
import TagToken, { TAG_SUGGESTIONS_PATH } from '~/runner/components/search_tokens/tag_token.vue';
@@ -168,8 +168,8 @@ describe('TagToken', () => {
});
it('error is shown', async () => {
- expect(createFlash).toHaveBeenCalledTimes(1);
- expect(createFlash).toHaveBeenCalledWith({ message: expect.any(String) });
+ expect(createAlert).toHaveBeenCalledTimes(1);
+ expect(createAlert).toHaveBeenCalledWith({ message: expect.any(String) });
});
});
diff --git a/spec/frontend/runner/group_runners/group_runners_app_spec.js b/spec/frontend/runner/group_runners/group_runners_app_spec.js
index 4451100de19..0ce6feceb5b 100644
--- a/spec/frontend/runner/group_runners/group_runners_app_spec.js
+++ b/spec/frontend/runner/group_runners/group_runners_app_spec.js
@@ -6,7 +6,7 @@ import createMockApollo from 'helpers/mock_apollo_helper';
import setWindowLocation from 'helpers/set_window_location_helper';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import { updateHistory } from '~/lib/utils/url_utility';
@@ -236,7 +236,7 @@ describe('GroupRunnersApp', () => {
});
it('error is shown to the user', async () => {
- expect(createFlash).toHaveBeenCalledTimes(1);
+ expect(createAlert).toHaveBeenCalledTimes(1);
});
it('error is reported to sentry', async () => {
diff --git a/spec/frontend/runner/runner_detail/runner_details_app_spec.js b/spec/frontend/runner/runner_detail/runner_details_app_spec.js
index 3f3bbddd3bd..64b2d3c30e2 100644
--- a/spec/frontend/runner/runner_detail/runner_details_app_spec.js
+++ b/spec/frontend/runner/runner_detail/runner_details_app_spec.js
@@ -2,7 +2,7 @@ import { createLocalVue, mount, shallowMount } from '@vue/test-utils';
import VueApollo from 'vue-apollo';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
-import createFlash from '~/flash';
+import { createAlert } from '~/flash';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
import RunnerHeader from '~/runner/components/runner_header.vue';
@@ -82,7 +82,7 @@ describe('RunnerDetailsApp', () => {
});
it('error is shown to the user', () => {
- expect(createFlash).toHaveBeenCalled();
+ expect(createAlert).toHaveBeenCalled();
});
});
});
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 01db06617dc..8c546390201 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -11,6 +11,32 @@ RSpec.describe Feature, stub_feature_flags: false do
skip_feature_flags_yaml_validation
end
+ describe '.feature_flags_available?' do
+ it 'returns false on connection error' do
+ expect(ActiveRecord::Base.connection).to receive(:active?).and_raise(PG::ConnectionBad) # rubocop:disable Database/MultipleDatabases
+
+ expect(described_class.feature_flags_available?).to eq(false)
+ end
+
+ it 'returns false when connection is not active' do
+ expect(ActiveRecord::Base.connection).to receive(:active?).and_return(false) # rubocop:disable Database/MultipleDatabases
+
+ expect(described_class.feature_flags_available?).to eq(false)
+ end
+
+ it 'returns false when the flipper table does not exist' do
+ expect(Feature::FlipperFeature).to receive(:table_exists?).and_return(false)
+
+ expect(described_class.feature_flags_available?).to eq(false)
+ end
+
+ it 'returns false on NoDatabaseError' do
+ expect(Feature::FlipperFeature).to receive(:table_exists?).and_raise(ActiveRecord::NoDatabaseError)
+
+ expect(described_class.feature_flags_available?).to eq(false)
+ end
+ end
+
describe '.get' do
let(:feature) { double(:feature) }
let(:key) { 'my_feature' }
diff --git a/spec/lib/gitlab/background_migration/encrypt_static_object_token_spec.rb b/spec/lib/gitlab/background_migration/encrypt_static_object_token_spec.rb
new file mode 100644
index 00000000000..94d9f4509a7
--- /dev/null
+++ b/spec/lib/gitlab/background_migration/encrypt_static_object_token_spec.rb
@@ -0,0 +1,56 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+RSpec.describe Gitlab::BackgroundMigration::EncryptStaticObjectToken do
+ let(:users) { table(:users) }
+ let!(:user_without_tokens) { create_user!(name: 'notoken') }
+ let!(:user_with_plaintext_token_1) { create_user!(name: 'plaintext_1', token: 'token') }
+ let!(:user_with_plaintext_token_2) { create_user!(name: 'plaintext_2', token: 'TOKEN') }
+ let!(:user_with_plaintext_empty_token) { create_user!(name: 'plaintext_3', token: '') }
+ let!(:user_with_encrypted_token) { create_user!(name: 'encrypted', encrypted_token: 'encrypted') }
+ let!(:user_with_both_tokens) { create_user!(name: 'both', token: 'token2', encrypted_token: 'encrypted2') }
+
+ before do
+ allow(Gitlab::CryptoHelper).to receive(:aes256_gcm_encrypt).and_call_original
+ allow(Gitlab::CryptoHelper).to receive(:aes256_gcm_encrypt).with('token') { 'secure_token' }
+ allow(Gitlab::CryptoHelper).to receive(:aes256_gcm_encrypt).with('TOKEN') { 'SECURE_TOKEN' }
+ end
+
+ subject { described_class.new.perform(start_id, end_id) }
+
+ let(:start_id) { users.minimum(:id) }
+ let(:end_id) { users.maximum(:id) }
+
+ it 'backfills encrypted tokens to users with plaintext token only', :aggregate_failures do
+ subject
+
+ new_state = users.pluck(:id, :static_object_token, :static_object_token_encrypted).to_h do |row|
+ [row[0], [row[1], row[2]]]
+ end
+
+ expect(new_state.count).to eq(6)
+
+ expect(new_state[user_with_plaintext_token_1.id]).to match_array(%w[token secure_token])
+ expect(new_state[user_with_plaintext_token_2.id]).to match_array(%w[TOKEN SECURE_TOKEN])
+
+ expect(new_state[user_with_plaintext_empty_token.id]).to match_array(['', nil])
+ expect(new_state[user_without_tokens.id]).to match_array([nil, nil])
+ expect(new_state[user_with_both_tokens.id]).to match_array(%w[token2 encrypted2])
+ expect(new_state[user_with_encrypted_token.id]).to match_array([nil, 'encrypted'])
+ end
+
+ private
+
+ def create_user!(name:, token: nil, encrypted_token: nil)
+ email = "#{name}@example.com"
+
+ table(:users).create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 0,
+ static_object_token: token,
+ static_object_token_encrypted: encrypted_token
+ )
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index 9e9cc2cfab6..5fea68ca2dc 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -1289,6 +1289,8 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
let(:categories) { ::Gitlab::UsageDataCounters::HLLRedisCounter.categories }
+ let(:ignored_metrics) { ["i_package_composer_deploy_token_weekly"] }
+
it 'has all known_events' do
expect(subject).to have_key(:redis_hll_counters)
@@ -1298,6 +1300,7 @@ RSpec.describe Gitlab::UsageData, :aggregate_failures do
keys = ::Gitlab::UsageDataCounters::HLLRedisCounter.events_for_category(category)
metrics = keys.map { |key| "#{key}_weekly" } + keys.map { |key| "#{key}_monthly" }
+ metrics -= ignored_metrics
if ::Gitlab::UsageDataCounters::HLLRedisCounter::CATEGORIES_FOR_TOTALS.include?(category)
metrics.append("#{category}_total_unique_counts_weekly", "#{category}_total_unique_counts_monthly")
diff --git a/spec/migrations/20211210140629_encrypt_static_object_token_spec.rb b/spec/migrations/20211210140629_encrypt_static_object_token_spec.rb
new file mode 100644
index 00000000000..289cf9a93ed
--- /dev/null
+++ b/spec/migrations/20211210140629_encrypt_static_object_token_spec.rb
@@ -0,0 +1,50 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+require_migration!
+
+RSpec.describe EncryptStaticObjectToken, :migration do
+ let_it_be(:background_migration_jobs) { table(:background_migration_jobs) }
+ let_it_be(:users) { table(:users) }
+
+ let!(:user_without_tokens) { create_user!(name: 'notoken') }
+ let!(:user_with_plaintext_token_1) { create_user!(name: 'plaintext_1', token: 'token') }
+ let!(:user_with_plaintext_token_2) { create_user!(name: 'plaintext_2', token: 'TOKEN') }
+ let!(:user_with_encrypted_token) { create_user!(name: 'encrypted', encrypted_token: 'encrypted') }
+ let!(:user_with_both_tokens) { create_user!(name: 'both', token: 'token2', encrypted_token: 'encrypted2') }
+
+ before do
+ stub_const("#{described_class}::BATCH_SIZE", 1)
+ end
+
+ around do |example|
+ freeze_time { Sidekiq::Testing.fake! { example.run } }
+ end
+
+ it 'schedules background migrations' do
+ migrate!
+
+ expect(background_migration_jobs.count).to eq(2)
+ expect(background_migration_jobs.first.arguments).to match_array([user_with_plaintext_token_1.id, user_with_plaintext_token_1.id])
+ expect(background_migration_jobs.second.arguments).to match_array([user_with_plaintext_token_2.id, user_with_plaintext_token_2.id])
+
+ expect(BackgroundMigrationWorker.jobs.size).to eq(2)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(2.minutes, user_with_plaintext_token_1.id, user_with_plaintext_token_1.id)
+ expect(described_class::MIGRATION).to be_scheduled_delayed_migration(4.minutes, user_with_plaintext_token_2.id, user_with_plaintext_token_2.id)
+ end
+
+ private
+
+ def create_user!(name:, token: nil, encrypted_token: nil)
+ email = "#{name}@example.com"
+
+ table(:users).create!(
+ name: name,
+ email: email,
+ username: name,
+ projects_limit: 0,
+ static_object_token: token,
+ static_object_token_encrypted: encrypted_token
+ )
+ end
+end
diff --git a/spec/models/preloaders/environments/deployment_preloader_spec.rb b/spec/models/preloaders/environments/deployment_preloader_spec.rb
new file mode 100644
index 00000000000..c1812d45628
--- /dev/null
+++ b/spec/models/preloaders/environments/deployment_preloader_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Preloaders::Environments::DeploymentPreloader do
+ let_it_be(:user) { create(:user) }
+ let_it_be(:project, reload: true) { create(:project, :repository) }
+
+ let_it_be(:pipeline) { create(:ci_pipeline, user: user, project: project, sha: project.commit.sha) }
+ let_it_be(:ci_build_a) { create(:ci_build, user: user, project: project, pipeline: pipeline) }
+ let_it_be(:ci_build_b) { create(:ci_build, user: user, project: project, pipeline: pipeline) }
+ let_it_be(:ci_build_c) { create(:ci_build, user: user, project: project, pipeline: pipeline) }
+
+ let_it_be(:environment_a) { create(:environment, project: project, state: :available) }
+ let_it_be(:environment_b) { create(:environment, project: project, state: :available) }
+
+ before do
+ create(:deployment, :success, project: project, environment: environment_a, deployable: ci_build_a)
+ create(:deployment, :success, project: project, environment: environment_a, deployable: ci_build_b)
+ create(:deployment, :success, project: project, environment: environment_b, deployable: ci_build_c)
+ end
+
+ def preload_association(association_name)
+ described_class.new(project.environments)
+ .execute_with_union(association_name, deployment_associations)
+ end
+
+ def deployment_associations
+ {
+ user: [],
+ deployable: {
+ pipeline: {
+ manual_actions: []
+ }
+ }
+ }
+ end
+
+ it 'does not trigger N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new { preload_association(:last_deployment) }
+
+ ci_build_d = create(:ci_build, user: user, project: project, pipeline: pipeline)
+ create(:deployment, :success, project: project, environment: environment_b, deployable: ci_build_d)
+
+ expect { preload_association(:last_deployment) }.not_to exceed_query_limit(control)
+ end
+
+ it 'batch loads the dependent associations' do
+ preload_association(:last_deployment)
+
+ expect do
+ project.environments.first.last_deployment.deployable.pipeline.manual_actions
+ end.not_to exceed_query_limit(0)
+ end
+
+ # Example query scoped with IN clause for `last_deployment` association preload:
+ # SELECT DISTINCT ON (environment_id) deployments.* FROM "deployments" WHERE "deployments"."status" IN (1, 2, 3, 4, 6) AND "deployments"."environment_id" IN (35, 34, 33) ORDER BY environment_id, deployments.id DESC
+ it 'avoids scoping with IN clause during preload' do
+ control = ActiveRecord::QueryRecorder.new { preload_association(:last_deployment) }
+
+ default_preload_query = control.occurrences_by_line_method.first[1][:occurrences].any? { |i| i.include?('"deployments"."environment_id" IN') }
+
+ expect(default_preload_query).to be(false)
+ end
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 510c78eb5a0..0d4ae19a890 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1773,6 +1773,29 @@ RSpec.describe User do
expect(static_object_token).not_to be_blank
expect(user.reload.static_object_token).to eq static_object_token
end
+
+ it 'generates an encrypted version of the token' do
+ user = create(:user, static_object_token: nil)
+
+ expect(user[:static_object_token]).to be_nil
+ expect(user[:static_object_token_encrypted]).to be_nil
+
+ user.static_object_token
+
+ expect(user[:static_object_token]).to be_nil
+ expect(user[:static_object_token_encrypted]).to be_present
+ end
+
+ it 'prefers an encoded version of the token' do
+ user = create(:user, static_object_token: nil)
+
+ token = user.static_object_token
+
+ user.update_column(:static_object_token, 'Test')
+
+ expect(user.static_object_token).not_to eq('Test')
+ expect(user.static_object_token).to eq(token)
+ end
end
describe 'enabled_static_object_token' do
diff --git a/spec/serializers/analytics_build_entity_spec.rb b/spec/serializers/analytics_build_entity_spec.rb
index 09804681f5d..b8ccc698526 100644
--- a/spec/serializers/analytics_build_entity_spec.rb
+++ b/spec/serializers/analytics_build_entity_spec.rb
@@ -27,6 +27,10 @@ RSpec.describe AnalyticsBuildEntity do
expect(subject).to include(:author)
end
+ it 'contains the project path' do
+ expect(subject).to include(:project_path)
+ end
+
it 'does not contain sensitive information' do
expect(subject).not_to include(/token/)
expect(subject).not_to include(/variables/)
diff --git a/spec/serializers/analytics_issue_entity_spec.rb b/spec/serializers/analytics_issue_entity_spec.rb
index 447c5e7d02a..57385471f2a 100644
--- a/spec/serializers/analytics_issue_entity_spec.rb
+++ b/spec/serializers/analytics_issue_entity_spec.rb
@@ -32,6 +32,10 @@ RSpec.describe AnalyticsIssueEntity do
expect(subject).to include(:author)
end
+ it 'contains the project path' do
+ expect(subject).to include(:project_path)
+ end
+
it 'does not contain sensitive information' do
expect(subject).not_to include(/token/)
expect(subject).not_to include(/variables/)
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 985e18f27a0..80b6f00d8c9 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -185,6 +185,42 @@ RSpec.describe EnvironmentSerializer do
end
end
+ context 'batching loading' do
+ let(:resource) { Environment.all }
+
+ before do
+ create(:environment, name: 'staging/review-1')
+ create_environment_with_associations(project)
+ end
+
+ it 'uses the custom preloader service' do
+ expect_next_instance_of(Preloaders::Environments::DeploymentPreloader) do |preloader|
+ expect(preloader).to receive(:execute_with_union).with(:last_deployment, hash_including(:deployable)).and_call_original
+ end
+
+ expect_next_instance_of(Preloaders::Environments::DeploymentPreloader) do |preloader|
+ expect(preloader).to receive(:execute_with_union).with(:upcoming_deployment, hash_including(:deployable)).and_call_original
+ end
+
+ json
+ end
+
+ # Including for test coverage pipeline failure, remove along with feature flag.
+ context 'when custom preload feature is disabled' do
+ before do
+ Feature.disable(:custom_preloader_for_deployments)
+ end
+
+ it 'avoids N+1 database queries' do
+ control_count = ActiveRecord::QueryRecorder.new { json }.count
+
+ create_environment_with_associations(project)
+
+ expect { json }.not_to exceed_query_limit(control_count)
+ end
+ end
+ end
+
def create_environment_with_associations(project)
create(:environment, project: project).tap do |environment|
create(:deployment, :success, environment: environment, project: project)
diff --git a/spec/services/packages/terraform_module/create_package_service_spec.rb b/spec/services/packages/terraform_module/create_package_service_spec.rb
index f911bb5b82c..e172aa726fd 100644
--- a/spec/services/packages/terraform_module/create_package_service_spec.rb
+++ b/spec/services/packages/terraform_module/create_package_service_spec.rb
@@ -37,7 +37,7 @@ RSpec.describe Packages::TerraformModule::CreatePackageService do
let!(:existing_package) { create(:terraform_module_package, project: project2, name: 'foo/bar', version: '1.0.0') }
it { expect(subject[:http_status]).to eq 403 }
- it { expect(subject[:message]).to be 'Package already exists.' }
+ it { expect(subject[:message]).to be 'Access Denied' }
end
context 'version already exists' do