Add Security & Compliance menu

In this commit we're refactoring the Security & Compliance
menu in the group sidebar.

We're moving away from partial defined logic to object
contained one. Now menus are defined in objects that
will contain all the information instead of in partials.
parent f98a5d04
= render_if_exists "layouts/nav/ee/security_link" # EE-specific
= render_if_exists "layouts/nav/ee/push_rules_link" # EE-specific
- if group_sidebar_link?(:runners)
......
......@@ -11,5 +11,12 @@ module EE
show_discover_project_security: show_discover_project_security?(project)
})
end
override :group_sidebar_context_data
def group_sidebar_context_data(group, user)
super.merge(
show_discover_group_security: show_discover_group_security?(group)
)
end
end
end
# frozen_string_literal: true
module Groups::SecurityFeaturesHelper
def group_level_security_dashboard_available?(group)
group.licensed_feature_available?(:security_dashboard)
end
def group_level_compliance_dashboard_available?(group)
group.licensed_feature_available?(:group_level_compliance_dashboard) &&
can?(current_user, :read_group_compliance_dashboard, group)
......@@ -20,23 +16,6 @@ module Groups::SecurityFeaturesHelper
group.enforced_group_managed_accounts?
end
def primary_group_level_security_feature_path(group)
if group_level_security_dashboard_available?(group)
group_security_dashboard_path(group)
elsif group_level_compliance_dashboard_available?(group)
group_security_compliance_dashboard_path(group)
elsif group_level_credentials_inventory_available?(group)
group_security_credentials_path(group)
elsif group_level_audit_events_available?(group)
group_audit_events_path(group)
end
end
def group_level_audit_events_available?(group)
group.licensed_feature_available?(:audit_events) &&
can?(current_user, :read_group_audit_events, group)
end
def group_level_security_dashboard_data(group)
{
projects_endpoint: expose_url(api_v4_groups_projects_path(id: group.id)),
......
- main_path = primary_group_level_security_feature_path(@group)
- if main_path.present?
= nav_link(path: %w[dashboard#show vulnerabilities#index compliance_dashboards#show credentials#index audit_events#index]) do
= link_to main_path, data: { qa_selector: 'security_compliance_link' }, class: 'has-sub-items' do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security & Compliance')
%ul.sidebar-sub-level-items{ data: { qa_selector: 'group_secure_submenu' } }
= nav_link(path: %w[dashboard#show vulnerabilities#index compliance_dashboards#show credentials#index audit_events#index], html_options: { class: "fly-out-top-item" } ) do
%span.fly-out-top-item-container
%strong.fly-out-top-item-name
= _('Security & Compliance')
%li.divider.fly-out-top-item
- if group_level_security_dashboard_available?(@group)
= nav_link(path: 'dashboard#show') do
= link_to group_security_dashboard_path(@group), title: _('Security Dashboard'), data: { qa_selector: 'security_dashboard_link' } do
%span= _('Security Dashboard')
- if group_level_security_dashboard_available?(@group)
= nav_link(path: 'vulnerabilities#index') do
= link_to group_security_vulnerabilities_path(@group), title: _('Vulnerability Report'), data: { qa_selector: 'vulnerability_report_link' } do
%span= _('Vulnerability Report')
- if group_level_compliance_dashboard_available?(@group)
= nav_link(path: 'compliance_dashboards#show') do
= link_to group_security_compliance_dashboard_path(@group), title: _('Compliance') do
%span= _('Compliance')
- if group_level_credentials_inventory_available?(@group)
= nav_link(path: 'credentials#index') do
= link_to group_security_credentials_path(@group), title: _('Credentials') do
%span= _('Credentials')
- if group_level_audit_events_available?(@group)
= nav_link(path: 'audit_events#index') do
= link_to group_audit_events_path(@group), title: _('Audit Events'), data: { qa_selector: 'audit_events_settings_link' } do
%span= _('Audit Events')
- elsif show_discover_group_security?(@group)
= nav_link(path: group_security_discover_path(@group)) do
= link_to group_security_discover_path(@group) do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security')
......@@ -12,6 +12,7 @@ module EE
insert_menu_before(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::TrialExperimentMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::GroupInformationMenu, ::Sidebars::Groups::Menus::EpicsMenu.new(context))
insert_menu_after(::Sidebars::Groups::Menus::MergeRequestsMenu, ::Sidebars::Groups::Menus::SecurityComplianceMenu.new(context))
end
end
end
......
# frozen_string_literal: true
module Sidebars
module Groups
module Menus
class SecurityComplianceMenu < ::Sidebars::Menu
override :configure_menu_items
def configure_menu_items
add_item(security_dashboard_menu_item)
add_item(vulnerability_report_menu_item)
add_item(compliance_menu_item)
add_item(credentials_menu_item)
add_item(audit_events_menu_item)
true
end
override :link
def link
return renderable_items.first.link if renderable_items.any?
group_security_discover_path(context.group)
end
override :title
def title
renderable_items.any? ? _('Security & Compliance') : _('Security')
end
override :sprite_icon
def sprite_icon
'shield'
end
override :render?
def render?
super || context.show_discover_group_security
end
override :active_routes
def active_routes
return {} if renderable_items.empty?
{ page: link }
end
private
def security_dashboard_menu_item
unless context.group.licensed_feature_available?(:security_dashboard)
return ::Sidebars::NilMenuItem.new(item_id: :security_dashboard)
end
::Sidebars::MenuItem.new(
title: _('Security Dashboard'),
link: group_security_dashboard_path(context.group),
active_routes: { path: 'dashboard#show' },
item_id: :security_dashboard
)
end
def vulnerability_report_menu_item
unless context.group.licensed_feature_available?(:security_dashboard)
return ::Sidebars::NilMenuItem.new(item_id: :vulnerability_report)
end
::Sidebars::MenuItem.new(
title: _('Vulnerability Report'),
link: group_security_vulnerabilities_path(context.group),
active_routes: { path: 'vulnerabilities#index' },
item_id: :vulnerability_report
)
end
def compliance_menu_item
unless group_level_compliance_dashboard_available?
return ::Sidebars::NilMenuItem.new(item_id: :compliance)
end
::Sidebars::MenuItem.new(
title: _('Compliance'),
link: group_security_compliance_dashboard_path(context.group),
active_routes: { path: 'compliance_dashboards#show' },
item_id: :compliance
)
end
def group_level_compliance_dashboard_available?
context.group.licensed_feature_available?(:group_level_compliance_dashboard) &&
can?(context.current_user, :read_group_compliance_dashboard, context.group)
end
def credentials_menu_item
unless group_level_credentials_inventory_available?
return ::Sidebars::NilMenuItem.new(item_id: :credentials)
end
::Sidebars::MenuItem.new(
title: _('Credentials'),
link: group_security_credentials_path(context.group),
active_routes: { path: 'credentials#index' },
item_id: :credentials
)
end
def group_level_credentials_inventory_available?
context.group.licensed_feature_available?(:credentials_inventory) &&
can?(context.current_user, :read_group_credentials_inventory, context.group) &&
context.group.enforced_group_managed_accounts?
end
def audit_events_menu_item
unless group_level_audit_events_available?
return ::Sidebars::NilMenuItem.new(item_id: :audit_events)
end
::Sidebars::MenuItem.new(
title: _('Audit Events'),
link: group_audit_events_path(context.group),
active_routes: { path: 'audit_events#index' },
item_id: :audit_events
)
end
def group_level_audit_events_available?
context.group.licensed_feature_available?(:audit_events) &&
can?(context.current_user, :read_group_audit_events, context.group)
end
end
end
end
end
......@@ -36,7 +36,7 @@ RSpec.describe 'Groups > Audit Events', :js do
end
it 'has Audit Events button in head nav bar' do
visit group_security_dashboard_path(group)
visit group_audit_events_path(group)
expect(page).to have_link('Audit Events')
end
......
......@@ -13,23 +13,6 @@ RSpec.describe Groups::SecurityFeaturesHelper do
allow(helper).to receive(:can?).and_return(false)
end
describe '#group_level_security_dashboard_available?' do
where(:security_dashboard_feature_enabled, :result) do
true | true
false | false
end
with_them do
before do
stub_licensed_features(security_dashboard: security_dashboard_feature_enabled)
end
it 'returns the expected result' do
expect(helper.group_level_security_dashboard_available?(group)).to eq(result)
end
end
end
describe '#group_level_security_dashboard_available?' do
where(:group_level_compliance_dashboard_enabled, :read_group_compliance_dashboard_permission, :result) do
false | false | false
......@@ -75,84 +58,6 @@ RSpec.describe Groups::SecurityFeaturesHelper do
end
end
describe '#group_level_audit_events_available?' do
where(:audit_events_feature_enabled, :read_group_audit_events_permission, :result) do
true | false | false
true | true | true
false | false | false
false | true | false
end
with_them do
before do
stub_licensed_features(audit_events: audit_events_feature_enabled)
allow(helper).to receive(:can?).with(user, :read_group_audit_events, group)
.and_return(read_group_audit_events_permission)
end
it 'returns the expected result' do
expect(helper.group_level_audit_events_available?(group)).to eq(result)
end
end
end
describe '#primary_group_level_security_feature_path' do
subject { helper.primary_group_level_security_feature_path(group) }
context 'group_level_security_dashboard is available' do
before do
allow(helper).to receive(:group_level_security_dashboard_available?).with(group).and_return(true)
end
it 'returns path to security dashboard' do
expect(subject).to eq(group_security_dashboard_path(group))
end
end
context 'group_level_compliance_dashboard is available' do
before do
allow(helper).to receive(:group_level_compliance_dashboard_available?).with(group).and_return(true)
end
it 'returns path to compliance dashboard' do
expect(subject).to eq(group_security_compliance_dashboard_path(group))
end
end
context 'group_level_credentials_inventory is available' do
before do
allow(helper).to receive(:group_level_credentials_inventory_available?).with(group).and_return(true)
end
it 'returns path to credentials inventory dashboard' do
expect(subject).to eq(group_security_credentials_path(group))
end
end
context 'group_level_audit_events is available' do
before do
allow(helper).to receive(:group_level_audit_events_available?).with(group).and_return(true)
end
it 'returns path to audit events' do
expect(subject).to eq(group_audit_events_path(group))
end
end
context 'when no security features are available' do
before do
allow(helper).to receive(:group_level_security_dashboard_available?).with(group).and_return(false)
allow(helper).to receive(:group_level_compliance_dashboard_available?).with(group).and_return(false)
allow(helper).to receive(:group_level_credentials_inventory_available?).with(group).and_return(false)
allow(helper).to receive(:group_level_audit_events_available?).with(group).and_return(false)
end
it 'returns nil' do
expect(subject).to be_nil
end
end
end
describe '#group_level_security_dashboard_data' do
subject { helper.group_level_security_dashboard_data(group) }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Sidebars::Groups::Menus::SecurityComplianceMenu do
let_it_be(:owner) { create(:user) }
let_it_be_with_refind(:group) do
create(:group, :private).tap do |g|
g.add_owner(owner)
end
end
let(:user) { owner }
let(:show_group_discover_security) { false }
let(:context) { Sidebars::Groups::Context.new(current_user: user, container: group, show_discover_group_security: show_group_discover_security) }
let(:menu) { described_class.new(context) }
describe '#link' do
subject { menu.link }
context 'when menu has menu items' do
it 'returns first visible menu item link' do
expect(subject).to eq menu.renderable_items.first.link
end
end
context 'when menu does no have any menu item' do
let(:user) { nil }
it 'returns show group security page' do
expect(subject).to eq "/groups/#{group.full_path}/-/security/discover"
end
end
end
describe '#title' do
subject { menu.title }
specify do
is_expected.to eq 'Security & Compliance'
end
context 'when menu does not have any menu items' do
let(:user) { nil }
specify do
is_expected.to eq 'Security'
end
end
end
describe '#render?' do
subject { menu.render? }
it 'returns true if there are menu items' do
is_expected.to be true
end
context 'when there are no menu items' do
let(:user) { nil }
it 'returns false if there are no menu items' do
is_expected.to be false
end
context 'when show group discover security option is enabled' do
let(:show_group_discover_security) { true }
specify { is_expected.to be true }
end
end
end
describe 'Menu Items' do
subject { described_class.new(context).renderable_items.index { |e| e.item_id == item_id } }
shared_examples 'menu access rights' do
specify { is_expected.not_to be_nil }
describe 'when the user does not have access' do
let(:user) { nil }
specify { is_expected.to be_nil }
end
end
describe 'Security Dashboard' do
let(:item_id) { :security_dashboard }
context 'when security_dashboard feature is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
specify { is_expected.not_to be_nil }
end
context 'when security_dashboard feature is not enabled' do
specify { is_expected.to be_nil }
end
end
describe 'Vulnerability Report' do
let(:item_id) { :vulnerability_report }
context 'when security_dashboard feature is enabled' do
before do
stub_licensed_features(security_dashboard: true)
end
specify { is_expected.not_to be_nil }
end
context 'when security_dashboard feature is not enabled' do
specify { is_expected.to be_nil }
end
end
describe 'Compliance' do
let(:item_id) { :compliance }
context 'when group_level_compliance_dashboard feature is enabled' do
before do
stub_licensed_features(group_level_compliance_dashboard: true)
end
it_behaves_like 'menu access rights'
end
context 'when group_level_compliance_dashboard feature is not enabled' do
specify { is_expected.to be_nil }
end
end
describe 'Credentials' do
let(:item_id) { :credentials }
context 'when credentials_inventory feature is enabled' do
before do
stub_licensed_features(credentials_inventory: true)
end
context 'when group magement is not enforced' do
specify { is_expected.to be_nil }
end
context 'when group magement is enforced' do
before do
allow(group).to receive(:enforced_group_managed_accounts?).and_return(true)
end
it_behaves_like 'menu access rights'
end
end
context 'when credentials_inventory feature is not enabled' do
specify { is_expected.to be_nil }
end
end
describe 'Audit Events' do
let(:item_id) { :audit_events }
context 'when audit_events feature is enabled' do
before do
stub_licensed_features(audit_events: true)
end
it_behaves_like 'menu access rights'
end
context 'when audit_events feature is not enabled' do
before do
stub_licensed_features(audit_events: false)
end
specify { is_expected.to be_nil }
end
end
end
end
......@@ -175,128 +175,7 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
end
describe 'DevOps adoption link' do
let!(:current_user) { create(:user) }
before do
group.add_maintainer(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'DevOps adoption feature is available' do
before do
stub_licensed_features(group_level_devops_adoption: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'DevOps adoption'
end
end
context 'DevOps adoption feature is not available' do
before do
stub_licensed_features(group_level_devops_adoption: false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'DevOps adoption'
end
end
end
describe 'contribution analytics tab' do
let!(:current_user) { create(:user) }
before do
group.add_guest(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'contribution analytics feature is available' do
before do
stub_licensed_features(contribution_analytics: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'Contribution'
end
end
context 'contribution analytics feature is not available' do
before do
stub_licensed_features(contribution_analytics: false)
end
context 'we do not show promotions' do
before do
allow(LicenseHelper).to receive(:show_promotions?).and_return(false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'Contribution'
end
end
end
context 'no license installed' do
before do
allow(License).to receive(:current).and_return(nil)
stub_application_setting(check_namespace_plan: false)
allow(view).to receive(:can?) { |*args| Ability.allowed?(*args) }
end
it 'is visible when there is no valid license but we show promotions' do
stub_licensed_features(contribution_analytics: false)
render
expect(rendered).to have_text 'Contribution'
end
end
it 'is visible' do
stub_licensed_features(contribution_analytics: true)
render
expect(rendered).to have_text 'Contribution'
end
describe 'group issue boards link' do
context 'when multiple issue board is disabled' do
it 'shows link text in singular' do
render
expect(rendered).to have_text 'Board'
end
end
context 'when multiple issue board is enabled' do
before do
stub_licensed_features(multiple_group_issue_boards: true)
end
it 'shows link text in plural' do
render
expect(rendered).to have_text 'Boards'
end
end
end
end
describe 'security dashboard tab' do
describe 'Security & Compliance menu' do
let(:group) { create(:group_with_plan, plan: :ultimate_plan) }
before do
......@@ -433,6 +312,127 @@ RSpec.describe 'layouts/nav/sidebar/_group' do
end
end
describe 'DevOps adoption link' do
let!(:current_user) { create(:user) }
before do
group.add_maintainer(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'DevOps adoption feature is available' do
before do
stub_licensed_features(group_level_devops_adoption: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'DevOps adoption'
end
end
context 'DevOps adoption feature is not available' do
before do
stub_licensed_features(group_level_devops_adoption: false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'DevOps adoption'
end
end
end
describe 'contribution analytics tab' do
let!(:current_user) { create(:user) }
before do
group.add_guest(current_user)
allow(view).to receive(:current_user).and_return(current_user)
end
context 'contribution analytics feature is available' do
before do
stub_licensed_features(contribution_analytics: true)
end
it 'is visible' do
render
expect(rendered).to have_text 'Contribution'
end
end
context 'contribution analytics feature is not available' do
before do
stub_licensed_features(contribution_analytics: false)
end
context 'we do not show promotions' do
before do
allow(LicenseHelper).to receive(:show_promotions?).and_return(false)
end
it 'is not visible' do
render
expect(rendered).not_to have_text 'Contribution'
end
end
end
context 'no license installed' do
before do
allow(License).to receive(:current).and_return(nil)
stub_application_setting(check_namespace_plan: false)
allow(view).to receive(:can?) { |*args| Ability.allowed?(*args) }
end
it 'is visible when there is no valid license but we show promotions' do
stub_licensed_features(contribution_analytics: false)
render
expect(rendered).to have_text 'Contribution'
end
end
it 'is visible' do
stub_licensed_features(contribution_analytics: true)
render
expect(rendered).to have_text 'Contribution'
end
describe 'group issue boards link' do
context 'when multiple issue board is disabled' do
it 'shows link text in singular' do
render
expect(rendered).to have_text 'Board'
end
end
context 'when multiple issue board is enabled' do
before do
stub_licensed_features(multiple_group_issue_boards: true)
end
it 'shows link text in plural' do
render
expect(rendered).to have_text 'Boards'
end
end
end
end
describe 'wiki tab' do
let(:can_read_wiki) { true }
......
......@@ -33,14 +33,6 @@ module QA
element :billing_link
end
view 'ee/app/views/layouts/nav/ee/_security_link.html.haml' do
element :security_compliance_link
element :group_secure_submenu
element :security_dashboard_link
element :vulnerability_report_link
element :audit_events_settings_link
end
view 'ee/app/views/layouts/nav/_group_insights_link.html.haml' do
element :group_insights_link
end
......@@ -53,14 +45,6 @@ module QA
end
end
def go_to_audit_events_settings
hover_element(:security_compliance_link) do
within_submenu(:group_secure_submenu) do
click_element(:audit_events_settings_link)
end
end
end
def go_to_issue_boards
hover_issues do
within_submenu do
......@@ -116,17 +100,25 @@ module QA
end
def click_group_security_link
hover_element(:security_compliance_link) do
within_submenu(:group_secure_submenu) do
click_element(:security_dashboard_link)
hover_security_and_compliance do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Security Dashboard')
end
end
end
def click_group_vulnerability_link
hover_element(:security_compliance_link) do
within_submenu(:group_secure_submenu) do
click_element(:vulnerability_report_link)
hover_security_and_compliance do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Vulnerability Report')
end
end
end
def go_to_audit_events
hover_security_and_compliance do
within_submenu do
click_element(:sidebar_menu_item_link, menu_item: 'Audit Events')
end
end
end
......@@ -152,6 +144,17 @@ module QA
end
end
end
private
def hover_security_and_compliance
within_sidebar do
scroll_to_element(:sidebar_menu_link, menu_item: 'Security & Compliance')
find_element(:sidebar_menu_link, menu_item: 'Security & Compliance').hover
yield
end
end
end
end
end
......
......@@ -11,7 +11,7 @@ module QA
it 'logs audit events for UI operations' do
wait_for_audit_events(expected_events, group)
Page::Group::Menu.perform(&:go_to_audit_events_settings)
Page::Group::Menu.perform(&:go_to_audit_events)
expected_events.each do |expected_event|
# Sometimes the audit logs are not displayed in the UI
# right away so a refresh may be needed.
......
......@@ -5,7 +5,7 @@ module QA
RSpec.describe 'Manage' do
shared_examples 'audit event' do |expected_events|
it 'logs audit events for UI operations' do
Page::Group::Menu.perform(&:go_to_audit_events_settings)
Page::Group::Menu.perform(&:go_to_audit_events)
expected_events.each do |expected_event|
expect(page).to have_text(expected_event)
end
......
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