Fixes #3086 - Deletion of communication article works for admins
This commit is contained in:
parent
7f14332fe0
commit
5c5c69a785
5 changed files with 135 additions and 66 deletions
|
@ -18,7 +18,6 @@ class Delete
|
|||
actions
|
||||
|
||||
@isDeletable: (actions, ticket, article, ui) ->
|
||||
return { isDeletable: true } if ui.permissionCheck('admin')
|
||||
return { isDeletable: false } if !@deletableForAgent(actions, ticket, article, ui)
|
||||
return { isDeletable: true } if !@hasDeletableTimeframe()
|
||||
|
||||
|
@ -44,7 +43,7 @@ class Delete
|
|||
@deletableForAgent: (actions, ticket, article, ui) ->
|
||||
return false if !ui.permissionCheck('ticket.agent')
|
||||
return false if article.created_by_id != App.User.current()?.id
|
||||
return false if article.type.communication and !article.internal
|
||||
return false if article.type.communication
|
||||
|
||||
true
|
||||
|
||||
|
|
|
@ -16,17 +16,26 @@ class Ticket::ArticlePolicy < ApplicationPolicy
|
|||
end
|
||||
|
||||
def destroy?
|
||||
return true if user.permissions?('admin')
|
||||
return false if !access?(__method__)
|
||||
# don't let edge case exceptions raised in the TicketPolicy stop
|
||||
# other possible positive authorization checks
|
||||
rescue Pundit::NotAuthorizedError
|
||||
return false if !access?('show?')
|
||||
|
||||
# agents can destroy articles of type 'note'
|
||||
# which were created by themselves within the last 10 minutes
|
||||
return missing_admin_permission if !user.permissions?('ticket.agent')
|
||||
return missing_admin_permission if record.created_by_id != user.id
|
||||
return missing_admin_permission if record.type.communication? && !record.internal?
|
||||
return too_old_to_undo if deletable_timeframe? && record.created_at <= deletable_timeframe.ago
|
||||
# which were created by themselves within the last x minutes
|
||||
|
||||
if !user.permissions?('ticket.agent')
|
||||
return not_authorized('agent permission required') if !user.permissions?('ticket.agent')
|
||||
end
|
||||
|
||||
if record.created_by_id != user.id
|
||||
return not_authorized('you can only delete your own notes')
|
||||
end
|
||||
|
||||
if record.type.communication?
|
||||
return not_authorized('communication articles cannot be deleted')
|
||||
end
|
||||
|
||||
if deletable_timeframe? && record.created_at <= deletable_timeframe.ago
|
||||
return not_authorized('note is too old to be deleted')
|
||||
end
|
||||
|
||||
true
|
||||
end
|
||||
|
@ -53,12 +62,4 @@ class Ticket::ArticlePolicy < ApplicationPolicy
|
|||
ticket = Ticket.lookup(id: record.ticket_id)
|
||||
Pundit.authorize(user, ticket, query)
|
||||
end
|
||||
|
||||
def missing_admin_permission
|
||||
not_authorized('admin permission required')
|
||||
end
|
||||
|
||||
def too_old_to_undo
|
||||
not_authorized('articles more than 10 minutes old may not be deleted')
|
||||
end
|
||||
end
|
||||
|
|
|
@ -481,6 +481,8 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
end
|
||||
|
||||
describe 'DELETE /api/v1/ticket_articles/:id', authenticated_as: -> { user } do
|
||||
let(:other_agent) { create(:agent, groups: [Group.first]) }
|
||||
|
||||
let(:ticket) do
|
||||
create(:ticket, group: Group.first)
|
||||
end
|
||||
|
@ -491,10 +493,16 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
updated_by_id: agent.id, created_by_id: agent.id )
|
||||
end
|
||||
|
||||
let(:article_note) do
|
||||
let(:article_note_self) do
|
||||
create(:ticket_article,
|
||||
sender_name: 'Agent', internal: true, type_name: 'note', ticket: ticket,
|
||||
updated_by_id: agent.id, created_by_id: agent.id )
|
||||
updated_by_id: user.id, created_by_id: user.id )
|
||||
end
|
||||
|
||||
let(:article_note_other) do
|
||||
create(:ticket_article,
|
||||
sender_name: 'Agent', internal: true, type_name: 'note', ticket: ticket,
|
||||
updated_by_id: other_agent.id, created_by_id: other_agent.id )
|
||||
end
|
||||
|
||||
let(:article_note_customer) do
|
||||
|
@ -503,12 +511,20 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
updated_by_id: customer.id, created_by_id: customer.id )
|
||||
end
|
||||
|
||||
let(:article_note_communication) do
|
||||
let(:article_note_communication_self) do
|
||||
create(:ticket_article_type, name: 'note_communication', communication: true)
|
||||
|
||||
create(:ticket_article,
|
||||
sender_name: 'Agent', internal: true, type_name: 'note_communication', ticket: ticket,
|
||||
updated_by_id: agent.id, created_by_id: agent.id )
|
||||
updated_by_id: user.id, created_by_id: user.id )
|
||||
end
|
||||
|
||||
let(:article_note_communication_other) do
|
||||
create(:ticket_article_type, name: 'note_communication', communication: true)
|
||||
|
||||
create(:ticket_article,
|
||||
sender_name: 'Agent', internal: true, type_name: 'note_communication', ticket: ticket,
|
||||
updated_by_id: other_agent.id, created_by_id: other_agent.id )
|
||||
end
|
||||
|
||||
def delete_article_via_rest(article)
|
||||
|
@ -552,19 +568,27 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
|
||||
include_examples 'deleting',
|
||||
item: 'article_communication',
|
||||
now: true, later: true, much_later: true
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note',
|
||||
now: true, later: true, much_later: true
|
||||
item: 'article_note_self',
|
||||
now: true, later: true, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_other',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_customer',
|
||||
now: true, later: true, much_later: true
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_communication',
|
||||
now: true, later: true, much_later: true
|
||||
item: 'article_note_communication_self',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_communication_other',
|
||||
now: false, later: false, much_later: false
|
||||
end
|
||||
|
||||
context 'as agent' do
|
||||
|
@ -575,17 +599,24 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note',
|
||||
item: 'article_note_self',
|
||||
now: true, later: true, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_other',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_customer',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_communication',
|
||||
now: true, later: true, much_later: false
|
||||
item: 'article_note_communication_self',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_communication_other',
|
||||
now: false, later: false, much_later: false
|
||||
end
|
||||
|
||||
context 'as customer' do
|
||||
|
@ -596,7 +627,7 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note',
|
||||
item: 'article_note_other',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
|
@ -604,7 +635,11 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_communication',
|
||||
item: 'article_note_communication_self',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting',
|
||||
item: 'article_note_communication_other',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
end
|
||||
|
@ -612,15 +647,21 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
context 'with custom timeframe' do
|
||||
before { Setting.set 'ui_ticket_zoom_article_delete_timeframe', 6000 }
|
||||
|
||||
let(:article) { article_note }
|
||||
let(:article) { article_note_self }
|
||||
|
||||
context 'as admin' do
|
||||
let(:user) { admin }
|
||||
|
||||
context 'deleting before timeframe' do
|
||||
before { article && travel(5000.seconds) }
|
||||
|
||||
include_examples 'succeeds'
|
||||
end
|
||||
|
||||
context 'deleting after timeframe' do
|
||||
before { article && travel(8000.seconds) }
|
||||
|
||||
include_examples 'succeeds'
|
||||
include_examples 'fails'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -644,7 +685,7 @@ AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
|
|||
context 'with timeframe as 0' do
|
||||
before { Setting.set 'ui_ticket_zoom_article_delete_timeframe', 0 }
|
||||
|
||||
let(:article) { article_note }
|
||||
let(:article) { article_note_self }
|
||||
|
||||
context 'as agent' do
|
||||
let(:user) { agent }
|
||||
|
|
|
@ -894,7 +894,7 @@ RSpec.describe 'Ticket', type: :request do
|
|||
delete "/api/v1/ticket_articles/#{json_response['id']}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response['error']).to eq('Not authorized (admin permission required)!')
|
||||
expect(json_response['error']).to eq('Not authorized (communication articles cannot be deleted)!')
|
||||
|
||||
delete "/api/v1/tickets/#{ticket.id}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
|
@ -991,7 +991,7 @@ RSpec.describe 'Ticket', type: :request do
|
|||
expect(json_response['type_id']).to eq(Ticket::Article::Type.lookup(name: 'email').id)
|
||||
|
||||
delete "/api/v1/ticket_articles/#{json_response['id']}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
|
||||
delete "/api/v1/tickets/#{ticket.id}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:ok)
|
||||
|
@ -1237,7 +1237,7 @@ RSpec.describe 'Ticket', type: :request do
|
|||
delete "/api/v1/ticket_articles/#{article_json_response['id']}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response['error']).to eq('Not authorized (admin permission required)!')
|
||||
expect(json_response['error']).to eq('Not authorized (agent permission required)!')
|
||||
|
||||
params = {
|
||||
ticket_id: ticket.id,
|
||||
|
@ -1261,7 +1261,7 @@ RSpec.describe 'Ticket', type: :request do
|
|||
delete "/api/v1/ticket_articles/#{json_response['id']}", params: {}, as: :json
|
||||
expect(response).to have_http_status(:unauthorized)
|
||||
expect(json_response).to be_a_kind_of(Hash)
|
||||
expect(json_response['error']).to eq('Not authorized (admin permission required)!')
|
||||
expect(json_response['error']).to eq('Not authorized (agent permission required)!')
|
||||
|
||||
params = {
|
||||
from: 'something which should not be changed on server side',
|
||||
|
|
|
@ -208,28 +208,39 @@ RSpec.describe 'Ticket zoom', type: :system do
|
|||
end
|
||||
|
||||
describe 'delete article', authenticated_as: :user do
|
||||
let(:admin) { create :admin, groups: [Group.first] }
|
||||
let(:agent) { create :agent, groups: [Group.first] }
|
||||
let(:customer) { create :customer }
|
||||
let(:ticket) { create :ticket, group: agent.groups.first, customer: customer }
|
||||
let(:article) { send(item) }
|
||||
let(:admin) { create :admin, groups: [Group.first] }
|
||||
let(:agent) { create :agent, groups: [Group.first] }
|
||||
let(:other_agent) { create :agent, groups: [Group.first] }
|
||||
let(:customer) { create :customer }
|
||||
let(:ticket) { create :ticket, group: agent.groups.first, customer: customer }
|
||||
let(:article) { send(item) }
|
||||
|
||||
def article_communication
|
||||
create_ticket_article(sender_name: 'Agent', internal: false, type_name: 'email', updated_by: customer)
|
||||
end
|
||||
|
||||
def article_note
|
||||
create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: agent)
|
||||
def article_note_self
|
||||
create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: user)
|
||||
end
|
||||
|
||||
def article_note_other
|
||||
create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note', updated_by: other_agent)
|
||||
end
|
||||
|
||||
def article_note_customer
|
||||
create_ticket_article(sender_name: 'Customer', internal: false, type_name: 'note', updated_by: customer)
|
||||
end
|
||||
|
||||
def article_note_communication
|
||||
def article_note_communication_self
|
||||
create(:ticket_article_type, name: 'note_communication', communication: true)
|
||||
|
||||
create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: agent)
|
||||
create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: user)
|
||||
end
|
||||
|
||||
def article_note_communication_other
|
||||
create(:ticket_article_type, name: 'note_communication', communication: true)
|
||||
|
||||
create_ticket_article(sender_name: 'Agent', internal: true, type_name: 'note_communication', updated_by: other_agent)
|
||||
end
|
||||
|
||||
def create_ticket_article(sender_name:, internal:, type_name:, updated_by:)
|
||||
|
@ -243,7 +254,7 @@ RSpec.describe 'Ticket zoom', type: :system do
|
|||
context 'going through full stack' do
|
||||
context 'as admin' do
|
||||
let(:user) { admin }
|
||||
let(:item) { 'article_communication' }
|
||||
let(:item) { 'article_note_self' }
|
||||
let(:offset) { 0.minutes }
|
||||
|
||||
it 'succeeds' do
|
||||
|
@ -298,19 +309,27 @@ RSpec.describe 'Ticket zoom', type: :system do
|
|||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_communication',
|
||||
now: true, later: true, much_later: true
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note',
|
||||
now: true, later: true, much_later: true
|
||||
item: 'article_note_self',
|
||||
now: true, later: true, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note_other',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note_customer',
|
||||
now: true, later: true, much_later: true
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note_communication',
|
||||
now: true, later: true, much_later: true
|
||||
item: 'article_note_communication_self',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note_communication_other',
|
||||
now: false, later: false, much_later: false
|
||||
end
|
||||
|
||||
context 'as agent' do
|
||||
|
@ -321,16 +340,24 @@ RSpec.describe 'Ticket zoom', type: :system do
|
|||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note',
|
||||
item: 'article_note_self',
|
||||
now: true, later: true, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note_other',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note_customer',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note_communication',
|
||||
now: true, later: true, much_later: false
|
||||
item: 'article_note_communication_self',
|
||||
now: false, later: false, much_later: false
|
||||
|
||||
include_examples 'deleting ticket article',
|
||||
item: 'article_note_communication_other',
|
||||
now: false, later: false, much_later: false
|
||||
end
|
||||
|
||||
context 'as customer' do
|
||||
|
@ -352,14 +379,15 @@ RSpec.describe 'Ticket zoom', type: :system do
|
|||
context 'as admin' do
|
||||
let(:user) { admin }
|
||||
|
||||
include_examples 'according to permission matrix', item: 'article_note', expects_visible: true, offset: 8000.seconds, description: 'outside of delete timeframe'
|
||||
include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
|
||||
include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
|
||||
end
|
||||
|
||||
context 'as agent' do
|
||||
let(:user) { agent }
|
||||
|
||||
include_examples 'according to permission matrix', item: 'article_note', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
|
||||
include_examples 'according to permission matrix', item: 'article_note', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
|
||||
include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 5000.seconds, description: 'outside of delete timeframe'
|
||||
include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: false, offset: 8000.seconds, description: 'outside of delete timeframe'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -369,7 +397,7 @@ RSpec.describe 'Ticket zoom', type: :system do
|
|||
context 'as agent' do
|
||||
let(:user) { agent }
|
||||
|
||||
include_examples 'according to permission matrix', item: 'article_note', expects_visible: true, offset: 99.days, description: 'long after'
|
||||
include_examples 'according to permission matrix', item: 'article_note_self', expects_visible: true, offset: 99.days, description: 'long after'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -378,7 +406,7 @@ RSpec.describe 'Ticket zoom', type: :system do
|
|||
before { Setting.set 'ui_ticket_zoom_article_delete_timeframe', 5 }
|
||||
|
||||
let(:user) { agent }
|
||||
let(:item) { 'article_note' }
|
||||
let(:item) { 'article_note_self' }
|
||||
let!(:article) { send(item) }
|
||||
let(:offset) { 0.seconds }
|
||||
|
||||
|
|
Loading…
Reference in a new issue