Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-06-15 15:10:04 +00:00
parent 96acc69fae
commit 68d7192881
51 changed files with 714 additions and 619 deletions

View file

@ -1 +1 @@
56aaf62b7d7045b9f6bdcd25566c005e5eca72fd 86d069ca736dfde9fa61f8476e974c7b8c15a1e9

Binary file not shown.

After

Width:  |  Height:  |  Size: 316 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 327 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 371 B

View file

@ -119,7 +119,7 @@ export default {
return { return {
title: this.pageInfo.title?.trim() || '', title: this.pageInfo.title?.trim() || '',
format: this.pageInfo.format || 'markdown', format: this.pageInfo.format || 'markdown',
content: this.pageInfo.content?.trim() || '', content: this.pageInfo.content || '',
isContentEditorLoading: true, isContentEditorLoading: true,
useContentEditor: false, useContentEditor: false,
commitMessage: '', commitMessage: '',
@ -131,7 +131,7 @@ export default {
computed: { computed: {
noContent() { noContent() {
if (this.isContentEditorActive) return this.contentEditor?.empty; if (this.isContentEditorActive) return this.contentEditor?.empty;
return !this.content; return !this.content.trim();
}, },
csrfToken() { csrfToken() {
return csrf.token; return csrf.token;
@ -326,7 +326,7 @@ export default {
<div class="col-sm-10"> <div class="col-sm-10">
<input <input
id="wiki_title" id="wiki_title"
v-model.trim="title" v-model="title"
name="wiki[title]" name="wiki[title]"
type="text" type="text"
class="form-control" class="form-control"
@ -418,7 +418,7 @@ export default {
<textarea <textarea
id="wiki_content" id="wiki_content"
ref="textarea" ref="textarea"
v-model.trim="content" v-model="content"
name="wiki[content]" name="wiki[content]"
class="note-textarea js-gfm-input js-autosize markdown-area" class="note-textarea js-gfm-input js-autosize markdown-area"
dir="auto" dir="auto"

View file

@ -145,6 +145,27 @@ table.content {
padding: 15px 5px; padding: 15px 5px;
text-align: center; text-align: center;
} }
td.mailer-align-left {
vertical-align: top;
padding: 16px 32px;
text-align: left;
h4 {
margin: 0;
}
ul {
list-style: none;
line-height: 1.6;
padding-left: 0;
margin: 8px 0 16px;
}
.mailer-icon {
margin-bottom: -1px;
}
}
} }
tr.footer td { tr.footer td {

View file

@ -253,7 +253,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
end end
def perform_update def perform_update
successful = ApplicationSettings::UpdateService successful = ::ApplicationSettings::UpdateService
.new(@application_setting, current_user, application_setting_params) .new(@application_setting, current_user, application_setting_params)
.execute .execute

View file

@ -14,7 +14,7 @@ module Projects
private private
def verify_feature_enabled! def verify_feature_enabled!
render_404 unless Feature.enabled?(:infrastructure_registry_page) render_404 unless Feature.enabled?(:infrastructure_registry_page, default_enabled: :yaml)
end end
end end
end end

View file

@ -12,7 +12,7 @@ module Members
end end
def resolve_variant_name def resolve_variant_name
RoundRobin.new(feature_flag_name, %i[avatar permission_info control]).execute RoundRobin.new(feature_flag_name, %i[activity control]).execute
end end
end end

View file

@ -9,29 +9,15 @@ module NotifyHelper
link_to(entity.to_reference(full: full), issue_url(entity, *args)) link_to(entity.to_reference(full: full), issue_url(entity, *args))
end end
def invited_role_description(role_name)
case role_name
when "Guest"
s_("InviteEmail|As a guest, you can view projects, leave comments, and create issues.")
when "Reporter"
s_("InviteEmail|As a reporter, you can view projects and reports, and leave comments on issues.")
when "Developer"
s_("InviteEmail|As a developer, you have full access to projects, so you can take an idea from concept to production.")
when "Maintainer"
s_("InviteEmail|As a maintainer, you have full access to projects. You can push commits to the default branch and deploy to production.")
when "Owner"
s_("InviteEmail|As an owner, you have full access to projects and can manage access to the group, including inviting new members.")
when "Minimal Access"
s_("InviteEmail|As a user with minimal access, you can view the high-level group from the UI and API.")
end
end
def invited_to_description(source) def invited_to_description(source)
case source default_description =
when "project" case source
s_('InviteEmail|Projects can be used to host your code, track issues, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD.') when Project
when "group" s_('InviteEmail|Projects are used to host and collaborate on code, track issues, and continuously build, test, and deploy your app with built-in GitLab CI/CD.')
s_('InviteEmail|Groups assemble related projects together and grant members access to several projects at once.') when Group
end s_('InviteEmail|Groups assemble related projects together and grant members access to several projects at once.')
end
(source.description || default_description).truncate(200, separator: ' ')
end end
end end

View file

@ -722,6 +722,18 @@ class Group < Namespace
Gitlab::Routing.url_helpers.activity_group_path(self) Gitlab::Routing.url_helpers.activity_group_path(self)
end end
# rubocop: disable CodeReuse/ServiceClass
def open_issues_count(current_user = nil)
Groups::OpenIssuesCountService.new(self, current_user).count
end
# rubocop: enable CodeReuse/ServiceClass
# rubocop: disable CodeReuse/ServiceClass
def open_merge_requests_count(current_user = nil)
Groups::MergeRequestsCountService.new(self, current_user).count
end
# rubocop: enable CodeReuse/ServiceClass
private private
def max_member_access(user_ids) def max_member_access(user_ids)

View file

@ -1753,7 +1753,7 @@ class Project < ApplicationRecord
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass
# rubocop: disable CodeReuse/ServiceClass # rubocop: disable CodeReuse/ServiceClass
def open_merge_requests_count def open_merge_requests_count(_current_user = nil)
Projects::OpenMergeRequestsCountService.new(self).count Projects::OpenMergeRequestsCountService.new(self).count
end end
# rubocop: enable CodeReuse/ServiceClass # rubocop: enable CodeReuse/ServiceClass

View file

@ -13,28 +13,48 @@
= html_escape(s_("InviteEmail|You are invited to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders = html_escape(s_("InviteEmail|You are invited to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders
%p.invite-actions %p.invite-actions
= link_to s_('InviteEmail|Join now'), invite_url(@token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE), class: 'invite-btn-join' = link_to s_('InviteEmail|Join now'), invite_url(@token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE), class: 'invite-btn-join'
- experiment_instance.try(:avatar) do - experiment_instance.try(:activity) do
%tr %tr
%td.text-content %td.text-content{ colspan: 2 }
%img.mail-avatar{ height: "60", src: avatar_icon_for_user(member.created_by, 60, only_path: false), width: "60", alt: "" } %img.mail-avatar{ height: "60", src: avatar_icon_for_user(member.created_by, 60, only_path: false), width: "60", alt: "" }
%p %p
= html_escape(s_("InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders.merge({ inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe }) = html_escape(s_("InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}")) % placeholders.merge({ inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe })
%p.invite-actions %p.invite-actions
= link_to s_('InviteEmail|Join now'), invite_url(@token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE), class: 'invite-btn-join' = link_to s_('InviteEmail|Join now'), invite_url(@token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE), class: 'invite-btn-join'
- experiment_instance.try(:permission_info) do
%tr
%td.text-content{ colspan: 2 }
%img.mail-avatar{ height: "60", src: avatar_icon_for_user(member.created_by, 60, only_path: false), width: "60", alt: "" }
%p
= html_escape(s_("InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} with the %{role} permission level.")) % placeholders.merge({ inviter: (link_to member.created_by.name, user_url(member.created_by)).html_safe })
%p.invite-actions
= link_to s_('InviteEmail|Join now'), invite_url(@token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE), class: 'invite-btn-join'
%tr.border-top %tr.border-top
%td.text-content.half-width %td.text-content.mailer-align-left.half-width
%h4 %h4
= s_('InviteEmail|What is a GitLab %{project_or_group}?') % { project_or_group: member_source.model_name.singular } = s_('InviteEmail|%{project_or_group} details') % { project_or_group: member_source.model_name.singular.capitalize }
%p= invited_to_description(member_source.model_name.singular) %ul
%td.text-content.half-width %li
%div
%img.mailer-icon{ alt: '', src: image_url("mailers/members/users.png") }
%span
- member_count = member_source.members.size
= n_('%{bold_start}%{count}%{bold_end} member', '%{bold_start}%{count}%{bold_end} members',
member_count).html_safe % { count: number_with_delimiter(member_count),
bold_start: '<b>'.html_safe,
bold_end: '</b>'.html_safe }
%li
%div
%img.mailer-icon{ alt: '', src: image_url("mailers/members/issues.png") }
%span
- issue_count = member_source.open_issues_count(member.created_by)
= n_('%{bold_start}%{count}%{bold_end} issue', '%{bold_start}%{count}%{bold_end} issues',
issue_count).html_safe % { count: number_with_delimiter(issue_count),
bold_start: '<b>'.html_safe,
bold_end: '</b>'.html_safe }
%li
%div
%img.mailer-icon{ alt: '', src: image_url("mailers/members/merge-request-open.png") }
%span
- mr_count = member_source.open_merge_requests_count(member.created_by)
= n_('%{bold_start}%{count}%{bold_end} opened merge request', '%{bold_start}%{count}%{bold_end} opened merge requests',
mr_count).html_safe % { count: number_with_delimiter(mr_count),
bold_start: '<b>'.html_safe,
bold_end: '</b>'.html_safe }
%td.text-content.mailer-align-left.half-width
%h4 %h4
= s_('InviteEmail|What can I do with the %{role} permission level?') % { role: member.human_access.downcase } = s_("InviteEmail|What's it about?")
%p= invited_role_description(member.human_access) %p
= invited_to_description(member_source)

View file

@ -1,5 +1,5 @@
- add_to_breadcrumbs _("Infrastructure Registry"), project_packages_path(@project) - add_to_breadcrumbs _("Infrastructure Registry"), project_infrastructure_registry_index_path(@project)
- add_to_breadcrumbs @package.name, project_packages_path(@project) - add_to_breadcrumbs @package.name, project_infrastructure_registry_index_path(@project)
- breadcrumb_title @package.version - breadcrumb_title @package.version
- page_title _("Infrastructure Registry") - page_title _("Infrastructure Registry")
- @content_class = "limit-container-width" unless fluid_layout - @content_class = "limit-container-width" unless fluid_layout

View file

@ -15,7 +15,8 @@ class BulkImportWorker # rubocop:disable Scalability/IdempotentWorker
@bulk_import = BulkImport.find_by_id(bulk_import_id) @bulk_import = BulkImport.find_by_id(bulk_import_id)
return unless @bulk_import return unless @bulk_import
return if @bulk_import.finished? return if @bulk_import.finished? || @bulk_import.failed?
return @bulk_import.fail_op! if all_entities_failed?
return @bulk_import.finish! if all_entities_processed? && @bulk_import.started? return @bulk_import.finish! if all_entities_processed? && @bulk_import.started?
return re_enqueue if max_batch_size_exceeded? # Do not start more jobs if max allowed are already running return re_enqueue if max_batch_size_exceeded? # Do not start more jobs if max allowed are already running
@ -55,6 +56,10 @@ class BulkImportWorker # rubocop:disable Scalability/IdempotentWorker
entities.all? { |entity| entity.finished? || entity.failed? } entities.all? { |entity| entity.finished? || entity.failed? }
end end
def all_entities_failed?
entities.all? { |entity| entity.failed? }
end
def max_batch_size_exceeded? def max_batch_size_exceeded?
started_entities.count >= DEFAULT_BATCH_SIZE started_entities.count >= DEFAULT_BATCH_SIZE
end end

View file

@ -165,6 +165,10 @@ module Gitlab
# like if you have constraints or database-specific column types # like if you have constraints or database-specific column types
config.active_record.schema_format = :sql config.active_record.schema_format = :sql
# Use new connection handling so that we can use Rails 6.1+ multiple
# database support.
config.active_record.legacy_connection_handling = false
config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob" config.action_mailer.delivery_job = "ActionMailer::MailDeliveryJob"
# Enable the asset pipeline # Enable the asset pipeline

View file

@ -54,6 +54,9 @@ Rails.application.configure do
# Enable serving of images, stylesheets, and JavaScripts from an asset server # Enable serving of images, stylesheets, and JavaScripts from an asset server
config.action_controller.asset_host = ENV['GITLAB_CDN_HOST'] if ENV['GITLAB_CDN_HOST'].present? config.action_controller.asset_host = ENV['GITLAB_CDN_HOST'] if ENV['GITLAB_CDN_HOST'].present?
# We use a env var to keep at old default until we enable this for GitLab.com
config.active_record.legacy_connection_handling = !Gitlab::Utils.to_boolean(ENV.fetch('ENABLE_RAILS_61_CONNECTION_HANDLING', false))
# Do not dump schema after migrations. # Do not dump schema after migrations.
config.active_record.dump_schema_after_migration = false config.active_record.dump_schema_after_migration = false

View file

@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326460
milestone: '13.11' milestone: '13.11'
type: development type: development
group: group::package group: group::package
default_enabled: false default_enabled: true

View file

@ -29,7 +29,8 @@ in the first patch release, such as `13.10.1`.
You can configure the What's new variant: You can configure the What's new variant:
1. Navigate to **Admin Area > Settings > Preferences**, then expand **What's new**. 1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. On the left sidebar, select **Settings > Preferences**, then expand **What's new**.
1. Choose one of the following options: 1. Choose one of the following options:
| Option | Description | | Option | Description |

View file

@ -9686,12 +9686,27 @@ A block of time for which a participant is on-call.
| Name | Type | Description | | Name | Type | Description |
| ---- | ---- | ----------- | | ---- | ---- | ----------- |
| <a id="instancesecuritydashboardprojects"></a>`projects` | [`ProjectConnection!`](#projectconnection) | Projects selected in Instance Security Dashboard. (see [Connections](#connections)) |
| <a id="instancesecuritydashboardvulnerabilitygrades"></a>`vulnerabilityGrades` | [`[VulnerableProjectsByGrade!]!`](#vulnerableprojectsbygrade) | Represents vulnerable project counts for each grade. | | <a id="instancesecuritydashboardvulnerabilitygrades"></a>`vulnerabilityGrades` | [`[VulnerableProjectsByGrade!]!`](#vulnerableprojectsbygrade) | Represents vulnerable project counts for each grade. |
| <a id="instancesecuritydashboardvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the vulnerabilities from projects selected in Instance Security Dashboard. (see [Connections](#connections)) | | <a id="instancesecuritydashboardvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the vulnerabilities from projects selected in Instance Security Dashboard. (see [Connections](#connections)) |
#### Fields with arguments #### Fields with arguments
##### `InstanceSecurityDashboard.projects`
Projects selected in Instance Security Dashboard.
Returns [`ProjectConnection!`](#projectconnection).
This field returns a [connection](#connections). It accepts the
four standard [pagination arguments](#connection-pagination-arguments):
`before: String`, `after: String`, `first: Int`, `last: Int`.
###### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="instancesecuritydashboardprojectssearch"></a>`search` | [`String`](#string) | Search query for project name, path, or description. |
##### `InstanceSecurityDashboard.vulnerabilitySeveritiesCount` ##### `InstanceSecurityDashboard.vulnerabilitySeveritiesCount`
Counts for each vulnerability severity from projects selected in Instance Security Dashboard. Counts for each vulnerability severity from projects selected in Instance Security Dashboard.

View file

@ -99,8 +99,8 @@ build:
KANIKOCFG="${KANIKOCFG} }" KANIKOCFG="${KANIKOCFG} }"
echo "${KANIKOCFG}" > /kaniko/.docker/config.json echo "${KANIKOCFG}" > /kaniko/.docker/config.json
- /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile $KANIKOPROXYBUILDARGS --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile $KANIKOPROXYBUILDARGS --destination $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
only: rules:
- tags - if: $CI_COMMIT_TAG
``` ```
## Using a registry with a custom certificate ## Using a registry with a custom certificate

View file

@ -99,10 +99,10 @@ deploy_review:
environment: environment:
name: review/$CI_COMMIT_REF_NAME name: review/$CI_COMMIT_REF_NAME
url: https://$CI_ENVIRONMENT_SLUG.example.com url: https://$CI_ENVIRONMENT_SLUG.example.com
only: rules:
- branches - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
except: when: never
- master - if: $CI_COMMIT_BRANCH
``` ```
In this example: In this example:
@ -158,8 +158,8 @@ deploy_prod:
name: production name: production
url: https://example.com url: https://example.com
when: manual when: manual
only: rules:
- master - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
``` ```
The `when: manual` action: The `when: manual` action:
@ -200,8 +200,8 @@ deploy:
url: https://example.com url: https://example.com
kubernetes: kubernetes:
namespace: production namespace: production
only: rules:
- master - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
``` ```
When you use the GitLab Kubernetes integration to deploy to a Kubernetes cluster, When you use the GitLab Kubernetes integration to deploy to a Kubernetes cluster,

View file

@ -349,12 +349,14 @@ variable entry.
GitLab does support a [`when` keyword](../yaml/README.md#when) which is used to indicate when a job should be GitLab does support a [`when` keyword](../yaml/README.md#when) which is used to indicate when a job should be
run in case of (or despite) failure, but most of the logic for controlling pipelines can be found in run in case of (or despite) failure, but most of the logic for controlling pipelines can be found in
our very powerful [`only/except` rules system](../yaml/README.md#only--except) our very powerful [`rules` system](../yaml/README.md#rules):
(see also our [advanced syntax](../yaml/README.md#only--except)):
```yaml ```yaml
my_job: my_job:
only: [branches] script:
- echo
rules:
- if: $CI_COMMIT_BRANCH
``` ```
## Additional resources ## Additional resources

View file

@ -38,7 +38,7 @@ set of concurrently running child pipelines, but within the same project:
Child pipelines work well with other GitLab CI/CD features: Child pipelines work well with other GitLab CI/CD features:
- Use [`only: changes`](yaml/README.md#onlychanges--exceptchanges) to trigger pipelines only when - Use [`rules: changes`](yaml/README.md#ruleschanges) to trigger pipelines only when
certain files change. This is useful for monorepos, for example. certain files change. This is useful for monorepos, for example.
- Since the parent pipeline in `.gitlab-ci.yml` and the child pipeline run as normal - Since the parent pipeline in `.gitlab-ci.yml` and the child pipeline run as normal
pipelines, they can have their own behaviors and sequencing in relation to triggers. pipelines, they can have their own behaviors and sequencing in relation to triggers.

View file

@ -8,9 +8,6 @@ type: reference, howto
# Pipeline schedules **(FREE)** # Pipeline schedules **(FREE)**
> - Introduced in GitLab 9.1 as [Trigger Schedule](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/10533).
> - [Renamed to Pipeline Schedule](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/10853) in GitLab 9.2.
Pipelines are normally run based on certain conditions being met. For example, when a branch is pushed to repository. Pipelines are normally run based on certain conditions being met. For example, when a branch is pushed to repository.
Pipeline schedules can be used to also run [pipelines](index.md) at specific intervals. For example: Pipeline schedules can be used to also run [pipelines](index.md) at specific intervals. For example:
@ -54,31 +51,29 @@ is installed on.
### Using variables ### Using variables
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/12328) in GitLab 9.4.
You can pass any number of arbitrary variables. They are available in You can pass any number of arbitrary variables. They are available in
GitLab CI/CD so that they can be used in your [`.gitlab-ci.yml` file](../../ci/yaml/README.md). GitLab CI/CD so that they can be used in your [`.gitlab-ci.yml` file](../../ci/yaml/README.md).
![Scheduled pipeline variables](img/pipeline_schedule_variables.png) ![Scheduled pipeline variables](img/pipeline_schedule_variables.png)
### Using only and except ### Using `rules`
To configure a job to be executed only when the pipeline has been To configure a job to be executed only when the pipeline has been
scheduled (or the opposite), use scheduled, use the [`rules`](../yaml/README.md#rules) keyword.
[only and except](../yaml/README.md#only--except) configuration keywords.
In the example below `make world` runs in scheduled pipelines, and `make build` runs in pipelines that are not scheduled: In this example, `make world` runs in scheduled pipelines, and `make build`
runs in branch and tag pipelines:
```yaml ```yaml
job:on-schedule: job:on-schedule:
only: rules:
- schedules - if: $CI_PIPELINE_SOURCE == "schedule"
script: script:
- make world - make world
job: job:
except: rules:
- schedules - if: $CI_PIPELINE_SOURCE = "push"
script: script:
- make build - make build
``` ```

View file

@ -57,8 +57,8 @@ trigger_pipeline:
stage: deploy stage: deploy
script: script:
- curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=main "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline" - curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=main "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
only: rules:
- tags - if: $CI_COMMIT_TAG
``` ```
Pipelines triggered that way also expose a special variable: Pipelines triggered that way also expose a special variable:
@ -83,8 +83,8 @@ build_submodule:
- apt update && apt install -y unzip - apt update && apt install -y unzip
- curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/main/download?job=test&job_token=$CI_JOB_TOKEN" - curl --location --output artifacts.zip "https://gitlab.example.com/api/v4/projects/1/jobs/artifacts/main/download?job=test&job_token=$CI_JOB_TOKEN"
- unzip artifacts.zip - unzip artifacts.zip
only: rules:
- tags - if: $CI_COMMIT_TAG
``` ```
This allows you to use that for multi-project pipelines and download artifacts This allows you to use that for multi-project pipelines and download artifacts
@ -163,8 +163,8 @@ trigger_pipeline:
stage: deploy stage: deploy
script: script:
- 'curl --request POST --form token=TOKEN --form ref=main "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"' - 'curl --request POST --form token=TOKEN --form ref=main "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"'
only: rules:
- tags - if: $CI_COMMIT_TAG
``` ```
This means that whenever a new tag is pushed on project A, the job runs and the This means that whenever a new tag is pushed on project A, the job runs and the

View file

@ -35,7 +35,7 @@ There are two places defined variables can be used. On the:
| `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | | `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) |
| `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment | | `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment |
| `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) | | `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) |
| `only:variables:[]`, `except:variables:[]`, `rules:if` | no | n/a | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). | | `only:variables:[]`, `except:variables:[]`, `rules:if` | no | n/a | The variable must be in the form of `$variable`. Not supported are the following:<br/><br/>- Variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`).<br/>- Any other variables related to environment (currently only `CI_ENVIRONMENT_URL`).<br/>- [Persisted variables](#persisted-variables). |
### `config.toml` file ### `config.toml` file
@ -193,7 +193,6 @@ my-job:
name: review/$CI_JOB_STAGE/deploy name: review/$CI_JOB_STAGE/deploy
script: script:
- 'deploy staging' - 'deploy staging'
only: rules:
variables: - if: $STAGING_SECRET == 'something'
- $STAGING_SECRET == 'something'
``` ```

View file

@ -991,8 +991,8 @@ but you can use as many as eleven. The following example has two levels of inher
```yaml ```yaml
.tests: .tests:
only: rules:
- pushes - if: $CI_PIPELINE_SOURCE == "push"
.rspec: .rspec:
extends: .tests extends: .tests
@ -1028,9 +1028,9 @@ levels. For example:
variables: variables:
URL: "http://my-url.internal" URL: "http://my-url.internal"
IMPORTANT_VAR: "the details" IMPORTANT_VAR: "the details"
only: rules:
- main - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- stable - if: $CI_COMMIT_BRANCH == "stable"
tags: tags:
- production - production
script: script:
@ -1061,9 +1061,9 @@ rspec:
URL: "http://docker-url.internal" URL: "http://docker-url.internal"
IMPORTANT_VAR: "the details" IMPORTANT_VAR: "the details"
GITLAB: "is-awesome" GITLAB: "is-awesome"
only: rules:
- main - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
- stable - if: $CI_COMMIT_BRANCH == "stable"
tags: tags:
- docker - docker
image: alpine image: alpine
@ -2333,8 +2333,8 @@ To protect a manual job:
name: production name: production
url: https://example.com url: https://example.com
when: manual when: manual
only: rules:
- main - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
``` ```
1. In the [protected environments settings](../environments/protected_environments.md#protecting-environments), 1. In the [protected environments settings](../environments/protected_environments.md#protecting-environments),
@ -3281,8 +3281,8 @@ Create artifacts only for tags (`default-job` doesn't create artifacts):
default-job: default-job:
script: script:
- mvn test -U - mvn test -U
except: rules:
- tags - if: $CI_COMMIT_BRANCH
release-job: release-job:
script: script:
@ -3290,8 +3290,8 @@ release-job:
artifacts: artifacts:
paths: paths:
- target/*.war - target/*.war
only: rules:
- tags - if: $CI_COMMIT_TAG
``` ```
You can use wildcards for directories too. For example, if you want to get all the files inside the directories that end with `xyz`: You can use wildcards for directories too. For example, if you want to get all the files inside the directories that end with `xyz`:
@ -4370,7 +4370,10 @@ job:
description: 'Release description' description: 'Release description'
``` ```
It is also possible to create any unique tag, in which case `only: tags` is not mandatory. It is also possible for the release job to automatically create a new unique tag. In that case,
do not use [`rules`](#rules) or [`only`](#only--except) to configure the job to
only run for tags.
A semantic versioning example: A semantic versioning example:
```yaml ```yaml
@ -4626,8 +4629,8 @@ pages:
artifacts: artifacts:
paths: paths:
- public - public
only: rules:
- main - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
``` ```
View the [GitLab Pages user documentation](../../user/project/pages/index.md). View the [GitLab Pages user documentation](../../user/project/pages/index.md).

View file

@ -111,8 +111,8 @@ production:
environment: environment:
name: production name: production
url: https://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN url: https://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
only: rules:
- main - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
``` ```
Content of `.gitlab-ci.yml`: Content of `.gitlab-ci.yml`:

View file

@ -53,7 +53,7 @@ Snowplow tracking is enabled on GitLab.com, and we use it for most of our tracki
To enable Snowplow tracking on a self-managed instance: To enable Snowplow tracking on a self-managed instance:
1. Go to the Admin Area (**{admin}**) and select **Settings > General**. 1. On the top bar, select **Menu >** **{admin}** **Admin**, then select **Settings > General**.
Alternatively, go to `admin/application_settings/general` in your browser. Alternatively, go to `admin/application_settings/general` in your browser.
1. Expand **Snowplow**. 1. Expand **Snowplow**.

View file

@ -50,10 +50,10 @@ More links:
You can view the exact JSON payload sent to GitLab Inc. in the administration panel. To view the payload: You can view the exact JSON payload sent to GitLab Inc. in the administration panel. To view the payload:
1. Sign in as a user with [Administrator](../../user/permissions.md) permissions. 1. Sign in as a user with [Administrator](../../user/permissions.md) permissions.
1. In the top navigation bar, click **(admin)** **Admin Area**. 1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. In the left sidebar, go to **Settings > Metrics and profiling**. 1. On the left sidebar, select **Settings > Metrics and profiling**.
1. Expand the **Usage statistics** section. 1. Expand the **Usage statistics** section.
1. Click the **Preview payload** button. 1. Select **Preview payload**.
For an example payload, see [Example Usage Ping payload](#example-usage-ping-payload). For an example payload, see [Example Usage Ping payload](#example-usage-ping-payload).
@ -62,10 +62,10 @@ For an example payload, see [Example Usage Ping payload](#example-usage-ping-pay
To disable Usage Ping in the GitLab UI: To disable Usage Ping in the GitLab UI:
1. Sign in as a user with [Administrator](../../user/permissions.md) permissions. 1. Sign in as a user with [Administrator](../../user/permissions.md) permissions.
1. In the top navigation bar, click **(admin)** **Admin Area**. 1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. In the left sidebar, go to **Settings > Metrics and profiling**. 1. On the left sidebar, select **Settings > Metrics and profiling**.
1. Expand the **Usage statistics** section. 1. Expand the **Usage statistics** section.
1. Clear the **Usage Ping** checkbox and click **Save changes**. 1. Clear the **Enable usage ping** checkbox and select **Save changes**.
To disable Usage Ping and prevent it from being configured in the future through To disable Usage Ping and prevent it from being configured in the future through
the administration panel, Omnibus installs can set the following in the administration panel, Omnibus installs can set the following in

View file

@ -5,21 +5,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: reference, howto type: reference, howto
--- ---
# Broadcast Messages **(FREE SELF)** # Broadcast messages **(FREE SELF)**
GitLab can display broadcast messages to all users of a GitLab instance. There are two types of broadcast messages: GitLab can display broadcast messages to all users of a GitLab instance. There are two types of broadcast messages:
- banners - Banners
- notifications - Notifications
You can style a message's content using the `a` and `br` HTML tags. The `br` tag inserts a line break. The `a` HTML tag accepts `class` and `style` attributes with the following CSS properties: Broadcast messages can be managed using the [broadcast messages API](../../api/broadcast_messages.md).
- `color`
- `border`
- `background`
- `padding`
- `margin`
- `text-decoration`
## Banners ## Banners
@ -36,6 +29,8 @@ remote:
... ...
``` ```
If more than one banner is active at one time, they are displayed in a stack in order of creation.
## Notifications ## Notifications
Notifications are shown on the bottom right of a page and can contain placeholders. A placeholder is replaced with an attribute of the active user. Placeholders must be surrounded by curly braces, for example `{{name}}`. Notifications are shown on the bottom right of a page and can contain placeholders. A placeholder is replaced with an attribute of the active user. Placeholders must be surrounded by curly braces, for example `{{name}}`.
@ -51,65 +46,63 @@ If the user is not signed in, user related values are empty.
![Broadcast Message Notification](img/broadcast_messages_notification_v12_10.png) ![Broadcast Message Notification](img/broadcast_messages_notification_v12_10.png)
Broadcast messages can be managed using the [broadcast messages API](../../api/broadcast_messages.md). If more than one notification is active at one time, only the newest is shown.
NOTE: ## Add a broadcast message
If more than one banner message is active at one time, they are displayed in a stack in order of creation.
If more than one notification message is active at one time, only the newest is shown.
## Adding a broadcast message To display messages to users on your GitLab instance, add a broadcast message.
To display messages to users on your GitLab instance, add broadcast message.
To add a broadcast message: To add a broadcast message:
1. Navigate to the **Admin Area > Messages** page. 1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. Add the text for the message to the **Message** field. Markdown and emoji are supported. 1. On the left sidebar, select **Messages**.
1. Add the text for the message to the **Message** field. You can style a message's content using Markdown, emoji, and the `a` and `br` HTML tags.
The `br` tag inserts a line break. The `a` HTML tag accepts `class` and `style` attributes with the following CSS properties:
- `color`
- `border`
- `background`
- `padding`
- `margin`
- `text-decoration`
1. Select one of the suggested background colors, or add the hex code of a different color. The default color is orange. 1. Select one of the suggested background colors, or add the hex code of a different color. The default color is orange.
1. Select the **Dismissable** checkbox to enable users to dismiss the broadcast message.
1. If required, add a **Target Path** to only show the broadcast message on URLs matching that path. You can use the wildcard character `*` to match multiple URLs, for example `mygroup/myproject*`. 1. If required, add a **Target Path** to only show the broadcast message on URLs matching that path. You can use the wildcard character `*` to match multiple URLs, for example `mygroup/myproject*`.
1. Select a date for the message to start and end. 1. Select a date for the message to start and end.
1. Click the **Add broadcast message** button. 1. Select **Add broadcast message**.
NOTE:
When scoping messages, you can't use preceding or trailing slashes. For example,
instead of `/mygroup/myproject/`, you must use `mygroup/myproject`. A fix is
[planned for GitLab 13.12](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59482).
NOTE: NOTE:
The **Background color** field expects the value to be a hexadecimal code because The **Background color** field expects the value to be a hexadecimal code because
the form uses the [color_field](https://api.rubyonrails.org/v6.0.3.4/classes/ActionView/Helpers/FormHelper.html#method-i-color_field) the form uses the [color_field](https://api.rubyonrails.org/v6.0.3.4/classes/ActionView/Helpers/FormHelper.html#method-i-color_field)
helper method, which generates the proper HTML to render. helper method, which generates the proper HTML to render.
NOTE: When a broadcast message expires, it no longer displays in the user interface but is still listed in the
Once a broadcast message has expired, it is no longer displayed in the UI but is still listed in the list of broadcast messages.
list of broadcast messages. User can also dismiss a broadcast message if the option **Dismissable** is set.
## Editing a broadcast message ## Edit a broadcast message
If changes are required to a broadcast message, they can be edited. If you need to make changes to a broadcast message, you can edit it.
To edit a broadcast message: To edit a broadcast message:
1. Navigate to the **Admin Area > Messages** page. 1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. From the list of broadcast messages, click the appropriate button to edit the message. 1. On the left sidebar, select **Messages**.
1. After making the required changes, click the **Update broadcast message** button. 1. From the list of broadcast messages, select the edit button for the message.
1. After making the required changes, select **Update broadcast message**.
NOTE:
Expired messages can be made active again by changing their end date. Expired messages can be made active again by changing their end date.
## Deleting a broadcast message ## Delete a broadcast message
Broadcast messages that are no longer required can be deleted. If you no longer require a broadcast message, you can delete it.
You can delete a broadcast message while it's active.
To delete a broadcast message: To delete a broadcast message:
1. Navigate to the **Admin Area > Messages** page. 1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. From the list of broadcast messages, click the appropriate button to delete the message. 1. On the left sidebar, select **Messages**.
1. From the list of broadcast messages, select the delete button for the message.
Once deleted, the broadcast message is removed from the list of broadcast messages. When a broadcast message is deleted, it's removed from the list of broadcast messages.
NOTE:
Broadcast messages can be deleted while active.
<!-- ## Troubleshooting <!-- ## Troubleshooting

View file

@ -34,13 +34,13 @@ is locked.
The first time you visit your GitLab EE installation signed in as an administrator, The first time you visit your GitLab EE installation signed in as an administrator,
you should see a note urging you to upload a license with a link that takes you you should see a note urging you to upload a license with a link that takes you
to **Admin Area > License**. to the **License** area.
Otherwise, you can: Otherwise, to manually go to the **License** area:
1. Navigate manually to the **Admin Area** by selecting the wrench (**{admin}**) icon in the top menu. 1. On the top bar, select **Menu >** **{admin}** **Admin**.
1. Navigate to the **License** tab, and select **Upload New License**. 1. On the left sidebar, select **License**, and select **Upload New License**.
- *If you've received a `.gitlab-license` file:* - *If you've received a `.gitlab-license` file:*
1. Download the license file to your local machine. 1. Download the license file to your local machine.
@ -113,9 +113,9 @@ before this occurs.
To remove a license from a self-managed instance: To remove a license from a self-managed instance:
1. In the top navigation bar, click the **{admin}** wrench icon to navigate to the [Admin Area](index.md). 1. On the top bar, select **Menu >** **{admin}** **Admin** to go to the [Admin Area](index.md).
1. Click **License** in the left sidebar. 1. On the left sidebar, select **License**.
1. Click **Remove License**. 1. Select **Remove license**.
## License history ## License history

View file

@ -51,77 +51,20 @@ The browser-based crawler can be configured using CI/CD variables.
| CI/CD variable | Type | Example | Description | | CI/CD variable | Type | Example | Description |
|--------------------------------------| ----------------| --------------------------------- | ------------| |--------------------------------------| ----------------| --------------------------------- | ------------|
| `DAST_WEBSITE` | URL | `http://www.site.com` | The URL of the website to scan. | | `DAST_WEBSITE` | URL | `http://www.site.com` | The URL of the website to scan. |
| `DAST_BROWSER_SCAN` | boolean | `true` | Configures DAST to use the browser-based crawler engine. | | `DAST_BROWSER_SCAN` | boolean | `true` | Configures DAST to use the browser-based crawler engine. |
| `DAST_BROWSER_ALLOWED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. | | `DAST_BROWSER_ALLOWED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered in scope when crawled. By default the `DAST_WEBSITE` hostname is included in the allowed hosts list. |
| `DAST_BROWSER_EXCLUDED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered excluded and connections are forcibly dropped. | | `DAST_BROWSER_EXCLUDED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are considered excluded and connections are forcibly dropped. |
| `DAST_BROWSER_IGNORED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are accessed but not reported against. | | `DAST_BROWSER_IGNORED_HOSTS` | List of strings | `site.com,another.com` | Hostnames included in this variable are accessed but not reported against. |
| `DAST_BROWSER_MAX_ACTIONS` | number | `10000` | The maximum number of actions that the crawler performs. For example, clicking a link, or filling a form. | | `DAST_BROWSER_MAX_ACTIONS` | number | `10000` | The maximum number of actions that the crawler performs. For example, clicking a link, or filling a form. |
| `DAST_BROWSER_MAX_DEPTH` | number | `10` | The maximum number of chained actions that the crawler takes. For example, `Click -> Form Fill -> Click` is a depth of three. | | `DAST_BROWSER_MAX_DEPTH` | number | `10` | The maximum number of chained actions that the crawler takes. For example, `Click -> Form Fill -> Click` is a depth of three. |
| `DAST_BROWSER_NUMBER_OF_BROWSERS` | number | `3` | The maximum number of concurrent browser instances to use. For shared runners on GitLab.com we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but will likely produce little benefit after five to seven instances. | | `DAST_BROWSER_NUMBER_OF_BROWSERS` | number | `3` | The maximum number of concurrent browser instances to use. For shared runners on GitLab.com we recommended a maximum of three. Private runners with more resources may benefit from a higher number, but will likely produce little benefit after five to seven instances. |
| `DAST_BROWSER_COOKIES` | dictionary | `abtesting_group:3,region:locked` | A cookie name and value to be added to every request. | | `DAST_BROWSER_COOKIES` | dictionary | `abtesting_group:3,region:locked` | A cookie name and value to be added to every request. |
| `DAST_BROWSER_LOG` | List of strings | `brows:debug,auth:debug` | A list of modules and their intended log level. | | `DAST_BROWSER_LOG` | List of strings | `brows:debug,auth:debug` | A list of modules and their intended log level. |
| `DAST_AUTH_URL` | string | `https://example.com/sign-in` | The URL of page that hosts the sign-in form. |
| `DAST_USERNAME` | string | `user123` | The username to enter into the username field on the sign-in HTML form. |
| `DAST_PASSWORD` | string | `p@55w0rd` | The password to enter into the password field on the sign-in HTML form. |
| `DAST_USERNAME_FIELD` | selector | `id:user` | A selector describing the username field on the sign-in HTML form. |
| `DAST_PASSWORD_FIELD` | selector | `css:.password-field` | A selector describing the password field on the sign-in HTML form. |
| `DAST_SUBMIT_FIELD` | selector | `xpath://input[@value='Login']` | A selector describing the element that when clicked submits the login form, or the password form of a multi-page login process. |
| `DAST_FIRST_SUBMIT_FIELD` | selector | `.submit` | A selector describing the element that when clicked submits the username form of a multi-page login process. |
| `DAST_BROWSER_AUTH_REPORT` | boolean | `true` | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. |
| `DAST_AUTH_VERIFICATION_URL` | URL | `http://site.com/welcome` | Verifies successful authentication by checking the URL once the login form has been submitted. |
| `DAST_BROWSER_AUTH_VERIFICATION_SELECTOR` | selector | `css:.user-photo` | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. |
| `DAST_BROWSER_AUTH_VERIFICATION_LOGIN_FORM` | boolean | `true` | Verifies successful authentication by checking for the lack of a login form once the login form has been submitted. |
The [DAST variables](index.md#available-cicd-variables) `SECURE_ANALYZERS_PREFIX`, `DAST_FULL_SCAN_ENABLED`, `DAST_AUTO_UPDATE_ADDONS`, `DAST_EXCLUDE_RULES`, `DAST_REQUEST_HEADERS`, `DAST_HTML_REPORT`, `DAST_MARKDOWN_REPORT`, `DAST_XML_REPORT`, The [DAST variables](index.md#available-cicd-variables) `SECURE_ANALYZERS_PREFIX`, `DAST_FULL_SCAN_ENABLED`, `DAST_AUTO_UPDATE_ADDONS`, `DAST_EXCLUDE_RULES`, `DAST_REQUEST_HEADERS`, `DAST_HTML_REPORT`, `DAST_MARKDOWN_REPORT`, `DAST_XML_REPORT`,
`DAST_AUTH_URL`, `DAST_USERNAME`, `DAST_PASSWORD`, `DAST_USERNAME_FIELD`, `DAST_PASSWORD_FIELD`, `DAST_FIRST_SUBMIT_FIELD`, `DAST_SUBMIT_FIELD`, `DAST_EXCLUDE_URLS`, `DAST_AUTH_VERIFICATION_URL`, `DAST_BROWSER_AUTH_VERIFICATION_SELECTOR`, `DAST_BROWSER_AUTH_VERIFICATION_LOGIN_FORM`, `DAST_BROWSER_AUTH_REPORT`,
`DAST_INCLUDE_ALPHA_VULNERABILITIES`, `DAST_PATHS_FILE`, `DAST_PATHS`, `DAST_ZAP_CLI_OPTIONS`, and `DAST_ZAP_LOG_CONFIGURATION` are also compatible with browser-based crawler scans. `DAST_INCLUDE_ALPHA_VULNERABILITIES`, `DAST_PATHS_FILE`, `DAST_PATHS`, `DAST_ZAP_CLI_OPTIONS`, and `DAST_ZAP_LOG_CONFIGURATION` are also compatible with browser-based crawler scans.
#### Selectors
Selectors are used by CI/CD variables to specify the location of an element displayed on a page in a browser.
Selectors have the format `type`:`search string`. The crawler will search for the selector using the search string based on the type.
| Selector type | Example | Description |
| ------------- | ---------------------------------- | ----------- |
| `css` | `css:.password-field` | Searches for a HTML element having the supplied CSS selector. Selectors should be as specific as possible for performance reasons. |
| `id` | `id:element` | Searches for an HTML element with the provided element ID. |
| `name` | `name:element` | Searches for an HTML element with the provided element name. |
| `xpath` | `xpath://input[@id="my-button"]/a` | Searches for a HTML element with the provided XPath. Note that XPath searches are expected to be less performant than other searches. |
| None provided | `a.click-me` | Defaults to searching using a CSS selector. |
##### Find selectors with Google Chrome
Chrome DevTools element selector tool is an effective way to find a selector.
1. Open Chrome and navigate to the page where you would like to find a selector, for example, the login page for your site.
1. Open the `Elements` tab in Chrome DevTools with the keyboard shortcut `Command + Shift + c` in macOS or `Ctrl + Shift + c` in Windows.
1. Select the `Select an element in the page to select it` tool.
![search-elements](img/dast_auth_browser_scan_search_elements.png)
1. Select the field on your page that you would like to know the selector for.
1. Once the tool is active, highlight a field you wish to view the details of.
![highlight](img/dast_auth_browser_scan_highlight.png)
1. Once highlighted, you can see the element's details, including attributes that would make a good candidate for a selector.
In this example, the `id="user_login"` appears to be a good candidate. You can use this as a selector as the DAST username field by setting `DAST_USERNAME_FIELD: "css:[id=user_login]"`, or more simply, `DAST_USERNAME_FIELD: "id:user_login"`.
##### Choose the right selector
Judicious choice of selector leads to a scan that is resilient to the application changing.
In order of preference, it is recommended to choose as selectors:
- `id` fields. These are generally unique on a page, and rarely change.
- `name` fields. These are generally unique on a page, and rarely change.
- `class` values specific to the field, such as the selector `"css:.username"` for the `username` class on the username field.
- Presence of field specific data attributes, such as the selector, `"css:[data-username]"` when the `data-username` field has any value on the username field.
- Multiple `class` hierarchy values, such as the selector `"css:.login-form .username"` when there are multiple elements with class `username` but only one nested inside the element with the class `login-form`.
When using selectors to locate specific fields we recommend you avoid searching on:
- Any `id`, `name`, `attribute`, `class` or `value` that is dynamically generated.
- Generic class names, such as `column-10` and `dark-grey`.
- XPath searches as they are less performant than other selector searches.
- Unscoped searches, such as those beginning with `css:*` and `xpath://*`.
## Vulnerability detection ## Vulnerability detection
While the browser-based crawler crawls modern web applications efficiently, vulnerability detection is still managed by the standard DAST/Zed Attack Proxy (ZAP) solution. While the browser-based crawler crawls modern web applications efficiently, vulnerability detection is still managed by the standard DAST/Zed Attack Proxy (ZAP) solution.
@ -132,151 +75,6 @@ When running a full scan, active vulnerability checks executed by DAST/ZAP do no
For example, for a target website that contains forms with Anti-CSRF tokens, a passive scan will scan as intended because the browser displays pages/forms as if a user is viewing the page. For example, for a target website that contains forms with Anti-CSRF tokens, a passive scan will scan as intended because the browser displays pages/forms as if a user is viewing the page.
However, active vulnerability checks run in a full scan will not be able to submit forms containing Anti-CSRF tokens. In such cases we recommend you disable Anti-CSRF tokens when running a full scan. However, active vulnerability checks run in a full scan will not be able to submit forms containing Anti-CSRF tokens. In such cases we recommend you disable Anti-CSRF tokens when running a full scan.
## Authentication
If your application hosts content only available to logged in users then you will likely get much higher crawl coverage of your application by configuring authentication.
We recommended you periodically confirm that authentication is still working as this tends to break over
time due to changes to the application.
Authentication supports single form logins, multi-step login forms, and authenticating to URLs outside of the configured target URL.
An example configuration that authenticates a user might look like the following:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true"
DAST_AUTH_URL: "https://login.example.com/"
DAST_USERNAME: "admin"
DAST_PASSWORD: "P@55w0rd!"
DAST_USERNAME_FIELD: "name:username"
DAST_PASSWORD_FIELD: "name:password"
DAST_SUBMIT_FIELD: "css:button[type='submit']"
```
### Log in using automatic detection of the login form
By providing a `DAST_USERNAME`, `DAST_PASSWORD`, and `DAST_AUTH_URL`, the browser-based crawler will attempt to authenticate to the
target application by locating the login form based on a determination about whether or not the form contains username or password fields.
Automatic detection is "best-effort", and depending on the application being scanned may provide either a resilient login experience or one that fails to authenticate the user.
Login process:
1. The `DAST_AUTH_URL` is loaded into the browser, and any forms on the page are located.
1. If a form contains a username and password field, `DAST_USERNAME` and `DAST_PASSWORD` is inputted into the respective fields, the form submit button is clicked and the user is logged in.
1. If a form contains only a username field, it is assumed that the login form is multi-step.
1. The `DAST_USERNAME` is inputted into the username field and the form submit button is clicked.
1. The subsequent pages loads where it is expected that a form exists and contains a password field. If found, `DAST_PASSWORD` is inputted, form submit button is clicked and the user is logged in.
### Log in using explicit selection of the login form
By providing a `DAST_USERNAME_FIELD`, `DAST_PASSWORD_FIELD`, and `DAST_SUBMIT_FIELD`, in addition to the fields required for automatic login,
the browser-based crawler will attempt to authenticate to the target application by locating the login form based on the selectors provided.
Most applications will benefit from this approach to authentication.
Login process:
1. The `DAST_AUTH_URL` is loaded into the browser, and any forms on the page are located.
1. If the `DAST_FIRST_SUBMIT_FIELD` is not defined, then `DAST_USERNAME` is inputted into `DAST_USERNAME_FIELD`, `DAST_PASSWORD` is inputted into `DAST_PASSWORD_FIELD`, `DAST_SUBMIT_FIELD` is clicked and the user is logged in.
1. If the `DAST_FIRST_SUBMIT_FIELD` is defined, then it is assumed that the login form is multi-step.
1. The `DAST_USERNAME` is inputted into the `DAST_USERNAME_FIELD` field and the `DAST_FIRST_SUBMIT_FIELD` is clicked.
1. The subsequent pages loads where the `DAST_PASSWORD` is inputted into the `DAST_PASSWORD_FIELD` field, the `DAST_SUBMIT_FIELD` is clicked and the user is logged in.
### Verifying successful login
Once the login form has been submitted, the browser-based crawler determines if the login was successful. Unsuccessful attempts at authentication cause the scan to halt.
Following the submission of the login form, authentication is determined to be unsuccessful when:
- A `400` or `500` series HTTP response status code is returned.
- A new cookie/browser storage value determined to be sufficiently random has not been set.
In addition to these checks, the user can configure their own verification checks.
Each of the following checks can be used in conjunction with one another, if none are configured by default the presence of a login form is checked.
#### Verifying based on the URL
When `DAST_AUTH_VERIFICATION_URL` is configured, the URL displayed in the browser tab post login form submission is directly compared to the URL in the CI/CD variable.
If these are not exactly the same, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true"
...
DAST_AUTH_VERIFICATION_URL: "https://example.com/user/welcome"
```
#### Verify based on presence of an element
When `DAST_BROWSER_AUTH_VERIFICATION_SELECTOR` is configured, the page displayed in the browser tab is searched for an element described by the selector in the CI/CD variable.
If no element is found, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true"
...
DAST_BROWSER_AUTH_VERIFICATION_SELECTOR: "css:.welcome-user"
```
#### Verify based on presence of a login form
When `DAST_BROWSER_AUTH_VERIFICATION_LOGIN_FORM` is configured, the page displayed in the browser tab is searched for a form that is detected to be a login form.
If any such form is found, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true"
...
DAST_BROWSER_AUTH_VERIFICATION_LOGIN_FORM: "true"
```
### Configure the authentication debug output
It is often difficult to understand the cause of an authentication failure when running DAST in a CI/CD pipeline.
To assist users in debugging authentication issues, a debug report can be generated and saved as a job artifact.
This HTML report contains all steps the browser crawler took, along with HTTP requests and responses, the Document Object Model (DOM) and screenshots.
![dast-auth-report](img/dast_auth_report.jpg)
An example configuration where the authentication debug report is exported may look like the following:
```yaml
dast:
variables:
DAST_WEBSITE: "https://example.com"
DAST_BROWSER_SCAN: "true"
...
DAST_BROWSER_AUTH_REPORT: "true"
artifacts:
paths: [gl-dast-debug-auth-report.html]
```
## Managing scan time ## Managing scan time
It is expected that running the browser-based crawler will result in better coverage for many web applications, when compared to the normal GitLab DAST solution. It is expected that running the browser-based crawler will result in better coverage for many web applications, when compared to the normal GitLab DAST solution.

View file

@ -267,7 +267,7 @@ page.
#### Crawling web applications dependent on JavaScript #### Crawling web applications dependent on JavaScript
GitLab has released a new browser-based crawler, an add-on to DAST that uses a browser to crawl web applications for content. This crawler replaces the standard DAST Spider and Ajax Crawler. GitLab has released a new browser-based crawler, an add-on to DAST that uses a browser to crawl web applications for content. This crawler replaces the standard DAST Spider and Ajax Crawler, and uses the same authentication mechanisms as a normal DAST scan.
The browser-based crawler crawls websites by browsing web pages as a user would. This approach works well with web applications that make heavy use of JavaScript, such as Single Page Applications. The browser-based crawler crawls websites by browsing web pages as a user would. This approach works well with web applications that make heavy use of JavaScript, such as Single Page Applications.
@ -289,126 +289,6 @@ variables:
If your DAST job exceeds the job timeout and you need to reduce the scan duration, we shared some If your DAST job exceeds the job timeout and you need to reduce the scan duration, we shared some
tips for optimizing DAST scans in a [blog post](https://about.gitlab.com/blog/2020/08/31/how-to-configure-dast-full-scans-for-complex-web-applications/). tips for optimizing DAST scans in a [blog post](https://about.gitlab.com/blog/2020/08/31/how-to-configure-dast-full-scans-for-complex-web-applications/).
#### Domain validation
WARNING:
In GitLab 13.8, domain validation, outside of the new on-demand scan site profile validation, was deprecated. In GitLab 14.0, domain validation in CI/CD jobs will be permanently removed.
The DAST job can be run anywhere, which means you can accidentally hit live web servers
and potentially damage them. You could even take down your production environment.
For that reason, you should use domain validation.
Domain validation is not required by default. It can be required by setting the
[CI/CD variable](#available-cicd-variables) `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` to `"true"`.
```yaml
include:
- template: DAST.gitlab-ci.yml
variables:
DAST_FULL_SCAN_ENABLED: "true"
DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED: "true"
```
Since ZAP full scan actively attacks the target application, DAST sends a ping
to the target (normally defined in `DAST_WEBSITE` or `environment_url.txt`) beforehand.
- If `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` is `false` or unset, the scan
proceeds unless the response to the ping includes a `Gitlab-DAST-Permission`
header with a value of `deny`.
- If `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` is `true`, the scan exits
unless the response to the ping includes a `Gitlab-DAST-Permission` header with
a value of `allow`.
Here are some examples of adding the `Gitlab-DAST-Permission` header to a response
in Rails, Django, and Node (with Express).
##### Ruby on Rails
Here's how you would add a
[custom header in Ruby on Rails](https://guides.rubyonrails.org/action_controller_overview.html#setting-custom-headers):
```ruby
class DastWebsiteTargetController < ActionController::Base
def dast_website_target
response.headers['Gitlab-DAST-Permission'] = 'allow'
head :ok
end
end
```
##### Django
Here's how you would add a
[custom header in Django](https://docs.djangoproject.com/en/2.2/ref/request-response/#setting-header-fields):
```python
class DastWebsiteTargetView(View):
def head(self, *args, **kwargs):
response = HttpResponse()
response['Gitlab-Dast-Permission'] = 'allow'
return response
```
##### Node (with Express)
Here's how you would add a
[custom header in Node (with Express)](http://expressjs.com/en/5x/api.html#res.append):
```javascript
app.get('/dast-website-target', function(req, res) {
res.append('Gitlab-DAST-Permission', 'allow')
res.send('Respond to DAST ping')
})
```
##### Domain validation header via a proxy
It's also possible to add the `Gitlab-DAST-Permission` header via a proxy.
###### NGINX
The following configuration allows NGINX to act as a reverse proxy and add the
`Gitlab-DAST-Permission` [header](http://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header):
```nginx
# default.conf
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://test-application;
add_header Gitlab-DAST-Permission allow;
}
}
```
###### Apache
Apache can also be used as a [reverse proxy](https://httpd.apache.org/docs/2.4/mod/mod_proxy.html)
to add the `Gitlab-DAST-Permission` [header](https://httpd.apache.org/docs/current/mod/mod_headers.html).
To do so, add the following lines to `httpd.conf`:
```plaintext
# httpd.conf
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_connect_module modules/mod_proxy_connect.so
LoadModule proxy_http_module modules/mod_proxy_http.so
<VirtualHost *:80>
ProxyPass "/" "http://test-application.com/"
ProxyPassReverse "/" "http://test-application.com/"
Header set Gitlab-DAST-Permission "allow"
</VirtualHost>
```
[This snippet](https://gitlab.com/gitlab-org/security-products/dast/snippets/1894732) contains a complete `httpd.conf` file
configured to act as a remote proxy and add the `Gitlab-DAST-Permission` header.
### API scan ### API scan
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10928) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/10928) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.10.
@ -661,9 +541,7 @@ Using the [`DAST_MASK_HTTP_HEADERS` CI/CD variable](#available-cicd-variables),
headers whose values you want masked. For details on how to mask headers, see headers whose values you want masked. For details on how to mask headers, see
[Customizing the DAST settings](#customizing-the-dast-settings). [Customizing the DAST settings](#customizing-the-dast-settings).
### Authentication ## Authentication
It's also possible to authenticate the user before performing the DAST checks.
NOTE: NOTE:
We highly recommend you configure the scanner to authenticate to the application. If you don't, it cannot check most of the application for security risks, as most We highly recommend you configure the scanner to authenticate to the application. If you don't, it cannot check most of the application for security risks, as most
@ -680,21 +558,29 @@ After DAST has authenticated with the application, all cookies are collected fro
For each cookie a matching session token is created for use by ZAP. This ensures ZAP is recognized For each cookie a matching session token is created for use by ZAP. This ensures ZAP is recognized
by the application as correctly authenticated. by the application as correctly authenticated.
Other variables that are related to authenticated scans are: Authentication supports single form logins, multi-step login forms, and authenticating to URLs outside of the configured target URL.
Variables that are related to authenticated scans are:
```yaml ```yaml
include: include:
- template: DAST.gitlab-ci.yml - template: DAST.gitlab-ci.yml
variables: dast:
DAST_WEBSITE: https://example.com variables:
DAST_AUTH_URL: https://example.com/sign-in DAST_WEBSITE: "https://example.com"
DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form DAST_AUTH_URL: "https://login.example.com/"
DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form DAST_USERNAME: "admin"
DAST_SUBMIT_FIELD: login # the `id` or `name` of the element that when clicked will submit the login form or the password form of a multi-page login process DAST_PASSWORD: "P@55w0rd!"
DAST_FIRST_SUBMIT_FIELD: next # the `id` or `name` of the element that when clicked will submit the username form of a multi-page login process DAST_USERNAME_FIELD: "name:username" # a selector describing the element containing the username field at the sign-in HTML form
DAST_EXCLUDE_URLS: http://example.com/sign-out,http://example.com/sign-out-2 # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between DAST_PASSWORD_FIELD: "id:password" # a selector describing the element containing the password field at the sign-in HTML form
DAST_AUTH_VERIFICATION_URL: http://example.com/loggedin_page # optional, a URL only accessible to logged in users that DAST can use to confirm successful authentication DAST_FIRST_SUBMIT_FIELD: "css:button[type='user-submit']" # optional, the selector of the element that when clicked will submit the username form of a multi-page login process
DAST_SUBMIT_FIELD: "css:button[type='submit']" # the selector of the element that when clicked will submit the login form or the password form of a multi-page login process
DAST_EXCLUDE_URLS: "http://example.com/sign-out" # optional, URLs to skip during the authenticated scan; comma-separated, no spaces in between
DAST_AUTH_VERIFICATION_URL: "http://example.com/loggedin_page" # optional, used to verify authentication is successful by expecting this URL once the login form has been submitted
DAST_AUTH_VERIFICATION_SELECTOR: "css:.user-profile" # optional, used to verify authentication is successful by expecting a selector to be present on the page once the login form has been submitted
DAST_AUTH_VERIFICATION_LOGIN_FORM: "true" # optional, used to verify authentication is successful by ensuring there are no login forms on the page once the login form has been submitted
DAST_AUTH_REPORT: "true" # optionally output an authentication debug report
``` ```
WARNING: WARNING:
@ -703,6 +589,121 @@ scan is run, it may perform *any* function that the authenticated user can. This
includes actions like modifying and deleting data, submitting forms, and following links. includes actions like modifying and deleting data, submitting forms, and following links.
Only run an authenticated scan against a test server. Only run an authenticated scan against a test server.
### Log in using automatic detection of the login form
By providing a `DAST_USERNAME`, `DAST_PASSWORD`, and `DAST_AUTH_URL`, DAST will attempt to authenticate to the
target application by locating the login form based on a determination about whether or not the form contains username or password fields.
Automatic detection is "best-effort", and depending on the application being scanned may provide either a resilient login experience or one that fails to authenticate the user.
Login process:
1. The `DAST_AUTH_URL` is loaded into the browser, and any forms on the page are located.
1. If a form contains a username and password field, `DAST_USERNAME` and `DAST_PASSWORD` is inputted into the respective fields, the form submit button is clicked and the user is logged in.
1. If a form contains only a username field, it is assumed that the login form is multi-step.
1. The `DAST_USERNAME` is inputted into the username field and the form submit button is clicked.
1. The subsequent pages loads where it is expected that a form exists and contains a password field. If found, `DAST_PASSWORD` is inputted, form submit button is clicked and the user is logged in.
### Log in using explicit selection of the login form
By providing a `DAST_USERNAME_FIELD`, `DAST_PASSWORD_FIELD`, and `DAST_SUBMIT_FIELD`, in addition to the fields required for automatic login,
DAST will attempt to authenticate to the target application by locating the login form based on the selectors provided.
Most applications will benefit from this approach to authentication.
Login process:
1. The `DAST_AUTH_URL` is loaded into the browser, and any forms on the page are located.
1. If the `DAST_FIRST_SUBMIT_FIELD` is not defined, then `DAST_USERNAME` is inputted into `DAST_USERNAME_FIELD`, `DAST_PASSWORD` is inputted into `DAST_PASSWORD_FIELD`, `DAST_SUBMIT_FIELD` is clicked and the user is logged in.
1. If the `DAST_FIRST_SUBMIT_FIELD` is defined, then it is assumed that the login form is multi-step.
1. The `DAST_USERNAME` is inputted into the `DAST_USERNAME_FIELD` field and the `DAST_FIRST_SUBMIT_FIELD` is clicked.
1. The subsequent pages loads where the `DAST_PASSWORD` is inputted into the `DAST_PASSWORD_FIELD` field, the `DAST_SUBMIT_FIELD` is clicked and the user is logged in.
### Verifying successful login
Once the login form has been submitted, DAST determines if the login was successful. Unsuccessful attempts at authentication cause the scan to halt.
Following the submission of the login form, authentication is determined to be unsuccessful when:
- A `400` or `500` series HTTP response status code is returned.
- A new cookie/browser storage value determined to be sufficiently random has not been set.
In addition to these checks, the user can configure their own verification checks.
Each of the following checks can be used in conjunction with one another, if none are configured by default the presence of a login form is checked.
#### Verifying based on the URL
When `DAST_AUTH_VERIFICATION_URL` is configured, the URL displayed in the browser tab post login form submission is directly compared to the URL in the CI/CD variable.
If these are not exactly the same, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
...
DAST_AUTH_VERIFICATION_URL: "https://example.com/user/welcome"
```
#### Verify based on presence of an element
When `DAST_AUTH_VERIFICATION_SELECTOR` is configured, the page displayed in the browser tab is searched for an element described by the selector in the CI/CD variable.
If no element is found, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
...
DAST_AUTH_VERIFICATION_SELECTOR: "css:.welcome-user"
```
#### Verify based on presence of a login form
When `DAST_AUTH_VERIFICATION_LOGIN_FORM` is configured, the page displayed in the browser tab is searched for a form that is detected to be a login form.
If any such form is found, authentication is deemed to be unsuccessful.
For example:
```yaml
include:
- template: DAST.gitlab-ci.yml
dast:
variables:
DAST_WEBSITE: "https://example.com"
...
DAST_AUTH_VERIFICATION_LOGIN_FORM: "true"
```
### Configure the authentication debug output
It is often difficult to understand the cause of an authentication failure when running DAST in a CI/CD pipeline.
To assist users in debugging authentication issues, a debug report can be generated and saved as a job artifact.
This HTML report contains all steps made during the login process, along with HTTP requests and responses, the Document Object Model (DOM) and screenshots.
![dast-auth-report](img/dast_auth_report.jpg)
An example configuration where the authentication debug report is exported may look like the following:
```yaml
dast:
variables:
DAST_WEBSITE: "https://example.com"
...
DAST_AUTH_REPORT: "true"
artifacts:
paths: [gl-dast-debug-auth-report.html]
```
### Available CI/CD variables ### Available CI/CD variables
DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables. DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables.
@ -713,18 +714,17 @@ DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables.
| `DAST_WEBSITE` (**1**) | URL | The URL of the website to scan. `DAST_API_OPENAPI` must be specified if this is omitted. | | `DAST_WEBSITE` (**1**) | URL | The URL of the website to scan. `DAST_API_OPENAPI` must be specified if this is omitted. |
| `DAST_API_OPENAPI` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. | | `DAST_API_OPENAPI` | URL or string | The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
| `DAST_API_SPECIFICATION` (**1**) | URL or string | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290241) in GitLab 13.12 and replaced by `DAST_API_OPENAPI`. To be removed in GitLab 15.0. The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. | | `DAST_API_SPECIFICATION` (**1**) | URL or string | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/290241) in GitLab 13.12 and replaced by `DAST_API_OPENAPI`. To be removed in GitLab 15.0. The API specification to import. The specification can be hosted at a URL, or the name of a file present in the `/zap/wrk` directory. `DAST_WEBSITE` must be specified if this is omitted. |
| `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `true`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. | | `DAST_SPIDER_START_AT_HOST` | boolean | Set to `false` to prevent DAST from resetting the target to its host before scanning. When `true`, non-host targets `http://test.site/some_path` is reset to `http://test.site` before scan. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258805) in GitLab 13.6. |
| `DAST_AUTH_URL` (**1**) | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. | | `DAST_AUTH_URL` (**1**) | URL | The URL of the page containing the sign-in HTML form on the target website. `DAST_USERNAME` and `DAST_PASSWORD` are submitted with the login form to create an authenticated scan. Not supported for API scans. |
| `DAST_AUTH_VERIFICATION_URL` (**1**) | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST exits if it cannot access the URL. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8. | | `DAST_AUTH_VERIFICATION_URL` (**1**) | URL | A URL only accessible to logged in users that DAST can use to confirm successful authentication. If provided, DAST exits if it cannot access the URL. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/207335) in GitLab 13.8. |
| `DAST_USERNAME` (**1**) | string | The username to authenticate to in the website. | | `DAST_USERNAME` (**1**) | string | The username to enter into the username field on the sign-in HTML form. |
| `DAST_PASSWORD` (**1**) | string | The password to authenticate to in the website. | | `DAST_PASSWORD` (**1**) | string | The password to enter into the password field on the sign-in HTML form. |
| `DAST_USERNAME_FIELD` (**1**) | string | The name of username field at the sign-in HTML form. | | `DAST_USERNAME_FIELD` (**1**) | selector | A selector describing the username field on the sign-in HTML form. Example: `id:user` |
| `DAST_PASSWORD_FIELD` (**1**) | string | The name of password field at the sign-in HTML form. | | `DAST_PASSWORD_FIELD` (**1**) | selector | A selector describing the password field on the sign-in HTML form. Example: `css:.password-field` |
| `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. | | `DAST_SKIP_TARGET_CHECK` | boolean | Set to `true` to prevent DAST from checking that the target is available before scanning. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/229067) in GitLab 13.8. |
| `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). | | `DAST_MASK_HTTP_HEADERS` | string | Comma-separated list of request and response headers to be masked (GitLab 13.1). Must contain **all** headers to be masked. Refer to [list of headers that are masked by default](#hide-sensitive-information). |
| `DAST_EXCLUDE_URLS` (**1**) | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. | | `DAST_EXCLUDE_URLS` (**1**) | URLs | The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. |
| `DAST_FULL_SCAN_ENABLED` (**1**) | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` | | `DAST_FULL_SCAN_ENABLED` (**1**) | boolean | Set to `true` to run a [ZAP Full Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Full-Scan) instead of a [ZAP Baseline Scan](https://github.com/zaproxy/zaproxy/wiki/ZAP-Baseline-Scan). Default: `false` |
| `DAST_FULL_SCAN_DOMAIN_VALIDATION_REQUIRED` | boolean | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/293595) in GitLab 13.8, to be removed in 14.0. Set to `true` to require [domain validation](#domain-validation) when running DAST full scans. Not supported for API scans. Default: `false` |
| `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` | | `DAST_AUTO_UPDATE_ADDONS` | boolean | ZAP add-ons are pinned to specific versions in the DAST Docker image. Set to `true` to download the latest versions when the scan starts. Default: `false` |
| `DAST_API_HOST_OVERRIDE` (**1**) | string | Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080` | | `DAST_API_HOST_OVERRIDE` (**1**) | string | Used to override domains defined in API specification files. Only supported when importing the API specification from a URL. Example: `example.com:8080` |
| `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. | | `DAST_EXCLUDE_RULES` | string | Set to a comma-separated list of Vulnerability Rule IDs to exclude them from running during the scan. Rule IDs are numbers and can be found from the DAST log or on the [ZAP project](https://www.zaproxy.org/docs/alerts/). For example, `HTTP Parameter Override` has a rule ID of `10026`. Cannot be used when `DAST_ONLY_INCLUDE_RULES` is set. **Note:** In earlier versions of GitLab the excluded rules were executed but vulnerabilities they generated were suppressed. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/118641) in GitLab 12.10. |
@ -740,16 +740,65 @@ DAST can be [configured](#customizing-the-dast-settings) using CI/CD variables.
| `DAST_USE_AJAX_SPIDER` (**1**) | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. | | `DAST_USE_AJAX_SPIDER` (**1**) | boolean | Set to `true` to use the AJAX spider in addition to the traditional spider, useful for crawling sites that require JavaScript. Default: `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_PATHS` | string | Set to a comma-separated list of URLs for DAST to scan. For example, `/page1.html,/category1/page3.html,/page2.html`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214120) in GitLab 13.4. | | `DAST_PATHS` | string | Set to a comma-separated list of URLs for DAST to scan. For example, `/page1.html,/category1/page3.html,/page2.html`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214120) in GitLab 13.4. |
| `DAST_PATHS_FILE` | string | The file path containing the paths within `DAST_WEBSITE` to scan. The file must be plain text with one path per line. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258825) in GitLab 13.6. | | `DAST_PATHS_FILE` | string | The file path containing the paths within `DAST_WEBSITE` to scan. The file must be plain text with one path per line. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/258825) in GitLab 13.6. |
| `DAST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when clicked submits the login form or the password form of a multi-page login process. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. | | `DAST_SUBMIT_FIELD` | selector | A selector describing the element that when clicked submits the login form, or the password form of a multi-page login process. Example: `xpath://input[@value='Login']`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
| `DAST_FIRST_SUBMIT_FIELD` | string | The `id` or `name` of the element that when clicked submits the username form of a multi-page login process. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. | | `DAST_FIRST_SUBMIT_FIELD` | selector | A selector describing the element that when clicked submits the username form of a multi-page login process. Example: `.submit`. [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9894) in GitLab 12.4. |
| `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. | | `DAST_ZAP_CLI_OPTIONS` | string | ZAP server command-line options. For example, `-Xmx3072m` would set the Java maximum memory allocation pool size. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12652) in GitLab 13.1. |
| `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. For example, `log4j.logger.org.parosproxy.paros.network.HttpSender=DEBUG;log4j.logger.com.crawljax=DEBUG` | | `DAST_ZAP_LOG_CONFIGURATION` | string | Set to a semicolon-separated list of additional log4j properties for the ZAP Server. For example, `log4j.logger.org.parosproxy.paros.network.HttpSender=DEBUG;log4j.logger.com.crawljax=DEBUG` |
| `DAST_AGGREGATE_VULNERABILITIES` | boolean | Vulnerability aggregation is set to `true` by default. To disable this feature and see each vulnerability individually set to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254043) in GitLab 14.0. | | `DAST_AGGREGATE_VULNERABILITIES` | boolean | Vulnerability aggregation is set to `true` by default. To disable this feature and see each vulnerability individually set to `false`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/254043) in GitLab 14.0. |
| `DAST_MAX_URLS_PER_VULNERABILITY` | number | The maximum number of URLs reported for a single vulnerability. `DAST_MAX_URLS_PER_VULNERABILITY` is set to `50` by default. To list all the URLs set to `0`. [Introduced](https://gitlab.com/gitlab-org/security-products/dast/-/merge_requests/433) in GitLab 13.12. | | `DAST_MAX_URLS_PER_VULNERABILITY` | number | The maximum number of URLs reported for a single vulnerability. `DAST_MAX_URLS_PER_VULNERABILITY` is set to `50` by default. To list all the URLs set to `0`. [Introduced](https://gitlab.com/gitlab-org/security-products/dast/-/merge_requests/433) in GitLab 13.12. |
| `DAST_AUTH_EXCLUDE_URLS` | URLs | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/289959) in GitLab 13.8, to be removed in 14.0, and replaced by `DAST_EXCLUDE_URLS`. The URLs to skip during the authenticated scan; comma-separated. Regular expression syntax can be used to match multiple URLs. For example, `.*` matches an arbitrary character sequence. Not supported for API scans. | | `DAST_AUTH_REPORT` | boolean | Used in combination with exporting the `gl-dast-debug-auth-report.html` artifact to aid in debugging authentication issues. |
| `DAST_AUTH_VERIFICATION_SELECTOR` | selector | Verifies successful authentication by checking for presence of a selector once the login form has been submitted. Example: `css:.user-photo` |
| `DAST_AUTH_VERIFICATION_LOGIN_FORM` | boolean | Verifies successful authentication by checking for the lack of a login form once the login form has been submitted. |
1. DAST CI/CD variable available to an on-demand scan. 1. DAST CI/CD variable available to an on-demand scan.
#### Selectors
Selectors are used by CI/CD variables to specify the location of an element displayed on a page in a browser.
Selectors have the format `type`:`search string`. The crawler will search for the selector using the search string based on the type.
| Selector type | Example | Description |
| ------------- | ---------------------------------- | ----------- |
| `css` | `css:.password-field` | Searches for a HTML element having the supplied CSS selector. Selectors should be as specific as possible for performance reasons. |
| `id` | `id:element` | Searches for an HTML element with the provided element ID. |
| `name` | `name:element` | Searches for an HTML element with the provided element name. |
| `xpath` | `xpath://input[@id="my-button"]/a` | Searches for a HTML element with the provided XPath. Note that XPath searches are expected to be less performant than other searches. |
| None provided | `a.click-me` | Defaults to searching using a CSS selector. |
##### Find selectors with Google Chrome
Chrome DevTools element selector tool is an effective way to find a selector.
1. Open Chrome and navigate to the page where you would like to find a selector, for example, the login page for your site.
1. Open the `Elements` tab in Chrome DevTools with the keyboard shortcut `Command + Shift + c` in macOS or `Ctrl + Shift + c` in Windows.
1. Select the `Select an element in the page to select it` tool.
![search-elements](img/dast_auth_browser_scan_search_elements.png)
1. Select the field on your page that you would like to know the selector for.
1. Once the tool is active, highlight a field you wish to view the details of.
![highlight](img/dast_auth_browser_scan_highlight.png)
1. Once highlighted, you can see the element's details, including attributes that would make a good candidate for a selector.
In this example, the `id="user_login"` appears to be a good candidate. You can use this as a selector as the DAST username field by setting `DAST_USERNAME_FIELD: "css:[id=user_login]"`, or more simply, `DAST_USERNAME_FIELD: "id:user_login"`.
##### Choose the right selector
Judicious choice of selector leads to a scan that is resilient to the application changing.
In order of preference, it is recommended to choose as selectors:
- `id` fields. These are generally unique on a page, and rarely change.
- `name` fields. These are generally unique on a page, and rarely change.
- `class` values specific to the field, such as the selector `"css:.username"` for the `username` class on the username field.
- Presence of field specific data attributes, such as the selector, `"css:[data-username]"` when the `data-username` field has any value on the username field.
- Multiple `class` hierarchy values, such as the selector `"css:.login-form .username"` when there are multiple elements with class `username` but only one nested inside the element with the class `login-form`.
When using selectors to locate specific fields we recommend you avoid searching on:
- Any `id`, `name`, `attribute`, `class` or `value` that is dynamically generated.
- Generic class names, such as `column-10` and `dark-grey`.
- XPath searches as they are less performant than other selector searches.
- Unscoped searches, such as those beginning with `css:*` and `xpath://*`.
### DAST command-line options ### DAST command-line options
Not all DAST configuration is available via CI/CD variables. To find out all Not all DAST configuration is available via CI/CD variables. To find out all
@ -1248,11 +1297,6 @@ The DAST tool always emits a JSON report file called `gl-dast-report.json` and
sample reports can be found in the sample reports can be found in the
[DAST repository](https://gitlab.com/gitlab-org/security-products/dast/-/tree/master/test/end-to-end/expect). [DAST repository](https://gitlab.com/gitlab-org/security-products/dast/-/tree/master/test/end-to-end/expect).
There are two formats of data in the JSON report that are used side by side:
- The proprietary ZAP format, which is planned to be deprecated.
- A common format that is planned to the default in the future.
### Other formats ### Other formats
Reports can also be generated in Markdown, HTML, and XML. These can be published as artifacts using the following configuration: Reports can also be generated in Markdown, HTML, and XML. These can be published as artifacts using the following configuration:

View file

@ -219,14 +219,14 @@ There are two ways to resolve/unresolve a Design thread:
![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png) ![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
Resolving a discussion thread will also mark any pending to-do related to notes Resolving a discussion thread also marks any pending to-do items related to notes
inside the thread as done. This is applicable only for to-dos owned by the user triggering the action. inside the thread as done. This is applicable only for to-do items owned by the user triggering the action.
Note that your resolved comment pins disappear from the Design to free up space for new discussions. Note that your resolved comment pins disappear from the Design to free up space for new discussions.
However, if you need to revisit or find a resolved discussion, all of your resolved threads are However, if you need to revisit or find a resolved discussion, all of your resolved threads are
available in the **Resolved Comment** area at the bottom of the right sidebar. available in the **Resolved Comment** area at the bottom of the right sidebar.
## Add to dos for designs ## Add to-do items for designs
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198439) in GitLab 13.4. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/198439) in GitLab 13.4.
> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/245074) in GitLab 13.5. > - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/245074) in GitLab 13.5.

View file

@ -10,29 +10,39 @@ module BulkImports
def initialize(context) def initialize(context)
@context = context @context = context
@entity = @context.entity
@trackers = @entity.trackers
end end
def run def run
return if context.entity.finished? return if entity.finished? || entity.failed?
context.entity.finish! if all_other_trackers_failed?
entity.fail_op!
else
entity.finish!
end
logger.info( logger.info(
bulk_import_id: context.bulk_import.id, bulk_import_id: context.bulk_import.id,
bulk_import_entity_id: context.entity.id, bulk_import_entity_id: context.entity.id,
bulk_import_entity_type: context.entity.source_type, bulk_import_entity_type: context.entity.source_type,
pipeline_class: self.class.name, pipeline_class: self.class.name,
message: 'Entity finished' message: "Entity #{entity.status_name}"
) )
end end
private private
attr_reader :context attr_reader :context, :entity, :trackers
def logger def logger
@logger ||= Gitlab::Import::Logger.build @logger ||= Gitlab::Import::Logger.build
end end
def all_other_trackers_failed?
trackers.where.not(relation: self.class.name).all? { |tracker| tracker.failed? } # rubocop: disable CodeReuse/ActiveRecord
end
end end
end end
end end

View file

@ -58,7 +58,7 @@ module Sidebars
end end
def infrastructure_registry_menu_item def infrastructure_registry_menu_item
if Feature.disabled?(:infrastructure_registry_page, context.current_user) if Feature.disabled?(:infrastructure_registry_page, context.current_user, default_enabled: :yaml)
return ::Sidebars::NilMenuItem.new(item_id: :infrastructure_registry) return ::Sidebars::NilMenuItem.new(item_id: :infrastructure_registry)
end end

View file

@ -421,6 +421,21 @@ msgstr ""
msgid "%{board_target} not found" msgid "%{board_target} not found"
msgstr "" msgstr ""
msgid "%{bold_start}%{count}%{bold_end} issue"
msgid_plural "%{bold_start}%{count}%{bold_end} issues"
msgstr[0] ""
msgstr[1] ""
msgid "%{bold_start}%{count}%{bold_end} member"
msgid_plural "%{bold_start}%{count}%{bold_end} members"
msgstr[0] ""
msgstr[1] ""
msgid "%{bold_start}%{count}%{bold_end} opened merge request"
msgid_plural "%{bold_start}%{count}%{bold_end} opened merge requests"
msgstr[0] ""
msgstr[1] ""
msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements." msgid "%{code_open}Masked:%{code_close} Hidden in job logs. Must match masking requirements."
msgstr "" msgstr ""
@ -13045,6 +13060,9 @@ msgstr ""
msgid "EscalationPolicies|Add policy" msgid "EscalationPolicies|Add policy"
msgstr "" msgstr ""
msgid "EscalationPolicies|Are you sure you want to delete the \"%{escalationPolicy}\" escalation policy? This action cannot be undone."
msgstr ""
msgid "EscalationPolicies|Create an escalation policy in GitLab" msgid "EscalationPolicies|Create an escalation policy in GitLab"
msgstr "" msgstr ""
@ -13087,6 +13105,9 @@ msgstr ""
msgid "EscalationPolicies|THEN %{doAction} %{schedule}" msgid "EscalationPolicies|THEN %{doAction} %{schedule}"
msgstr "" msgstr ""
msgid "EscalationPolicies|The escalation policy could not be deleted. Please try again."
msgstr ""
msgid "EscalationPolicies|mins" msgid "EscalationPolicies|mins"
msgstr "" msgstr ""
@ -17917,25 +17938,7 @@ msgstr ""
msgid "InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}" msgid "InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}"
msgstr "" msgstr ""
msgid "InviteEmail|%{inviter} invited you to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} with the %{role} permission level." msgid "InviteEmail|%{project_or_group} details"
msgstr ""
msgid "InviteEmail|As a developer, you have full access to projects, so you can take an idea from concept to production."
msgstr ""
msgid "InviteEmail|As a guest, you can view projects, leave comments, and create issues."
msgstr ""
msgid "InviteEmail|As a maintainer, you have full access to projects. You can push commits to the default branch and deploy to production."
msgstr ""
msgid "InviteEmail|As a reporter, you can view projects and reports, and leave comments on issues."
msgstr ""
msgid "InviteEmail|As a user with minimal access, you can view the high-level group from the UI and API."
msgstr ""
msgid "InviteEmail|As an owner, you have full access to projects and can manage access to the group, including inviting new members."
msgstr "" msgstr ""
msgid "InviteEmail|Groups assemble related projects together and grant members access to several projects at once." msgid "InviteEmail|Groups assemble related projects together and grant members access to several projects at once."
@ -17944,13 +17947,10 @@ msgstr ""
msgid "InviteEmail|Join now" msgid "InviteEmail|Join now"
msgstr "" msgstr ""
msgid "InviteEmail|Projects can be used to host your code, track issues, collaborate on code, and continuously build, test, and deploy your app with built-in GitLab CI/CD." msgid "InviteEmail|Projects are used to host and collaborate on code, track issues, and continuously build, test, and deploy your app with built-in GitLab CI/CD."
msgstr "" msgstr ""
msgid "InviteEmail|What can I do with the %{role} permission level?" msgid "InviteEmail|What's it about?"
msgstr ""
msgid "InviteEmail|What is a GitLab %{project_or_group}?"
msgstr "" msgstr ""
msgid "InviteEmail|You are invited to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}" msgid "InviteEmail|You are invited to join the %{strong_start}%{project_or_group_name}%{strong_end}%{br_tag}%{project_or_group} as a %{role}"

View file

@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Packages::InfrastructureRegistryController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :private) }
let(:params) { { namespace_id: project.namespace, project_id: project } }
before do
sign_in(user)
project.add_maintainer(user)
end
describe 'GET #index' do
subject { get :index, params: params, format: :html }
it_behaves_like 'returning response status', :ok
context 'when the feature is disabled' do
before do
stub_feature_flags(infrastructure_registry_page: false)
end
it_behaves_like 'returning response status', :not_found
end
end
describe 'GET #show' do
let_it_be(:terraform_module) { create(:terraform_module_package, project: project) }
subject { get :show, params: params.merge(id: terraform_module.id), format: :html }
it_behaves_like 'returning response status', :ok
context 'when the feature is disabled' do
before do
stub_feature_flags(infrastructure_registry_page: false)
end
it_behaves_like 'returning response status', :not_found
end
end
end

View file

@ -39,20 +39,14 @@ RSpec.describe Members::InviteEmailExperiment, :clean_gitlab_redis_shared_state
allow(instance_1).to receive(:enabled?).and_return(true) allow(instance_1).to receive(:enabled?).and_return(true)
instance_2 = described_class.new('members/invite_email', **context) instance_2 = described_class.new('members/invite_email', **context)
allow(instance_2).to receive(:enabled?).and_return(true) allow(instance_2).to receive(:enabled?).and_return(true)
instance_3 = described_class.new('members/invite_email', **context)
allow(instance_3).to receive(:enabled?).and_return(true)
instance_1.try { } instance_1.try { }
expect(instance_1.variant.name).to eq('permission_info') expect(instance_1.variant.name).to eq('control')
instance_2.try { } instance_2.try { }
expect(instance_2.variant.name).to eq('control') expect(instance_2.variant.name).to eq('activity')
instance_3.try { }
expect(instance_3.variant.name).to eq('avatar')
end end
end end

View file

@ -19,5 +19,11 @@ FactoryBot.define do
sequence(:jid) { |n| "bulk_import_entity_#{n}" } sequence(:jid) { |n| "bulk_import_entity_#{n}" }
end end
trait :failed do
status { -1 }
sequence(:jid) { |n| "bulk_import_entity_#{n}" }
end
end end
end end

View file

@ -63,7 +63,7 @@ describe('WikiForm', () => {
persisted: true, persisted: true,
title: 'My page', title: 'My page',
content: 'My page content', content: ' My page content ',
format: 'markdown', format: 'markdown',
path: '/project/path/-/wikis/home', path: '/project/path/-/wikis/home',
}; };
@ -129,6 +129,12 @@ describe('WikiForm', () => {
expect(findMessage().element.value).toBe('Update My page'); expect(findMessage().element.value).toBe('Update My page');
}); });
it('does not trim page content by default', () => {
createWrapper(true);
expect(findContent().element.value).toBe(' My page content ');
});
it.each` it.each`
value | text value | text
${'markdown'} | ${'[Link Title](page-slug)'} ${'markdown'} | ${'[Link Title](page-slug)'}
@ -183,10 +189,10 @@ describe('WikiForm', () => {
describe('when wiki content is updated', () => { describe('when wiki content is updated', () => {
beforeEach(() => { beforeEach(() => {
createWrapper(); createWrapper(true);
const input = findContent(); const input = findContent();
input.setValue('Lorem ipsum dolar sit!'); input.setValue(' Lorem ipsum dolar sit! ');
input.element.dispatchEvent(new Event('input')); input.element.dispatchEvent(new Event('input'));
return wrapper.vm.$nextTick(); return wrapper.vm.$nextTick();
@ -213,6 +219,10 @@ describe('WikiForm', () => {
it('does not trigger tracking event', async () => { it('does not trigger tracking event', async () => {
expect(trackingSpy).not.toHaveBeenCalled(); expect(trackingSpy).not.toHaveBeenCalled();
}); });
it('does not trim page content', () => {
expect(findContent().element.value).toBe(' Lorem ipsum dolar sit! ');
});
}); });
}); });
@ -434,7 +444,7 @@ describe('WikiForm', () => {
it('updates content from content editor on form submit', async () => { it('updates content from content editor on form submit', async () => {
// old value // old value
expect(findContent().element.value).toBe('My page content'); expect(findContent().element.value).toBe(' My page content ');
// wait for content editor to load // wait for content editor to load
await waitForPromises(); await waitForPromises();
@ -484,7 +494,7 @@ describe('WikiForm', () => {
}); });
it('the old editor retains its old value and does not use the content from the content editor', () => { it('the old editor retains its old value and does not use the content from the content editor', () => {
expect(findContent().element.value).toBe('My page content'); expect(findContent().element.value).toBe(' My page content ');
}); });
}); });
}); });

View file

@ -28,27 +28,12 @@ RSpec.describe NotifyHelper do
end end
end end
describe '#invited_role_description' do
where(:role, :description) do
"Guest" | /As a guest/
"Reporter" | /As a reporter/
"Developer" | /As a developer/
"Maintainer" | /As a maintainer/
"Owner" | /As an owner/
"Minimal Access" | /As a user with minimal access/
end
with_them do
specify do
expect(helper.invited_role_description(role)).to match description
end
end
end
describe '#invited_to_description' do describe '#invited_to_description' do
where(:source, :description) do where(:source, :description) do
"project" | /Projects can/ build(:project, description: nil) | /Projects are/
"group" | /Groups assemble/ build(:group, description: nil) | /Groups assemble/
build(:project, description: '_description_') | '_description_'
build(:group, description: '_description_') | '_description_'
end end
with_them do with_them do
@ -56,6 +41,15 @@ RSpec.describe NotifyHelper do
expect(helper.invited_to_description(source)).to match description expect(helper.invited_to_description(source)).to match description
end end
end end
it 'truncates long descriptions', :aggregate_failures do
description = '_description_ ' * 30
project = build(:project, description: description)
result = helper.invited_to_description(project)
expect(result).not_to match description
expect(result.length).to be <= 200
end
end end
def reference_link(entity, url) def reference_link(entity, url)

View file

@ -7,8 +7,15 @@ RSpec.describe 'Database config initializer' do
load Rails.root.join('config/initializers/database_config.rb') load Rails.root.join('config/initializers/database_config.rb')
end end
around do |example|
original_config = ActiveRecord::Base.connection_db_config
example.run
ActiveRecord::Base.establish_connection(original_config)
end
before do before do
allow(ActiveRecord::Base).to receive(:establish_connection)
allow(Gitlab::Runtime).to receive(:max_threads).and_return(max_threads) allow(Gitlab::Runtime).to receive(:max_threads).and_return(max_threads)
end end
@ -21,6 +28,8 @@ RSpec.describe 'Database config initializer' do
it "sets it based on the max number of worker threads" do it "sets it based on the max number of worker threads" do
expect { subject }.to change { Gitlab::Database.config['pool'] }.from(nil).to(18) expect { subject }.to change { Gitlab::Database.config['pool'] }.from(nil).to(18)
expect(ActiveRecord::Base.connection_db_config.pool).to eq(18)
end end
end end
@ -31,6 +40,8 @@ RSpec.describe 'Database config initializer' do
it "sets it based on the max number of worker threads" do it "sets it based on the max number of worker threads" do
expect { subject }.to change { Gitlab::Database.config['pool'] }.from(1).to(18) expect { subject }.to change { Gitlab::Database.config['pool'] }.from(1).to(18)
expect(ActiveRecord::Base.connection_db_config.pool).to eq(18)
end end
end end
@ -41,6 +52,8 @@ RSpec.describe 'Database config initializer' do
it "sets it based on the max number of worker threads" do it "sets it based on the max number of worker threads" do
expect { subject }.to change { Gitlab::Database.config['pool'] }.from(100).to(18) expect { subject }.to change { Gitlab::Database.config['pool'] }.from(100).to(18)
expect(ActiveRecord::Base.connection_db_config.pool).to eq(18)
end end
end end
@ -56,15 +69,16 @@ RSpec.describe 'Database config initializer' do
expect { subject }.to change { Gitlab::Database.config['pool'] } expect { subject }.to change { Gitlab::Database.config['pool'] }
.from(1) .from(1)
.to(max_threads + headroom) .to(max_threads + headroom)
expect(ActiveRecord::Base.connection_db_config.pool).to eq(max_threads + headroom)
end end
end end
def stub_database_config(pool_size:) def stub_database_config(pool_size:)
config = { original_config = Gitlab::Database.config
'adapter' => 'postgresql',
'host' => 'db.host.com', config = original_config.dup
'pool' => pool_size config['pool'] = pool_size
}.compact
allow(Gitlab::Database).to receive(:config).and_return(config) allow(Gitlab::Database).to receive(:config).and_return(config)
end end

View file

@ -25,13 +25,33 @@ RSpec.describe BulkImports::Groups::Pipelines::EntityFinisher do
.to change(entity, :status_name).to(:finished) .to change(entity, :status_name).to(:finished)
end end
it 'does nothing when the entity is already finished' do context 'when entity is in a final finished or failed state' do
entity = create(:bulk_import_entity, :finished) shared_examples 'performs no state update' do |entity_state|
pipeline_tracker = create(:bulk_import_tracker, entity: entity) it 'does nothing' do
context = BulkImports::Pipeline::Context.new(pipeline_tracker) entity = create(:bulk_import_entity, entity_state)
subject = described_class.new(context) pipeline_tracker = create(:bulk_import_tracker, entity: entity)
context = BulkImports::Pipeline::Context.new(pipeline_tracker)
subject = described_class.new(context)
expect { subject.run } expect { subject.run }
.not_to change(entity, :status_name) .not_to change(entity, :status_name)
end
end
include_examples 'performs no state update', :finished
include_examples 'performs no state update', :failed
end
context 'when all entity trackers failed' do
it 'marks entity as failed' do
entity = create(:bulk_import_entity, :started)
create(:bulk_import_tracker, :failed, entity: entity)
pipeline_tracker = create(:bulk_import_tracker, entity: entity, relation: described_class)
context = BulkImports::Pipeline::Context.new(pipeline_tracker)
described_class.new(context).run
expect(entity.reload.failed?).to eq(true)
end
end end
end end

View file

@ -790,7 +790,7 @@ RSpec.describe Notify do
it_behaves_like 'appearance header and footer not enabled' it_behaves_like 'appearance header and footer not enabled'
it_behaves_like 'does not render a manage notifications link' it_behaves_like 'does not render a manage notifications link'
context 'when there is an inviter' do context 'when there is an inviter', :aggregate_failures do
it 'contains all the useful information' do it 'contains all the useful information' do
is_expected.to have_subject "#{inviter.name} invited you to join GitLab" is_expected.to have_subject "#{inviter.name} invited you to join GitLab"
is_expected.to have_body_text project.full_name is_expected.to have_body_text project.full_name
@ -799,21 +799,16 @@ RSpec.describe Notify do
is_expected.to have_link('Join now', href: invite_url(project_member.invite_token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE)) is_expected.to have_link('Join now', href: invite_url(project_member.invite_token, invite_type: Members::InviteEmailExperiment::INVITE_TYPE))
end end
it 'contains invite link for the avatar' do it 'contains invite link for the group activity' do
stub_experiments('members/invite_email': :avatar) stub_experiments('members/invite_email': :activity)
is_expected.to have_content("#{inviter.name} invited you to join the")
is_expected.to have_content('Project details')
is_expected.to have_content("What's it about?")
is_expected.not_to have_content('You are invited!') is_expected.not_to have_content('You are invited!')
is_expected.not_to have_body_text 'What is a GitLab' is_expected.not_to have_body_text 'What is a GitLab'
end end
it 'contains invite link for the avatar' do
stub_experiments('members/invite_email': :permission_info)
is_expected.not_to have_content('You are invited!')
is_expected.to have_body_text 'What is a GitLab'
is_expected.to have_body_text 'What can I do with'
end
it 'has invite link for the control group' do it 'has invite link for the control group' do
stub_experiments('members/invite_email': :control) stub_experiments('members/invite_email': :control)
@ -821,7 +816,7 @@ RSpec.describe Notify do
end end
end end
context 'when there is no inviter' do context 'when there is no inviter', :aggregate_failures do
let(:inviter) { nil } let(:inviter) { nil }
it 'contains all the useful information' do it 'contains all the useful information' do

View file

@ -2617,7 +2617,7 @@ RSpec.describe Group do
context 'with export' do context 'with export' do
let(:group) { create(:group, :with_export) } let(:group) { create(:group, :with_export) }
it '#export_file_exists returns true' do it '#export_file_exists? returns true' do
expect(group.export_file_exists?).to be true expect(group.export_file_exists?).to be true
end end
@ -2625,4 +2625,54 @@ RSpec.describe Group do
expect(group.export_archive_exists?).to be true expect(group.export_archive_exists?).to be true
end end
end end
describe '#open_issues_count', :aggregate_failures do
let(:group) { build(:group) }
it 'provides the issue count' do
expect(group.open_issues_count).to eq 0
end
it 'invokes the count service with current_user' do
user = build(:user)
count_service = instance_double(Groups::OpenIssuesCountService)
expect(Groups::OpenIssuesCountService).to receive(:new).with(group, user).and_return(count_service)
expect(count_service).to receive(:count)
group.open_issues_count(user)
end
it 'invokes the count service with no current_user' do
count_service = instance_double(Groups::OpenIssuesCountService)
expect(Groups::OpenIssuesCountService).to receive(:new).with(group, nil).and_return(count_service)
expect(count_service).to receive(:count)
group.open_issues_count
end
end
describe '#open_merge_requests_count', :aggregate_failures do
let(:group) { build(:group) }
it 'provides the merge request count' do
expect(group.open_merge_requests_count).to eq 0
end
it 'invokes the count service with current_user' do
user = build(:user)
count_service = instance_double(Groups::MergeRequestsCountService)
expect(Groups::MergeRequestsCountService).to receive(:new).with(group, user).and_return(count_service)
expect(count_service).to receive(:count)
group.open_merge_requests_count(user)
end
it 'invokes the count service with no current_user' do
count_service = instance_double(Groups::MergeRequestsCountService)
expect(Groups::MergeRequestsCountService).to receive(:new).with(group, nil).and_return(count_service)
expect(count_service).to receive(:count)
group.open_merge_requests_count
end
end
end end

View file

@ -994,6 +994,39 @@ RSpec.describe Project, factory_default: :keep do
end end
end end
describe '#open_issues_count', :aggregate_failures do
let(:project) { build(:project) }
it 'provides the issue count' do
expect(project.open_issues_count).to eq 0
end
it 'invokes the count service with current_user' do
user = build(:user)
count_service = instance_double(Projects::OpenIssuesCountService)
expect(Projects::OpenIssuesCountService).to receive(:new).with(project, user).and_return(count_service)
expect(count_service).to receive(:count)
project.open_issues_count(user)
end
it 'invokes the count service with no current_user' do
count_service = instance_double(Projects::OpenIssuesCountService)
expect(Projects::OpenIssuesCountService).to receive(:new).with(project, nil).and_return(count_service)
expect(count_service).to receive(:count)
project.open_issues_count
end
end
describe '#open_merge_requests_count' do
it 'provides the merge request count' do
project = build(:project)
expect(project.open_merge_requests_count).to eq 0
end
end
describe '#issue_exists?' do describe '#issue_exists?' do
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }

View file

@ -21,7 +21,8 @@ JS_CONSOLE_FILTER = Regexp.union([
'"[WDS] Hot Module Replacement enabled."', '"[WDS] Hot Module Replacement enabled."',
'"[WDS] Live Reloading enabled."', '"[WDS] Live Reloading enabled."',
'Download the Vue Devtools extension', 'Download the Vue Devtools extension',
'Download the Apollo DevTools' 'Download the Apollo DevTools',
"Unrecognized feature: 'interest-cohort'"
]) ])
CAPYBARA_WINDOW_SIZE = [1366, 768].freeze CAPYBARA_WINDOW_SIZE = [1366, 768].freeze

View file

@ -22,6 +22,16 @@ RSpec.describe BulkImportWorker do
end end
end end
context 'when bulk import is failed' do
it 'does nothing' do
bulk_import = create(:bulk_import, :failed)
expect(described_class).not_to receive(:perform_in)
subject.perform(bulk_import.id)
end
end
context 'when all entities are processed' do context 'when all entities are processed' do
it 'marks bulk import as finished' do it 'marks bulk import as finished' do
bulk_import = create(:bulk_import, :started) bulk_import = create(:bulk_import, :started)
@ -34,6 +44,18 @@ RSpec.describe BulkImportWorker do
end end
end end
context 'when all entities are failed' do
it 'marks bulk import as failed' do
bulk_import = create(:bulk_import, :started)
create(:bulk_import_entity, :failed, bulk_import: bulk_import)
create(:bulk_import_entity, :failed, bulk_import: bulk_import)
subject.perform(bulk_import.id)
expect(bulk_import.reload.failed?).to eq(true)
end
end
context 'when maximum allowed number of import entities in progress' do context 'when maximum allowed number of import entities in progress' do
it 'reenqueues itself' do it 'reenqueues itself' do
bulk_import = create(:bulk_import, :started) bulk_import = create(:bulk_import, :started)