chore(refactor): Improve conversation permission filtering (#11166)

1. Add permission filter service to separate permission filtering logic
from conversation queries
2. Implement hierarchical permissions with cleaner logic:
   - conversation_manage gives access to all conversations
- conversation_unassigned_manage gives access to unassigned and user's
conversations
- conversation_participating_manage gives access only to user's
conversations

---------

Co-authored-by: Pranav <pranav@chatwoot.com>
This commit is contained in:
Sojan Jose
2025-03-31 19:30:02 -07:00
committed by GitHub
parent f20a18b03f
commit ca83a27e95
12 changed files with 759 additions and 260 deletions

View File

@@ -1,17 +1,21 @@
class Api::V1::Accounts::Contacts::ConversationsController < Api::V1::Accounts::Contacts::BaseController
def index
@conversations = Current.account.conversations.includes(
# Start with all conversations for this contact
conversations = Current.account.conversations.includes(
:assignee, :contact, :inbox, :taggings
).where(inbox_id: inbox_ids, contact_id: @contact.id).order(last_activity_at: :desc).limit(20)
end
).where(contact_id: @contact.id)
private
# Apply permission-based filtering using the existing service
conversations = Conversations::PermissionFilterService.new(
conversations,
Current.user,
Current.account
).perform
def inbox_ids
if Current.user.administrator? || Current.user.agent?
Current.user.assigned_inboxes.pluck(:id)
else
[]
end
# Only allow conversations from inboxes the user has access to
inbox_ids = Current.user.assigned_inboxes.pluck(:id)
conversations = conversations.where(inbox_id: inbox_ids)
@conversations = conversations.order(last_activity_at: :desc).limit(20)
end
end

View File

@@ -48,7 +48,7 @@ class Api::V1::Accounts::ConversationsController < Api::V1::Accounts::BaseContro
end
def filter
result = ::Conversations::FilterService.new(params.permit!, current_user).perform
result = ::Conversations::FilterService.new(params.permit!, current_user, current_account).perform
@conversations = result[:conversations]
@conversations_count = result[:count]
rescue CustomExceptions::CustomFilter::InvalidAttribute,

View File

@@ -93,6 +93,12 @@ class ConversationFinder
def find_all_conversations
find_conversation_by_inbox
# Apply permission-based filtering
@conversations = Conversations::PermissionFilterService.new(
@conversations,
current_user,
current_account
).perform
filter_by_conversation_type if params[:conversation_type]
@conversations
end

View File

@@ -1,8 +1,8 @@
class Conversations::FilterService < FilterService
ATTRIBUTE_MODEL = 'conversation_attribute'.freeze
def initialize(params, user, filter_account = nil)
@account = filter_account || Current.account
def initialize(params, user, account)
@account = account
super(params, user)
end
@@ -24,9 +24,25 @@ class Conversations::FilterService < FilterService
end
def base_relation
@account.conversations.includes(
conversations = @account.conversations.includes(
:taggings, :inbox, { assignee: { avatar_attachment: [:blob] } }, { contact: { avatar_attachment: [:blob] } }, :team, :messages, :contact_inbox
)
account_user = @account.account_users.find_by(user_id: @user.id)
is_administrator = account_user&.role == 'administrator'
# Ensure we only include conversations from inboxes the user has access to
unless is_administrator
inbox_ids = @user.inboxes.where(account_id: @account.id).pluck(:id)
conversations = conversations.where(inbox_id: inbox_ids)
end
# Apply permission-based filtering
Conversations::PermissionFilterService.new(
conversations,
@user,
@account
).perform
end
def current_page

View File

@@ -0,0 +1,17 @@
class Conversations::PermissionFilterService
attr_reader :conversations, :user, :account
def initialize(conversations, user, account)
@conversations = conversations
@user = user
@account = account
end
def perform
# The base implementation simply returns all conversations
# Enterprise edition extends this with permission-based filtering
conversations
end
end
Conversations::PermissionFilterService.prepend_mod_with('Conversations::PermissionFilterService')