+ checkPermissions(responseManagePermissions)
+);
const selectedAssistantId = computed(() => Number(route.params.assistantId));
@@ -206,7 +213,7 @@ onMounted(() => {
{
{
:created-at="response.created_at"
:updated-at="response.updated_at"
:is-selected="bulkSelectedIds.has(response.id)"
- :selectable="hoveredCard === response.id || bulkSelectedIds.size > 0"
- :show-menu="!bulkSelectedIds.has(response.id)"
+ :selectable="
+ canManageResponses &&
+ (hoveredCard === response.id || bulkSelectedIds.size > 0)
+ "
+ :show-menu="canManageResponses && !bulkSelectedIds.has(response.id)"
:show-actions="false"
@action="handleAction"
@navigate="handleNavigationAction"
diff --git a/app/javascript/dashboard/routes/dashboard/captain/responses/Pending.vue b/app/javascript/dashboard/routes/dashboard/captain/responses/Pending.vue
index b26658f9c..49c5aa4ea 100644
--- a/app/javascript/dashboard/routes/dashboard/captain/responses/Pending.vue
+++ b/app/javascript/dashboard/routes/dashboard/captain/responses/Pending.vue
@@ -7,6 +7,8 @@ import { useRouter, useRoute } from 'vue-router';
import { FEATURE_FLAGS } from 'dashboard/featureFlags';
import { debounce } from '@chatwoot/utils';
import { useAccount } from 'dashboard/composables/useAccount';
+import { usePolicy } from 'dashboard/composables/usePolicy';
+import { PORTAL_PERMISSIONS } from 'dashboard/constants/permissions';
import Button from 'dashboard/components-next/button/Button.vue';
import Input from 'dashboard/components-next/input/Input.vue';
@@ -25,6 +27,7 @@ const router = useRouter();
const route = useRoute();
const store = useStore();
const { isOnChatwootCloud } = useAccount();
+const { checkPermissions } = usePolicy();
const uiFlags = useMapGetter('captainResponses/getUIFlags');
const responseMeta = useMapGetter('captainResponses/getMeta');
const responses = useMapGetter('captainResponses/getRecords');
@@ -40,6 +43,10 @@ const searchQuery = ref('');
const { t } = useI18n();
const createDialog = ref(null);
+const responseManagePermissions = ['administrator', PORTAL_PERMISSIONS];
+const canManageResponses = computed(() =>
+ checkPermissions(responseManagePermissions)
+);
const backUrl = computed(() => ({
name: 'captain_assistants_responses_index',
@@ -286,6 +293,7 @@ onMounted(() => {
{
:created-at="response.created_at"
:updated-at="response.updated_at"
:is-selected="bulkSelectedIds.has(response.id)"
- :selectable="hoveredCard === response.id || bulkSelectedIds.size > 0"
+ :selectable="
+ canManageResponses &&
+ (hoveredCard === response.id || bulkSelectedIds.size > 0)
+ "
:show-menu="false"
- :show-actions="!bulkSelectedIds.has(response.id)"
+ :show-actions="
+ canManageResponses && !bulkSelectedIds.has(response.id)
+ "
@action="handleAction"
@navigate="handleNavigationAction"
@select="handleCardSelect"
diff --git a/enterprise/app/policies/captain/assistant_response_policy.rb b/enterprise/app/policies/captain/assistant_response_policy.rb
index 7504a7e05..852c2c85c 100644
--- a/enterprise/app/policies/captain/assistant_response_policy.rb
+++ b/enterprise/app/policies/captain/assistant_response_policy.rb
@@ -24,10 +24,6 @@ class Captain::AssistantResponsePolicy < ApplicationPolicy
def manage?
return true if @account_user.administrator?
- if @account_user.custom_role.present?
- return @account_user.custom_role.permissions.include?('knowledge_base_manage')
- end
-
- @account_user.agent?
+ @account_user.custom_role&.permissions&.include?('knowledge_base_manage') || false
end
end
diff --git a/spec/enterprise/controllers/api/v1/accounts/captain/assistant_responses_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/captain/assistant_responses_controller_spec.rb
index 012cbbd1b..467d0460a 100644
--- a/spec/enterprise/controllers/api/v1/accounts/captain/assistant_responses_controller_spec.rb
+++ b/spec/enterprise/controllers/api/v1/accounts/captain/assistant_responses_controller_spec.rb
@@ -180,16 +180,15 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
expect(json_response[:answer]).to eq('Test answer')
end
- it 'creates a new response if the user is an agent' do
+ it 'does not create a new response if the user is an agent without knowledge base permission' do
expect do
post "/api/v1/accounts/#{account.id}/captain/assistant_responses",
params: valid_params,
headers: agent.create_new_auth_token,
as: :json
- end.to change(Captain::AssistantResponse, :count).by(1)
+ end.not_to change(Captain::AssistantResponse, :count)
- expect(response).to have_http_status(:success)
- expect(json_response[:question]).to eq('Test question?')
+ expect(response).to have_http_status(:forbidden)
end
it 'creates a new response if the user has a custom role with knowledge base permission' do
@@ -254,7 +253,6 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
expect(response).to have_http_status(:unprocessable_entity)
end
-
end
end
@@ -281,12 +279,25 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
expect(json_response[:answer]).to eq('Updated answer')
end
- it 'updates the response if the user is an agent' do
+ it 'does not update the response if the user is an agent without knowledge base permission' do
patch "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
params: update_params,
headers: agent.create_new_auth_token,
as: :json
+ expect(response).to have_http_status(:forbidden)
+ expect(response_record.reload.question).not_to eq('Updated question?')
+ end
+
+ it 'updates the response if the user has a custom role with knowledge base permission' do
+ custom_role = create(:custom_role, account: account, permissions: ['knowledge_base_manage'])
+ AccountUser.find_by!(account: account, user: agent_with_custom_role).update!(custom_role: custom_role)
+
+ patch "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
+ params: update_params,
+ headers: agent_with_custom_role.create_new_auth_token,
+ as: :json
+
expect(response).to have_http_status(:ok)
expect(json_response[:question]).to eq('Updated question?')
end
@@ -315,11 +326,24 @@ RSpec.describe 'Api::V1::Accounts::Captain::AssistantResponses', type: :request
describe 'DELETE /api/v1/accounts/:account_id/captain/assistant_responses/:id' do
let!(:response_record) { create(:captain_assistant_response, assistant: assistant) }
- it 'deletes the response' do
+ it 'does not delete the response if the user is an agent without knowledge base permission' do
expect do
delete "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
headers: agent.create_new_auth_token,
as: :json
+ end.not_to change(Captain::AssistantResponse, :count)
+
+ expect(response).to have_http_status(:forbidden)
+ end
+
+ it 'deletes the response if the user has a custom role with knowledge base permission' do
+ custom_role = create(:custom_role, account: account, permissions: ['knowledge_base_manage'])
+ AccountUser.find_by!(account: account, user: agent_with_custom_role).update!(custom_role: custom_role)
+
+ expect do
+ delete "/api/v1/accounts/#{account.id}/captain/assistant_responses/#{response_record.id}",
+ headers: agent_with_custom_role.create_new_auth_token,
+ as: :json
end.to change(Captain::AssistantResponse, :count).by(-1)
expect(response).to have_http_status(:no_content)
diff --git a/spec/enterprise/controllers/api/v1/accounts/captain/bulk_actions_controller_spec.rb b/spec/enterprise/controllers/api/v1/accounts/captain/bulk_actions_controller_spec.rb
index da4b8c61c..441b45d9c 100644
--- a/spec/enterprise/controllers/api/v1/accounts/captain/bulk_actions_controller_spec.rb
+++ b/spec/enterprise/controllers/api/v1/accounts/captain/bulk_actions_controller_spec.rb
@@ -30,19 +30,16 @@ RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
}
end
- it 'approves the responses and returns the updated records' do
+ it 'does not approve the responses if the user is an agent without knowledge base permission' do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: valid_params,
headers: agent.create_new_auth_token,
as: :json
- expect(response).to have_http_status(:ok)
- expect(json_response).to be_an(Array)
- expect(json_response.length).to eq(2)
+ expect(response).to have_http_status(:forbidden)
- # Verify responses were approved
pending_responses.each do |response|
- expect(response.reload.status).to eq('approved')
+ expect(response.reload.status).to eq('pending')
end
end
end
@@ -56,20 +53,18 @@ RSpec.describe 'Api::V1::Accounts::Captain::BulkActions', type: :request do
}
end
- it 'deletes the responses and returns an empty array' do
+ it 'does not delete the responses if the user is an agent without knowledge base permission' do
expect do
post "/api/v1/accounts/#{account.id}/captain/bulk_actions",
params: delete_params,
headers: agent.create_new_auth_token,
as: :json
- end.to change(Captain::AssistantResponse, :count).by(-2)
+ end.not_to change(Captain::AssistantResponse, :count)
- expect(response).to have_http_status(:ok)
- expect(json_response).to eq([])
+ expect(response).to have_http_status(:forbidden)
- # Verify responses were deleted
pending_responses.each do |response|
- expect { response.reload }.to raise_error(ActiveRecord::RecordNotFound)
+ expect(response.reload.status).to eq('pending')
end
end
end