Differential D10986 Diff 26396 src/applications/conduit/controller/PhabricatorConduitAPIController.php
Changeset View
Changeset View
Standalone View
Standalone View
src/applications/conduit/controller/PhabricatorConduitAPIController.php
| Show All 22 Lines | public function processRequest() { | ||||
| $api_request = null; | $api_request = null; | ||||
| $log = new PhabricatorConduitMethodCallLog(); | $log = new PhabricatorConduitMethodCallLog(); | ||||
| $log->setMethod($method); | $log->setMethod($method); | ||||
| $metadata = array(); | $metadata = array(); | ||||
| try { | try { | ||||
| $params = $this->decodeConduitParams($request, $method); | list($metadata, $params) = $this->decodeConduitParams($request, $method); | ||||
| $metadata = idx($params, '__conduit__', array()); | |||||
| unset($params['__conduit__']); | |||||
| $call = new ConduitCall( | $call = new ConduitCall($method, $params); | ||||
| $method, $params, idx($metadata, 'isProxied', false)); | |||||
| $result = null; | $result = null; | ||||
| // TODO: Straighten out the auth pathway here. We shouldn't be creating | // TODO: Straighten out the auth pathway here. We shouldn't be creating | ||||
| // a ConduitAPIRequest at this level, but some of the auth code expects | // a ConduitAPIRequest at this level, but some of the auth code expects | ||||
| // it. Landing a halfway version of this to unblock T945. | // it. Landing a halfway version of this to unblock T945. | ||||
| $api_request = new ConduitAPIRequest($params); | $api_request = new ConduitAPIRequest($params); | ||||
| ▲ Show 20 Lines • Show All 246 Lines • ▼ Show 20 Lines | if ($auth_type === ConduitClient::AUTH_ASYMMETRIC) { | ||||
| return array( | return array( | ||||
| 'ERR-INVALID-AUTH', | 'ERR-INVALID-AUTH', | ||||
| pht( | pht( | ||||
| 'Provided "auth.type" ("%s") is not recognized.', | 'Provided "auth.type" ("%s") is not recognized.', | ||||
| $auth_type), | $auth_type), | ||||
| ); | ); | ||||
| } | } | ||||
| $token_string = idx($metadata, 'token'); | |||||
| if (strlen($token_string)) { | |||||
| if (strlen($token_string) != 32) { | |||||
| return array( | |||||
| 'ERR-INVALID-AUTH', | |||||
| pht( | |||||
| 'API token "%s" has the wrong length. API tokens should be '. | |||||
| '32 characters long.'), | |||||
| ); | |||||
| } | |||||
| $type = head(explode('-', $token_string)); | |||||
| switch ($type) { | |||||
| case 'api': | |||||
| case 'tmp': | |||||
| break; | |||||
| default: | |||||
| return array( | |||||
| 'ERR-INVALID-AUTH', | |||||
| pht( | |||||
| 'API token "%s" has the wrong format. API tokens should begin '. | |||||
| 'with "api-" or "tmp-" and be 32 characters long.', | |||||
| $token_string), | |||||
| ); | |||||
| } | |||||
| $token = id(new PhabricatorConduitTokenQuery()) | |||||
| ->setViewer(PhabricatorUser::getOmnipotentUser()) | |||||
| ->withTokens(array($token_string)) | |||||
| ->withExpired(false) | |||||
| ->executeOne(); | |||||
| if (!$token) { | |||||
| $token = id(new PhabricatorConduitTokenQuery()) | |||||
| ->setViewer(PhabricatorUser::getOmnipotentUser()) | |||||
| ->withTokens(array($token_string)) | |||||
| ->withExpired(true) | |||||
| ->executeOne(); | |||||
| if ($token) { | |||||
| return array( | |||||
| 'ERR-INVALID-AUTH', | |||||
| pht( | |||||
| 'API token "%s" was previously valid, but has expired.', | |||||
| $token_string), | |||||
| ); | |||||
| } else { | |||||
| return array( | |||||
| 'ERR-INVALID-AUTH', | |||||
| pht( | |||||
| 'API token "%s" is not valid.', | |||||
| $token_string), | |||||
| ); | |||||
| } | |||||
| } | |||||
| $user = $token->getObject(); | |||||
| if (!($user instanceof PhabricatorUser)) { | |||||
| return array( | |||||
| 'ERR-INVALID-AUTH', | |||||
| pht( | |||||
| 'API token is not associated with a valid user.'), | |||||
| ); | |||||
| } | |||||
| return $this->validateAuthenticatedUser( | |||||
| $api_request, | |||||
| $user); | |||||
| } | |||||
| // handle oauth | // handle oauth | ||||
| $access_token = $request->getStr('access_token'); | $access_token = idx($metadata, 'access_token'); | ||||
| $method_scope = $metadata['scope']; | $method_scope = idx($metadata, 'scope'); | ||||
| if ($access_token && | if ($access_token && | ||||
| $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) { | $method_scope != PhabricatorOAuthServerScope::SCOPE_NOT_ACCESSIBLE) { | ||||
| $token = id(new PhabricatorOAuthServerAccessToken()) | $token = id(new PhabricatorOAuthServerAccessToken()) | ||||
| ->loadOneWhere('token = %s', | ->loadOneWhere('token = %s', | ||||
| $access_token); | $access_token); | ||||
| if (!$token) { | if (!$token) { | ||||
| return array( | return array( | ||||
| 'ERR-INVALID-AUTH', | 'ERR-INVALID-AUTH', | ||||
| Show All 22 Lines | if ($access_token && | ||||
| 'Access token is for invalid user.', | 'Access token is for invalid user.', | ||||
| ); | ); | ||||
| } | } | ||||
| return $this->validateAuthenticatedUser( | return $this->validateAuthenticatedUser( | ||||
| $api_request, | $api_request, | ||||
| $user); | $user); | ||||
| } | } | ||||
| // Handle sessionless auth. TOOD: This is super messy. | // Handle sessionless auth. | ||||
| // TODO: This is super messy. | |||||
| // TODO: Remove this in favor of token-based auth. | |||||
| if (isset($metadata['authUser'])) { | if (isset($metadata['authUser'])) { | ||||
| $user = id(new PhabricatorUser())->loadOneWhere( | $user = id(new PhabricatorUser())->loadOneWhere( | ||||
| 'userName = %s', | 'userName = %s', | ||||
| $metadata['authUser']); | $metadata['authUser']); | ||||
| if (!$user) { | if (!$user) { | ||||
| return array( | return array( | ||||
| 'ERR-INVALID-AUTH', | 'ERR-INVALID-AUTH', | ||||
| 'Authentication is invalid.', | 'Authentication is invalid.', | ||||
| ); | ); | ||||
| } | } | ||||
| $token = idx($metadata, 'authToken'); | $token = idx($metadata, 'authToken'); | ||||
| $signature = idx($metadata, 'authSignature'); | $signature = idx($metadata, 'authSignature'); | ||||
| $certificate = $user->getConduitCertificate(); | $certificate = $user->getConduitCertificate(); | ||||
| if (sha1($token.$certificate) !== $signature) { | if (sha1($token.$certificate) !== $signature) { | ||||
| return array( | return array( | ||||
| 'ERR-INVALID-AUTH', | 'ERR-INVALID-AUTH', | ||||
| 'Authentication is invalid.', | 'Authentication is invalid.', | ||||
| ); | ); | ||||
| } | } | ||||
| return $this->validateAuthenticatedUser( | return $this->validateAuthenticatedUser( | ||||
| $api_request, | $api_request, | ||||
| $user); | $user); | ||||
| } | } | ||||
| // Handle session-based auth. | |||||
| // TODO: Remove this in favor of token-based auth. | |||||
| $session_key = idx($metadata, 'sessionKey'); | $session_key = idx($metadata, 'sessionKey'); | ||||
| if (!$session_key) { | if (!$session_key) { | ||||
| return array( | return array( | ||||
| 'ERR-INVALID-SESSION', | 'ERR-INVALID-SESSION', | ||||
| 'Session key is not present.', | 'Session key is not present.', | ||||
| ); | ); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 147 Lines • ▼ Show 20 Lines | if ($params !== null) { | ||||
| "The value for parameter '{$key}' is not valid JSON. All ". | "The value for parameter '{$key}' is not valid JSON. All ". | ||||
| "parameters must be encoded as JSON values, including strings ". | "parameters must be encoded as JSON values, including strings ". | ||||
| "(which means you need to surround them in double quotes). ". | "(which means you need to surround them in double quotes). ". | ||||
| "Check your syntax. Value was: {$value}"); | "Check your syntax. Value was: {$value}"); | ||||
| } | } | ||||
| $params[$key] = $decoded_value; | $params[$key] = $decoded_value; | ||||
| } | } | ||||
| return $params; | $metadata = idx($params, '__conduit__', array()); | ||||
| unset($params['__conduit__']); | |||||
| return array($metadata, $params); | |||||
| } | } | ||||
| // Otherwise, look for a single parameter called 'params' which has the | // Otherwise, look for a single parameter called 'params' which has the | ||||
| // entire param dictionary JSON encoded. This is the usual case for remote | // entire param dictionary JSON encoded. | ||||
| // requests. | |||||
| $params_json = $request->getStr('params'); | $params_json = $request->getStr('params'); | ||||
| if (!strlen($params_json)) { | if (strlen($params_json)) { | ||||
| if ($request->getBool('allowEmptyParams')) { | |||||
| // TODO: This is a bit messy, but otherwise you can't call | |||||
| // "conduit.ping" from the web console. | |||||
| $params = array(); | |||||
| } else { | |||||
| throw new Exception( | |||||
| "Request has no 'params' key. This may mean that an extension like ". | |||||
| "Suhosin has dropped data from the request. Check the PHP ". | |||||
| "configuration on your server. If you are developing a Conduit ". | |||||
| "client, you MUST provide a 'params' parameter when making a ". | |||||
| "Conduit request, even if the value is empty (e.g., provide '{}')."); | |||||
| } | |||||
| } else { | |||||
| $params = json_decode($params_json, true); | $params = json_decode($params_json, true); | ||||
| if (!is_array($params)) { | if (!is_array($params)) { | ||||
| throw new Exception( | throw new Exception( | ||||
| "Invalid parameter information was passed to method ". | "Invalid parameter information was passed to method ". | ||||
| "'{$method}', could not decode JSON serialization. Data: ". | "'{$method}', could not decode JSON serialization. Data: ". | ||||
| $params_json); | $params_json); | ||||
| } | } | ||||
| $metadata = idx($params, '__conduit__', array()); | |||||
| unset($params['__conduit__']); | |||||
| return array($metadata, $params); | |||||
| } | |||||
| // If we do not have `params`, assume this is a simple HTTP request with | |||||
| // HTTP key-value pairs. | |||||
| $params = array(); | |||||
| $metadata = array(); | |||||
| foreach ($request->getPassthroughRequestData() as $key => $value) { | |||||
| $meta_key = ConduitAPIMethod::getParameterMetadataKey($key); | |||||
| if ($meta_key !== null) { | |||||
| $metadata[$meta_key] = $value; | |||||
| } else { | |||||
| $params[$key] = $value; | |||||
| } | |||||
| } | } | ||||
| return $params; | return array($metadata, $params); | ||||
| } | } | ||||
| } | } | ||||