diff --git a/changelogs/unreleased/36739-standalone-vulnerability-page.yml b/changelogs/unreleased/36739-standalone-vulnerability-page.yml new file mode 100644 index 0000000000000000000000000000000000000000..8cb32673d3bfccc2d043e3e88e8adaf60bd0fce2 --- /dev/null +++ b/changelogs/unreleased/36739-standalone-vulnerability-page.yml @@ -0,0 +1,5 @@ +--- +title: Creates a standalone vulnerability page +merge_request: 20734 +author: +type: other diff --git a/ee/app/assets/javascripts/pages/projects/security/dashboard/show/index.js b/ee/app/assets/javascripts/pages/projects/security/dashboard/index.js similarity index 100% rename from ee/app/assets/javascripts/pages/projects/security/dashboard/show/index.js rename to ee/app/assets/javascripts/pages/projects/security/dashboard/index.js diff --git a/ee/app/controllers/projects/security/dashboard_controller.rb b/ee/app/controllers/projects/security/dashboard_controller.rb index 65ec2f11776a55b0bbdb04c405826bcfa97ccd43..5a2feb0f5e9a6280393257d3199b82a25fe78ac7 100644 --- a/ee/app/controllers/projects/security/dashboard_controller.rb +++ b/ee/app/controllers/projects/security/dashboard_controller.rb @@ -7,10 +7,22 @@ class DashboardController < Projects::ApplicationController alias_method :vulnerable, :project - def show + before_action only: [:index] do + push_frontend_feature_flag(:hide_dismissed_vulnerabilities) + end + + def index @pipeline = @project.latest_pipeline_with_security_reports &.present(current_user: current_user) end + + def show + return render_404 unless Feature.enabled?(:first_class_vulnerabilities, project) + + @vulnerability = project.vulnerabilities.find(params[:id]) + pipeline = @vulnerability.finding.pipelines.first + @pipeline = pipeline if Ability.allowed?(current_user, :read_pipeline, pipeline) + end end end end diff --git a/ee/app/presenters/ee/project_presenter.rb b/ee/app/presenters/ee/project_presenter.rb index 98bd9397eeb44fa862da17d95b37403a9a614010..2cc3f2efc57a2fda5beb686b5e209329845b7cc8 100644 --- a/ee/app/presenters/ee/project_presenter.rb +++ b/ee/app/presenters/ee/project_presenter.rb @@ -28,7 +28,7 @@ def approver_groups def security_dashboard_data OpenStruct.new(is_link: false, label: statistic_icon('lock') + _('Security Dashboard'), - link: project_security_dashboard_path(project), + link: project_security_dashboard_index_path(project), class_modifier: 'default') end end diff --git a/ee/app/views/layouts/nav/sidebar/_project_security_link.html.haml b/ee/app/views/layouts/nav/sidebar/_project_security_link.html.haml index 9c3833100b1db808fac4e4787f8ce2b8783ceeb8..4749d1a90258095e2f949b99932230ab511e6fe2 100644 --- a/ee/app/views/layouts/nav/sidebar/_project_security_link.html.haml +++ b/ee/app/views/layouts/nav/sidebar/_project_security_link.html.haml @@ -1,6 +1,6 @@ - return unless any_project_nav_tab?([:security, :dependencies, :licenses]) -- top_level_link = project_nav_tab?(:security) ? project_security_dashboard_path(@project) : project_dependencies_path(@project) +- top_level_link = project_nav_tab?(:security) ? project_security_dashboard_index_path(@project) : project_dependencies_path(@project) - top_level_qa_selector = project_nav_tab?(:security) ? 'security_dashboard_link' : 'dependency_list_link' = nav_link(path: sidebar_security_paths) do @@ -20,7 +20,7 @@ - if project_nav_tab?(:security) = nav_link(path: 'projects/security/dashboard#show') do - = link_to project_security_dashboard_path(@project), title: _('Security Dashboard') do + = link_to project_security_dashboard_index_path(@project), title: _('Security Dashboard') do %span= _('Security Dashboard') - if project_nav_tab?(:dependencies) diff --git a/ee/app/views/projects/security/dashboard/index.html.haml b/ee/app/views/projects/security/dashboard/index.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..3154cbfb060e0460f74aa167481c3dd6182bda91 --- /dev/null +++ b/ee/app/views/projects/security/dashboard/index.html.haml @@ -0,0 +1,4 @@ +- breadcrumb_title _("Security Dashboard") +- page_title _("Security Dashboard") + +#js-security-report-app{ data: project_security_dashboard_config(@project, @pipeline) } diff --git a/ee/app/views/projects/security/dashboard/list.html.haml b/ee/app/views/projects/security/dashboard/list.html.haml new file mode 100644 index 0000000000000000000000000000000000000000..5c82dde253e03f3a92c72196723d875bea3f5dc6 --- /dev/null +++ b/ee/app/views/projects/security/dashboard/list.html.haml @@ -0,0 +1,9 @@ +- @content_class = "limit-container-width" unless fluid_layout +-# - add_to_breadcrumbs _("Security Dashboard"), project_security_dashboard_index_path(@project) +- breadcrumb_title "Vulnerability list" +- page_title "Vulnerability list" + +.issue-details.issuable-details + .detail-page-description.content-block + %h2.title= Vulnerability List + diff --git a/ee/app/views/projects/security/dashboard/show.html.haml b/ee/app/views/projects/security/dashboard/show.html.haml index 3154cbfb060e0460f74aa167481c3dd6182bda91..9108123724893f6b336fa628c1737c5d10bab4c6 100644 --- a/ee/app/views/projects/security/dashboard/show.html.haml +++ b/ee/app/views/projects/security/dashboard/show.html.haml @@ -1,4 +1,50 @@ -- breadcrumb_title _("Security Dashboard") -- page_title _("Security Dashboard") +- @content_class = "limit-container-width" unless fluid_layout +- add_to_breadcrumbs _("Security Dashboard"), project_security_dashboard_index_path(@project) +- breadcrumb_title @vulnerability.id +- page_title @vulnerability.title +- page_description @vulnerability.description -#js-security-report-app{ data: project_security_dashboard_config(@project, @pipeline) } +.detail-page-header + .detail-page-header-body + .issuable-status-box.status-box.status-box-open.closed + %span= @vulnerability.state + - if @pipeline + %span#js-pipeline-created + - timeago = time_ago_with_tooltip(@pipeline.created_at) + - pipeline_link = '%{id}'.html_safe % { url: pipeline_url(@pipeline), id: @pipeline.id } + = _('Detected %{timeago} in pipeline %{pipeline_link}').html_safe % { pipeline_link: pipeline_link, timeago: timeago } + - else + %spa#js-vulnerability-created + = time_ago_with_tooltip(@vulnerability.created_at) + +.issue-details.issuable-details + .detail-page-description.content-block + %h2.title= @vulnerability.title + .description + .md + %h3= "Description" + %p= @vulnerability.finding.description + %ul + %li= _("Severity: %{severity}") % { severity: @vulnerability.severity } + %li= _("Confidence: %{confidence}") % { confidence: @vulnerability.confidence } + %li= _("Report Type: %{report_type}") % { report_type: @vulnerability.report_type } + + - if @vulnerability.finding.location["image"] + %li= _("Image: %{image}") % { image: @vulnerability.finding.location['image'] } + + - if @vulnerability.finding.location["operating_system"] + %li= _("Namespace: %{namespace}") % { namespace: @vulnerability.finding.location['operating_system'] } + + - if @vulnerability.finding.links.any? + %h3= _("Links") + %ul + - @vulnerability.finding.links.each do |link| + %li + %a{ :href=>link["url"], target: "_blank", rel: 'noopener noreferrer' }= link["url"] + + - if @vulnerability.finding.identifiers.any? + %h3= _("Identifiers") + %ul + - @vulnerability.finding.identifiers.each do |identifier| + %li + %a{ :href=>identifier.url, target: "_blank", rel: 'noopener noreferrer' }= identifier.name diff --git a/ee/config/routes/project.rb b/ee/config/routes/project.rb index 82e9899c6d97d44e87d9861d415229b3b2008940..7aa2277c9e144f1a14bf8f5e217a8d665c694f78 100644 --- a/ee/config/routes/project.rb +++ b/ee/config/routes/project.rb @@ -169,7 +169,7 @@ end namespace :security do - resource :dashboard, only: [:show], controller: :dashboard + resources :dashboard, only: [:show, :index], controller: :dashboard resource :configuration, only: [:show], controller: :configuration resources :vulnerability_findings, only: [:index] do diff --git a/ee/spec/controllers/projects/security/dashboard_controller_spec.rb b/ee/spec/controllers/projects/security/dashboard_controller_spec.rb index 14d0894467cb6b23f4ea312795d2c534fbb1a6af..a304c953240044669287652cf0d2cb14ba47edca 100644 --- a/ee/spec/controllers/projects/security/dashboard_controller_spec.rb +++ b/ee/spec/controllers/projects/security/dashboard_controller_spec.rb @@ -11,23 +11,23 @@ let(:vulnerable) { project } let(:security_dashboard_action) do - get :show, params: { namespace_id: project.namespace, project_id: project } + get :index, params: { namespace_id: project.namespace, project_id: project } end end before do group.add_developer(user) + stub_licensed_features(security_dashboard: true) end - describe 'GET #show' do + describe 'GET #index' do let(:pipeline) { create(:ci_pipeline, sha: project.commit.id, project: project, user: user) } render_views def show_security_dashboard(current_user = user) - stub_licensed_features(security_dashboard: true) sign_in(current_user) - get :show, params: { namespace_id: project.namespace, project_id: project } + get :index, params: { namespace_id: project.namespace, project_id: project } end context 'when uses legacy reports syntax' do @@ -39,7 +39,7 @@ def show_security_dashboard(current_user = user) show_security_dashboard expect(response).to have_gitlab_http_status(200) - expect(response).to render_template(:show) + expect(response).to render_template(:index) expect(response.body).to have_css("div#js-security-report-app[data-has-pipeline-data=true]") end end @@ -53,7 +53,7 @@ def show_security_dashboard(current_user = user) show_security_dashboard expect(response).to have_gitlab_http_status(200) - expect(response).to render_template(:show) + expect(response).to render_template(:index) expect(response.body).to have_css("div#js-security-report-app[data-has-pipeline-data=true]") end end @@ -63,9 +63,61 @@ def show_security_dashboard(current_user = user) show_security_dashboard expect(response).to have_gitlab_http_status(200) - expect(response).to render_template(:show) + expect(response).to render_template(:index) expect(response.body).to have_css("div#js-security-report-app[data-has-pipeline-data=false]") end end end + + describe 'GET #show' do + let_it_be(:pipeline) { create(:ci_pipeline, sha: project.commit.id, project: project, user: user) } + let_it_be(:vulnerability) { create(:vulnerability, project: project) } + + render_views + + def show_vulnerability + sign_in(user) + get :show, params: { namespace_id: project.namespace, project_id: project, id: vulnerability.id } + end + + context "when there's an attached pipeline" do + let_it_be(:finding) { create(:vulnerabilities_occurrence, vulnerability: vulnerability, pipelines: [pipeline]) } + + it 'renders the vulnerability page' do + show_vulnerability + + expect(response).to have_gitlab_http_status(200) + expect(response).to render_template(:show) + expect(response.body).to have_text(vulnerability.title) + end + + it 'renders the time pipeline ran' do + show_vulnerability + + expect(response.body).to have_css("#js-pipeline-created") + end + end + + context "when there's no attached pipeline" do + let_it_be(:finding) { create(:vulnerabilities_occurrence, vulnerability: vulnerability) } + + it 'renders the time the vulnerability was created' do + show_vulnerability + + expect(response.body).to have_css("#js-vulnerability-created") + end + end + + context 'when the feature flag is disabled' do + before do + stub_feature_flags(first_class_vulnerabilities: false) + end + + it 'renders the 404 page' do + show_vulnerability + + expect(response).to have_gitlab_http_status(404) + end + end + end end diff --git a/ee/spec/presenters/project_presenter_spec.rb b/ee/spec/presenters/project_presenter_spec.rb index 77cdc3fbaa8e96b20dd50c638224adacb910d32a..8faaa929ccda3bf807b15c71b0954f37fe6367c5 100644 --- a/ee/spec/presenters/project_presenter_spec.rb +++ b/ee/spec/presenters/project_presenter_spec.rb @@ -15,7 +15,7 @@ let(:security_dashboard_data) do OpenStruct.new(is_link: false, label: a_string_including('Security Dashboard'), - link: project_security_dashboard_path(project), + link: project_security_dashboard_index_path(project), class_modifier: 'default') end @@ -25,7 +25,7 @@ end it 'has security dashboard link' do - expect(presenter.extra_statistics_buttons.find { |button| button[:link] == project_security_dashboard_path(project) }).not_to be_nil + expect(presenter.extra_statistics_buttons.find { |button| button[:link] == project_security_dashboard_index_path(project) }).not_to be_nil end end @@ -35,7 +35,7 @@ end it 'has no security dashboard link' do - expect(presenter.extra_statistics_buttons.find { |button| button[:link] == project_security_dashboard_path(project) }).to be_nil + expect(presenter.extra_statistics_buttons.find { |button| button[:link] == project_security_dashboard_index_path(project) }).to be_nil end end end diff --git a/ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb b/ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb index 9910c74ca4a441f3d0a8f992dc0cd2b7798ed046..4e7c893f4e0a024a654b54a24c1a394372b56e7a 100644 --- a/ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb +++ b/ee/spec/views/layouts/nav/sidebar/_project.html.haml_spec.rb @@ -141,11 +141,11 @@ let(:can_read_dependencies) { true } it 'top level navigation link is visible' do - expect(rendered).to have_link('Security & Compliance', href: project_security_dashboard_path(project)) + expect(rendered).to have_link('Security & Compliance', href: project_security_dashboard_index_path(project)) end it 'security dashboard link is visible' do - expect(rendered).to have_link('Security Dashboard', href: project_security_dashboard_path(project)) + expect(rendered).to have_link('Security Dashboard', href: project_security_dashboard_index_path(project)) end it 'security configuration link is visible' do @@ -162,11 +162,11 @@ let(:can_read_dependencies) { false } it 'top level navigation link is visible' do - expect(rendered).to have_link('Security & Compliance', href: project_security_dashboard_path(project)) + expect(rendered).to have_link('Security & Compliance', href: project_security_dashboard_index_path(project)) end it 'security dashboard link is visible' do - expect(rendered).to have_link('Security Dashboard', href: project_security_dashboard_path(project)) + expect(rendered).to have_link('Security Dashboard', href: project_security_dashboard_index_path(project)) end it 'security configuration link is visible' do @@ -187,7 +187,7 @@ end it 'security dashboard link is not visible' do - expect(rendered).not_to have_link('Security Dashboard', href: project_security_dashboard_path(project)) + expect(rendered).not_to have_link('Security Dashboard', href: project_security_dashboard_index_path(project)) end it 'security configuration link is not visible' do @@ -204,11 +204,11 @@ let(:can_read_dashboard) { false } it 'top level navigation link is visible' do - expect(rendered).not_to have_link('Security & Compliance', href: project_security_dashboard_path(project)) + expect(rendered).not_to have_link('Security & Compliance', href: project_security_dashboard_index_path(project)) end it 'security dashboard link is not visible' do - expect(rendered).not_to have_link('Security Dashboard', href: project_security_dashboard_path(project)) + expect(rendered).not_to have_link('Security Dashboard', href: project_security_dashboard_index_path(project)) end it 'security configuration link is not visible' do diff --git a/locale/gitlab.pot b/locale/gitlab.pot index ab01af440ac5724089616e7b4df1e5294b552689..942c81aafd5eddbb274db16ed7632b5f355a5466 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4825,6 +4825,9 @@ msgstr "" msgid "Complete" msgstr "" +msgid "Confidence: %{confidence}" +msgstr "" + msgid "Confidential" msgstr "" @@ -6402,6 +6405,9 @@ msgstr "" msgid "Detect host keys" msgstr "" +msgid "Detected %{timeago} in pipeline %{pipeline_link}" +msgstr "" + msgid "DevOps Score" msgstr "" @@ -9857,6 +9863,9 @@ msgstr "" msgid "Identifier" msgstr "" +msgid "Identifiers" +msgstr "" + msgid "Identities" msgstr "" @@ -9914,6 +9923,9 @@ msgstr "" msgid "Image %{imageName} was scheduled for deletion from the registry." msgstr "" +msgid "Image: %{image}" +msgstr "" + msgid "ImageDiffViewer|2-up" msgstr "" @@ -11104,6 +11116,9 @@ msgstr "" msgid "LinkedPipelines|%{counterLabel} more downstream pipelines" msgstr "" +msgid "Links" +msgstr "" + msgid "List" msgstr "" @@ -12078,6 +12093,9 @@ msgstr "" msgid "Name:" msgstr "" +msgid "Namespace: %{namespace}" +msgstr "" + msgid "Namespaces to index" msgstr "" @@ -15569,6 +15587,9 @@ msgstr "" msgid "Repo by URL" msgstr "" +msgid "Report Type: %{report_type}" +msgstr "" + msgid "Report abuse to admin" msgstr "" @@ -16944,6 +16965,9 @@ msgstr "" msgid "Settings" msgstr "" +msgid "Severity: %{severity}" +msgstr "" + msgid "Share" msgstr ""