Commit b4ea0859 authored by Phil Hughes's avatar Phil Hughes

Merge branch 'jnnkl-security-config-redesign-ee-baseline' into 'master'

Bring redesigned security-configuration page to EE

See merge request gitlab-org/gitlab!65171
parents 384861b8 66e23443
import { initStaticSecurityConfiguration } from '~/security_configuration'; import { initCESecurityConfiguration } from '~/security_configuration';
initStaticSecurityConfiguration(document.querySelector('#js-security-configuration-static')); initCESecurityConfiguration(document.querySelector('#js-security-configuration-static'));
...@@ -112,6 +112,7 @@ export default { ...@@ -112,6 +112,7 @@ export default {
<feature-card <feature-card
v-for="feature in augmentedSecurityFeatures" v-for="feature in augmentedSecurityFeatures"
:key="feature.type" :key="feature.type"
data-testid="security-testing-card"
:feature="feature" :feature="feature"
class="gl-mb-6" class="gl-mb-6"
/> />
......
...@@ -7,11 +7,7 @@ import { securityFeatures, complianceFeatures } from './components/constants'; ...@@ -7,11 +7,7 @@ import { securityFeatures, complianceFeatures } from './components/constants';
import RedesignedSecurityConfigurationApp from './components/redesigned_app.vue'; import RedesignedSecurityConfigurationApp from './components/redesigned_app.vue';
import { augmentFeatures } from './utils'; import { augmentFeatures } from './utils';
export const initStaticSecurityConfiguration = (el) => { export const initRedesignedSecurityConfiguration = (el) => {
if (!el) {
return null;
}
Vue.use(VueApollo); Vue.use(VueApollo);
const apolloProvider = new VueApollo({ const apolloProvider = new VueApollo({
...@@ -26,33 +22,50 @@ export const initStaticSecurityConfiguration = (el) => { ...@@ -26,33 +22,50 @@ export const initStaticSecurityConfiguration = (el) => {
gitlabCiHistoryPath, gitlabCiHistoryPath,
} = el.dataset; } = el.dataset;
if (gon.features.securityConfigurationRedesign) { const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures(
const { augmentedSecurityFeatures, augmentedComplianceFeatures } = augmentFeatures( securityFeatures,
securityFeatures, complianceFeatures,
complianceFeatures, features ? JSON.parse(features) : [],
features ? JSON.parse(features) : [], );
);
return new Vue({
el,
apolloProvider,
provide: {
projectPath,
upgradePath,
},
render(createElement) {
return createElement(RedesignedSecurityConfigurationApp, {
props: {
augmentedComplianceFeatures,
augmentedSecurityFeatures,
latestPipelinePath,
gitlabCiHistoryPath,
...parseBooleanDataAttributes(el, ['gitlabCiPresent']),
},
});
},
});
};
export const initCESecurityConfiguration = (el) => {
if (!el) {
return null;
}
return new Vue({ if (gon.features?.securityConfigurationRedesign) {
el, return initRedesignedSecurityConfiguration(el);
apolloProvider,
provide: {
projectPath,
upgradePath,
},
render(createElement) {
return createElement(RedesignedSecurityConfigurationApp, {
props: {
augmentedComplianceFeatures,
augmentedSecurityFeatures,
latestPipelinePath,
gitlabCiHistoryPath,
...parseBooleanDataAttributes(el, ['gitlabCiPresent']),
},
});
},
});
} }
Vue.use(VueApollo);
const apolloProvider = new VueApollo({
defaultClient: createDefaultClient(),
});
const { projectPath, upgradePath } = el.dataset;
return new Vue({ return new Vue({
el, el,
apolloProvider, apolloProvider,
......
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
export const augmentFeatures = (securityFeatures, complianceFeatures, features = []) => { export const augmentFeatures = (securityFeatures, complianceFeatures, features = []) => {
const featuresByType = features.reduce((acc, feature) => { const featuresByType = features.reduce((acc, feature) => {
acc[feature.type] = feature; acc[feature.type] = convertObjectPropsToCamelCase(feature, { deep: true });
return acc; return acc;
}, {}); }, {});
......
...@@ -74,6 +74,7 @@ export default { ...@@ -74,6 +74,7 @@ export default {
<template> <template>
<gl-button <gl-button
v-if="!feature.configured" v-if="!feature.configured"
data-testid="configure-via-mr-button"
:loading="isLoading" :loading="isLoading"
:variant="variant" :variant="variant"
:category="category" :category="category"
......
- breadcrumb_title _("Security Configuration") - breadcrumb_title _("Security Configuration")
- page_title _("Security Configuration") - page_title _("Security Configuration")
- redesign_enabled = ::Feature.enabled?(:security_configuration_redesign, default_enabled: :yaml) - redesign_enabled = ::Feature.enabled?(:security_configuration_redesign, @project, default_enabled: :yaml)
- @content_class = "limit-container-width" unless fluid_layout || !redesign_enabled - @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_ee
introduced_by_url:
rollout_issue_url:
milestone: '14.1'
type: development
group: group::analyzer frontend
default_enabled: false
...@@ -28,10 +28,15 @@ For each security control the page displays: ...@@ -28,10 +28,15 @@ For each security control the page displays:
## UI redesign ## 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. > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/326926) in 14.0 for GitLab Free and Premium, behind a feature flag, disabled by default.
> - Enabled on GitLab.com. > - Enabled on GitLab.com for Free & Premium.
> - Recommended for production use. > - Recommended for production use.
> - It can be enabled or disabled for a single project. > - 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)** > - 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: WARNING:
This feature might not be available to you. Check the **version history** note above for details. This feature might not be available to you. Check the **version history** note above for details.
...@@ -98,3 +103,28 @@ Feature.disable(:security_configuration_redesign) ...@@ -98,3 +103,28 @@ Feature.disable(:security_configuration_redesign)
# For a single project # For a single project
Feature.disable(:security_configuration_redesign, Project.find(<project id>)) 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 'ee/security_configuration';
import { initStaticSecurityConfiguration } from '~/security_configuration'; import { initCESecurityConfiguration } from '~/security_configuration';
const el = document.querySelector('#js-security-configuration'); const el = document.querySelector('#js-security-configuration');
if (el) { if (el) {
initSecurityConfiguration(el); initSecurityConfiguration(el);
} else { } else {
initStaticSecurityConfiguration(document.querySelector('#js-security-configuration-static')); initCESecurityConfiguration(document.querySelector('#js-security-configuration-static'));
} }
import Vue from 'vue'; import Vue from 'vue';
import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils'; import { parseBooleanDataAttributes } from '~/lib/utils/dom_utils';
import { initRedesignedSecurityConfiguration } from '~/security_configuration';
import SecurityConfigurationApp from './components/app.vue'; import SecurityConfigurationApp from './components/app.vue';
export const initSecurityConfiguration = (el) => { export const initSecurityConfiguration = (el) => {
...@@ -7,6 +8,10 @@ export const initSecurityConfiguration = (el) => { ...@@ -7,6 +8,10 @@ export const initSecurityConfiguration = (el) => {
return null; return null;
} }
if (gon.features?.securityConfigurationRedesignEE) {
return initRedesignedSecurityConfiguration(el);
}
const { const {
autoDevopsHelpPagePath, autoDevopsHelpPagePath,
autoDevopsPath, autoDevopsPath,
......
...@@ -24,6 +24,10 @@ module EE ...@@ -24,6 +24,10 @@ 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
......
...@@ -75,7 +75,8 @@ module Projects ...@@ -75,7 +75,8 @@ module Projects
{ {
type: type, type: type,
configured: configured, configured: configured,
configuration_path: configuration_path(type) configuration_path: configuration_path(type),
available: feature_available(type)
} }
end end
...@@ -95,6 +96,13 @@ module Projects ...@@ -95,6 +96,13 @@ module Projects
api_fuzzing: project_security_configuration_api_fuzzing_path(project) api_fuzzing: project_security_configuration_api_fuzzing_path(project)
}[type] }[type]
end end
def feature_available(type)
# SAST and Secret Detection are always available, but this isn't
# reflected by our license model yet.
# TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/333113
%w[sast secret_detection].include?(type) || project.licensed_feature_available?(type)
end
end end
end end
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_ee, @project, default_enabled: :yaml)
- @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,9 +15,72 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -15,9 +15,72 @@ RSpec.describe 'User sees Security Configuration table', :js do
sign_in(user) sign_in(user)
end end
context 'with security_dashboard feature available' do context 'with security_dashboard feature available and redesign feature flag on' do
before do
stub_licensed_features(security_dashboard: true, sast: true, dast: true)
end
context 'with no SAST report' do
it 'shows SAST is not enabled' do
visit(project_security_configuration_path(project))
within_sast_card do
expect(page).to have_text('SAST')
expect(page).to have_text('Not enabled')
expect(page).to have_link('Enable SAST')
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_card do
expect(page).to have_text('SAST')
expect(page).to have_text('Enabled')
expect(page).to have_link('Configure SAST')
end
end
end
context 'with no DAST report' do
it 'shows DAST is not enabled' do
visit(project_security_configuration_path(project))
within_dast_card do
expect(page).to have_text('DAST')
expect(page).to have_text('Not enabled')
expect(page).to have_link('Enable DAST')
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_card do
expect(page).to have_text('DAST')
expect(page).to have_text('Enabled')
expect(page).to have_link('Configure DAST')
end
end
end
end
context 'with security_dashboard feature available and redesign feature flag off' do
before do before do
stub_licensed_features(security_dashboard: true) stub_licensed_features(security_dashboard: true)
stub_feature_flags(security_configuration_redesign_ee: false)
end end
context 'with no SAST report' do context 'with no SAST report' do
...@@ -27,7 +90,7 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -27,7 +90,7 @@ RSpec.describe 'User sees Security Configuration table', :js do
within_sast_row do within_sast_row do
expect(page).to have_text('SAST') expect(page).to have_text('SAST')
expect(page).to have_text('Not enabled') expect(page).to have_text('Not enabled')
expect(page).to have_css('[data-testid="enable-button"]') expect(page).to have_link('Enable')
end end
end end
end end
...@@ -43,7 +106,7 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -43,7 +106,7 @@ RSpec.describe 'User sees Security Configuration table', :js do
within_sast_row do within_sast_row do
expect(page).to have_text('SAST') expect(page).to have_text('SAST')
expect(page).to have_text('Enabled') expect(page).to have_text('Enabled')
expect(page).to have_css('[data-testid="configure-button"]') expect(page).to have_link('Configure')
end end
end end
end end
...@@ -55,7 +118,7 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -55,7 +118,7 @@ RSpec.describe 'User sees Security Configuration table', :js do
within_dast_row do within_dast_row do
expect(page).to have_text('DAST') expect(page).to have_text('DAST')
expect(page).to have_text('Not enabled') expect(page).to have_text('Not enabled')
expect(page).to have_css('[data-testid="enable-button"]') expect(page).to have_link('Enable')
end end
end end
end end
...@@ -71,7 +134,7 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -71,7 +134,7 @@ RSpec.describe 'User sees Security Configuration table', :js do
within_dast_row do within_dast_row do
expect(page).to have_text('DAST') expect(page).to have_text('DAST')
expect(page).to have_text('Enabled') expect(page).to have_text('Enabled')
expect(page).to have_css('[data-testid="configure-button"]') expect(page).to have_link('Configure')
end end
end end
...@@ -97,4 +160,16 @@ RSpec.describe 'User sees Security Configuration table', :js do ...@@ -97,4 +160,16 @@ RSpec.describe 'User sees Security Configuration table', :js do
yield yield
end end
end end
def within_sast_card
within '[data-testid="security-testing-card"]:nth-of-type(1)' do
yield
end
end
def within_dast_card
within '[data-testid="security-testing-card"]:nth-of-type(2)' do
yield
end
end
end end
...@@ -17,6 +17,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -17,6 +17,7 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
before do before do
project.add_maintainer(current_user) project.add_maintainer(current_user)
stub_licensed_features(licensed_scan_types.to_h { |type| [type, true] })
end end
describe '#to_h' do describe '#to_h' do
...@@ -265,7 +266,8 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -265,7 +266,8 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
{ {
"type" => type.to_s, "type" => type.to_s,
"configured" => configured, "configured" => configured,
"configuration_path" => configuration_path "configuration_path" => configuration_path,
"available" => licensed_scan_types.include?(type)
} }
end end
...@@ -277,4 +279,8 @@ RSpec.describe Projects::Security::ConfigurationPresenter do ...@@ -277,4 +279,8 @@ RSpec.describe Projects::Security::ConfigurationPresenter do
api_fuzzing: project_security_configuration_api_fuzzing_path(project) api_fuzzing: project_security_configuration_api_fuzzing_path(project)
}[type] }[type]
end end
def licensed_scan_types
::Security::SecurityJobsFinder.allowed_job_types + ::Security::LicenseComplianceJobsFinder.allowed_job_types - [:cluster_image_scanning]
end
end end
...@@ -35,7 +35,15 @@ const mockValidCustomFeature = [ ...@@ -35,7 +35,15 @@ const mockValidCustomFeature = [
{ {
name: 'SAST', name: 'SAST',
type: 'SAST', type: 'SAST',
customfield: 'customvalue', customField: 'customvalue',
},
];
const mockValidCustomFeatureSnakeCase = [
{
name: 'SAST',
type: 'SAST',
custom_field: 'customvalue',
}, },
]; ];
...@@ -79,3 +87,15 @@ describe('returns an object with augmentedSecurityFeatures and augmentedComplian ...@@ -79,3 +87,15 @@ describe('returns an object with augmentedSecurityFeatures and augmentedComplian
).toEqual(expectedOutputCustomFeature); ).toEqual(expectedOutputCustomFeature);
}); });
}); });
describe('returns an object with camelcased keys', () => {
it('given a customfeature in snakecase', () => {
expect(
augmentFeatures(
mockSecurityFeatures,
mockComplianceFeatures,
mockValidCustomFeatureSnakeCase,
),
).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