instructure/canvas-lms · error · GraphQL::ExecutionError

Insufficient permission

Error message

Insufficient permission

What it means

In MoveOutcomeLinks#get_group!, when the target outcome group has a context (Course or Account), the current user must hold :manage_outcomes there; otherwise this ExecutionError is raised. It is the authorization gate for moving outcome links within a contextual group.

Solutions

  1. Perform the move as a user with manage_outcomes permission on the group's context (typically an admin or outcome manager)
  2. Grant the user the manage_outcomes role in the Course/Account if appropriate
  3. Check which enrollment/role the current user actually has in the target context
Defensive patterns

Strategy: validation

Validate before calling

// check permissions client-side before attempting the move
const perms = await client.query({ query: CONTEXT_PERMS, variables: { contextId, contextType } });
if (!perms.data?.context?.permissions?.manageOutcomes) {
  throw new Error('User lacks manage_outcomes on the group context');
}

Try / catch

try {
  await client.mutate({ mutation: MOVE_OUTCOME_LINKS, variables: { input } });
} catch (e) {
  if (e.message.includes('Insufficient permission')) {
    // hide move actions and show an explanatory message
  }
}

Prevention

When it happens

Trigger: A user without manage_outcomes rights on the group's Course/Account calling moveOutcomeLinks with that group's group_id.

Common situations: Students or teachers (without outcome-management rights) attempting moves; users whose enrollment changed; API tokens of a different user than the UI session expects.

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/4d67d89cf59d52e8. Report an issue: GitHub.

Appendix: source

Thrown at app/graphql/mutations/move_outcome_links.rb:68

    {
      errors:,
      moved_outcome_links: ContentTag.where(id: outcome_links.pluck(:id))
    }
  end

  def self.moved_outcome_link_ids_log_entry(_ids, ctx)
    ctx[:group]
  end

  private

  def get_group!(input)
    LearningOutcomeGroup.active.find_by(id: input[:group_id]).tap do |group|
      raise GraphQL::ExecutionError, I18n.t("Group not found") unless group

      if group.context
        raise GraphQL::ExecutionError, I18n.t("Insufficient permission") unless
          group.context.grants_right?(current_user, session, :manage_outcomes)
      else
        raise GraphQL::ExecutionError, I18n.t("Insufficient permission") unless
          Account.site_admin.grants_right?(current_user, session, :manage_global_outcomes)
      end
    end
  end

  def get_outcome_links(input, context)
    ids = input[:outcome_link_ids].map(&:to_i).uniq
    links = if context
              ContentTag.active.learning_outcome_links.where(
                context:,
                id: ids
              )
            else
              ContentTag.active.learning_outcome_links.where(id: ids, context_type: "LearningOutcomeGroup")
            end

View on GitHub (pinned to 1c9f0bb801)