From 9faface7e1a2f22c399510c44f648453d49da199 Mon Sep 17 00:00:00 2001 From: Sojan Jose Date: Thu, 5 Jun 2025 03:49:15 +0000 Subject: [PATCH] chore: more updates --- .../v1/accounts/conversations_controller.rb | 1 + .../conversation/contextMenu/Index.vue | 17 ++++-- app/models/conversation.rb | 1 + app/policies/conversation_policy.rb | 4 ++ .../models/enterprise/audit/conversation.rb | 7 +++ .../accounts/conversations_controller_spec.rb | 59 +++++++++++++++++++ .../enterprise/audit/conversation_spec.rb | 34 +++++++++++ spec/policies/conversation_policy_spec.rb | 34 +++++++++++ 8 files changed, 151 insertions(+), 6 deletions(-) create mode 100644 enterprise/app/models/enterprise/audit/conversation.rb create mode 100644 spec/models/enterprise/audit/conversation_spec.rb create mode 100644 spec/policies/conversation_policy_spec.rb diff --git a/app/controllers/api/v1/accounts/conversations_controller.rb b/app/controllers/api/v1/accounts/conversations_controller.rb index 041ff8af2..e27869d82 100644 --- a/app/controllers/api/v1/accounts/conversations_controller.rb +++ b/app/controllers/api/v1/accounts/conversations_controller.rb @@ -125,6 +125,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro end def destroy + authorize @conversation, :destroy? ::DeleteObjectJob.perform_later(@conversation, Current.user, request.ip) head :ok end diff --git a/app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue b/app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue index cce6609aa..a136f30bf 100644 --- a/app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue +++ b/app/javascript/dashboard/components/widgets/conversation/contextMenu/Index.vue @@ -168,6 +168,9 @@ export default { // Don't show snooze if the conversation is already snoozed/resolved/pending return this.status === wootConstants.STATUS_TYPE.OPEN; }, + isAdmin() { + return this.currentUser?.role === 'administrator'; + }, }, mounted() { this.$store.dispatch('inboxAssignableAgents/fetch', [this.inboxId]); @@ -286,11 +289,13 @@ export default { @click.stop="$emit('assignTeam', team)" /> -
- + diff --git a/app/models/conversation.rb b/app/models/conversation.rb index 3f1a20037..922118b09 100644 --- a/app/models/conversation.rb +++ b/app/models/conversation.rb @@ -305,5 +305,6 @@ class Conversation < ApplicationRecord end end +Conversation.include_mod_with('Audit::Conversation') Conversation.include_mod_with('Concerns::Conversation') Conversation.prepend_mod_with('Conversation') diff --git a/app/policies/conversation_policy.rb b/app/policies/conversation_policy.rb index 133cb3b02..931e17435 100644 --- a/app/policies/conversation_policy.rb +++ b/app/policies/conversation_policy.rb @@ -2,4 +2,8 @@ class ConversationPolicy < ApplicationPolicy def index? true end + + def destroy? + @account_user&.administrator? + end end diff --git a/enterprise/app/models/enterprise/audit/conversation.rb b/enterprise/app/models/enterprise/audit/conversation.rb new file mode 100644 index 000000000..7ea08c343 --- /dev/null +++ b/enterprise/app/models/enterprise/audit/conversation.rb @@ -0,0 +1,7 @@ +module Enterprise::Audit::Conversation + extend ActiveSupport::Concern + + included do + audited only: [], on: [:destroy] + end +end \ No newline at end of file diff --git a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb index a3697be84..38bd649fe 100644 --- a/spec/controllers/api/v1/accounts/conversations_controller_spec.rb +++ b/spec/controllers/api/v1/accounts/conversations_controller_spec.rb @@ -926,4 +926,63 @@ RSpec.describe 'Conversations API', type: :request do end end end + + describe 'DELETE /api/v1/accounts/{account.id}/conversations/:id' do + let(:conversation) { create(:conversation, account: account) } + let(:agent) { create(:user, account: account, role: :agent) } + let(:administrator) { create(:user, account: account, role: :administrator) } + + context 'when it is an unauthenticated user' do + it 'returns unauthorized' do + delete "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}" + + expect(response).to have_http_status(:unauthorized) + end + end + + context 'when it is an authenticated agent' do + before do + create(:inbox_member, user: agent, inbox: conversation.inbox) + end + + it 'returns unauthorized' do + delete "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}", + headers: agent.create_new_auth_token, + as: :json + + expect(response).to have_http_status(:unauthorized) + response_body = response.parsed_body + expect(response_body['error']).to eq('You are not authorized to do this action') + end + end + + context 'when it is an authenticated administrator' do + before do + create(:inbox_member, user: administrator, inbox: conversation.inbox) + end + + it 'successfully deletes the conversation' do + expect do + delete "/api/v1/accounts/#{account.id}/conversations/#{conversation.display_id}", + headers: administrator.create_new_auth_token, + as: :json + end.to have_enqueued_job(DeleteObjectJob).with(conversation, administrator, anything) + + expect(response).to have_http_status(:ok) + end + + it 'can delete conversations from inboxes without direct access' do + other_inbox = create(:inbox, account: account) + other_conversation = create(:conversation, account: account, inbox: other_inbox) + + expect do + delete "/api/v1/accounts/#{account.id}/conversations/#{other_conversation.display_id}", + headers: administrator.create_new_auth_token, + as: :json + end.to have_enqueued_job(DeleteObjectJob).with(other_conversation, administrator, anything) + + expect(response).to have_http_status(:ok) + end + end + end end diff --git a/spec/models/enterprise/audit/conversation_spec.rb b/spec/models/enterprise/audit/conversation_spec.rb new file mode 100644 index 000000000..195e23c32 --- /dev/null +++ b/spec/models/enterprise/audit/conversation_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +RSpec.describe 'Conversation Audit', type: :model do + let(:account) { create(:account) } + let(:conversation) { create(:conversation, account: account) } + + before do + # Enable auditing for conversations + conversation.class.send(:include, Enterprise::Audit::Conversation) if defined?(Enterprise::Audit::Conversation) + end + + describe 'audit logging on destroy' do + it 'creates an audit log when conversation is destroyed' do + skip 'Enterprise audit module not available' unless defined?(Enterprise::Audit::Conversation) + + expect do + conversation.destroy! + end.to change { Audited::Audit.count }.by(1) + + audit = Audited::Audit.last + expect(audit.auditable_type).to eq('Conversation') + expect(audit.action).to eq('destroy') + expect(audit.auditable_id).to eq(conversation.id) + end + + it 'does not create audit log for other actions by default' do + skip 'Enterprise audit module not available' unless defined?(Enterprise::Audit::Conversation) + + expect do + conversation.update!(priority: 'high') + end.not_to(change { Audited::Audit.count }) + end + end +end \ No newline at end of file diff --git a/spec/policies/conversation_policy_spec.rb b/spec/policies/conversation_policy_spec.rb new file mode 100644 index 000000000..aaf23b20f --- /dev/null +++ b/spec/policies/conversation_policy_spec.rb @@ -0,0 +1,34 @@ +require 'rails_helper' + +RSpec.describe ConversationPolicy, type: :policy do + subject { described_class } + + let(:account) { create(:account) } + let(:conversation) { create(:conversation, account: account) } + let(:administrator) { create(:user, account: account, role: :administrator) } + let(:agent) { create(:user, account: account, role: :agent) } + let(:administrator_context) { { user: administrator, account: account, account_user: administrator.account_users.first } } + let(:agent_context) { { user: agent, account: account, account_user: agent.account_users.first } } + + permissions :destroy? do + context 'when user is an administrator' do + it 'allows destroy' do + expect(subject).to permit(administrator_context, conversation) + end + end + + context 'when user is an agent' do + it 'denies destroy' do + expect(subject).not_to permit(agent_context, conversation) + end + end + end + + permissions :index? do + context 'for any authenticated user' do + it 'allows index' do + expect(subject).to permit(agent_context, conversation) + end + end + end +end \ No newline at end of file