{"record":{"id":"b925c09d45f4052b","repo":"phacility/phabricator","slug":"expected-s-to-be-s","errorCode":null,"errorMessage":"Expected '%s' to be '%s'!","messagePattern":"Expected '(.+?)' to be '(.+?)'!","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"src/applications/auth/adapter/PhutilOAuth1AuthAdapter.php","lineNumber":150,"sourceCode":"\n    return $future;\n  }\n\n  public function getClientRedirectURI() {\n    $request_token_uri = $this->getRequestTokenURI();\n\n    $future = $this->newOAuth1Future($request_token_uri);\n    if (strlen($this->getCallbackURI())) {\n      $future->setCallbackURI($this->getCallbackURI());\n    }\n\n    list($body) = $future->resolvex();\n    $data = id(new PhutilQueryStringParser())->parseQueryString($body);\n\n    // NOTE: Per the spec, this value MUST be the string 'true'.\n    $confirmed = idx($data, 'oauth_callback_confirmed');\n    if ($confirmed !== 'true') {\n      throw new Exception(\n        pht(\"Expected '%s' to be '%s'!\", 'oauth_callback_confirmed', 'true'));\n    }\n\n    $this->readTokenAndTokenSecret($data);\n\n    $authorize_token_uri = new PhutilURI($this->getAuthorizeTokenURI());\n    $authorize_token_uri->replaceQueryParam('oauth_token', $this->getToken());\n\n    return phutil_string_cast($authorize_token_uri);\n  }\n\n  protected function finishOAuthHandshake() {\n    $this->willFinishOAuthHandshake();\n\n    if (!$this->getToken()) {\n      throw new Exception(pht('Expected token to finish OAuth handshake!'));\n    }\n    if (!$this->getVerifier()) {","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/applications/auth/adapter/PhutilOAuth1AuthAdapter.php#L132-L168","documentation":"Thrown during the OAuth 1.0a request-token step of getAuthenticateURI(): the adapter POSTs to the provider's request token URI, parses the body as an HTTP query string, and requires oauth_callback_confirmed to be exactly the string 'true', as mandated by RFC 5849 section 2.1. If the provider omits the parameter or returns any other value (including a parsed-HTML garbage body where idx() finds nothing), the handshake is aborted before the user is ever redirected to the provider.","triggerScenarios":"Calling getAuthenticateURI() on a PhutilOAuth1AuthAdapter subclass when: the provider implements plain OAuth 1.0 (which has no callback confirmation) instead of 1.0a; the configured request token URI is wrong and returns an HTML error page that parseQueryString() turns into an array without 'oauth_callback_confirmed'; the provider rejects the application and returns a 200 body such as 'oauth_problem=parameter_absent'; or no callback URI was set so the provider never echoes confirmation.","commonSituations":"Misconfigured request-token endpoint URL in a custom provider adapter; a provider that ignores the oauth_callback parameter; an intermediate proxy or login wall returning HTML instead of the token body; migrating a legacy OAuth 1.0 integration to 1.0a.","solutions":["Verify the request token URI returned by getRequestTokenURI() is the provider's OAuth 1.0a endpoint, not a legacy OAuth 1.0 one.","Reproduce the request manually (curl with a signed oauth_callback) and inspect the raw response body to see exactly what the provider returned.","Confirm the provider supports OAuth 1.0a with callbacks; if it only supports OAuth 1.0, the handshake cannot pass this check and needs a different adapter strategy.","Make sure setCallbackURI() was called with a valid callback before getAuthenticateURI() so the provider confirms it."],"exampleFix":"// before: adapter points at a legacy OAuth 1.0 endpoint that never confirms callbacks\nprotected function getRequestTokenURI() {\n  return 'https://provider.tld/oauth/request';\n}\n\n// after: use the OAuth 1.0a endpoint per RFC 5849\nprotected function getRequestTokenURI() {\n  return 'https://provider.tld/oauth1a/request';\n}","handlingStrategy":"try-catch","validationCode":"// Sanity-check provider config before starting the handshake\nif (!strlen($adapter->getRequestTokenURI())) {\n  throw new PhutilAuthConfigurationException('request token URI missing');\n}\nif (!strlen($adapter->getCallbackURI())) {\n  throw new PhutilAuthConfigurationException('callback URI missing');\n}","typeGuard":null,"tryCatchPattern":"try {\n  $uri = $adapter->getAuthenticateURI();\n} catch (Exception $ex) {\n  // Log the provider/endpoint context; the message names the missing param.\n  phlog($ex);\n  return $this->newDialog()->setTitle(pht('Authentication Failed'))\n    ->appendParagraph($ex->getMessage());\n}","preventionTips":["Verify provider endpoints with a manual signed request before shipping an adapter.","Only integrate OAuth 1.0a providers; plain OAuth 1.0 cannot pass the callback confirmation check.","Always set a concrete callback URI on the adapter before getAuthenticateURI()."],"tags":["oauth1","authentication","handshake","third-party-provider","php"],"backgroundTag":"oauth1-handshake-failed","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}