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

  1. Verify current_user is a participant of the source conversation before forwarding
  2. Pass the correct user (the participant) rather than an admin/other user
  3. 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

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


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)