Commit 2969cfe5 authored by Arturo Herrero's avatar Arturo Herrero

Merge branch 'jnnkl-security-compliance-core' into 'master'

Render Static Security & Compliance page for non-Ultimate users [RUN ALL RSPEC] [RUN AS-IF-FOSS]

See merge request gitlab-org/gitlab!50282
parents d3602916 fe63b388
# frozen_string_literal: true
module Projects
module Security
class ConfigurationController < Projects::ApplicationController
feature_category :static_application_security_testing
def show
return render_404 unless feature_enabled?
render_403 unless can?(current_user, :read_security_configuration, project)
end
private
def feature_enabled?
::Feature.enabled?(:secure_security_and_compliance_configuration_page_on_ce, @project, default_enabled: :yaml)
end
end
end
end
Projects::Security::ConfigurationController.prepend_if_ee('EE::Projects::Security::ConfigurationController')
......@@ -139,6 +139,10 @@ module ProjectsHelper
project_nav_tabs.include? name
end
def any_project_nav_tab?(tabs)
tabs.any? { |tab| project_nav_tab?(tab) }
end
def project_for_deploy_key(deploy_key)
if deploy_key.has_access_to?(@project)
@project
......@@ -374,6 +378,20 @@ module ProjectsHelper
private
def can_read_security_configuration?(project, current_user)
::Feature.enabled?(:secure_security_and_compliance_configuration_page_on_ce, @subject, default_enabled: :yaml) &&
can?(current_user, :read_security_configuration, project)
end
def get_project_security_nav_tabs(project, current_user)
if can_read_security_configuration?(project, current_user)
[:security_and_compliance, :security_configuration]
else
[]
end
end
# rubocop:disable Metrics/CyclomaticComplexity
def get_project_nav_tabs(project, current_user)
nav_tabs = [:home]
......@@ -382,6 +400,8 @@ module ProjectsHelper
nav_tabs << :releases if can?(current_user, :read_release, project)
end
nav_tabs += get_project_security_nav_tabs(project, current_user)
if project.repo_exists? && can?(current_user, :read_merge_request, project)
nav_tabs << :merge_requests
end
......@@ -415,6 +435,7 @@ module ProjectsHelper
nav_tabs
end
# rubocop:enable Metrics/CyclomaticComplexity
def package_nav_tabs(project, current_user)
[].tap do |tabs|
......@@ -695,6 +716,12 @@ module ProjectsHelper
"#{request.path}?#{options.to_param}"
end
def sidebar_security_configuration_paths
%w[
projects/security/configuration#show
]
end
def sidebar_projects_paths
%w[
projects#show
......@@ -759,6 +786,10 @@ module ProjectsHelper
]
end
def sidebar_security_paths
%w[projects/security/configuration#show]
end
def user_can_see_auto_devops_implicitly_enabled_banner?(project, user)
Ability.allowed?(user, :admin_project, project) &&
project.has_auto_devops_implicitly_enabled? &&
......
......@@ -581,6 +581,10 @@ class ProjectPolicy < BasePolicy
enable :read_issue_link
end
rule { can?(:developer_access) }.policy do
enable :read_security_configuration
end
# Design abilities could also be prevented in the issue policy.
rule { design_management_disabled }.policy do
prevent :read_design
......
- top_level_link = project_security_configuration_path(@project)
- top_level_qa_selector = 'security_configuration_link'
- if any_project_nav_tab?([:security_configuration])
= nav_link(path: sidebar_security_paths) do
= link_to top_level_link, data: { qa_selector: top_level_qa_selector } do
.nav-icon-container
= sprite_icon('shield')
%span.nav-item-name
= _('Security & Compliance')
%ul.sidebar-sub-level-items
= nav_link(path: sidebar_security_paths, html_options: { class: "fly-out-top-item" } ) do
= link_to top_level_link do
%strong.fly-out-top-item-name
= _('Security & Compliance')
%li.divider.fly-out-top-item
- if project_nav_tab?(:security_configuration)
= nav_link(path: sidebar_security_configuration_paths) do
= link_to project_security_configuration_path(@project), title: _('Configuration'), data: { qa_selector: 'security_configuration_link'} do
%span= _('Configuration')
- breadcrumb_title _("Security Configuration")
- page_title _("Security Configuration")
#js-security-configuration-static
---
name: secure_security_and_compliance_configuration_page_on_ce
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/50282
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/294076
milestone: '13.9'
type: development
group: group::static analysis
default_enabled: false
......@@ -37,6 +37,10 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
namespace :security do
resource :configuration, only: [:show], controller: :configuration
end
resources :artifacts, only: [:index, :destroy]
resources :packages, only: [:index, :show, :destroy], module: :packages
......
......@@ -4,6 +4,11 @@ import SecurityConfigurationApp from './components/app.vue';
export default function init() {
const el = document.getElementById('js-security-configuration');
if (!el) {
return null;
}
const {
autoDevopsHelpPagePath,
autoDevopsPath,
......
# frozen_string_literal: true
module EE
module Projects
module Security
module ConfigurationController
extend ActiveSupport::Concern
extend ::Gitlab::Utils::Override
prepended do
alias_method :vulnerable, :project
before_action :ensure_security_dashboard_feature_enabled!, except: [:show]
before_action :authorize_read_security_dashboard!, except: [:show]
before_action only: [:show] do
push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false)
push_frontend_feature_flag(:sast_configuration_ui, project, default_enabled: true)
push_frontend_feature_flag(:api_fuzzing_configuration_ui, project, default_enabled: :yaml)
end
before_action only: [:auto_fix] do
check_feature_flag!
authorize_modify_auto_fix_setting!
end
feature_category :static_application_security_testing
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
override :show
def show
return super unless security_dashboard_feature_enabled? && can_read_security_dashboard?
@configuration = ::Projects::Security::ConfigurationPresenter.new(project,
auto_fix_permission: auto_fix_authorized?,
current_user: current_user)
respond_to do |format|
format.html
format.json do
render status: :ok, json: @configuration.to_h
end
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
def auto_fix
service = ::Security::Configuration::SaveAutoFixService.new(project, auto_fix_params[:feature])
return respond_422 unless service.execute(enabled: auto_fix_params[:enabled])
render status: :ok, json: auto_fix_settings
end
private
def auto_fix_authorized?
can?(current_user, :modify_auto_fix_setting, project)
end
def auto_fix_params
@auto_fix_params ||= begin
fix_params = params.permit(:feature, :enabled)
feature = fix_params[:feature]
fix_params[:feature] = feature.blank? ? 'all' : feature.to_s
fix_params
end
end
def check_auto_fix_permissions!
render_403 unless auto_fix_authorized?
end
def check_feature_flag!
render_404 if ::Feature.disabled?(:security_auto_fix, project)
end
def auto_fix_settings
setting = project.security_setting
{
dependency_scanning: setting.auto_fix_dependency_scanning,
container_scanning: setting.auto_fix_container_scanning
}
end
def security_dashboard_feature_enabled?
vulnerable.feature_available?(:security_dashboard)
end
def can_read_security_dashboard?
can?(current_user, :read_project_security_dashboard, vulnerable)
end
def ensure_security_dashboard_feature_enabled!
render_404 unless security_dashboard_feature_enabled?
end
def authorize_read_security_dashboard!
render_403 unless can_read_security_dashboard?
end
end
end
end
end
# frozen_string_literal: true
module Projects
module Security
class ConfigurationController < Projects::ApplicationController
include SecurityDashboardsPermissions
alias_method :vulnerable, :project
before_action only: [:show] do
push_frontend_feature_flag(:security_auto_fix, project, default_enabled: false)
push_frontend_feature_flag(:sast_configuration_ui, project, default_enabled: true)
push_frontend_feature_flag(:api_fuzzing_configuration_ui, project, default_enabled: :yaml)
end
before_action only: [:auto_fix] do
check_feature_flag!
authorize_modify_auto_fix_setting!
end
feature_category :static_application_security_testing
def show
@configuration = ConfigurationPresenter.new(project,
auto_fix_permission: auto_fix_authorized?,
current_user: current_user)
respond_to do |format|
format.html
format.json do
render status: :ok, json: @configuration.to_h
end
end
end
def auto_fix
service = ::Security::Configuration::SaveAutoFixService.new(project, auto_fix_params[:feature])
return respond_422 unless service.execute(enabled: auto_fix_params[:enabled])
render status: :ok, json: auto_fix_settings
end
private
def auto_fix_authorized?
can?(current_user, :modify_auto_fix_setting, project)
end
def auto_fix_params
return @auto_fix_params if @auto_fix_params
@auto_fix_params = params.permit(:feature, :enabled)
feature = @auto_fix_params[:feature]
@auto_fix_params[:feature] = feature.blank? ? 'all' : feature.to_s
@auto_fix_params
end
def check_auto_fix_permissions!
render_403 unless auto_fix_authorized?
end
def check_feature_flag!
render_404 if Feature.disabled?(:security_auto_fix, project)
end
def auto_fix_settings
setting = project.security_setting
{
dependency_scanning: setting.auto_fix_dependency_scanning,
container_scanning: setting.auto_fix_container_scanning
}
end
end
end
end
......@@ -27,8 +27,6 @@ module EE
def get_project_nav_tabs(project, current_user)
nav_tabs = super
nav_tabs += get_project_security_nav_tabs(project, current_user)
if can?(current_user, :read_code_review_analytics, project)
nav_tabs << :code_review
end
......@@ -160,9 +158,9 @@ module EE
@project.feature_available?(:merge_trains)
end
override :sidebar_security_paths
def sidebar_security_paths
%w[
projects/security/configuration#show
super + %w[
projects/security/sast_configuration#show
projects/security/api_fuzzing_configuration#show
projects/security/vulnerabilities#show
......@@ -199,9 +197,9 @@ module EE
]
end
override :sidebar_security_configuration_paths
def sidebar_security_configuration_paths
%w[
projects/security/configuration#show
super + %w[
projects/security/sast_configuration#show
projects/security/api_fuzzing_configuration#show
projects/security/dast_profiles#show
......@@ -324,14 +322,20 @@ module EE
private
override :can_read_security_configuration?
def can_read_security_configuration?(project, current_user)
super || (project.feature_available?(:security_dashboard) &&
can?(current_user, :read_project_security_dashboard, project))
end
override :get_project_security_nav_tabs
def get_project_security_nav_tabs(project, current_user)
return [] unless can?(current_user, :access_security_and_compliance, project)
nav_tabs = [:security_and_compliance]
nav_tabs = super.union([:security_and_compliance])
if can?(current_user, :read_project_security_dashboard, project)
nav_tabs << :security
nav_tabs << :security_configuration
end
if can?(current_user, :read_on_demand_scans, @project)
......
- on_demand_scans_path = Feature.enabled?(:dast_saved_scans, @project, default_enabled: :yaml) ? new_project_on_demand_scan_path(@project) : project_on_demand_scans_path(@project)
- if any_project_nav_tab?([:security, :dependencies, :licenses, :audit_events])
- if any_project_nav_tab?([:security, :security_configuration, :dependencies, :licenses, :audit_events])
= nav_link(path: sidebar_security_paths) do
= link_to top_level_link(@project), data: { qa_selector: top_level_qa_selector(@project) } do
.nav-icon-container
......
- breadcrumb_title _("Security Configuration")
- page_title _("Security Configuration")
#js-security-configuration{ data: { **@configuration.to_html_data_attribute,
- if @configuration.nil?
= render_ce 'projects/security/configuration/show'
- else
#js-security-configuration{ data: { **@configuration.to_html_data_attribute,
auto_fix_help_path: '/',
toggle_autofix_setting_endpoint: 'configuration/auto_fix',
container_scanning_help_path: help_page_path('user/application_security/container_scanning/index'),
......
......@@ -63,7 +63,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :dashboard, only: [:index], controller: :dashboard
resources :vulnerability_report, only: [:index], controller: :vulnerability_report
resource :configuration, only: [:show], controller: :configuration do
resource :configuration, only: [], controller: :configuration do
post :auto_fix, on: :collection
resource :corpus_management, only: [:show], controller: :corpus_management
resource :sast, only: [:show, :create], controller: :sast_configuration
......
......@@ -7,19 +7,60 @@ RSpec.describe Projects::Security::ConfigurationController do
let(:project) { create(:project, :repository, namespace: group) }
describe 'GET #show' do
subject(:request) { get :show, params: { namespace_id: project.namespace, project_id: project } }
using RSpec::Parameterized::TableSyntax
it_behaves_like SecurityDashboardsPermissions do
let(:vulnerable) { project }
let(:security_dashboard_action) { request }
end
subject(:request) { get :show, params: { namespace_id: project.namespace, project_id: project } }
context 'with user' do
let(:user) { create(:user) }
render_views
where(:user_role, :security_dashboard_enabled, :ce_flag_enabled, :status, :selector) do
:guest | false | false | :not_found | nil
:guest | false | true | :forbidden | nil
:guest | true | false | :not_found | nil
:guest | true | true | :forbidden | nil
:developer | false | false | :not_found | nil
:developer | false | true | :ok | '#js-security-configuration-static'
:developer | true | false | :ok | '#js-security-configuration'
:developer | true | true | :ok | '#js-security-configuration'
end
with_them do
before do
stub_licensed_features(security_dashboard: security_dashboard_enabled)
stub_feature_flags(secure_security_and_compliance_configuration_page_on_ce: ce_flag_enabled)
group.send("add_#{user_role}", user)
sign_in(user)
end
it 'responds with the correct status' do
request
expect(response).to have_gitlab_http_status(status)
unless selector.nil?
expect(response).to render_template(:show)
expect(response.body).to have_css(selector)
end
end
end
context 'with developer and security dashboard feature enabled' do
let(:flag) { :secure_security_and_compliance_configuration_page_on_ce }
# The tests in this context should be unaffected by this feature flag,
# and should behave identically whether this is enabled or disabled.
where(:flag_enabled) do
[
[true],
[false]
]
end
with_them do
before do
stub_feature_flags(flag => flag_enabled)
stub_licensed_features(security_dashboard: true)
group.add_developer(user)
......@@ -75,6 +116,7 @@ RSpec.describe Projects::Security::ConfigurationController do
end
end
end
end
describe 'POST #auto_fix' do
subject(:request) { post :auto_fix, params: params }
......
......@@ -302,7 +302,8 @@ RSpec.describe ProjectsHelper do
describe 'Security & Compliance tabs' do
where(:ability, :nav_tabs) do
:read_project_security_dashboard | [:security, :security_configuration]
:read_project_security_dashboard | [:security]
:read_security_configuration | [:security_configuration]
:read_on_demand_scans | [:on_demand_scans]
:read_dependencies | [:dependencies]
:read_licenses | [:licenses]
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Security::ConfigurationController, 'routing' do
let(:base_params) { { namespace_id: 'gitlab', project_id: 'gitlabhq' } }
before do
allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq', any_args).and_return(true)
end
it 'to #show' do
expect(get('/gitlab/gitlabhq/-/security/configuration')).to route_to('projects/security/configuration#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
it 'to #auto_fix' do
expect(post('/gitlab/gitlabhq/-/security/configuration/auto_fix')).to route_to('projects/security/configuration#auto_fix', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
......@@ -93,6 +93,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
allow(view).to receive(:can?).with(nil, :read_dependencies, project).and_return(can_read_dependencies)
allow(view).to receive(:can?).with(nil, :read_project_security_dashboard, project).and_return(can_read_dashboard)
allow(view).to receive(:can?).with(nil, :read_project_audit_events, project).and_return(can_read_project_audit_events)
allow(view).to receive(:can?).with(nil, :read_security_configuration, project).and_return(can_read_security_configuration)
allow(view).to receive(:can?).with(nil, :access_security_and_compliance, project).and_return(can_access_security_and_compliance)
render
......@@ -105,24 +106,25 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
let(:can_read_dashboard) { true }
let(:can_read_dependencies) { true }
let(:can_read_project_audit_events) { true }
let(:can_read_security_configuration) { true }
it 'top level navigation link is visible' do
it 'top level navigation link is not visible' do
expect(rendered).not_to have_link('Security & Compliance', href: project_security_dashboard_index_path(project))
end
it 'security dashboard link is visible' do
it 'security dashboard link is not visible' do
expect(rendered).not_to have_link('Security Dashboard', href: project_security_dashboard_index_path(project))
end
it 'security configuration link is visible' do
it 'security configuration link is not visible' do
expect(rendered).not_to have_link('Configuration', href: project_security_configuration_path(project))
end
it 'dependency list link is visible' do
it 'dependency list link is not visible' do
expect(rendered).not_to have_link('Dependency List', href: project_dependencies_path(project))
end
it 'audit events link is visible' do
it 'audit events link is not visible' do
expect(rendered).not_to have_link('Audit Events', href: project_audit_events_path(project))
end
end
......@@ -135,6 +137,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
let(:can_read_dashboard) { true }
let(:can_read_dependencies) { true }
let(:can_read_project_audit_events) { true }
let(:can_read_security_configuration) { true }
it 'top level navigation link is visible' do
expect(rendered).to have_link('Security & Compliance', href: project_security_dashboard_index_path(project))
......@@ -161,6 +164,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
let(:can_read_dashboard) { true }
let(:can_read_dependencies) { false }
let(:can_read_project_audit_events) { false }
let(:can_read_security_configuration) { true }
it 'top level navigation link is visible' do
expect(rendered).to have_link('Security & Compliance', href: project_security_dashboard_index_path(project))
......@@ -187,6 +191,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
let(:can_read_dashboard) { false }
let(:can_read_dependencies) { true }
let(:can_read_project_audit_events) { false }
let(:can_read_security_configuration) { false }
it 'top level navigation link is visible' do
expect(rendered).to have_link('Security & Compliance', href: project_dependencies_path(project))
......@@ -213,6 +218,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
let(:can_read_dashboard) { false }
let(:can_read_dependencies) { false }
let(:can_read_project_audit_events) { true }
let(:can_read_security_configuration) { false }
it 'top level navigation link is visible' do
expect(rendered).to have_link('Security & Compliance', href: project_audit_events_path(project))
......@@ -239,6 +245,7 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
let(:can_read_dependencies) { false }
let(:can_read_dashboard) { false }
let(:can_read_project_audit_events) { false }
let(:can_read_security_configuration) { false }
it 'top level navigation link is visible' do
expect(rendered).not_to have_link('Security & Compliance', href: project_security_dashboard_index_path(project))
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Security::ConfigurationController do
let(:project) { create(:project, :public) }
let(:user) { create(:user) }
before do
sign_in(user)
end
describe 'GET show' do
context 'when feature flag is disabled' do
before do
stub_feature_flags(secure_security_and_compliance_configuration_page_on_ce: false)
end
it 'renders not found' do
get :show, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when feature flag is enabled' do
context 'when user has guest access' do
before do
project.add_guest(user)
end
it 'denies access' do
get :show, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'when user has developer access' do
before do
project.add_developer(user)
end
it 'grants access' do
get :show, params: { namespace_id: project.namespace, project_id: project }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to render_template(:show)
end
end
end
end
end
......@@ -398,6 +398,45 @@ RSpec.describe ProjectsHelper do
helper.send(:get_project_nav_tabs, project, user)
end
context 'Security & Compliance tabs' do
before do
stub_feature_flags(secure_security_and_compliance_configuration_page_on_ce: feature_flag_enabled)
allow(helper).to receive(:can?).with(user, :read_security_configuration, project).and_return(can_read_security_configuration)
end
context 'when user cannot read security configuration' do
let(:can_read_security_configuration) { false }
context 'when feature flag is disabled' do
let(:feature_flag_enabled) { false }
it { is_expected.not_to include(:security_configuration) }
end
context 'when feature flag is enabled' do
let(:feature_flag_enabled) { true }
it { is_expected.not_to include(:security_configuration) }
end
end
context 'when user can read security configuration' do
let(:can_read_security_configuration) { true }
context 'when feature flag is disabled' do
let(:feature_flag_enabled) { false }
it { is_expected.not_to include(:security_configuration) }
end
context 'when feature flag is enabled' do
let(:feature_flag_enabled) { true }
it { is_expected.to include(:security_configuration) }
end
end
end
context 'when builds feature is enabled' do
before do
allow(project).to receive(:builds_enabled?).and_return(true)
......
......@@ -865,6 +865,28 @@ RSpec.describe ProjectPolicy do
end
end
context 'security configuration feature' do
%w(guest reporter).each do |role|
context role do
let(:current_user) { send(role) }
it 'prevents reading security configuration' do
expect_disallowed(:read_security_configuration)
end
end
end
%w(developer maintainer owner).each do |role|
context role do
let(:current_user) { send(role) }
it 'allows reading security configuration' do
expect_allowed(:read_security_configuration)
end
end
end
end
describe 'design permissions' do
let(:current_user) { guest }
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::Security::ConfigurationController, 'routing' do
let(:base_params) { { namespace_id: 'gitlab', project_id: 'gitlabhq' } }
before do
allow(Project).to receive(:find_by_full_path).with('gitlab/gitlabhq', any_args).and_return(true)
end
it 'routes to #show' do
expect(get('/gitlab/gitlabhq/-/security/configuration')).to route_to('projects/security/configuration#show', namespace_id: 'gitlab', project_id: 'gitlabhq')
end
end
......@@ -18,7 +18,8 @@ RSpec.shared_context 'project navbar structure' do
{
nav_item: _('Security & Compliance'),
nav_sub_items: [
_('Audit Events')
_('Configuration'),
(_('Audit Events') if Gitlab.ee?)
]
}
end
......@@ -71,7 +72,7 @@ RSpec.shared_context 'project navbar structure' do
_('Schedules')
]
},
(security_and_compliance_nav_item if Gitlab.ee?),
security_and_compliance_nav_item,
{
nav_item: _('Operations'),
nav_sub_items: [
......@@ -190,7 +191,7 @@ RSpec.shared_context 'group navbar structure' do
nav_item: _('Merge Requests'),
nav_sub_items: []
},
(security_and_compliance_nav_item if Gitlab.ee?),
security_and_compliance_nav_item,
(push_rules_nav_item if Gitlab.ee?),
{
nav_item: _('Kubernetes'),
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'layouts/nav/sidebar/_project_security_link' do
let_it_be_with_reload(:project) { create(:project) }
context 'on security configuration' do
before do
assign(:project, project)
allow(controller).to receive(:controller_name).and_return('configuration')
allow(controller).to receive(:controller_path).and_return('projects/security/configuration')
allow(controller).to receive(:action_name).and_return('show')
allow(view).to receive(:any_project_nav_tab?).and_return(true)
allow(view).to receive(:project_nav_tab?).and_return(true)
end
it 'activates Security & Compliance tab' do
render
expect(rendered).to have_css('li.active', text: 'Security & Compliance')
end
it 'activates Configuration sub tab' do
render
expect(rendered).to have_css('.sidebar-sub-level-items > li.active', text: 'Configuration')
end
end
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