'required string', 'clientVersion' => 'required int', 'clientDescription' => 'optional string', 'user' => 'optional string', 'authToken' => 'optional int', 'authSignature' => 'optional string', 'host' => 'deprecated', ); } public function defineReturnType() { return 'dict'; } public function defineErrorTypes() { return array( 'ERR-BAD-VERSION' => 'Client/server version mismatch. Upgrade your server or downgrade '. 'your client.', 'NEW-ARC-VERSION' => 'Client/server version mismatch. Upgrade your client.', 'ERR-UNKNOWN-CLIENT' => 'Client is unknown.', 'ERR-INVALID-USER' => 'The username you are attempting to authenticate with is not valid.', 'ERR-INVALID-CERTIFICATE' => 'Your authentication certificate for this server is invalid.', 'ERR-INVALID-TOKEN' => "The challenge token you are authenticating with is outside of the ". "allowed time range. Either your system clock is out of whack or ". "you're executing a replay attack.", 'ERR-NO-CERTIFICATE' => 'This server requires authentication.', ); } protected function execute(ConduitAPIRequest $request) { $client = $request->getValue('client'); $client_version = (int)$request->getValue('clientVersion'); $client_description = (string)$request->getValue('clientDescription'); $client_description = id(new PhutilUTF8StringTruncator()) ->setMaximumCodepoints(255) ->truncateString($client_description); $username = (string)$request->getValue('user'); // Log the connection, regardless of the outcome of checks below. $connection = new PhabricatorConduitConnectionLog(); $connection->setClient($client); $connection->setClientVersion($client_version); $connection->setClientDescription($client_description); $connection->setUsername($username); $connection->save(); switch ($client) { case 'arc': $server_version = 6; $supported_versions = array( $server_version => true, // Client version 5 introduced "user.query" call 4 => true, // Client version 6 introduced "diffusion.getlintmessages" call 5 => true, ); if (empty($supported_versions[$client_version])) { if ($server_version < $client_version) { $ex = new ConduitException('ERR-BAD-VERSION'); $ex->setErrorDescription( "Your 'arc' client version is '{$client_version}', which ". "is newer than the server version, '{$server_version}'. ". "Upgrade your Phabricator install."); } else { $ex = new ConduitException('NEW-ARC-VERSION'); $ex->setErrorDescription( "A new version of arc is available! You need to upgrade ". "to connect to this server (you are running version ". "{$client_version}, the server is running version ". "{$server_version})."); } throw $ex; } break; default: // Allow new clients by default. break; } $token = $request->getValue('authToken'); $signature = $request->getValue('authSignature'); $user = id(new PhabricatorUser())->loadOneWhere('username = %s', $username); if (!$user) { throw new ConduitException('ERR-INVALID-USER'); } $session_key = null; if ($token && $signature) { $threshold = 60 * 15; $now = time(); if (abs($token - $now) > $threshold) { throw id(new ConduitException('ERR-INVALID-TOKEN')) ->setErrorDescription( pht( 'The request you submitted is signed with a timestamp, but that '. 'timestamp is not within %s of the current time. The '. 'signed timestamp is %s (%s), and the current server time is '. '%s (%s). This is a difference of %s seconds, but the '. 'timestamp must differ from the server time by no more than '. '%s seconds. Your client or server clock may not be set '. 'correctly.', phutil_format_relative_time($threshold), $token, date('r', $token), $now, date('r', $now), ($token - $now), $threshold)); } $valid = sha1($token.$user->getConduitCertificate()); if ($valid != $signature) { throw new ConduitException('ERR-INVALID-CERTIFICATE'); } $session_key = id(new PhabricatorAuthSessionEngine())->establishSession( PhabricatorAuthSession::TYPE_CONDUIT, $user->getPHID(), $partial = false); } else { throw new ConduitException('ERR-NO-CERTIFICATE'); } return array( 'connectionID' => $connection->getID(), 'sessionKey' => $session_key, 'userPHID' => $user->getPHID(), ); } }