{"record":{"id":"c0b56aea5205937d","repo":"instructure/canvas-lms","slug":"you-do-not-have-permission-to-view-this-course","errorCode":null,"errorMessage":"You do not have permission to view this course.","messagePattern":"You do not have permission to view this course\\.","errorType":"exception","errorClass":"GraphQL::ExecutionError","httpStatus":null,"severity":"error","filePath":"app/graphql/interfaces/discussions_connection_interface.rb","lineNumber":46,"sourceCode":"    argument :search_term, String, <<~MD, required: false\n      only return discussions whose title matches this search term\n    MD\n    argument :is_announcement, Boolean, <<~MD, required: false\n      only return discussions that are announcements (true) or\n      regular discussions (false). If not provided, returns both.\n    MD\n  end\n\n  def discussions_scope(course, user_id = nil, search_term = nil, is_announcement = nil)\n    scoped_user = user_id.nil? ? current_user : User.find_by(id: user_id)\n\n    # If user_id was provided but user not found, return no discussions\n    return DiscussionTopic.none if user_id.present? && scoped_user.nil?\n\n    # Check if current user has permission to view discussions as the scoped user\n    unless current_user.can_current_user_view_as_user(course, scoped_user)\n      # Current user lacks permissions to view as the scoped user\n      raise GraphQL::ExecutionError, \"You do not have permission to view this course.\"\n    end\n\n    discussions = if is_announcement == true\n                    # For announcements, use the same logic as /courses/X/announcements page\n                    # Start with active_announcements scope (just workflow_state <> 'deleted')\n                    course.active_announcements\n                  else\n                    course.discussion_topics.active\n                  end\n\n    # Apply announcement filter for non-announcement-specific queries\n    if is_announcement == false\n      discussions = discussions.where(type: [\"DiscussionTopic\", nil])\n    end\n\n    # Apply search term filter if provided\n    if search_term.present?\n      discussions = discussions.where(DiscussionTopic.wildcard(:title, search_term))","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/graphql/interfaces/discussions_connection_interface.rb#L28-L64","documentation":"discussions_scope in discussions_connection_interface.rb raises this GraphQL::ExecutionError when the current user is not allowed to view discussions on behalf of the requested scoped user. The permission check delegates to current_user.can_current_user_view_as_user(course, scoped_user), which fails unless the viewer is the scoped user or an admin permitted to 'view as' that user in the course. It is a masquerade-style authorization guard, not a general read-permission check.","triggerScenarios":"Querying legacyNode/course.discussionsConnection (discussions_connection) with a user_id argument for a user the caller is not allowed to view as — e.g. a student supplying another student's user_id, or a teacher without 'view as' rights targeting an admin.","commonSituations":"Developers testing the GraphQL API with a hard-coded user_id belonging to a different account; tooling that caches one user's token while querying another user's discussions; admins operating in a sub-account where 'View as' is restricted.","solutions":["Remove the user_id argument (or pass the current user's own id) so the scope targets the caller itself.","Log in as, or use a token for, the user whose discussions you need.","Use an account-level admin token with 'View as' permission over the target user.","Check can_current_user_view_as_user(course, scoped_user) client-side before adding user_id to the query.","If user_id was intentionally passed but the user is missing, note that a nil scoped_user already returns DiscussionTopic.none — only valid user_ids are permission-checked."],"exampleFix":"// before\nquery { course(id: 1) { discussionsConnection(userId: 42) { nodes { id } } } }\n// after\nquery { course(id: 1) { discussionsConnection { nodes { id } } } } // omit userId, or pass your own id","handlingStrategy":"validation","validationCode":"// before sending the query\nconst canViewAs = (course, targetUserId, currentUserId, isAdmin) =>\n  targetUserId === undefined || targetUserId === currentUserId || isAdmin\nif (!canViewAs(course, variables.userId, currentUser.id, currentUser.admin))\n  throw new Error(\"user_id not viewable by current user; omit userId or use admin token\")","typeGuard":"const isOwnScope = (v) => v.userId === undefined || v.userId === currentUser.id","tryCatchPattern":null,"preventionTips":["Only pass user_id when impersonation ('view as') is genuinely intended","Reuse one auth token per user identity in tooling","Check role/admin status before masquerading queries","Strip user_id from shared query templates"],"tags":["graphql","authorization","masquerade","permissions"],"backgroundTag":"permission-denied","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"}