Commit 93c8002a authored by James Johnson's avatar James Johnson Committed by Mayra Cabrera

Adds a CVE ID Request button to issue sidebar

Also:

* Adds a setting to enable the button in the project general settings
* Adds a new column to the `project_settings` database table
* Adds new vue components:
    * project_feature_labeled_toggle.vue
    * cve_id_request_sidebar.vue
parent a3aa78d8
export function createCveIdRequestIssueBody(fullPath, iid) {
return `### Vulnerability Submission
**NOTE:** Only maintainers of GitLab-hosted projects may request a CVE for
a vulnerability within their project.
Project issue: ${fullPath}#${iid}
#### Publishing Schedule
After a CVE request is validated, a CVE identifier will be assigned. On what
schedule should the details of the CVE be published?
* [ ] Publish immediately
* [ ] Wait to publish
<!--
Please fill out the yaml codeblock below
-->
\`\`\`yaml
reporter:
name: "TODO" # "First Last"
email: "TODO" # "email@domain.tld"
vulnerability:
description: "TODO" # "[VULNTYPE] in [COMPONENT] in [VENDOR][PRODUCT] [VERSION] allows [ATTACKER] to [IMPACT] via [VECTOR]"
cwe: "TODO" # "CWE-22" # Path Traversal
product:
gitlab_path: "${fullPath}"
vendor: "TODO" # "Deluxe Sandwich Maker Company"
name: "TODO" # "Deluxe Sandwich Maker 2"
affected_versions:
- "TODO" # "1.2.3"
- "TODO" # ">1.3.0, <=1.3.9"
fixed_versions:
- "TODO" # "1.2.4"
- "TODO" # "1.3.10"
impact: "TODO" # "CVSS v3 string" # https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator
solution: "TODO" # "Upgrade to version 1.2.4 or 1.3.10"
credit: "TODO"
references:
- "TODO" # "https://some.domain.tld/a/reference"
\`\`\`
CVSS scores can be computed by means of the [NVD CVSS Calculator](https://nvd.nist.gov/vuln-metrics/cvss/v3-calculator).
/relate ${fullPath}#${iid}
/label ~"devops::secure" ~"group::vulnerability research" ~"vulnerability research::cve" ~"advisory::queued"
`;
}
...@@ -11,6 +11,7 @@ import { ...@@ -11,6 +11,7 @@ import {
featureAccessLevelEveryone, featureAccessLevelEveryone,
featureAccessLevel, featureAccessLevel,
featureAccessLevelNone, featureAccessLevelNone,
CVE_ID_REQUEST_BUTTON_I18N,
} from '../constants'; } from '../constants';
import { toggleHiddenClassBySelector } from '../external'; import { toggleHiddenClassBySelector } from '../external';
import projectFeatureSetting from './project_feature_setting.vue'; import projectFeatureSetting from './project_feature_setting.vue';
...@@ -19,6 +20,10 @@ import projectSettingRow from './project_setting_row.vue'; ...@@ -19,6 +20,10 @@ import projectSettingRow from './project_setting_row.vue';
const PAGE_FEATURE_ACCESS_LEVEL = s__('ProjectSettings|Everyone'); const PAGE_FEATURE_ACCESS_LEVEL = s__('ProjectSettings|Everyone');
export default { export default {
i18n: {
...CVE_ID_REQUEST_BUTTON_I18N,
},
components: { components: {
projectFeatureSetting, projectFeatureSetting,
projectSettingRow, projectSettingRow,
...@@ -31,6 +36,11 @@ export default { ...@@ -31,6 +36,11 @@ export default {
mixins: [settingsMixin, glFeatureFlagsMixin()], mixins: [settingsMixin, glFeatureFlagsMixin()],
props: { props: {
requestCveAvailable: {
type: Boolean,
required: false,
default: false,
},
currentSettings: { currentSettings: {
type: Object, type: Object,
required: true, required: true,
...@@ -99,6 +109,11 @@ export default { ...@@ -99,6 +109,11 @@ export default {
required: false, required: false,
default: '', default: '',
}, },
cveIdRequestHelpPath: {
type: String,
required: false,
default: '',
},
registryHelpPath: { registryHelpPath: {
type: String, type: String,
required: false, required: false,
...@@ -152,6 +167,7 @@ export default { ...@@ -152,6 +167,7 @@ export default {
requestAccessEnabled: true, requestAccessEnabled: true,
highlightChangesClass: false, highlightChangesClass: false,
emailsDisabled: false, emailsDisabled: false,
cveIdRequestEnabled: true,
featureAccessLevelEveryone, featureAccessLevelEveryone,
featureAccessLevelMembers, featureAccessLevelMembers,
}; };
...@@ -230,6 +246,9 @@ export default { ...@@ -230,6 +246,9 @@ export default {
'ProjectSettings|View and edit files in this project. Non-project members will only have read access.', 'ProjectSettings|View and edit files in this project. Non-project members will only have read access.',
); );
}, },
cveIdRequestIsDisabled() {
return this.visibilityLevel !== visibilityOptions.PUBLIC;
},
}, },
watch: { watch: {
...@@ -417,6 +436,19 @@ export default { ...@@ -417,6 +436,19 @@ export default {
:options="featureAccessLevelOptions" :options="featureAccessLevelOptions"
name="project[project_feature_attributes][issues_access_level]" name="project[project_feature_attributes][issues_access_level]"
/> />
<project-setting-row
v-if="requestCveAvailable"
:help-path="cveIdRequestHelpPath"
:help-text="$options.i18n.cve_request_toggle_label"
>
<gl-toggle
v-model="cveIdRequestEnabled"
class="gl-my-2"
:disabled="cveIdRequestIsDisabled"
name="project[project_setting_attributes][cve_id_request_enabled]"
data-testid="cve_id_request_toggle"
/>
</project-setting-row>
</project-setting-row> </project-setting-row>
<project-setting-row <project-setting-row
ref="repository-settings" ref="repository-settings"
......
import { __ } from '~/locale'; import { s__, __ } from '~/locale';
export const visibilityOptions = { export const visibilityOptions = {
PRIVATE: 0, PRIVATE: 0,
...@@ -42,3 +42,7 @@ export const featureAccessLevelEveryone = [ ...@@ -42,3 +42,7 @@ export const featureAccessLevelEveryone = [
featureAccessLevel.EVERYONE, featureAccessLevel.EVERYONE,
featureAccessLevelDescriptions[featureAccessLevel.EVERYONE], featureAccessLevelDescriptions[featureAccessLevel.EVERYONE],
]; ];
export const CVE_ID_REQUEST_BUTTON_I18N = {
cve_request_toggle_label: s__('CVE|Enable CVE ID requests in the issue sidebar'),
};
...@@ -118,6 +118,8 @@ ...@@ -118,6 +118,8 @@
%script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe %script#js-confidential-issue-data{ type: "application/json" }= { is_confidential: issuable_sidebar[:confidential], is_editable: can_edit_issuable }.to_json.html_safe
#js-confidential-entry-point #js-confidential-entry-point
= render_if_exists 'shared/issuable/sidebar_cve_id_request', issuable_sidebar: issuable_sidebar
%script#js-lock-issue-data{ type: "application/json" }= { is_locked: !!issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe %script#js-lock-issue-data{ type: "application/json" }= { is_locked: !!issuable_sidebar[:discussion_locked], is_editable: can_edit_issuable }.to_json.html_safe
#js-lock-entry-point #js-lock-entry-point
......
---
title: Adds Request CVE ID button to issue sidebar
merge_request: 41203
author:
type: added
# frozen_string_literal: true
class AddCveIdRequestProjectSetting < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
add_column :project_settings, :cve_id_request_enabled, :boolean, default: true, null: false
end
def down
remove_column :project_settings, :cve_id_request_enabled
end
end
37196d54d03791f7509e411d5c545f22aa70f7c07d1f13d76f70008a06e72b57
\ No newline at end of file
...@@ -16270,6 +16270,7 @@ CREATE TABLE project_settings ( ...@@ -16270,6 +16270,7 @@ CREATE TABLE project_settings (
has_vulnerabilities boolean DEFAULT false NOT NULL, has_vulnerabilities boolean DEFAULT false NOT NULL,
allow_editing_commit_messages boolean DEFAULT false NOT NULL, allow_editing_commit_messages boolean DEFAULT false NOT NULL,
prevent_merge_without_jira_issue boolean DEFAULT false NOT NULL, prevent_merge_without_jira_issue boolean DEFAULT false NOT NULL,
cve_id_request_enabled boolean DEFAULT true NOT NULL,
CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)) CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL))
); );
<script>
import { GlLink, GlIcon, GlTooltipDirective } from '@gitlab/ui';
import { mapState } from 'vuex';
import { createCveIdRequestIssueBody } from '~/helpers/cve_id_request_helper';
import { joinPaths } from '~/lib/utils/url_utility';
import { CVE_ID_REQUEST_SIDEBAR_I18N } from '../../constants';
export default {
i18n: {
...CVE_ID_REQUEST_SIDEBAR_I18N,
},
components: {
GlIcon,
GlLink,
},
directives: {
GlTooltip: GlTooltipDirective,
},
inject: {
iid: {
required: true,
type: String,
},
fullPath: {
required: true,
type: String,
},
},
data() {
return {
showHelp: false,
};
},
computed: {
...mapState({ confidential: (state) => state.noteableData.confidential }),
helpHref() {
return joinPaths(
gon.relative_url_root || '',
'/help/user/application_security/cve_id_request.md',
);
},
showHelpState() {
return Boolean(this.showHelp);
},
tooltipTitle() {
return this.$options.i18n.description;
},
newCveIdRequestUrl() {
const currUrl = new URL(window.location.href);
const newUrl = new URL(currUrl.origin);
newUrl.pathname = '/gitlab-org/cves/-/issues/new';
const params = {
'issue[confidential]': 'true',
// eslint-disable-next-line @gitlab/require-i18n-strings
'issue[title]': `CVE ID Request - ${this.fullPath}`,
'issue[description]': createCveIdRequestIssueBody(this.fullPath, this.iid),
};
Object.keys(params).forEach((k) => newUrl.searchParams.append(k, params[k]));
return newUrl.toString();
},
},
methods: {
toggleHelpState(show) {
this.showHelp = show;
},
},
};
</script>
<template>
<div v-if="confidential" class="block cve-id-request">
<div
v-gl-tooltip.viewport.left
:title="tooltipTitle"
class="sidebar-collapsed-icon"
data-container="body"
data-placement="left"
data-boundary="viewport"
>
<gl-icon name="bug" class="sidebar-item-icon is-active" />
</div>
<div class="hide-collapsed">
{{ $options.i18n.action }}
<div
v-if="!showHelpState"
class="help-button float-right"
data-testid="help-button"
@click="toggleHelpState(true)"
>
<gl-icon name="question-o" />
</div>
<div
v-else
class="close-help-button float-right"
data-testid="close-help-button"
@click="toggleHelpState(false)"
>
<gl-icon name="close" />
</div>
<div class="cve-id-request-content">
<gl-link
:href="newCveIdRequestUrl"
target="_blank"
class="btn btn-default btn-block js-cve-id-request-button"
data-testid="request-button"
data-qa-selector="cve_id_request_button"
>{{ $options.i18n.createRequest }}</gl-link
>
</div>
<div class="hide-collapsed">
<transition name="help-state-toggle">
<div v-if="showHelpState" class="cve-id-request-help-state" data-testid="help-state">
<h4>{{ $options.i18n.whyRequest }}</h4>
<p>
{{ $options.i18n.whyText1 }}
</p>
<p>
{{ $options.i18n.whyText2 }}
</p>
<div>
<gl-link
:href="helpHref"
target="_blank"
class="btn btn-default js-cve-id-request-learn-more-link"
data-qa-selector="cve_id_request_learn_more_link"
>{{ $options.i18n.learnMore }}</gl-link
>
</div>
</div>
</transition>
</div>
</div>
</div>
</template>
...@@ -45,3 +45,17 @@ export const I18N_DROPDOWN = { ...@@ -45,3 +45,17 @@ export const I18N_DROPDOWN = {
noneText: s__('Sidebar|None'), noneText: s__('Sidebar|None'),
selectPlaceholderText: s__('Select health status'), selectPlaceholderText: s__('Select health status'),
}; };
export const CVE_ID_REQUEST_SIDEBAR_I18N = {
action: s__('CVE|Request CVE ID'),
description: s__('CVE|CVE ID Request'),
createRequest: s__('CVE|Create CVE ID Request'),
whyRequest: s__('CVE|Why Request a CVE ID?'),
whyText1: s__(
'CVE|Common Vulnerability Enumeration (CVE) identifiers are used to track distinct vulnerabilities in specific versions of code.',
),
whyText2: s__(
'CVE|As a maintainer, requesting a CVE for a vulnerability in your project will help your users stay secure and informed.',
),
learnMore: __('Learn more'),
};
...@@ -4,6 +4,7 @@ import { parseBoolean } from '~/lib/utils/common_utils'; ...@@ -4,6 +4,7 @@ import { parseBoolean } from '~/lib/utils/common_utils';
import { store } from '~/notes/stores'; import { store } from '~/notes/stores';
import { apolloProvider } from '~/sidebar/graphql'; import { apolloProvider } from '~/sidebar/graphql';
import * as CEMountSidebar from '~/sidebar/mount_sidebar'; import * as CEMountSidebar from '~/sidebar/mount_sidebar';
import CveIdRequest from './components/cve_id_request/cve_id_request_sidebar.vue';
import IterationSelect from './components/iteration_select.vue'; import IterationSelect from './components/iteration_select.vue';
import SidebarItemEpicsSelect from './components/sidebar_item_epics_select.vue'; import SidebarItemEpicsSelect from './components/sidebar_item_epics_select.vue';
import SidebarStatus from './components/status/sidebar_status.vue'; import SidebarStatus from './components/status/sidebar_status.vue';
...@@ -53,6 +54,26 @@ const mountStatusComponent = (mediator) => { ...@@ -53,6 +54,26 @@ const mountStatusComponent = (mediator) => {
}); });
}; };
function mountCveIdRequestComponent() {
const el = document.getElementById('js-sidebar-cve-id-request-entry-point');
if (!el) {
return false;
}
const { iid, fullPath } = CEMountSidebar.getSidebarOptions();
return new Vue({
store,
el,
provide: {
iid: String(iid),
fullPath,
},
render: (createElement) => createElement(CveIdRequest),
});
}
const mountEpicsSelect = () => { const mountEpicsSelect = () => {
const el = document.querySelector('#js-vue-sidebar-item-epics-select'); const el = document.querySelector('#js-vue-sidebar-item-epics-select');
...@@ -111,4 +132,8 @@ export default function mountSidebar(mediator) { ...@@ -111,4 +132,8 @@ export default function mountSidebar(mediator) {
mountStatusComponent(mediator); mountStatusComponent(mediator);
mountEpicsSelect(); mountEpicsSelect();
mountIterationSelect(); mountIterationSelect();
if (gon.features.cveIdRequestButton) {
mountCveIdRequestComponent();
}
} }
...@@ -87,3 +87,42 @@ ...@@ -87,3 +87,42 @@
outline: none; outline: none;
} }
} }
.cve-id-request {
padding-bottom: 0;
border-bottom: 0;
.help-button,
.close-help-button {
cursor: pointer;
}
.help-state-toggle-enter-active {
transition: all 0.8s ease;
}
.help-state-toggle-leave-active {
transition: all 0.5s ease;
}
.help-state-toggle-enter,
.help-state-toggle-leave-active {
opacity: 0;
}
.cve-id-request-content {
margin-top: 16px;
}
.cve-id-request-help-state {
background: $white;
margin: 16px -20px -20px;
padding: 16px 20px;
border-top: 1px solid $border-gray-light;
border-bottom: 1px solid $border-gray-light;
a:hover {
color: $btn-white-active;
}
}
}
...@@ -10,10 +10,15 @@ module EE ...@@ -10,10 +10,15 @@ module EE
include DescriptionDiffActions include DescriptionDiffActions
before_action :whitelist_query_limiting_ee, only: [:update] before_action :whitelist_query_limiting_ee, only: [:update]
before_action only: [:new, :create] do before_action only: [:new, :create] do
populate_vulnerability_id populate_vulnerability_id
end end
before_action only: :show do
push_frontend_feature_flag(:cve_id_request_button, project)
end
before_action :redirect_if_test_case, only: [:show] before_action :redirect_if_test_case, only: [:show]
feature_category :issue_tracking, [:delete_description_version, :description_diff] feature_category :issue_tracking, [:delete_description_version, :description_diff]
......
...@@ -10,6 +10,10 @@ module EE ...@@ -10,6 +10,10 @@ module EE
before_action :log_archive_audit_event, only: [:archive] before_action :log_archive_audit_event, only: [:archive]
before_action :log_unarchive_audit_event, only: [:unarchive] before_action :log_unarchive_audit_event, only: [:unarchive]
before_action only: :show do
push_frontend_feature_flag(:cve_id_request_button, project)
end
feature_category :projects, [:restore] feature_category :projects, [:restore]
end end
...@@ -75,7 +79,13 @@ module EE ...@@ -75,7 +79,13 @@ module EE
override :project_setting_attributes override :project_setting_attributes
def project_setting_attributes def project_setting_attributes
super + [:prevent_merge_without_jira_issue] proj_setting_attrs = super + [:prevent_merge_without_jira_issue]
if ::Feature.enabled?(:cve_id_request_button, project)
proj_setting_attrs << :cve_id_request_enabled
end
proj_setting_attrs
end end
def project_params_ee def project_params_ee
......
...@@ -57,16 +57,29 @@ module EE ...@@ -57,16 +57,29 @@ module EE
override :project_permissions_settings override :project_permissions_settings
def project_permissions_settings(project) def project_permissions_settings(project)
super.merge( settings = super.merge(
requirementsAccessLevel: project.requirements_access_level requirementsAccessLevel: project.requirements_access_level
) )
if ::Feature.enabled?(:cve_id_request_button, project)
settings[:cveIdRequestEnabled] = project.public? && project.project_setting.cve_id_request_enabled?
end
settings
end end
override :project_permissions_panel_data override :project_permissions_panel_data
def project_permissions_panel_data(project) def project_permissions_panel_data(project)
super.merge( panel_data = super.merge(
requirementsAvailable: project.feature_available?(:requirements) requirementsAvailable: project.feature_available?(:requirements)
) )
if ::Feature.enabled?(:cve_id_request_button, project)
panel_data[:requestCveAvailable] = ::Gitlab.com?
panel_data[:cveIdRequestHelpPath] = help_page_path('user/application_security/cve_id_request')
end
panel_data
end end
override :show_security_and_compliance_toggle? override :show_security_and_compliance_toggle?
......
...@@ -18,6 +18,13 @@ module EE ...@@ -18,6 +18,13 @@ module EE
issuable.project&.group&.feature_available?(:epics) issuable.project&.group&.feature_available?(:epics)
end end
end end
expose :request_cve_enabled_for_user, if: ->(issue) { ::Feature.enabled?(:cve_id_request_button, issue.project) } do |issue|
::Gitlab.com? \
&& can?(current_user, :admin_project, issue.project) \
&& issue.project.public? \
&& issue.project.project_setting.cve_id_request_enabled?
end
end end
end end
end end
- if issuable_sidebar[:request_cve_enabled_for_user]
#js-sidebar-cve-id-request-entry-point
---
name: cve_id_request_button
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/41203
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/299569
milestone: '13.10'
type: development
group: group::vulnerability research
default_enabled: false
...@@ -622,6 +622,35 @@ RSpec.describe ProjectsController do ...@@ -622,6 +622,35 @@ RSpec.describe ProjectsController do
end end
end end
end end
context 'cve_id_request_button feature flag' do
where(feature_flag_enabled: [true, false])
with_them do
before do
stub_feature_flags(cve_id_request_button: feature_flag_enabled)
end
it 'handles setting cve_id_request_enabled' do
project.project_setting.cve_id_request_enabled = false
project.project_setting.save!
params = {
project_setting_attributes: {
cve_id_request_enabled: true
}
}
put :update,
params: {
namespace_id: project.namespace,
id: project,
project: params
}
project.reload
expect(project.project_setting.cve_id_request_enabled).to eq(feature_flag_enabled)
end
end
end
end end
describe '#download_export' do describe '#download_export' do
......
...@@ -46,5 +46,15 @@ FactoryBot.modify do ...@@ -46,5 +46,15 @@ FactoryBot.modify do
trait :with_sox_compliance_framework do trait :with_sox_compliance_framework do
association :compliance_framework_setting, :sox, factory: :compliance_framework_project_setting association :compliance_framework_setting, :sox, factory: :compliance_framework_project_setting
end end
trait :with_cve_request do
transient do
cve_request_enabled { true }
end
after(:create) do |project, evaluator|
project.project_setting.cve_id_request_enabled = evaluator.cve_request_enabled
project.project_setting.save!
end
end
end end
end end
...@@ -72,4 +72,108 @@ RSpec.describe 'Project settings > Issues', :js do ...@@ -72,4 +72,108 @@ RSpec.describe 'Project settings > Issues', :js do
expect(page).to have_selector('#project_issues_template') expect(page).to have_selector('#project_issues_template')
end end
end end
context 'when viewing CVE request settings with different :cve_id_request_button feature flag values' do
using RSpec::Parameterized::TableSyntax
where(:feature_flag_enabled, :should_show_toggle) do
true | true
false | false
end
with_them do
before do
stub_feature_flags(cve_id_request_button: feature_flag_enabled)
# setup the project so that it *should* be visible IF the feature flag
# were enabled
allow(::Gitlab).to receive(:com?).and_return(true)
vis_val = Gitlab::VisibilityLevel.const_get(:PUBLIC, false)
project.visibility_level = vis_val
project.save!
project_setting = project.project_setting
project_setting.cve_id_request_enabled = true
project_setting.save!
visit edit_project_path(project)
end
it 'CVE ID Request toggle should only be visible if the feature is enabled' do
if should_show_toggle
expect(page).to have_selector('[data-testid="cve_id_request_toggle"')
else
expect(page).not_to have_selector('[data-testid="cve_id_request_toggle"')
end
end
end
end
context 'when viewing CVE request settings on GitLab.com' do
using RSpec::Parameterized::TableSyntax
where(:project_vis, :cve_enabled, :toggle_checked, :toggle_disabled) do
:public | true | true | false
:public | false | false | false
:internal | true | false | true
:internal | false | false | true
:private | true | false | true
:private | false | false | true
end
with_them do
let(:project) do
create(:project, project_vis, :with_cve_request, cve_request_enabled: cve_enabled)
end
before do
allow(::Gitlab).to receive(:com?).and_return(true)
visit edit_project_path(project)
end
it "CVE ID Request toggle should be correctly visible" do
toggle_btn = find('[data-testid="cve_id_request_toggle"] button')
if toggle_disabled
expect(toggle_btn).to match_css('.is-disabled', wait: 0)
else
expect(toggle_btn).not_to match_css('.is-disabled', wait: 0)
end
if toggle_checked
expect(toggle_btn).to match_css('.is-checked', wait: 0)
else
expect(toggle_btn).not_to match_css('.is-checked', wait: 0)
end
end
end
end
context 'when viewing CVE request settings not on GitLab.com' do
using RSpec::Parameterized::TableSyntax
where(:project_vis, :cve_enabled) do
:public | true
:internal | true
:private | true
end
with_them do
let(:project) do
create(:project, project_vis, :with_cve_request, cve_request_enabled: cve_enabled)
end
before do
allow(::Gitlab).to receive(:com?).and_return(false)
visit edit_project_path(project)
end
it "CVE ID Request toggle should never be visible" do
expect(page).not_to have_selector('[data-testid="cve_id_request_toggle"]')
end
end
end
end end
import { getByTestId as getByTestIdHelper, within } from '@testing-library/dom';
import { createWrapper, shallowMount } from '@vue/test-utils';
import CveIdRequest from 'ee/sidebar/components/cve_id_request/cve_id_request_sidebar.vue';
import { store } from '~/notes/stores';
describe('CveIdRequest', () => {
let wrapper;
const provide = {
iid: 'test',
fullPath: 'some/path',
issueTitle: 'Issue Title',
};
const createComponent = () => {
wrapper = shallowMount(CveIdRequest, {
provide,
store,
});
};
const getByTestId = (id, options) =>
createWrapper(getByTestIdHelper(wrapper.element, id, options));
const queryByTestId = (id, options) => within(wrapper.element).queryByTestId(id, options);
beforeEach(() => {
store.state.noteableData.confidential = true;
createComponent();
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
it('Renders the main "Request CVE ID" button', () => {
expect(getByTestId('request-button').exists()).toBe(true);
});
it('Renders the "help-button" by default', () => {
expect(getByTestId('help-button').exists()).toBe(true);
});
describe('Help Pane', () => {
const findHelpButton = () => getByTestId('help-button');
const findCloseHelpButton = () => getByTestId('close-help-button');
const queryHelpPane = () => queryByTestId('help-state');
beforeEach(() => {
createComponent();
});
it('should not show the "Help" pane by default', () => {
expect(wrapper.vm.showHelpState).toBe(false);
expect(queryHelpPane()).toBe(null);
});
it('should show the "Help" pane when help button is clicked', async () => {
findHelpButton().trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.vm.showHelpState).toBe(true);
// let animations run
jest.advanceTimersByTime(500);
expect(queryHelpPane()).not.toBe(null);
});
it('should not show the "Help" pane when help button is clicked and then closed', async () => {
findHelpButton().trigger('click');
await wrapper.vm.$nextTick();
findCloseHelpButton().trigger('click');
await wrapper.vm.$nextTick();
expect(wrapper.vm.showHelpState).toBe(false);
expect(queryHelpPane()).toBe(null);
});
});
});
...@@ -561,14 +561,48 @@ RSpec.describe ProjectsHelper do ...@@ -561,14 +561,48 @@ RSpec.describe ProjectsHelper do
end end
describe '#project_permissions_settings' do describe '#project_permissions_settings' do
using RSpec::Parameterized::TableSyntax
let(:expected_settings) { { requirementsAccessLevel: 20, securityAndComplianceAccessLevel: 10 } } let(:expected_settings) { { requirementsAccessLevel: 20, securityAndComplianceAccessLevel: 10 } }
subject { helper.project_permissions_settings(project) } subject { helper.project_permissions_settings(project) }
it { is_expected.to include(expected_settings) } it { is_expected.to include(expected_settings) }
context 'cveIdRequestEnabled' do
context "with cve_id_request_button feature flag" do
where(feature_flag_enabled: [true, false])
with_them do
before do
stub_feature_flags(cve_id_request_button: feature_flag_enabled)
end
it 'includes cveIdRequestEnabled' do
expect(subject.key?(:cveIdRequestEnabled)).to eq(feature_flag_enabled)
end
end
end
where(:project_attrs, :cve_enabled, :expected) do
[:public] | true | true
[:public] | false | false
[:internal] | true | false
[:private] | true | false
end
with_them do
let(:project) { create(:project, :with_cve_request, *project_attrs, cve_request_enabled: cve_enabled) }
subject { helper.project_permissions_settings(project) }
it 'has the correct cveIdRequestEnabled value' do
expect(subject[:cveIdRequestEnabled]).to eq(expected)
end
end
end
end end
describe '#project_permissions_panel_data' do describe '#project_permissions_panel_data' do
using RSpec::Parameterized::TableSyntax
let(:user) { instance_double(User, admin?: false) } let(:user) { instance_double(User, admin?: false) }
let(:expected_data) { { requirementsAvailable: false, securityAndComplianceAvailable: true } } let(:expected_data) { { requirementsAvailable: false, securityAndComplianceAvailable: true } }
...@@ -580,5 +614,31 @@ RSpec.describe ProjectsHelper do ...@@ -580,5 +614,31 @@ RSpec.describe ProjectsHelper do
end end
it { is_expected.to include(expected_data) } it { is_expected.to include(expected_data) }
context "if in Gitlab.com" do
where(is_gitlab_com: [true, false])
with_them do
before do
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
end
it 'sets requestCveAvailable to the correct value' do
expect(subject[:requestCveAvailable]).to eq(is_gitlab_com)
end
end
end
context "with cve_id_request_button feature flag" do
where(feature_flag_enabled: [true, false])
with_them do
before do
stub_feature_flags(cve_id_request_button: feature_flag_enabled)
end
it 'includes requestCveAvailable' do
expect(subject.key?(:requestCveAvailable)).to eq(feature_flag_enabled)
end
end
end
end end
end end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe EE::IssueSidebarBasicEntity do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let(:project) { create(:project, :repository) }
let(:issue) { create(:issue, project: project, assignees: [user]) }
let(:subject) { IssueSerializer.new(current_user: user, project: project) }
context "When serializing" do
context "with the cve_id_request_button feature flag enabled" do
using RSpec::Parameterized::TableSyntax
where(:is_gitlab_com, :is_public, :is_admin, :expected_value) do
true | true | true | true
true | false | true | false
true | false | false | false
false | false | true | false
false | false | false | false
end
with_them do
before do
allow(issue.project).to receive(:public?).and_return(is_public)
issue.project.add_maintainer(user) if is_admin
allow(Gitlab).to receive(:com?).and_return(is_gitlab_com)
stub_feature_flags(cve_id_request_button: true)
end
it 'uses the value from request_cve_enabled_for_user when the feature flag is on' do
data = subject.represent(issue, serializer: 'sidebar')
expect(data[:request_cve_enabled_for_user]).to eq(expected_value)
end
end
end
context "with the cve_id_request_button feature flag disabled" do
before do
stub_feature_flags(cve_id_request_button: false)
end
it 'does not use the value from request_cve_enabled_for_user when the feature flag is off' do
data = subject.represent(issue, serializer: 'sidebar')
expect(data).not_to include(:request_cve_enabled_for_user)
end
end
end
end
...@@ -5312,6 +5312,27 @@ msgstr "" ...@@ -5312,6 +5312,27 @@ msgstr ""
msgid "CPU" msgid "CPU"
msgstr "" msgstr ""
msgid "CVE|As a maintainer, requesting a CVE for a vulnerability in your project will help your users stay secure and informed."
msgstr ""
msgid "CVE|CVE ID Request"
msgstr ""
msgid "CVE|Common Vulnerability Enumeration (CVE) identifiers are used to track distinct vulnerabilities in specific versions of code."
msgstr ""
msgid "CVE|Create CVE ID Request"
msgstr ""
msgid "CVE|Enable CVE ID requests in the issue sidebar"
msgstr ""
msgid "CVE|Request CVE ID"
msgstr ""
msgid "CVE|Why Request a CVE ID?"
msgstr ""
msgid "Callback URL" msgid "Callback URL"
msgstr "" msgstr ""
......
...@@ -29,6 +29,7 @@ const defaultProps = { ...@@ -29,6 +29,7 @@ const defaultProps = {
showDefaultAwardEmojis: true, showDefaultAwardEmojis: true,
allowEditingCommitMessages: false, allowEditingCommitMessages: false,
}, },
isGitlabCom: true,
canDisableEmails: true, canDisableEmails: true,
canChangeVisibilityLevel: true, canChangeVisibilityLevel: true,
allowedVisibilityOptions: [0, 10, 20], allowedVisibilityOptions: [0, 10, 20],
......
...@@ -141,6 +141,7 @@ project_setting: ...@@ -141,6 +141,7 @@ project_setting:
- show_default_award_emojis - show_default_award_emojis
- squash_option - squash_option
- updated_at - updated_at
- cve_id_request_enabled
build_service_desk_setting: # service_desk_setting build_service_desk_setting: # service_desk_setting
unexposed_attributes: unexposed_attributes:
......
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