From 511898775788a51ef54cc610625375b6249266c5 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 15 Dec 2014 11:12:38 -0800 Subject: [PATCH] Support simpler, token-based Conduit authentication in Arcanist Summary: Ref T5955. If the server supports token-based authentication, prefer it over certificate-based authentication. Also fixes T3117. Test Plan: - Used `arc install-certificate` to install credentials from both token-based and certificate-based hosts. - Used `arc list` with a token-based host. Reviewers: btrahan Reviewed By: btrahan Subscribers: epriestley Maniphest Tasks: T3117, T2878, T5955 Differential Revision: https://secure.phabricator.com/D10988 --- scripts/arcanist.php | 19 +-- .../ArcanistInstallCertificateWorkflow.php | 108 ++++++++++++++---- src/workflow/ArcanistWorkflow.php | 28 ++++- 3 files changed, 123 insertions(+), 32 deletions(-) diff --git a/scripts/arcanist.php b/scripts/arcanist.php index 8714bcd1..94019395 100755 --- a/scripts/arcanist.php +++ b/scripts/arcanist.php @@ -275,29 +275,34 @@ try { $host_config = idx($hosts_config, $conduit_uri, array()); $user_name = idx($host_config, 'user'); $certificate = idx($host_config, 'cert'); + $conduit_token = idx($host_config, 'token'); $description = implode(' ', $original_argv); $credentials = array( - 'user' => $user_name, + 'user' => $user_name, 'certificate' => $certificate, 'description' => $description, + 'token' => $conduit_token, ); $workflow->setConduitCredentials($credentials); if ($need_auth) { - if (!$user_name || !$certificate) { + if ((!$user_name || !$certificate) && (!$conduit_token)) { $arc = 'arc'; if ($force_conduit) { $arc .= csprintf(' --conduit-uri=%s', $conduit_uri); } + $conduit_domain = id(new PhutilURI($conduit_uri))->getDomain(); + throw new ArcanistUsageException( phutil_console_format( - "YOU NEED TO __INSTALL A CERTIFICATE__ TO LOGIN TO PHABRICATOR\n\n". - "You are trying to connect to '{$conduit_uri}' but do not have ". - "a certificate installed for this host. Run:\n\n". - " $ **{$arc} install-certificate**\n\n". - "...to install one.")); + "YOU NEED TO AUTHENTICATE TO CONTINUE\n\n". + "You are trying to connect to a server ({$conduit_domain}) that you ". + "do not have any credentials stored for.\n\n". + "To retrieve and store credentials for this server, ". + "**run this command:**\n\n". + " $ **{$arc} install-certificate**\n")); } $workflow->authenticateConduit(); } diff --git a/src/workflow/ArcanistInstallCertificateWorkflow.php b/src/workflow/ArcanistInstallCertificateWorkflow.php index a2ed4879..7a1da36c 100644 --- a/src/workflow/ArcanistInstallCertificateWorkflow.php +++ b/src/workflow/ArcanistInstallCertificateWorkflow.php @@ -48,66 +48,126 @@ EOTEXT } public function run() { + $console = PhutilConsole::getConsole(); + $uri = $this->determineConduitURI(); $this->setConduitURI($uri); $configuration_manager = $this->getConfigurationManager(); - echo "Installing certificate for '{$uri}'...\n"; - $config = $configuration_manager->readUserConfigurationFile(); - echo "Trying to connect to server...\n"; + $console->writeOut( + "%s\n", + pht('Trying to connect to server...')); + $conduit = $this->establishConduit()->getConduit(); try { $conduit->callMethodSynchronous('conduit.ping', array()); } catch (Exception $ex) { throw new ArcanistUsageException( - 'Failed to connect to server: '.$ex->getMessage()); + pht( + 'Failed to connect to server (%s): %s', + $uri, + $ex->getMessage())); } - echo "Connection OK!\n"; $token_uri = new PhutilURI($uri); $token_uri->setPath('/conduit/token/'); - echo "\n"; + // Check if this server supports the more modern token-based login. + $is_token_auth = false; + try { + $capabilities = $conduit->callMethodSynchronous( + 'conduit.getcapabilities', + array()); + $auth = idx($capabilities, 'authentication', array()); + if (in_array('token', $auth)) { + $token_uri->setPath('/conduit/login/'); + $is_token_auth = true; + } + } catch (Exception $ex) { + // Ignore. + } + echo phutil_console_format("**LOGIN TO PHABRICATOR**\n"); echo "Open this page in your browser and login to Phabricator if ". "necessary:\n"; echo "\n"; echo " {$token_uri}\n"; echo "\n"; - echo 'Then paste the token on that page below.'; + echo 'Then paste the API Token on that page below.'; do { - $token = phutil_console_prompt('Paste token from that page:'); + $token = phutil_console_prompt('Paste API Token from that page:'); $token = trim($token); if (strlen($token)) { break; } } while (true); - echo "\n"; - echo "Downloading authentication certificate...\n"; - $info = $conduit->callMethodSynchronous( - 'conduit.getcertificate', - array( - 'token' => $token, - 'host' => $uri, - )); + if ($is_token_auth) { + if (strlen($token) != 32) { + throw new ArcanistUsageException( + pht( + 'The token "%s" is not formatted correctly. API tokens should '. + 'be 32 characters long. Make sure you visited the correct URI '. + 'and copy/pasted the token correctly.', + $token)); + } - $user = $info['username']; - echo "Installing certificate for '{$user}'...\n"; - $config['hosts'][$uri] = array( - 'user' => $user, - 'cert' => $info['certificate'], - ); + if (strncmp($token, 'cli-', 4) !== 0) { + throw new ArcanistUsageException( + pht( + 'The token "%s" is not formatted correctly. Valid API tokens '. + 'should begin "cli-" and be 32 characters long. Make sure you '. + 'visited the correct URI and copy/pasted the token correctly.', + $token)); + } + + $conduit->setConduitToken($token); + try { + $conduit->callMethodSynchronous('user.whoami', array()); + } catch (Exception $ex) { + throw new ArcanistUsageException( + pht( + 'The token "%s" is not a valid API Token. The server returned '. + 'this response when trying to use it as a token: %s', + $token, + $ex->getMessage())); + } + + $config['hosts'][$uri] = array( + 'token' => $token, + ); + } else { + echo "\n"; + echo "Downloading authentication certificate...\n"; + $info = $conduit->callMethodSynchronous( + 'conduit.getcertificate', + array( + 'token' => $token, + 'host' => $uri, + )); + + $user = $info['username']; + echo "Installing certificate for '{$user}'...\n"; + $config['hosts'][$uri] = array( + 'user' => $user, + 'cert' => $info['certificate'], + ); + } echo "Writing ~/.arcrc...\n"; $configuration_manager->writeUserConfigurationFile($config); - echo phutil_console_format( - "** SUCCESS! ** Certificate installed.\n"); + if ($is_token_auth) { + echo phutil_console_format( + "** SUCCESS! ** API Token installed.\n"); + } else { + echo phutil_console_format( + "** SUCCESS! ** Certificate installed.\n"); + } return 0; } diff --git a/src/workflow/ArcanistWorkflow.php b/src/workflow/ArcanistWorkflow.php index 4bfcbd84..194d9211 100644 --- a/src/workflow/ArcanistWorkflow.php +++ b/src/workflow/ArcanistWorkflow.php @@ -324,6 +324,31 @@ abstract class ArcanistWorkflow extends Phobject { 'authenticating conduit!'); } + // If we have `token`, this server supports the simpler, new-style + // token-based authentication. Use that instead of all the certificate + // stuff. + if (isset($credentials['token'])) { + $conduit = $this->getConduit(); + + $conduit->setConduitToken($credentials['token']); + + try { + $result = $this->getConduit()->callMethodSynchronous( + 'user.whoami', + array()); + + $this->userName = $result['userName']; + $this->userPHID = $result['phid']; + + $this->conduitAuthenticated = true; + + return; + } catch (Exception $ex) { + $conduit->setConduitToken(null); + throw $ex; + } + } + if (empty($credentials['user'])) { throw new ConduitClientException( 'ERR-INVALID-USER', @@ -351,7 +376,8 @@ abstract class ArcanistWorkflow extends Phobject { )); } catch (ConduitClientException $ex) { if ($ex->getErrorCode() == 'ERR-NO-CERTIFICATE' || - $ex->getErrorCode() == 'ERR-INVALID-USER') { + $ex->getErrorCode() == 'ERR-INVALID-USER' || + $ex->getErrorCode() == 'ERR-INVALID-AUTH') { $conduit_uri = $this->conduitURI; $message = "\n".