instructure/canvas-lms · error
user doesn't have permission to forward these messages
Error message
user doesn't have permission to forward these messages
What it means
After confirming forwarded messages belong to one conversation, the code checks the current user is actually a participant of that conversation via their all_conversations. If not, this error is raised to stop users forwarding conversations they are not in.
Solutions
- Verify current_user is a participant of the source conversation before forwarding
- Pass the correct user (the participant) rather than an admin/other user
- If admin forwarding is required, first check user.all_conversations.exists?(conversation_id: cid) and handle gracefully
Example fix
// before conversation.add_message(admin, 'fyi', forwarded_message_ids: ids) // after raise 'not a participant' unless participant_user.all_conversations.where(conversation_id: source_cid).exists? conversation.add_message(participant_user, 'fyi', forwarded_message_ids: ids)
Defensive patterns
Strategy: validation
Validate before calling
cid = ConversationMessage.find(id).conversation_id raise 'not a participant' unless user.all_conversations.where(conversation_id: cid).exists?
Type guard
null
Try / catch
begin conversation.add_message(user, text, forwarded_message_ids: ids) rescue RuntimeError => e raise unless e.message == "user doesn't have permission to forward these messages" # surface 'you are not a participant of this conversation' to the user end
Prevention
- Always pass the participant user, not an admin, to add_message
- Check participant membership before enabling forward UI actions
- Re-check membership if the user may have left the conversation since page load
When it happens
Trigger: Calling Conversation#add_message with forwarded_message_ids whose (single) conversation the current user has no ConversationParticipant record for — e.g. forwarding someone else's messages, or after being removed from the conversation.
Common situations: Acting on behalf of another user (masquerading or passing the wrong user), stale UI state where the user left/deleted the conversation, or scripts using admin users who are not participants.
Understand the failure class
Background: Permission denied / not authorized / 403 Forbidden: access-control rejections when the caller lacks the required role, grant, or ownership — this error's family across 18 libraries.
Related errors
- can only forward one conversation at a time
- Must be a siteadmin user!
- assessor and assessee required
- association required
- can't accept
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/ddb13fe74b1a7ac8.
Report an issue: GitHub.
Appendix: source
Thrown at app/models/conversation.rb:356
def self.build_message(current_user, body, options = {})
message = ConversationMessage.new
message.author_id = current_user.id
message.body = body
message.generated = options[:generated] || false
message.automated = options[:automated] || false
if options[:root_account_id]
message.context_type = "Account"
message.context_id = options[:root_account_id]
end
message.asset = options[:asset]
message.attachment_ids = options[:attachment_ids] if options[:attachment_ids].present?
message.media_comment = options[:media_comment] if options[:media_comment].present?
if options[:forwarded_message_ids].present?
messages = ConversationMessage.where(id: options[:forwarded_message_ids].map(&:to_i))
conversation_ids = messages.select(&:forwardable?).map(&:conversation_id).uniq
raise "can only forward one conversation at a time" if conversation_ids.size != 1
raise "user doesn't have permission to forward these messages" unless current_user.all_conversations.where(conversation_id: conversation_ids.first).exists?
# TODO: optimize me
message.forwarded_message_ids = messages.map(&:id).join(",")
end
# Grab snapshot hash of user's inbox settings and save to message (If FF is enabled)
if Account.site_admin.feature_enabled?(:inbox_settings)
message.inbox_settings_ooo_hash = Inbox::InboxService.inbox_settings_ooo_hash(user_id: current_user.id, root_account_id: options[:root_account_id])
end
message
end
def preload_users_and_context_codes
users = User.where(id: conversation_participants.map(&:user_id)).pluck(:id, :updated_at).map do |id, updated_at|
User.send(:instantiate, "id" => id, "updated_at" => updated_at)
end
User.preload_conversation_context_codes(users)View on GitHub (pinned to 1c9f0bb801)