Add latest changes from gitlab-org/gitlab@master
This commit is contained in:
parent
96acc69fae
commit
68d7192881
51 changed files with 714 additions and 619 deletions
|
@ -1 +1 @@
|
|||
56aaf62b7d7045b9f6bdcd25566c005e5eca72fd
|
||||
86d069ca736dfde9fa61f8476e974c7b8c15a1e9
|
||||
|
|
BIN
app/assets/images/mailers/members/issues.png
Normal file
BIN
app/assets/images/mailers/members/issues.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 316 B |
BIN
app/assets/images/mailers/members/merge-request-open.png
Normal file
BIN
app/assets/images/mailers/members/merge-request-open.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 327 B |
BIN
app/assets/images/mailers/members/users.png
Normal file
BIN
app/assets/images/mailers/members/users.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 371 B |
|
@ -119,7 +119,7 @@ export default {
|
|||
return {
|
||||
title: this.pageInfo.title?.trim() || '',
|
||||
format: this.pageInfo.format || 'markdown',
|
||||
content: this.pageInfo.content?.trim() || '',
|
||||
content: this.pageInfo.content || '',
|
||||
isContentEditorLoading: true,
|
||||
useContentEditor: false,
|
||||
commitMessage: '',
|
||||
|
@ -131,7 +131,7 @@ export default {
|
|||
computed: {
|
||||
noContent() {
|
||||
if (this.isContentEditorActive) return this.contentEditor?.empty;
|
||||
return !this.content;
|
||||
return !this.content.trim();
|
||||
},
|
||||
csrfToken() {
|
||||
return csrf.token;
|
||||
|
@ -326,7 +326,7 @@ export default {
|
|||
<div class="col-sm-10">
|
||||
<input
|
||||
id="wiki_title"
|
||||
v-model.trim="title"
|
||||
v-model="title"
|
||||
name="wiki[title]"
|
||||
type="text"
|
||||
class="form-control"
|
||||
|
@ -418,7 +418,7 @@ export default {
|
|||
<textarea
|
||||
id="wiki_content"
|
||||
ref="textarea"
|
||||
v-model.trim="content"
|
||||
v-model="content"
|
||||
name="wiki[content]"
|
||||
class="note-textarea js-gfm-input js-autosize markdown-area"
|
||||
dir="auto"
|
||||
|
|
|
@ -145,6 +145,27 @@ table.content {
|
|||
padding: 15px 5px;
|
||||
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 {
|
||||
|
|
|
@ -253,7 +253,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
end
|
||||
|
||||
def perform_update
|
||||
successful = ApplicationSettings::UpdateService
|
||||
successful = ::ApplicationSettings::UpdateService
|
||||
.new(@application_setting, current_user, application_setting_params)
|
||||
.execute
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ module Projects
|
|||
private
|
||||
|
||||
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
|
||||
|
|
|
@ -12,7 +12,7 @@ module Members
|
|||
end
|
||||
|
||||
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
|
||||
|
||||
|
|
|
@ -9,29 +9,15 @@ module NotifyHelper
|
|||
link_to(entity.to_reference(full: full), issue_url(entity, *args))
|
||||
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)
|
||||
case source
|
||||
when "project"
|
||||
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 "group"
|
||||
s_('InviteEmail|Groups assemble related projects together and grant members access to several projects at once.')
|
||||
end
|
||||
default_description =
|
||||
case source
|
||||
when Project
|
||||
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.')
|
||||
when Group
|
||||
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
|
||||
|
|
|
@ -722,6 +722,18 @@ class Group < Namespace
|
|||
Gitlab::Routing.url_helpers.activity_group_path(self)
|
||||
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
|
||||
|
||||
def max_member_access(user_ids)
|
||||
|
|
|
@ -1753,7 +1753,7 @@ class Project < ApplicationRecord
|
|||
# rubocop: enable CodeReuse/ServiceClass
|
||||
|
||||
# rubocop: disable CodeReuse/ServiceClass
|
||||
def open_merge_requests_count
|
||||
def open_merge_requests_count(_current_user = nil)
|
||||
Projects::OpenMergeRequestsCountService.new(self).count
|
||||
end
|
||||
# rubocop: enable CodeReuse/ServiceClass
|
||||
|
|
|
@ -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
|
||||
%p.invite-actions
|
||||
= 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
|
||||
%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: "" }
|
||||
%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 })
|
||||
%p.invite-actions
|
||||
= 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
|
||||
%td.text-content.half-width
|
||||
%td.text-content.mailer-align-left.half-width
|
||||
%h4
|
||||
= s_('InviteEmail|What is a GitLab %{project_or_group}?') % { project_or_group: member_source.model_name.singular }
|
||||
%p= invited_to_description(member_source.model_name.singular)
|
||||
%td.text-content.half-width
|
||||
= s_('InviteEmail|%{project_or_group} details') % { project_or_group: member_source.model_name.singular.capitalize }
|
||||
%ul
|
||||
%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
|
||||
= s_('InviteEmail|What can I do with the %{role} permission level?') % { role: member.human_access.downcase }
|
||||
%p= invited_role_description(member.human_access)
|
||||
= s_("InviteEmail|What's it about?")
|
||||
%p
|
||||
= invited_to_description(member_source)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
- add_to_breadcrumbs _("Infrastructure Registry"), project_packages_path(@project)
|
||||
- add_to_breadcrumbs @package.name, project_packages_path(@project)
|
||||
- add_to_breadcrumbs _("Infrastructure Registry"), project_infrastructure_registry_index_path(@project)
|
||||
- add_to_breadcrumbs @package.name, project_infrastructure_registry_index_path(@project)
|
||||
- breadcrumb_title @package.version
|
||||
- page_title _("Infrastructure Registry")
|
||||
- @content_class = "limit-container-width" unless fluid_layout
|
||||
|
|
|
@ -15,7 +15,8 @@ class BulkImportWorker # rubocop:disable Scalability/IdempotentWorker
|
|||
@bulk_import = BulkImport.find_by_id(bulk_import_id)
|
||||
|
||||
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 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? }
|
||||
end
|
||||
|
||||
def all_entities_failed?
|
||||
entities.all? { |entity| entity.failed? }
|
||||
end
|
||||
|
||||
def max_batch_size_exceeded?
|
||||
started_entities.count >= DEFAULT_BATCH_SIZE
|
||||
end
|
||||
|
|
|
@ -165,6 +165,10 @@ module Gitlab
|
|||
# like if you have constraints or database-specific column types
|
||||
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"
|
||||
|
||||
# Enable the asset pipeline
|
||||
|
|
|
@ -54,6 +54,9 @@ Rails.application.configure do
|
|||
# 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?
|
||||
|
||||
# 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.
|
||||
config.active_record.dump_schema_after_migration = false
|
||||
|
||||
|
|
|
@ -5,4 +5,4 @@ rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/326460
|
|||
milestone: '13.11'
|
||||
type: development
|
||||
group: group::package
|
||||
default_enabled: false
|
||||
default_enabled: true
|
||||
|
|
|
@ -29,7 +29,8 @@ in the first patch release, such as `13.10.1`.
|
|||
|
||||
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:
|
||||
|
||||
| Option | Description |
|
||||
|
|
|
@ -9686,12 +9686,27 @@ A block of time for which a participant is on-call.
|
|||
|
||||
| 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="instancesecuritydashboardvulnerabilityscanners"></a>`vulnerabilityScanners` | [`VulnerabilityScannerConnection`](#vulnerabilityscannerconnection) | Vulnerability scanners reported on the vulnerabilities from projects selected in Instance Security Dashboard. (see [Connections](#connections)) |
|
||||
|
||||
#### 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`
|
||||
|
||||
Counts for each vulnerability severity from projects selected in Instance Security Dashboard.
|
||||
|
|
|
@ -99,8 +99,8 @@ build:
|
|||
KANIKOCFG="${KANIKOCFG} }"
|
||||
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
|
||||
only:
|
||||
- tags
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
```
|
||||
|
||||
## Using a registry with a custom certificate
|
||||
|
|
|
@ -99,10 +99,10 @@ deploy_review:
|
|||
environment:
|
||||
name: review/$CI_COMMIT_REF_NAME
|
||||
url: https://$CI_ENVIRONMENT_SLUG.example.com
|
||||
only:
|
||||
- branches
|
||||
except:
|
||||
- master
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
when: never
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
```
|
||||
|
||||
In this example:
|
||||
|
@ -158,8 +158,8 @@ deploy_prod:
|
|||
name: production
|
||||
url: https://example.com
|
||||
when: manual
|
||||
only:
|
||||
- master
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
```
|
||||
|
||||
The `when: manual` action:
|
||||
|
@ -200,8 +200,8 @@ deploy:
|
|||
url: https://example.com
|
||||
kubernetes:
|
||||
namespace: production
|
||||
only:
|
||||
- master
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
```
|
||||
|
||||
When you use the GitLab Kubernetes integration to deploy to a Kubernetes cluster,
|
||||
|
|
|
@ -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
|
||||
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)
|
||||
(see also our [advanced syntax](../yaml/README.md#only--except)):
|
||||
our very powerful [`rules` system](../yaml/README.md#rules):
|
||||
|
||||
```yaml
|
||||
my_job:
|
||||
only: [branches]
|
||||
script:
|
||||
- echo
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
```
|
||||
|
||||
## Additional resources
|
||||
|
|
|
@ -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:
|
||||
|
||||
- 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.
|
||||
- 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.
|
||||
|
|
|
@ -8,9 +8,6 @@ type: reference, howto
|
|||
|
||||
# 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.
|
||||
|
||||
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
|
||||
|
||||
> [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
|
||||
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)
|
||||
|
||||
### Using only and except
|
||||
### Using `rules`
|
||||
|
||||
To configure a job to be executed only when the pipeline has been
|
||||
scheduled (or the opposite), use
|
||||
[only and except](../yaml/README.md#only--except) configuration keywords.
|
||||
scheduled, use the [`rules`](../yaml/README.md#rules) keyword.
|
||||
|
||||
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
|
||||
job:on-schedule:
|
||||
only:
|
||||
- schedules
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "schedule"
|
||||
script:
|
||||
- make world
|
||||
|
||||
job:
|
||||
except:
|
||||
- schedules
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE = "push"
|
||||
script:
|
||||
- make build
|
||||
```
|
||||
|
|
|
@ -57,8 +57,8 @@ trigger_pipeline:
|
|||
stage: deploy
|
||||
script:
|
||||
- curl --request POST --form "token=$CI_JOB_TOKEN" --form ref=main "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"
|
||||
only:
|
||||
- tags
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
```
|
||||
|
||||
Pipelines triggered that way also expose a special variable:
|
||||
|
@ -83,8 +83,8 @@ build_submodule:
|
|||
- 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"
|
||||
- unzip artifacts.zip
|
||||
only:
|
||||
- tags
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
```
|
||||
|
||||
This allows you to use that for multi-project pipelines and download artifacts
|
||||
|
@ -163,8 +163,8 @@ trigger_pipeline:
|
|||
stage: deploy
|
||||
script:
|
||||
- 'curl --request POST --form token=TOKEN --form ref=main "https://gitlab.example.com/api/v4/projects/9/trigger/pipeline"'
|
||||
only:
|
||||
- tags
|
||||
rules:
|
||||
- if: $CI_COMMIT_TAG
|
||||
```
|
||||
|
||||
This means that whenever a new tag is pushed on project A, the job runs and the
|
||||
|
|
|
@ -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) |
|
||||
| `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) |
|
||||
| `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
|
||||
|
||||
|
@ -193,7 +193,6 @@ my-job:
|
|||
name: review/$CI_JOB_STAGE/deploy
|
||||
script:
|
||||
- 'deploy staging'
|
||||
only:
|
||||
variables:
|
||||
- $STAGING_SECRET == 'something'
|
||||
rules:
|
||||
- if: $STAGING_SECRET == 'something'
|
||||
```
|
||||
|
|
|
@ -991,8 +991,8 @@ but you can use as many as eleven. The following example has two levels of inher
|
|||
|
||||
```yaml
|
||||
.tests:
|
||||
only:
|
||||
- pushes
|
||||
rules:
|
||||
- if: $CI_PIPELINE_SOURCE == "push"
|
||||
|
||||
.rspec:
|
||||
extends: .tests
|
||||
|
@ -1028,9 +1028,9 @@ levels. For example:
|
|||
variables:
|
||||
URL: "http://my-url.internal"
|
||||
IMPORTANT_VAR: "the details"
|
||||
only:
|
||||
- main
|
||||
- stable
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_BRANCH == "stable"
|
||||
tags:
|
||||
- production
|
||||
script:
|
||||
|
@ -1061,9 +1061,9 @@ rspec:
|
|||
URL: "http://docker-url.internal"
|
||||
IMPORTANT_VAR: "the details"
|
||||
GITLAB: "is-awesome"
|
||||
only:
|
||||
- main
|
||||
- stable
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
- if: $CI_COMMIT_BRANCH == "stable"
|
||||
tags:
|
||||
- docker
|
||||
image: alpine
|
||||
|
@ -2333,8 +2333,8 @@ To protect a manual job:
|
|||
name: production
|
||||
url: https://example.com
|
||||
when: manual
|
||||
only:
|
||||
- main
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
```
|
||||
|
||||
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:
|
||||
script:
|
||||
- mvn test -U
|
||||
except:
|
||||
- tags
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH
|
||||
|
||||
release-job:
|
||||
script:
|
||||
|
@ -3290,8 +3290,8 @@ release-job:
|
|||
artifacts:
|
||||
paths:
|
||||
- target/*.war
|
||||
only:
|
||||
- tags
|
||||
rules:
|
||||
- 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`:
|
||||
|
@ -4370,7 +4370,10 @@ job:
|
|||
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:
|
||||
|
||||
```yaml
|
||||
|
@ -4626,8 +4629,8 @@ pages:
|
|||
artifacts:
|
||||
paths:
|
||||
- public
|
||||
only:
|
||||
- main
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
```
|
||||
|
||||
View the [GitLab Pages user documentation](../../user/project/pages/index.md).
|
||||
|
|
|
@ -111,8 +111,8 @@ production:
|
|||
environment:
|
||||
name: production
|
||||
url: https://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
|
||||
only:
|
||||
- main
|
||||
rules:
|
||||
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
|
||||
```
|
||||
|
||||
Content of `.gitlab-ci.yml`:
|
||||
|
|
|
@ -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:
|
||||
|
||||
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.
|
||||
|
||||
1. Expand **Snowplow**.
|
||||
|
|
|
@ -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:
|
||||
|
||||
1. Sign in as a user with [Administrator](../../user/permissions.md) permissions.
|
||||
1. In the top navigation bar, click **(admin)** **Admin Area**.
|
||||
1. In the left sidebar, go to **Settings > Metrics and profiling**.
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. On the left sidebar, select **Settings > Metrics and profiling**.
|
||||
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).
|
||||
|
||||
|
@ -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:
|
||||
|
||||
1. Sign in as a user with [Administrator](../../user/permissions.md) permissions.
|
||||
1. In the top navigation bar, click **(admin)** **Admin Area**.
|
||||
1. In the left sidebar, go to **Settings > Metrics and profiling**.
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. On the left sidebar, select **Settings > Metrics and profiling**.
|
||||
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
|
||||
the administration panel, Omnibus installs can set the following in
|
||||
|
|
|
@ -5,21 +5,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w
|
|||
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:
|
||||
|
||||
- banners
|
||||
- notifications
|
||||
- Banners
|
||||
- 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:
|
||||
|
||||
- `color`
|
||||
- `border`
|
||||
- `background`
|
||||
- `padding`
|
||||
- `margin`
|
||||
- `text-decoration`
|
||||
Broadcast messages can be managed using the [broadcast messages API](../../api/broadcast_messages.md).
|
||||
|
||||
## 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 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 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:
|
||||
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.
|
||||
## Add a broadcast message
|
||||
|
||||
## Adding a broadcast message
|
||||
|
||||
To display messages to users on your GitLab instance, add broadcast message.
|
||||
To display messages to users on your GitLab instance, add a broadcast message.
|
||||
|
||||
To add a broadcast message:
|
||||
|
||||
1. Navigate to the **Admin Area > Messages** page.
|
||||
1. Add the text for the message to the **Message** field. Markdown and emoji are supported.
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
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 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. Select a date for the message to start and end.
|
||||
1. Click the **Add broadcast message** button.
|
||||
|
||||
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).
|
||||
1. Select **Add broadcast message**.
|
||||
|
||||
NOTE:
|
||||
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)
|
||||
helper method, which generates the proper HTML to render.
|
||||
|
||||
NOTE:
|
||||
Once a broadcast message has expired, it is no longer displayed in the UI but is still listed in the
|
||||
list of broadcast messages. User can also dismiss a broadcast message if the option **Dismissable** is set.
|
||||
When a broadcast message expires, it no longer displays in the user interface but is still listed in the
|
||||
list of broadcast messages.
|
||||
|
||||
## 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:
|
||||
|
||||
1. Navigate to the **Admin Area > Messages** page.
|
||||
1. From the list of broadcast messages, click the appropriate button to edit the message.
|
||||
1. After making the required changes, click the **Update broadcast message** button.
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
1. On the left sidebar, select **Messages**.
|
||||
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.
|
||||
|
||||
## 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:
|
||||
|
||||
1. Navigate to the **Admin Area > Messages** page.
|
||||
1. From the list of broadcast messages, click the appropriate button to delete the message.
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin**.
|
||||
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.
|
||||
|
||||
NOTE:
|
||||
Broadcast messages can be deleted while active.
|
||||
When a broadcast message is deleted, it's removed from the list of broadcast messages.
|
||||
|
||||
<!-- ## Troubleshooting
|
||||
|
||||
|
|
|
@ -34,13 +34,13 @@ is locked.
|
|||
|
||||
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
|
||||
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:*
|
||||
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:
|
||||
|
||||
1. In the top navigation bar, click the **{admin}** wrench icon to navigate to the [Admin Area](index.md).
|
||||
1. Click **License** in the left sidebar.
|
||||
1. Click **Remove License**.
|
||||
1. On the top bar, select **Menu >** **{admin}** **Admin** to go to the [Admin Area](index.md).
|
||||
1. On the left sidebar, select **License**.
|
||||
1. Select **Remove license**.
|
||||
|
||||
## License history
|
||||
|
||||
|
|
|
@ -51,77 +51,20 @@ The browser-based crawler can be configured using CI/CD variables.
|
|||
| CI/CD variable | Type | Example | Description |
|
||||
|--------------------------------------| ----------------| --------------------------------- | ------------|
|
||||
| `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_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_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_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_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_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. |
|
||||
| `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_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_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_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_LOG` | List of strings | `brows:debug,auth:debug` | A list of modules and their intended log level. |
|
||||
|
||||
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.
|
||||
|
||||
#### 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
|
||||
|
||||
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.
|
||||
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
|
||||
|
||||
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.
|
||||
|
|
|
@ -267,7 +267,7 @@ page.
|
|||
|
||||
#### 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.
|
||||
|
||||
|
@ -289,126 +289,6 @@ variables:
|
|||
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/).
|
||||
|
||||
#### 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
|
||||
|
||||
> - [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
|
||||
[Customizing the DAST settings](#customizing-the-dast-settings).
|
||||
|
||||
### Authentication
|
||||
|
||||
It's also possible to authenticate the user before performing the DAST checks.
|
||||
## Authentication
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
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
|
||||
include:
|
||||
- template: DAST.gitlab-ci.yml
|
||||
|
||||
variables:
|
||||
DAST_WEBSITE: https://example.com
|
||||
DAST_AUTH_URL: https://example.com/sign-in
|
||||
DAST_USERNAME_FIELD: session[user] # the name of username field at the sign-in HTML form
|
||||
DAST_PASSWORD_FIELD: session[password] # the name of password field at the sign-in HTML form
|
||||
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_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_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_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:
|
||||
variables:
|
||||
DAST_WEBSITE: "https://example.com"
|
||||
DAST_AUTH_URL: "https://login.example.com/"
|
||||
DAST_USERNAME: "admin"
|
||||
DAST_PASSWORD: "P@55w0rd!"
|
||||
DAST_USERNAME_FIELD: "name:username" # a selector describing the element containing the username field at the sign-in HTML form
|
||||
DAST_PASSWORD_FIELD: "id:password" # a selector describing the element containing the password field at the sign-in HTML form
|
||||
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:
|
||||
|
@ -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.
|
||||
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
|
||||
|
||||
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_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_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_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_PASSWORD` (**1**) | string | The password to authenticate to in the website. |
|
||||
| `DAST_USERNAME_FIELD` (**1**) | string | The name of username field at the sign-in HTML form. |
|
||||
| `DAST_PASSWORD_FIELD` (**1**) | string | The name of password field at the sign-in HTML form. |
|
||||
| `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 enter into the password field on 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**) | 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_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_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_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. |
|
||||
|
@ -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_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_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_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_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` | 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_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_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.
|
||||
|
||||
#### 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
|
||||
|
||||
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
|
||||
[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
|
||||
|
||||
Reports can also be generated in Markdown, HTML, and XML. These can be published as artifacts using the following configuration:
|
||||
|
|
|
@ -219,14 +219,14 @@ There are two ways to resolve/unresolve a Design thread:
|
|||
|
||||
![Resolve checkbox](img/resolve_design-discussion_checkbox_v13_1.png)
|
||||
|
||||
Resolving a discussion thread will also mark any pending to-do related to notes
|
||||
inside the thread as done. This is applicable only for to-dos owned by the user triggering the action.
|
||||
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-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.
|
||||
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.
|
||||
|
||||
## Add to dos for designs
|
||||
## Add to-do items for designs
|
||||
|
||||
> - [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.
|
||||
|
|
|
@ -10,29 +10,39 @@ module BulkImports
|
|||
|
||||
def initialize(context)
|
||||
@context = context
|
||||
@entity = @context.entity
|
||||
@trackers = @entity.trackers
|
||||
end
|
||||
|
||||
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(
|
||||
bulk_import_id: context.bulk_import.id,
|
||||
bulk_import_entity_id: context.entity.id,
|
||||
bulk_import_entity_type: context.entity.source_type,
|
||||
pipeline_class: self.class.name,
|
||||
message: 'Entity finished'
|
||||
message: "Entity #{entity.status_name}"
|
||||
)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :context
|
||||
attr_reader :context, :entity, :trackers
|
||||
|
||||
def logger
|
||||
@logger ||= Gitlab::Import::Logger.build
|
||||
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
|
||||
|
|
|
@ -58,7 +58,7 @@ module Sidebars
|
|||
end
|
||||
|
||||
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)
|
||||
end
|
||||
|
||||
|
|
|
@ -421,6 +421,21 @@ msgstr ""
|
|||
msgid "%{board_target} not found"
|
||||
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."
|
||||
msgstr ""
|
||||
|
||||
|
@ -13045,6 +13060,9 @@ msgstr ""
|
|||
msgid "EscalationPolicies|Add policy"
|
||||
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"
|
||||
msgstr ""
|
||||
|
||||
|
@ -13087,6 +13105,9 @@ msgstr ""
|
|||
msgid "EscalationPolicies|THEN %{doAction} %{schedule}"
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|The escalation policy could not be deleted. Please try again."
|
||||
msgstr ""
|
||||
|
||||
msgid "EscalationPolicies|mins"
|
||||
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}"
|
||||
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."
|
||||
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."
|
||||
msgid "InviteEmail|%{project_or_group} details"
|
||||
msgstr ""
|
||||
|
||||
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"
|
||||
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 ""
|
||||
|
||||
msgid "InviteEmail|What can I do with the %{role} permission level?"
|
||||
msgstr ""
|
||||
|
||||
msgid "InviteEmail|What is a GitLab %{project_or_group}?"
|
||||
msgid "InviteEmail|What's it about?"
|
||||
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}"
|
||||
|
|
|
@ -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
|
|
@ -39,20 +39,14 @@ RSpec.describe Members::InviteEmailExperiment, :clean_gitlab_redis_shared_state
|
|||
allow(instance_1).to receive(:enabled?).and_return(true)
|
||||
instance_2 = described_class.new('members/invite_email', **context)
|
||||
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 { }
|
||||
|
||||
expect(instance_1.variant.name).to eq('permission_info')
|
||||
expect(instance_1.variant.name).to eq('control')
|
||||
|
||||
instance_2.try { }
|
||||
|
||||
expect(instance_2.variant.name).to eq('control')
|
||||
|
||||
instance_3.try { }
|
||||
|
||||
expect(instance_3.variant.name).to eq('avatar')
|
||||
expect(instance_2.variant.name).to eq('activity')
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -19,5 +19,11 @@ FactoryBot.define do
|
|||
|
||||
sequence(:jid) { |n| "bulk_import_entity_#{n}" }
|
||||
end
|
||||
|
||||
trait :failed do
|
||||
status { -1 }
|
||||
|
||||
sequence(:jid) { |n| "bulk_import_entity_#{n}" }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -63,7 +63,7 @@ describe('WikiForm', () => {
|
|||
persisted: true,
|
||||
|
||||
title: 'My page',
|
||||
content: 'My page content',
|
||||
content: ' My page content ',
|
||||
format: 'markdown',
|
||||
path: '/project/path/-/wikis/home',
|
||||
};
|
||||
|
@ -129,6 +129,12 @@ describe('WikiForm', () => {
|
|||
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`
|
||||
value | text
|
||||
${'markdown'} | ${'[Link Title](page-slug)'}
|
||||
|
@ -183,10 +189,10 @@ describe('WikiForm', () => {
|
|||
|
||||
describe('when wiki content is updated', () => {
|
||||
beforeEach(() => {
|
||||
createWrapper();
|
||||
createWrapper(true);
|
||||
|
||||
const input = findContent();
|
||||
input.setValue('Lorem ipsum dolar sit!');
|
||||
input.setValue(' Lorem ipsum dolar sit! ');
|
||||
input.element.dispatchEvent(new Event('input'));
|
||||
|
||||
return wrapper.vm.$nextTick();
|
||||
|
@ -213,6 +219,10 @@ describe('WikiForm', () => {
|
|||
it('does not trigger tracking event', async () => {
|
||||
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 () => {
|
||||
// old value
|
||||
expect(findContent().element.value).toBe('My page content');
|
||||
expect(findContent().element.value).toBe(' My page content ');
|
||||
|
||||
// wait for content editor to load
|
||||
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', () => {
|
||||
expect(findContent().element.value).toBe('My page content');
|
||||
expect(findContent().element.value).toBe(' My page content ');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -28,27 +28,12 @@ RSpec.describe NotifyHelper do
|
|||
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
|
||||
where(:source, :description) do
|
||||
"project" | /Projects can/
|
||||
"group" | /Groups assemble/
|
||||
build(:project, description: nil) | /Projects are/
|
||||
build(:group, description: nil) | /Groups assemble/
|
||||
build(:project, description: '_description_') | '_description_'
|
||||
build(:group, description: '_description_') | '_description_'
|
||||
end
|
||||
|
||||
with_them do
|
||||
|
@ -56,6 +41,15 @@ RSpec.describe NotifyHelper do
|
|||
expect(helper.invited_to_description(source)).to match description
|
||||
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
|
||||
|
||||
def reference_link(entity, url)
|
||||
|
|
|
@ -7,8 +7,15 @@ RSpec.describe 'Database config initializer' do
|
|||
load Rails.root.join('config/initializers/database_config.rb')
|
||||
end
|
||||
|
||||
around do |example|
|
||||
original_config = ActiveRecord::Base.connection_db_config
|
||||
|
||||
example.run
|
||||
|
||||
ActiveRecord::Base.establish_connection(original_config)
|
||||
end
|
||||
|
||||
before do
|
||||
allow(ActiveRecord::Base).to receive(:establish_connection)
|
||||
allow(Gitlab::Runtime).to receive(:max_threads).and_return(max_threads)
|
||||
end
|
||||
|
||||
|
@ -21,6 +28,8 @@ RSpec.describe 'Database config initializer' 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(ActiveRecord::Base.connection_db_config.pool).to eq(18)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -31,6 +40,8 @@ RSpec.describe 'Database config initializer' 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(ActiveRecord::Base.connection_db_config.pool).to eq(18)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -41,6 +52,8 @@ RSpec.describe 'Database config initializer' 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(ActiveRecord::Base.connection_db_config.pool).to eq(18)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -56,15 +69,16 @@ RSpec.describe 'Database config initializer' do
|
|||
expect { subject }.to change { Gitlab::Database.config['pool'] }
|
||||
.from(1)
|
||||
.to(max_threads + headroom)
|
||||
|
||||
expect(ActiveRecord::Base.connection_db_config.pool).to eq(max_threads + headroom)
|
||||
end
|
||||
end
|
||||
|
||||
def stub_database_config(pool_size:)
|
||||
config = {
|
||||
'adapter' => 'postgresql',
|
||||
'host' => 'db.host.com',
|
||||
'pool' => pool_size
|
||||
}.compact
|
||||
original_config = Gitlab::Database.config
|
||||
|
||||
config = original_config.dup
|
||||
config['pool'] = pool_size
|
||||
|
||||
allow(Gitlab::Database).to receive(:config).and_return(config)
|
||||
end
|
||||
|
|
|
@ -25,13 +25,33 @@ RSpec.describe BulkImports::Groups::Pipelines::EntityFinisher do
|
|||
.to change(entity, :status_name).to(:finished)
|
||||
end
|
||||
|
||||
it 'does nothing when the entity is already finished' do
|
||||
entity = create(:bulk_import_entity, :finished)
|
||||
pipeline_tracker = create(:bulk_import_tracker, entity: entity)
|
||||
context = BulkImports::Pipeline::Context.new(pipeline_tracker)
|
||||
subject = described_class.new(context)
|
||||
context 'when entity is in a final finished or failed state' do
|
||||
shared_examples 'performs no state update' do |entity_state|
|
||||
it 'does nothing' do
|
||||
entity = create(:bulk_import_entity, entity_state)
|
||||
pipeline_tracker = create(:bulk_import_tracker, entity: entity)
|
||||
context = BulkImports::Pipeline::Context.new(pipeline_tracker)
|
||||
subject = described_class.new(context)
|
||||
|
||||
expect { subject.run }
|
||||
.not_to change(entity, :status_name)
|
||||
expect { subject.run }
|
||||
.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
|
||||
|
|
|
@ -790,7 +790,7 @@ RSpec.describe Notify do
|
|||
it_behaves_like 'appearance header and footer not enabled'
|
||||
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
|
||||
is_expected.to have_subject "#{inviter.name} invited you to join GitLab"
|
||||
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))
|
||||
end
|
||||
|
||||
it 'contains invite link for the avatar' do
|
||||
stub_experiments('members/invite_email': :avatar)
|
||||
it 'contains invite link for the group activity' do
|
||||
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_body_text 'What is a GitLab'
|
||||
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
|
||||
stub_experiments('members/invite_email': :control)
|
||||
|
||||
|
@ -821,7 +816,7 @@ RSpec.describe Notify do
|
|||
end
|
||||
end
|
||||
|
||||
context 'when there is no inviter' do
|
||||
context 'when there is no inviter', :aggregate_failures do
|
||||
let(:inviter) { nil }
|
||||
|
||||
it 'contains all the useful information' do
|
||||
|
|
|
@ -2617,7 +2617,7 @@ RSpec.describe Group do
|
|||
context 'with export' do
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -2625,4 +2625,54 @@ RSpec.describe Group do
|
|||
expect(group.export_archive_exists?).to be true
|
||||
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
|
||||
|
|
|
@ -994,6 +994,39 @@ RSpec.describe Project, factory_default: :keep do
|
|||
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
|
||||
let_it_be(:project) { create(:project) }
|
||||
|
||||
|
|
|
@ -21,7 +21,8 @@ JS_CONSOLE_FILTER = Regexp.union([
|
|||
'"[WDS] Hot Module Replacement enabled."',
|
||||
'"[WDS] Live Reloading enabled."',
|
||||
'Download the Vue Devtools extension',
|
||||
'Download the Apollo DevTools'
|
||||
'Download the Apollo DevTools',
|
||||
"Unrecognized feature: 'interest-cohort'"
|
||||
])
|
||||
|
||||
CAPYBARA_WINDOW_SIZE = [1366, 768].freeze
|
||||
|
|
|
@ -22,6 +22,16 @@ RSpec.describe BulkImportWorker do
|
|||
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
|
||||
it 'marks bulk import as finished' do
|
||||
bulk_import = create(:bulk_import, :started)
|
||||
|
@ -34,6 +44,18 @@ RSpec.describe BulkImportWorker do
|
|||
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
|
||||
it 'reenqueues itself' do
|
||||
bulk_import = create(:bulk_import, :started)
|
||||
|
|
Loading…
Reference in a new issue