1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-20 04:20:55 +01:00

Modularize Aphront exception handling

Summary:
Ref T1806. Ref T7173. Depends on D14047.

Currently, all exception handling is in this big messy clump in `AphrontDefaultApplicationConfiguration`.

Split it out into modular classes. This will let a future change add new classes in the Phacility cluster which intercept particular exceptions we care about and replaces the default, generic responses with more useful, tailored responses.

Test Plan:
{F777391}

- Hit a Conduit error (made a method throw).
- Hit an Ajax error (made comment preview throw).
- Hit a high security error (tried to edit TOTP).
- Hit a rate limiting error (added a bunch of email addresses).
- Hit a policy error (tried to look at something with no permission).
- Hit an arbitrary exception (made a randomc ontroller throw).

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T1806, T7173

Differential Revision: https://secure.phabricator.com/D14049
This commit is contained in:
epriestley 2015-09-03 10:04:42 -07:00
parent 20ce1a905f
commit 1fc60a9a6e
15 changed files with 550 additions and 218 deletions

View file

@ -157,6 +157,7 @@ phutil_register_library_map(array(
'AphrontRedirectResponseTestCase' => 'aphront/response/__tests__/AphrontRedirectResponseTestCase.php',
'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php',
'AphrontRequest' => 'aphront/AphrontRequest.php',
'AphrontRequestExceptionHandler' => 'aphront/handler/AphrontRequestExceptionHandler.php',
'AphrontRequestTestCase' => 'aphront/__tests__/AphrontRequestTestCase.php',
'AphrontResponse' => 'aphront/response/AphrontResponse.php',
'AphrontResponseProducerInterface' => 'aphront/interface/AphrontResponseProducerInterface.php',
@ -1484,6 +1485,7 @@ phutil_register_library_map(array(
'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php',
'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php',
'PhabricatorAdministratorsPolicyRule' => 'applications/policy/rule/PhabricatorAdministratorsPolicyRule.php',
'PhabricatorAjaxRequestExceptionHandler' => 'aphront/handler/PhabricatorAjaxRequestExceptionHandler.php',
'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php',
'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php',
'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php',
@ -1786,6 +1788,7 @@ phutil_register_library_map(array(
'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php',
'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php',
'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php',
'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php',
'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php',
'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php',
'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php',
@ -1838,6 +1841,7 @@ phutil_register_library_map(array(
'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php',
'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php',
'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php',
'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php',
'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php',
'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php',
'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php',
@ -1993,6 +1997,7 @@ phutil_register_library_map(array(
'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php',
'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php',
'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php',
'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php',
'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php',
'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php',
'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php',
@ -2183,6 +2188,7 @@ phutil_register_library_map(array(
'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php',
'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php',
'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php',
'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php',
'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php',
'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php',
'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php',
@ -2580,6 +2586,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php',
'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php',
'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php',
'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php',
'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php',
'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php',
'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php',
@ -2667,6 +2674,7 @@ phutil_register_library_map(array(
'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php',
'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php',
'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php',
'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php',
'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php',
'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php',
'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
@ -2758,6 +2766,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php',
'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php',
'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php',
'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php',
'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php',
'PhabricatorRobotsController' => 'applications/system/controller/PhabricatorRobotsController.php',
'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php',
@ -3788,6 +3797,7 @@ phutil_register_library_map(array(
'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase',
'AphrontReloadResponse' => 'AphrontRedirectResponse',
'AphrontRequest' => 'Phobject',
'AphrontRequestExceptionHandler' => 'Phobject',
'AphrontRequestTestCase' => 'PhabricatorTestCase',
'AphrontResponse' => 'Phobject',
'AphrontRoutingMap' => 'Phobject',
@ -5303,6 +5313,7 @@ phutil_register_library_map(array(
'PhabricatorActionView' => 'AphrontView',
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule',
'PhabricatorAjaxRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorAlmanacApplication' => 'PhabricatorApplication',
'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorAnchorView' => 'AphrontView',
@ -5667,6 +5678,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface',
),
'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitTestCase' => 'PhabricatorTestCase',
'PhabricatorConduitToken' => array(
@ -5729,6 +5741,7 @@ phutil_register_library_map(array(
'PhabricatorConfigOptionType' => 'Phobject',
'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule',
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule',
'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse',
'PhabricatorConfigSchemaQuery' => 'Phobject',
'PhabricatorConfigSchemaSpec' => 'Phobject',
@ -5913,6 +5926,7 @@ phutil_register_library_map(array(
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDebugController' => 'PhabricatorController',
'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDestructionEngine' => 'Phobject',
'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
@ -6138,6 +6152,7 @@ phutil_register_library_map(array(
'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
'PhabricatorHeraldApplication' => 'PhabricatorApplication',
'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorHomeApplication' => 'PhabricatorApplication',
'PhabricatorHomeController' => 'PhabricatorController',
'PhabricatorHomeMainController' => 'PhabricatorHomeController',
@ -6587,6 +6602,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorPolicyRule' => 'Phobject',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => array(
@ -6702,6 +6718,7 @@ phutil_register_library_map(array(
'Phobject',
'Iterator',
),
'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType',
'PhabricatorRedirectController' => 'PhabricatorController',
@ -6828,6 +6845,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryVersion' => 'Phobject',
'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler',
'PhabricatorResourceSite' => 'PhabricatorSite',
'PhabricatorRobotsController' => 'PhabricatorController',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',

View file

@ -3,6 +3,7 @@
/**
* @task routing URI Routing
* @task response Response Handling
* @task exception Exception Handling
*/
abstract class AphrontApplicationConfiguration extends Phobject {
@ -11,7 +12,6 @@ abstract class AphrontApplicationConfiguration extends Phobject {
private $path;
private $console;
abstract public function getApplicationName();
abstract public function buildRequest();
abstract public function build404Controller();
abstract public function buildRedirectController($uri, $external);
@ -482,7 +482,7 @@ abstract class AphrontApplicationConfiguration extends Phobject {
/**
* Verifies that the erturn value from an
* Verifies that the return value from an
* @{class:AphrontResponseProducerInterface} is of an allowed type.
*
* @param AphrontResponseProducerInterface Object which produced
@ -511,6 +511,36 @@ abstract class AphrontApplicationConfiguration extends Phobject {
}
/**
* Verifies that the return value from an
* @{class:AphrontRequestExceptionHandler} is of an allowed type.
*
* @param AphrontRequestExceptionHandler Object which produced this
* response.
* @param wild Supposedly valid response.
* @return void
* @task response
*/
private function validateErrorHandlerResponse(
AphrontRequestExceptionHandler $handler,
$response) {
if ($this->isValidResponseObject($response)) {
return;
}
throw new Exception(
pht(
'Exception handler "%s" returned an invalid response from call to '.
'"%s". This method must return an object of class "%s", or an object '.
'which implements the "%s" interface.',
get_class($handler),
'handleRequestException()',
'AphrontResponse',
'AphrontResponseProducerInterface'));
}
/**
* Resolves a response object into an @{class:AphrontResponse}.
*
@ -572,4 +602,34 @@ abstract class AphrontApplicationConfiguration extends Phobject {
}
/* -( Error Handling )----------------------------------------------------- */
/**
* Convert an exception which has escaped the controller into a response.
*
* This method delegates exception handling to available subclasses of
* @{class:AphrontRequestExceptionHandler}.
*
* @param Exception Exception which needs to be handled.
* @return wild Response or response producer, or null if no available
* handler can produce a response.
* @task exception
*/
private function handleException(Exception $ex) {
$handlers = AphrontRequestExceptionHandler::getAllHandlers();
$request = $this->getRequest();
foreach ($handlers as $handler) {
if ($handler->canHandleRequestException($request, $ex)) {
$response = $handler->handleRequestException($request, $ex);
$this->validateErrorHandlerResponse($handler, $response);
return $response;
}
}
throw $ex;
}
}

View file

@ -8,12 +8,6 @@
class AphrontDefaultApplicationConfiguration
extends AphrontApplicationConfiguration {
public function __construct() {}
public function getApplicationName() {
return 'aphront-default';
}
/**
* @phutil-external-symbol class PhabricatorStartup
*/
@ -50,213 +44,6 @@ class AphrontDefaultApplicationConfiguration
return $request;
}
public function handleException(Exception $ex) {
$request = $this->getRequest();
// For Conduit requests, return a Conduit response.
if ($request->isConduit()) {
$response = new ConduitAPIResponse();
$response->setErrorCode(get_class($ex));
$response->setErrorInfo($ex->getMessage());
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
}
// For non-workflow requests, return a Ajax response.
if ($request->isAjax() && !$request->isWorkflow()) {
// Log these; they don't get shown on the client and can be difficult
// to debug.
phlog($ex);
$response = new AphrontAjaxResponse();
$response->setError(
array(
'code' => get_class($ex),
'info' => $ex->getMessage(),
));
return $response;
}
$user = $request->getUser();
if (!$user) {
// If we hit an exception very early, we won't have a user.
$user = new PhabricatorUser();
}
if ($ex instanceof PhabricatorSystemActionRateLimitException) {
$dialog = id(new AphrontDialogView())
->setTitle(pht('Slow Down!'))
->setUser($user)
->setErrors(array(pht('You are being rate limited.')))
->appendParagraph($ex->getMessage())
->appendParagraph($ex->getRateExplanation())
->addCancelButton('/', pht('Okaaaaaaaaaaaaaay...'));
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
if ($ex instanceof PhabricatorAuthHighSecurityRequiredException) {
$form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm(
$ex->getFactors(),
$ex->getFactorValidationResults(),
$user,
$request);
$dialog = id(new AphrontDialogView())
->setUser($user)
->setTitle(pht('Entering High Security'))
->setShortTitle(pht('Security Checkpoint'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->addHiddenInput(AphrontRequest::TYPE_HISEC, true)
->setErrors(
array(
pht(
'You are taking an action which requires you to enter '.
'high security.'),
))
->appendParagraph(
pht(
'High security mode helps protect your account from security '.
'threats, like session theft or someone messing with your stuff '.
'while you\'re grabbing a coffee. To enter high security mode, '.
'confirm your credentials.'))
->appendChild($form->buildLayoutView())
->appendParagraph(
pht(
'Your account will remain in high security mode for a short '.
'period of time. When you are finished taking sensitive '.
'actions, you should leave high security.'))
->setSubmitURI($request->getPath())
->addCancelButton($ex->getCancelURI())
->addSubmitButton(pht('Enter High Security'));
$request_parameters = $request->getPassthroughRequestParameters(
$respect_quicksand = true);
foreach ($request_parameters as $key => $value) {
$dialog->addHiddenInput($key, $value);
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
if ($ex instanceof PhabricatorPolicyException) {
if (!$user->isLoggedIn()) {
// If the user isn't logged in, just give them a login form. This is
// probably a generally more useful response than a policy dialog that
// they have to click through to get a login form.
//
// Possibly we should add a header here like "you need to login to see
// the thing you are trying to look at".
$login_controller = new PhabricatorAuthStartController();
$login_controller->setRequest($request);
$auth_app_class = 'PhabricatorAuthApplication';
$auth_app = PhabricatorApplication::getByClass($auth_app_class);
$login_controller->setCurrentApplication($auth_app);
return $login_controller->handleRequest($request);
}
$content = array(
phutil_tag(
'div',
array(
'class' => 'aphront-policy-rejection',
),
$ex->getRejection()),
);
$list = null;
if ($ex->getCapabilityName()) {
$list = $ex->getMoreInfo();
foreach ($list as $key => $item) {
$list[$key] = $item;
}
$content[] = phutil_tag(
'div',
array(
'class' => 'aphront-capability-details',
),
pht('Users with the "%s" capability:', $ex->getCapabilityName()));
}
$dialog = id(new AphrontDialogView())
->setTitle($ex->getTitle())
->setClass('aphront-access-dialog')
->setUser($user)
->appendChild($content);
if ($list) {
$dialog->appendList($list);
}
if ($this->getRequest()->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
} else {
$dialog->addCancelButton('/', pht('OK'));
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
return $response;
}
// Always log the unhandled exception.
phlog($ex);
$class = get_class($ex);
$message = $ex->getMessage();
if ($ex instanceof AphrontSchemaQueryException) {
$message .= "\n\n".pht(
"NOTE: This usually indicates that the MySQL schema has not been ".
"properly upgraded. Run '%s' to ensure your schema is up to date.",
'bin/storage upgrade');
}
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$trace = id(new AphrontStackTraceView())
->setUser($user)
->setTrace($ex->getTrace());
} else {
$trace = null;
}
$content = phutil_tag(
'div',
array('class' => 'aphront-unhandled-exception'),
array(
phutil_tag('div', array('class' => 'exception-message'), $message),
$trace,
));
$dialog = new AphrontDialogView();
$dialog
->setTitle(pht('Unhandled Exception ("%s")', $class))
->setClass('aphront-exception-dialog')
->setUser($user)
->appendChild($content);
if ($this->getRequest()->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
}
$response = new AphrontDialogResponse();
$response->setDialog($dialog);
$response->setHTTPResponseCode(500);
return $response;
}
public function build404Controller() {
return array(new Phabricator404Controller(), array());
}

View file

@ -0,0 +1,36 @@
<?php
/**
* React to an unhandled exception escaping request handling in a controller
* and convert it into a response.
*
* These handlers are generally used to render error pages, but they may
* also perform more specialized handling in situations where an error page
* is not appropriate.
*/
abstract class AphrontRequestExceptionHandler extends Phobject {
abstract public function getRequestExceptionHandlerPriority();
public function shouldLogException(
AphrontRequest $request,
Exception $ex) {
return null;
}
abstract public function canHandleRequestException(
AphrontRequest $request,
Exception $ex);
abstract public function handleRequestException(
AphrontRequest $request,
Exception $ex);
final public static function getAllHandlers() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setSortMethod('getRequestExceptionHandlerPriority')
->execute();
}
}

View file

@ -0,0 +1,38 @@
<?php
final class PhabricatorAjaxRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 110000;
}
public function getRequestExceptionHandlerDescription() {
return pht('Responds to requests made by AJAX clients.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
// For non-workflow requests, return a Ajax response.
return ($request->isAjax() && !$request->isWorkflow());
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
// Log these; they don't get shown on the client and can be difficult
// to debug.
phlog($ex);
$response = new AphrontAjaxResponse();
$response->setError(
array(
'code' => get_class($ex),
'info' => $ex->getMessage(),
));
return $response;
}
}

View file

@ -0,0 +1,33 @@
<?php
final class PhabricatorConduitRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 100000;
}
public function getRequestExceptionHandlerDescription() {
return pht('Responds to requests made by Conduit clients.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
return $request->isConduit();
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$response = id(new ConduitAPIResponse())
->setErrorCode(get_class($ex))
->setErrorInfo($ex->getMessage());
return id(new AphrontJSONResponse())
->setAddJSONShield(false)
->setContent($response->toDictionary());
}
}

View file

@ -0,0 +1,76 @@
<?php
final class PhabricatorDefaultRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 900000;
}
public function getRequestExceptionHandlerDescription() {
return pht('Handles all other exceptions.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return true;
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$viewer = $this->getViewer($request);
// Always log the unhandled exception.
phlog($ex);
$class = get_class($ex);
$message = $ex->getMessage();
if ($ex instanceof AphrontSchemaQueryException) {
$message .= "\n\n".pht(
"NOTE: This usually indicates that the MySQL schema has not been ".
"properly upgraded. Run '%s' to ensure your schema is up to date.",
'bin/storage upgrade');
}
if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) {
$trace = id(new AphrontStackTraceView())
->setUser($viewer)
->setTrace($ex->getTrace());
} else {
$trace = null;
}
$content = phutil_tag(
'div',
array('class' => 'aphront-unhandled-exception'),
array(
phutil_tag('div', array('class' => 'exception-message'), $message),
$trace,
));
$dialog = new AphrontDialogView();
$dialog
->setTitle(pht('Unhandled Exception ("%s")', $class))
->setClass('aphront-exception-dialog')
->setUser($viewer)
->appendChild($content);
if ($request->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
}
return id(new AphrontDialogResponse())
->setDialog($dialog)
->setHTTPResponseCode(500);
}
}

View file

@ -0,0 +1,76 @@
<?php
final class PhabricatorHighSecurityRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 310000;
}
public function getRequestExceptionHandlerDescription() {
return pht(
'Handles high security exceptions which occur when a user needs '.
'to present MFA credentials to take an action.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return ($ex instanceof PhabricatorAuthHighSecurityRequiredException);
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$viewer = $this->getViewer($request);
$form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm(
$ex->getFactors(),
$ex->getFactorValidationResults(),
$viewer,
$request);
$dialog = id(new AphrontDialogView())
->setUser($viewer)
->setTitle(pht('Entering High Security'))
->setShortTitle(pht('Security Checkpoint'))
->setWidth(AphrontDialogView::WIDTH_FORM)
->addHiddenInput(AphrontRequest::TYPE_HISEC, true)
->setErrors(
array(
pht(
'You are taking an action which requires you to enter '.
'high security.'),
))
->appendParagraph(
pht(
'High security mode helps protect your account from security '.
'threats, like session theft or someone messing with your stuff '.
'while you\'re grabbing a coffee. To enter high security mode, '.
'confirm your credentials.'))
->appendChild($form->buildLayoutView())
->appendParagraph(
pht(
'Your account will remain in high security mode for a short '.
'period of time. When you are finished taking sensitive '.
'actions, you should leave high security.'))
->setSubmitURI($request->getPath())
->addCancelButton($ex->getCancelURI())
->addSubmitButton(pht('Enter High Security'));
$request_parameters = $request->getPassthroughRequestParameters(
$respect_quicksand = true);
foreach ($request_parameters as $key => $value) {
$dialog->addHiddenInput($key, $value);
}
return $dialog;
}
}

View file

@ -0,0 +1,93 @@
<?php
final class PhabricatorPolicyRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 320000;
}
public function getRequestExceptionHandlerDescription() {
return pht(
'Handles policy exceptions which occur when a user tries to '.
'do something they do not have permission to do.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return ($ex instanceof PhabricatorPolicyException);
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$viewer = $this->getViewer($request);
if (!$viewer->isLoggedIn()) {
// If the user isn't logged in, just give them a login form. This is
// probably a generally more useful response than a policy dialog that
// they have to click through to get a login form.
//
// Possibly we should add a header here like "you need to login to see
// the thing you are trying to look at".
$auth_app_class = 'PhabricatorAuthApplication';
$auth_app = PhabricatorApplication::getByClass($auth_app_class);
return id(new PhabricatorAuthStartController())
->setRequest($request)
->setCurrentApplication($auth_app)
->handleRequest($request);
}
$content = array(
phutil_tag(
'div',
array(
'class' => 'aphront-policy-rejection',
),
$ex->getRejection()),
);
$list = null;
if ($ex->getCapabilityName()) {
$list = $ex->getMoreInfo();
foreach ($list as $key => $item) {
$list[$key] = $item;
}
$content[] = phutil_tag(
'div',
array(
'class' => 'aphront-capability-details',
),
pht('Users with the "%s" capability:', $ex->getCapabilityName()));
}
$dialog = id(new AphrontDialogView())
->setTitle($ex->getTitle())
->setClass('aphront-access-dialog')
->setUser($viewer)
->appendChild($content);
if ($list) {
$dialog->appendList($list);
}
if ($request->isAjax()) {
$dialog->addCancelButton('/', pht('Close'));
} else {
$dialog->addCancelButton('/', pht('OK'));
}
return $dialog;
}
}

View file

@ -0,0 +1,42 @@
<?php
final class PhabricatorRateLimitRequestExceptionHandler
extends PhabricatorRequestExceptionHandler {
public function getRequestExceptionHandlerPriority() {
return 300000;
}
public function getRequestExceptionHandlerDescription() {
return pht(
'Handles action rate limiting exceptions which occur when a user '.
'does something too frequently.');
}
public function canHandleRequestException(
AphrontRequest $request,
Exception $ex) {
if (!$this->isPhabricatorSite($request)) {
return false;
}
return ($ex instanceof PhabricatorSystemActionRateLimitException);
}
public function handleRequestException(
AphrontRequest $request,
Exception $ex) {
$viewer = $this->getViewer($request);
return id(new AphrontDialogView())
->setTitle(pht('Slow Down!'))
->setUser($viewer)
->setErrors(array(pht('You are being rate limited.')))
->appendParagraph($ex->getMessage())
->appendParagraph($ex->getRateExplanation())
->addCancelButton('/', pht('Okaaaaaaaaaaaaaay...'));
}
}

View file

@ -0,0 +1,26 @@
<?php
abstract class PhabricatorRequestExceptionHandler
extends AphrontRequestExceptionHandler {
protected function isPhabricatorSite(AphrontRequest $request) {
$site = $request->getSite();
if (!$site) {
return false;
}
return ($site instanceof PhabricatorSite);
}
protected function getViewer(AphrontRequest $request) {
$viewer = $request->getUser();
if ($viewer) {
return $viewer;
}
// If we hit an exception very early, we won't have a user yet.
return new PhabricatorUser();
}
}

View file

@ -41,7 +41,7 @@ final class PhabricatorConfigEdgeModule extends PhabricatorConfigModule {
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Edge Types'))
->appendChild($table);
->setTable($table);
}
}

View file

@ -41,7 +41,7 @@ final class PhabricatorConfigPHIDModule extends PhabricatorConfigModule {
return id(new PHUIObjectBoxView())
->setHeaderText(pht('PHID Types'))
->appendChild($table);
->setTable($table);
}
}

View file

@ -0,0 +1,47 @@
<?php
final class PhabricatorConfigRequestExceptionHandlerModule
extends PhabricatorConfigModule {
public function getModuleKey() {
return 'exception-handler';
}
public function getModuleName() {
return pht('Exception Handlers');
}
public function renderModuleStatus(AphrontRequest $request) {
$viewer = $request->getViewer();
$handlers = AphrontRequestExceptionHandler::getAllHandlers();
$rows = array();
foreach ($handlers as $key => $handler) {
$rows[] = array(
$handler->getRequestExceptionHandlerPriority(),
$key,
$handler->getRequestExceptionHandlerDescription(),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Priority'),
pht('Class'),
pht('Description'),
))
->setColumnClasses(
array(
null,
'pri',
'wide',
));
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Exception Handlers'))
->setTable($table);
}
}

View file

@ -40,7 +40,7 @@ final class PhabricatorConfigSiteModule extends PhabricatorConfigModule {
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Sites'))
->appendChild($table);
->setTable($table);
}
}