Commit d59718db authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab master

parents 9d7e8c0e c0753f25
<script>
import { GlIcon, GlPopover } from '@gitlab/ui';
import { __ } from '~/locale';
export default {
i18n: {
issueTypes: __('Issue types'),
issue: __('Issue'),
incident: __('Incident'),
issueHelpText: __('For general work'),
incidentHelpText: __('For investigating IT service disruptions or outages'),
},
components: {
GlIcon,
GlPopover,
},
};
</script>
<template>
<span id="popovercontainer">
<gl-icon id="issuable-type-info" name="question-o" class="gl-ml-5 gl-text-gray-500" />
<gl-popover
target="issuable-type-info"
container="popovercontainer"
:title="$options.i18n.issueTypes"
triggers="focus hover"
>
<ul class="gl-list-style-none gl-p-0 gl-m-0">
<li class="gl-mb-3">
<div class="gl-font-weight-bold">{{ $options.i18n.issue }}</div>
<span>{{ $options.i18n.issueHelpText }}</span>
</li>
<li>
<div class="gl-font-weight-bold">{{ $options.i18n.incident }}</div>
<span>{{ $options.i18n.incidentHelpText }}</span>
</li>
</ul>
</gl-popover>
</span>
</template>
import Vue from 'vue';
import InfoPopover from './components/info_popover.vue';
export default function initIssuableTypeSelector() {
const el = document.getElementById('js-type-popover');
return new Vue({
el,
components: {
InfoPopover,
},
render(h) {
return h(InfoPopover);
},
});
}
......@@ -5,6 +5,7 @@ import IssuableForm from 'ee_else_ce/issuable_form';
import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
import GLForm from '~/gl_form';
import initSuggestions from '~/issuable_suggestions';
import initIssuableTypeSelector from '~/issuable_type_selector';
import LabelsSelect from '~/labels_select';
import MilestoneSelect from '~/milestone_select';
import IssuableTemplateSelectors from '~/templates/issuable_template_selectors';
......@@ -20,4 +21,5 @@ export default () => {
});
initSuggestions();
initIssuableTypeSelector();
};
......@@ -97,7 +97,7 @@ class Projects::PipelinesController < Projects::ApplicationController
Gitlab::QueryLimiting.disable!('https://gitlab.com/gitlab-org/gitlab/-/issues/26657')
respond_to do |format|
format.html
format.html { render_show }
format.json do
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
......@@ -187,10 +187,7 @@ class Projects::PipelinesController < Projects::ApplicationController
def test_report
respond_to do |format|
format.html do
render 'show'
end
format.html { render_show }
format.json do
render json: TestReportSerializer
.new(current_user: @current_user)
......@@ -219,6 +216,8 @@ class Projects::PipelinesController < Projects::ApplicationController
end
def render_show
@stages = @pipeline.stages.with_latest_and_retried_statuses
respond_to do |format|
format.html do
render 'show'
......
......@@ -14,6 +14,7 @@ module Ci
has_many :statuses, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :latest_statuses, -> { ordered.latest }, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :retried_statuses, -> { ordered.retried }, class_name: 'CommitStatus', foreign_key: :stage_id
has_many :processables, class_name: 'Ci::Processable', foreign_key: :stage_id
has_many :builds, foreign_key: :stage_id
has_many :bridges, foreign_key: :stage_id
......@@ -21,6 +22,12 @@ module Ci
scope :ordered, -> { order(position: :asc) }
scope :in_pipelines, ->(pipelines) { where(pipeline: pipelines) }
scope :by_name, ->(names) { where(name: names) }
scope :with_latest_and_retried_statuses, -> do
includes(
latest_statuses: [:pipeline, project: :namespace],
retried_statuses: [:pipeline, project: :namespace]
)
end
with_options unless: :importing? do
validates :project, presence: true
......
......@@ -28,7 +28,7 @@
#js-pipeline-graph-vue
#js-tab-builds.tab-pane
- if pipeline.legacy_stages.present?
- if stages.present?
.table-holder.pipeline-holder
%table.table.ci-table.pipeline
%thead
......@@ -39,7 +39,7 @@
%th
%th= _('Coverage')
%th
= render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
= render partial: "projects/stage/stage", collection: stages, as: :stage
- if @pipeline.failed_builds.present?
#js-tab-failures.build-failures.tab-pane.build-page
......
......@@ -24,6 +24,6 @@
- lint_link_start = '<a href="%{url}">'.html_safe % { url: lint_link_url }
= s_('You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}').html_safe % { gitlab_ci_yml: '.gitlab-ci.yml', lint_link_start: lint_link_start, lint_link_end: '</a>'.html_safe }
= render "projects/pipelines/with_tabs", pipeline: @pipeline, pipeline_has_errors: pipeline_has_errors
= render "projects/pipelines/with_tabs", pipeline: @pipeline, stages: @stages, pipeline_has_errors: pipeline_has_errors
.js-pipeline-details-vue{ data: { endpoint: project_pipeline_path(@project, @pipeline, format: :json), metrics_path: namespace_project_ci_prometheus_metrics_histograms_path(namespace_id: @project.namespace, project_id: @project, format: :json), pipeline_project_path: @project.full_path, pipeline_iid: @pipeline.iid, graphql_resource_etag: graphql_etag_pipeline_path(@pipeline) } }
- stage = stage.present(current_user: current_user)
%tr
%th{ colspan: 10 }
%strong
......@@ -8,8 +6,8 @@
= ci_icon_for_status(stage.status)
&nbsp;
= stage.name.titleize
= render stage.latest_ordered_statuses, stage: false, ref: false, pipeline_link: false, allow_retry: true
= render stage.retried_ordered_statuses, stage: false, ref: false, pipeline_link: false, retried: true
= render stage.latest_statuses, stage: false, ref: false, pipeline_link: false, allow_retry: true
= render stage.retried_statuses, stage: false, ref: false, pipeline_link: false, retried: true
%tr
%td{ colspan: 10 }
&nbsp;
......@@ -3,26 +3,30 @@
.form-group.row.gl-mb-0
= form.label :type, 'Type', class: 'col-form-label col-sm-2'
.col-sm-10
.issuable-form-select-holder.selectbox.form-group.gl-mb-0
.dropdown.js-issuable-type-filter-dropdown-wrap
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.dropdown-toggle-text.is-default
= issuable.issue_type.capitalize || _("Select type")
= sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
.dropdown-menu.dropdown-menu-selectable.dropdown-select
.dropdown-title.gl-display-flex
%span.gl-ml-auto
= _("Select type")
%button.dropdown-title-button.dropdown-menu-close.gl-ml-auto{ type: 'button', "aria-label" => _('Close') }
= sprite_icon('close', size: 16, css_class: 'dropdown-menu-close-icon')
.dropdown-content
%ul
%li.js-filter-issuable-type
= link_to new_project_issue_path(@project), class: ("is-active" if issuable.issue?) do
= _("Issue")
%li.js-filter-issuable-type{ data: { track: { event: "select_issue_type_incident", label: "select_issue_type_incident_dropdown_option" } } }
= link_to new_project_issue_path(@project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }), class: ("is-active" if issuable.incident?) do
= _("Incident")
.gl-display-flex.gl-align-items-center
.issuable-form-select-holder.selectbox.form-group.gl-mb-0
.dropdown.js-issuable-type-filter-dropdown-wrap
%button.dropdown-menu-toggle{ type: 'button', 'data-toggle' => 'dropdown' }
%span.dropdown-toggle-text.is-default
= issuable.issue_type.capitalize || _("Select type")
= sprite_icon('chevron-down', css_class: "dropdown-menu-toggle-icon gl-top-3")
.dropdown-menu.dropdown-menu-selectable.dropdown-select
.dropdown-title.gl-display-flex
%span.gl-ml-auto
= _("Select type")
%button.dropdown-title-button.dropdown-menu-close.gl-ml-auto{ type: 'button', "aria-label" => _('Close') }
= sprite_icon('close', size: 16, css_class: 'dropdown-menu-close-icon')
.dropdown-content
%ul
%li.js-filter-issuable-type
= link_to new_project_issue_path(@project), class: ("is-active" if issuable.issue?) do
= _("Issue")
%li.js-filter-issuable-type{ data: { track: { event: "select_issue_type_incident", label: "select_issue_type_incident_dropdown_option" } } }
= link_to new_project_issue_path(@project, { issuable_template: 'incident', issue: { issue_type: 'incident' } }), class: ("is-active" if issuable.incident?) do
= _("Incident")
#js-type-popover
- if issuable.incident?
%p.form-text.text-muted
- incident_docs_url = help_page_path('operations/incident_management/incidents.md')
......
---
title: Clarify the impact of selecting incidents in the new issue form
merge_request: 57373
author:
type: added
---
title: Updated UI text to match style guidelines
merge_request: 57884
author:
type: other
---
stage: Configure
group: Configure
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Install the Kubernetes Agent Server (KAS) **(PREMIUM SELF)**
The Kubernetes Agent Server (KAS) is a GitLab backend service dedicated to
managing [Kubernetes Agents](../../user/clusters/agent/index.md).
The KAS is already installed and available in GitLab.com under `wss://kas.gitlab.com`.
See [how to use GitLab.com's KAS](../../user/clusters/agent/index.md#set-up-the-kubernetes-agent-server).
This document describes how to install a KAS for GitLab self-managed instances.
## Installation options
As a GitLab administrator of self-managed instances, you can install KAS according to your GitLab
installation method:
- For [Omnibus installations](#install-kas-with-omnibus).
- For [GitLab Helm Chart installations](#install-kas-with-the-gitlab-helm-chart).
You can also opt to use an [external KAS](#use-an-external-kas-installation).
### Install KAS with Omnibus
For [Omnibus](https://docs.gitlab.com/omnibus/) package installations:
1. Edit `/etc/gitlab/gitlab.rb` to enable the Kubernetes Agent Server:
```ruby
gitlab_kas['enable'] = true
```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
To configure any additional options related to your KAS,
refer to the **Enable GitLab KAS** section of the
[`gitlab.rb.template`](https://gitlab.com/gitlab-org/omnibus-gitlab/-/blob/master/files/gitlab-config-template/gitlab.rb.template).
### Install KAS with the GitLab Helm Chart
For GitLab [Helm Chart](https://docs.gitlab.com/charts/)
installations, you must set `global.kas.enabled` to `true`.
For example, in a shell with `helm` and `kubectl`
installed, run:
```shell
helm repo add gitlab https://charts.gitlab.io/
helm repo update
helm upgrade --install gitlab gitlab/gitlab \
--timeout 600s \
--set global.hosts.domain=<YOUR_DOMAIN> \
--set global.hosts.externalIP=<YOUR_IP> \
--set certmanager-issuer.email=<YOUR_EMAIL> \
--set global.kas.enabled=true # <-- without this, KAS will not be installed
```
To configure KAS, use a `gitlab.kas` sub-section in your `values.yaml` file:
```yaml
gitlab:
kas:
# put your KAS custom options here
```
For details, see [how to use the GitLab-KAS chart](https://docs.gitlab.com/charts/charts/gitlab/kas/).
### Use an external KAS installation
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299850) in GitLab 13.10.
Besides installing KAS with GitLab, you can opt to configure GitLab to use an external KAS.
For GitLab instances installed through the GitLab Helm Chart, see [how to configure your external KAS](https://docs.gitlab.com/charts/charts/globals.html#external-kas).
For GitLab instances installed through Omnibus packages:
1. Edit `/etc/gitlab/gitlab.rb` adding the paths to your external KAS:
```ruby
gitlab_kas['enable'] = false
gitlab_kas['api_secret_key'] = 'Your shared secret between GitLab and KAS'
gitlab_rails['gitlab_kas_enabled'] = true
gitlab_rails['gitlab_kas_external_url'] = 'wss://kas.gitlab.example.com' # User-facing URL for the in-cluster agentk
gitlab_rails['gitlab_kas_internal_url'] = 'grpc://kas.internal.gitlab.example.com' # Internal URL for the GitLab backend
```
1. [Reconfigure GitLab](../restart_gitlab.md#omnibus-gitlab-reconfigure).
## Troubleshooting
If you face any issues with KAS, you can read the service logs
with the following command:
```shell
kubectl logs -f -l=app=kas -n <YOUR-GITLAB-NAMESPACE>
```
In Omnibus GitLab, find the logs in `/var/log/gitlab/gitlab-kas/`.
See also the [user documentation](../../user/clusters/agent/index.md#troubleshooting)
for troubleshooting problems with individual agents.
### KAS logs - GitOps: failed to get project info
If you get the following error message:
```json
{"level":"warn","time":"2020-10-30T08:37:26.123Z","msg":"GitOps: failed to get project info","agent_id":4,"project_id":"root/kas-manifest001","error":"error kind: 0; status: 404"}
```
It means that the specified manifest project `root/kas-manifest001`
doesn't exist or the manifest project is private. To fix it, make sure the project path is correct
and its visibility is [set to public](../../public_access/public_access.md).
### KAS logs - Configuration file not found
If you get the following error message:
```plaintext
time="2020-10-29T04:44:14Z" level=warning msg="Config: failed to fetch" agent_id=2 error="configuration file not found: \".gitlab/agents/test-agent/config.yaml\
```
It means that the path to the configuration project is incorrect,
or the path to `config.yaml` inside the project is not valid.
To fix this, ensure that the paths to the configuration repo and to the `config.yaml` file
are correct.
......@@ -13,6 +13,9 @@ type: reference, api
> - Not recommended for production use.
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-dora4-analytics-group-api).
WARNING:
These endpoints are deprecated and will be removed in GitLab 14.0. Use the [DORA metrics API](dora/metrics.md) instead.
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
......
......@@ -9,6 +9,9 @@ type: reference, api
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/279039) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.7.
WARNING:
These endpoints are deprecated and will be removed in GitLab 14.0. Use the [DORA metrics API](dora/metrics.md) instead.
All methods require reporter authorization.
## List project deployment frequencies
......
......@@ -376,6 +376,7 @@ Returns [`VulnerabilityConnection`](#vulnerabilityconnection).
| `projectId` | [`[ID!]`](#id) | Filter vulnerabilities by project. |
| `reportType` | [`[VulnerabilityReportType!]`](#vulnerabilityreporttype) | Filter vulnerabilities by report type. |
| `scanner` | [`[String!]`](#string) | Filter vulnerabilities by VulnerabilityScanner.externalId. |
| `scannerId` | [`[Int!]`](#int) | Filter vulnerabilities by scanner ID. |
| `severity` | [`[VulnerabilitySeverity!]`](#vulnerabilityseverity) | Filter vulnerabilities by severity. |
| `sort` | [`VulnerabilitySort`](#vulnerabilitysort) | List vulnerabilities by sort order. |
| `state` | [`[VulnerabilityState!]`](#vulnerabilitystate) | Filter vulnerabilities by state. |
......@@ -4745,7 +4746,7 @@ An edge in a connection.
| `alertManagementIntegrations` | [`AlertManagementIntegrationConnection`](#alertmanagementintegrationconnection) | Integrations which can receive alerts for the project. |
| `alertManagementPayloadFields` | [`[AlertManagementPayloadAlertField!]`](#alertmanagementpayloadalertfield) | Extract alert fields from payload for custom mapping. |
| `allowMergeOnSkippedPipeline` | [`Boolean`](#boolean) | If `only_allow_merge_if_pipeline_succeeds` is true, indicates if merge requests of the project can also be merged with skipped jobs. |
| `apiFuzzingCiConfiguration` | [`ApiFuzzingCiConfiguration`](#apifuzzingciconfiguration) | API fuzzing configuration for the project. Null unless feature flag `api_fuzzing_configuration_ui` is enabled. |
| `apiFuzzingCiConfiguration` | [`ApiFuzzingCiConfiguration`](#apifuzzingciconfiguration) | API fuzzing configuration for the project. |
| `archived` | [`Boolean`](#boolean) | Indicates the archived status of the project. |
| `autocloseReferencedIssues` | [`Boolean`](#boolean) | Indicates if issues referenced by merge requests and commits within the default branch are closed automatically. |
| `avatarUrl` | [`String`](#string) | URL to avatar image file of the project. |
......
......@@ -645,7 +645,7 @@ DELETE /projects/:id/external_approval_rules/:rule_id
You can update an existing external approval rule for a project using the following endpoint:
```plaintext
PATCH /projects/:id/external_approval_rules/:rule_id
PUT /projects/:id/external_approval_rules/:rule_id
```
| Attribute | Type | Required | Description |
......
......@@ -74,10 +74,6 @@ starting in GitLab 14.0, GitLab will not check your repository's root for config
### Configuration form
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/299234) in GitLab 13.10.
> - It's [deployed behind a feature flag](../../../user/feature_flags.md), enabled by default.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can opt to [disable it](#enable-or-disable-api-fuzzing-configuration-form). **(ULTIMATE)**
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
......@@ -103,25 +99,6 @@ to your project's `.gitlab-ci.yml` file where you can paste the YAML configurati
Select **Copy code only** to copy the snippet to your clipboard and close the modal.
#### Enable or disable API Fuzzing configuration form **(ULTIMATE)**
The API Fuzzing configuration form is under development but ready for production use.
It is deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can opt to disable it.
To enable it:
```ruby
Feature.enable(:api_fuzzing_configuration_ui)
```
To disable it:
```ruby
Feature.disable(:api_fuzzing_configuration_ui)
```
### OpenAPI Specification
> Support for OpenAPI Specification v3 was
......
This diff is collapsed.
......@@ -8,6 +8,7 @@ query instance(
$severity: [VulnerabilitySeverity!]
$reportType: [VulnerabilityReportType!]
$scanner: [String!]
$scannerId: [Int!]
$state: [VulnerabilityState!]
$sort: VulnerabilitySort
$hasIssues: Boolean
......@@ -21,6 +22,7 @@ query instance(
state: $state
projectId: $projectId
scanner: $scanner
scannerId: $scannerId
sort: $sort
hasIssues: $hasIssues
hasResolution: $hasResolution
......
......@@ -31,7 +31,7 @@ export default {
'StatusPage|Configure file storage settings to link issues in this project to an external status page.',
),
introText: s__(
'StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}',
'StatusPage|To publish incidents to an external status page, GitLab stores a JSON file in your Amazon S3 account at a location that your external status page service can access. Make sure to also set up %{docsLink}',
),
introLinkText: s__('StatusPage|your status page frontend.'),
activeLabel: s__('StatusPage|Active'),
......@@ -46,8 +46,8 @@ export default {
},
region: {
label: s__('StatusPage|AWS region'),
helpText: s__('StatusPage|For help with configuration, visit %{docsLink}'),
linkText: s__('StatusPage|AWS documentation'),
helpText: s__('StatusPage|AWS %{docsLink}'),
linkText: s__('StatusPage|configuration documentation'),
},
accessKey: {
label: s__('StatusPage|AWS access key ID'),
......
......@@ -14,7 +14,6 @@ module EE
before_action only: [:show] do
push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false)
push_frontend_feature_flag(:api_fuzzing_configuration_ui, project, default_enabled: :yaml)
push_frontend_feature_flag(:sec_dependency_scanning_ui_enable, project, default_enabled: :yaml)
end
......
......@@ -11,7 +11,6 @@ module Projects
feature_category :fuzz_testing
def show
not_found unless Feature.enabled?(:api_fuzzing_configuration_ui, @project, default_enabled: :yaml)
end
end
end
......
......@@ -67,8 +67,8 @@ module Security
end
def filter_by_scanner_ids
if params[:scanner_ids].present?
@vulnerabilities = vulnerabilities.by_scanner_ids(params[:scanner_ids])
if params[:scanner_id].present?
@vulnerabilities = vulnerabilities.by_scanner_ids(params[:scanner_id])
end
end
......
......@@ -133,8 +133,7 @@ module EE
field :api_fuzzing_ci_configuration,
::Types::AppSec::Fuzzing::Api::CiConfigurationType,
null: true,
description: 'API fuzzing configuration for the project. '\
'Null unless feature flag `api_fuzzing_configuration_ui` is enabled.'
description: 'API fuzzing configuration for the project. '
field :push_rules,
::Types::PushRulesType,
......@@ -144,8 +143,7 @@ module EE
end
def api_fuzzing_ci_configuration
return unless ::Feature.enabled?(:api_fuzzing_configuration_ui, object, default_enabled: :yaml) && \
Ability.allowed?(current_user, :read_vulnerability, object)
return unless Ability.allowed?(current_user, :read_vulnerability, object)
configuration = ::AppSec::Fuzzing::Api::CiConfiguration.new(project: object)
......
......@@ -53,8 +53,6 @@ module Mutations
def resolve(args)
project = authorized_find!(args[:project_path])
raise_feature_off_error unless feature_enabled?(project)
create_service = ::AppSec::Fuzzing::Api::CiConfigurationCreateService.new(
container: project, current_user: current_user, params: args
)
......@@ -72,10 +70,6 @@ module Mutations
raise ::Gitlab::Graphql::Errors::ResourceNotAvailable,
'The API fuzzing CI configuration feature is off'
end
def feature_enabled?(project)
Feature.enabled?(:api_fuzzing_configuration_ui, project, default_enabled: :yaml)
end
end
end
end
......
......@@ -42,29 +42,35 @@ module Mutations
authorize :create_on_demand_dast_scan
def resolve(full_path:, profile_name:, target_url: nil, excluded_urls: [], request_headers: nil, auth: nil)
def resolve(full_path:, profile_name:, target_url: nil, **params)
project = authorized_find!(full_path)
service = ::DastSiteProfiles::CreateService.new(project, current_user)
result = service.execute(
auth_params = feature_flagged(project, params[:auth], default: {})
dast_site_profile_params = {
name: profile_name,
target_url: target_url,
excluded_urls: feature_flagged_excluded_urls(project, excluded_urls)
)
if result.success?
{ id: result.payload.to_global_id, errors: [] }
else
{ errors: result.errors }
end
excluded_urls: feature_flagged(project, params[:excluded_urls]),
request_headers: feature_flagged(project, params[:request_headers]),
auth_enabled: auth_params[:enabled],
auth_url: auth_params[:url],
auth_username_field: auth_params[:username_field],
auth_password_field: auth_params[:password_field],
auth_username: auth_params[:username],
auth_password: auth_params[:password]
}.compact
result = ::DastSiteProfiles::CreateService.new(project, current_user).execute(**dast_site_profile_params)
{ id: result.payload.try(:to_global_id), errors: result.errors }
end
private
def feature_flagged_excluded_urls(project, excluded_urls)
return [] unless Feature.enabled?(:security_dast_site_profiles_additional_fields, project, default_enabled: :yaml)
def feature_flagged(project, value, opts = {})
return opts[:default] unless Feature.enabled?(:security_dast_site_profiles_additional_fields, project, default_enabled: :yaml)
excluded_urls
value || opts[:default]
end
end
end
......
......@@ -26,6 +26,10 @@ module Resolvers
required: false,
description: 'Filter vulnerabilities by VulnerabilityScanner.externalId.'
argument :scanner_id, [GraphQL::INT_TYPE],
required: false,
description: 'Filter vulnerabilities by scanner ID.'
argument :sort, Types::VulnerabilitySortEnum,
required: false,
default_value: 'severity_desc',
......
......@@ -91,7 +91,7 @@ module Projects
{
sast: project_security_configuration_sast_path(project),
dast_profiles: project_security_configuration_dast_scans_path(project),
api_fuzzing: ::Feature.enabled?(:api_fuzzing_configuration_ui, project, default_enabled: :yaml) ? project_security_configuration_api_fuzzing_path(project) : nil
api_fuzzing: project_security_configuration_api_fuzzing_path(project)
}[type]
end
end
......
# frozen_string_literal: true
module Dast
module SiteProfileSecretVariables
class CreateOrUpdateService < BaseContainerService
def execute
return error_response('Insufficient permissions') unless allowed?
return error_response('Dast site profile param is missing') unless site_profile
return error_response('Key param is missing') unless key
return error_response('Raw value param is missing') unless raw_value
secret_variable = find_or_create_secret_variable
return error_response(secret_variable.errors.full_messages) unless secret_variable.valid? && secret_variable.persisted?
success_response(secret_variable)
end
private
def allowed?
Feature.enabled?(:security_dast_site_profiles_additional_fields, container, default_enabled: :yaml) &&
Ability.allowed?(current_user, :create_on_demand_dast_scan, container)
end
def site_profile
params[:dast_site_profile]
end
def key
params[:key]
end
def raw_value
params[:raw_value]
end
def success_response(secret_variable)
ServiceResponse.success(payload: secret_variable)
end
def error_response(message)
ServiceResponse.error(message: message)
end
# rubocop: disable CodeReuse/ActiveRecord
def find_or_create_secret_variable
secret_variable = Dast::SiteProfileSecretVariable.find_or_initialize_by(dast_site_profile: site_profile, key: key)
secret_variable.update(raw_value: raw_value)
secret_variable
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
......@@ -2,25 +2,33 @@
module DastSiteProfiles
class CreateService < BaseService
def execute(name:, target_url:, excluded_urls: [])
class Rollback < StandardError
attr_reader :errors
def initialize(errors)
@errors = errors
end
end
attr_reader :dast_site_profile
def execute(name:, target_url:, **params)
return ServiceResponse.error(message: 'Insufficient permissions') unless allowed?
ActiveRecord::Base.transaction do
service = DastSites::FindOrCreateService.new(project, current_user)
dast_site = service.execute!(url: target_url)
dast_site = DastSites::FindOrCreateService.new(project, current_user).execute!(url: target_url)
params.merge!(project: project, dast_site: dast_site, name: name).compact!
dast_site_profile = DastSiteProfile.create!(
project: project,
dast_site: dast_site,
name: name,
excluded_urls: excluded_urls || []
)
@dast_site_profile = DastSiteProfile.create!(params.except(:request_headers, :auth_password))
create_secret_variable!(Dast::SiteProfileSecretVariable::PASSWORD, params[:auth_password])
create_secret_variable!(Dast::SiteProfileSecretVariable::REQUEST_HEADERS, params[:request_headers])
ServiceResponse.success(payload: dast_site_profile)
end
rescue ActiveRecord::RecordInvalid => err
ServiceResponse.error(message: err.record.errors.full_messages)
rescue Rollback => e
ServiceResponse.error(message: e.errors)
rescue ActiveRecord::RecordInvalid => e
ServiceResponse.error(message: e.record.errors.full_messages)
end
private
......@@ -28,5 +36,19 @@ module DastSiteProfiles
def allowed?
Ability.allowed?(current_user, :create_on_demand_dast_scan, project)
end
def create_secret_variable!(key, value)
return ServiceResponse.success unless value
response = Dast::SiteProfileSecretVariables::CreateOrUpdateService.new(
container: project,
current_user: current_user,
params: { dast_site_profile: dast_site_profile, key: key, raw_value: value }
).execute
raise Rollback, response.errors if response.error?
response
end
end
end
......@@ -9,8 +9,8 @@
# returns true, false
module GitlabSubscriptions
class CheckFutureRenewalService
def initialize(namespace_id:)
@namespace_id = namespace_id
def initialize(namespace:)
@namespace = namespace
end
def execute
......@@ -21,14 +21,14 @@ module GitlabSubscriptions
private
attr_reader :namespace_id
attr_reader :namespace
def client
Gitlab::SubscriptionPortal::Client
end
def last_term_request
response = client.subscription_last_term(namespace_id)
response = client.subscription_last_term(namespace.id)
if response[:success]
response[:last_term] == false
......@@ -42,7 +42,7 @@ module GitlabSubscriptions
end
def cache_key
"subscription:future_renewal:namespace:#{namespace_id}"
"subscription:future_renewal:namespace:#{namespace.gitlab_subscription.cache_key}"
end
def future_renewal
......
---
title: Add GraphQL field for vulnerability scanner ID
merge_request: 56041
author:
type: changed
---
title: Remove the api_fuzzing_configuration_ui feature flag
merge_request: 57583
author:
type: changed
---
name: api_fuzzing_configuration_ui
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51940
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299234
milestone: '13.9'
type: development
group: group::fuzz testing
default_enabled: true
......@@ -6,32 +6,38 @@ module API
feature_category :source_code_management
before { authenticate! }
before { user_project }
before { check_feature_enabled!(@project) }
before do
authenticate!
check_feature_enabled!
end
helpers do
def check_feature_enabled!(project)
unauthorized! unless project.feature_available?(:compliance_approval_gates) &&
Feature.enabled?(:ff_compliance_approval_gates, project)
def check_feature_enabled!
unauthorized! unless user_project.feature_available?(:compliance_approval_gates) &&
Feature.enabled?(:ff_compliance_approval_gates, user_project)
end
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
segment ':id/external_approval_rules' do
params do
requires :name, type: String, desc: 'The name of the rule'
requires :external_url, type: String, desc: 'The URL to notify when MR receives new commits'
optional :protected_branch_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The protected branch ids for this rule'
end
desc 'Create a new external approval rule' do
success ::API::Entities::ExternalApprovalRule
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :name, type: String, desc: 'The name of the external approval rule'
requires :external_url, type: String, desc: 'The URL to notify when MR receives new commits'
optional :protected_branch_ids,
type: Array[Integer],
coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce,
desc: 'The protected branch ids for this rule'
end
post do
service = ::ExternalApprovalRules::CreateService.new(container: @project,
current_user: current_user,
params: declared(params, include_missing: false)).execute
service = ::ExternalApprovalRules::CreateService.new(
container: user_project,
current_user: current_user,
params: declared_params(include_missing: false)
).execute
if service.success?
present service.payload[:rule], with: ::API::Entities::ExternalApprovalRule
......@@ -47,42 +53,48 @@ module API
use :pagination
end
get do
unauthorized! unless current_user.can?(:admin_project, @project)
unauthorized! unless current_user.can?(:admin_project, user_project)
present paginate(@project.external_approval_rules), with: ::API::Entities::ExternalApprovalRule
present paginate(user_project.external_approval_rules), with: ::API::Entities::ExternalApprovalRule
end
segment ':rule_id' do
desc 'Delete an external approval rule' do
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :rule_id, type: Integer, desc: 'The approval rule ID'
requires :rule_id, type: Integer, desc: 'The ID of the external approval rule'
end
delete do
external_approval_rule = user_project.external_approval_rules.find(params[:rule_id])
destroy_conditionally!(external_approval_rule) do |external_approval_rule|
::ExternalApprovalRules::DestroyService.new(
container: @project,
container: user_project,
current_user: current_user
).execute(external_approval_rule)
end
end
desc 'Update new external approval rule' do
desc 'Update an external approval rule' do
success ::API::Entities::ExternalApprovalRule
detail 'This feature is gated by the :ff_compliance_approval_gates feature flag.'
end
params do
requires :rule_id, type: Integer, desc: 'The approval rule ID'
optional :name, type: String, desc: 'The approval rule\'s name'
requires :rule_id, type: Integer, desc: 'The ID of the external approval rule'
optional :name, type: String, desc: 'The name of the approval rule'
optional :external_url, type: String, desc: 'The URL to notify when MR receives new commits'
optional :protected_branch_ids, type: Array[Integer], coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce, desc: 'The protected branch ids for this rule'
optional :protected_branch_ids,
type: Array[Integer],
coerce_with: ::API::Validations::Types::CommaSeparatedToIntegerArray.coerce,
desc: 'The protected branch ids for this rule'
end
put do
service = ::ExternalApprovalRules::UpdateService.new(container: @project,
current_user: current_user,
params: declared(params, include_missing: false)).execute
service = ::ExternalApprovalRules::UpdateService.new(
container: user_project,
current_user: current_user,
params: declared_params(include_missing: false)
).execute
if service.success?
present service.payload[:rule], with: ::API::Entities::ExternalApprovalRule
......
......@@ -124,6 +124,7 @@ module EE
subscription_params = declared_params(include_missing: false)
subscription_params[:trial_starts_on] ||= subscription_params[:start_date] if subscription_params[:trial]
subscription_params[:updated_at] = Time.current
if subscription.update(subscription_params)
present subscription, with: ::EE::API::Entities::GitlabSubscription
......
......@@ -122,7 +122,7 @@ module Gitlab
def subscription_future_renewal?
return if self_managed? || namespace.nil?
::GitlabSubscriptions::CheckFutureRenewalService.new(namespace_id: namespace.id).execute
::GitlabSubscriptions::CheckFutureRenewalService.new(namespace: namespace).execute
end
def require_notification?
......
......@@ -51,18 +51,6 @@ RSpec.describe Projects::Security::ApiFuzzingConfigurationController do
expect(response.body).to have_active_sub_navigation('Configuration')
end
context 'with feature flag disabled' do
before do
stub_feature_flags(api_fuzzing_configuration_ui: false)
end
it 'returns a 404 for an HTML request' do
request
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'with unauthorized user' do
......
......@@ -67,7 +67,7 @@ RSpec.describe Security::VulnerabilitiesFinder do
end
context 'when filtered by scanner_id' do
let(:filters) { { scanner_ids: [vulnerability1.finding_scanner_id, vulnerability3.finding_scanner_id] } }
let(:filters) { { scanner_id: [vulnerability1.finding_scanner_id, vulnerability3.finding_scanner_id] } }
it 'only returns vulnerabilities matching the given scanner IDs' do
is_expected.to contain_exactly(vulnerability1, vulnerability3)
......
......@@ -39,7 +39,7 @@ exports[`Status Page settings form default state should match the default snapsh
>
<p>
<gl-sprintf-stub
message="To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
message="To publish incidents to an external status page, GitLab stores a JSON file in your Amazon S3 account at a location that your external status page service can access. Make sure to also set up %{docsLink}"
/>
</p>
......@@ -117,7 +117,7 @@ exports[`Status Page settings form default state should match the default snapsh
class="form-text text-muted"
>
<gl-sprintf-stub
message="For help with configuration, visit %{docsLink}"
message="AWS %{docsLink}"
/>
</p>
</gl-form-group-stub>
......
......@@ -30,42 +30,23 @@ RSpec.describe Mutations::AppSec::Fuzzing::Api::CiConfiguration::Create do
stub_licensed_features(security_dashboard: true)
end
context 'when the api_fuzzing_configuration_ui feature is on' do
before do
stub_feature_flags(api_fuzzing_configuration_ui: true)
end
it 'returns a YAML snippet that can be used to configure API fuzzing scans for the project' do
aggregate_failures do
expect(subject[:errors]).to be_empty
expect(subject[:gitlab_ci_yaml_edit_path]).to eq(
Rails.application.routes.url_helpers.project_ci_pipeline_editor_path(project)
)
expect(Psych.load(subject[:configuration_yaml])).to eq({
'stages' => ['fuzz'],
'include' => [{ 'template' => 'API-Fuzzing.gitlab-ci.yml' }],
'variables' => {
'FUZZAPI_HTTP_PASSWORD' => '$PASSWORD',
'FUZZAPI_HTTP_USERNAME' => '$USERNAME',
'FUZZAPI_HAR' => 'https://api.gov/api_spec',
'FUZZAPI_PROFILE' => 'Quick-10',
'FUZZAPI_TARGET_URL' => 'https://api.gov'
}
})
end
end
context 'when the api_fuzzing_configuration_ui feature is off' do
before do
stub_feature_flags(api_fuzzing_configuration_ui: false)
end
it 'errors' do
expect { subject }.to raise_error(
::Gitlab::Graphql::Errors::ResourceNotAvailable,
'The API fuzzing CI configuration feature is off'
)
end
it 'returns a YAML snippet that can be used to configure API fuzzing scans for the project' do
aggregate_failures do
expect(subject[:errors]).to be_empty
expect(subject[:gitlab_ci_yaml_edit_path]).to eq(
Rails.application.routes.url_helpers.project_ci_pipeline_editor_path(project)
)
expect(Psych.load(subject[:configuration_yaml])).to eq({
'stages' => ['fuzz'],
'include' => [{ 'template' => 'API-Fuzzing.gitlab-ci.yml' }],
'variables' => {
'FUZZAPI_HTTP_PASSWORD' => '$PASSWORD',
'FUZZAPI_HTTP_USERNAME' => '$USERNAME',
'FUZZAPI_HAR' => 'https://api.gov/api_spec',
'FUZZAPI_PROFILE' => 'Quick-10',
'FUZZAPI_TARGET_URL' => 'https://api.gov'
}
})
end
end
......
......@@ -11,6 +11,18 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
let(:profile_name) { SecureRandom.hex }
let(:target_url) { generate(:url) }
let(:excluded_urls) { ["#{target_url}/signout"] }
let(:request_headers) { 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0' }
let(:auth) do
{
enabled: true,
url: "#{target_url}/login",
username_field: 'session[username]',
password_field: 'session[password]',
username: generate(:email),
password: SecureRandom.hex
}
end
let(:dast_site_profile) { DastSiteProfile.find_by(project: project, name: profile_name) }
......@@ -29,15 +41,8 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
profile_name: profile_name,
target_url: target_url,
excluded_urls: excluded_urls,
request_headers: 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0',
auth: {
enabled: true,
url: "#{target_url}/login",
username_field: 'session[username]',
password_field: 'session[password]',
username: generate(:email),
password: SecureRandom.hex
}
request_headers: request_headers,
auth: auth
)
end
......@@ -55,15 +60,47 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
project.add_developer(user)
end
it 'creates a dast_site_profile and dast_site_profile_secret_variables', :aggregate_failures do
dast_site_profile = subject[:id].find
expect(dast_site_profile).to have_attributes(
name: profile_name,
excluded_urls: excluded_urls,
auth_enabled: auth[:enabled],
auth_url: auth[:url],
auth_username_field: auth[:username_field],
auth_password_field: auth[:password_field],
auth_username: auth[:username],
dast_site: have_attributes(url: target_url)
)
password_variable = dast_site_profile.secret_variables.find_by!(key: Dast::SiteProfileSecretVariable::PASSWORD)
expect(password_variable.value).to eq(Base64.strict_encode64(auth[:password]))
request_headers_variable = dast_site_profile.secret_variables.find_by!(key: Dast::SiteProfileSecretVariable::REQUEST_HEADERS)
expect(request_headers_variable.value).to eq(Base64.strict_encode64(request_headers))
end
it 'returns the dast_site_profile id' do
expect(subject[:id]).to eq(dast_site_profile.to_global_id)
end
it 'calls the dast_site_profile creation service' do
service = double(described_class)
result = double('result', success?: false, errors: [])
service_params = { name: profile_name, target_url: target_url, excluded_urls: excluded_urls }
service = double(DastSiteProfiles::CreateService)
result = ServiceResponse.error(message: '')
service_params = {
name: profile_name,
target_url: target_url,
excluded_urls: excluded_urls,
request_headers: request_headers,
auth_enabled: auth[:enabled],
auth_url: auth[:url],
auth_username_field: auth[:username_field],
auth_password_field: auth[:password_field],
auth_username: auth[:username],
auth_password: auth[:password]
}
expect(DastSiteProfiles::CreateService).to receive(:new).and_return(service)
expect(service).to receive(:execute).with(service_params).and_return(result)
......@@ -85,23 +122,39 @@ RSpec.describe Mutations::DastSiteProfiles::Create do
end
end
context 'when excluded_urls is supplied as a param' do
context 'when the feature flag security_dast_site_profiles_additional_fields is disabled' do
it 'does not set the excluded_urls' do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
context 'when the feature flag security_dast_site_profiles_additional_fields is disabled' do
before do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
end
subject
it 'does not set the request_headers or the password dast_site_profile_secret_variables' do
subject
expect(dast_site_profile.excluded_urls).to be_empty
end
expect(dast_site_profile.secret_variables).to be_empty
end
context 'when the feature flag security_dast_site_profiles_additional_fields is enabled' do
it 'sets the excluded_urls' do
subject
it 'does not set non-secret auth fields' do
subject
expect(dast_site_profile).to have_attributes(
auth_enabled: false,
auth_url: nil,
auth_username_field: nil,
auth_password_field: nil,
auth_username: nil
)
end
end
context 'when variable creation fails' do
it 'returns an error and the dast_site_profile' do
service = double(Dast::SiteProfileSecretVariables::CreateOrUpdateService)
result = ServiceResponse.error(payload: create(:dast_site_profile), message: 'Oops')
allow(Dast::SiteProfileSecretVariables::CreateOrUpdateService).to receive(:new).and_return(service)
allow(service).to receive(:execute).and_return(result)
expect(dast_site_profile.excluded_urls).to eq(excluded_urls)
end
expect(subject).to include(errors: ['Oops'])
end
end
end
......
......@@ -69,10 +69,18 @@ RSpec.describe Resolvers::VulnerabilitiesResolver do
end
end
context 'when given scanner' do
context 'when given scanner external IDs' do
let(:params) { { scanner: [high_vulnerability.finding_scanner_external_id] } }
it 'only returns vulnerabilities of the given scanner' do
it 'only returns vulnerabilities of the given scanner external IDs' do
is_expected.to contain_exactly(high_vulnerability)
end
end
context 'when given scanner ID' do
let(:params) { { scanner_id: [high_vulnerability.finding_scanner_id] } }
it 'only returns vulnerabilities of the given scanner IDs' do
is_expected.to contain_exactly(high_vulnerability)
end
end
......
......@@ -115,7 +115,7 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
let_it_be(:namespace) { create(:group, name: 'No Limit Records') }
before do
allow_next_instance_of(GitlabSubscriptions::CheckFutureRenewalService, namespace_id: namespace.id) do |service|
allow_next_instance_of(GitlabSubscriptions::CheckFutureRenewalService, namespace: namespace) do |service|
allow(service).to receive(:execute).and_return(has_future_renewal)
end
end
......@@ -203,7 +203,7 @@ RSpec.describe Gitlab::ExpiringSubscriptionMessage do
let_it_be(:namespace) { create(:group, name: 'No Limit Records') }
before do
allow_next_instance_of(GitlabSubscriptions::CheckFutureRenewalService, namespace_id: namespace.id) do |service|
allow_next_instance_of(GitlabSubscriptions::CheckFutureRenewalService, namespace: namespace) do |service|
allow(service).to receive(:execute).and_return(has_future_renewal)
end
end
......
......@@ -42,58 +42,38 @@ RSpec.describe 'Query.project(fullPath).apiFuzzingCiConfiguration' do
).to_return(body: profiles_yaml)
end
context 'when the api_fuzzing_configuration_ui feature flag is enabled' do
context 'when the user can read vulnerabilities for the project' do
before do
stub_feature_flags(api_fuzzing_configuration_ui: true)
stub_licensed_features(security_dashboard: true)
end
context 'when the user can read vulnerabilities for the project' do
before do
stub_licensed_features(security_dashboard: true)
end
it 'returns scan modes and scan profiles' do
post_graphql(query, current_user: user)
expect(response).to have_gitlab_http_status(:ok)
fuzzing_config = graphql_data.dig('project', 'apiFuzzingCiConfiguration')
modes = fuzzing_config['scanModes']
profiles = fuzzing_config['scanProfiles']
expect(modes).to contain_exactly('HAR', 'OPENAPI', 'POSTMAN')
expect(profiles).to contain_exactly({
'name' => 'Quick-10',
'description' => 'Fuzzing 10 times per parameter',
'yaml' => "---\nName: Quick-10\n"
})
end
end
context 'when the user cannot read vulnerabilities for the project' do
before do
stub_licensed_features(security_dashboard: false)
end
it 'returns nil' do
post_graphql(query, current_user: user)
it 'returns scan modes and scan profiles' do
post_graphql(query, current_user: user)
expect(response).to have_gitlab_http_status(:ok)
expect(response).to have_gitlab_http_status(:ok)
fuzzing_config = graphql_data.dig('project', 'apiFuzzingCiConfiguration')
expect(fuzzing_config).to be_nil
end
fuzzing_config = graphql_data.dig('project', 'apiFuzzingCiConfiguration')
modes = fuzzing_config['scanModes']
profiles = fuzzing_config['scanProfiles']
expect(modes).to contain_exactly('HAR', 'OPENAPI', 'POSTMAN')
expect(profiles).to contain_exactly({
'name' => 'Quick-10',
'description' => 'Fuzzing 10 times per parameter',
'yaml' => "---\nName: Quick-10\n"
})
end
end
context 'when the api_fuzzing_configuration_ui feature flag is disabled' do
context 'when the user cannot read vulnerabilities for the project' do
before do
stub_feature_flags(api_fuzzing_configuration_ui: false)
stub_licensed_features(security_dashboard: false)
end
it 'returns nil' do
post_graphql(query, current_user: user)
expect(response).to have_gitlab_http_status(:ok)
fuzzing_config = graphql_data.dig('project', 'apiFuzzingCiConfiguration')
expect(fuzzing_config).to be_nil
end
......
......@@ -33,7 +33,6 @@ RSpec.describe 'CreateApiFuzzingCiConfiguration' do
end
before do
stub_feature_flags(api_fuzzing_configuration_ui: true)
stub_licensed_features(security_dashboard: true)
end
......
......@@ -534,6 +534,12 @@ RSpec.describe API::Namespaces do
max_seats_used: 42
)
end
it 'updates the timestamp when the attributes are the same' do
expect do
do_put(namespace.id, admin, gitlab_subscription.attributes)
end.to change { gitlab_subscription.reload.updated_at }
end
end
end
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Dast::SiteProfileSecretVariables::CreateOrUpdateService do
let_it_be(:project) { create(:project) }
let_it_be(:dast_profile) { create(:dast_profile, project: project) }
let_it_be(:developer) { create(:user, developer_projects: [project] ) }
let_it_be(:default_params) do
{
dast_site_profile: dast_profile.dast_site_profile,
key: 'DAST_PASSWORD_BASE64',
raw_value: SecureRandom.hex
}
end
let(:params) { default_params }
subject { described_class.new(container: project, current_user: developer, params: params).execute }
describe 'execute' do
context 'when on demand scan licensed feature is not available' do
it 'communicates failure' do
stub_licensed_features(security_on_demand_scans: false)
aggregate_failures do
expect(subject.status).to eq(:error)
expect(subject.message).to include('Insufficient permissions')
end
end
end
context 'when the feature is enabled' do
before do
stub_licensed_features(security_on_demand_scans: true)
end
shared_examples 'it errors when a required param is missing' do |parameter|
context "when #{parameter} param is missing" do
let(:params) { default_params.except(parameter) }
it 'communicates failure', :aggregate_failures do
expect(subject.status).to eq(:error)
expect(subject.message).to eq("#{parameter.to_s.humanize} param is missing")
end
end
end
shared_examples 'it errors when there is a validation failure' do
let(:params) { default_params.merge(raw_value: '') }
it 'communicates failure', :aggregate_failures do
expect(subject.status).to eq(:error)
expect(subject.message).to include('Value is invalid')
end
end
it_behaves_like 'it errors when a required param is missing', :dast_site_profile
it_behaves_like 'it errors when a required param is missing', :key
it_behaves_like 'it errors when a required param is missing', :raw_value
it_behaves_like 'it errors when there is a validation failure'
it 'communicates success' do
expect(subject.status).to eq(:success)
end
it 'creates a dast_site_profile_secret_variable', :aggregate_failures do
expect { subject }.to change { Dast::SiteProfileSecretVariable.count }.by(1)
expect(subject.payload.value).to eq(Base64.strict_encode64(params[:raw_value]))
end
context 'when a variable already exists' do
let_it_be(:dast_site_profile_secret_variable) do
create(:dast_site_profile_secret_variable, key: default_params[:key], dast_site_profile: dast_profile.dast_site_profile)
end
let(:params) { default_params.merge(raw_value: 'hello, world') }
it_behaves_like 'it errors when there is a validation failure'
it 'does not create a dast_site_profile_secret_variable' do
expect { subject }.not_to change { Dast::SiteProfileSecretVariable.count }
end
it 'updates the existing dast_site_profile_secret_variable' do
subject
expect(dast_site_profile_secret_variable.reload.value).to eq(Base64.strict_encode64(params[:raw_value]))
end
end
context 'when the feature is disabled' do
it 'communicates failure', :aggregate_failures do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
expect(subject.status).to eq(:error)
expect(subject.message).to include('Insufficient permissions')
end
end
end
end
end
......@@ -3,18 +3,36 @@
require 'spec_helper'
RSpec.describe DastSiteProfiles::CreateService do
let(:user) { create(:user) }
let(:project) { create(:project, :repository, creator: user) }
let(:name) { FFaker::Company.catch_phrase }
let(:target_url) { generate(:url) }
let(:excluded_urls) { ["#{target_url}/signout"] }
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project, :repository, creator: user) }
let_it_be(:name) { FFaker::Company.catch_phrase }
let_it_be(:target_url) { generate(:url) }
let_it_be(:excluded_urls) { ["#{target_url}/signout"] }
let_it_be(:request_headers) { "Authorization: Bearer #{SecureRandom.hex}" }
let(:default_params) do
{
name: name,
target_url: target_url,
excluded_urls: excluded_urls,
request_headers: request_headers,
auth_enabled: true,
auth_url: "#{target_url}/login",
auth_username_field: 'session[username]',
auth_password_field: 'session[password]',
auth_username: generate(:email),
auth_password: SecureRandom.hex
}
end
let(:params) { default_params }
before do
stub_licensed_features(security_on_demand_scans: true)
end
describe '#execute' do
subject { described_class.new(project, user).execute(name: name, target_url: target_url, excluded_urls: excluded_urls) }
subject { described_class.new(project, user).execute(**params) }
let(:status) { subject.status }
let(:message) { subject.message }
......@@ -48,6 +66,12 @@ RSpec.describe DastSiteProfiles::CreateService do
expect { subject }.to change(DastSite, :count).by(1)
end
it 'sets attributes correctly' do
expect(payload).to have_attributes(
params.except(:request_headers, :auth_password, :target_url).merge(dast_site: have_attributes(url: target_url))
)
end
it 'returns a dast_site_profile payload' do
expect(payload).to be_a(DastSiteProfile)
end
......@@ -87,13 +111,74 @@ RSpec.describe DastSiteProfiles::CreateService do
end
context 'when excluded_urls is not supplied' do
subject { described_class.new(project, user).execute(name: name, target_url: target_url) }
let(:params) { default_params.except(:excluded_urls) }
it 'defaults to an empty array' do
expect(payload.excluded_urls).to be_empty
end
end
context 'when auth values are not supplied' do
let(:params) { default_params.except(:auth_enabled, :auth_url, :auth_username_field, :auth_password_field, :auth_password_field, :auth_username) }
it 'uses sensible defaults' do
expect(payload).to have_attributes(
auth_enabled: false,
auth_url: nil,
auth_username_field: nil,
auth_password_field: nil,
auth_username: nil
)
end
end
shared_examples 'it handles secret variable creation' do
it 'correctly sets the value' do
variable = Dast::SiteProfileSecretVariable.find_by(key: key, dast_site_profile: payload)
expect(Base64.strict_decode64(variable.value)).to eq(raw_value)
end
context 'when the feature flag is disabled' do
it 'does not create a secret variable' do
stub_feature_flags(security_dast_site_profiles_additional_fields: false)
expect { subject }.not_to change { Dast::SiteProfileSecretVariable.count }
end
end
end
shared_examples 'it handles secret variable creation failure' do
before do
allow_next_instance_of(Dast::SiteProfileSecretVariables::CreateOrUpdateService) do |service|
response = ServiceResponse.error(message: 'Something went wrong')
allow(service).to receive(:execute).and_return(response)
end
end
it 'returns an error response', :aggregate_failures do
expect(status).to eq(:error)
expect(message).to include('Something went wrong')
end
end
context 'when request_headers are supplied' do
let(:key) { 'DAST_REQUEST_HEADERS_BASE64' }
let(:raw_value) { params[:request_headers] }
it_behaves_like 'it handles secret variable creation'
it_behaves_like 'it handles secret variable creation failure'
end
context 'when auth_password is supplied' do
let(:key) { 'DAST_PASSWORD_BASE64' }
let(:raw_value) { params[:auth_password] }
it_behaves_like 'it handles secret variable creation'
it_behaves_like 'it handles secret variable creation failure'
end
context 'when on demand scan licensed feature is not available' do
before do
stub_licensed_features(security_on_demand_scans: false)
......
......@@ -6,11 +6,11 @@ RSpec.describe GitlabSubscriptions::CheckFutureRenewalService, :use_clean_rails_
using RSpec::Parameterized::TableSyntax
describe '#execute' do
let(:namespace) { create(:namespace) }
let(:namespace_id) { namespace.id }
let(:cache_key) { "subscription:future_renewal:namespace:#{namespace_id}" }
let_it_be(:namespace) { create(:namespace_with_plan) }
subject(:execute_service) { described_class.new(namespace_id: namespace_id).execute }
let(:cache_key) { "subscription:future_renewal:namespace:#{namespace.gitlab_subscription.cache_key}" }
subject(:execute_service) { described_class.new(namespace: namespace).execute }
where(:in_last_term, :expected_response) do
true | false
......
......@@ -13529,9 +13529,15 @@ msgstr ""
msgid "For each job, re-use the project workspace. If the workspace doesn't exist, use %{code_open}git clone%{code_close}."
msgstr ""
msgid "For general work"
msgstr ""
msgid "For individual use, create a separate account under your personal email address, not tied to the Enterprise email domain or group."
msgstr ""
msgid "For investigating IT service disruptions or outages"
msgstr ""
msgid "For more info, read the documentation."
msgstr ""
......@@ -17201,6 +17207,9 @@ msgstr ""
msgid "Issue title"
msgstr ""
msgid "Issue types"
msgstr ""
msgid "Issue update failed"
msgstr ""
......@@ -29172,13 +29181,13 @@ msgstr ""
msgid "Status: %{title}"
msgstr ""
msgid "StatusPage|AWS Secret access key"
msgid "StatusPage|AWS %{docsLink}"
msgstr ""
msgid "StatusPage|AWS access key ID"
msgid "StatusPage|AWS Secret access key"
msgstr ""
msgid "StatusPage|AWS documentation"
msgid "StatusPage|AWS access key ID"
msgstr ""
msgid "StatusPage|AWS region"
......@@ -29193,9 +29202,6 @@ msgstr ""
msgid "StatusPage|Configure file storage settings to link issues in this project to an external status page."
msgstr ""
msgid "StatusPage|For help with configuration, visit %{docsLink}"
msgstr ""
msgid "StatusPage|S3 Bucket name"
msgstr ""
......@@ -29208,7 +29214,7 @@ msgstr ""
msgid "StatusPage|Status page frontend documentation"
msgstr ""
msgid "StatusPage|To publish incidents to an external status page, GitLab will store a JSON file in your Amazon S3 account in a location accessible to your external status page service. Make sure to also set up %{docsLink}"
msgid "StatusPage|To publish incidents to an external status page, GitLab stores a JSON file in your Amazon S3 account at a location that your external status page service can access. Make sure to also set up %{docsLink}"
msgstr ""
msgid "StatusPage|configuration documentation"
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Issuable type info popover renders 1`] = `
<span
id="popovercontainer"
>
<gl-icon-stub
class="gl-ml-5 gl-text-gray-500"
id="issuable-type-info"
name="question-o"
size="16"
/>
<gl-popover-stub
container="popovercontainer"
cssclasses=""
target="issuable-type-info"
title="Issue types"
triggers="focus hover"
>
<ul
class="gl-list-style-none gl-p-0 gl-m-0"
>
<li
class="gl-mb-3"
>
<div
class="gl-font-weight-bold"
>
Issue
</div>
<span>
For general work
</span>
</li>
<li>
<div
class="gl-font-weight-bold"
>
Incident
</div>
<span>
For investigating IT service disruptions or outages
</span>
</li>
</ul>
</gl-popover-stub>
</span>
`;
import { shallowMount } from '@vue/test-utils';
import InfoPopover from '~/issuable_type_selector/components/info_popover.vue';
describe('Issuable type info popover', () => {
let wrapper;
function createComponent() {
wrapper = shallowMount(InfoPopover);
}
afterEach(() => {
wrapper.destroy();
});
it('renders', () => {
createComponent();
expect(wrapper.element).toMatchSnapshot();
});
});
......@@ -269,6 +269,7 @@ stages:
- builds
- bridges
- latest_statuses
- retried_statuses
statuses:
- project
- pipeline
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment