public function loadPage()
 {
     $table = new PhabricatorConduitMethodCallLog();
     $conn_r = $table->establishConnection('r');
     $data = queryfx_all($conn_r, 'SELECT * FROM %T %Q %Q %Q', $table->getTableName(), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r));
     return $table->loadAllFromArray($data);
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $conn_table = new PhabricatorConduitConnectionLog();
     $call_table = new PhabricatorConduitMethodCallLog();
     $conn_r = $call_table->establishConnection('r');
     $pager = new AphrontPagerView();
     $pager->setOffset($request->getInt('page'));
     $calls = $call_table->loadAllWhere('1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1);
     $calls = $pager->sliceResults($calls);
     $pager->setURI(new PhutilURI('/conduit/log/'), 'page');
     $pager->setEnableKeyboardShortcuts(true);
     $min = $pager->getOffset() + 1;
     $max = $min + count($calls) - 1;
     $conn_ids = array_filter(mpull($calls, 'getConnectionID'));
     $conns = array();
     if ($conn_ids) {
         $conns = $conn_table->loadAllWhere('id IN (%Ld)', $conn_ids);
     }
     $table = $this->renderCallTable($calls, $conns);
     $panel = new AphrontPanelView();
     $panel->setHeader('Conduit Method Calls (' . $min . '-' . $max . ')');
     $panel->appendChild($table);
     $panel->appendChild($pager);
     $this->setShowSideNav(false);
     return $this->buildStandardPageResponse($panel, array('title' => 'Conduit Logs'));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $conn_table = new PhabricatorConduitConnectionLog();
     $call_table = new PhabricatorConduitMethodCallLog();
     $conn_r = $call_table->establishConnection('r');
     $pager = new AphrontCursorPagerView();
     $pager->readFromRequest($request);
     $pager->setPageSize(500);
     $query = id(new PhabricatorConduitLogQuery())->setViewer($viewer);
     $methods = $request->getStrList('methods');
     if ($methods) {
         $query->withMethods($methods);
     }
     $calls = $query->executeWithCursorPager($pager);
     $conn_ids = array_filter(mpull($calls, 'getConnectionID'));
     $conns = array();
     if ($conn_ids) {
         $conns = $conn_table->loadAllWhere('id IN (%Ld)', $conn_ids);
     }
     $table = $this->renderCallTable($calls, $conns);
     $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Call Logs'))->appendChild($table);
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Call Logs'));
     return $this->buildApplicationPage(array($crumbs, $box, $pager), array('title' => pht('Conduit Logs')));
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $nav = new AphrontSideNavView();
     $links = array('calls' => 'All Calls');
     if (empty($links[$this->view])) {
         $this->view = key($links);
     }
     foreach ($links as $slug => $name) {
         $nav->addNavItem(phutil_render_tag('a', array('href' => '/conduit/log/view/' . $slug . '/', 'class' => $slug == $this->view ? 'aphront-side-nav-selected' : null), phutil_escape_html($name)));
     }
     $conn_table = new PhabricatorConduitConnectionLog();
     $call_table = new PhabricatorConduitMethodCallLog();
     $conn_r = $call_table->establishConnection('r');
     $pager = new AphrontPagerView();
     $pager->setOffset($request->getInt('page'));
     $calls = $call_table->loadAllWhere('1 = 1 ORDER BY id DESC LIMIT %d, %d', $pager->getOffset(), $pager->getPageSize() + 1);
     $calls = $pager->sliceResults($calls);
     $pager->setURI(new PhutilURI('/conduit/log/view/' . $this->view . '/'), 'page');
     $pager->setEnableKeyboardShortcuts(true);
     $min = $pager->getOffset() + 1;
     $max = $min + count($calls) - 1;
     $conn_ids = array_filter(mpull($calls, 'getConnectionID'));
     $conns = array();
     if ($conn_ids) {
         $conns = $conn_table->loadAllWhere('id IN (%Ld)', $conn_ids);
     }
     $table = $this->renderCallTable($calls, $conns);
     $panel = new AphrontPanelView();
     $panel->setHeader('Conduit Method Calls (' . $min . '-' . $max . ')');
     $panel->appendChild($table);
     $panel->appendChild($pager);
     $nav->appendChild($panel);
     return $this->buildStandardPageResponse($nav, array('title' => 'Conduit Logs', 'tab' => 'logs'));
 }
 protected function collectGarbage()
 {
     $table = new PhabricatorConduitMethodCallLog();
     $conn_w = $table->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE dateCreated < %d
     ORDER BY dateCreated ASC LIMIT 100', $table->getTableName(), $this->getGarbageEpoch());
     return $conn_w->getAffectedRows() == 100;
 }
 public function collectGarbage()
 {
     $key = 'gcdaemon.ttl.conduit-logs';
     $ttl = PhabricatorEnv::getEnvConfig($key);
     if ($ttl <= 0) {
         return false;
     }
     $table = new PhabricatorConduitMethodCallLog();
     $conn_w = $table->establishConnection('w');
     queryfx($conn_w, 'DELETE FROM %T WHERE dateCreated < %d
     ORDER BY dateCreated ASC LIMIT 100', $table->getTableName(), time() - $ttl);
     return $conn_w->getAffectedRows() == 100;
 }
 protected function executeChecks()
 {
     $methods = id(new PhabricatorConduitMethodQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withIsDeprecated(true)->execute();
     if (!$methods) {
         return;
     }
     $methods = mpull($methods, null, 'getAPIMethodName');
     $method_names = mpull($methods, 'getAPIMethodName');
     $table = new PhabricatorConduitMethodCallLog();
     $conn_r = $table->establishConnection('r');
     $calls = queryfx_all($conn_r, 'SELECT DISTINCT method FROM %T WHERE dateCreated > %d
     AND method IN (%Ls)', $table->getTableName(), time() - 60 * 60 * 24 * 30, $method_names);
     $calls = ipull($calls, 'method', 'method');
     foreach ($calls as $method_name) {
         $method = $methods[$method_name];
         $summary = pht('Deprecated Conduit method `%s` was called in the last 30 days. ' . 'You should migrate away from use of this method: it will be ' . 'removed in a future version of Phabricator.', $method_name);
         $uri = PhabricatorEnv::getURI('/conduit/log/?methods=' . $method_name);
         $description = $method->getMethodStatusDescription();
         $message = pht('Deprecated Conduit method %s was called in the last 30 days. ' . 'You should migrate away from use of this method: it will be ' . 'removed in a future version of Phabricator.' . "\n\n" . "%s: %s" . "\n\n" . 'If you have already migrated all callers away from this method, ' . 'you can safely ignore this setup issue.', phutil_tag('tt', array(), $method_name), phutil_tag('tt', array(), $method_name), $description);
         $this->newIssue('conduit.deprecated.' . $method_name)->setShortName(pht('Deprecated Conduit Method'))->setName(pht('Deprecated Conduit Method "%s" In Use', $method_name))->setSummary($summary)->setMessage($message)->addLink($uri, pht('View Method Call Logs'));
     }
 }
 public function processRequest()
 {
     $time_start = microtime(true);
     $request = $this->getRequest();
     $method = $this->method;
     $api_request = null;
     $log = new PhabricatorConduitMethodCallLog();
     $log->setMethod($method);
     $metadata = array();
     try {
         $params = $this->decodeConduitParams($request, $method);
         $metadata = idx($params, '__conduit__', array());
         unset($params['__conduit__']);
         $call = new ConduitCall($method, $params);
         $result = null;
         // TODO: Straighten out the auth pathway here. We shouldn't be creating
         // a ConduitAPIRequest at this level, but some of the auth code expects
         // it. Landing a halfway version of this to unblock T945.
         $api_request = new ConduitAPIRequest($params);
         $allow_unguarded_writes = false;
         $auth_error = null;
         $conduit_username = '******';
         if ($call->shouldRequireAuthentication()) {
             $metadata['scope'] = $call->getRequiredScope();
             $auth_error = $this->authenticateUser($api_request, $metadata);
             // If we've explicitly authenticated the user here and either done
             // CSRF validation or are using a non-web authentication mechanism.
             $allow_unguarded_writes = true;
             if (isset($metadata['actAsUser'])) {
                 $this->actAsUser($api_request, $metadata['actAsUser']);
             }
             if ($auth_error === null) {
                 $conduit_user = $api_request->getUser();
                 if ($conduit_user && $conduit_user->getPHID()) {
                     $conduit_username = $conduit_user->getUsername();
                 }
                 $call->setUser($api_request->getUser());
             }
         }
         $access_log = PhabricatorAccessLog::getLog();
         if ($access_log) {
             $access_log->setData(array('u' => $conduit_username, 'm' => $method));
         }
         if ($call->shouldAllowUnguardedWrites()) {
             $allow_unguarded_writes = true;
         }
         if ($auth_error === null) {
             if ($allow_unguarded_writes) {
                 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             }
             try {
                 $result = $call->execute();
                 $error_code = null;
                 $error_info = null;
             } catch (ConduitException $ex) {
                 $result = null;
                 $error_code = $ex->getMessage();
                 if ($ex->getErrorDescription()) {
                     $error_info = $ex->getErrorDescription();
                 } else {
                     $error_info = $call->getErrorDescription($error_code);
                 }
             }
             if ($allow_unguarded_writes) {
                 unset($unguarded);
             }
         } else {
             list($error_code, $error_info) = $auth_error;
         }
     } catch (Exception $ex) {
         phlog($ex);
         $result = null;
         $error_code = 'ERR-CONDUIT-CORE';
         $error_info = $ex->getMessage();
     }
     $time_end = microtime(true);
     $connection_id = null;
     if (idx($metadata, 'connectionID')) {
         $connection_id = $metadata['connectionID'];
     } else {
         if ($method == 'conduit.connect' && $result) {
             $connection_id = idx($result, 'connectionID');
         }
     }
     $log->setConnectionID($connection_id);
     $log->setError((string) $error_code);
     $log->setDuration(1000000 * ($time_end - $time_start));
     // TODO: This is a hack, but the insert is comparatively expensive and
     // we only really care about having these logs for real CLI clients, if
     // even that.
     if (empty($metadata['authToken'])) {
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $log->save();
         unset($unguarded);
     }
     $response = id(new ConduitAPIResponse())->setResult($result)->setErrorCode($error_code)->setErrorInfo($error_info);
     switch ($request->getStr('output')) {
         case 'human':
             return $this->buildHumanReadableResponse($method, $api_request, $response->toDictionary());
         case 'json':
         default:
             return id(new AphrontJSONResponse())->setAddJSONShield(false)->setContent($response->toDictionary());
     }
 }
 public function processRequest()
 {
     $time_start = microtime(true);
     $request = $this->getRequest();
     $method = $this->method;
     $api_request = null;
     $method_implementation = null;
     $log = new PhabricatorConduitMethodCallLog();
     $log->setMethod($method);
     $metadata = array();
     $multimeter = MultimeterControl::getInstance();
     if ($multimeter) {
         $multimeter->setEventContext('api.' . $method);
     }
     try {
         list($metadata, $params) = $this->decodeConduitParams($request, $method);
         $call = new ConduitCall($method, $params);
         $method_implementation = $call->getMethodImplementation();
         $result = null;
         // TODO: The relationship between ConduitAPIRequest and ConduitCall is a
         // little odd here and could probably be improved. Specifically, the
         // APIRequest is a sub-object of the Call, which does not parallel the
         // role of AphrontRequest (which is an indepenent object).
         // In particular, the setUser() and getUser() existing independently on
         // the Call and APIRequest is very awkward.
         $api_request = $call->getAPIRequest();
         $allow_unguarded_writes = false;
         $auth_error = null;
         $conduit_username = '******';
         if ($call->shouldRequireAuthentication()) {
             $metadata['scope'] = $call->getRequiredScope();
             $auth_error = $this->authenticateUser($api_request, $metadata);
             // If we've explicitly authenticated the user here and either done
             // CSRF validation or are using a non-web authentication mechanism.
             $allow_unguarded_writes = true;
             if ($auth_error === null) {
                 $conduit_user = $api_request->getUser();
                 if ($conduit_user && $conduit_user->getPHID()) {
                     $conduit_username = $conduit_user->getUsername();
                 }
                 $call->setUser($api_request->getUser());
             }
         }
         $access_log = PhabricatorAccessLog::getLog();
         if ($access_log) {
             $access_log->setData(array('u' => $conduit_username, 'm' => $method));
         }
         if ($call->shouldAllowUnguardedWrites()) {
             $allow_unguarded_writes = true;
         }
         if ($auth_error === null) {
             if ($allow_unguarded_writes) {
                 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             }
             try {
                 $result = $call->execute();
                 $error_code = null;
                 $error_info = null;
             } catch (ConduitException $ex) {
                 $result = null;
                 $error_code = $ex->getMessage();
                 if ($ex->getErrorDescription()) {
                     $error_info = $ex->getErrorDescription();
                 } else {
                     $error_info = $call->getErrorDescription($error_code);
                 }
             }
             if ($allow_unguarded_writes) {
                 unset($unguarded);
             }
         } else {
             list($error_code, $error_info) = $auth_error;
         }
     } catch (Exception $ex) {
         if (!$ex instanceof ConduitMethodNotFoundException) {
             phlog($ex);
         }
         $result = null;
         $error_code = $ex instanceof ConduitException ? 'ERR-CONDUIT-CALL' : 'ERR-CONDUIT-CORE';
         $error_info = $ex->getMessage();
     }
     $time_end = microtime(true);
     $connection_id = null;
     if (idx($metadata, 'connectionID')) {
         $connection_id = $metadata['connectionID'];
     } else {
         if ($method == 'conduit.connect' && $result) {
             $connection_id = idx($result, 'connectionID');
         }
     }
     $log->setCallerPHID(isset($conduit_user) ? $conduit_user->getPHID() : null)->setConnectionID($connection_id)->setError((string) $error_code)->setDuration(1000000 * ($time_end - $time_start));
     $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
     $log->save();
     unset($unguarded);
     $response = id(new ConduitAPIResponse())->setResult($result)->setErrorCode($error_code)->setErrorInfo($error_info);
     switch ($request->getStr('output')) {
         case 'human':
             return $this->buildHumanReadableResponse($method, $api_request, $response->toDictionary(), $method_implementation);
         case 'json':
         default:
             return id(new AphrontJSONResponse())->setAddJSONShield(false)->setContent($response->toDictionary());
     }
 }
Example #10
0
} catch (Exception $e) {
    // no op; we'll error in a line or two
}
if (empty($user)) {
    echo "usage: api.php <user_phid> <method>\n" . "user {$user_str} does not exist or failed to load\n";
    exit(1);
}
$method = $argv[2];
$method_class_str = ConduitAPIMethod::getClassNameFromAPIMethodName($method);
try {
    $method_class = newv($method_class_str, array());
} catch (Exception $e) {
    echo "usage: api.php <user_phid> <method>\n" . "method {$method_class_str} does not exist\n";
    exit(1);
}
$log = new PhabricatorConduitMethodCallLog();
$log->setMethod($method);
$params = @file_get_contents('php://stdin');
$params = json_decode($params, true);
if (!is_array($params)) {
    echo "provide method parameters on stdin as a JSON blob";
    exit(1);
}
// build a quick ConduitAPIRequest from stdin PLUS the authenticated user
$conduit_request = new ConduitAPIRequest($params);
$conduit_request->setUser($user);
try {
    $result = $method_class->executeMethod($conduit_request);
    $error_code = null;
    $error_info = null;
} catch (ConduitException $ex) {
 public function processRequest()
 {
     $time_start = microtime(true);
     $request = $this->getRequest();
     $method = $this->method;
     $method_class = ConduitAPIMethod::getClassNameFromAPIMethodName($method);
     $api_request = null;
     $log = new PhabricatorConduitMethodCallLog();
     $log->setMethod($method);
     $metadata = array();
     try {
         if (!class_exists($method_class)) {
             throw new Exception("Unable to load the implementation class for method '{$method}'. " . "You may have misspelled the method, need to define " . "'{$method_class}', or need to run 'arc build'.");
         }
         // Fake out checkModule, the class has already been autoloaded by the
         // class_exists() call above.
         $method_handler = newv($method_class, array());
         if (isset($_REQUEST['params']) && is_array($_REQUEST['params'])) {
             $params_post = $request->getArr('params');
             foreach ($params_post as $key => $value) {
                 $params_post[$key] = json_decode($value, true);
             }
             $params = $params_post;
         } else {
             $params_json = $request->getStr('params');
             if (!strlen($params_json)) {
                 $params = array();
             } else {
                 $params = json_decode($params_json, true);
                 if (!is_array($params)) {
                     throw new Exception("Invalid parameter information was passed to method " . "'{$method}', could not decode JSON serialization.");
                 }
             }
         }
         $metadata = idx($params, '__conduit__', array());
         unset($params['__conduit__']);
         $result = null;
         $api_request = new ConduitAPIRequest($params);
         $auth_error = null;
         if ($method_handler->shouldRequireAuthentication()) {
             $auth_error = $this->authenticateUser($api_request, $metadata);
         }
         if ($auth_error === null) {
             try {
                 $result = $method_handler->executeMethod($api_request);
                 $error_code = null;
                 $error_info = null;
             } catch (ConduitException $ex) {
                 $result = null;
                 $error_code = $ex->getMessage();
                 $error_info = $method_handler->getErrorDescription($error_code);
             }
         } else {
             list($error_code, $error_info) = $auth_error;
         }
     } catch (Exception $ex) {
         $result = null;
         $error_code = 'ERR-CONDUIT-CORE';
         $error_info = $ex->getMessage();
     }
     $time_end = microtime(true);
     $connection_id = null;
     if (idx($metadata, 'connectionID')) {
         $connection_id = $metadata['connectionID'];
     } else {
         if ($method == 'conduit.connect' && $result) {
             $connection_id = idx($result, 'connectionID');
         }
     }
     $log->setConnectionID($connection_id);
     $log->setError((string) $error_code);
     $log->setDuration(1000000 * ($time_end - $time_start));
     // TODO: This is a hack, but the insert is comparatively expensive and
     // we only really care about having these logs for real CLI clients, if
     // even that.
     if (empty($metadata['authToken'])) {
         $log->save();
     }
     $result = array('result' => $result, 'error_code' => $error_code, 'error_info' => $error_info);
     switch ($request->getStr('output')) {
         case 'human':
             return $this->buildHumanReadableResponse($method, $api_request, $result);
         case 'json':
         default:
             return id(new AphrontFileResponse())->setMimeType('application/json')->setContent('for(;;);' . json_encode($result));
     }
 }
 public function processRequest()
 {
     $time_start = microtime(true);
     $request = $this->getRequest();
     $method = $this->method;
     $method_class = ConduitAPIMethod::getClassNameFromAPIMethodName($method);
     $api_request = null;
     $log = new PhabricatorConduitMethodCallLog();
     $log->setMethod($method);
     $metadata = array();
     try {
         if (!class_exists($method_class)) {
             throw new Exception("Unable to load the implementation class for method '{$method}'. " . "You may have misspelled the method, need to define " . "'{$method_class}', or need to run 'arc build'.");
         }
         $class_info = new ReflectionClass($method_class);
         if ($class_info->isAbstract()) {
             throw new Exception("Method '{$method}' is not valid; the implementation is an abstract " . "base class.");
         }
         $method_handler = newv($method_class, array());
         if (isset($_REQUEST['params']) && is_array($_REQUEST['params'])) {
             $params_post = $request->getArr('params');
             foreach ($params_post as $key => $value) {
                 if ($value == '') {
                     // Interpret empty string null (e.g., the user didn't type anything
                     // into the box).
                     $value = 'null';
                 }
                 $decoded_value = json_decode($value, true);
                 if ($decoded_value === null && strtolower($value) != 'null') {
                     // When json_decode() fails, it returns null. This almost certainly
                     // indicates that a user was using the web UI and didn't put quotes
                     // around a string value. We can either do what we think they meant
                     // (treat it as a string) or fail. For now, err on the side of
                     // caution and fail. In the future, if we make the Conduit API
                     // actually do type checking, it might be reasonable to treat it as
                     // a string if the parameter type is string.
                     throw new Exception("The value for parameter '{$key}' is not valid JSON. All " . "parameters must be encoded as JSON values, including strings " . "(which means you need to surround them in double quotes). " . "Check your syntax. Value was: {$value}");
                 }
                 $params_post[$key] = $decoded_value;
             }
             $params = $params_post;
         } else {
             $params_json = $request->getStr('params');
             if (!strlen($params_json)) {
                 $params = array();
             } else {
                 $params = json_decode($params_json, true);
                 if (!is_array($params)) {
                     throw new Exception("Invalid parameter information was passed to method " . "'{$method}', could not decode JSON serialization.");
                 }
             }
         }
         $metadata = idx($params, '__conduit__', array());
         unset($params['__conduit__']);
         $result = null;
         $api_request = new ConduitAPIRequest($params);
         $allow_unguarded_writes = false;
         $auth_error = null;
         if ($method_handler->shouldRequireAuthentication()) {
             $auth_error = $this->authenticateUser($api_request, $metadata);
             // If we've explicitly authenticated the user here and either done
             // CSRF validation or are using a non-web authentication mechanism.
             $allow_unguarded_writes = true;
             if (isset($metadata['actAsUser'])) {
                 $this->actAsUser($api_request, $metadata['actAsUser']);
             }
         }
         if ($method_handler->shouldAllowUnguardedWrites()) {
             $allow_unguarded_writes = true;
         }
         if ($auth_error === null) {
             if ($allow_unguarded_writes) {
                 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             }
             try {
                 $result = $method_handler->executeMethod($api_request);
                 $error_code = null;
                 $error_info = null;
             } catch (ConduitException $ex) {
                 $result = null;
                 $error_code = $ex->getMessage();
                 if ($ex->getErrorDescription()) {
                     $error_info = $ex->getErrorDescription();
                 } else {
                     $error_info = $method_handler->getErrorDescription($error_code);
                 }
             }
             if ($allow_unguarded_writes) {
                 unset($unguarded);
             }
         } else {
             list($error_code, $error_info) = $auth_error;
         }
     } catch (Exception $ex) {
         phlog($ex);
         $result = null;
         $error_code = 'ERR-CONDUIT-CORE';
         $error_info = $ex->getMessage();
     }
     $time_end = microtime(true);
     $connection_id = null;
     if (idx($metadata, 'connectionID')) {
         $connection_id = $metadata['connectionID'];
     } else {
         if ($method == 'conduit.connect' && $result) {
             $connection_id = idx($result, 'connectionID');
         }
     }
     $log->setConnectionID($connection_id);
     $log->setError((string) $error_code);
     $log->setDuration(1000000 * ($time_end - $time_start));
     // TODO: This is a hack, but the insert is comparatively expensive and
     // we only really care about having these logs for real CLI clients, if
     // even that.
     if (empty($metadata['authToken'])) {
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         $log->save();
         unset($unguarded);
     }
     $result = array('result' => $result, 'error_code' => $error_code, 'error_info' => $error_info);
     switch ($request->getStr('output')) {
         case 'human':
             return $this->buildHumanReadableResponse($method, $api_request, $result);
         case 'json':
         default:
             return id(new AphrontFileResponse())->setMimeType('application/json')->setContent('for(;;);' . json_encode($result));
     }
 }