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()); } }
} 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)); } }