diff --git a/resources/sql/patches/045.conduittoken.sql b/resources/sql/patches/045.conduittoken.sql new file mode 100644 index 0000000000..f14071c66f --- /dev/null +++ b/resources/sql/patches/045.conduittoken.sql @@ -0,0 +1,12 @@ +CREATE TABLE phabricator_conduit.conduit_certificatetoken ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + userPHID VARCHAR(64) BINARY NOT NULL, + token VARCHAR(64), + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, +); + +ALTER TABLE phabricator_conduit.conduit_certificatetoken + ADD UNIQUE KEY (userPHID); +ALTER TABLE phabricator_conduit.conduit_certificatetoken + ADD UNIQUE KEY (token); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 499d0f108d..e42980f2c9 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -82,6 +82,7 @@ phutil_register_library_map(array( 'ConduitAPIMethod' => 'applications/conduit/method/base', 'ConduitAPIRequest' => 'applications/conduit/protocol/request', 'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect', + 'ConduitAPI_conduit_getcertificate_Method' => 'applications/conduit/method/conduit/getcertificate', 'ConduitAPI_conduit_ping_Method' => 'applications/conduit/method/conduit/ping', 'ConduitAPI_daemon_launched_Method' => 'applications/conduit/method/daemon/launched', 'ConduitAPI_daemon_log_Method' => 'applications/conduit/method/daemon/log', @@ -279,12 +280,14 @@ phutil_register_library_map(array( 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAuthController' => 'applications/auth/controller/base', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', + 'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/token', 'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog', 'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console', 'PhabricatorConduitController' => 'applications/conduit/controller/base', 'PhabricatorConduitDAO' => 'applications/conduit/storage/base', 'PhabricatorConduitLogController' => 'applications/conduit/controller/log', 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog', + 'PhabricatorConduitTokenController' => 'applications/conduit/controller/token', 'PhabricatorController' => 'applications/base/controller/base', 'PhabricatorCountdownController' => 'applications/countdown/controller/base', 'PhabricatorCountdownDAO' => 'applications/countdown/storage/base', @@ -619,6 +622,7 @@ phutil_register_library_map(array( 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', + 'ConduitAPI_conduit_getcertificate_Method' => 'ConduitAPIMethod', 'ConduitAPI_conduit_ping_Method' => 'ConduitAPIMethod', 'ConduitAPI_daemon_launched_Method' => 'ConduitAPIMethod', 'ConduitAPI_daemon_log_Method' => 'ConduitAPIMethod', @@ -756,12 +760,14 @@ phutil_register_library_map(array( 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', + 'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', + 'PhabricatorConduitTokenController' => 'PhabricatorConduitController', 'PhabricatorController' => 'AphrontController', 'PhabricatorCountdownController' => 'PhabricatorController', 'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO', diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index cfae0b58db..945ca3cf34 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -84,10 +84,10 @@ class AphrontDefaultApplicationConfiguration '$' => 'PhabricatorConduitConsoleController', 'method/(?P[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', + 'token/$' => 'PhabricatorConduitTokenController', ), '/api/(?P[^/]+)$' => 'PhabricatorConduitAPIController', - '/D(?P\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', diff --git a/src/applications/conduit/controller/token/PhabricatorConduitTokenController.php b/src/applications/conduit/controller/token/PhabricatorConduitTokenController.php new file mode 100644 index 0000000000..4fe6a55942 --- /dev/null +++ b/src/applications/conduit/controller/token/PhabricatorConduitTokenController.php @@ -0,0 +1,58 @@ +getRequest()->getUser(); + + $old_token = id(new PhabricatorConduitCertificateToken()) + ->loadOneWhere( + 'userPHID = %s', + $user->getPHID()); + if ($old_token) { + $old_token->delete(); + } + + $token = id(new PhabricatorConduitCertificateToken()) + ->setUserPHID($user->getPHID()) + ->setToken(sha1(Filesystem::readRandomBytes(128))) + ->save(); + + $panel = new AphrontPanelView(); + $panel->setHeader('Certificate Install Token'); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + + $panel->appendChild( + '

Copy and paste this token into '. + 'the prompt given to you by "arc install-certificate":

'. + '

'. + ''.phutil_escape_html($token->getToken()).''. + '

'. + '

arc will then complete the '. + 'install process for you.

'); + + + return $this->buildStandardPageResponse( + $panel, + array( + 'title' => 'Certificate Install Token', + )); + } +} diff --git a/src/applications/conduit/controller/token/__init__.php b/src/applications/conduit/controller/token/__init__.php new file mode 100644 index 0000000000..1b1c245749 --- /dev/null +++ b/src/applications/conduit/controller/token/__init__.php @@ -0,0 +1,18 @@ + 'required string', + ); + } + + public function defineReturnType() { + return 'dict'; + } + + public function defineErrorTypes() { + return array( + "ERR-BAD-TOKEN" => "Token does not exist or has expired.", + "ERR-RATE-LIMIT" => + "You have made too many invalid token requests recently. Wait before ". + "making more.", + ); + } + + protected function execute(ConduitAPIRequest $request) { + + $failed_attempts = PhabricatorUserLog::loadRecentEventsFromThisIP( + PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE_FAILURE, + 60 * 5); + + if (count($failed_attempts) > 5) { + $this->logFailure(); + throw new ConduitException('ERR-RATE-LIMIT'); + } + + $token = $request->getValue('token'); + $info = id(new PhabricatorConduitCertificateToken())->loadOneWhere( + 'token = %s', + trim($token)); + + if (!$info || $info->getDateCreated() < time() - (60 * 15)) { + $this->logFailure(); + throw new ConduitException('ERR-BAD-TOKEN'); + } else { + $log = id(new PhabricatorUserLog()) + ->setActorPHID($info->getUserPHID()) + ->setUserPHID($info->getUserPHID()) + ->setAction(PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE) + ->save(); + } + + $user = id(new PhabricatorUser())->loadOneWhere( + 'phid = %s', + $info->getUserPHID()); + if (!$user) { + throw new Exception("Certificate token points to an invalid user!"); + } + + return array( + 'username' => $user->getUserName(), + 'certificate' => $user->getConduitCertificate(), + ); + } + + private function logFailure() { + $log = id(new PhabricatorUserLog()) + ->setUserPHID('-') + ->setAction(PhabricatorUserLog::ACTION_CONDUIT_CERTIFICATE_FAILURE) + ->save(); + } + +} diff --git a/src/applications/conduit/method/conduit/getcertificate/__init__.php b/src/applications/conduit/method/conduit/getcertificate/__init__.php new file mode 100644 index 0000000000..5fa1e5e96c --- /dev/null +++ b/src/applications/conduit/method/conduit/getcertificate/__init__.php @@ -0,0 +1,18 @@ +loadAllWhere( + 'action = %s AND remoteAddr = %s AND dateCreated > %d + ORDER BY dateCreated DESC', + $action, + idx($_SERVER, 'REMOTE_ADDR'), + time() - $timespan); + } + public function save() { if (!$this->remoteAddr) { $this->remoteAddr = idx($_SERVER, 'REMOTE_ADDR');