Commit 30e31e4a authored by Nick Thomas's avatar Nick Thomas

Merge branch '10364-update-dismissal-comments' into 'master'

Support editing and deleting of dismissal comments

See merge request gitlab-org/gitlab-ee!11963
parents 45e1779d 9b10d632
...@@ -483,7 +483,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do ...@@ -483,7 +483,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
# EE-specific end # EE-specific end
## EE-specific ## EE-specific
resources :vulnerability_feedback, only: [:index, :create, :destroy], constraints: { id: /\d+/ } resources :vulnerability_feedback, only: [:index, :create, :update, :destroy], constraints: { id: /\d+/ }
get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics } get :issues, to: 'issues#calendar', constraints: lambda { |req| req.format == :ics }
......
# frozen_string_literal: true # frozen_string_literal: true
class Projects::VulnerabilityFeedbackController < Projects::ApplicationController class Projects::VulnerabilityFeedbackController < Projects::ApplicationController
before_action :vulnerability_feedback, only: [:destroy] before_action :vulnerability_feedback, only: [:update, :destroy]
before_action :authorize_read_vulnerability_feedback!, only: [:index] before_action :authorize_read_vulnerability_feedback!, only: [:index]
before_action :authorize_create_vulnerability_feedback!, only: [:create] before_action :authorize_create_vulnerability_feedback!, only: [:create]
before_action :authorize_update_vulnerability_feedback!, only: [:update]
before_action :authorize_destroy_vulnerability_feedback!, only: [:destroy] before_action :authorize_destroy_vulnerability_feedback!, only: [:destroy]
skip_before_action :authenticate_user!, only: [:index], raise: false skip_before_action :authenticate_user!, only: [:index], raise: false
respond_to :json respond_to :json
...@@ -39,6 +41,17 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle ...@@ -39,6 +41,17 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle
end end
end end
def update
service = VulnerabilityFeedbackModule::UpdateService.new(project, current_user, vulnerability_feedback_params )
result = service.execute(vulnerability_feedback)
if result[:status] == :success
render json: serializer.represent(result[:vulnerability_feedback])
else
render json: result[:message], status: :unprocessable_entity
end
end
def destroy def destroy
service = VulnerabilityFeedback::DestroyService.new(project, current_user, vulnerability_feedback) service = VulnerabilityFeedback::DestroyService.new(project, current_user, vulnerability_feedback)
service.execute service.execute
...@@ -61,6 +74,10 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle ...@@ -61,6 +74,10 @@ class Projects::VulnerabilityFeedbackController < Projects::ApplicationControlle
render_403 unless can?(current_user, :destroy_vulnerability_feedback, vulnerability_feedback) render_403 unless can?(current_user, :destroy_vulnerability_feedback, vulnerability_feedback)
end end
def authorize_update_vulnerability_feedback!
render_403 unless can?(current_user, :update_vulnerability_feedback, vulnerability_feedback)
end
def serializer def serializer
Vulnerabilities::FeedbackSerializer.new(current_user: current_user, project: project) Vulnerabilities::FeedbackSerializer.new(current_user: current_user, project: project)
end end
......
...@@ -53,5 +53,11 @@ module Vulnerabilities ...@@ -53,5 +53,11 @@ module Vulnerabilities
raise ArgumentError.new("'#{feedback_params[:category]}' is not a valid category") raise ArgumentError.new("'#{feedback_params[:category]}' is not a valid category")
end end
end end
# A hard delete of the comment_author will cause the comment_author to be nil, but the comment
# will still exist.
def has_comment?
comment.present? && comment_author.present?
end
end end
end end
...@@ -113,6 +113,7 @@ module EE ...@@ -113,6 +113,7 @@ module EE
enable :admin_board enable :admin_board
enable :create_vulnerability_feedback enable :create_vulnerability_feedback
enable :destroy_vulnerability_feedback enable :destroy_vulnerability_feedback
enable :update_vulnerability_feedback
enable :create_package enable :create_package
enable :read_feature_flag enable :read_feature_flag
enable :create_feature_flag enable :create_feature_flag
......
...@@ -15,6 +15,6 @@ module Vulnerabilities ...@@ -15,6 +15,6 @@ module Vulnerabilities
(~can?(:create_merge_request_in) | ~can?(:create_merge_request_from)) (~can?(:create_merge_request_in) | ~can?(:create_merge_request_from))
end.prevent :create_vulnerability_feedback end.prevent :create_vulnerability_feedback
rule { ~dismissal }.prevent :destroy_vulnerability_feedback rule { ~dismissal }.prevent :destroy_vulnerability_feedback, :update_vulnerability_feedback
end end
end end
...@@ -7,7 +7,7 @@ class Vulnerabilities::FeedbackEntity < Grape::Entity ...@@ -7,7 +7,7 @@ class Vulnerabilities::FeedbackEntity < Grape::Entity
expose :created_at expose :created_at
expose :project_id expose :project_id
expose :author, using: UserEntity expose :author, using: UserEntity
expose :comment_details, if: -> (feedback, _) { feedback.comment.present? } do expose :comment_details, if: -> (feedback, _) { feedback.has_comment? } do
expose :comment expose :comment
expose :comment_timestamp expose :comment_timestamp
expose :comment_author, using: UserEntity expose :comment_author, using: UserEntity
......
# frozen_string_literal: true
module VulnerabilityFeedbackModule
class UpdateService < ::BaseService
def execute(vulnerability_feedback)
raise Gitlab::Access::AccessDeniedError unless can?(current_user, :update_vulnerability_feedback, vulnerability_feedback)
if vulnerability_feedback.update(vulnerability_feedback_attributes)
success(vulnerability_feedback)
else
error(vulnerability_feedback.errors)
end
rescue ArgumentError => e
# VulnerabilityFeedback relies on #enum attributes which raise this exception
error(e.message)
end
private
def vulnerability_feedback_attributes
if params[:comment].present?
{ comment: params[:comment], comment_timestamp: Time.zone.now, comment_author: current_user }
else
{ comment: nil, comment_timestamp: nil, comment_author: nil }
end
end
def success(vulnerability_feedback)
super().merge(vulnerability_feedback: vulnerability_feedback)
end
end
end
...@@ -195,6 +195,75 @@ describe Projects::VulnerabilityFeedbackController do ...@@ -195,6 +195,75 @@ describe Projects::VulnerabilityFeedbackController do
end end
end end
describe 'PATCH #update' do
let(:vuln_feedback) do
create(:vulnerability_feedback, :dismissal, :sast, :comment,
project: project,
author: user
)
end
context 'with valid params' do
before do
vuln_feedback.comment = 'new dismissal comment'
update_feedback user: user, params: vuln_feedback
end
it 'returns the updated feedback' do
expect(response).to match_response_schema('vulnerability_feedback', dir: 'ee')
end
it 'returns a successful 200 response' do
expect(response).to have_gitlab_http_status(200)
end
it 'updates the comment attributes' do
expect(vuln_feedback.reload.comment).to eq('new dismissal comment')
end
end
context 'with invalid params' do
it 'returns a not found 404 response for invalid vulnerability feedback id' do
vuln_feedback.id = 123
update_feedback user: user, params: vuln_feedback
expect(response).to have_gitlab_http_status(404)
end
end
context 'with unauthorized user for feedback update' do
it 'returns a forbidden 403 response' do
group.add_guest(guest)
update_feedback user: guest, params: vuln_feedback
expect(response).to have_gitlab_http_status(403)
end
end
context 'with unauthorized user for given project' do
let(:unauthorized_user) { create(:user) }
let(:project) { create(:project, :private, namespace: group) }
it 'returns a 404 response' do
update_feedback user: unauthorized_user, params: vuln_feedback
expect(response).to have_gitlab_http_status(404)
end
end
def update_feedback(user:, params:)
sign_in(user)
patch :update, params: {
namespace_id: project.namespace.to_param,
project_id: project,
id: params[:id],
vulnerability_feedback: params
}, as: :json
end
end
describe 'DELETE #destroy' do describe 'DELETE #destroy' do
let(:pipeline) { create(:ci_pipeline, project: project) } let(:pipeline) { create(:ci_pipeline, project: project) }
let!(:vuln_feedback) { create(:vulnerability_feedback, :dismissal, :sast, project: project, author: user, pipeline: pipeline) } let!(:vuln_feedback) { create(:vulnerability_feedback, :dismissal, :sast, project: project, author: user, pipeline: pipeline) }
......
...@@ -35,6 +35,31 @@ describe Vulnerabilities::Feedback do ...@@ -35,6 +35,31 @@ describe Vulnerabilities::Feedback do
end end
end end
describe '#has_comment?' do
let(:feedback) { build(:vulnerability_feedback, comment: comment, comment_author: comment_author) }
let(:comment) { 'a comment' }
let(:comment_author) { build(:user) }
subject { feedback.has_comment? }
context 'comment and comment_author are set' do
it { is_expected.to be_truthy }
end
context 'comment is set and comment_author is not' do
let(:comment_author) { nil }
it { is_expected.to be_falsy }
end
context 'comment and comment_author are not set' do
let(:comment) { nil }
let(:comment_author) { nil }
it { is_expected.to be_falsy }
end
end
describe '#find_or_init_for' do describe '#find_or_init_for' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:project) { create(:project, :public, :repository, namespace: group) } let(:project) { create(:project, :public, :repository, namespace: group) }
......
...@@ -324,6 +324,7 @@ describe ProjectPolicy do ...@@ -324,6 +324,7 @@ describe ProjectPolicy do
where(permission: %i[ where(permission: %i[
create_vulnerability_feedback create_vulnerability_feedback
update_vulnerability_feedback
destroy_vulnerability_feedback destroy_vulnerability_feedback
]) ])
......
...@@ -75,6 +75,32 @@ describe Vulnerabilities::FeedbackPolicy do ...@@ -75,6 +75,32 @@ describe Vulnerabilities::FeedbackPolicy do
end end
end end
describe 'update_vulnerability_feedback' do
context 'when feedback type is issue' do
let(:vulnerability_feedback) { Vulnerabilities::Feedback.new(project: project, feedback_type: :issue) }
it 'does not allow to update issue feedback' do
is_expected.to be_disallowed(:update_vulnerability_feedback)
end
end
context 'when feedback type is merge_request' do
let(:vulnerability_feedback) { Vulnerabilities::Feedback.new(project: project, feedback_type: :merge_request) }
it 'does not allow to update merge request feedback' do
is_expected.to be_disallowed(:update_vulnerability_feedback)
end
end
context 'when feedback type is dismissal' do
let(:vulnerability_feedback) { Vulnerabilities::Feedback.new(project: project, feedback_type: :dismissal) }
it 'allows to update dismissal feedback' do
is_expected.to be_allowed(:update_vulnerability_feedback)
end
end
end
describe 'destroy_vulnerability_feedback' do describe 'destroy_vulnerability_feedback' do
context 'when feedback type is issue' do context 'when feedback type is issue' do
let(:vulnerability_feedback) { Vulnerabilities::Feedback.new(project: project, feedback_type: :issue) } let(:vulnerability_feedback) { Vulnerabilities::Feedback.new(project: project, feedback_type: :issue) }
......
# frozen_string_literal: true
require 'spec_helper'
describe VulnerabilityFeedbackModule::UpdateService, '#execute' do
let(:group) { create(:group) }
let(:project) { create(:project, :public, :repository, namespace: group) }
let(:user) { create(:user) }
let(:pipeline) { create(:ci_pipeline, project: project) }
let(:vuln_feedback) { create(:vulnerability_feedback, :comment, project: project) }
let(:service) { described_class.new(project, user, comment: comment) }
before do
group.add_developer(user)
end
subject(:result) { service.execute(vuln_feedback) }
context 'when params are valid' do
context 'when new comment is passed' do
let(:comment) {'new dismissal comment' }
let(:vuln_feedback) do
create(:vulnerability_feedback, :dismissal, :sast,
project: project,
author: user,
pipeline: pipeline)
end
it 'adds the comments' do
expect(result[:status]).to eq(:success)
feedback = result[:vulnerability_feedback]
expect(feedback.comment).to eq('new dismissal comment')
expect(feedback.comment_author).to eq(user)
expect(feedback.comment_timestamp).not_to be_nil
end
end
context 'second user updates the comment' do
let(:comment) { 'updated dismissal comment' }
let(:second_user) { create(:user) }
let(:service) { described_class.new(project, second_user, vuln_feedback) }
before do
group.add_developer(second_user)
end
it 'sets second user as the comment author' do
feedback = result[:vulnerability_feedback]
expect(feedback.comment_author).to eq(second_user)
end
end
context 'when updated comment is passed' do
let(:comment) { 'updated dismissal comment' }
it 'updates the comments' do
feedback = result[:vulnerability_feedback]
expect(feedback.comment).to eq('updated dismissal comment')
end
end
context 'when deleting a comment' do
let(:comment) { '' }
it 'removes the comment data' do
expect(result[:status]).to eq(:success)
feedback = result[:vulnerability_feedback]
expect(feedback.comment).to be_nil
expect(feedback.comment_author).to be_nil
expect(feedback.comment_timestamp).to be_nil
end
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