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/CODEOWNERS4
-rw-r--r--.gitlab/ci/package-and-test/main.gitlab-ci.yml10
-rw-r--r--app/assets/javascripts/search/sidebar/components/scope_navigation.vue28
-rw-r--r--app/assets/javascripts/search/sidebar/constants/index.js2
-rw-r--r--app/mailers/emails/profile.rb3
-rw-r--r--app/models/analytics/cycle_analytics/aggregation.rb9
-rw-r--r--app/services/notification_service.rb4
-rw-r--r--app/services/personal_access_tokens/revoke_service.rb24
-rw-r--r--app/validators/json_schemas/ci_secure_file_metadata.json4
-rw-r--r--app/views/notify/access_token_revoked_email.html.haml2
-rw-r--r--app/views/notify/access_token_revoked_email.text.erb4
-rw-r--r--config/feature_flags/development/gitlab_pat_auto_revocation.yml8
-rw-r--r--doc/.vale/gitlab/Substitutions.yml1
-rw-r--r--doc/architecture/blueprints/ci_pipeline_components/index.md186
-rw-r--r--doc/ci/environments/protected_environments.md2
-rw-r--r--doc/subscriptions/gitlab_com/index.md2
-rw-r--r--doc/topics/autodevops/index.md2
-rw-r--r--doc/user/application_security/secret_detection/post_processing.md9
-rw-r--r--doc/user/group/manage.md2
-rw-r--r--doc/user/project/import/github.md35
-rw-r--r--doc/user/project/integrations/webhooks.md35
-rw-r--r--doc/user/project/merge_requests/status_checks.md2
-rw-r--r--doc/user/project/repository/branches/default.md2
-rw-r--r--doc/user/project/settings/index.md4
-rw-r--r--locale/gitlab.pot3
-rw-r--r--qa/qa/ce/strategy.rb7
-rw-r--r--qa/qa/page/profile/two_factor_auth.rb2
-rw-r--r--qa/qa/resource/bulk_import_group.rb8
-rw-r--r--qa/qa/runtime/env.rb4
-rw-r--r--qa/qa/scenario/test/integration/import.rb13
-rw-r--r--qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb72
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb77
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb40
-rw-r--r--qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb69
-rw-r--r--spec/frontend/search/sidebar/components/scope_navigation_spec.js25
-rw-r--r--spec/mailers/emails/profile_spec.rb32
-rw-r--r--spec/models/ci/secure_file_spec.rb33
-rw-r--r--spec/services/notification_service_spec.rb10
-rw-r--r--spec/services/personal_access_tokens/revoke_service_spec.rb44
39 files changed, 594 insertions, 229 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 2bb47c77ba5..4f8d8401e92 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -922,6 +922,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/free_user_limit.md @phillipwells
/doc/user/group/ @lciutacu
/doc/user/group/clusters/ @phillipwells
+/doc/user/group/compliance_frameworks.md @eread
/doc/user/group/contribution_analytics/ @lciutacu
/doc/user/group/custom_project_templates.md @eread
/doc/user/group/devops_adoption/ @lciutacu
@@ -931,6 +932,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/group/issues_analytics/ @msedlakjakubowski
/doc/user/group/iterations/ @msedlakjakubowski
/doc/user/group/planning_hierarchy/ @msedlakjakubowski
+/doc/user/group/reporting/ @phillipwells
/doc/user/group/repositories_analytics/ @marcel.amirault
/doc/user/group/roadmap/ @msedlakjakubowski
/doc/user/group/saml_sso/ @jglassman1
@@ -1017,6 +1019,7 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/project/requirements/ @msedlakjakubowski
/doc/user/project/service_desk.md @msedlakjakubowski
/doc/user/project/settings/import_export.md @eread
+/doc/user/project/settings/import_export_troubleshooting.md @eread
/doc/user/project/settings/index.md @lciutacu
/doc/user/project/settings/project_access_tokens.md @jglassman1
/doc/user/project/time_tracking.md @msedlakjakubowski
@@ -1026,7 +1029,6 @@ lib/gitlab/checks/** @proglottis @toon @zj-gitlab
/doc/user/public_access.md @lciutacu
/doc/user/reserved_names.md @lciutacu
/doc/user/search/ @ashrafkhamis
-/doc/user/search/global_search/ @ashrafkhamis
/doc/user/shortcuts.md @ashrafkhamis
/doc/user/snippets.md @ashrafkhamis
/doc/user/ssh.md @jglassman1
diff --git a/.gitlab/ci/package-and-test/main.gitlab-ci.yml b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
index f0bf79f009d..a434518537a 100644
--- a/.gitlab/ci/package-and-test/main.gitlab-ci.yml
+++ b/.gitlab/ci/package-and-test/main.gitlab-ci.yml
@@ -573,6 +573,16 @@ ee:registry-object-storage-tls:
GITLAB_TLS_CERTIFICATE: $QA_GITLAB_TLS_CERTIFICATE
GITLAB_QA_OPTS: --omnibus-config registry_object_storage
+ee:importers:
+ extends: .qa
+ variables:
+ QA_SCENARIO: Test::Integration::Import
+ QA_ALLOW_LOCAL_REQUESTS: "true"
+ rules:
+ - !reference [.rules:test:qa, rules]
+ - if: $QA_SUITES =~ /Test::Integration::Import/
+ - !reference [.rules:test:manual, rules]
+
# ==========================================
# Post test stage
# ==========================================
diff --git a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
index f5e1525090e..ff23767f364 100644
--- a/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
+++ b/app/assets/javascripts/search/sidebar/components/scope_navigation.vue
@@ -3,7 +3,11 @@ import { GlNav, GlNavItem } from '@gitlab/ui';
import { mapActions, mapState } from 'vuex';
import { formatNumber } from '~/locale';
import Tracking from '~/tracking';
-import { NAV_LINK_DEFAULT_CLASSES, NUMBER_FORMATING_OPTIONS } from '../constants';
+import {
+ NAV_LINK_DEFAULT_CLASSES,
+ NUMBER_FORMATING_OPTIONS,
+ NAV_LINK_COUNT_DEFAULT_CLASSES,
+} from '../constants';
export default {
name: 'ScopeNavigation',
@@ -20,9 +24,6 @@ export default {
},
methods: {
...mapActions(['fetchSidebarCount']),
- activeClasses(currentScope) {
- return currentScope === this.urlQuery.scope ? 'gl-font-weight-bold' : '';
- },
showFormatedCount(count) {
if (!count) {
return '0';
@@ -33,14 +34,21 @@ export default {
handleClick(scope) {
this.track('click_menu_item', { label: `vertical_navigation_${scope}` });
},
- linkClasses(scope) {
+ linkClasses(isHighlighted) {
+ return [...this.$options.NAV_LINK_DEFAULT_CLASSES, { 'gl-font-weight-bold': isHighlighted }];
+ },
+ countClasses(isHighlighted) {
return [
- { 'gl-font-weight-bold': scope === this.urlQuery.scope },
- ...this.$options.NAV_LINK_DEFAULT_CLASSES,
+ ...this.$options.NAV_LINK_COUNT_DEFAULT_CLASSES,
+ isHighlighted ? 'gl-text-gray-900' : 'gl-text-gray-500',
];
},
+ isActive(scope, index) {
+ return this.urlQuery.scope ? this.urlQuery.scope === scope : index === 0;
+ },
},
NAV_LINK_DEFAULT_CLASSES,
+ NAV_LINK_COUNT_DEFAULT_CLASSES,
};
</script>
@@ -50,13 +58,13 @@ export default {
<gl-nav-item
v-for="(item, scope, index) in navigation"
:key="scope"
- :link-classes="linkClasses(scope)"
+ :link-classes="linkClasses(isActive(scope, index))"
class="gl-mb-1"
:href="item.link"
- :active="urlQuery.scope ? urlQuery.scope === scope : index === 0"
+ :active="isActive(scope, index)"
@click="handleClick(scope)"
><span>{{ item.label }}</span
- ><span v-if="item.count" class="gl-font-sm gl-font-weight-normal">
+ ><span v-if="item.count" :class="countClasses(isActive(scope, index))">
{{ showFormatedCount(item.count) }}
</span>
</gl-nav-item>
diff --git a/app/assets/javascripts/search/sidebar/constants/index.js b/app/assets/javascripts/search/sidebar/constants/index.js
index 3621138afe4..a9c031f91a4 100644
--- a/app/assets/javascripts/search/sidebar/constants/index.js
+++ b/app/assets/javascripts/search/sidebar/constants/index.js
@@ -9,3 +9,5 @@ export const NAV_LINK_DEFAULT_CLASSES = [
'gl-justify-content-space-between',
'gl-text-gray-900',
];
+
+export const NAV_LINK_COUNT_DEFAULT_CLASSES = ['gl-font-sm', 'gl-font-weight-normal'];
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index 65ea90d0b5d..ede6007e0e2 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -94,12 +94,13 @@ module Emails
end
end
- def access_token_revoked_email(user, token_name)
+ def access_token_revoked_email(user, token_name, source = nil)
return unless user&.active?
@user = user
@token_name = token_name
@target_url = profile_personal_access_tokens_url
+ @source = source
Gitlab::I18n.with_locale(@user.preferred_language) do
mail_with_locale(to: @user.notification_email_or_default, subject: subject(_("A personal access token has been revoked")))
diff --git a/app/models/analytics/cycle_analytics/aggregation.rb b/app/models/analytics/cycle_analytics/aggregation.rb
index 2e58d64ae95..880b3a7e310 100644
--- a/app/models/analytics/cycle_analytics/aggregation.rb
+++ b/app/models/analytics/cycle_analytics/aggregation.rb
@@ -1,7 +1,6 @@
# frozen_string_literal: true
class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
- include IgnorableColumns
include FromUnion
belongs_to :group, optional: false
@@ -11,14 +10,6 @@ class Analytics::CycleAnalytics::Aggregation < ApplicationRecord
scope :priority_order, -> (column_to_sort = :last_incremental_run_at) { order(arel_table[column_to_sort].asc.nulls_first) }
scope :enabled, -> { where('enabled IS TRUE') }
- # These columns were added with wrong naming convention, the columns were never used.
- ignore_column :last_full_run_processed_records, remove_with: '15.1', remove_after: '2022-05-22'
- ignore_column :last_full_run_runtimes_in_seconds, remove_with: '15.1', remove_after: '2022-05-22'
- ignore_column :last_full_run_issues_updated_at, remove_with: '15.1', remove_after: '2022-05-22'
- ignore_column :last_full_run_mrs_updated_at, remove_with: '15.1', remove_after: '2022-05-22'
- ignore_column :last_full_run_issues_id, remove_with: '15.1', remove_after: '2022-05-22'
- ignore_column :last_full_run_merge_requests_id, remove_with: '15.1', remove_after: '2022-05-22'
-
def cursor_for(mode, model)
{
updated_at: self["last_#{mode}_#{model.table_name}_updated_at"],
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 660d9891e46..a2997d3226d 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -98,10 +98,10 @@ class NotificationService
end
# Notify the user when one of their personal access tokens is revoked
- def access_token_revoked(user, token_name)
+ def access_token_revoked(user, token_name, source = nil)
return unless user.can?(:receive_notifications)
- mailer.access_token_revoked_email(user, token_name).deliver_later
+ mailer.access_token_revoked_email(user, token_name, source).deliver_later
end
# Notify the user when at least one of their ssh key has expired today
diff --git a/app/services/personal_access_tokens/revoke_service.rb b/app/services/personal_access_tokens/revoke_service.rb
index 5371b6c91ef..bb5edc27340 100644
--- a/app/services/personal_access_tokens/revoke_service.rb
+++ b/app/services/personal_access_tokens/revoke_service.rb
@@ -4,10 +4,13 @@ module PersonalAccessTokens
class RevokeService < BaseService
attr_reader :token, :current_user, :group
- def initialize(current_user = nil, token: nil, group: nil)
+ VALID_SOURCES = %w[secret_detection].freeze
+
+ def initialize(current_user = nil, token: nil, group: nil, source: nil)
@current_user = current_user
@token = token
@group = group
+ @source = source
end
def execute
@@ -15,7 +18,7 @@ module PersonalAccessTokens
if token.revoke!
log_event
- notification_service.access_token_revoked(token.user, token.name)
+ notification_service.access_token_revoked(token.user, token.name, @source)
ServiceResponse.success(message: success_message)
else
ServiceResponse.error(message: error_message)
@@ -33,11 +36,24 @@ module PersonalAccessTokens
end
def revocation_permitted?
- Ability.allowed?(current_user, :revoke_token, token)
+ if current_user
+ Ability.allowed?(current_user, :revoke_token, token)
+ else
+ source && VALID_SOURCES.include?(source)
+ end
+ end
+
+ def source
+ current_user&.username || @source
end
def log_event
- Gitlab::AppLogger.info("PAT REVOCATION: revoked_by: '#{current_user.username}', revoked_for: '#{token.user.username}', token_id: '#{token.id}'")
+ Gitlab::AppLogger.info(
+ class: self.class.name,
+ message: "PAT Revoked",
+ revoked_by: source,
+ revoked_for: token.user.username,
+ token_id: token.id)
end
end
end
diff --git a/app/validators/json_schemas/ci_secure_file_metadata.json b/app/validators/json_schemas/ci_secure_file_metadata.json
index 46a7ff60b8f..66e778d6026 100644
--- a/app/validators/json_schemas/ci_secure_file_metadata.json
+++ b/app/validators/json_schemas/ci_secure_file_metadata.json
@@ -4,10 +4,10 @@
"properties": {
"id": { "type": "string" },
"team_name": { "type": "string" },
- "team_id": { "type": "string" },
+ "team_id": { "type": "array" },
"app_name": { "type": "string" },
"app_id": { "type": "string" },
- "app_id_prefix": { "type": "string" },
+ "app_id_prefix": { "type": "array" },
"xcode_managed": { "type": "boolean" },
"entitlements": { "type": "object" },
"devices": { "type": "array" },
diff --git a/app/views/notify/access_token_revoked_email.html.haml b/app/views/notify/access_token_revoked_email.html.haml
index 4d9b9e14d14..ecd2b3e84b2 100644
--- a/app/views/notify/access_token_revoked_email.html.haml
+++ b/app/views/notify/access_token_revoked_email.html.haml
@@ -2,6 +2,8 @@
= _('Hi %{username}!') % { username: sanitize_name(@user.name) }
%p
= html_escape(_('A personal access token, named %{code_start}%{token_name}%{code_end}, has been revoked.')) % { code_start: '<code>'.html_safe, token_name: @token_name, code_end: '</code>'.html_safe }
+- if @source == 'secret_detection'
+ = _('We found your token in a public project and have automatically revoked it to protect your account.')
%p
- pat_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url }
= html_escape(_('You can check your tokens or create a new one in your %{pat_link_start}personal access tokens settings%{pat_link_end}.')) % { pat_link_start: pat_link_start, pat_link_end: '</a>'.html_safe }
diff --git a/app/views/notify/access_token_revoked_email.text.erb b/app/views/notify/access_token_revoked_email.text.erb
index 17dd628d76c..a0623f96488 100644
--- a/app/views/notify/access_token_revoked_email.text.erb
+++ b/app/views/notify/access_token_revoked_email.text.erb
@@ -1,5 +1,9 @@
<%= _('Hi %{username}!') % { username: sanitize_name(@user.name) } %>
<%= _('A personal access token, named %{token_name}, has been revoked.') % { token_name: @token_name } %>
+<% if @source == 'secret_detection' %>
+
+<%= _('We found your token in a public project and have automatically revoked it to protect your account.') %>
+<% end %>
<%= _('You can check your tokens or create a new one in your personal access tokens settings %{pat_link}.') % { pat_link: @target_url } %>
diff --git a/config/feature_flags/development/gitlab_pat_auto_revocation.yml b/config/feature_flags/development/gitlab_pat_auto_revocation.yml
new file mode 100644
index 00000000000..3bbbadac23f
--- /dev/null
+++ b/config/feature_flags/development/gitlab_pat_auto_revocation.yml
@@ -0,0 +1,8 @@
+---
+name: gitlab_pat_auto_revocation
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103713
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/382610
+milestone: '15.6'
+type: development
+group: group::static analysis
+default_enabled: false
diff --git a/doc/.vale/gitlab/Substitutions.yml b/doc/.vale/gitlab/Substitutions.yml
index 92791486491..1205795233f 100644
--- a/doc/.vale/gitlab/Substitutions.yml
+++ b/doc/.vale/gitlab/Substitutions.yml
@@ -60,3 +60,4 @@ swap:
reporter access: the Reporter role
reporter permission: the Reporter role
reporter permissions: the Reporter role
+ at least the Owner role: the Owner role
diff --git a/doc/architecture/blueprints/ci_pipeline_components/index.md b/doc/architecture/blueprints/ci_pipeline_components/index.md
index a3c72227f3e..e8fbc15a376 100644
--- a/doc/architecture/blueprints/ci_pipeline_components/index.md
+++ b/doc/architecture/blueprints/ci_pipeline_components/index.md
@@ -110,7 +110,8 @@ identifying abstract concepts and are subject to changes as we refine the design
## Definition of pipeline component
-A pipeline component is a reusable single-purpose building block that abstracts away a single pipeline configuration unit. Components are used to compose a part or entire pipeline configuration.
+A pipeline component is a reusable single-purpose building block that abstracts away a single pipeline configuration unit.
+Components are used to compose a part or entire pipeline configuration.
It can optionally take input parameters and set output data to be adaptable and reusable in different pipeline contexts,
while encapsulating and isolating implementation details.
@@ -133,27 +134,145 @@ For best experience with any systems made of components it's fundamental that co
The version identifies the exact interface and behavior of the component.
- **Resolvable**: when a component depends on another component, this dependency must be explicit and trackable.
-## Proposal
+## Structure of a component
-Prerequisites to create a component:
+A pipeline component is identified by the path to a repository or directory that defines it
+and a specific version: `<component-path>@<version>`.
-- Create a project. Description and avatar are highly recommended to improve discoverability.
-- Add a `README.md` in the top level directory that documents the component.
- What it does, how to use it, how to contribute, etc.
- This file is mandatory.
-- Add a `.gitlab-ci.yml` in the top level directory to test that the components works as expected.
- This file is highly recommended.
+For example: `gitlab-org/dast@1.0`.
-Characteristics of a component:
+### The component path
-- It must have a **name** to be referenced to and **description** for extra details.
-- It must specify its **type** which defines how it can be used (raw configuration to be `include`d, child pipeline workflow, job step).
-- It must define its **content** based on the type.
-- It must specify **input parameters** that it accepts. Components should depend on input parameters for dynamic values and not environment variables.
-- It can optionally define **output data** that it returns.
-- Its YAML specification should be **validated statically** (for example: using JSON schema validators).
-- It should be possible to use specific **versions** of a component by referencing official releases and SHA.
-- It should be possible to use components defined locally in the same repository.
+A component path must contain at least the metadata YAML and optionally a related `README.md` documentation file.
+
+The component path can be:
+
+- A path to a project: `gitlab-org/dast`. In this case the 2 files are defined in the root directory of the repository.
+- A path to a project subdirectory: `gitlab-org/dast/api-scan`. In this case the 2 files are defined in the `api-scan` directory.
+- A path to a local directory: `/path/to/component`. This path must contain the metadata YAML that defines the component.
+ The path must start with `/` to indicate a full path in the repository.
+
+The metadata YAML file follows the filename convention `gitlab-<component-type>.yml` where component type is one of:
+
+| Component type | Context |
+| -------------- | ------- |
+| `template` | For components used under `include:` keyword |
+| `step` | For components used under `steps:` keyword |
+| `workflow` | For components used under `trigger:` keyword |
+
+Based on the context where the component is used we fetch the correct YAML file.
+For example, if we are including a component `gitlab-org/dast@1.0` we expect a YAML file named `gitlab-template.yml` in the
+top level directory of `gitlab-org/dast` repository.
+
+A `gitlab-<component-type>.yml` file:
+
+- Must have a **name** to be referenced to and **description** for extra details.
+- Must specify its **type** in the filename, which defines how it can be used (raw configuration to be `include`d, child pipeline workflow, job step).
+- Must define its **content** based on the type.
+- Must specify **input parameters** that it accepts. Components should depend on input parameters for dynamic values and not environment variables.
+- Can optionally define **output data** that it returns.
+- Should be **validated statically** (for example: using JSON schema validators).
+
+Components that are released in the catalog must have a `README.md` file in the same directory as the
+metadata YAML file. The `README.md` represents the documentation for the specific component, hence it's recommended
+even when not releasing versions in the catalog.
+
+### The component version
+
+The version of the component can be (in order of highest priority first):
+
+1. A commit SHA - For example: `gitlab-org/dast@e3262fdd0914fa823210cdb79a8c421e2cef79d8`
+1. A released tag - For example: `gitlab-org/dast@1.0`
+1. A special moving target version that points to the most recent released tag - For example: `gitlab-org/dast@~latest`
+1. An unreleased tag - For example: `gitlab-org/dast@rc-1.0`
+1. A branch name - For example: `gitlab-org/dast@master`
+
+If a tag and branch exist with the same name, the tag takes precedence over the branch.
+Similarly, if a tag is named `e3262fdd0914fa823210cdb79a8c421e2cef79d8`, a commit SHA (if exists)
+takes precedence over the tag.
+
+As we want to be able to reference any revisions (even those not released), a component must be defined in a Git repository.
+
+NOTE:
+When referencing a component by local path (for example `./path/to/component`), its version is implicit and matches
+the commit SHA of the current pipeline context.
+
+## Components project
+
+A components project is a GitLab project/repository that exclusively hosts one or more pipeline components.
+
+For components projects it's highly recommended to set an appropriate avatar and project description
+to improve discoverability in the catalog.
+
+### Structure of a components project
+
+A project can host one or more components depending on whether the author wants to define a single component
+per project or include multiple cohesive components under the same project.
+
+Let's imagine we are developing a component that runs RSpec tests for a Rails app. We create a component project
+called `myorg/rails-rspec`.
+
+The following directory structure would support 1 component per project:
+
+```plaintext
+.
+├── gitlab-<type>.yml
+├── README.md
+└── .gitlab-ci.yml
+```
+
+The `.gitlab-ci.yml` is recommended for the project to ensure changes are verified accordingly.
+
+The component is now identified by the path `myorg/rails-rspec`. In other words, this means that
+the `gitlab-<type>.yml` and `README.md` are located in the root directory of the repository.
+
+The following directory structure would support multiple components per project:
+
+```plaintext
+.
+├── .gitlab-ci.yml
+├── unit/
+│ ├── gitlab-workflow.yml
+│ └── README.md
+├── integration/
+│ ├── gitlab-workflow.yml
+│ └── README.md
+└── feature/
+ ├── gitlab-workflow.yml
+ └── README.md
+```
+
+In this example we are defining multiple test profiles that are executed with RSpec.
+The user could choose to use one or more of these.
+
+Each of these components are identified by their path `myorg/rails-rspec/unit`, `myorg/rails-rspec/integration`
+and `myorg/rails-rspec/feature`.
+
+This directory structure could also support both strategies:
+
+```plaintext
+.
+├── gitlab-template.yml # myorg/rails-rspec
+├── README.md
+├── .gitlab-ci.yml
+├── unit/
+│ ├── gitlab-workflow.yml # myorg/rails-rspec/unit
+│ └── README.md
+├── integration/
+│ ├── gitlab-workflow.yml # myorg/rails-rspec/integration
+│ └── README.md
+└── feature/
+ ├── gitlab-workflow.yml # myorg/rails-rspec/feature
+ └── README.md
+```
+
+With the above structure we could have a top-level component that can be used as the
+default component. For example, `myorg/rails-rspec` could run all the test profiles together.
+However, more specific test profiles could be used separately (for example `myorg/rails-rspec/integration`).
+
+NOTE:
+Any nesting more than 1 level is initially not permitted.
+This limitation encourages cohesion at project level and keeps complexity low.
## Limits
@@ -188,3 +307,34 @@ Some limits we could consider adding:
- Allow self-managed administrators to populate their self-managed catalog by importing/updating
components from GitLab.com or from repository exports.
- Iterate on feedback.
+
+## Who
+
+Proposal:
+
+<!-- vale gitlab.Spelling = NO -->
+
+| Role | Who
+|------------------------------|-------------------------|
+| Author | Fabio Pitino |
+| Engineering Leader | ? |
+| Product Manager | Dov Hershkovitch |
+| Architecture Evolution Coach | Kamil Trzciński, Grzegorz Bizon |
+
+DRIs:
+
+| Role | Who
+|------------------------------|------------------------|
+| Leadership | ? |
+| Product | Dov Hershkovitch |
+| Engineering | Fabio Pitino |
+| UX | Kevin Comoli |
+
+Domain experts:
+
+| Area | Who
+|------------------------------|------------------------|
+| Verify / Pipeline authoring | Avielle Wolfe |
+| Verify / Pipeline authoring | Furkan Ayhan |
+
+<!-- vale gitlab.Spelling = YES -->
diff --git a/doc/ci/environments/protected_environments.md b/doc/ci/environments/protected_environments.md
index 5c120da32a0..356ebcc9125 100644
--- a/doc/ci/environments/protected_environments.md
+++ b/doc/ci/environments/protected_environments.md
@@ -200,7 +200,7 @@ To maximize the effectiveness of group-level protected environments,
[group-level memberships](../../user/group/index.md) must be correctly
configured:
-- Operators should be given at least the Owner role
+- Operators should be given the Owner role
for the top-level group. They can maintain CI/CD configurations for
the higher environments (such as production) in the group-level settings page,
which includes group-level protected environments,
diff --git a/doc/subscriptions/gitlab_com/index.md b/doc/subscriptions/gitlab_com/index.md
index 6f1bf0df044..49a03e7285d 100644
--- a/doc/subscriptions/gitlab_com/index.md
+++ b/doc/subscriptions/gitlab_com/index.md
@@ -335,7 +335,7 @@ locked. Projects can only be unlocked by purchasing more storage subscription un
Prerequisite:
-- You must have at least the Owner role.
+- You must have the Owner role.
You can purchase a storage subscription for your personal or group namespace.
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 73e87aa190f..d16229b525f 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -129,7 +129,7 @@ for the subgroups and projects where you don't want to use it.
Prerequisites:
-- You must have at least the Owner role for the group.
+- You must have the Owner role for the group.
To enable Auto DevOps for a group:
diff --git a/doc/user/application_security/secret_detection/post_processing.md b/doc/user/application_security/secret_detection/post_processing.md
index 8dbe459d4af..f1186e4ff53 100644
--- a/doc/user/application_security/secret_detection/post_processing.md
+++ b/doc/user/application_security/secret_detection/post_processing.md
@@ -6,7 +6,12 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Secret Detection post-processing and revocation **(FREE SAAS)**
-> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6.
+> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/4639) in GitLab 13.6.
+> - [Disabled by default for GitLab personal access tokens](https://gitlab.com/gitlab-org/gitlab/-/issues/371658) in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `gitlab_pat_auto_revocation`. Available to GitLab.com only.
+
+FLAG:
+By default, auto revocation of GitLab personal access tokens is not available. To opt-in on GitLab.com,
+please reach out to GitLab support.
GitLab supports running post-processing hooks after detecting a secret. These
hooks can perform actions, like notifying the cloud service that issued the secret.
@@ -16,7 +21,7 @@ The cloud provider can then confirm the credentials and take remediation actions
- Reissuing a secret.
- Notifying the creator of the secret.
-GitLab SaaS supports post-processing for Amazon Web Services (AWS).
+GitLab SaaS supports post-processing for [GitLab personal access tokens](../../profile/personal_access_tokens.md) and Amazon Web Services (AWS).
Post-processing workflows vary by supported cloud providers.
Post-processing is limited to a project's default branch. The epic
diff --git a/doc/user/group/manage.md b/doc/user/group/manage.md
index a63e6c6dd7f..163354fc37d 100644
--- a/doc/user/group/manage.md
+++ b/doc/user/group/manage.md
@@ -78,7 +78,7 @@ If you don't want to wait, you can remove a group immediately.
Prerequisites:
-- You must have at least the Owner role for a group.
+- You must have the Owner role for a group.
- You have [marked the group for deletion](#remove-a-group).
To immediately remove a group marked for deletion:
diff --git a/doc/user/project/import/github.md b/doc/user/project/import/github.md
index c0b13c76322..6b34908c20c 100644
--- a/doc/user/project/import/github.md
+++ b/doc/user/project/import/github.md
@@ -222,21 +222,26 @@ References to pull requests and issues are preserved. Each imported repository m
[visibility level is restricted](../../public_access.md#restrict-use-of-public-or-internal-projects), in which case it
defaults to the default project visibility.
-### Branch protection rules
-
-Supported GitHub branch protection rules are mapped to GitLab branch protection rules or project-wide GitLab settings when they are imported:
-
-- GitHub rule **Require conversation resolution before merging** for the project's default branch is mapped to the [**All threads must be resolved** GitLab setting](../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) in GitLab 15.5.
-- GitHub rule **Require a pull request before merging** is mapped to the **No one** option in the **Allowed to push** list of the branch protection rule. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370951) in GitLab 15.5.
-- GitHub rule **Require a pull request before merging - Require review from Code Owners** is mapped to the
- [**Code owner approval** branch protection rule](../protected_branches.md#require-code-owner-approval-on-a-protected-branch). Requires GitLab Premium or higher.
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/376683) in GitLab 15.6.
-- GitHub rule **Require signed commits** for the project's default branch is mapped to the **Reject unsigned commits** GitLab push rule. Requires GitLab Premium or higher.
- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) in GitLab 15.5.
-- GitHub rule **Allow force pushes - Everyone** is mapped to the [**Allowed to force push** branch protection rule](../protected_branches.md#allow-force-push-on-a-protected-branch). [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/370943) in GitLab 15.6.
-- GitHub rule **Allow force pushes - Specify who can force push** is proposed in issue [370945](https://gitlab.com/gitlab-org/gitlab/-/issues/370945).
-- Support for GitHub rule **Require status checks to pass before merging** was proposed in issue [370948](https://gitlab.com/gitlab-org/gitlab/-/issues/370948). However, this rule cannot be translated during project import into GitLab due to technical difficulties.
-You can still create [status checks](../merge_requests/status_checks.md) in GitLab yourself.
+### Branch protection rules and project settings
+
+When they are imported, supported GitHub branch protection rules are mapped to either:
+
+- GitLab branch protection rules.
+- Project-wide GitLab settings.
+
+| GitHub rule | GitLab rule | Introduced in |
+|:------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------|:--------------------------------------------------------------------|
+| **Require conversation resolution before merging** for the project's default branch | **All threads must be resolved** [project setting](../../discussions/index.md#prevent-merge-unless-all-threads-are-resolved) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/371110) |
+| **Require a pull request before merging** | **No one** option in the **Allowed to push** list of [branch protection settings](../protected_branches.md#configure-a-protected-branch) | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370951) |
+| **Require signed commits** for the project's default branch | **Reject unsigned commits** GitLab [push rule](../repository/push_rules.md#prevent-unintended-consequences) **(PREMIUM)** | [GitLab 15.5](https://gitlab.com/gitlab-org/gitlab/-/issues/370949) |
+| **Allow force pushes - Everyone** | **Allowed to force push** [branch protection setting](../protected_branches.md#allow-force-push-on-a-protected-branch) | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/370943) |
+| **Require a pull request before merging - Require review from Code Owners** | **Require approval from code owners** [branch protection setting](../protected_branches.md#require-code-owner-approval-on-a-protected-branch) **(PREMIUM)** | [GitLab 15.6](https://gitlab.com/gitlab-org/gitlab/-/issues/376683) |
+
+Mapping GitHub rule **Require status checks to pass before merging** to
+[external status checks](../merge_requests/status_checks.md) was considered in issue
+[370948](https://gitlab.com/gitlab-org/gitlab/-/issues/370948). However, this rule is not imported during project import
+into GitLab due to technical difficulties. You can still create [external status checks](../merge_requests/status_checks.md)
+manually.
## Alternative way to import notes and diff notes
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index ef6957ac2d8..399e9f32ae1 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -62,6 +62,41 @@ You can configure a webhook for a group or a project.
1. Optional. Clear the **Enable SSL verification** checkbox to disable [SSL verification](index.md#manage-ssl-verification).
1. Select **Add webhook**.
+## Mask sensitive portions of webhook URLs
+
+> Introduced in GitLab 15.6 [with a flag](../../../administration/feature_flags.md) named `webhook_form_mask_url`. Disabled by default.
+
+FLAG:
+On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../../../administration/feature_flags.md) named `webhook_form_mask_url`. On GitLab.com, this feature is not available.
+
+You can define and mask sensitive portions of webhook URLs and replace them
+with configured values any number of times when webhooks are executed.
+Sensitive portions do not get logged and are encrypted at rest in the database.
+
+To mask sensitive portions of the webhook URL:
+
+1. In your project or group, on the left sidebar, select **Settings > Webhooks**.
+1. In **URL**, enter the full webhook URL.
+1. Select **Mask portions of URL**.
+1. In **Sensitive portion of URL**, enter the portion you want to mask.
+1. In **How it looks in the UI**, enter the masking value.
+
+To interpolate sensitive portions for each webhook, use `url_variables`.
+For example, if a webhook has the following URL:
+
+```plaintext
+https://{subdomain}.example.com/{path}?key={value}
+```
+
+You must define the following variables:
+
+- `subdomain`
+- `path`
+- `value`
+
+Variable names can contain only lowercase letters (`a-z`), numbers (`0-9`), or underscores (`_`).
+You can define URL variables directly using the REST API.
+
## Configure your webhook receiver endpoint
Webhook receiver endpoints should be fast and stable.
diff --git a/doc/user/project/merge_requests/status_checks.md b/doc/user/project/merge_requests/status_checks.md
index d330ccdefb6..74c3b3e24b6 100644
--- a/doc/user/project/merge_requests/status_checks.md
+++ b/doc/user/project/merge_requests/status_checks.md
@@ -6,7 +6,7 @@ type: reference, concepts
disqus_identifier: 'https://docs.gitlab.com/ee/user/project/merge_requests/status_checks.html'
---
-# External Status Checks **(ULTIMATE)**
+# External status checks **(ULTIMATE)**
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3869) in GitLab 14.0, disabled behind the `:ff_external_status_checks` feature flag.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/320783) in GitLab 14.1.
diff --git a/doc/user/project/repository/branches/default.md b/doc/user/project/repository/branches/default.md
index 6801899160d..f708e62e634 100644
--- a/doc/user/project/repository/branches/default.md
+++ b/doc/user/project/repository/branches/default.md
@@ -78,7 +78,7 @@ overrides it.
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/221014) in GitLab 13.6.
-Users with at least the Owner role of groups and subgroups can configure the default branch name for a group:
+Users with the Owner role of groups and subgroups can configure the default branch name for a group:
1. Go to the group **Settings > Repository**.
1. Expand **Default branch**.
diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md
index a872a339433..1d0bc674601 100644
--- a/doc/user/project/settings/index.md
+++ b/doc/user/project/settings/index.md
@@ -270,7 +270,7 @@ You can mark a project to be deleted.
Prerequisite:
-- You must have at least the Owner role for a project.
+- You must have the Owner role for a project.
To delete a project:
@@ -308,7 +308,7 @@ If you don't want to wait, you can delete a project immediately.
Prerequisites:
-- You must have at least the Owner role for a project.
+- You must have the Owner role for a project.
- You have [marked the project for deletion](#delete-a-project).
To immediately delete a project marked for deletion:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index c196a5744d4..fdab41e0ecd 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -45552,6 +45552,9 @@ msgstr ""
msgid "We don't have enough data to show this stage."
msgstr ""
+msgid "We found your token in a public project and have automatically revoked it to protect your account."
+msgstr ""
+
msgid "We have found the following errors:"
msgstr ""
diff --git a/qa/qa/ce/strategy.rb b/qa/qa/ce/strategy.rb
index 981b60d1920..8143595a18b 100644
--- a/qa/qa/ce/strategy.rb
+++ b/qa/qa/ce/strategy.rb
@@ -8,12 +8,12 @@ module QA
def perform_before_hooks
if QA::Runtime::Env.admin_personal_access_token.present?
QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::User.admin_username,
- QA::Runtime::Env.admin_personal_access_token)
+ QA::Runtime::Env.admin_personal_access_token)
end
if QA::Runtime::Env.personal_access_token.present? && QA::Runtime::Env.user_username.present?
QA::Resource::PersonalAccessTokenCache.set_token_for_username(QA::Runtime::Env.user_username,
- QA::Runtime::Env.personal_access_token)
+ QA::Runtime::Env.personal_access_token)
end
# The login page could take some time to load the first time it is visited.
@@ -22,6 +22,9 @@ module QA
QA::Support::Retrier.retry_on_exception do
QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
end
+ return unless QA::Runtime::Env.allow_local_requests?
+
+ Runtime::ApplicationSettings.set_application_settings(allow_local_requests_from_web_hooks_and_services: true)
end
end
end
diff --git a/qa/qa/page/profile/two_factor_auth.rb b/qa/qa/page/profile/two_factor_auth.rb
index 16aa60262d8..0436b726911 100644
--- a/qa/qa/page/profile/two_factor_auth.rb
+++ b/qa/qa/page/profile/two_factor_auth.rb
@@ -25,7 +25,7 @@ module QA
def click_configure_it_later_button
# TO DO: Investigate why button does not appear sometimes:
# https://gitlab.com/gitlab-org/gitlab/-/issues/382698
- return unless has_element?(:configure_it_later_button)
+ return unless has_element?(:configure_it_later_button, wait: 20)
click_element :configure_it_later_button
wait_until(max_duration: 10, message: "Waiting for create a group page") do
diff --git a/qa/qa/resource/bulk_import_group.rb b/qa/qa/resource/bulk_import_group.rb
index 31db8ae4cc6..2daf9684bbc 100644
--- a/qa/qa/resource/bulk_import_group.rb
+++ b/qa/qa/resource/bulk_import_group.rb
@@ -11,7 +11,7 @@ module QA
api_client.personal_access_token
end
- attribute :gitlab_address do
+ attribute :source_gitlab_address do
QA::Runtime::Scenario.gitlab_address
end
@@ -28,7 +28,7 @@ module QA
Page::Group::New.perform do |group|
group.switch_to_import_tab
- group.connect_gitlab_instance(gitlab_address, import_access_token)
+ group.connect_gitlab_instance(source_gitlab_address, import_access_token)
end
Page::Group::BulkImport.perform do |import_page|
@@ -50,7 +50,7 @@ module QA
def api_post_body
{
configuration: {
- url: gitlab_address,
+ url: source_gitlab_address,
access_token: import_access_token
},
entities: [
@@ -93,7 +93,7 @@ module QA
# override transformation only for /bulk_imports endpoint which doesn't have web_url in response and
# ignore others so import_id is not overwritten incorrectly
- api_resource[:web_url] = "#{gitlab_address}/#{full_path}"
+ api_resource[:web_url] = "#{source_gitlab_address}/#{full_path}"
api_resource[:import_id] = api_resource[:id]
api_resource
end
diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb
index 7cb7625118e..33ef37fcc2e 100644
--- a/qa/qa/runtime/env.rb
+++ b/qa/qa/runtime/env.rb
@@ -493,6 +493,10 @@ module QA
enabled?(ENV['QA_USE_PUBLIC_IP_API'], default: false)
end
+ def allow_local_requests?
+ enabled?(ENV['QA_ALLOW_LOCAL_REQUESTS'], default: false)
+ end
+
def chrome_default_download_path
ENV['DEFAULT_CHROME_DOWNLOAD_PATH']
end
diff --git a/qa/qa/scenario/test/integration/import.rb b/qa/qa/scenario/test/integration/import.rb
new file mode 100644
index 00000000000..4b0966998cd
--- /dev/null
+++ b/qa/qa/scenario/test/integration/import.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module QA
+ module Scenario
+ module Test
+ module Integration
+ class Import < Test::Instance::All
+ tags :import
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb
index e17e12cdaf3..e87bc5422d7 100644
--- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb
+++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_group_spec.rb
@@ -1,70 +1,14 @@
# frozen_string_literal: true
module QA
- RSpec.describe 'Manage', :reliable, :requires_admin, product_group: :import do
- describe 'Gitlab migration' do
- let(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } }
- let(:admin_api_client) { Runtime::API::Client.as_admin }
- let(:user) do
- Resource::User.fabricate_via_api! do |usr|
- usr.api_client = admin_api_client
- usr.hard_delete_on_api_removal = true
- end
- end
-
- let(:api_client) { Runtime::API::Client.new(user: user) }
-
- let(:sandbox) do
- Resource::Sandbox.fabricate_via_api! do |group|
- group.api_client = admin_api_client
- end
- end
-
- let(:destination_group) do
- Resource::Group.fabricate_via_api! do |group|
- group.api_client = api_client
- group.sandbox = sandbox
- group.path = "destination-group-for-import-#{SecureRandom.hex(4)}"
- end
- end
-
- let(:source_group) do
- Resource::Group.fabricate_via_api! do |group|
- group.api_client = api_client
- group.sandbox = sandbox
- group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
- group.avatar = File.new('qa/fixtures/designs/tanuki.jpg', 'r')
- end
- end
-
- let(:imported_group) do
- Resource::BulkImportGroup.fabricate_via_api! do |group|
- group.api_client = api_client
- group.sandbox = destination_group
- group.source_group = source_group
- end
- end
-
- let(:import_failures) do
- imported_group.import_details.sum([]) { |details| details[:failures] }
- end
-
- before do
- sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
- end
-
- after do |example|
- # Checking for failures in the test currently makes test very flaky due to catching unrelated failures
- # Log failures for easier debugging
- Runtime::Logger.warn("Import failures: #{import_failures}") if example.exception && !import_failures.empty?
- ensure
- user.remove_via_api!
- end
+ RSpec.describe "Manage", :reliable, product_group: :import do
+ include_context "with gitlab group migration"
+ describe "Gitlab migration" do
context 'with subgroups and labels' do
let(:subgroup) do
Resource::Group.fabricate_via_api! do |group|
- group.api_client = api_client
+ group.api_client = source_admin_api_client
group.sandbox = source_group
group.path = "subgroup-for-import-#{SecureRandom.hex(4)}"
end
@@ -80,12 +24,12 @@ module QA
before do
Resource::GroupLabel.fabricate_via_api! do |label|
- label.api_client = api_client
+ label.api_client = source_admin_api_client
label.group = source_group
label.title = "source-group-#{SecureRandom.hex(4)}"
end
Resource::GroupLabel.fabricate_via_api! do |label|
- label.api_client = api_client
+ label.api_client = source_admin_api_client
label.group = subgroup
label.title = "subgroup-#{SecureRandom.hex(4)}"
end
@@ -112,7 +56,7 @@ module QA
context 'with milestones and badges' do
let(:source_milestone) do
Resource::GroupMilestone.fabricate_via_api! do |milestone|
- milestone.api_client = api_client
+ milestone.api_client = source_admin_api_client
milestone.group = source_group
end
end
@@ -121,7 +65,7 @@ module QA
source_milestone
Resource::GroupBadge.fabricate_via_api! do |badge|
- badge.api_client = api_client
+ badge.api_client = source_admin_api_client
badge.group = source_group
badge.link_url = "http://example.com/badge"
badge.image_url = "http://shields.io/badge"
diff --git a/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb
deleted file mode 100644
index c690202f091..00000000000
--- a/qa/qa/specs/features/browser_ui/1_manage/group/gitlab_migration_group_spec.rb
+++ /dev/null
@@ -1,77 +0,0 @@
-# frozen_string_literal: true
-
-module QA
- describe 'Manage', :requires_admin, :reliable, product_group: :import do
- describe 'Gitlab migration' do
- let!(:admin_api_client) { Runtime::API::Client.as_admin }
- let!(:user) do
- Resource::User.fabricate_via_api! do |usr|
- usr.api_client = admin_api_client
- usr.hard_delete_on_api_removal = true
- end
- end
-
- let!(:api_client) { Runtime::API::Client.new(user: user) }
- let!(:personal_access_token) { api_client.personal_access_token }
-
- let(:sandbox) do
- Resource::Sandbox.fabricate_via_api! do |group|
- group.api_client = admin_api_client
- end
- end
-
- let(:source_group) do
- Resource::Sandbox.fabricate! do |group|
- group.api_client = api_client
- group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
- end
- end
-
- let(:imported_group) do
- Resource::BulkImportGroup.init do |group|
- group.api_client = api_client
- group.sandbox = sandbox
- group.source_group = source_group
- end
- end
-
- before do
- sandbox.add_member(user, Resource::Members::AccessLevel::MAINTAINER)
-
- Flow::Login.sign_in(as: user)
-
- source_group
-
- Page::Main::Menu.perform(&:go_to_create_group)
- Page::Group::New.perform do |group|
- group.switch_to_import_tab
- group.connect_gitlab_instance(Runtime::Scenario.gitlab_address, personal_access_token)
- end
- end
-
- after do
- user.remove_via_api!
- end
-
- it(
- 'imports group from UI',
- testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347862',
- issue_1: 'https://gitlab.com/gitlab-org/gitlab/-/issues/331252',
- issue_2: 'https://gitlab.com/gitlab-org/gitlab/-/issues/333678',
- issue_3: 'https://gitlab.com/gitlab-org/gitlab/-/issues/332351',
- except: { job: 'instance-image-slow-network' }
- ) do
- Page::Group::BulkImport.perform do |import_page|
- import_page.import_group(imported_group.path, imported_group.sandbox.path)
-
- expect(import_page).to have_imported_group(imported_group.path, wait: 300)
-
- imported_group.reload!.visit!
- Page::Group::Show.perform do |group|
- expect(group).to have_content(imported_group.path)
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb
new file mode 100644
index 00000000000..4bcd2c44617
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/1_manage/migration/gitlab_migration_group_spec.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module QA
+ describe 'Manage', :reliable, product_group: :import do
+ describe 'Gitlab migration' do
+ include_context "with gitlab group migration"
+
+ let!(:imported_group) do
+ Resource::BulkImportGroup.init do |group|
+ group.api_client = api_client
+ group.sandbox = target_sandbox
+ group.source_group = source_group
+ end
+ end
+
+ before do
+ Flow::Login.sign_in(as: user)
+
+ Page::Main::Menu.perform(&:go_to_create_group)
+ Page::Group::New.perform do |group|
+ group.switch_to_import_tab
+ group.connect_gitlab_instance(source_gitlab_address, source_admin_api_client.personal_access_token)
+ end
+ end
+
+ it 'imports group from UI', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347862' do
+ Page::Group::BulkImport.perform do |import_page|
+ import_page.import_group(source_group.path, target_sandbox.path)
+
+ expect(import_page).to have_imported_group(imported_group.path, wait: 300)
+
+ imported_group.reload!.visit!
+ Page::Group::Show.perform do |group|
+ expect(group).to have_content(imported_group.path)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb
new file mode 100644
index 00000000000..9ec84e12ac0
--- /dev/null
+++ b/qa/qa/specs/features/shared_contexts/import/gitlab_group_migration_common.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module QA
+ RSpec.shared_context(
+ 'with gitlab group migration',
+ :import,
+ :orchestrated,
+ requires_admin: 'creates a user via API'
+ ) do
+ let!(:import_wait_duration) { { max_duration: 300, sleep_interval: 2 } }
+
+ # source instance objects
+ #
+ let!(:source_gitlab_address) { ENV["QA_IMPORT_SOURCE_URL"] || raise("QA_IMPORT_SOURCE_URL is required!") }
+ let!(:source_admin_api_client) do
+ Runtime::API::Client.new(
+ source_gitlab_address,
+ personal_access_token: Runtime::Env.admin_personal_access_token || raise("Admin access token missing!"),
+ is_new_session: false
+ )
+ end
+ let!(:source_admin_user) { Resource::User.fabricate_via_api! { |usr| usr.api_client = source_admin_api_client } }
+ let!(:source_group) do
+ Resource::Sandbox.fabricate_via_api! do |group|
+ group.api_client = source_admin_api_client
+ group.path = "source-group-for-import-#{SecureRandom.hex(4)}"
+ group.avatar = File.new("qa/fixtures/designs/tanuki.jpg", "r")
+ end
+ end
+
+ # target instance objects
+ #
+ let!(:admin_api_client) { Runtime::API::Client.as_admin }
+ let!(:user) { Resource::User.fabricate_via_api! { |usr| usr.api_client = admin_api_client } }
+ let!(:api_client) { Runtime::API::Client.new(user: user) }
+ let!(:target_sandbox) do
+ Resource::Sandbox.fabricate_via_api! do |group|
+ group.api_client = admin_api_client
+ end
+ end
+
+ let(:imported_group) do
+ Resource::BulkImportGroup.fabricate_via_api! do |group|
+ group.api_client = api_client
+ group.sandbox = target_sandbox
+ group.source_group = source_group
+ group.source_gitlab_address = source_gitlab_address
+ group.import_access_token = source_admin_api_client.personal_access_token
+ end
+ end
+
+ let(:import_failures) do
+ imported_group.import_details.sum([]) { |details| details[:failures] }
+ end
+
+ before do
+ target_sandbox.add_member(user, Resource::Members::AccessLevel::OWNER)
+ source_admin_user.set_public_email
+ end
+
+ after do |example|
+ # Checking for failures in the test currently makes test very flaky due to catching unrelated failures
+ # Log failures for easier debugging
+ Runtime::Logger.warn("Import failures: #{import_failures}") if example.exception && !import_failures.empty?
+ rescue QA::Resource::Base::NoValueError
+ # rescue when import did not happen at all
+ end
+ end
+end
diff --git a/spec/frontend/search/sidebar/components/scope_navigation_spec.js b/spec/frontend/search/sidebar/components/scope_navigation_spec.js
index 6262a52e01a..d08fb9911ff 100644
--- a/spec/frontend/search/sidebar/components/scope_navigation_spec.js
+++ b/spec/frontend/search/sidebar/components/scope_navigation_spec.js
@@ -37,6 +37,7 @@ describe('ScopeNavigation', () => {
const findGlNav = () => wrapper.findComponent(GlNav);
const findGlNavItems = () => wrapper.findAllComponents(GlNavItem);
const findGlNavItemActive = () => findGlNavItems().wrappers.filter((w) => w.attributes('active'));
+ const findGlNavItemActiveLabel = () => findGlNavItemActive().at(0).findAll('span').at(0).text();
const findGlNavItemActiveCount = () => findGlNavItemActive().at(0).findAll('span').at(1);
describe('scope navigation', () => {
@@ -64,17 +65,35 @@ describe('ScopeNavigation', () => {
});
});
- describe('scope navigation sets proper state', () => {
+ describe('scope navigation sets proper state with url scope set', () => {
beforeEach(() => {
createComponent();
});
- it('sets proper class to active item', () => {
+ it('correct item is active', () => {
expect(findGlNavItemActive()).toHaveLength(1);
+ expect(findGlNavItemActiveLabel()).toBe('Issues');
});
- it('active item', () => {
+ it('correct active item count', () => {
expect(findGlNavItemActiveCount().text()).toBe('2.4K');
});
+
+ it('correct active item count is highlighted', () => {
+ expect(findGlNavItemActiveCount().classes('gl-text-gray-900')).toBe(true);
+ });
+ });
+
+ describe('scope navigation sets proper state with NO url scope set', () => {
+ beforeEach(() => {
+ createComponent({
+ urlQuery: {},
+ });
+ });
+
+ it('correct item is active', () => {
+ expect(findGlNavItems().at(0).attributes('active')).toBe('true');
+ expect(findGlNavItemActiveLabel()).toBe('Projects');
+ });
});
});
diff --git a/spec/mailers/emails/profile_spec.rb b/spec/mailers/emails/profile_spec.rb
index 767eddb7f98..1812ac0288d 100644
--- a/spec/mailers/emails/profile_spec.rb
+++ b/spec/mailers/emails/profile_spec.rb
@@ -269,6 +269,38 @@ RSpec.describe Emails::Profile do
is_expected.to have_body_text /#{token.name}/
end
+ it 'wont include the revocation reason' do
+ is_expected.not_to have_body_text %r{We found your token in a public project and have automatically revoked it to protect your account.$}
+ end
+
+ it 'includes the email reason' do
+ is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
+ end
+ end
+
+ context 'when source is provided' do
+ subject { Notify.access_token_revoked_email(user, token.name, 'secret_detection') }
+
+ it_behaves_like 'an email sent from GitLab'
+ it_behaves_like 'it should not have Gmail Actions links'
+ it_behaves_like 'a user cannot unsubscribe through footer link'
+
+ it 'is sent to the user' do
+ is_expected.to deliver_to user.email
+ end
+
+ it 'has the correct subject' do
+ is_expected.to have_subject /^A personal access token has been revoked$/i
+ end
+
+ it 'provides the names of the token' do
+ is_expected.to have_body_text /#{token.name}/
+ end
+
+ it 'includes the revocation reason' do
+ is_expected.to have_body_text %r{We found your token in a public project and have automatically revoked it to protect your account.$}
+ end
+
it 'includes the email reason' do
is_expected.to have_body_text %r{You're receiving this email because of your account on <a .*>localhost<\/a>}
end
diff --git a/spec/models/ci/secure_file_spec.rb b/spec/models/ci/secure_file_spec.rb
index 4413bd8e98b..87077fe2db1 100644
--- a/spec/models/ci/secure_file_spec.rb
+++ b/spec/models/ci/secure_file_spec.rb
@@ -138,17 +138,48 @@ RSpec.describe Ci::SecureFile do
end
describe '#update_metadata!' do
- it 'assigns the expected metadata when a parsable file is supplied' do
+ it 'assigns the expected metadata when a parsable .cer file is supplied' do
file = create(:ci_secure_file, name: 'file1.cer',
file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.cer')))
file.update_metadata!
+ file.reload
+
expect(file.expires_at).to eq(DateTime.parse('2022-04-26 19:20:40'))
expect(file.metadata['id']).to eq('33669367788748363528491290218354043267')
expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority')
expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8')
end
+ it 'assigns the expected metadata when a parsable .p12 file is supplied' do
+ file = create(:ci_secure_file, name: 'file1.p12',
+ file: CarrierWaveStringFile.new(fixture_file('ci_secure_files/sample.p12')))
+ file.update_metadata!
+
+ file.reload
+
+ expect(file.expires_at).to eq(DateTime.parse('2022-09-21 14:56:00'))
+ expect(file.metadata['id']).to eq('75949910542696343243264405377658443914')
+ expect(file.metadata['issuer']['CN']).to eq('Apple Worldwide Developer Relations Certification Authority')
+ expect(file.metadata['subject']['OU']).to eq('N7SYAN8PX8')
+ end
+
+ it 'assigns the expected metadata when a parsable .mobileprovision file is supplied' do
+ file = create(:ci_secure_file, name: 'file1.mobileprovision',
+ file: CarrierWaveStringFile.new(
+ fixture_file('ci_secure_files/sample.mobileprovision')
+ ))
+ file.update_metadata!
+
+ file.reload
+
+ expect(file.expires_at).to eq(DateTime.parse('2023-08-01 23:15:13'))
+ expect(file.metadata['id']).to eq('6b9fcce1-b9a9-4b37-b2ce-ec4da2044abf')
+ expect(file.metadata['platforms'].first).to eq('iOS')
+ expect(file.metadata['app_name']).to eq('iOS Demo')
+ expect(file.metadata['app_id']).to eq('match Development com.gitlab.ios-demo')
+ end
+
it 'logs an error when something goes wrong with the file parsing' do
corrupt_file = create(:ci_secure_file, name: 'file1.cer', file: CarrierWaveStringFile.new('11111111'))
message = 'Validation failed: Metadata must be a valid json schema - not enough data.'
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 7857bd2263f..1ca14cd430b 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -361,8 +361,14 @@ RSpec.describe NotificationService, :mailer do
subject(:notification_service) { notification.access_token_revoked(user, pat.name) }
- it 'sends email to the token owner' do
- expect { notification_service }.to have_enqueued_email(user, pat.name, mail: "access_token_revoked_email")
+ it 'sends email to the token owner without source' do
+ expect { notification_service }.to have_enqueued_email(user, pat.name, nil, mail: "access_token_revoked_email")
+ end
+
+ it 'sends email to the token owner with source' do
+ expect do
+ notification.access_token_revoked(user, pat.name, 'secret_detection')
+ end.to have_enqueued_email(user, pat.name, 'secret_detection', mail: "access_token_revoked_email")
end
context 'when user is not allowed to receive notifications' do
diff --git a/spec/services/personal_access_tokens/revoke_service_spec.rb b/spec/services/personal_access_tokens/revoke_service_spec.rb
index f16b6f00a0a..562d6017405 100644
--- a/spec/services/personal_access_tokens/revoke_service_spec.rb
+++ b/spec/services/personal_access_tokens/revoke_service_spec.rb
@@ -8,7 +8,12 @@ RSpec.describe PersonalAccessTokens::RevokeService do
it { expect(service.token.revoked?).to be true }
it 'logs the event' do
- expect(Gitlab::AppLogger).to receive(:info).with(/PAT REVOCATION: revoked_by: '#{current_user.username}', revoked_for: '#{token.user.username}', token_id: '\d+'/)
+ expect(Gitlab::AppLogger).to receive(:info).with(
+ class: described_class.to_s,
+ message: 'PAT Revoked',
+ revoked_by: revoked_by,
+ revoked_for: token.user.username,
+ token_id: token.id)
subject
end
@@ -29,7 +34,9 @@ RSpec.describe PersonalAccessTokens::RevokeService do
let_it_be(:current_user) { create(:admin) }
let_it_be(:token) { create(:personal_access_token) }
- it_behaves_like 'a successfully revoked token'
+ it_behaves_like 'a successfully revoked token' do
+ let(:revoked_by) { current_user.username }
+ end
end
context 'when admin mode is disabled' do
@@ -52,7 +59,38 @@ RSpec.describe PersonalAccessTokens::RevokeService do
context 'token belongs to current_user' do
let_it_be(:token) { create(:personal_access_token, user: current_user) }
- it_behaves_like 'a successfully revoked token'
+ it_behaves_like 'a successfully revoked token' do
+ let(:revoked_by) { current_user.username }
+ end
+ end
+ end
+
+ context 'when source' do
+ let(:service) { described_class.new(nil, token: token, source: source) }
+
+ let_it_be(:current_user) { nil }
+
+ context 'when source is valid' do
+ let_it_be(:source) { 'secret_detection' }
+ let_it_be(:token) { create(:personal_access_token) }
+
+ it_behaves_like 'a successfully revoked token' do
+ let(:revoked_by) { 'secret_detection' }
+ end
+ end
+
+ context 'when source is invalid' do
+ let_it_be(:source) { 'external_request' }
+ let_it_be(:token) { create(:personal_access_token) }
+
+ it_behaves_like 'an unsuccessfully revoked token'
+ end
+
+ context 'when source is missing' do
+ let_it_be(:source) { nil }
+ let_it_be(:token) { create(:personal_access_token) }
+
+ it_behaves_like 'an unsuccessfully revoked token'
end
end
end