Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/global.gitlab-ci.yml5
-rw-r--r--.gitlab/ci/review-apps/main.gitlab-ci.yml5
-rw-r--r--.gitlab/ci/review.gitlab-ci.yml18
-rw-r--r--app/assets/javascripts/issues/list/components/issue_card_statistics.vue56
-rw-r--r--app/assets/javascripts/issues/list/components/issues_list_app.vue43
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue18
-rw-r--r--app/assets/stylesheets/page_bundles/merge_requests.scss15
-rw-r--r--app/controllers/concerns/web_hooks/hook_actions.rb4
-rw-r--r--app/controllers/profiles/keys_controller.rb2
-rw-r--r--app/graphql/types/subscription_type.rb5
-rw-r--r--app/helpers/profiles_helper.rb8
-rw-r--r--app/models/ci/pending_build.rb3
-rw-r--r--app/models/ci/running_build.rb4
-rw-r--r--app/models/concerns/ci/partitionable.rb2
-rw-r--r--app/models/group_deploy_key.rb5
-rw-r--r--app/models/hooks/active_hook_filter.rb4
-rw-r--r--app/models/hooks/web_hook.rb13
-rw-r--r--app/models/key.rb8
-rw-r--r--app/models/user.rb2
-rw-r--r--app/services/users/keys_count_service.rb2
-rw-r--r--app/views/profiles/keys/_form.html.haml7
-rw-r--r--app/views/profiles/keys/_key.html.haml4
-rw-r--r--app/views/profiles/keys/_key_details.html.haml4
-rw-r--r--app/views/shared/_file_highlight.html.haml28
-rw-r--r--app/views/shared/web_hooks/_form.html.haml11
-rw-r--r--config/feature_flags/development/enhanced_webhook_support_regex.yml8
-rw-r--r--config/feature_flags/development/ssh_key_usage_types.yml (renamed from config/feature_flags/development/subgroups_approval_rules.yml)10
-rw-r--r--config/open_api.yml4
-rw-r--r--db/migrate/20221116161126_add_auth_signing_type_to_keys.rb7
-rw-r--r--db/schema_migrations/202211161611261
-rw-r--r--db/structure.sql3
-rw-r--r--doc/api/product_analytics.md17
-rw-r--r--doc/integration/mattermost/index.md4
-rw-r--r--lib/api/api.rb4
-rw-r--r--lib/api/container_registry_event.rb12
-rw-r--r--lib/api/internal/base.rb2
-rw-r--r--lib/api/pypi_packages.rb83
-rw-r--r--lib/api/support/git_access_actor.rb2
-rw-r--r--lib/gitlab/ssh/signature.rb2
-rw-r--r--lib/tasks/gitlab/shell.rake2
-rw-r--r--locale/gitlab.pot24
-rw-r--r--package.json4
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb4
-rw-r--r--qa/qa/tools/reliable_report.rb4
-rw-r--r--qa/spec/tools/reliable_report_spec.rb2
-rw-r--r--spec/controllers/profiles/keys_controller_spec.rb3
-rw-r--r--spec/controllers/projects/hooks_controller_spec.rb22
-rw-r--r--spec/factories/ci/builds.rb1
-rw-r--r--spec/factories/ci/pipelines.rb3
-rw-r--r--spec/features/projects/settings/webhooks_settings_spec.rb50
-rw-r--r--spec/frontend/issues/list/components/issue_card_statistics_spec.js64
-rw-r--r--spec/graphql/types/subscription_type_spec.rb1
-rw-r--r--spec/lib/api/support/git_access_actor_spec.rb39
-rw-r--r--spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb6
-rw-r--r--spec/lib/gitlab/ssh/signature_spec.rb24
-rw-r--r--spec/models/ci/pending_build_spec.rb22
-rw-r--r--spec/models/ci/running_build_spec.rb22
-rw-r--r--spec/models/group_deploy_key_spec.rb6
-rw-r--r--spec/models/hooks/active_hook_filter_spec.rb18
-rw-r--r--spec/models/key_spec.rb16
-rw-r--r--spec/models/user_spec.rb8
-rw-r--r--spec/requests/api/internal/base_spec.rb46
-rw-r--r--spec/services/users/keys_count_service_spec.rb6
-rw-r--r--spec/spec_helper.rb7
-rw-r--r--spec/support/helpers/ci/partitioning_helpers.rb11
-rw-r--r--spec/tasks/gitlab/shell_rake_spec.rb19
-rw-r--r--spec/views/profiles/keys/_form.html.haml_spec.rb18
-rw-r--r--spec/views/profiles/keys/_key.html.haml_spec.rb32
-rw-r--r--spec/views/profiles/keys/_key_details.html.haml_spec.rb44
-rw-r--r--yarn.lock18
70 files changed, 739 insertions, 242 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml
index 0c178f349a5..203f4826ae4 100644
--- a/.gitlab/ci/global.gitlab-ci.yml
+++ b/.gitlab/ci/global.gitlab-ci.yml
@@ -365,3 +365,8 @@
docker run --rm --privileged ${QEMU_IMAGE} --install all;
fi
- docker buildx create --use # creates and set's to active buildkit builder
+
+.use-kube-context:
+ before_script:
+ - export KUBE_CONTEXT="gitlab-org/gitlab:review-apps"
+ - kubectl config use-context ${KUBE_CONTEXT}
diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml
index 27e2014baf1..cda13dd4be9 100644
--- a/.gitlab/ci/review-apps/main.gitlab-ci.yml
+++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml
@@ -123,6 +123,7 @@ review-deploy:
- echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
- echo "QA_GITLAB_URL=${CI_ENVIRONMENT_URL}" > environment.env
- *base-before_script
+ - !reference [".use-kube-context", before_script]
script:
- run_timed_command "check_kube_domain"
- run_timed_command "download_chart"
@@ -156,6 +157,7 @@ review-deploy-sample-projects:
- export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION)
- echo "${CI_ENVIRONMENT_URL}" > environment_url.txt
- *base-before_script
+ - !reference [".use-kube-context", before_script]
script:
- date
- create_sample_projects
@@ -173,6 +175,7 @@ review-deploy-sample-projects:
before_script:
- source ./scripts/utils.sh
- source ./scripts/review_apps/review-apps.sh
+ - !reference [".use-kube-context", before_script]
review-delete-deployment:
extends:
@@ -186,7 +189,7 @@ review-stop:
extends:
- .review-stop-base
- .review:rules:review-stop
- resource_group: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # CI_ENVIRONMENT_SLUG is not available here and we want this to be the same as the environment
+ resource_group: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # CI_ENVIRONMENT_SLUG is not available here and we want this to be the same as the environment
stage: deploy
needs: []
script:
diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml
index be9ad5128f3..1fb8985aba1 100644
--- a/.gitlab/ci/review.gitlab-ci.yml
+++ b/.gitlab/ci/review.gitlab-ci.yml
@@ -7,14 +7,20 @@ review-cleanup:
environment:
name: review/regular-cleanup
action: access
+ variables:
+ KUBE_NAMESPACE: "review-apps" # gcp_cleanup.sh requires passing KUBE_NAMESPACE variable
before_script:
- source scripts/utils.sh
- source scripts/review_apps/gcp_cleanup.sh
+ - !reference [".use-kube-context", before_script]
- install_gitlab_gem
- setup_gcp_dependencies
script:
- - scripts/review_apps/automated_cleanup.rb
- - gcp_cleanup
+ - exit_code_cmd_1=0;
+ - exit_code_cmd_2=0;
+ - scripts/review_apps/automated_cleanup.rb || exit_code_cmd_1=$?
+ - gcp_cleanup || exit_code_cmd_2=$?
+ - if [ $exit_code_cmd_1 -ne 0 ] || [ $exit_code_cmd_2 -ne 0 ]; then (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" warning "GitLab Bot" && exit 1); fi;
.base-review-checks:
extends:
@@ -35,8 +41,10 @@ review-k8s-resources-count-checks:
environment:
name: review/k8s-resources-count-checks
action: verify
+ before_script:
+ - !reference [".use-kube-context", before_script]
script:
- - scripts/review_apps/k8s-resources-count-checks.sh
+ - scripts/review_apps/k8s-resources-count-checks.sh || (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" warning "GitLab Bot" && exit 1);
review-gcp-quotas-checks:
extends:
@@ -46,8 +54,10 @@ review-gcp-quotas-checks:
environment:
name: review/gcp-quotas-checks
action: verify
+ before_script:
+ - !reference [".use-kube-context", before_script]
script:
- - ruby scripts/review_apps/gcp-quotas-checks.rb
+ - ruby scripts/review_apps/gcp-quotas-checks.rb || (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" warning "GitLab Bot" && exit 1);
start-review-app-pipeline:
extends:
diff --git a/app/assets/javascripts/issues/list/components/issue_card_statistics.vue b/app/assets/javascripts/issues/list/components/issue_card_statistics.vue
new file mode 100644
index 00000000000..2d00c3e549d
--- /dev/null
+++ b/app/assets/javascripts/issues/list/components/issue_card_statistics.vue
@@ -0,0 +1,56 @@
+<script>
+import { GlIcon, GlTooltipDirective } from '@gitlab/ui';
+import { i18n } from '../constants';
+
+export default {
+ i18n,
+ components: {
+ GlIcon,
+ },
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
+ props: {
+ issue: {
+ type: Object,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <ul class="gl-display-contents">
+ <li
+ v-if="issue.mergeRequestsCount"
+ v-gl-tooltip
+ class="gl-display-none gl-sm-display-block gl-mr-3"
+ :title="$options.i18n.relatedMergeRequests"
+ data-testid="merge-requests"
+ >
+ <gl-icon name="merge-request" />
+ {{ issue.mergeRequestsCount }}
+ </li>
+ <li
+ v-if="issue.upvotes"
+ v-gl-tooltip
+ class="gl-display-none gl-sm-display-block gl-mr-3"
+ :title="$options.i18n.upvotes"
+ data-testid="issuable-upvotes"
+ >
+ <gl-icon name="thumb-up" />
+ {{ issue.upvotes }}
+ </li>
+ <li
+ v-if="issue.downvotes"
+ v-gl-tooltip
+ class="gl-display-none gl-sm-display-block gl-mr-3"
+ :title="$options.i18n.downvotes"
+ data-testid="issuable-downvotes"
+ >
+ <gl-icon name="thumb-down" />
+ {{ issue.downvotes }}
+ </li>
+ <slot></slot>
+ </ul>
+</template>
diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue
index 021e3c867b0..29faf6ed16c 100644
--- a/app/assets/javascripts/issues/list/components/issues_list_app.vue
+++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue
@@ -1,13 +1,8 @@
<script>
-import {
- GlButton,
- GlEmptyState,
- GlFilteredSearchToken,
- GlIcon,
- GlTooltipDirective,
-} from '@gitlab/ui';
+import { GlButton, GlEmptyState, GlFilteredSearchToken, GlTooltipDirective } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import fuzzaldrinPlus from 'fuzzaldrin-plus';
+import IssueCardStatistics from 'ee_else_ce/issues/list/components/issue_card_statistics.vue';
import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue';
import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql';
import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql';
@@ -116,9 +111,9 @@ export default {
EmptyStateSignedOut,
GlButton,
GlEmptyState,
- GlIcon,
IssuableByEmail,
IssuableList,
+ IssueCardStatistics,
IssueCardTimeInfo,
NewIssueDropdown,
},
@@ -854,37 +849,7 @@ export default {
</template>
<template #statistics="{ issuable = {} }">
- <li
- v-if="issuable.mergeRequestsCount"
- v-gl-tooltip
- class="gl-display-none gl-sm-display-block"
- :title="$options.i18n.relatedMergeRequests"
- data-testid="merge-requests"
- >
- <gl-icon name="merge-request" />
- {{ issuable.mergeRequestsCount }}
- </li>
- <li
- v-if="issuable.upvotes"
- v-gl-tooltip
- class="gl-display-none gl-sm-display-block"
- :title="$options.i18n.upvotes"
- data-testid="issuable-upvotes"
- >
- <gl-icon name="thumb-up" />
- {{ issuable.upvotes }}
- </li>
- <li
- v-if="issuable.downvotes"
- v-gl-tooltip
- class="gl-display-none gl-sm-display-block"
- :title="$options.i18n.downvotes"
- data-testid="issuable-downvotes"
- >
- <gl-icon name="thumb-down" />
- {{ issuable.downvotes }}
- </li>
- <slot name="blocking-count" :issuable="issuable"></slot>
+ <issue-card-statistics :issue="issuable" />
</template>
<template #empty-state>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
index f71b1fbc539..79ea2624ec5 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue
@@ -1,8 +1,11 @@
<script>
-import { GlTooltipDirective } from '@gitlab/ui';
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
export default {
name: 'MrWidgetAuthor',
+ components: {
+ GlLink,
+ },
directives: {
GlTooltip: GlTooltipDirective,
},
@@ -28,13 +31,16 @@ export default {
};
</script>
<template>
- <a
+ <gl-link
v-gl-tooltip
:href="authorUrl"
:title="showAuthorName ? null : author.name"
- class="author-link inline"
+ class="mr-widget-author"
>
- <img :src="avatarUrl" class="avatar avatar-inline s16" />
- <span v-if="showAuthorName" class="author">{{ author.name }}</span>
- </a>
+ <img :src="avatarUrl" :alt="author.name" class="avatar avatar-inline s16" /><span
+ v-if="showAuthorName"
+ class="author"
+ >{{ author.name }}</span
+ >
+ </gl-link>
</template>
diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss
index b4ede515a7c..f36f6d652ba 100644
--- a/app/assets/stylesheets/page_bundles/merge_requests.scss
+++ b/app/assets/stylesheets/page_bundles/merge_requests.scss
@@ -1053,15 +1053,14 @@ $tabs-holder-z-index: 250;
}
}
-.mr-ready-merge-related-links,
-.mr-widget-merge-details {
- a {
- @include gl-text-decoration-underline;
+.mr-ready-merge-related-links a,
+.mr-widget-merge-details a,
+.mr-widget-author {
+ @include gl-text-decoration-underline;
- &:hover,
- &:focus {
- @include gl-text-decoration-none;
- }
+ &:hover,
+ &:focus {
+ @include gl-text-decoration-none;
}
}
diff --git a/app/controllers/concerns/web_hooks/hook_actions.rb b/app/controllers/concerns/web_hooks/hook_actions.rb
index 75065ef9d24..093a7d943b2 100644
--- a/app/controllers/concerns/web_hooks/hook_actions.rb
+++ b/app/controllers/concerns/web_hooks/hook_actions.rb
@@ -66,9 +66,7 @@ module WebHooks
end
def hook_param_names
- param_names = %i[enable_ssl_verification token url push_events_branch_filter]
- param_names.push(:branch_filter_strategy) if Feature.enabled?(:enhanced_webhook_support_regex)
- param_names
+ %i[enable_ssl_verification token url push_events_branch_filter branch_filter_strategy]
end
def destroy_hook(hook)
diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb
index 90d5f945d78..39e8f6c500d 100644
--- a/app/controllers/profiles/keys_controller.rb
+++ b/app/controllers/profiles/keys_controller.rb
@@ -37,6 +37,6 @@ class Profiles::KeysController < Profiles::ApplicationController
private
def key_params
- params.require(:key).permit(:title, :key, :expires_at)
+ params.require(:key).permit(:title, :key, :usage_type, :expires_at)
end
end
diff --git a/app/graphql/types/subscription_type.rb b/app/graphql/types/subscription_type.rb
index 9d5edec82b2..f7f26ba4c5a 100644
--- a/app/graphql/types/subscription_type.rb
+++ b/app/graphql/types/subscription_type.rb
@@ -34,6 +34,11 @@ module Types
subscription: Subscriptions::IssuableUpdated,
null: true,
description: 'Triggered when the merge status of a merge request is updated.'
+
+ field :merge_request_approval_state_updated,
+ subscription: Subscriptions::IssuableUpdated,
+ null: true,
+ description: 'Triggered when approval state of a merge request is updated.'
end
end
diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb
index bfe39bbc211..979b979fba7 100644
--- a/app/helpers/profiles_helper.rb
+++ b/app/helpers/profiles_helper.rb
@@ -46,6 +46,14 @@ module ProfilesHelper
end
end
+ def ssh_key_usage_types
+ {
+ s_('SSHKey|Authentication & Signing') => 'auth_and_signing',
+ s_('SSHKey|Authentication') => 'auth',
+ s_('SSHKey|Signing') => 'signing'
+ }
+ end
+
# Overridden in EE::ProfilesHelper#ssh_key_expiration_tooltip
def ssh_key_expiration_tooltip(key)
return key.errors.full_messages.join(', ') if key.errors.full_messages.any?
diff --git a/app/models/ci/pending_build.rb b/app/models/ci/pending_build.rb
index 0fa6a234a3d..2b1eb67d4f2 100644
--- a/app/models/ci/pending_build.rb
+++ b/app/models/ci/pending_build.rb
@@ -3,11 +3,14 @@
module Ci
class PendingBuild < Ci::ApplicationRecord
include EachBatch
+ include Ci::Partitionable
belongs_to :project
belongs_to :build, class_name: 'Ci::Build'
belongs_to :namespace, inverse_of: :pending_builds, class_name: 'Namespace'
+ partitionable scope: :build
+
validates :namespace, presence: true
scope :ref_protected, -> { where(protected: true) }
diff --git a/app/models/ci/running_build.rb b/app/models/ci/running_build.rb
index ae38d54862d..62a428a0c1e 100644
--- a/app/models/ci/running_build.rb
+++ b/app/models/ci/running_build.rb
@@ -2,6 +2,10 @@
module Ci
class RunningBuild < Ci::ApplicationRecord
+ include Ci::Partitionable
+
+ partitionable scope: :build
+
belongs_to :project
belongs_to :build, class_name: 'Ci::Build'
belongs_to :runner, class_name: 'Ci::Runner'
diff --git a/app/models/concerns/ci/partitionable.rb b/app/models/concerns/ci/partitionable.rb
index 7d18ee137ea..4001ba7d0fe 100644
--- a/app/models/concerns/ci/partitionable.rb
+++ b/app/models/concerns/ci/partitionable.rb
@@ -30,6 +30,8 @@ module Ci
Ci::BuildPendingState
Ci::JobArtifact
Ci::Pipeline
+ Ci::PendingBuild
+ Ci::RunningBuild
Ci::PipelineVariable
Ci::Stage
Ci::UnitTestFailure
diff --git a/app/models/group_deploy_key.rb b/app/models/group_deploy_key.rb
index c65b00a6de0..9495df7ab6d 100644
--- a/app/models/group_deploy_key.rb
+++ b/app/models/group_deploy_key.rb
@@ -12,6 +12,11 @@ class GroupDeployKey < Key
joins(:group_deploy_keys_groups).where(group_deploy_keys_groups: { group_id: group_ids }).uniq
end
+ # Remove usage_type because it defined in Key class but doesn't have a column in group_deploy_keys table
+ def self.defined_enums
+ super.without('usage_type')
+ end
+
def type
'DeployKey'
end
diff --git a/app/models/hooks/active_hook_filter.rb b/app/models/hooks/active_hook_filter.rb
index cdcfd3f3ff5..4599ebf8717 100644
--- a/app/models/hooks/active_hook_filter.rb
+++ b/app/models/hooks/active_hook_filter.rb
@@ -18,10 +18,6 @@ class ActiveHookFilter
branch_name = Gitlab::Git.branch_name(data[:ref])
- if Feature.disabled?(:enhanced_webhook_support_regex)
- return RefMatcher.new(@hook.push_events_branch_filter).matches?(branch_name)
- end
-
case @hook.branch_filter_strategy
when 'all_branches'
true
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 7b628de93c3..0c72158b407 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -39,12 +39,9 @@ class WebHook < ApplicationRecord
validates :token, format: { without: /\n/ }
after_initialize :initialize_url_variables
- before_validation :set_branch_filter_nil, \
- if: -> { branch_filter_strategy_all_branches? && enhanced_webhook_support_regex? }
- validates :push_events_branch_filter, \
- untrusted_regexp: true, if: -> { branch_filter_strategy_regex? && enhanced_webhook_support_regex? }
- validates :push_events_branch_filter, \
- "web_hooks/wildcard_branch_filter": true, if: -> { branch_filter_strategy_wildcard? }
+ before_validation :set_branch_filter_nil, if: :branch_filter_strategy_all_branches?
+ validates :push_events_branch_filter, untrusted_regexp: true, if: :branch_filter_strategy_regex?
+ validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true, if: :branch_filter_strategy_wildcard?
validates :url_variables, json_schema: { filename: 'web_hooks_url_variables' }
validate :no_missing_url_variables
@@ -239,10 +236,6 @@ class WebHook < ApplicationRecord
errors.add(:url, "Invalid URL template. Missing keys: #{missing}")
end
- def enhanced_webhook_support_regex?
- Feature.enabled?(:enhanced_webhook_support_regex)
- end
-
def set_branch_filter_nil
self.push_events_branch_filter = nil
end
diff --git a/app/models/key.rb b/app/models/key.rb
index 78b0a38bcaa..35fc42a935f 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -32,6 +32,12 @@ class Key < ApplicationRecord
delegate :name, :email, to: :user, prefix: true
+ enum usage_type: {
+ auth_and_signing: 0,
+ auth: 1,
+ signing: 2
+ }
+
after_commit :add_to_authorized_keys, on: :create
after_create :post_create_hook
after_create :refresh_user_cache
@@ -45,6 +51,8 @@ class Key < ApplicationRecord
scope :preload_users, -> { preload(:user) }
scope :for_user, -> (user) { where(user: user) }
scope :order_last_used_at_desc, -> { reorder(arel_table[:last_used_at].desc.nulls_last) }
+ scope :auth, -> { where(usage_type: [:auth, :auth_and_signing]) }
+ scope :signing, -> { where(usage_type: [:signing, :auth_and_signing]) }
# Date is set specifically in this scope to improve query time.
scope :expired_today_and_not_notified, -> { where(["date(expires_at AT TIME ZONE 'UTC') = CURRENT_DATE AND expiry_notification_delivered_at IS NULL"]) }
diff --git a/app/models/user.rb b/app/models/user.rb
index b2235bff456..dae7ea75583 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -811,7 +811,7 @@ class User < ApplicationRecord
# Returns a user for the given SSH key.
def find_by_ssh_key_id(key_id)
- find_by('EXISTS (?)', Key.select(1).where('keys.user_id = users.id').where(id: key_id))
+ find_by('EXISTS (?)', Key.select(1).where('keys.user_id = users.id').auth.where(id: key_id))
end
def find_by_full_path(path, follow_redirects: false)
diff --git a/app/services/users/keys_count_service.rb b/app/services/users/keys_count_service.rb
index f82d27eded9..378093f2e1b 100644
--- a/app/services/users/keys_count_service.rb
+++ b/app/services/users/keys_count_service.rb
@@ -11,7 +11,7 @@ module Users
end
def relation_for_count
- user.keys
+ user.keys.auth
end
def raw?
diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml
index e6d91543585..cf51d120edf 100644
--- a/app/views/profiles/keys/_form.html.haml
+++ b/app/views/profiles/keys/_form.html.haml
@@ -12,7 +12,12 @@
= f.label :title, s_('Profiles|Title'), class: 'label-bold'
= f.text_field :title, class: "form-control gl-form-input input-lg", required: true, placeholder: s_('Profiles|Example: MacBook key'), data: { qa_selector: 'key_title_field' }
%p.form-text.text-muted= s_('Profiles|Key titles are publicly visible.')
-
+ - if Feature.enabled?(:ssh_key_usage_types, current_user)
+ .form-row
+ .col.form-group
+ = f.label :usage_type, s_('Profiles|Usage type')
+ .gl-md-form-input-lg
+ = f.select :usage_type, options_for_select(ssh_key_usage_types, :auth_and_signing), {}, { class: 'gl-form-select custom-select' }
.form-row
.col.form-group
.js-access-tokens-expires-at{ data: {min_date: Date.tomorrow, max_date: max_date, default_date_offset: 365, description: ssh_key_expires_field_description } }
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index de4a19bdad7..f0e4b143d0d 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -25,6 +25,10 @@
%span.expires.gl-mr-3
= key.expired? ? s_('Profiles|Expired:') : s_('Profiles|Expires:')
= key.expires_at ? key.expires_at.to_date : _('Never')
+ - if Feature.enabled?(:ssh_key_usage_types, current_user)
+ %span.last-used-at.gl-mr-3
+ = s_('Profiles|Usage type:')
+ = ssh_key_usage_types.invert[key.usage_type]
%span.key-created-at.gl-display-flex.gl-align-items-center
- if key.can_delete?
.gl-ml-3
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 04fa1d96204..1a8de16471f 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -9,6 +9,10 @@
%li
%span.light= _('Title:')
%strong= @key.title
+ - if Feature.enabled?(:ssh_key_usage_types, current_user)
+ %li
+ %span.light= s_('Profiles|Usage type:')
+ %strong= ssh_key_usage_types.invert[@key.usage_type]
%li
%span.light= _('Created on:')
%strong= @key.created_at.to_s(:medium)
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 73ace033dc6..a749d1037a1 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,16 +1,28 @@
+-# We're not using `link_to` in the line loop because it is too slow once we get to thousands of lines.
+
+- offset = defined?(first_line_number) ? first_line_number : 1
+- highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil
+- file_line_blame = Feature.enabled?(:file_line_blame)
+
+- if file_line_blame
+ - line_class = "js-line-links"
+ - blame_path = project_blame_path(@project, tree_join(@ref, blob.path))
+- else
+ - line_class = nil
+ - blame_path = nil
+
+- highlighted_blob = blob.present.highlight
+
#blob-content.file-content.code.js-syntax-highlight
- - offset = defined?(first_line_number) ? first_line_number : 1
- - if Feature.enabled?(:file_line_blame)
- - blame_path = project_blame_path(@project, tree_join(@ref, blob.path))
.line-numbers{ class: "gl-px-0!", data: { blame_path: blame_path } }
- if blob.data.present?
- - blob.data.each_line.each_with_index do |_, index|
+ - highlighted_blob.lines.count.times do |index|
- i = index + offset
- -# We're not using `link_to` because it is too slow once we get to thousands of lines.
- %a.file-line-num.diff-line-num{ class: ("js-line-links" if Feature.enabled?(:file_line_blame)), href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
+
+ %a.file-line-num.diff-line-num{ class: line_class, href: "#L#{i}", id: "L#{i}", 'data-line-number' => i }
= i
- - highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil
+
.blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } }
%pre.code.highlight
%code
- = blob.present.highlight
+ = highlighted_blob
diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml
index ecb736dac4f..12b105ae58c 100644
--- a/app/views/shared/web_hooks/_form.html.haml
+++ b/app/views/shared/web_hooks/_form.html.haml
@@ -19,16 +19,7 @@
= form.label :url, s_('Webhooks|Trigger'), class: 'label-bold'
%ul.list-unstyled
%li.gl-pb-5
- - if Feature.enabled?(:enhanced_webhook_support_regex)
- - is_new_hook = hook.id.nil?
- .js-vue-push-events{ data: { push_events: hook.push_events.to_s, strategy: hook.branch_filter_strategy, is_new_hook: is_new_hook.to_s, push_events_branch_filter: hook.push_events_branch_filter } }
- - else
- = form.gitlab_ui_checkbox_component :push_events, s_('Webhooks|Push events')
- .gl-pl-6
- = form.text_field :push_events_branch_filter, class: 'form-control gl-form-input',
- placeholder: 'Branch name or wildcard pattern to trigger on (leave blank for all)'
- %p.form-text.text-muted.custom-control
- = s_('Webhooks|Push to the repository.')
+ .js-vue-push-events{ data: { push_events: hook.push_events.to_s, strategy: hook.branch_filter_strategy, is_new_hook: hook.new_record?.to_s, push_events_branch_filter: hook.push_events_branch_filter } }
%li.gl-pb-5
= form.gitlab_ui_checkbox_component :tag_push_events,
s_('Webhooks|Tag push events'),
diff --git a/config/feature_flags/development/enhanced_webhook_support_regex.yml b/config/feature_flags/development/enhanced_webhook_support_regex.yml
deleted file mode 100644
index 2c0d2c82dbf..00000000000
--- a/config/feature_flags/development/enhanced_webhook_support_regex.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: enhanced_webhook_support_regex
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97235
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375728
-milestone: '15.6'
-type: development
-group: group::integrations
-default_enabled: false
diff --git a/config/feature_flags/development/subgroups_approval_rules.yml b/config/feature_flags/development/ssh_key_usage_types.yml
index e7935f5e5d2..fc7c719c13a 100644
--- a/config/feature_flags/development/subgroups_approval_rules.yml
+++ b/config/feature_flags/development/ssh_key_usage_types.yml
@@ -1,8 +1,8 @@
---
-name: subgroups_approval_rules
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91598
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366741
-milestone: '15.2'
+name: ssh_key_usage_types
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104283
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383046
+milestone: '15.7'
type: development
group: group::source code
-default_enabled: true
+default_enabled: false
diff --git a/config/open_api.yml b/config/open_api.yml
index e01b5265525..100bf4df67e 100644
--- a/config/open_api.yml
+++ b/config/open_api.yml
@@ -29,6 +29,8 @@ metadata:
description: Operations related to clusters
- name: container_registry
description: Operations related to container registry
+ - name: container_registry_event
+ description: Operations related to container registry events
- name: dashboard_annotations
description: Operations related to dashboard annotations
- name: dependency_proxy
@@ -99,6 +101,8 @@ metadata:
description: Operations related to project packages
- name: protected environments
description: Operations related to protected environments
+ - name: pypi_packages
+ description: Operations related to PyPI packages
- name: release_links
description: Operations related to release assets (links)
- name: releases
diff --git a/db/migrate/20221116161126_add_auth_signing_type_to_keys.rb b/db/migrate/20221116161126_add_auth_signing_type_to_keys.rb
new file mode 100644
index 00000000000..795074fa0ca
--- /dev/null
+++ b/db/migrate/20221116161126_add_auth_signing_type_to_keys.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddAuthSigningTypeToKeys < Gitlab::Database::Migration[2.0]
+ def change
+ add_column :keys, :usage_type, :integer, limit: 2, null: false, default: 0
+ end
+end
diff --git a/db/schema_migrations/20221116161126 b/db/schema_migrations/20221116161126
new file mode 100644
index 00000000000..5d65ed55915
--- /dev/null
+++ b/db/schema_migrations/20221116161126
@@ -0,0 +1 @@
+93286f75aec167041985c2cde8ef1fc32447eae4f520c87131b89c28c402675c \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 69b25972812..ab7b632b04a 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -17126,7 +17126,8 @@ CREATE TABLE keys (
fingerprint_sha256 bytea,
expires_at timestamp with time zone,
expiry_notification_delivered_at timestamp with time zone,
- before_expiry_notification_delivered_at timestamp with time zone
+ before_expiry_notification_delivered_at timestamp with time zone,
+ usage_type smallint DEFAULT 0 NOT NULL
);
CREATE SEQUENCE keys_id_seq
diff --git a/doc/api/product_analytics.md b/doc/api/product_analytics.md
index e10327bc59b..b7401f83128 100644
--- a/doc/api/product_analytics.md
+++ b/doc/api/product_analytics.md
@@ -16,12 +16,13 @@ This feature is not ready for production use.
NOTE:
Make sure to define the `cube_api_base_url` and `cube_api_key` application settings first using [the API](settings.md).
-## Send request to Cube
+## Send query request to Cube
Generate an access token that can be used to query the Cube API. For example:
```plaintext
POST /projects/:id/product_analytics/request/load
+POST /projects/:id/product_analytics/request/dry-run
```
| Attribute | Type | Required | Description |
@@ -30,7 +31,7 @@ POST /projects/:id/product_analytics/request/load
### Request body
-The body of the request should be a valid Cube query.
+The body of the load request should be a valid Cube query.
```json
{
@@ -66,3 +67,15 @@ The body of the request should be a valid Cube query.
"queryType": "multi"
}
```
+
+## Send meta request to Cube
+
+Returns Cube Meta data for the Analytics data. For example:
+
+```plaintext
+GET /projects/:id/product_analytics/request/meta
+```
+
+| Attribute | Type | Required | Description |
+| --------- |------------------| -------- |---------------------------------------------------------------|
+| `id` | integer | yes | The ID of a project that the current user has read access to. |
diff --git a/doc/integration/mattermost/index.md b/doc/integration/mattermost/index.md
index 04b0157b737..df6130a7540 100644
--- a/doc/integration/mattermost/index.md
+++ b/doc/integration/mattermost/index.md
@@ -123,7 +123,7 @@ http://mattermost.example.com/signup/gitlab/complete
http://mattermost.example.com/login/gitlab/complete
```
-Note that you do not need to select any options under **Scopes**. Choose **Save application**.
+Make sure to select the **Trusted** and **Confidential** settings. Under **Scopes**, select `read_user`. Then, choose **Save application**.
Once the application is created you are provided with an `Application ID` and `Secret`. One other piece of information needed is the URL of GitLab instance.
Return to the server running GitLab Mattermost and edit the `/etc/gitlab/gitlab.rb` configuration file as follows using the values you received above:
@@ -132,7 +132,7 @@ Return to the server running GitLab Mattermost and edit the `/etc/gitlab/gitlab.
mattermost['gitlab_enable'] = true
mattermost['gitlab_id'] = "12345656"
mattermost['gitlab_secret'] = "123456789"
-mattermost['gitlab_scope'] = ""
+mattermost['gitlab_scope'] = "read_user"
mattermost['gitlab_auth_endpoint'] = "http://gitlab.example.com/oauth/authorize"
mattermost['gitlab_token_endpoint'] = "http://gitlab.example.com/oauth/token"
mattermost['gitlab_user_api_endpoint'] = "http://gitlab.example.com/api/v4/user"
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 10fdb8d7682..46d0aa3d19d 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -196,6 +196,7 @@ module API
mount ::API::Clusters::Agents
mount ::API::Commits
mount ::API::CommitStatuses
+ mount ::API::ContainerRegistryEvent
mount ::API::DependencyProxy
mount ::API::DeployKeys
mount ::API::DeployTokens
@@ -252,6 +253,7 @@ module API
mount ::API::ProjectTemplates
mount ::API::ProtectedBranches
mount ::API::ProtectedTags
+ mount ::API::PypiPackages
mount ::API::Releases
mount ::API::Release::Links
mount ::API::RemoteMirrors
@@ -289,7 +291,6 @@ module API
mount ::API::ComposerPackages
mount ::API::ConanInstancePackages
mount ::API::ConanProjectPackages
- mount ::API::ContainerRegistryEvent
mount ::API::ContainerRepositories
mount ::API::DebianGroupPackages
mount ::API::DebianProjectPackages
@@ -318,7 +319,6 @@ module API
mount ::API::ProjectMilestones
mount ::API::Projects
mount ::API::ProtectedTags
- mount ::API::PypiPackages
mount ::API::ResourceLabelEvents
mount ::API::ResourceStateEvents
mount ::API::RpmProjectPackages
diff --git a/lib/api/container_registry_event.rb b/lib/api/container_registry_event.rb
index 9acf2fca1b3..9e59401ddf6 100644
--- a/lib/api/container_registry_event.rb
+++ b/lib/api/container_registry_event.rb
@@ -26,15 +26,21 @@ module API
desc 'Receives notifications from the container registry when an operation occurs' do
detail 'This feature was introduced in GitLab 12.10'
consumes [:json, DOCKER_DISTRIBUTION_EVENTS_V1_JSON]
+ success code: 200, message: 'Success'
+ failure [
+ { code: 401, message: 'Invalid Token' }
+ ]
+ tags %w[container_registry_event]
end
params do
requires :events, type: Array, desc: 'Event notifications' do
requires :action, type: String, desc: 'The action to perform, `push`, `delete`',
values: %w[push delete].freeze
optional :target, type: Hash, desc: 'The target of the action' do
- optional :tag, type: String, desc: 'The target tag'
- optional :repository, type: String, desc: 'The target repository'
- optional :digest, type: String, desc: 'Unique identifier for target image manifest'
+ optional :tag, type: String, desc: 'The target tag', documentation: { example: 'latest' }
+ optional :repository, type: String, desc: 'The target repository', documentation: { example: 'group/p1' }
+ optional :digest, type: String, desc: 'Unique identifier for target image manifest',
+ documentation: { example: 'imagedigest' }
end
end
end
diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb
index c4464666020..dbd5c5f9db1 100644
--- a/lib/api/internal/base.rb
+++ b/lib/api/internal/base.rb
@@ -191,7 +191,7 @@ module API
get '/authorized_keys', feature_category: :source_code_management, urgency: :high do
fingerprint = Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint_sha256
- key = Key.find_by_fingerprint_sha256(fingerprint)
+ key = Key.auth.find_by_fingerprint_sha256(fingerprint)
not_found!('Key') if key.nil?
present key, with: Entities::SSHKey
end
diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb
index 0707a0b0ec4..f9470ce1cb6 100644
--- a/lib/api/pypi_packages.rb
+++ b/lib/api/pypi_packages.rb
@@ -32,12 +32,12 @@ module API
helpers do
params :package_download do
- requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true
- requires :sha256, type: String, desc: 'The PyPi package sha256 check sum'
+ requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true, documentation: { example: 'my.pypi.package-0.0.1.tar.gz' }
+ requires :sha256, type: String, desc: 'The PyPi package sha256 check sum', documentation: { example: '5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff' }
end
params :package_name do
- requires :package_name, type: String, file_path: true, desc: 'The PyPi package name'
+ requires :package_name, type: String, file_path: true, desc: 'The PyPi package name', documentation: { example: 'my.pypi.package' }
end
def present_simple_index(group_or_project)
@@ -102,7 +102,7 @@ module API
end
params do
- requires :id, type: String, desc: 'The ID of a group'
+ requires :id, types: [Integer, String], desc: 'The ID or full path of the group.'
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
after_validation do
@@ -110,6 +110,16 @@ module API
end
namespace ':id/-/packages/pypi' do
+ desc 'Download a package file from a group' do
+ detail 'This feature was introduced in GitLab 13.12'
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[pypi_packages]
+ end
params do
use :package_download
end
@@ -130,6 +140,13 @@ module API
desc 'The PyPi Simple Group Index Endpoint' do
detail 'This feature was introduced in GitLab 15.1'
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[pypi_packages]
end
# An API entry point but returns an HTML file instead of JSON.
@@ -141,6 +158,13 @@ module API
desc 'The PyPi Simple Group Package Endpoint' do
detail 'This feature was introduced in GitLab 12.10'
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[pypi_packages]
end
params do
@@ -164,6 +188,13 @@ module API
namespace ':id/packages/pypi' do
desc 'The PyPi package download endpoint' do
detail 'This feature was introduced in GitLab 12.10'
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[pypi_packages]
end
params do
@@ -185,6 +216,13 @@ module API
desc 'The PyPi Simple Project Index Endpoint' do
detail 'This feature was introduced in GitLab 15.1'
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[pypi_packages]
end
# An API entry point but returns an HTML file instead of JSON.
@@ -196,6 +234,13 @@ module API
desc 'The PyPi Simple Project Package Endpoint' do
detail 'This feature was introduced in GitLab 12.10'
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[pypi_packages]
end
params do
@@ -211,15 +256,24 @@ module API
desc 'The PyPi Package upload endpoint' do
detail 'This feature was introduced in GitLab 12.10'
+ success code: 201
+ failure [
+ { code: 400, message: 'Bad Request' },
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' },
+ { code: 422, message: 'Unprocessable Entity' }
+ ]
+ tags %w[pypi_packages]
end
params do
requires :content, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' }
- requires :name, type: String
- requires :version, type: String
- optional :requires_python, type: String
- optional :md5_digest, type: String
- optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex
+ requires :name, type: String, documentation: { example: 'my.pypi.package' }
+ requires :version, type: String, documentation: { example: '1.3.7' }
+ optional :requires_python, type: String, documentation: { example: '>=3.7' }
+ optional :md5_digest, type: String, documentation: { example: '900150983cd24fb0d6963f7d28e17f72' }
+ optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex, documentation: { example: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad' }
end
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
@@ -243,6 +297,17 @@ module API
forbidden!
end
+ desc 'Authorize the PyPi package upload from workhorse' do
+ detail 'This feature was introduced in GitLab 12.10'
+ success code: 200
+ failure [
+ { code: 401, message: 'Unauthorized' },
+ { code: 403, message: 'Forbidden' },
+ { code: 404, message: 'Not Found' }
+ ]
+ tags %w[pypi_packages]
+ end
+
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
post 'authorize' do
project = project!(action: :read_project)
diff --git a/lib/api/support/git_access_actor.rb b/lib/api/support/git_access_actor.rb
index 16861a146ae..7a4e6f3e14c 100644
--- a/lib/api/support/git_access_actor.rb
+++ b/lib/api/support/git_access_actor.rb
@@ -16,7 +16,7 @@ module API
def self.from_params(params)
if params[:key_id]
- new(key: Key.find_by_id(params[:key_id]))
+ new(key: Key.auth.find_by_id(params[:key_id]))
elsif params[:user_id]
new(user: UserFinder.new(params[:user_id]).find_by_id)
elsif params[:username]
diff --git a/lib/gitlab/ssh/signature.rb b/lib/gitlab/ssh/signature.rb
index 3b4df9a8d0c..b1cad8d76c9 100644
--- a/lib/gitlab/ssh/signature.rb
+++ b/lib/gitlab/ssh/signature.rb
@@ -30,7 +30,7 @@ module Gitlab
strong_memoize(:signed_by_key) do
next unless key_fingerprint
- Key.find_by_fingerprint_sha256(key_fingerprint)
+ Key.signing.find_by_fingerprint_sha256(key_fingerprint)
end
end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index cf9876366aa..59c87c2b01b 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -81,7 +81,7 @@ namespace :gitlab do
authorized_keys.clear
- Key.find_in_batches(batch_size: 1000) do |keys|
+ Key.auth.find_in_batches(batch_size: 1000) do |keys|
unless authorized_keys.batch_add_keys(keys)
puts "Failed to add keys...".color(:red)
exit 1
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 0393bfbe671..0bfabf00780 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -31222,9 +31222,15 @@ msgstr ""
msgid "Product analytics"
msgstr ""
+msgid "ProductAnalytics|Add to Dashboard"
+msgstr ""
+
msgid "ProductAnalytics|Audience"
msgstr ""
+msgid "ProductAnalytics|New Analytics Widget Title"
+msgstr ""
+
msgid "ProductAnalytics|There is no data for this type of chart currently. Please see the Setup tab if you have not configured the product analytics tool already."
msgstr ""
@@ -31591,6 +31597,12 @@ msgstr ""
msgid "Profiles|Upload new avatar"
msgstr ""
+msgid "Profiles|Usage type"
+msgstr ""
+
+msgid "Profiles|Usage type:"
+msgstr ""
+
msgid "Profiles|Use a private email - %{email}"
msgstr ""
@@ -35812,6 +35824,15 @@ msgstr ""
msgid "SSH public key"
msgstr ""
+msgid "SSHKey|Authentication"
+msgstr ""
+
+msgid "SSHKey|Authentication & Signing"
+msgstr ""
+
+msgid "SSHKey|Signing"
+msgstr ""
+
msgid "SSL Verification:"
msgstr ""
@@ -45942,9 +45963,6 @@ msgstr ""
msgid "Webhooks|Push events"
msgstr ""
-msgid "Webhooks|Push to the repository."
-msgstr ""
-
msgid "Webhooks|Regex such as %{REGEX_CODE} is supported."
msgstr ""
diff --git a/package.json b/package.json
index 37b400455c9..5d0ad0226ba 100644
--- a/package.json
+++ b/package.json
@@ -53,8 +53,8 @@
"@codesandbox/sandpack-client": "^1.2.2",
"@gitlab/at.js": "1.5.7",
"@gitlab/favicon-overlay": "2.0.0",
- "@gitlab/svgs": "3.8.0",
- "@gitlab/ui": "49.11.1",
+ "@gitlab/svgs": "3.11.0",
+ "@gitlab/ui": "49.11.2",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "0.0.1-dev-20221114183058",
"@rails/actioncable": "6.1.4-7",
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
index a7fe9c7630e..82df0b23312 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb
@@ -225,8 +225,8 @@ module QA
comment_diff = verify_comments(type, actual, expected)
{
- "missing_#{type}s": (expected.keys - actual.keys).map { |it| actual[it]&.slice(:title, :url) }.compact,
- "extra_#{type}s": (actual.keys - expected.keys).map { |it| expected[it]&.slice(:title, :url) }.compact,
+ "missing_#{type}s": (expected.keys - actual.keys).map { |it| expected[it]&.slice(:title, :url) }.compact,
+ "extra_#{type}s": (actual.keys - expected.keys).map { |it| actual[it]&.slice(:title, :url) }.compact,
"#{type}_comments": comment_diff
}
end
diff --git a/qa/qa/tools/reliable_report.rb b/qa/qa/tools/reliable_report.rb
index b3df6de3d54..08e87d994f8 100644
--- a/qa/qa/tools/reliable_report.rb
+++ b/qa/qa/tools/reliable_report.rb
@@ -341,7 +341,7 @@ module QA
runs = records.count
failed = records.count { |r| r.values["status"] == "failed" }
- failure_rate = (failed.to_f / runs.to_f) * 100
+ failure_rate = (failed.to_f / runs) * 100
result[stage][name] = {
file: file,
@@ -358,7 +358,7 @@ module QA
# @return [String]
def query(reliable)
<<~QUERY
- from(bucket: "#{Support::InfluxdbTools::INFLUX_TEST_METRICS_BUCKET}")
+ from(bucket: "#{Support::InfluxdbTools::INFLUX_MAIN_TEST_METRICS_BUCKET}")
|> range(start: -#{range}d)
|> filter(fn: (r) => r._measurement == "test-stats")
|> filter(fn: (r) => r.run_type == "staging-full" or
diff --git a/qa/spec/tools/reliable_report_spec.rb b/qa/spec/tools/reliable_report_spec.rb
index f08af8a717a..c1ac5899279 100644
--- a/qa/spec/tools/reliable_report_spec.rb
+++ b/qa/spec/tools/reliable_report_spec.rb
@@ -56,7 +56,7 @@ describe QA::Tools::ReliableReport do
def flux_query(reliable:)
<<~QUERY
- from(bucket: "e2e-test-stats")
+ from(bucket: "e2e-test-stats-main")
|> range(start: -#{range}d)
|> filter(fn: (r) => r._measurement == "test-stats")
|> filter(fn: (r) => r.run_type == "staging-full" or
diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb
index 63818337722..ed9022faf1b 100644
--- a/spec/controllers/profiles/keys_controller_spec.rb
+++ b/spec/controllers/profiles/keys_controller_spec.rb
@@ -14,13 +14,14 @@ RSpec.describe Profiles::KeysController do
expires_at = 3.days.from_now
expect do
- post :create, params: { key: build(:key, expires_at: expires_at).attributes }
+ post :create, params: { key: build(:key, usage_type: :signing, expires_at: expires_at).attributes }
end.to change { Key.count }.by(1)
key = Key.last
expect(key.expires_at).to be_like_time(expires_at)
expect(key.fingerprint_md5).to be_present
expect(key.fingerprint_sha256).to be_present
+ expect(key.usage_type).to eq('signing')
end
context 'with FIPS mode', :fips_mode do
diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb
index 18f16937505..0045262a4af 100644
--- a/spec/controllers/projects/hooks_controller_spec.rb
+++ b/spec/controllers/projects/hooks_controller_spec.rb
@@ -154,28 +154,6 @@ RSpec.describe Projects::HooksController do
expect(flash[:alert]).to be_blank
end
- it 'ignores branch_filter_strategy when flag is disabled' do
- stub_feature_flags(enhanced_webhook_support_regex: false)
- hook_params = {
- url: 'http://example.com',
- branch_filter_strategy: 'regex',
- push_events: true
- }
- params = { namespace_id: project.namespace, project_id: project, hook: hook_params }
-
- expect { post :create, params: params }.to change(ProjectHook, :count).by(1)
-
- project_hook = ProjectHook.order_id_desc.take
-
- expect(project_hook).to have_attributes(
- url: 'http://example.com',
- branch_filter_strategy: 'wildcard'
- )
-
- expect(response).to have_gitlab_http_status(:found)
- expect(flash[:alert]).to be_blank
- end
-
it 'alerts the user if the new hook is invalid' do
hook_params = {
token: "TEST\nTOKEN",
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index b88d6b5fda4..eb9ec3e0483 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -7,6 +7,7 @@ FactoryBot.define do
created_at { 'Di 29. Okt 09:50:00 CET 2013' }
scheduling_type { 'stage' }
pending
+ partition_id { pipeline.partition_id }
options do
{
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 891628a0fc2..fea1d249e2b 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -8,7 +8,7 @@ FactoryBot.define do
sha { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' }
status { 'pending' }
add_attribute(:protected) { false }
- partition_id { 100 }
+ partition_id { Ci::Pipeline.current_partition_value }
project
@@ -54,7 +54,6 @@ FactoryBot.define do
end
factory :ci_pipeline do
- partition_id { 100 }
transient { ci_ref_presence { true } }
before(:create) do |pipeline, evaluator|
diff --git a/spec/features/projects/settings/webhooks_settings_spec.rb b/spec/features/projects/settings/webhooks_settings_spec.rb
index adbf2f6ee5c..0b6c4144340 100644
--- a/spec/features/projects/settings/webhooks_settings_spec.rb
+++ b/spec/features/projects/settings/webhooks_settings_spec.rb
@@ -48,47 +48,21 @@ RSpec.describe 'Projects > Settings > Webhook Settings' do
expect(page).to have_content('Releases events')
end
- context 'when feature flag "enhanced_webhook_support_regex" is disabled' do
- before do
- stub_feature_flags(enhanced_webhook_support_regex: false)
- end
-
- it 'create webhook', :js do
- visit webhooks_path
-
- fill_in 'URL', with: url
- check 'Tag push events'
- fill_in 'hook_push_events_branch_filter', with: 'master'
- check 'Enable SSL verification'
- check 'Job events'
-
- click_button 'Add webhook'
-
- expect(page).to have_content(url)
- expect(page).to have_content('SSL Verification: enabled')
- expect(page).to have_content('Tag push events')
- expect(page).to have_content('Job events')
- expect(page).to have_content('Push events')
- end
- end
-
- context 'when feature flag "enhanced_webhook_support_regex" is enabled' do
- it 'create webhook', :js do
- visit webhooks_path
+ it 'create webhook', :js do
+ visit webhooks_path
- fill_in 'URL', with: url
- check 'Tag push events'
- check 'Enable SSL verification'
- check 'Job events'
+ fill_in 'URL', with: url
+ check 'Tag push events'
+ check 'Enable SSL verification'
+ check 'Job events'
- click_button 'Add webhook'
+ click_button 'Add webhook'
- expect(page).to have_content(url)
- expect(page).to have_content('SSL Verification: enabled')
- expect(page).to have_content('Tag push events')
- expect(page).to have_content('Job events')
- expect(page).to have_content('Push events')
- end
+ expect(page).to have_content(url)
+ expect(page).to have_content('SSL Verification: enabled')
+ expect(page).to have_content('Tag push events')
+ expect(page).to have_content('Job events')
+ expect(page).to have_content('Push events')
end
it 'edit existing webhook', :js do
diff --git a/spec/frontend/issues/list/components/issue_card_statistics_spec.js b/spec/frontend/issues/list/components/issue_card_statistics_spec.js
new file mode 100644
index 00000000000..180d4ab7eb6
--- /dev/null
+++ b/spec/frontend/issues/list/components/issue_card_statistics_spec.js
@@ -0,0 +1,64 @@
+import { GlIcon } from '@gitlab/ui';
+import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
+import IssueCardStatistics from '~/issues/list/components/issue_card_statistics.vue';
+import { i18n } from '~/issues/list/constants';
+
+describe('IssueCardStatistics CE component', () => {
+ let wrapper;
+
+ const findMergeRequests = () => wrapper.findByTestId('merge-requests');
+ const findUpvotes = () => wrapper.findByTestId('issuable-upvotes');
+ const findDownvotes = () => wrapper.findByTestId('issuable-downvotes');
+
+ const mountComponent = ({ mergeRequestsCount, upvotes, downvotes } = {}) => {
+ wrapper = shallowMountExtended(IssueCardStatistics, {
+ propsData: {
+ issue: {
+ mergeRequestsCount,
+ upvotes,
+ downvotes,
+ },
+ },
+ });
+ };
+
+ describe('when issue attributes are undefined', () => {
+ it('does not render the attributes', () => {
+ mountComponent();
+
+ expect(findMergeRequests().exists()).toBe(false);
+ expect(findUpvotes().exists()).toBe(false);
+ expect(findDownvotes().exists()).toBe(false);
+ });
+ });
+
+ describe('when issue attributes are defined', () => {
+ beforeEach(() => {
+ mountComponent({ mergeRequestsCount: 1, upvotes: 5, downvotes: 9 });
+ });
+
+ it('renders merge requests', () => {
+ const mergeRequests = findMergeRequests();
+
+ expect(mergeRequests.text()).toBe('1');
+ expect(mergeRequests.attributes('title')).toBe(i18n.relatedMergeRequests);
+ expect(mergeRequests.findComponent(GlIcon).props('name')).toBe('merge-request');
+ });
+
+ it('renders upvotes', () => {
+ const upvotes = findUpvotes();
+
+ expect(upvotes.text()).toBe('5');
+ expect(upvotes.attributes('title')).toBe(i18n.upvotes);
+ expect(upvotes.findComponent(GlIcon).props('name')).toBe('thumb-up');
+ });
+
+ it('renders downvotes', () => {
+ const downvotes = findDownvotes();
+
+ expect(downvotes.text()).toBe('9');
+ expect(downvotes.attributes('title')).toBe(i18n.downvotes);
+ expect(downvotes.findComponent(GlIcon).props('name')).toBe('thumb-down');
+ });
+ });
+});
diff --git a/spec/graphql/types/subscription_type_spec.rb b/spec/graphql/types/subscription_type_spec.rb
index 04f0c72b06f..a57a8e751ac 100644
--- a/spec/graphql/types/subscription_type_spec.rb
+++ b/spec/graphql/types/subscription_type_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe GitlabSchema.types['Subscription'] do
issuable_milestone_updated
merge_request_reviewers_updated
merge_request_merge_status_updated
+ merge_request_approval_state_updated
]
expect(described_class).to include_graphql_fields(*expected_fields)
diff --git a/spec/lib/api/support/git_access_actor_spec.rb b/spec/lib/api/support/git_access_actor_spec.rb
index e1c800d25a7..b3e8787583c 100644
--- a/spec/lib/api/support/git_access_actor_spec.rb
+++ b/spec/lib/api/support/git_access_actor_spec.rb
@@ -9,7 +9,8 @@ RSpec.describe API::Support::GitAccessActor do
subject { described_class.new(user: user, key: key) }
describe '.from_params' do
- let(:key) { create(:key) }
+ let_it_be(:user) { create(:user) }
+ let_it_be(:key) { create(:key, user: user) }
context 'with params that are valid' do
it 'returns an instance of API::Support::GitAccessActor' do
@@ -31,6 +32,42 @@ RSpec.describe API::Support::GitAccessActor do
expect(described_class.from_params(identifier: "key-#{key.id}").user).to eq(key.user)
end
end
+
+ context 'when passing a signing key' do
+ let_it_be(:key) { create(:key, usage_type: :signing, user: user) }
+
+ it 'does not identify the user' do
+ actor = described_class.from_params({ identifier: "key-#{key.id}" })
+
+ expect(actor).to be_instance_of(described_class)
+ expect(actor.user).to be_nil
+ end
+
+ it 'does not identify the key' do
+ actor = described_class.from_params({ key_id: key.id })
+
+ expect(actor).to be_instance_of(described_class)
+ expect(actor.key).to be_nil
+ end
+ end
+
+ context 'when passing an auth-only key' do
+ let_it_be(:key) { create(:key, usage_type: :auth, user: user) }
+
+ it 'identifies the user' do
+ actor = described_class.from_params({ identifier: "key-#{key.id}" })
+
+ expect(actor).to be_instance_of(described_class)
+ expect(actor.user).to eq(key.user)
+ end
+
+ it 'identifies the key' do
+ actor = described_class.from_params({ key_id: key.id })
+
+ expect(actor).to be_instance_of(described_class)
+ expect(actor.key).to eq(key)
+ end
+ end
end
describe 'attributes' do
diff --git a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
index 15df5b2f68c..74a68f28f3e 100644
--- a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb
@@ -10,13 +10,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::AssignPartition do
Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user)
end
- let(:pipeline) { build(:ci_pipeline, project: project) }
+ let(:pipeline) { build(:ci_pipeline, project: project, partition_id: nil) }
let(:step) { described_class.new(pipeline, command) }
let(:current_partition_id) { 123 }
describe '#perform!' do
+ include Ci::PartitioningHelpers
+
before do
- allow(Ci::Pipeline).to receive(:current_partition_value) { current_partition_id }
+ stub_current_partition_id(current_partition_id)
end
subject { step.perform! }
diff --git a/spec/lib/gitlab/ssh/signature_spec.rb b/spec/lib/gitlab/ssh/signature_spec.rb
index e8d366f0762..f3f1ba84f9e 100644
--- a/spec/lib/gitlab/ssh/signature_spec.rb
+++ b/spec/lib/gitlab/ssh/signature_spec.rb
@@ -7,7 +7,7 @@ RSpec.describe Gitlab::Ssh::Signature do
let_it_be(:committer_email) { 'ssh-commit-test@example.com' }
let_it_be(:public_key_text) { 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJKOfqOH0fDde+Ua/1SObkXB1CEDF5M6UfARMpW3F87u' }
let_it_be_with_reload(:user) { create(:user, email: committer_email) }
- let_it_be_with_reload(:key) { create(:key, key: public_key_text, user: user) }
+ let_it_be_with_reload(:key) { create(:key, usage_type: :signing, key: public_key_text, user: user) }
let(:signed_text) { 'This message was signed by an ssh key' }
@@ -204,13 +204,25 @@ RSpec.describe Gitlab::Ssh::Signature do
it_behaves_like 'unverified signature'
end
- context 'when key does not exist in GitLab' do
- before do
- key.delete
+ context 'when the signing key does not exist in GitLab' do
+ context 'when the key is not a signing one' do
+ before do
+ key.auth!
+ end
+
+ it 'reports unknown_key status' do
+ expect(signature.verification_status).to eq(:unknown_key)
+ end
end
- it 'reports unknown_key status' do
- expect(signature.verification_status).to eq(:unknown_key)
+ context 'when the key is removed' do
+ before do
+ key.delete
+ end
+
+ it 'reports unknown_key status' do
+ expect(signature.verification_status).to eq(:unknown_key)
+ end
end
end
diff --git a/spec/models/ci/pending_build_spec.rb b/spec/models/ci/pending_build_spec.rb
index 4bb43233dbd..331522070df 100644
--- a/spec/models/ci/pending_build_spec.rb
+++ b/spec/models/ci/pending_build_spec.rb
@@ -196,6 +196,28 @@ RSpec.describe Ci::PendingBuild do
end
end
+ describe 'partitioning', :ci_partitionable do
+ include Ci::PartitioningHelpers
+
+ before do
+ stub_current_partition_id
+ end
+
+ let(:new_pipeline ) { create(:ci_pipeline, project: pipeline.project) }
+ let(:new_build) { create(:ci_build, pipeline: new_pipeline) }
+
+ it 'assigns the same partition id as the one that build has', :aggregate_failures do
+ expect(new_build.partition_id).to eq ci_testing_partition_id
+ expect(new_build.partition_id).not_to eq pipeline.partition_id
+
+ described_class.upsert_from_build!(build)
+ described_class.upsert_from_build!(new_build)
+
+ expect(build.reload.queuing_entry.partition_id).to eq pipeline.partition_id
+ expect(new_build.reload.queuing_entry.partition_id).to eq ci_testing_partition_id
+ end
+ end
+
it_behaves_like 'cleanup by a loose foreign key' do
let!(:parent) { create(:namespace) }
let!(:model) { create(:ci_pending_build, namespace: parent) }
diff --git a/spec/models/ci/running_build_spec.rb b/spec/models/ci/running_build_spec.rb
index d2f74494308..84ef946980f 100644
--- a/spec/models/ci/running_build_spec.rb
+++ b/spec/models/ci/running_build_spec.rb
@@ -50,6 +50,28 @@ RSpec.describe Ci::RunningBuild do
end
end
+ describe 'partitioning', :ci_partitionable do
+ include Ci::PartitioningHelpers
+
+ before do
+ stub_current_partition_id
+ end
+
+ let(:new_pipeline ) { create(:ci_pipeline, project: pipeline.project) }
+ let(:new_build) { create(:ci_build, :running, pipeline: new_pipeline, runner: runner) }
+
+ it 'assigns the same partition id as the one that build has', :aggregate_failures do
+ expect(new_build.partition_id).to eq ci_testing_partition_id
+ expect(new_build.partition_id).not_to eq pipeline.partition_id
+
+ described_class.upsert_shared_runner_build!(build)
+ described_class.upsert_shared_runner_build!(new_build)
+
+ expect(build.reload.runtime_metadata.partition_id).to eq pipeline.partition_id
+ expect(new_build.reload.runtime_metadata.partition_id).to eq ci_testing_partition_id
+ end
+ end
+
it_behaves_like 'cleanup by a loose foreign key' do
let!(:parent) { create(:project) }
let!(:model) { create(:ci_running_build, project: parent) }
diff --git a/spec/models/group_deploy_key_spec.rb b/spec/models/group_deploy_key_spec.rb
index dfb4fee593f..c1fd88ad748 100644
--- a/spec/models/group_deploy_key_spec.rb
+++ b/spec/models/group_deploy_key_spec.rb
@@ -30,6 +30,12 @@ RSpec.describe GroupDeployKey do
end
end
+ describe '.defined_enums' do
+ it 'excludes the inherited enum' do
+ expect(described_class.defined_enums).to eq({})
+ end
+ end
+
describe '#can_be_edited_for' do
let_it_be(:user) { create(:user) }
diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb
index 47c0fbdb106..d2a8f89041e 100644
--- a/spec/models/hooks/active_hook_filter_spec.rb
+++ b/spec/models/hooks/active_hook_filter_spec.rb
@@ -85,23 +85,5 @@ RSpec.describe ActiveHookFilter do
it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/feature1' })).to be true }
end
end
-
- context 'when feature flag is disabled' do
- before do
- stub_feature_flags(enhanced_webhook_support_regex: false)
- end
-
- let(:hook) do
- build(
- :project_hook,
- push_events: true,
- push_events_branch_filter: '(master)',
- branch_filter_strategy: 'regex'
- )
- end
-
- it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/master' })).to be false }
- it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/(master)' })).to be true }
- end
end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index b98c0e8eae0..92f4d6d8531 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe Key, :mailer do
+ it_behaves_like 'having unique enum values'
+
describe "Associations" do
it { is_expected.to belong_to(:user) }
end
@@ -216,6 +218,20 @@ RSpec.describe Key, :mailer do
end
end
end
+
+ context 'usage type scopes' do
+ let_it_be(:auth_key) { create(:key, usage_type: :auth) }
+ let_it_be(:auth_and_signing_key) { create(:key, usage_type: :auth_and_signing) }
+ let_it_be(:signing_key) { create(:key, usage_type: :signing) }
+
+ it 'auth scope returns auth and auth_and_signing keys' do
+ expect(described_class.auth).to match_array([auth_key, auth_and_signing_key])
+ end
+
+ it 'signing scope returns signing and auth_and_signing keys' do
+ expect(described_class.signing).to match_array([signing_key, auth_and_signing_key])
+ end
+ end
end
context 'validation of uniqueness (based on fingerprint uniqueness)' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index fdeb98f52ae..43541250904 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -3090,6 +3090,14 @@ RSpec.describe User do
expect(described_class.find_by_ssh_key_id(-1)).to be_nil
end
end
+
+ it 'does not return a signing-only key', :aggregate_failures do
+ signing_key = create(:key, usage_type: :signing, user: user)
+ auth_and_signing_key = create(:key, usage_type: :auth_and_signing, user: user)
+
+ expect(described_class.find_by_ssh_key_id(signing_key.id)).to be_nil
+ expect(described_class.find_by_ssh_key_id(auth_and_signing_key.id)).to eq(user)
+ end
end
shared_examples "find user by login" do
diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb
index 32cacfc713c..5ad56d43f88 100644
--- a/spec/requests/api/internal/base_spec.rb
+++ b/spec/requests/api/internal/base_spec.rb
@@ -325,6 +325,28 @@ RSpec.describe API::Internal::Base do
expect(json_response['name']).to eq(user.name)
end
+ context 'when signing key is passed' do
+ it 'does not authenticate user' do
+ key.signing!
+
+ get(api("/internal/discover"), params: { key_id: key.id }, headers: gitlab_shell_internal_api_request_header)
+
+ expect(json_response).to be_nil
+ end
+ end
+
+ context 'when auth-only key is passed' do
+ it 'authenticates user' do
+ key.auth!
+
+ get(api("/internal/discover"), params: { key_id: key.id }, headers: gitlab_shell_internal_api_request_header)
+
+ expect(response).to have_gitlab_http_status(:ok)
+
+ expect(json_response['name']).to eq(user.name)
+ end
+ end
+
it "finds a user by username" do
get(api("/internal/discover"), params: { username: user.username }, headers: gitlab_shell_internal_api_request_header)
@@ -360,6 +382,30 @@ RSpec.describe API::Internal::Base do
expect(json_response['key'].split[1]).to eq(key.key.split[1])
end
+ context 'when signing key is passed' do
+ it 'does not return the key' do
+ key.signing!
+
+ get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header)
+
+ expect(response).to have_gitlab_http_status(:not_found)
+
+ expect(json_response['id']).to be_nil
+ end
+ end
+
+ context 'when auth-only key is passed' do
+ it 'authenticates user' do
+ key.auth!
+
+ get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header)
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['id']).to eq(key.id)
+ expect(json_response['key'].split[1]).to eq(key.key.split[1])
+ end
+ end
+
it 'exposes the comment of the key as a simple identifier of username + hostname' do
get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header)
diff --git a/spec/services/users/keys_count_service_spec.rb b/spec/services/users/keys_count_service_spec.rb
index aff267cce5e..607d2946b2c 100644
--- a/spec/services/users/keys_count_service_spec.rb
+++ b/spec/services/users/keys_count_service_spec.rb
@@ -17,6 +17,12 @@ RSpec.describe Users::KeysCountService, :use_clean_rails_memory_store_caching do
it 'returns the number of SSH keys as an Integer' do
expect(subject.count).to eq(1)
end
+
+ it 'does not count signing keys' do
+ create(:key, usage_type: :signing, user: user)
+
+ expect(subject.count).to eq(1)
+ end
end
describe '#uncached_count' do
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index e083bb06241..943105a57c4 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -457,6 +457,13 @@ RSpec.configure do |config|
config.before(:each, :js) do
allow_any_instance_of(VersionCheck).to receive(:response).and_return({ "severity" => "success" })
end
+
+ # Add warning for example missing feature_category
+ config.before do |example|
+ if example.metadata[:feature_category].blank? && !ENV['CI']
+ warn "Missing metadata feature_category: #{example.location} See https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#feature-category-metadata"
+ end
+ end
end
ActiveRecord::Migration.maintain_test_schema!
diff --git a/spec/support/helpers/ci/partitioning_helpers.rb b/spec/support/helpers/ci/partitioning_helpers.rb
new file mode 100644
index 00000000000..110199a3147
--- /dev/null
+++ b/spec/support/helpers/ci/partitioning_helpers.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Ci
+ module PartitioningHelpers
+ def stub_current_partition_id(id = Ci::PartitioningTesting::PartitionIdentifiers.ci_testing_partition_id)
+ allow(::Ci::Pipeline)
+ .to receive(:current_partition_value)
+ .and_return(id)
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb
index 52a9738fb51..195859eac70 100644
--- a/spec/tasks/gitlab/shell_rake_spec.rb
+++ b/spec/tasks/gitlab/shell_rake_spec.rb
@@ -22,4 +22,23 @@ RSpec.describe 'gitlab:shell rake tasks', :silence_stdout do
run_rake_task('gitlab:shell:install')
end
end
+
+ describe 'setup task' do
+ it 'writes authorized keys into the file' do
+ allow(Gitlab::CurrentSettings).to receive(:authorized_keys_enabled?).and_return(true)
+ stub_env('force', 'yes')
+
+ auth_key = create(:key)
+ auth_and_signing_key = create(:key, usage_type: :auth_and_signing)
+ create(:key, usage_type: :signing)
+
+ expect_next_instance_of(Gitlab::AuthorizedKeys) do |instance|
+ expect(instance).to receive(:batch_add_keys).once do |keys|
+ expect(keys).to match_array([auth_key, auth_and_signing_key])
+ end
+ end
+
+ run_rake_task('gitlab:shell:setup')
+ end
+ end
end
diff --git a/spec/views/profiles/keys/_form.html.haml_spec.rb b/spec/views/profiles/keys/_form.html.haml_spec.rb
index 3c61afb21c5..2a1bb5334b6 100644
--- a/spec/views/profiles/keys/_form.html.haml_spec.rb
+++ b/spec/views/profiles/keys/_form.html.haml_spec.rb
@@ -32,6 +32,11 @@ RSpec.describe 'profiles/keys/_form.html.haml' do
expect(rendered).to have_text('Key titles are publicly visible.')
end
+ it 'has the usage type field', :aggregate_failures do
+ expect(page).to have_select _('Usage type'),
+ selected: 'Authentication & Signing', options: ['Authentication & Signing', 'Authentication', 'Signing']
+ end
+
it 'has the expires at field', :aggregate_failures do
expect(rendered).to have_field('Expiration date', type: 'text')
expect(page.find_field('Expiration date')['min']).to eq(l(1.day.from_now, format: "%Y-%m-%d"))
@@ -47,4 +52,17 @@ RSpec.describe 'profiles/keys/_form.html.haml' do
expect(rendered).to have_button('Add key')
end
end
+
+ context 'when ssh_key_usage_types is disabled' do
+ before do
+ stub_feature_flags(ssh_key_usage_types: false)
+ end
+
+ it 'has the usage type field', :aggregate_failures do
+ render
+
+ expect(rendered).not_to have_field('Usage type', type: 'text')
+ expect(rendered).not_to have_text('Authentication & Signing')
+ end
+ end
end
diff --git a/spec/views/profiles/keys/_key.html.haml_spec.rb b/spec/views/profiles/keys/_key.html.haml_spec.rb
index 1040541332d..821e7ea794d 100644
--- a/spec/views/profiles/keys/_key.html.haml_spec.rb
+++ b/spec/views/profiles/keys/_key.html.haml_spec.rb
@@ -30,6 +30,38 @@ RSpec.describe 'profiles/keys/_key.html.haml' do
expect(response).to render_template(partial: 'shared/ssh_keys/_key_delete')
end
+ context 'displays the usage type' do
+ where(:usage_type, :usage_type_text) do
+ [
+ [:auth, 'Authentication'],
+ [:auth_and_signing, 'Authentication & Signing'],
+ [:signing, 'Signing']
+ ]
+ end
+
+ with_them do
+ let(:key) { create(:key, user: user, usage_type: usage_type) }
+
+ it 'renders usage type text' do
+ render
+
+ expect(rendered).to have_text(usage_type_text)
+ end
+
+ context 'when ssh_key_usage_types is disabled' do
+ before do
+ stub_feature_flags(ssh_key_usage_types: false)
+ end
+
+ it 'does not render usage type text' do
+ render
+
+ expect(rendered).not_to have_text(usage_type_text)
+ end
+ end
+ end
+ end
+
context 'when the key has not been used' do
let_it_be(:key) do
create(:personal_key,
diff --git a/spec/views/profiles/keys/_key_details.html.haml_spec.rb b/spec/views/profiles/keys/_key_details.html.haml_spec.rb
new file mode 100644
index 00000000000..acb22b5657e
--- /dev/null
+++ b/spec/views/profiles/keys/_key_details.html.haml_spec.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe 'profiles/keys/_key_details.html.haml' do
+ let_it_be(:user) { create(:user) }
+
+ before do
+ assign(:key, key)
+ allow(view).to receive(:is_admin).and_return(false)
+ end
+
+ describe 'displays the usage type' do
+ where(:usage_type, :usage_type_text) do
+ [
+ [:auth, 'Authentication'],
+ [:auth_and_signing, 'Authentication & Signing'],
+ [:signing, 'Signing']
+ ]
+ end
+
+ with_them do
+ let(:key) { create(:key, user: user, usage_type: usage_type) }
+
+ it 'renders usage type text' do
+ render
+
+ expect(rendered).to have_text(usage_type_text)
+ end
+
+ context 'when ssh_key_usage_types is disabled' do
+ before do
+ stub_feature_flags(ssh_key_usage_types: false)
+ end
+
+ it 'does not render usage type text' do
+ render
+
+ expect(rendered).not_to have_text(usage_type_text)
+ end
+ end
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index e6e7d5cb9ca..9930c5671f7 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1108,15 +1108,15 @@
stylelint-declaration-strict-value "1.8.0"
stylelint-scss "4.2.0"
-"@gitlab/svgs@3.8.0":
- version "3.8.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.8.0.tgz#bc7fa51e345e26cff56fdff629ea439adfa1e0cb"
- integrity sha512-DUWeG2Vx+1ntZ/1GT6S36ZOtXvM5Wm02MtDRrQS4GuOX4rkTeG9aoutSJuwQ2h9BNtxl0U/jkf5GVBxacj18XA==
-
-"@gitlab/ui@49.11.1":
- version "49.11.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.11.1.tgz#ce18f23ac4f48159e8f57f8dedef2f05890a97f2"
- integrity sha512-iFhhi03Vrz+wxxUzwVmaaP1s0qeJtACCEpj7xESDVbevjDEqf1muMz/PTH10NslrVbf1VchqNwSC+Ww6C7yAKQ==
+"@gitlab/svgs@3.11.0":
+ version "3.11.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.11.0.tgz#91e8e25583cddef48c0c79175203e5b0a4eaa519"
+ integrity sha512-1cJu1WXPoOHfGgv5fT3nmA9cgAQ3U1Fm/oMSVYUgBxU35R0I8W704GMLsIZwBuQ/S/Ow7WLwIkoOhLb/spNKPg==
+
+"@gitlab/ui@49.11.2":
+ version "49.11.2"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.11.2.tgz#290bba7a3d4682365ad81747cf54a2f9927526c1"
+ integrity sha512-qu5qcl+4niYBCPIZS9ZU0i1h/IGL4ZOp4hDsEAIUFGJg9Sp0TBmwdjwKJQbvnexDS3xs1eSBzi+kQ57H+c9wQQ==
dependencies:
"@popperjs/core" "^2.11.2"
bootstrap-vue "2.20.1"