{"record":{"id":"7198c3d56594631d","repo":"instructure/canvas-lms","slug":"insufficient-permissions-delete-conversation-messages","errorCode":null,"errorMessage":"Insufficient permissions","messagePattern":"Insufficient permissions","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/mutations/delete_conversation_messages.rb","lineNumber":31,"sourceCode":"# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR\n# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more\n# details.\n#\n# You should have received a copy of the GNU Affero General Public License along\n# with this program. If not, see <http://www.gnu.org/licenses/>.\n#\n\nclass Mutations::DeleteConversationMessages < Mutations::BaseMutation\n  graphql_name \"DeleteConversationMessages\"\n\n  # input arguments\n  argument :ids, [ID], required: true, prepare: GraphQLHelpers.relay_or_legacy_ids_prepare_func(\"ConversationMessage\")\n\n  field :conversation_message_ids, [ID], null: false\n\n  def resolve(input:)\n    if current_user.account.root_account.feature_enabled?(:restrict_student_access)\n      raise GraphQL::ExecutionError, \"Insufficient permissions\"\n    end\n\n    messages = ConversationMessage.preload(:conversation).find(input[:ids])\n    if messages.map(&:conversation).uniq.length > 1\n      raise GraphQL::ExecutionError, \"All ConversationMessages must exist within the same Conversation\"\n    end\n\n    participant_record = current_user.all_conversations.find_by(conversation_id: messages.first.conversation.id)\n    raise GraphQL::ExecutionError, \"Insufficient permissions\" if participant_record.nil?\n\n    participant_record.remove_messages(*messages)\n    context[:deleted_models] = { conversation_messages: {} }\n    messages.each { |message| context[:deleted_models][:conversation_messages][message.id.to_s] = message }\n    { conversation_message_ids: input[:ids] }\n  rescue ActiveRecord::RecordInvalid => e\n    errors_for(e.record)\n  rescue ActiveRecord::RecordNotFound\n    raise GraphQL::ExecutionError, \"Unable to find ConversationMessage\"","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/mutations/delete_conversation_messages.rb#L13-L49","documentation":"DeleteConversationMessages#resolve rejects the entire mutation when the root account has the restrict_student_access feature enabled and (per the check ordering) the caller is not permitted, raising \"Insufficient permissions\". The flag is intended to block students from destructive conversation operations like deleting messages.","triggerScenarios":"A student (or a session without the required role) calling deleteConversationMessages on a root account where restrict_student_access is enabled.","commonSituations":"Institutions that turn on restrict_student_access to lock down student messaging; K-12/self-service accounts; a client that previously worked before the institution enabled the flag; admins testing with a student masquerade off/on incorrectly.","solutions":["Log in / act as a non-student role (teacher, admin) if deletion of messages is required.","If deletion should be allowed, have an admin disable the restrict_student_access feature flag on the root account.","Update the client to hide/disable message deletion UI when the flag is active to avoid the failed call.","Confirm the current_user's root account matches the one where you expect the flag configured."],"exampleFix":"// before\ndeleteConversationMessages(input: { ids }) // student, flag on -> error\n// after\nif (me.isStudent && account.featureFlags.restrict_student_access) {\n  hideDeleteButton()\n} else {\n  deleteConversationMessages(input: { ids })\n}","handlingStrategy":"validation","validationCode":"if (me.isStudent && rootAccount.featureFlags?.restrict_student_access) {\n  throw new SkipError('message deletion blocked by restrict_student_access')\n}","typeGuard":"function canDeleteMessages(me, account) { return !(me.isStudent && account?.featureFlags?.includes('restrict_student_access')); }","tryCatchPattern":"try {\n  await deleteConversationMessages({ ids })\n} catch (e) {\n  if (e.message === 'Insufficient permissions') {\n    disableConversationDeleteUi();\n  } else throw e;\n}","preventionTips":["Gate conversation delete UI on the user's role and the restrict_student_access flag.","Only offer destructive conversation actions to teachers/admins where policy requires.","Document the flag's effect for institutions deploying it."],"tags":["graphql","permissions","feature-flag"],"backgroundTag":"insufficient-permissions","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}