Commit 042f10f1 authored by Jannik Lehmann's avatar Jannik Lehmann

Root Component for redesign of Security & Compliance Configuration Page

This commit introduces the root Component for
the redesigned Security & Compliance Configuration Page,
as well as the security_configuration_redesign feature flag
which toggle between the two versions.
parent bf020294
...@@ -18,18 +18,27 @@ import { ...@@ -18,18 +18,27 @@ import {
* Translations & helpPagePaths for Static Security Configuration Page * Translations & helpPagePaths for Static 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 = __('SAST');
export const SAST_DESCRIPTION = __('Analyze your source code for known vulnerabilities.'); export const SAST_DESCRIPTION = __('Analyze your source code for known vulnerabilities.');
export const SAST_HELP_PATH = helpPagePath('user/application_security/sast/index'); export const SAST_HELP_PATH = helpPagePath('user/application_security/sast/index');
export const SAST_CONFIG_HELP_PATH = helpPagePath('user/application_security/sast/index', {
anchor: 'configuration',
});
export const DAST_NAME = __('Dynamic Application Security Testing (DAST)'); export const DAST_NAME = __('Dynamic Application Security Testing (DAST)');
export const DAST_SHORT_NAME = __('DAST');
export const DAST_DESCRIPTION = __('Analyze a review version of your web application.'); export const DAST_DESCRIPTION = __('Analyze a review version of your web application.');
export const DAST_HELP_PATH = helpPagePath('user/application_security/dast/index'); export const DAST_HELP_PATH = helpPagePath('user/application_security/dast/index');
export const DAST_CONFIG_HELP_PATH = helpPagePath('user/application_security/dast/index', {
anchor: 'enable-dast',
});
export const DAST_PROFILES_NAME = __('DAST Scans'); export const DAST_PROFILES_NAME = __('DAST Scans');
export const DAST_PROFILES_DESCRIPTION = __( export const DAST_PROFILES_DESCRIPTION = __(
'Saved scan settings and target site settings which are reusable.', 'Saved scan settings and target site settings which are reusable.',
); );
export const DAST_PROFILES_HELP_PATH = helpPagePath('user/application_security/dast/index'); export const DAST_PROFILES_HELP_PATH = helpPagePath('user/application_security/dast/index');
export const DAST_PROFILES_CONFIG_TEXT = s__('SecurityConfiguration|Manage scans');
export const SECRET_DETECTION_NAME = __('Secret Detection'); export const SECRET_DETECTION_NAME = __('Secret Detection');
export const SECRET_DETECTION_DESCRIPTION = __( export const SECRET_DETECTION_DESCRIPTION = __(
...@@ -38,6 +47,10 @@ export const SECRET_DETECTION_DESCRIPTION = __( ...@@ -38,6 +47,10 @@ export const SECRET_DETECTION_DESCRIPTION = __(
export const SECRET_DETECTION_HELP_PATH = helpPagePath( export const SECRET_DETECTION_HELP_PATH = helpPagePath(
'user/application_security/secret_detection/index', 'user/application_security/secret_detection/index',
); );
export const SECRET_DETECTION_CONFIG_HELP_PATH = helpPagePath(
'user/application_security/secret_detection/index',
{ anchor: 'configuration' },
);
export const DEPENDENCY_SCANNING_NAME = __('Dependency Scanning'); export const DEPENDENCY_SCANNING_NAME = __('Dependency Scanning');
export const DEPENDENCY_SCANNING_DESCRIPTION = __( export const DEPENDENCY_SCANNING_DESCRIPTION = __(
...@@ -46,6 +59,10 @@ export const DEPENDENCY_SCANNING_DESCRIPTION = __( ...@@ -46,6 +59,10 @@ export const DEPENDENCY_SCANNING_DESCRIPTION = __(
export const DEPENDENCY_SCANNING_HELP_PATH = helpPagePath( export const DEPENDENCY_SCANNING_HELP_PATH = helpPagePath(
'user/application_security/dependency_scanning/index', 'user/application_security/dependency_scanning/index',
); );
export const DEPENDENCY_SCANNING_CONFIG_HELP_PATH = helpPagePath(
'user/application_security/dependency_scanning/index',
{ anchor: 'configuration' },
);
export const CONTAINER_SCANNING_NAME = __('Container Scanning'); export const CONTAINER_SCANNING_NAME = __('Container Scanning');
export const CONTAINER_SCANNING_DESCRIPTION = __( export const CONTAINER_SCANNING_DESCRIPTION = __(
...@@ -54,6 +71,10 @@ export const CONTAINER_SCANNING_DESCRIPTION = __( ...@@ -54,6 +71,10 @@ export const CONTAINER_SCANNING_DESCRIPTION = __(
export const CONTAINER_SCANNING_HELP_PATH = helpPagePath( export const CONTAINER_SCANNING_HELP_PATH = helpPagePath(
'user/application_security/container_scanning/index', 'user/application_security/container_scanning/index',
); );
export const CONTAINER_SCANNING_CONFIG_HELP_PATH = helpPagePath(
'user/application_security/container_scanning/index',
{ anchor: 'configuration' },
);
export const COVERAGE_FUZZING_NAME = __('Coverage Fuzzing'); export const COVERAGE_FUZZING_NAME = __('Coverage Fuzzing');
export const COVERAGE_FUZZING_DESCRIPTION = __( export const COVERAGE_FUZZING_DESCRIPTION = __(
...@@ -136,6 +157,83 @@ export const scanners = [ ...@@ -136,6 +157,83 @@ export const scanners = [
}, },
]; ];
export const securityFeatures = [
{
name: SAST_NAME,
shortName: SAST_SHORT_NAME,
description: SAST_DESCRIPTION,
helpPath: SAST_HELP_PATH,
configurationHelpPath: SAST_CONFIG_HELP_PATH,
type: REPORT_TYPE_SAST,
// This field is currently hardcoded because SAST is always availabled
// It will eventually come from the Backend, the progress is tracked in
// https://gitlab.com/gitlab-org/gitlab/-/issues/331622
available: true,
// This field is currently hardcoded because SAST can always be enabled via MR
// It will eventually come from the Backend, the progress is tracked in
// https://gitlab.com/gitlab-org/gitlab/-/issues/331621
canEnableByMergeRequest: true,
},
{
name: DAST_NAME,
shortName: DAST_SHORT_NAME,
description: DAST_DESCRIPTION,
helpPath: DAST_HELP_PATH,
configurationHelpPath: DAST_CONFIG_HELP_PATH,
type: REPORT_TYPE_DAST,
secondary: {
type: REPORT_TYPE_DAST_PROFILES,
name: DAST_PROFILES_NAME,
description: DAST_PROFILES_DESCRIPTION,
configurationText: DAST_PROFILES_CONFIG_TEXT,
},
},
{
name: DEPENDENCY_SCANNING_NAME,
description: DEPENDENCY_SCANNING_DESCRIPTION,
helpPath: DEPENDENCY_SCANNING_HELP_PATH,
configurationHelpPath: DEPENDENCY_SCANNING_CONFIG_HELP_PATH,
type: REPORT_TYPE_DEPENDENCY_SCANNING,
},
{
name: CONTAINER_SCANNING_NAME,
description: CONTAINER_SCANNING_DESCRIPTION,
helpPath: CONTAINER_SCANNING_HELP_PATH,
configurationHelpPath: CONTAINER_SCANNING_CONFIG_HELP_PATH,
type: REPORT_TYPE_CONTAINER_SCANNING,
},
{
name: SECRET_DETECTION_NAME,
description: SECRET_DETECTION_DESCRIPTION,
helpPath: SECRET_DETECTION_HELP_PATH,
configurationHelpPath: SECRET_DETECTION_CONFIG_HELP_PATH,
type: REPORT_TYPE_SECRET_DETECTION,
available: true,
},
{
name: API_FUZZING_NAME,
description: API_FUZZING_DESCRIPTION,
helpPath: API_FUZZING_HELP_PATH,
type: REPORT_TYPE_API_FUZZING,
},
{
name: COVERAGE_FUZZING_NAME,
description: COVERAGE_FUZZING_DESCRIPTION,
helpPath: COVERAGE_FUZZING_HELP_PATH,
type: REPORT_TYPE_COVERAGE_FUZZING,
},
];
export const complianceFeatures = [
{
name: LICENSE_COMPLIANCE_NAME,
description: LICENSE_COMPLIANCE_DESCRIPTION,
helpPath: LICENSE_COMPLIANCE_HELP_PATH,
type: REPORT_TYPE_LICENSE_COMPLIANCE,
},
];
export const featureToMutationMap = { export const featureToMutationMap = {
[REPORT_TYPE_SAST]: { [REPORT_TYPE_SAST]: {
mutationId: 'configureSast', mutationId: 'configureSast',
......
<script>
import { GlTab, GlTabs, GlSprintf, GlLink } from '@gitlab/ui';
import { __, s__ } from '~/locale';
import FeatureCard from './feature_card.vue';
export default {
components: {
GlTab,
GlLink,
GlTabs,
GlSprintf,
FeatureCard,
},
props: {
augmentedSecurityFeatures: {
type: Array,
required: true,
},
latestPipelinePath: {
type: String,
required: false,
default: '',
},
},
i18n: {
compliance: s__('SecurityConfiguration|Compliance'),
securityTesting: s__('SecurityConfiguration|Security testing'),
securityTestingDescription: s__(
`SecurityConfiguration|The status of the tools 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.`,
),
securityConfiguration: __('Security Configuration'),
},
};
</script>
<template>
<article>
<header>
<h1 class="gl-font-size-h1">{{ $options.i18n.securityConfiguration }}</h1>
</header>
<gl-tabs content-class="gl-pt-6">
<gl-tab data-testid="security-testing-tab" :title="$options.i18n.securityTesting">
<div class="row">
<div class="col-lg-5">
<h2 class="gl-font-size-h2 gl-mt-0">{{ $options.i18n.securityTesting }}</h2>
<p v-if="latestPipelinePath" class="gl-line-height-20">
<gl-sprintf :message="$options.i18n.securityTestingDescription">
<template #link="{ content }">
<gl-link :href="latestPipelinePath">{{ content }}</gl-link>
</template>
</gl-sprintf>
</p>
</div>
<div class="col-lg-7">
<feature-card
v-for="feature in augmentedSecurityFeatures"
:key="feature.type"
:feature="feature"
class="gl-mb-6"
/>
</div>
</div>
</gl-tab>
</gl-tabs>
</article>
</template>
...@@ -2,6 +2,9 @@ import Vue from 'vue'; ...@@ -2,6 +2,9 @@ import Vue from 'vue';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import createDefaultClient from '~/lib/graphql'; import createDefaultClient from '~/lib/graphql';
import SecurityConfigurationApp from './components/app.vue'; import SecurityConfigurationApp from './components/app.vue';
import { securityFeatures, complianceFeatures } from './components/constants';
import RedesignedSecurityConfigurationApp from './components/redesigned_app.vue';
import { augmentFeatures } from './utils';
export const initStaticSecurityConfiguration = (el) => { export const initStaticSecurityConfiguration = (el) => {
if (!el) { if (!el) {
...@@ -14,8 +17,32 @@ export const initStaticSecurityConfiguration = (el) => { ...@@ -14,8 +17,32 @@ export const initStaticSecurityConfiguration = (el) => {
defaultClient: createDefaultClient(), defaultClient: createDefaultClient(),
}); });
const { projectPath, upgradePath } = el.dataset; const { projectPath, upgradePath, features, latestPipelinePath } = el.dataset;
if (gon.features.securityConfigurationRedesign) {
const { augmentedSecurityFeatures } = augmentFeatures(
securityFeatures,
complianceFeatures,
features ? JSON.parse(features) : [],
);
return new Vue({
el,
apolloProvider,
provide: {
projectPath,
upgradePath,
},
render(createElement) {
return createElement(RedesignedSecurityConfigurationApp, {
props: {
augmentedSecurityFeatures,
latestPipelinePath,
},
});
},
});
}
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
......
export const augmentFeatures = (securityFeatures, complianceFeatures, features = []) => {
const featuresByType = features.reduce((acc, feature) => {
acc[feature.type] = feature;
return acc;
}, {});
const augmentFeature = (feature) => {
const augmented = {
...feature,
...featuresByType[feature.type],
};
if (augmented.secondary) {
augmented.secondary = { ...augmented.secondary, ...featuresByType[feature.secondary.type] };
}
return augmented;
};
return {
augmentedSecurityFeatures: securityFeatures.map(augmentFeature),
augmentedComplianceFeatures: complianceFeatures.map(augmentFeature),
};
};
...@@ -7,6 +7,10 @@ module Projects ...@@ -7,6 +7,10 @@ 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
......
---
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
...@@ -10006,6 +10006,9 @@ msgstr "" ...@@ -10006,6 +10006,9 @@ msgstr ""
msgid "DAG visualization requires at least 3 dependent jobs." msgid "DAG visualization requires at least 3 dependent jobs."
msgstr "" msgstr ""
msgid "DAST"
msgstr ""
msgid "DAST Configuration" msgid "DAST Configuration"
msgstr "" msgstr ""
...@@ -28480,6 +28483,9 @@ msgstr "" ...@@ -28480,6 +28483,9 @@ msgstr ""
msgid "SAML for %{group_name}" msgid "SAML for %{group_name}"
msgstr "" msgstr ""
msgid "SAST"
msgstr ""
msgid "SAST Configuration" msgid "SAST Configuration"
msgstr "" msgstr ""
...@@ -28955,6 +28961,9 @@ msgstr "" ...@@ -28955,6 +28961,9 @@ msgstr ""
msgid "SecurityConfiguration|By default, all analyzers are applied in order to cover all languages across your project, and only run if the language is detected in the Merge Request." msgid "SecurityConfiguration|By default, all analyzers are applied in order to cover all languages across your project, and only run if the language is detected in the Merge Request."
msgstr "" msgstr ""
msgid "SecurityConfiguration|Compliance"
msgstr ""
msgid "SecurityConfiguration|Configuration guide" msgid "SecurityConfiguration|Configuration guide"
msgstr "" msgstr ""
...@@ -28994,6 +29003,9 @@ msgstr "" ...@@ -28994,6 +29003,9 @@ msgstr ""
msgid "SecurityConfiguration|Manage" msgid "SecurityConfiguration|Manage"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Manage scans"
msgstr ""
msgid "SecurityConfiguration|More information" msgid "SecurityConfiguration|More information"
msgstr "" msgstr ""
...@@ -29009,12 +29021,18 @@ msgstr "" ...@@ -29009,12 +29021,18 @@ msgstr ""
msgid "SecurityConfiguration|Security Control" msgid "SecurityConfiguration|Security Control"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Security testing"
msgstr ""
msgid "SecurityConfiguration|Status" msgid "SecurityConfiguration|Status"
msgstr "" msgstr ""
msgid "SecurityConfiguration|Testing & Compliance" msgid "SecurityConfiguration|Testing & Compliance"
msgstr "" msgstr ""
msgid "SecurityConfiguration|The status of the tools 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 "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 ""
......
import { GlTab, GlTabs } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import FeatureCard from '~/security_configuration/components/feature_card.vue';
import RedesignedSecurityConfigurationApp from '~/security_configuration/components/redesigned_app.vue';
describe('NewApp component', () => {
let wrapper;
const createComponent = (propsData) => {
wrapper = extendedWrapper(
mount(RedesignedSecurityConfigurationApp, {
propsData,
}),
);
};
const findMainHeading = () => wrapper.find('h1');
const findSubHeading = () => wrapper.find('h2');
const findTab = () => wrapper.find(GlTab);
const findTabs = () => wrapper.findAll(GlTabs);
const findByTestId = (id) => wrapper.findByTestId(id);
const findFeatureCards = () => wrapper.findAll(FeatureCard);
const securityFeaturesMock = [
{
name: 'Static Application Security Testing (SAST)',
shortName: 'SAST',
description: 'Analyze your source code for known vulnerabilities.',
helpPath: '/help/user/application_security/sast/index',
configurationHelpPath: '/help/user/application_security/sast/index#configuration',
type: 'sast',
available: true,
},
];
afterEach(() => {
wrapper.destroy();
});
describe('basic structure', () => {
beforeEach(() => {
createComponent({
augmentedSecurityFeatures: securityFeaturesMock,
});
});
it('renders main-heading with correct text', () => {
const mainHeading = findMainHeading();
expect(mainHeading).toExist();
expect(mainHeading.text()).toContain('Security Configuration');
});
it('renders GlTab Component ', () => {
expect(findTab()).toExist();
});
it('renders right amount of tabs with correct title ', () => {
expect(findTabs().length).toEqual(1);
});
it('renders security-testing tab', () => {
expect(findByTestId('security-testing-tab')).toExist();
});
it('renders sub-heading with correct text', () => {
const subHeading = findSubHeading();
expect(subHeading).toExist();
expect(subHeading.text()).toContain('Security testing');
});
it('renders right amount of feature cards for given props with correct props', () => {
const cards = findFeatureCards();
expect(cards.length).toEqual(1);
expect(cards.at(0).props()).toEqual({ feature: securityFeaturesMock[0] });
});
});
});
export const mockSecurityFeatures = [
{
name: 'SAST',
type: 'SAST',
},
];
export const mockComplianceFeatures = [
{
name: 'LICENSE_COMPLIANCE',
type: 'LICENSE_COMPLIANCE',
},
];
export const mockFeaturesWithSecondary = [
{
name: 'DAST',
type: 'DAST',
secondary: {
type: 'DAST PROFILES',
name: 'DAST PROFILES',
},
},
];
export const mockInvalidCustomFeature = [
{
foo: 'bar',
},
];
export const mockValidCustomFeature = [
{
name: 'SAST',
type: 'SAST',
customfield: 'customvalue',
},
];
export const expectedOutputDefault = {
augmentedSecurityFeatures: mockSecurityFeatures,
augmentedComplianceFeatures: mockComplianceFeatures,
};
export const expectedOutputSecondary = {
augmentedSecurityFeatures: mockSecurityFeatures,
augmentedComplianceFeatures: mockFeaturesWithSecondary,
};
export const expectedOutputCustomFeature = {
augmentedSecurityFeatures: mockValidCustomFeature,
augmentedComplianceFeatures: mockComplianceFeatures,
};
import { augmentFeatures } from '~/security_configuration/utils';
import {
mockSecurityFeatures,
mockComplianceFeatures,
mockFeaturesWithSecondary,
mockInvalidCustomFeature,
mockValidCustomFeature,
expectedOutputCustomFeature,
expectedOutputDefault,
expectedOutputSecondary,
} from './utils_mocks';
describe('augmentFeatures', () => {
it('augments features array correctly when given an empty array', () => {
expect(augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, [])).toEqual(
expectedOutputDefault,
);
});
it('augments features array correctly when given an invalid populated array', () => {
expect(
augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockInvalidCustomFeature),
).toEqual(expectedOutputDefault);
});
it('augments features array correctly when features have secondary key', () => {
expect(augmentFeatures(mockSecurityFeatures, mockFeaturesWithSecondary, [])).toEqual(
expectedOutputSecondary,
);
});
it('augments features array correctly when given a valid populated array', () => {
expect(
augmentFeatures(mockSecurityFeatures, mockComplianceFeatures, mockValidCustomFeature),
).toEqual(expectedOutputCustomFeature);
});
});
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