Commit 1bb57e7f authored by Jose Ivan Vargas's avatar Jose Ivan Vargas

Merge branch...

Merge branch 'jnnkl-remove-security_configuration_redesign-and-security_configuration_redesign_ee' into 'master'

Remove security configuration page feature flags

See merge request gitlab-org/gitlab!66842
parents abef98ec 26e3acd2
import { initCESecurityConfiguration } from '~/security_configuration'; import { initSecurityConfiguration } from '~/security_configuration';
initCESecurityConfiguration(document.querySelector('#js-security-configuration-static')); initSecurityConfiguration(document.querySelector('#js-security-configuration-static'));
<script> <script>
import ConfigurationTable from './configuration_table.vue'; import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
import FeatureCard from './feature_card.vue';
import SectionLayout from './section_layout.vue';
import UpgradeBanner from './upgrade_banner.vue';
export const i18n = {
compliance: s__('SecurityConfiguration|Compliance'),
configurationHistory: s__('SecurityConfiguration|Configuration history'),
securityTesting: s__('SecurityConfiguration|Security testing'),
latestPipelineDescription: s__(
`SecurityConfiguration|The status of the tools only applies to the
default branch and is based on the %{linkStart}latest pipeline%{linkEnd}.`,
),
description: s__(
`SecurityConfiguration|Once you've enabled a scan for the default branch,
any subsequent feature branch you create will include the scan.`,
),
securityConfiguration: __('Security Configuration'),
};
export default { export default {
i18n,
components: { components: {
ConfigurationTable, AutoDevOpsAlert,
AutoDevOpsEnabledAlert,
FeatureCard,
GlLink,
GlSprintf,
GlTab,
GlTabs,
LocalStorageSync,
SectionLayout,
UpgradeBanner,
UserCalloutDismisser,
},
inject: ['projectPath'],
props: {
augmentedSecurityFeatures: {
type: Array,
required: true,
},
augmentedComplianceFeatures: {
type: Array,
required: true,
},
gitlabCiPresent: {
type: Boolean,
required: false,
default: false,
},
autoDevopsEnabled: {
type: Boolean,
required: false,
default: false,
},
canEnableAutoDevops: {
type: Boolean,
required: false,
default: false,
},
gitlabCiHistoryPath: {
type: String,
required: false,
default: '',
},
latestPipelinePath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
autoDevopsEnabledAlertDismissedProjects: [],
};
},
computed: {
canUpgrade() {
return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some(
({ available }) => !available,
);
}, },
canViewCiHistory() {
return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath);
},
shouldShowDevopsAlert() {
return !this.autoDevopsEnabled && !this.gitlabCiPresent && this.canEnableAutoDevops;
},
shouldShowAutoDevopsEnabledAlert() {
return (
this.autoDevopsEnabled &&
!this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectPath)
);
},
},
methods: {
dismissAutoDevopsEnabledAlert() {
const dismissedProjects = new Set(this.autoDevopsEnabledAlertDismissedProjects);
dismissedProjects.add(this.projectPath);
this.autoDevopsEnabledAlertDismissedProjects = Array.from(dismissedProjects);
},
},
autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
}; };
</script> </script>
<template> <template>
<article> <article>
<local-storage-sync
v-model="autoDevopsEnabledAlertDismissedProjects"
:storage-key="$options.autoDevopsEnabledAlertStorageKey"
as-json
/>
<user-callout-dismisser
v-if="shouldShowDevopsAlert"
feature-name="security_configuration_devops_alert"
>
<template #default="{ dismiss, shouldShowCallout }">
<auto-dev-ops-alert v-if="shouldShowCallout" class="gl-mt-3" @dismiss="dismiss" />
</template>
</user-callout-dismisser>
<header> <header>
<h4 class="gl-my-5"> <h1 class="gl-font-size-h1">{{ $options.i18n.securityConfiguration }}</h1>
{{ __('Security Configuration') }}
</h4>
<h5 class="gl-font-lg gl-mt-7">
{{ s__('SecurityConfiguration|Testing & Compliance') }}
</h5>
</header> </header>
<configuration-table /> <user-callout-dismisser v-if="canUpgrade" feature-name="security_configuration_upgrade_banner">
<template #default="{ dismiss, shouldShowCallout }">
<upgrade-banner v-if="shouldShowCallout" @close="dismiss" />
</template>
</user-callout-dismisser>
<gl-tabs content-class="gl-pt-0">
<gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting">
<auto-dev-ops-enabled-alert
v-if="shouldShowAutoDevopsEnabledAlert"
class="gl-mt-3"
@dismiss="dismissAutoDevopsEnabledAlert"
/>
<section-layout :heading="$options.i18n.securityTesting">
<template #description>
<p>
<span data-testid="latest-pipeline-info-security">
<gl-sprintf
v-if="latestPipelinePath"
:message="$options.i18n.latestPipelineDescription"
>
<template #link="{ content }">
<gl-link :href="latestPipelinePath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
{{ $options.i18n.description }}
</p>
<p v-if="canViewCiHistory">
<gl-link data-testid="security-view-history-link" :href="gitlabCiHistoryPath">{{
$options.i18n.configurationHistory
}}</gl-link>
</p>
</template>
<template #features>
<feature-card
v-for="feature in augmentedSecurityFeatures"
:key="feature.type"
data-testid="security-testing-card"
:feature="feature"
class="gl-mb-6"
/>
</template>
</section-layout>
</gl-tab>
<gl-tab data-testid="compliance-testing-tab" :title="$options.i18n.compliance">
<section-layout :heading="$options.i18n.compliance">
<template #description>
<p>
<span data-testid="latest-pipeline-info-compliance">
<gl-sprintf
v-if="latestPipelinePath"
:message="$options.i18n.latestPipelineDescription"
>
<template #link="{ content }">
<gl-link :href="latestPipelinePath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
{{ $options.i18n.description }}
</p>
<p v-if="canViewCiHistory">
<gl-link data-testid="compliance-view-history-link" :href="gitlabCiHistoryPath">{{
$options.i18n.configurationHistory
}}</gl-link>
</p>
</template>
<template #features>
<feature-card
v-for="feature in augmentedComplianceFeatures"
:key="feature.type"
:feature="feature"
class="gl-mb-6"
/>
</template>
</section-layout>
</gl-tab>
</gl-tabs>
</article> </article>
</template> </template>
<script>
import { GlLink, GlTable, GlAlert } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import ManageViaMR from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_DAST,
REPORT_TYPE_DAST_PROFILES,
REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_CONTAINER_SCANNING,
REPORT_TYPE_CLUSTER_IMAGE_SCANNING,
REPORT_TYPE_COVERAGE_FUZZING,
REPORT_TYPE_API_FUZZING,
REPORT_TYPE_LICENSE_COMPLIANCE,
} from '~/vue_shared/security_reports/constants';
import { scanners } from './constants';
import Upgrade from './upgrade.vue';
const borderClasses = 'gl-border-b-1! gl-border-b-solid! gl-border-gray-100!';
const thClass = `gl-text-gray-900 gl-bg-transparent! ${borderClasses}`;
export default {
components: {
GlLink,
GlTable,
GlAlert,
},
data() {
return {
errorMessage: '',
};
},
methods: {
getFeatureDocumentationLinkLabel(item) {
return sprintf(s__('SecurityConfiguration|Feature documentation for %{featureName}'), {
featureName: item.name,
});
},
onError(value) {
this.errorMessage = value;
},
getComponentForItem(item) {
const COMPONENTS = {
[REPORT_TYPE_SAST]: ManageViaMR,
[REPORT_TYPE_DAST]: Upgrade,
[REPORT_TYPE_DAST_PROFILES]: Upgrade,
[REPORT_TYPE_DEPENDENCY_SCANNING]: Upgrade,
[REPORT_TYPE_CONTAINER_SCANNING]: Upgrade,
[REPORT_TYPE_CLUSTER_IMAGE_SCANNING]: Upgrade,
[REPORT_TYPE_COVERAGE_FUZZING]: Upgrade,
[REPORT_TYPE_API_FUZZING]: Upgrade,
[REPORT_TYPE_LICENSE_COMPLIANCE]: Upgrade,
};
return COMPONENTS[item.type];
},
},
table: {
fields: [
{
key: 'feature',
label: s__('SecurityConfiguration|Security Control'),
thClass,
},
{
key: 'manage',
label: s__('SecurityConfiguration|Manage'),
thClass,
},
],
items: scanners,
},
};
</script>
<template>
<div>
<gl-alert v-if="errorMessage" variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
<gl-table :items="$options.table.items" :fields="$options.table.fields" stacked="md">
<template #cell(feature)="{ item }">
<div class="gl-text-gray-900">
{{ item.name }}
</div>
<div>
{{ item.description }}
<gl-link
target="_blank"
data-testid="help-link"
:href="item.helpPath"
:aria-label="getFeatureDocumentationLinkLabel(item)"
>
{{ s__('SecurityConfiguration|More information') }}
</gl-link>
</div>
</template>
<template #cell(manage)="{ item }">
<component
:is="getComponentForItem(item)"
:feature="item"
:data-testid="item.type"
@error="onError"
/>
</template>
</gl-table>
</div>
</template>
...@@ -18,8 +18,9 @@ import configureSastMutation from '../graphql/configure_sast.mutation.graphql'; ...@@ -18,8 +18,9 @@ import configureSastMutation from '../graphql/configure_sast.mutation.graphql';
import configureSecretDetectionMutation from '../graphql/configure_secret_detection.mutation.graphql'; import configureSecretDetectionMutation from '../graphql/configure_secret_detection.mutation.graphql';
/** /**
* Translations & helpPagePaths for Static Security Configuration Page * Translations & helpPagePaths for Security Configuration Page
*/ */
export const SAST_NAME = __('Static Application Security Testing (SAST)'); export const SAST_NAME = __('Static Application Security Testing (SAST)');
export const SAST_SHORT_NAME = s__('ciReport|SAST'); export const SAST_SHORT_NAME = s__('ciReport|SAST');
export const SAST_DESCRIPTION = __('Analyze your source code for known vulnerabilities.'); export const SAST_DESCRIPTION = __('Analyze your source code for known vulnerabilities.');
...@@ -115,73 +116,6 @@ export const LICENSE_COMPLIANCE_HELP_PATH = helpPagePath( ...@@ -115,73 +116,6 @@ export const LICENSE_COMPLIANCE_HELP_PATH = helpPagePath(
'user/compliance/license_compliance/index', 'user/compliance/license_compliance/index',
); );
export const UPGRADE_CTA = s__(
'SecurityConfiguration|Available with %{linkStart}upgrade or free trial%{linkEnd}',
);
export const scanners = [
{
name: SAST_NAME,
description: SAST_DESCRIPTION,
helpPath: SAST_HELP_PATH,
type: REPORT_TYPE_SAST,
},
{
name: DAST_NAME,
description: DAST_DESCRIPTION,
helpPath: DAST_HELP_PATH,
type: REPORT_TYPE_DAST,
},
{
name: DAST_PROFILES_NAME,
description: DAST_PROFILES_DESCRIPTION,
helpPath: DAST_PROFILES_HELP_PATH,
type: REPORT_TYPE_DAST_PROFILES,
},
{
name: DEPENDENCY_SCANNING_NAME,
description: DEPENDENCY_SCANNING_DESCRIPTION,
helpPath: DEPENDENCY_SCANNING_HELP_PATH,
type: REPORT_TYPE_DEPENDENCY_SCANNING,
},
{
name: CONTAINER_SCANNING_NAME,
description: CONTAINER_SCANNING_DESCRIPTION,
helpPath: CONTAINER_SCANNING_HELP_PATH,
type: REPORT_TYPE_CONTAINER_SCANNING,
},
{
name: CLUSTER_IMAGE_SCANNING_NAME,
description: CLUSTER_IMAGE_SCANNING_DESCRIPTION,
helpPath: CLUSTER_IMAGE_SCANNING_HELP_PATH,
type: REPORT_TYPE_CLUSTER_IMAGE_SCANNING,
},
{
name: SECRET_DETECTION_NAME,
description: SECRET_DETECTION_DESCRIPTION,
helpPath: SECRET_DETECTION_HELP_PATH,
type: REPORT_TYPE_SECRET_DETECTION,
},
{
name: COVERAGE_FUZZING_NAME,
description: COVERAGE_FUZZING_DESCRIPTION,
helpPath: COVERAGE_FUZZING_HELP_PATH,
type: REPORT_TYPE_COVERAGE_FUZZING,
},
{
name: API_FUZZING_NAME,
description: API_FUZZING_DESCRIPTION,
helpPath: API_FUZZING_HELP_PATH,
type: REPORT_TYPE_API_FUZZING,
},
{
name: LICENSE_COMPLIANCE_NAME,
description: LICENSE_COMPLIANCE_DESCRIPTION,
helpPath: LICENSE_COMPLIANCE_HELP_PATH,
type: REPORT_TYPE_LICENSE_COMPLIANCE,
},
];
export const securityFeatures = [ export const securityFeatures = [
{ {
name: SAST_NAME, name: SAST_NAME,
......
<script>
import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import UserCalloutDismisser from '~/vue_shared/components/user_callout_dismisser.vue';
import AutoDevOpsAlert from './auto_dev_ops_alert.vue';
import AutoDevOpsEnabledAlert from './auto_dev_ops_enabled_alert.vue';
import { AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY } from './constants';
import FeatureCard from './feature_card.vue';
import SectionLayout from './section_layout.vue';
import UpgradeBanner from './upgrade_banner.vue';
export const i18n = {
compliance: s__('SecurityConfiguration|Compliance'),
configurationHistory: s__('SecurityConfiguration|Configuration history'),
securityTesting: s__('SecurityConfiguration|Security testing'),
latestPipelineDescription: s__(
`SecurityConfiguration|The status of the tools only applies to the
default branch and is based on the %{linkStart}latest pipeline%{linkEnd}.`,
),
description: s__(
`SecurityConfiguration|Once you've enabled a scan for the default branch,
any subsequent feature branch you create will include the scan.`,
),
securityConfiguration: __('Security Configuration'),
};
export default {
i18n,
components: {
AutoDevOpsAlert,
AutoDevOpsEnabledAlert,
FeatureCard,
GlLink,
GlSprintf,
GlTab,
GlTabs,
LocalStorageSync,
SectionLayout,
UpgradeBanner,
UserCalloutDismisser,
},
inject: ['projectPath'],
props: {
augmentedSecurityFeatures: {
type: Array,
required: true,
},
augmentedComplianceFeatures: {
type: Array,
required: true,
},
gitlabCiPresent: {
type: Boolean,
required: false,
default: false,
},
autoDevopsEnabled: {
type: Boolean,
required: false,
default: false,
},
canEnableAutoDevops: {
type: Boolean,
required: false,
default: false,
},
gitlabCiHistoryPath: {
type: String,
required: false,
default: '',
},
latestPipelinePath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
autoDevopsEnabledAlertDismissedProjects: [],
};
},
computed: {
canUpgrade() {
return [...this.augmentedSecurityFeatures, ...this.augmentedComplianceFeatures].some(
({ available }) => !available,
);
},
canViewCiHistory() {
return Boolean(this.gitlabCiPresent && this.gitlabCiHistoryPath);
},
shouldShowDevopsAlert() {
return !this.autoDevopsEnabled && !this.gitlabCiPresent && this.canEnableAutoDevops;
},
shouldShowAutoDevopsEnabledAlert() {
return (
this.autoDevopsEnabled &&
!this.autoDevopsEnabledAlertDismissedProjects.includes(this.projectPath)
);
},
},
methods: {
dismissAutoDevopsEnabledAlert() {
const dismissedProjects = new Set(this.autoDevopsEnabledAlertDismissedProjects);
dismissedProjects.add(this.projectPath);
this.autoDevopsEnabledAlertDismissedProjects = Array.from(dismissedProjects);
},
},
autoDevopsEnabledAlertStorageKey: AUTO_DEVOPS_ENABLED_ALERT_DISMISSED_STORAGE_KEY,
};
</script>
<template>
<article>
<local-storage-sync
v-model="autoDevopsEnabledAlertDismissedProjects"
:storage-key="$options.autoDevopsEnabledAlertStorageKey"
as-json
/>
<user-callout-dismisser
v-if="shouldShowDevopsAlert"
feature-name="security_configuration_devops_alert"
>
<template #default="{ dismiss, shouldShowCallout }">
<auto-dev-ops-alert v-if="shouldShowCallout" class="gl-mt-3" @dismiss="dismiss" />
</template>
</user-callout-dismisser>
<header>
<h1 class="gl-font-size-h1">{{ $options.i18n.securityConfiguration }}</h1>
</header>
<user-callout-dismisser v-if="canUpgrade" feature-name="security_configuration_upgrade_banner">
<template #default="{ dismiss, shouldShowCallout }">
<upgrade-banner v-if="shouldShowCallout" @close="dismiss" />
</template>
</user-callout-dismisser>
<gl-tabs content-class="gl-pt-0">
<gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting">
<auto-dev-ops-enabled-alert
v-if="shouldShowAutoDevopsEnabledAlert"
class="gl-mt-3"
@dismiss="dismissAutoDevopsEnabledAlert"
/>
<section-layout :heading="$options.i18n.securityTesting">
<template #description>
<p>
<span data-testid="latest-pipeline-info-security">
<gl-sprintf
v-if="latestPipelinePath"
:message="$options.i18n.latestPipelineDescription"
>
<template #link="{ content }">
<gl-link :href="latestPipelinePath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
{{ $options.i18n.description }}
</p>
<p v-if="canViewCiHistory">
<gl-link data-testid="security-view-history-link" :href="gitlabCiHistoryPath">{{
$options.i18n.configurationHistory
}}</gl-link>
</p>
</template>
<template #features>
<feature-card
v-for="feature in augmentedSecurityFeatures"
:key="feature.type"
data-testid="security-testing-card"
:feature="feature"
class="gl-mb-6"
/>
</template>
</section-layout>
</gl-tab>
<gl-tab data-testid="compliance-testing-tab" :title="$options.i18n.compliance">
<section-layout :heading="$options.i18n.compliance">
<template #description>
<p>
<span data-testid="latest-pipeline-info-compliance">
<gl-sprintf
v-if="latestPipelinePath"
:message="$options.i18n.latestPipelineDescription"
>
<template #link="{ content }">
<gl-link :href="latestPipelinePath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</span>
{{ $options.i18n.description }}
</p>
<p v-if="canViewCiHistory">
<gl-link data-testid="compliance-view-history-link" :href="gitlabCiHistoryPath">{{
$options.i18n.configurationHistory
}}</gl-link>
</p>
</template>
<template #features>
<feature-card
v-for="feature in augmentedComplianceFeatures"
:key="feature.type"
:feature="feature"
class="gl-mb-6"
/>
</template>
</section-layout>
</gl-tab>
</gl-tabs>
</article>
</template>
<script>
import { GlLink, GlSprintf } from '@gitlab/ui';
import { UPGRADE_CTA } from './constants';
export default {
components: {
GlLink,
GlSprintf,
},
inject: {
upgradePath: {
from: 'upgradePath',
default: '#',
},
},
i18n: {
UPGRADE_CTA,
},
};
</script>
<template>
<span>
<gl-sprintf :message="$options.i18n.UPGRADE_CTA">
<template #link="{ content }">
<gl-link target="_blank" :href="upgradePath">
{{ content }}
</gl-link>
</template>
</gl-sprintf>
</span>
</template>
...@@ -4,10 +4,13 @@ import createDefaultClient from '~/lib/graphql'; ...@@ -4,10 +4,13 @@ import createDefaultClient from '~/lib/graphql';
import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils'; import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils';
import SecurityConfigurationApp from './components/app.vue'; import SecurityConfigurationApp from './components/app.vue';
import { securityFeatures, complianceFeatures } from './components/constants'; import { securityFeatures, complianceFeatures } from './components/constants';
import RedesignedSecurityConfigurationApp from './components/redesigned_app.vue';
import { augmentFeatures } from './utils'; import { augmentFeatures } from './utils';
export const initRedesignedSecurityConfiguration = (el) => { export const initSecurityConfiguration = (el) => {
if (!el) {
return null;
}
Vue.use(VueApollo); Vue.use(VueApollo);
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
...@@ -40,7 +43,7 @@ export const initRedesignedSecurityConfiguration = (el) => { ...@@ -40,7 +43,7 @@ export const initRedesignedSecurityConfiguration = (el) => {
autoDevopsPath, autoDevopsPath,
}, },
render(createElement) { render(createElement) {
return createElement(RedesignedSecurityConfigurationApp, { return createElement(SecurityConfigurationApp, {
props: { props: {
augmentedComplianceFeatures, augmentedComplianceFeatures,
augmentedSecurityFeatures, augmentedSecurityFeatures,
...@@ -56,33 +59,3 @@ export const initRedesignedSecurityConfiguration = (el) => { ...@@ -56,33 +59,3 @@ export const initRedesignedSecurityConfiguration = (el) => {
}, },
}); });
}; };
export const initCESecurityConfiguration = (el) => {
if (!el) {
return null;
}
if (gon.features?.securityConfigurationRedesign) {
return initRedesignedSecurityConfiguration(el);
}
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
const { projectPath, upgradePath } = el.dataset;
return new Vue({
el,
apolloProvider,
provide: {
projectPath,
upgradePath,
},
render(createElement) {
return createElement(SecurityConfigurationApp);
},
});
};
...@@ -7,10 +7,6 @@ module Projects ...@@ -7,10 +7,6 @@ module Projects
feature_category :static_application_security_testing feature_category :static_application_security_testing
before_action only: [:show] do
push_frontend_feature_flag(:security_configuration_redesign, project, default_enabled: :yaml)
end
def show def show
render_403 unless can?(current_user, :read_security_configuration, project) render_403 unless can?(current_user, :read_security_configuration, project)
end end
......
- breadcrumb_title _("Security Configuration") - breadcrumb_title _("Security Configuration")
- page_title _("Security Configuration") - page_title _("Security Configuration")
- redesign_enabled = ::Feature.enabled?(:security_configuration_redesign, @project, default_enabled: :yaml) - @content_class = "limit-container-width" unless fluid_layout
- @content_class = "limit-container-width" unless fluid_layout || !redesign_enabled
#js-security-configuration-static{ data: { project_path: @project.full_path, upgrade_path: security_upgrade_path } } #js-security-configuration-static{ data: { project_path: @project.full_path, upgrade_path: security_upgrade_path } }
---
name: security_configuration_redesign
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/62285
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/331614
milestone: '14.0'
type: development
group: group::static analysis
default_enabled: false
---
name: security_configuration_redesign_ee
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/65171
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/336077
milestone: '14.1'
type: development
group: group::analyzer frontend
default_enabled: false
...@@ -11,9 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -11,9 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - SAST configuration was [enabled](https://gitlab.com/groups/gitlab-org/-/epics/3659) in 13.3 and [improved](https://gitlab.com/gitlab-org/gitlab/-/issues/232862) in 13.4. **(ULTIMATE)** > - SAST configuration was [enabled](https://gitlab.com/groups/gitlab-org/-/epics/3659) in 13.3 and [improved](https://gitlab.com/gitlab-org/gitlab/-/issues/232862) in 13.4. **(ULTIMATE)**
> - DAST Profiles feature was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40474) in 13.4. **(ULTIMATE)** > - DAST Profiles feature was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40474) in 13.4. **(ULTIMATE)**
> - A simplified version was made [available in all tiers](https://gitlab.com/gitlab-org/gitlab/-/issues/294076) in GitLab 13.10. > - A simplified version was made [available in all tiers](https://gitlab.com/gitlab-org/gitlab/-/issues/294076) in GitLab 13.10.
> - [Redesigned](https://gitlab.com/gitlab-org/gitlab/-/issues/326926) in 14.2.
WARNING:
This feature might not be available to you. Check the **version history** note above for details.
The Security Configuration page displays what security scans are available, links to documentation and also simple enablement tools for the current project. The Security Configuration page displays what security scans are available, links to documentation and also simple enablement tools for the current project.
...@@ -22,34 +20,36 @@ then in the left sidebar go to **Security & Compliance > Configuration**. ...@@ -22,34 +20,36 @@ then in the left sidebar go to **Security & Compliance > Configuration**.
For each security control the page displays: For each security control the page displays:
- **Security Control:** Name, description, and a documentation link. - Its name, description and a documentation link.
- **Manage:** A management option or a documentation link. - Whether or not it is available.
- A configuration button or a link to its configuration guide.
## UI redesign
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326926) in 14.0 for GitLab Free and Premium, behind a feature flag, disabled by default. ## Security testing
> - Enabled on GitLab.com for Free & Premium.
> - Recommended for production use.
> - It can be enabled or disabled for a single project.
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-ui-redesign). **(FREE SELF)**
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/333109) in 14.1 for GitLab Ultimate, behind a feature flag, disabled by default.
> - Disabled on GitLab.com.
> - Not recommended for production use.
> - It can be enabled or disabled for a single project.
> - To use in GitLab self-managed instances, ask a GitLab administrator to [enable it](#enable-or-disable-ui-redesign-for-ultimate). **(ULTIMATE SELF)**
WARNING: You can configure the following security controls:
This feature might not be available to you. Check the **version history** note above for details.
The Security Configuration page has been redesigned in GitLab Free and Premium. - Auto DevOps
The same functionality exists as before, but presented in a more extensible - Click **Enable Auto DevOps** on the alert to enable it for the current project. For more details, see [Auto DevOps](../../../topics/autodevops/index.md).
way. - SAST
- Click **Enable SAST** to use SAST for the current project. For more details, see [Configure SAST in the UI](../sast/index.md#configure-sast-in-the-ui).
- DAST **(ULTIMATE)**
- Click **Enable DAST** to use DAST for the current Project. To manage the available DAST profiles used for on-demand scans Click **Manage Scans**. For more details, see [DAST on-demand scans](../dast/index.md#on-demand-scans).
- Dependency Scanning **(ULTIMATE)**
- Select **Configure via Merge Request** to create a merge request with the changes required to
enable Dependency Scanning. For more details, see [Enable Dependency Scanning via an automatic merge request](../dependency_scanning/index.md#enable-dependency-scanning-via-an-automatic-merge-request).
For each security control the page displays: - Container Scanning **(ULTIMATE)**
- Can be configured via `.gitlab-ci.yml`. For more details, see [Container Scanning](../../../user/application_security/container_scanning/index.md#configuration).
- Cluster Image Scanning **(ULTIMATE)**
- Can be configured via `.gitlab-ci.yml`. For more details, see [Cluster Image Scanning](../../../user/application_security/cluster_image_scanning/#configuration).
- Secret Detection
- Select **Configure via Merge Request** to create a merge request with the changes required to
enable Secret Detection. For more details, see [Enable Secret Detection via an automatic merge request](../secret_detection/index.md#enable-secret-detection-via-an-automatic-merge-request).
- Its name, description and a documentation link. - API Fuzzing **(ULTIMATE)**
- Whether or not it is available. - Click **Enable API Fuzzing** to use API Fuzzing for the current Project. For more details, see [API Fuzzing](../../../user/application_security/api_fuzzing/index.md#enable-web-api-fuzzing).
- A configuration button or a link to its configuration guide. - Coverage Fuzzing **(ULTIMATE)**
- Can be configured via `.gitlab-ci.yml`. For more details, see [Coverage Fuzzing](../../../user/application_security/coverage_fuzzing/index.md#configuration).
## Status **(ULTIMATE)** ## Status **(ULTIMATE)**
...@@ -63,71 +63,11 @@ _enabled_. ...@@ -63,71 +63,11 @@ _enabled_.
If the latest pipeline used [Auto DevOps](../../../topics/autodevops/index.md), If the latest pipeline used [Auto DevOps](../../../topics/autodevops/index.md),
all security features are configured by default. all security features are configured by default.
For SAST, click **View history** to see the `.gitlab-ci.yml` file's history. Click **View history** to see the `.gitlab-ci.yml` file's history.
## Manage **(ULTIMATE)** ## Compliance **(ULTIMATE)**
You can configure the following security controls: You can configure the following security controls:
- Auto DevOps - License Compliance **(ULTIMATE)**
- Click **Enable Auto DevOps** to enable it for the current project. For more details, see [Auto DevOps](../../../topics/autodevops/index.md). - Can be configured via `.gitlab-ci.yml`. For more details, see [License Compliance](../../../user/compliance/license_compliance/index.md#configuration).
- SAST
- Click either **Enable** or **Configure** to use SAST for the current project. For more details, see [Configure SAST in the UI](../sast/index.md#configure-sast-in-the-ui).
- DAST Profiles
- Click **Manage** to manage the available DAST profiles used for on-demand scans. For more details, see [DAST on-demand scans](../dast/index.md#on-demand-scans).
- Secret Detection
- Select **Configure via Merge Request** to create a merge request with the changes required to
enable Secret Detection. For more details, see [Enable Secret Detection via an automatic merge request](../secret_detection/index.md#enable-secret-detection-via-an-automatic-merge-request).
- Dependency Scanning
- Select **Configure via Merge Request** to create a merge request with the changes required to
enable Dependency Scanning. For more details, see [Enable Dependency Scanning via an automatic merge request](../dependency_scanning/index.md#enable-dependency-scanning-via-an-automatic-merge-request).
## Enable or disable UI redesign **(FREE SELF)**
The Security Configuration redesign is under development, but is ready for
production use. It is deployed behind a feature flag that is **disabled by
default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can enable it.
To enable it:
```ruby
# For the instance
Feature.enable(:security_configuration_redesign)
# For a single project
Feature.enable(:security_configuration_redesign, Project.find(<project id>))
```
To disable it:
```ruby
# For the instance
Feature.disable(:security_configuration_redesign)
# For a single project
Feature.disable(:security_configuration_redesign, Project.find(<project id>))
```
## Enable or disable UI redesign for Ultimate **(ULTIMATE SELF)**
The Security Configuration redesign is under development, and is not ready for
production use. It is deployed behind a feature flag that is **disabled by
default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md) can enable it.
To enable it:
```ruby
# For the instance
Feature.enable(:security_configuration_redesign_ee)
# For a single project
Feature.enable(:security_configuration_redesign_ee, Project.find(<project id>))
```
To disable it:
```ruby
# For the instance
Feature.disable(:security_configuration_redesign_ee)
# For a single project
Feature.disable(:security_configuration_redesign_ee, Project.find(<project id>))
```
import { initSecurityConfiguration } from 'ee/security_configuration'; import { initSecurityConfiguration } from '~/security_configuration';
import { initCESecurityConfiguration } from '~/security_configuration';
const el = document.querySelector('#js-security-configuration'); const el =
document.querySelector('#js-security-configuration') ||
document.querySelector('#js-security-configuration-static');
if (el) { initSecurityConfiguration(el);
initSecurityConfiguration(el);
} else {
initCESecurityConfiguration(document.querySelector('#js-security-configuration-static'));
}
<script>
import { GlAlert, GlLink, GlSprintf } from '@gitlab/ui';
import { parseBoolean } from '~/lib/utils/common_utils';
import { s__, __ } from '~/locale';
import { scanners } from '~/security_configuration/components/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import AutoFixSettings from './auto_fix_settings.vue';
import ConfigurationTable from './configuration_table.vue';
export default {
components: {
GlAlert,
GlLink,
GlSprintf,
AutoFixSettings,
LocalStorageSync,
ConfigurationTable,
},
mixins: [glFeatureFlagsMixin()],
props: {
autoDevopsEnabled: {
type: Boolean,
required: false,
default: false,
},
autoDevopsHelpPagePath: {
type: String,
required: true,
},
latestPipelinePath: {
type: String,
required: false,
default: '',
},
features: {
type: Array,
required: true,
},
autoFixSettingsProps: {
type: Object,
required: true,
},
gitlabCiPresent: {
type: Boolean,
required: false,
default: false,
},
gitlabCiHistoryPath: {
type: String,
required: false,
default: '',
},
autoDevopsPath: {
type: String,
required: false,
default: '',
},
canEnableAutoDevops: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
autoDevopsAlertDismissed: 'false',
};
},
computed: {
devopsMessage() {
return this.autoDevopsEnabled
? __(
'Several security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project',
)
: __(
`The status of the table below only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan.`,
);
},
devopsUrl() {
return this.autoDevopsEnabled ? this.autoDevopsHelpPagePath : this.latestPipelinePath;
},
shouldShowAutoDevopsAlert() {
return Boolean(
!parseBoolean(this.autoDevopsAlertDismissed) &&
!this.autoDevopsEnabled &&
!this.gitlabCiPresent &&
this.canEnableAutoDevops,
);
},
featuresForDisplay() {
const featuresByType = this.features.reduce((acc, feature) => {
acc[feature.type] = feature;
return acc;
}, {});
return scanners.map((scanner) => {
const feature = featuresByType[scanner.type] ?? {};
return {
...feature,
...scanner,
};
});
},
},
methods: {
dismissAutoDevopsAlert() {
this.autoDevopsAlertDismissed = 'true';
},
},
autoDevopsAlertMessage: s__(`
SecurityConfiguration|You can quickly enable all security scanning tools by
enabling %{linkStart}Auto DevOps%{linkEnd}.`),
autoDevopsAlertStorageKey: 'security_configuration_auto_devops_dismissed',
};
</script>
<template>
<article>
<header>
<h4 class="my-3">{{ __('Security Configuration') }}</h4>
<h5 class="gl-font-lg mt-5">{{ s__('SecurityConfiguration|Testing & Compliance') }}</h5>
<p>
<gl-sprintf :message="devopsMessage">
<template #link="{ content }">
<gl-link ref="pipelinesLink" :href="devopsUrl" target="_blank">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</header>
<local-storage-sync
v-model="autoDevopsAlertDismissed"
:storage-key="$options.autoDevopsAlertStorageKey"
/>
<gl-alert
v-if="shouldShowAutoDevopsAlert"
:title="__('Auto DevOps')"
:primary-button-text="__('Enable Auto DevOps')"
:primary-button-link="autoDevopsPath"
class="gl-mb-5"
@dismiss="dismissAutoDevopsAlert"
>
<gl-sprintf :message="$options.autoDevopsAlertMessage">
<template #link="{ content }">
<gl-link :href="autoDevopsHelpPagePath" v-text="content" />
</template>
</gl-sprintf>
</gl-alert>
<configuration-table
:features="featuresForDisplay"
:auto-devops-enabled="autoDevopsEnabled"
:gitlab-ci-present="gitlabCiPresent"
:gitlab-ci-history-path="gitlabCiHistoryPath"
/>
<auto-fix-settings v-if="glFeatures.securityAutoFix" v-bind="autoFixSettingsProps" />
</article>
</template>
<script>
import { GlIcon, GlLink, GlCard, GlFormCheckbox, GlSprintf } from '@gitlab/ui';
import * as Sentry from '@sentry/browser';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
export default {
components: {
GlIcon,
GlLink,
GlCard,
GlFormCheckbox,
GlSprintf,
},
mixins: [glFeatureFlagsMixin()],
props: {
autoFixEnabled: {
type: Object,
required: true,
},
autoFixHelpPath: {
type: String,
required: true,
},
autoFixUserPath: {
type: String,
required: true,
},
containerScanningHelpPath: {
type: String,
required: true,
},
dependencyScanningHelpPath: {
type: String,
required: true,
},
toggleAutofixSettingEndpoint: {
type: String,
required: true,
},
canToggleAutoFixSettings: {
type: Boolean,
required: true,
},
},
data() {
// In this first iteration, the auto-fix settings is toggled for all features at once via a
// single checkbox. The line below is a temporary workaround to initialize the setting's state
// until we have distinct checkboxes for each auto-fixable feature.
const autoFixEnabled = Object.values(this.autoFixEnabled).some((enabled) => enabled);
return {
autoFixEnabledLocal: autoFixEnabled,
isChecked: autoFixEnabled,
autoFixStatusLoading: false,
};
},
computed: {
hasAutoFixDisabled() {
return !this.canToggleAutoFixSettings || this.autoFixStatusLoading;
},
},
methods: {
toggleAutoFix(enabled) {
this.autoFixStatusLoading = true;
return axios
.post(this.toggleAutofixSettingEndpoint, {
// When we have distinct checkboxes for each feature, we'll need to pass the feature being
// toggled to the API. It's not required for now as all features are being toggled at once.
feature: '',
enabled,
})
.then(() => {
this.autoFixEnabledLocal = enabled;
this.isChecked = enabled;
})
.catch((e) => {
Sentry.captureException(e);
createFlash({
message: __(
'Something went wrong while toggling auto-fix settings, please try again later.',
),
});
this.isChecked = !enabled;
})
.finally(() => {
this.autoFixStatusLoading = false;
});
},
},
};
</script>
<template>
<section>
<h4 class="gl-h4 gl-my-5">
{{ __('Suggested Solutions') }}
<gl-link
target="_blank"
:href="autoFixHelpPath"
:aria-label="__('Suggested solutions help link')"
>
<gl-icon name="question" />
</gl-link>
</h4>
<gl-card>
<gl-form-checkbox v-model="isChecked" :disabled="hasAutoFixDisabled" @change="toggleAutoFix">
{{
__('Automatically create merge requests for vulnerabilities that have fixes available.')
}}
<template #help>{{ __('Available for dependency and container scanning') }}</template>
</gl-form-checkbox>
<footer class="gl-bg-blue-100 gl-px-5 gl-py-3">
<gl-sprintf
:message="
__(
'%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd} and/or %{dependencyScanningLinkStart}Dependency Scanning%{dependencyScanningLinkEnd} must be enabled. %{securityBotLinkStart}GitLab-Security-Bot%{securityBotLinkEnd} will be the author of the auto-created merge request. %{moreInfoLinkStart}More information%{moreInfoLinkEnd}.',
)
"
>
<template #containerScanningLink="{ content }">
<gl-link :href="containerScanningHelpPath">{{ content }}</gl-link>
</template>
<template #dependencyScanningLink="{ content }">
<gl-link :href="dependencyScanningHelpPath">{{ content }}</gl-link>
</template>
<template #securityBotLink="{ content }">
<gl-link :href="autoFixUserPath">{{ content }}</gl-link>
</template>
<template #moreInfoLink="{ content }">
<gl-link :href="autoFixHelpPath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</footer>
</gl-card>
</section>
</template>
<script>
import { GlAlert, GlLink, GlTable } from '@gitlab/ui';
import { s__, sprintf } from '~/locale';
import FeatureStatus from './feature_status.vue';
import ManageFeature from './manage_feature.vue';
const borderClasses = 'gl-border-b-1! gl-border-b-solid! gl-border-gray-100!';
const thClass = `gl-text-gray-900 gl-bg-transparent! ${borderClasses}`;
export default {
components: {
GlAlert,
GlLink,
GlTable,
FeatureStatus,
ManageFeature,
},
props: {
features: {
type: Array,
required: true,
},
autoDevopsEnabled: {
type: Boolean,
required: false,
default: false,
},
gitlabCiPresent: {
type: Boolean,
required: false,
default: false,
},
gitlabCiHistoryPath: {
type: String,
required: false,
default: '',
},
},
data() {
return {
errorMessage: '',
};
},
methods: {
getFeatureDocumentationLinkLabel(item) {
return sprintf(this.$options.i18n.docsLinkLabel, {
featureName: item.name,
});
},
onError(value) {
this.errorMessage = value;
},
},
fields: [
{
key: 'description',
label: s__('SecurityConfiguration|Security Control'),
thClass,
},
{
key: 'status',
label: s__('SecurityConfiguration|Status'),
thClass,
},
{
key: 'manage',
label: s__('SecurityConfiguration|Manage'),
thClass,
},
],
i18n: {
docsLinkLabel: s__('SecurityConfiguration|Feature documentation for %{featureName}'),
docsLinkText: s__('SecurityConfiguration|More information'),
},
};
</script>
<template>
<div>
<gl-alert v-if="errorMessage" variant="danger" :dismissible="false">
{{ errorMessage }}
</gl-alert>
<gl-table
:items="features"
:fields="$options.fields"
stacked="md"
:tbody-tr-attr="{ 'data-testid': 'security-scanner-row' }"
>
<template #cell(description)="{ item }">
<div class="gl-text-gray-900">{{ item.name }}</div>
<div>
{{ item.description }}
<gl-link
target="_blank"
:href="item.helpPath"
:aria-label="getFeatureDocumentationLinkLabel(item)"
>
{{ $options.i18n.docsLinkText }}
</gl-link>
</div>
</template>
<template #cell(status)="{ item }">
<feature-status
:feature="item"
:gitlab-ci-present="gitlabCiPresent"
:gitlab-ci-history-path="gitlabCiHistoryPath"
:auto-devops-enabled="autoDevopsEnabled"
:data-qa-selector="`${item.type}_status`"
/>
</template>
<template #cell(manage)="{ item }">
<manage-feature :feature="item" @error="onError" />
</template>
</gl-table>
</div>
</template>
<script>
import { propsUnion } from '~/vue_shared/components/lib/utils/props_utils';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_DAST_PROFILES,
REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants';
import StatusDastProfiles from './status_dast_profiles.vue';
import StatusGeneric from './status_generic.vue';
import StatusViewHistory from './status_view_history.vue';
const scannerComponentMap = {
[REPORT_TYPE_SAST]: StatusViewHistory,
[REPORT_TYPE_DAST_PROFILES]: StatusDastProfiles,
[REPORT_TYPE_SECRET_DETECTION]: StatusViewHistory,
};
export default {
props: propsUnion([StatusGeneric, ...Object.values(scannerComponentMap)]),
computed: {
statusComponent() {
return scannerComponentMap[this.feature.type] ?? StatusGeneric;
},
},
};
</script>
<template>
<component :is="statusComponent" v-bind="$props" />
</template>
<script>
import { GlButton } from '@gitlab/ui';
export default {
components: {
GlButton,
},
props: {
feature: {
type: Object,
required: true,
},
},
};
</script>
<template>
<gl-button :href="feature.configuration_path" data-testid="manage-button">{{
s__('SecurityConfiguration|Manage')
}}</gl-button>
</template>
<script>
import { propsUnion } from '~/vue_shared/components/lib/utils/props_utils';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import {
REPORT_TYPE_DAST_PROFILES,
REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants';
import ManageDastProfiles from './manage_dast_profiles.vue';
import ManageGeneric from './manage_generic.vue';
const scannerComponentMap = {
[REPORT_TYPE_DAST_PROFILES]: ManageDastProfiles,
[REPORT_TYPE_DEPENDENCY_SCANNING]: ManageViaMr,
[REPORT_TYPE_SECRET_DETECTION]: ManageViaMr,
};
export default {
mixins: [glFeatureFlagMixin()],
props: propsUnion([ManageGeneric, ...Object.values(scannerComponentMap)]),
computed: {
filteredScannerComponentMap() {
const scannerComponentMapCopy = { ...scannerComponentMap };
if (!this.glFeatures.secDependencyScanningUiEnable) {
delete scannerComponentMapCopy[REPORT_TYPE_DEPENDENCY_SCANNING];
}
return scannerComponentMapCopy;
},
manageComponent() {
return this.filteredScannerComponentMap[this.feature.type] ?? ManageGeneric;
},
},
};
</script>
<template>
<component :is="manageComponent" v-bind="$props" @error="$emit('error', $event)" />
</template>
<script>
import { GlButton } from '@gitlab/ui';
export default {
components: {
GlButton,
},
props: {
feature: {
type: Object,
required: true,
},
},
computed: {
canConfigure() {
return Boolean(this.feature.configuration_path && this.feature.configured);
},
canEnable() {
return Boolean(this.feature.configuration_path && !this.feature.configured);
},
},
};
</script>
<template>
<gl-button
v-if="canConfigure"
:href="feature.configuration_path"
data-testid="configure-button"
>{{ s__('SecurityConfiguration|Configure') }}</gl-button
>
<gl-button
v-else-if="canEnable"
variant="confirm"
category="primary"
:href="feature.configuration_path"
data-testid="enable-button"
:data-qa-selector="`${feature.type}_enable_button`"
>{{ s__('SecurityConfiguration|Enable') }}</gl-button
>
</template>
<script>
import { s__ } from '~/locale';
export default {
i18n: {
availableForOnDemand: s__('SecurityConfiguration|Available for on-demand DAST'),
},
};
</script>
<template>
<div>{{ $options.i18n.availableForOnDemand }}</div>
</template>
<script>
import { s__ } from '~/locale';
export default {
props: {
feature: {
type: Object,
required: true,
},
autoDevopsEnabled: {
type: Boolean,
required: true,
},
},
computed: {
status() {
if (this.feature.configured) {
return this.autoDevopsEnabled
? this.$options.i18n.enabledWithAutoDevOps
: this.$options.i18n.enabled;
}
return this.$options.i18n.notEnabled;
},
},
i18n: {
enabled: s__('SecurityConfiguration|Enabled'),
enabledWithAutoDevOps: s__('SecurityConfiguration|Enabled with Auto DevOps'),
notEnabled: s__('SecurityConfiguration|Not enabled'),
},
};
</script>
<template>
<div>{{ status }}</div>
</template>
<script>
import { GlLink } from '@gitlab/ui';
import StatusGeneric from './status_generic.vue';
export default {
components: {
GlLink,
StatusGeneric,
},
props: {
feature: {
type: Object,
required: true,
},
autoDevopsEnabled: {
type: Boolean,
required: true,
},
gitlabCiPresent: {
type: Boolean,
required: false,
default: false,
},
gitlabCiHistoryPath: {
type: String,
required: false,
default: '',
},
},
computed: {
canViewCiHistory() {
return this.feature.configured && this.gitlabCiPresent;
},
},
};
</script>
<template>
<div>
<status-generic :feature="feature" :auto-devops-enabled="autoDevopsEnabled" />
<gl-link v-if="canViewCiHistory" :href="gitlabCiHistoryPath">{{
s__('SecurityConfiguration|View history')
}}</gl-link>
</div>
</template>
import Vue from 'vue';
import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils';
import { initRedesignedSecurityConfiguration } from '~/security_configuration';
import SecurityConfigurationApp from './components/app.vue';
export const initSecurityConfiguration = (el) => {
if (!el) {
return null;
}
if (gon.features?.securityConfigurationRedesignEE) {
return initRedesignedSecurityConfiguration(el);
}
const {
autoDevopsHelpPagePath,
autoDevopsPath,
features,
latestPipelinePath,
autoFixEnabled,
autoFixHelpPath,
autoFixUserPath,
containerScanningHelpPath,
dependencyScanningHelpPath,
toggleAutofixSettingEndpoint,
projectPath,
gitlabCiHistoryPath,
} = el.dataset;
return new Vue({
el,
components: {
SecurityConfigurationApp,
},
provide: {
projectPath,
},
render(createElement) {
return createElement(SecurityConfigurationApp, {
props: {
autoDevopsHelpPagePath,
autoDevopsPath,
features: JSON.parse(features),
latestPipelinePath,
...parseBooleanDataAttributes(el, [
'autoDevopsEnabled',
'canEnableAutoDevops',
'gitlabCiPresent',
]),
gitlabCiHistoryPath,
autoFixSettingsProps: {
autoFixEnabled: JSON.parse(autoFixEnabled),
autoFixHelpPath,
autoFixUserPath,
containerScanningHelpPath,
dependencyScanningHelpPath,
toggleAutofixSettingEndpoint,
...parseBooleanDataAttributes(el, ['canToggleAutoFixSettings']),
},
},
});
},
});
};
...@@ -23,10 +23,6 @@ module EE ...@@ -23,10 +23,6 @@ module EE
end end
feature_category :static_application_security_testing feature_category :static_application_security_testing
before_action only: [:show] do
push_frontend_feature_flag(:security_configuration_redesign_ee, project, default_enabled: :yaml)
end
end end
# rubocop:disable Gitlab/ModuleWithInstanceVariables # rubocop:disable Gitlab/ModuleWithInstanceVariables
......
- breadcrumb_title _("Security Configuration") - breadcrumb_title _("Security Configuration")
- page_title _("Security Configuration") - page_title _("Security Configuration")
- redesign_enabled = ::Feature.enabled?(:security_configuration_redesign_ee, @project, default_enabled: :yaml) - @content_class = "limit-container-width" unless fluid_layout
- @content_class = "limit-container-width" unless fluid_layout || !redesign_enabled
- if @configuration.nil? - if @configuration.nil?
= render_ce 'projects/security/configuration/show' = render_ce 'projects/security/configuration/show'
......
...@@ -15,7 +15,7 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -15,7 +15,7 @@ RSpec.describe 'User sees Security Configuration table', :js do
sign_in(user) sign_in(user)
end end
context 'with security_dashboard feature available and redesign feature flag on' do context 'with security_dashboard feature available' do
before do before do
stub_licensed_features(security_dashboard: true, sast: true, dast: true) stub_licensed_features(security_dashboard: true, sast: true, dast: true)
end end
...@@ -77,90 +77,6 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -77,90 +77,6 @@ RSpec.describe 'User sees Security Configuration table', :js do
end end
end end
context 'with security_dashboard feature available and redesign feature flag off' do
before do
stub_licensed_features(security_dashboard: true)
stub_feature_flags(security_configuration_redesign_ee: false)
end
context 'with no SAST report' do
it 'shows SAST is not enabled' do
visit(project_security_configuration_path(project))
within_sast_row do
expect(page).to have_text('SAST')
expect(page).to have_text('Not enabled')
expect(page).to have_link('Enable')
end
end
end
context 'with SAST report' do
before do
create(:ci_build, :sast, pipeline: pipeline, status: 'success')
end
it 'shows SAST is enabled' do
visit(project_security_configuration_path(project))
within_sast_row do
expect(page).to have_text('SAST')
expect(page).to have_text('Enabled')
expect(page).to have_link('Configure')
end
end
end
context 'with no DAST report' do
it 'shows DAST is not enabled' do
visit(project_security_configuration_path(project))
within_dast_row do
expect(page).to have_text('DAST')
expect(page).to have_text('Not enabled')
expect(page).to have_link('Enable')
end
end
end
context 'with DAST report' do
before do
create(:ci_build, :dast, pipeline: pipeline, status: 'success')
end
it 'shows DAST is enabled' do
visit(project_security_configuration_path(project))
within_dast_row do
expect(page).to have_text('DAST')
expect(page).to have_text('Enabled')
expect(page).to have_link('Configure')
end
end
it 'links to configuration page' do
visit(project_security_configuration_path(project))
within_dast_row do
click_link_or_button 'Configure'
expect(current_path).to eq(project_security_configuration_dast_path(project))
end
end
end
end
def within_sast_row
within '[data-testid="security-scanner-row"]:nth-of-type(1)' do
yield
end
end
def within_dast_row
within '[data-testid="security-scanner-row"]:nth-of-type(2)' do
yield
end
end
def within_sast_card def within_sast_card
within '[data-testid="security-testing-card"]:nth-of-type(1)' do within '[data-testid="security-testing-card"]:nth-of-type(1)' do
yield yield
......
import { GlAlert, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { merge } from 'lodash';
import SecurityConfigurationApp from 'ee/security_configuration/components/app.vue';
import ConfigurationTable from 'ee/security_configuration/components/configuration_table.vue';
import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import stubChildren from 'helpers/stub_children';
import { scanners } from '~/security_configuration/components/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { generateFeatures } from './helpers';
const propsData = {
features: [],
autoDevopsEnabled: false,
latestPipelinePath: 'http://latestPipelinePath',
autoDevopsHelpPagePath: 'http://autoDevopsHelpPagePath',
autoDevopsPath: 'http://autoDevopsPath',
helpPagePath: 'http://helpPagePath',
gitlabCiPresent: false,
gitlabCiHistoryPath: '/ci/history',
autoFixSettingsProps: {},
};
describe('Security Configuration App', () => {
let wrapper;
const createComponent = (options = {}) => {
wrapper = mount(
SecurityConfigurationApp,
merge(
{},
{
stubs: {
...stubChildren(SecurityConfigurationApp),
GlSprintf: false,
},
propsData,
},
options,
),
);
};
beforeEach(() => {
localStorage.clear();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
const getPipelinesLink = () => wrapper.find({ ref: 'pipelinesLink' });
const getConfigurationTable = () => wrapper.find(ConfigurationTable);
const getAlert = () => wrapper.find(GlAlert);
describe('header', () => {
it.each`
autoDevopsEnabled | expectedUrl
${true} | ${propsData.autoDevopsHelpPagePath}
${false} | ${propsData.latestPipelinePath}
`(
'displays a link to "$expectedUrl" when autoDevops is "$autoDevopsEnabled"',
({ autoDevopsEnabled, expectedUrl }) => {
createComponent({ propsData: { autoDevopsEnabled } });
expect(getPipelinesLink().attributes('href')).toBe(expectedUrl);
expect(getPipelinesLink().attributes('target')).toBe('_blank');
},
);
});
describe('Auto DevOps alert', () => {
describe.each`
gitlabCiPresent | autoDevopsEnabled | canEnableAutoDevops | dismissed | shouldShowAlert
${false} | ${false} | ${true} | ${false} | ${true}
${false} | ${false} | ${true} | ${true} | ${false}
${true} | ${false} | ${true} | ${false} | ${false}
${false} | ${true} | ${true} | ${false} | ${false}
${false} | ${false} | ${false} | ${false} | ${false}
`(
'given gitlabCiPresent is $gitlabCiPresent, autoDevopsEnabled is $autoDevopsEnabled, dismissed is $dismissed, canEnableAutoDevops is $canEnableAutoDevops',
({ gitlabCiPresent, autoDevopsEnabled, canEnableAutoDevops, dismissed, shouldShowAlert }) => {
beforeEach(() => {
if (dismissed) {
localStorage.setItem(SecurityConfigurationApp.autoDevopsAlertStorageKey, 'true');
}
createComponent({
propsData: {
gitlabCiPresent,
autoDevopsEnabled,
canEnableAutoDevops,
},
stubs: {
LocalStorageSync,
},
});
});
it(`is${shouldShowAlert ? '' : ' not'} rendered`, () => {
expect(getAlert().exists()).toBe(shouldShowAlert);
});
if (shouldShowAlert) {
it('has the expected text', () => {
expect(getAlert().text()).toMatchInterpolatedText(
SecurityConfigurationApp.autoDevopsAlertMessage,
);
});
it('has a link to the Auto DevOps docs', () => {
const link = getAlert().find(GlLink);
expect(link.attributes().href).toBe(propsData.autoDevopsHelpPagePath);
});
it('has the correct primary button', () => {
expect(getAlert().props()).toMatchObject({
title: 'Auto DevOps',
primaryButtonText: 'Enable Auto DevOps',
primaryButtonLink: propsData.autoDevopsPath,
});
});
}
},
);
describe('dismissing the alert', () => {
useLocalStorageSpy();
beforeEach(() => {
createComponent({
propsData: {
gitlabCiPresent: false,
autoDevopsEnabled: false,
canEnableAutoDevops: true,
},
stubs: {
LocalStorageSync,
},
});
getAlert().vm.$emit('dismiss');
});
it('hides the alert', () => {
expect(getAlert().exists()).toBe(false);
});
it('saves dismissal in localStorage', () => {
expect(localStorage.setItem.mock.calls).toEqual([
[SecurityConfigurationApp.autoDevopsAlertStorageKey, 'true'],
]);
});
});
});
describe('features table', () => {
it('passes the expected features to the configuration table', () => {
const features = generateFeatures(scanners.length);
createComponent({ propsData: { features } });
const table = getConfigurationTable();
const receivedFeatures = table.props('features');
scanners.forEach((scanner, i) => {
expect(receivedFeatures[i]).toMatchObject({
...features[i],
name: scanner.name,
description: scanner.description,
helpPath: scanner.helpPath,
});
});
});
it('passes the expected props data to the configuration table', () => {
createComponent();
expect(getConfigurationTable().props()).toMatchObject({
autoDevopsEnabled: propsData.autoDevopsEnabled,
gitlabCiPresent: propsData.gitlabCiPresent,
gitlabCiHistoryPath: propsData.gitlabCiHistoryPath,
});
});
});
});
import { mount } from '@vue/test-utils';
import AxiosMockAdapter from 'axios-mock-adapter';
import AutoFixSettings from 'ee/security_configuration/components/auto_fix_settings.vue';
import { TEST_HOST } from 'helpers/test_constants';
import waitForPromises from 'helpers/wait_for_promises';
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
jest.mock('~/flash.js');
const AUTO_FIX_USER_PATH = `${TEST_HOST}/security_bot`;
const AUTO_FIX_HELP_PATH = `${TEST_HOST}/help/auto_fix`;
const CONTAINER_SCANNING_HELP_PATH = `${TEST_HOST}/help/container_scanning`;
const DEPENDENCY_SCANNING_HELP_PATH = `${TEST_HOST}/help/dependency_scanning`;
const TOGGLE_AUTO_FIX_ENDPOINT = 'auto_fix';
const AUTO_FIX_ENABLED_PROPS = {
container_scanning: true,
dependency_scanning: true,
};
const AUTO_FIX_DISABLED_PROPS = {
container_scanning: false,
dependency_scanning: false,
};
const FOOTER_TEXT =
'Container Scanning and/or Dependency Scanning must be enabled. ' +
'GitLab-Security-Bot will be the author of the auto-created merge request. More information.';
describe('Auto-fix Settings', () => {
let axiosMock;
let wrapper;
const createComponent = (props = {}) => {
axiosMock = new AxiosMockAdapter(axios);
wrapper = mount(AutoFixSettings, {
propsData: {
autoFixHelpPath: AUTO_FIX_HELP_PATH,
autoFixUserPath: AUTO_FIX_USER_PATH,
containerScanningHelpPath: CONTAINER_SCANNING_HELP_PATH,
dependencyScanningHelpPath: DEPENDENCY_SCANNING_HELP_PATH,
toggleAutofixSettingEndpoint: TOGGLE_AUTO_FIX_ENDPOINT,
canToggleAutoFixSettings: true,
...props,
},
});
};
afterEach(() => {
wrapper.destroy();
wrapper = null;
axiosMock.restore();
});
const findCheckbox = () => wrapper.find('input[type="checkbox"]');
const findFooter = () => wrapper.find('footer');
const findFooterLinks = () => findFooter().findAll('a');
const getFooterTextContent = () => findFooter().text().trim();
const expectCheckboxDisabled = () => expect(findCheckbox().attributes().disabled).toBeTruthy();
const toggleCheckbox = () => findCheckbox().setChecked(!wrapper.vm.autoFixEnabledLocal);
const expectCorrectLinks = () => {
const links = findFooterLinks();
expect(links.length).toBe(4);
expect(links.at(0).attributes('href')).toBe(CONTAINER_SCANNING_HELP_PATH);
expect(links.at(1).attributes('href')).toBe(DEPENDENCY_SCANNING_HELP_PATH);
expect(links.at(2).attributes('href')).toBe(AUTO_FIX_USER_PATH);
expect(links.at(3).attributes('href')).toBe(AUTO_FIX_HELP_PATH);
};
const itShowsEnabledInformation = () => {
it('checkbox is checked', () => {
expect(findCheckbox().element.checked).toBeTruthy();
});
it('explains how GitLab Security Bot will author auto-fix MRs', () => {
expect(getFooterTextContent()).toBe(FOOTER_TEXT);
});
it('shows links to GitLab Security Bot profile and auto-fix documentation', () => {
expectCorrectLinks();
});
};
const itShowsDisabledInformation = () => {
it('checkbox is unchecked', () => {
expect(findCheckbox().element.checked).toBeFalsy();
});
it('explains how auto-fix will behave when enabled', () => {
expect(getFooterTextContent()).toBe(FOOTER_TEXT);
});
it('shows links ', () => {
expectCorrectLinks();
});
};
const itSendsPostRequest = () => {
it('sends a post request and sets loading state', () => {
expect(axiosMock.history.post).toHaveLength(1);
expectCheckboxDisabled();
});
};
describe.each`
description | autoFixEnabled | itShowsInitialState | itShowsToggleSuccessState
${'enabled'} | ${AUTO_FIX_ENABLED_PROPS} | ${itShowsEnabledInformation} | ${itShowsDisabledInformation}
${'disabled'} | ${AUTO_FIX_DISABLED_PROPS} | ${itShowsDisabledInformation} | ${itShowsEnabledInformation}
`(
'with auto-fix $description',
({ autoFixEnabled, itShowsInitialState, itShowsToggleSuccessState }) => {
beforeEach(() => {
createComponent({ autoFixEnabled });
});
itShowsInitialState();
describe('when toggling the checkbox', () => {
describe('on success', () => {
beforeEach(() => {
axiosMock.onPost(TOGGLE_AUTO_FIX_ENDPOINT).reply(200);
toggleCheckbox();
});
itSendsPostRequest();
describe('when request resolves', () => {
beforeEach(waitForPromises);
itShowsToggleSuccessState();
it('resets loading state', () => {
expect(findCheckbox().attributes().disabled).toBeFalsy();
});
});
});
describe('on error', () => {
beforeEach(() => {
axiosMock.onPost(TOGGLE_AUTO_FIX_ENDPOINT).reply(500);
toggleCheckbox();
});
itSendsPostRequest();
describe('when request resolves', () => {
beforeEach(waitForPromises);
itShowsInitialState();
it('shows error flash', () => {
expect(createFlash).toHaveBeenCalledWith({
message:
'Something went wrong while toggling auto-fix settings, please try again later.',
});
});
});
});
});
},
);
describe("when user isn't allowed to toggle auto-fix settings", () => {
beforeEach(() => {
createComponent({
canToggleAutoFixSettings: false,
autoFixEnabled: AUTO_FIX_ENABLED_PROPS,
});
});
it('disables the checkbox', () => {
expectCheckboxDisabled();
});
});
});
import { GlAlert, GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import ConfigurationTable from 'ee/security_configuration/components/configuration_table.vue';
import FeatureStatus from 'ee/security_configuration/components/feature_status.vue';
import ManageFeature from 'ee/security_configuration/components/manage_feature.vue';
import stubChildren from 'helpers/stub_children';
import { generateFeatures } from './helpers';
const propsData = {
features: [],
autoDevopsEnabled: false,
gitlabCiPresent: false,
gitlabCiHistoryPath: '/ci/history',
};
describe('ConfigurationTable component', () => {
let wrapper;
const mockFeatures = [
...generateFeatures(1, {
name: 'foo',
description: 'Foo description',
helpPath: '/help/foo',
}),
...generateFeatures(1, {
name: 'bar',
description: 'Bar description',
helpPath: '/help/bar',
}),
];
const createComponent = (props) => {
wrapper = mount(ConfigurationTable, {
stubs: {
...stubChildren(ConfigurationTable),
GlTable: false,
},
propsData: {
...propsData,
...props,
},
});
};
const getTable = () => wrapper.find('table');
const getRows = () => wrapper.findAll('tbody tr');
const getRowCells = (row) => {
const [description, status, manage] = row.findAll('td').wrappers;
return { description, status, manage };
};
afterEach(() => {
wrapper.destroy();
});
it.each(mockFeatures)('renders the feature %p correctly', (feature) => {
createComponent({ features: [feature] });
expect(getTable().classes('b-table-stacked-md')).toBe(true);
const rows = getRows();
expect(rows).toHaveLength(1);
const { description, status, manage } = getRowCells(rows.at(0));
expect(description.text()).toMatch(feature.name);
expect(description.text()).toMatch(feature.description);
expect(status.find(FeatureStatus).props()).toEqual({
feature,
gitlabCiPresent: propsData.gitlabCiPresent,
gitlabCiHistoryPath: propsData.gitlabCiHistoryPath,
autoDevopsEnabled: propsData.autoDevopsEnabled,
});
expect(manage.find(ManageFeature).props()).toMatchObject({ feature });
expect(description.find(GlLink).attributes('href')).toBe(feature.helpPath);
});
it('catches errors and displays them in an alert', async () => {
const error = 'error message';
createComponent({ features: mockFeatures });
const firstRow = getRows().at(0);
await firstRow.findComponent(ManageFeature).vm.$emit('error', error);
const alert = wrapper.findComponent(GlAlert);
expect(alert.exists()).toBe(true);
expect(alert.text()).toBe(error);
});
});
import { shallowMount } from '@vue/test-utils';
import { pick } from 'lodash';
import FeatureStatus from 'ee/security_configuration/components/feature_status.vue';
import StatusDastProfiles from 'ee/security_configuration/components/status_dast_profiles.vue';
import StatusGeneric from 'ee/security_configuration/components/status_generic.vue';
import StatusViewHistory from 'ee/security_configuration/components/status_view_history.vue';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_DAST_PROFILES,
REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants';
import { generateFeatures } from './helpers';
const props = {
gitlabCiPresent: true,
gitlabCiHistoryPath: '/ci-history',
autoDevopsEnabled: false,
};
const attrs = {
'data-qa-selector': 'foo',
};
describe('FeatureStatus component', () => {
let wrapper;
const createComponent = (options) => {
wrapper = shallowMount(FeatureStatus, options);
};
afterEach(() => {
wrapper.destroy();
});
describe('always', () => {
beforeEach(() => {
const [feature] = generateFeatures(1);
createComponent({ attrs, propsData: { feature, ...props } });
});
it('passes through attributes to the expected component', () => {
expect(wrapper.attributes()).toMatchObject(attrs);
});
});
describe.each`
type | expectedComponent
${REPORT_TYPE_SECRET_DETECTION} | ${StatusViewHistory}
${REPORT_TYPE_SAST} | ${StatusViewHistory}
${REPORT_TYPE_DAST_PROFILES} | ${StatusDastProfiles}
${'foo'} | ${StatusGeneric}
`('given a $type feature', ({ type, expectedComponent }) => {
let feature;
let component;
beforeEach(() => {
[feature] = generateFeatures(1, { type });
createComponent({ propsData: { feature, ...props } });
component = wrapper.findComponent(expectedComponent);
});
it('renders expected component', () => {
expect(component.exists()).toBe(true);
});
it('passes through props to expected component', () => {
// Exclude props not defined on the expected component, since
// @vue/test-utils won't include them in `Wrapper#props`.
const expectedProps = pick({ feature, ...props }, Object.keys(expectedComponent.props ?? {}));
expect(component.props()).toEqual(expectedProps);
});
});
});
import { scanners } from '~/security_configuration/components/constants';
export const generateFeatures = (n, overrides = {}) => {
return [...Array(n).keys()].map((i) => ({
type: scanners[i % scanners.length].type,
configuration_path: i % 2 ? `configuration_path-${i}` : null,
configured: i % 2 === 0,
status: i % 2 === 0 ? 'Enabled' : 'Not enabled',
...overrides,
}));
};
import { shallowMount } from '@vue/test-utils';
import ManageDastProfiles from 'ee/security_configuration/components/manage_dast_profiles.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { generateFeatures } from './helpers';
describe('ManageDastProfiles component', () => {
let wrapper;
const createComponent = (propsData) => {
wrapper = extendedWrapper(shallowMount(ManageDastProfiles, { propsData }));
};
const findButton = () => wrapper.findByTestId('manage-button');
afterEach(() => {
wrapper.destroy();
});
it('renders the DAST Profiles manage button', () => {
const [feature] = generateFeatures(1, { configuration_path: '/foo' });
createComponent({ feature });
const button = findButton();
expect(button.text()).toBe('Manage');
expect(button.attributes('href')).toBe(feature.configuration_path);
});
});
import { shallowMount } from '@vue/test-utils';
import ManageDastProfiles from 'ee/security_configuration/components/manage_dast_profiles.vue';
import ManageFeature from 'ee/security_configuration/components/manage_feature.vue';
import ManageGeneric from 'ee/security_configuration/components/manage_generic.vue';
import ManageViaMr from '~/vue_shared/security_configuration/components/manage_via_mr.vue';
import {
REPORT_TYPE_DAST_PROFILES,
REPORT_TYPE_DEPENDENCY_SCANNING,
REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants';
import { generateFeatures } from './helpers';
const attrs = {
'data-qa-selector': 'foo',
};
describe('ManageFeature component', () => {
let wrapper;
const createComponent = (options) => {
wrapper = shallowMount(ManageFeature, {
provide: {
glFeatures: {
secDependencyScanningUiEnable: true,
},
},
...options,
});
};
afterEach(() => {
wrapper.destroy();
});
describe('always', () => {
beforeEach(() => {
const [feature] = generateFeatures(1);
createComponent({ attrs, propsData: { feature } });
});
it('passes through attributes to the expected component', () => {
expect(wrapper.attributes()).toMatchObject(attrs);
});
it('re-emits caught errors', () => {
const component = wrapper.findComponent(ManageGeneric);
component.vm.$emit('error', 'testerror');
expect(wrapper.emitted('error')).toEqual([['testerror']]);
});
});
describe.each`
type | expectedComponent
${REPORT_TYPE_DAST_PROFILES} | ${ManageDastProfiles}
${REPORT_TYPE_DEPENDENCY_SCANNING} | ${ManageViaMr}
${REPORT_TYPE_SECRET_DETECTION} | ${ManageViaMr}
${'foo'} | ${ManageGeneric}
`('given a $type feature', ({ type, expectedComponent }) => {
let feature;
let component;
beforeEach(() => {
[feature] = generateFeatures(1, { type });
createComponent({ propsData: { feature } });
component = wrapper.findComponent(expectedComponent);
});
it('renders expected component', () => {
expect(component.exists()).toBe(true);
});
it('passes through props to expected component', () => {
expect(component.props()).toMatchObject({ feature });
});
});
it.each`
type | featureFlag
${REPORT_TYPE_DEPENDENCY_SCANNING} | ${'secDependencyScanningUiEnable'}
`('renders generic component for $type if $featureFlag is disabled', ({ type, featureFlag }) => {
const [feature] = generateFeatures(1, { type });
createComponent({
propsData: { feature },
provide: {
glFeatures: {
[featureFlag]: false,
},
},
});
expect(wrapper.findComponent(ManageGeneric).exists()).toBe(true);
});
});
import { shallowMount } from '@vue/test-utils';
import ManageGeneric from 'ee/security_configuration/components/manage_generic.vue';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import { generateFeatures } from './helpers';
describe('ManageGeneric component', () => {
let wrapper;
let feature;
const createComponent = (propsData) => {
wrapper = extendedWrapper(shallowMount(ManageGeneric, { propsData }));
};
afterEach(() => {
wrapper.destroy();
});
describe.each`
configured | expectedTestId
${true} | ${'configure-button'}
${false} | ${'enable-button'}
`('given feature.configured is $configured', ({ configured, expectedTestId }) => {
describe('given a configuration path', () => {
beforeEach(() => {
[feature] = generateFeatures(1, { configured, configuration_path: 'foo' });
createComponent({ feature });
});
it('shows a button to configure the feature', () => {
const button = wrapper.findByTestId(expectedTestId);
expect(button.exists()).toBe(true);
expect(button.attributes('href')).toBe(feature.configuration_path);
});
});
});
describe('given a feature without a configuration path', () => {
beforeEach(() => {
[feature] = generateFeatures(1, { configuration_path: null });
createComponent({ feature });
});
it('renders nothing', () => {
expect(wrapper.html()).toBe('');
});
});
});
import { shallowMount } from '@vue/test-utils';
import StatusDastProfiles from 'ee/security_configuration/components/status_dast_profiles.vue';
describe('StatusDastProfiles component', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(StatusDastProfiles);
};
afterEach(() => {
wrapper.destroy();
});
it('renders the fixed DAST Profiles status', () => {
createComponent();
expect(wrapper.element).toMatchInlineSnapshot(`
<div>
Available for on-demand DAST
</div>
`);
});
});
import { shallowMount } from '@vue/test-utils';
import StatusGeneric from 'ee/security_configuration/components/status_generic.vue';
import { generateFeatures } from './helpers';
describe('StatusGeneric component', () => {
let wrapper;
const createComponent = (options) => {
wrapper = shallowMount(StatusGeneric, options);
};
afterEach(() => {
wrapper.destroy();
});
describe.each`
context | configured | autoDevopsEnabled | status
${'not configured'} | ${false} | ${false} | ${StatusGeneric.i18n.notEnabled}
${'not configured, but Auto DevOps is enabled'} | ${false} | ${true} | ${StatusGeneric.i18n.notEnabled}
${'configured'} | ${true} | ${false} | ${StatusGeneric.i18n.enabled}
${'configured with Auto DevOps'} | ${true} | ${true} | ${StatusGeneric.i18n.enabledWithAutoDevOps}
`('given the feature is $context', ({ configured, autoDevopsEnabled, status }) => {
let feature;
beforeEach(() => {
[feature] = generateFeatures(1, { configured });
createComponent({
propsData: { feature, autoDevopsEnabled },
});
});
it(`shows the status "${status}"`, () => {
expect(wrapper.text()).toBe(status);
});
});
});
import { GlLink } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import StatusGeneric from 'ee/security_configuration/components/status_generic.vue';
import StatusViewHistory from 'ee/security_configuration/components/status_view_history.vue';
import { REPORT_TYPE_SAST } from '~/vue_shared/security_reports/constants';
import { generateFeatures } from './helpers';
const gitlabCiHistoryPath = '/ci/history';
const autoDevopsEnabled = true;
describe('StatusSast component', () => {
let wrapper;
const createComponent = (options) => {
wrapper = shallowMount(StatusViewHistory, options);
};
afterEach(() => {
wrapper.destroy();
});
const findHistoryLink = () => wrapper.find(GlLink);
describe.each`
context | configured | gitlabCiPresent | shouldShowHistory
${'no CI with sast disabled'} | ${false} | ${false} | ${false}
${'CI with sast disabled'} | ${false} | ${true} | ${false}
${'no CI with sast enabled'} | ${true} | ${false} | ${false}
${'CI with sast enabled'} | ${true} | ${true} | ${true}
`('given $context', ({ configured, gitlabCiPresent, shouldShowHistory }) => {
let feature;
beforeEach(() => {
[feature] = generateFeatures(1, { type: REPORT_TYPE_SAST, configured });
createComponent({
propsData: { feature, gitlabCiPresent, gitlabCiHistoryPath, autoDevopsEnabled },
});
});
it('shows the generic status', () => {
const genericComponent = wrapper.findComponent(StatusGeneric);
expect(genericComponent.exists()).toBe(true);
expect(genericComponent.props()).toEqual({
feature,
autoDevopsEnabled,
});
});
it(`${shouldShowHistory ? 'shows' : 'does not show'} the history link`, () => {
expect(findHistoryLink().exists()).toBe(shouldShowHistory);
});
if (shouldShowHistory) {
it("sets the link's href correctly", () => {
expect(findHistoryLink().attributes('href')).toBe(gitlabCiHistoryPath);
});
}
});
});
...@@ -464,9 +464,6 @@ msgstr[1] "" ...@@ -464,9 +464,6 @@ msgstr[1] ""
msgid "%{completedWeight} of %{totalWeight} weight completed" msgid "%{completedWeight} of %{totalWeight} weight completed"
msgstr "" msgstr ""
msgid "%{containerScanningLinkStart}Container Scanning%{containerScanningLinkEnd} and/or %{dependencyScanningLinkStart}Dependency Scanning%{dependencyScanningLinkEnd} must be enabled. %{securityBotLinkStart}GitLab-Security-Bot%{securityBotLinkEnd} will be the author of the auto-created merge request. %{moreInfoLinkStart}More information%{moreInfoLinkEnd}."
msgstr ""
msgid "%{cores} cores" msgid "%{cores} cores"
msgstr "" msgstr ""
...@@ -4896,9 +4893,6 @@ msgstr "" ...@@ -4896,9 +4893,6 @@ msgstr ""
msgid "Automatically close associated incident when a recovery alert notification resolves an alert" msgid "Automatically close associated incident when a recovery alert notification resolves an alert"
msgstr "" msgstr ""
msgid "Automatically create merge requests for vulnerabilities that have fixes available."
msgstr ""
msgid "Automatically resolved" msgid "Automatically resolved"
msgstr "" msgstr ""
...@@ -4914,9 +4908,6 @@ msgstr "" ...@@ -4914,9 +4908,6 @@ msgstr ""
msgid "Available ID" msgid "Available ID"
msgstr "" msgstr ""
msgid "Available for dependency and container scanning"
msgstr ""
msgid "Available group runners: %{runners}" msgid "Available group runners: %{runners}"
msgstr "" msgstr ""
...@@ -29292,12 +29283,6 @@ msgstr "" ...@@ -29292,12 +29283,6 @@ msgstr ""
msgid "SecurityConfiguration|An error occurred while creating the merge request." msgid "SecurityConfiguration|An error occurred while creating the merge request."
msgstr "" msgstr ""
msgid "SecurityConfiguration|Available for on-demand DAST"
msgstr ""
msgid "SecurityConfiguration|Available with %{linkStart}upgrade or free trial%{linkEnd}"
msgstr ""
msgid "SecurityConfiguration|Available with Ultimate" msgid "SecurityConfiguration|Available with Ultimate"
msgstr "" msgstr ""
...@@ -29313,9 +29298,6 @@ msgstr "" ...@@ -29313,9 +29298,6 @@ msgstr ""
msgid "SecurityConfiguration|Configuration history" msgid "SecurityConfiguration|Configuration history"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Configure"
msgstr ""
msgid "SecurityConfiguration|Configure %{feature}" msgid "SecurityConfiguration|Configure %{feature}"
msgstr "" msgstr ""
...@@ -29337,9 +29319,6 @@ msgstr "" ...@@ -29337,9 +29319,6 @@ msgstr ""
msgid "SecurityConfiguration|Customize common SAST settings to suit your requirements. Configuration changes made here override those provided by GitLab and are excluded from updates. For details of more advanced configuration options, see the %{linkStart}GitLab SAST documentation%{linkEnd}." msgid "SecurityConfiguration|Customize common SAST settings to suit your requirements. Configuration changes made here override those provided by GitLab and are excluded from updates. For details of more advanced configuration options, see the %{linkStart}GitLab SAST documentation%{linkEnd}."
msgstr "" msgstr ""
msgid "SecurityConfiguration|Enable"
msgstr ""
msgid "SecurityConfiguration|Enable %{feature}" msgid "SecurityConfiguration|Enable %{feature}"
msgstr "" msgstr ""
...@@ -29349,30 +29328,18 @@ msgstr "" ...@@ -29349,30 +29328,18 @@ msgstr ""
msgid "SecurityConfiguration|Enabled" msgid "SecurityConfiguration|Enabled"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Enabled with Auto DevOps"
msgstr ""
msgid "SecurityConfiguration|Feature documentation for %{featureName}"
msgstr ""
msgid "SecurityConfiguration|High-level vulnerability statistics across projects and groups" msgid "SecurityConfiguration|High-level vulnerability statistics across projects and groups"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Immediately begin risk analysis and remediation with application security features. Start with SAST and Secret Detection, available to all plans. Upgrade to Ultimate to get all features, including:" msgid "SecurityConfiguration|Immediately begin risk analysis and remediation with application security features. Start with SAST and Secret Detection, available to all plans. Upgrade to Ultimate to get all features, including:"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Manage"
msgstr ""
msgid "SecurityConfiguration|Manage profiles for use by DAST scans." msgid "SecurityConfiguration|Manage profiles for use by DAST scans."
msgstr "" msgstr ""
msgid "SecurityConfiguration|Manage scans" msgid "SecurityConfiguration|Manage scans"
msgstr "" msgstr ""
msgid "SecurityConfiguration|More information"
msgstr ""
msgid "SecurityConfiguration|More scan types, including Container Scanning, DAST, Dependency Scanning, Fuzzing, and Licence Compliance" msgid "SecurityConfiguration|More scan types, including Container Scanning, DAST, Dependency Scanning, Fuzzing, and Licence Compliance"
msgstr "" msgstr ""
...@@ -29397,18 +29364,9 @@ msgstr "" ...@@ -29397,18 +29364,9 @@ msgstr ""
msgid "SecurityConfiguration|Secure your project" msgid "SecurityConfiguration|Secure your project"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Security Control"
msgstr ""
msgid "SecurityConfiguration|Security testing" msgid "SecurityConfiguration|Security testing"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Status"
msgstr ""
msgid "SecurityConfiguration|Testing & Compliance"
msgstr ""
msgid "SecurityConfiguration|The status of the tools only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}." msgid "SecurityConfiguration|The status of the tools only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}."
msgstr "" msgstr ""
...@@ -29418,15 +29376,9 @@ msgstr "" ...@@ -29418,15 +29376,9 @@ msgstr ""
msgid "SecurityConfiguration|Using custom settings. You won't receive automatic updates on this variable. %{anchorStart}Restore to default%{anchorEnd}" msgid "SecurityConfiguration|Using custom settings. You won't receive automatic updates on this variable. %{anchorStart}Restore to default%{anchorEnd}"
msgstr "" msgstr ""
msgid "SecurityConfiguration|View history"
msgstr ""
msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request" msgid "SecurityConfiguration|Vulnerability details and statistics in the merge request"
msgstr "" msgstr ""
msgid "SecurityConfiguration|You can quickly enable all security scanning tools by enabling %{linkStart}Auto DevOps%{linkEnd}."
msgstr ""
msgid "SecurityOrchestration|An error occurred assigning your security policy project" msgid "SecurityOrchestration|An error occurred assigning your security policy project"
msgstr "" msgstr ""
...@@ -30405,9 +30357,6 @@ msgstr "" ...@@ -30405,9 +30357,6 @@ msgstr ""
msgid "Setup" msgid "Setup"
msgstr "" msgstr ""
msgid "Several security scans are enabled because %{linkStart}Auto DevOps%{linkEnd} is enabled on this project"
msgstr ""
msgid "Severity" msgid "Severity"
msgstr "" msgstr ""
...@@ -31027,9 +30976,6 @@ msgstr "" ...@@ -31027,9 +30976,6 @@ msgstr ""
msgid "Something went wrong while stopping this environment. Please try again." msgid "Something went wrong while stopping this environment. Please try again."
msgstr "" msgstr ""
msgid "Something went wrong while toggling auto-fix settings, please try again later."
msgstr ""
msgid "Something went wrong while updating a requirement." msgid "Something went wrong while updating a requirement."
msgstr "" msgstr ""
...@@ -31987,15 +31933,9 @@ msgstr "" ...@@ -31987,15 +31933,9 @@ msgstr ""
msgid "Suggest code changes which can be immediately applied in one click. Try it out!" msgid "Suggest code changes which can be immediately applied in one click. Try it out!"
msgstr "" msgstr ""
msgid "Suggested Solutions"
msgstr ""
msgid "Suggested change" msgid "Suggested change"
msgstr "" msgstr ""
msgid "Suggested solutions help link"
msgstr ""
msgid "SuggestedColors|Aztec Gold" msgid "SuggestedColors|Aztec Gold"
msgstr "" msgstr ""
...@@ -33271,9 +33211,6 @@ msgstr "" ...@@ -33271,9 +33211,6 @@ msgstr ""
msgid "The start date must be ealier than the end date." msgid "The start date must be ealier than the end date."
msgstr "" msgstr ""
msgid "The status of the table below only applies to the default branch and is based on the %{linkStart}latest pipeline%{linkEnd}. Once you've enabled a scan for the default branch, any subsequent feature branch you create will include the scan."
msgstr ""
msgid "The subject will be used as the title of the new issue, and the message will be the description. %{quickActionsLinkStart}Quick actions%{quickActionsLinkEnd} and styling with %{markdownLinkStart}Markdown%{markdownLinkEnd} are supported." msgid "The subject will be used as the title of the new issue, and the message will be the description. %{quickActionsLinkStart}Quick actions%{quickActionsLinkEnd} and styling with %{markdownLinkStart}Markdown%{markdownLinkEnd} are supported."
msgstr "" msgstr ""
......
import { shallowMount } from '@vue/test-utils';
import App from '~/security_configuration/components/app.vue';
import ConfigurationTable from '~/security_configuration/components/configuration_table.vue';
describe('App Component', () => {
let wrapper;
const createComponent = () => {
wrapper = shallowMount(App, {});
};
const findConfigurationTable = () => wrapper.findComponent(ConfigurationTable);
afterEach(() => {
wrapper.destroy();
});
it('renders correct primary & Secondary Heading', () => {
createComponent();
expect(wrapper.text()).toContain('Security Configuration');
expect(wrapper.text()).toContain('Testing & Compliance');
});
it('renders ConfigurationTable Component', () => {
createComponent();
expect(findConfigurationTable().exists()).toBe(true);
});
});
...@@ -4,6 +4,7 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper'; ...@@ -4,6 +4,7 @@ import { useLocalStorageSpy } from 'helpers/local_storage_helper';
import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser'; import { makeMockUserCalloutDismisser } from 'helpers/mock_user_callout_dismisser';
import stubChildren from 'helpers/stub_children'; import stubChildren from 'helpers/stub_children';
import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import SecurityConfigurationApp, { i18n } from '~/security_configuration/components/app.vue';
import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue'; import AutoDevopsAlert from '~/security_configuration/components/auto_dev_ops_alert.vue';
import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev_ops_enabled_alert.vue'; import AutoDevopsEnabledAlert from '~/security_configuration/components/auto_dev_ops_enabled_alert.vue';
import { import {
...@@ -19,9 +20,6 @@ import { ...@@ -19,9 +20,6 @@ import {
} from '~/security_configuration/components/constants'; } from '~/security_configuration/components/constants';
import FeatureCard from '~/security_configuration/components/feature_card.vue'; import FeatureCard from '~/security_configuration/components/feature_card.vue';
import RedesignedSecurityConfigurationApp, {
i18n,
} from '~/security_configuration/components/redesigned_app.vue';
import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue'; import UpgradeBanner from '~/security_configuration/components/upgrade_banner.vue';
import { import {
REPORT_TYPE_LICENSE_COMPLIANCE, REPORT_TYPE_LICENSE_COMPLIANCE,
...@@ -36,7 +34,7 @@ const projectPath = 'namespace/project'; ...@@ -36,7 +34,7 @@ const projectPath = 'namespace/project';
useLocalStorageSpy(); useLocalStorageSpy();
describe('redesigned App component', () => { describe('App component', () => {
let wrapper; let wrapper;
let userCalloutDismissSpy; let userCalloutDismissSpy;
...@@ -44,7 +42,7 @@ describe('redesigned App component', () => { ...@@ -44,7 +42,7 @@ describe('redesigned App component', () => {
userCalloutDismissSpy = jest.fn(); userCalloutDismissSpy = jest.fn();
wrapper = extendedWrapper( wrapper = extendedWrapper(
mount(RedesignedSecurityConfigurationApp, { mount(SecurityConfigurationApp, {
propsData, propsData,
provide: { provide: {
upgradePath, upgradePath,
...@@ -53,7 +51,7 @@ describe('redesigned App component', () => { ...@@ -53,7 +51,7 @@ describe('redesigned App component', () => {
projectPath, projectPath,
}, },
stubs: { stubs: {
...stubChildren(RedesignedSecurityConfigurationApp), ...stubChildren(SecurityConfigurationApp),
GlLink: false, GlLink: false,
GlSprintf: false, GlSprintf: false,
LocalStorageSync: false, LocalStorageSync: false,
......
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import ConfigurationTable from '~/security_configuration/components/configuration_table.vue';
import { scanners, UPGRADE_CTA } from '~/security_configuration/components/constants';
import {
REPORT_TYPE_SAST,
REPORT_TYPE_SECRET_DETECTION,
} from '~/vue_shared/security_reports/constants';
describe('Configuration Table Component', () => {
let wrapper;
const createComponent = () => {
wrapper = extendedWrapper(
mount(ConfigurationTable, {
provide: {
projectPath: 'testProjectPath',
},
}),
);
};
const findHelpLinks = () => wrapper.findAll('[data-testid="help-link"]');
afterEach(() => {
wrapper.destroy();
});
beforeEach(() => {
createComponent();
});
describe.each(scanners.map((scanner, i) => [scanner, i]))('given scanner %s', (scanner, i) => {
it('should match strings', () => {
expect(wrapper.text()).toContain(scanner.name);
expect(wrapper.text()).toContain(scanner.description);
if (scanner.type === REPORT_TYPE_SAST) {
expect(wrapper.findByTestId(scanner.type).text()).toBe('Configure via Merge Request');
} else if (scanner.type === REPORT_TYPE_SECRET_DETECTION) {
expect(wrapper.findByTestId(scanner.type).exists()).toBe(false);
} else {
expect(wrapper.findByTestId(scanner.type).text()).toMatchInterpolatedText(UPGRADE_CTA);
}
});
it('should show expected help link', () => {
const helpLink = findHelpLinks().at(i);
expect(helpLink.attributes('href')).toBe(scanner.helpPath);
});
});
});
import { mount } from '@vue/test-utils';
import { UPGRADE_CTA } from '~/security_configuration/components/constants';
import Upgrade from '~/security_configuration/components/upgrade.vue';
const TEST_URL = 'http://www.example.test';
let wrapper;
const createComponent = (componentData = {}) => {
wrapper = mount(Upgrade, componentData);
};
afterEach(() => {
wrapper.destroy();
});
describe('Upgrade component', () => {
beforeEach(() => {
createComponent({ provide: { upgradePath: TEST_URL } });
});
it('renders correct text in link', () => {
expect(wrapper.text()).toMatchInterpolatedText(UPGRADE_CTA);
});
it('renders link with correct default attributes', () => {
expect(wrapper.find('a').attributes()).toMatchObject({
href: TEST_URL,
target: '_blank',
});
});
});
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