{"record":{"id":"e415d77dac7ac1b9","repo":"instructure/canvas-lms","slug":"s-service-split-last-split-last-request-method-not-included","errorCode":null,"errorMessage":"#{s.service.split(\":\").last.split(\"#\").last}.#{request.method} not included in ToolProxy security Contract","messagePattern":"#(.+?)\\.#(.+?) not included in ToolProxy security Contract","errorType":"exception","errorClass":"Lti::OAuth2::InvalidTokenError","httpStatus":401,"severity":"error","filePath":"app/controllers/lti/ims/access_token_helper.rb","lineNumber":70,"sourceCode":"\n  def tool_proxy\n    @_tool_proxy ||= Lti::ToolProxy.find_by(guid: access_token.sub)\n  end\n\n  def validate_services!(tool_proxy)\n    ims_tp = ::IMS::LTI::Models::ToolProxy.from_json(tool_proxy.raw_data)\n    service_names = [*lti2_service_name]\n    service = ims_tp.security_contract.tool_services.find(\n      lambda do\n        raise Lti::OAuth2::InvalidTokenError,\n              \"The ToolProxy security contract doesn't include #{service_names.join(\", or \")}\"\n      end\n    ) do |s|\n      service_names.include? s.service.split(\":\").last.split(\"#\").last\n    end\n    unless service.actions.map(&:downcase).include? request.method.downcase\n      msg = \"#{s.service.split(\":\").last.split(\"#\").last}.#{request.method} not included in ToolProxy security Contract\"\n      raise Lti::OAuth2::InvalidTokenError, msg\n    end\n  end\n\n  def developer_key\n    @_developer_key ||= access_token && begin\n      tp = Lti::ToolProxy.find_by(guid: access_token.sub)\n      if tp.present?\n        raise Lti::OAuth2::InvalidTokenError, \"Tool Proxy is not active\" if tp.workflow_state != \"active\"\n\n        validate_services!(tp)\n        tp.product_family.developer_key\n      else\n        DeveloperKey.find_cached(access_token.sub)\n      end\n    rescue ActiveRecord::RecordNotFound\n      nil\n    end\n  end","sourceCodeStart":52,"sourceCodeEnd":88,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/controllers/lti/ims/access_token_helper.rb#L52-L88","documentation":"Guard in Lti::IMS::AccessTokenHelper#validate_services!: after finding a matching tool service in the ToolProxy's security contract, the service's allowed actions don't include the current HTTP method, so the request (e.g. a GET to a POST-only service) is rejected with Lti::OAuth2::InvalidTokenError.","triggerScenarios":"Thrown at app/controllers/lti/ims/access_token_helper.rb:70 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Use the HTTP method the ToolProxy declares for that service action","Update the ToolProxy's security contract to include the needed action"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"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"}