Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-11-17 09:08:17 +00:00
parent da92a12093
commit 375c6d54dd
39 changed files with 592 additions and 227 deletions

View File

@ -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

View File

@ -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
# ==========================================

View File

@ -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>

View File

@ -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'];

View File

@ -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")))

View File

@ -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"],

View File

@ -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

View File

@ -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

View File

@ -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" },

View File

@ -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 }

View File

@ -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 } %>

View File

@ -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

View File

@ -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

View File

@ -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 -->

View File

@ -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,

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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
### Branch protection rules and project settings
Supported GitHub branch protection rules are mapped to GitLab branch protection rules or project-wide GitLab settings when they are imported:
When they are imported, supported GitHub branch protection rules are mapped to either:
- 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.
- 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

View File

@ -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.

View File

@ -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.

View File

@ -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**.

View File

@ -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:

View File

@ -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 ""

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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');
});
});
});

View File

@ -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

View File

@ -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.'

View File

@ -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

View File

@ -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