Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14411644
D14049.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
37 KB
Referenced Files
None
Subscribers
None
D14049.diff
View Options
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -157,6 +157,7 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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 @@
'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase',
'AphrontReloadResponse' => 'AphrontRedirectResponse',
'AphrontRequest' => 'Phobject',
+ 'AphrontRequestExceptionHandler' => 'Phobject',
'AphrontRequestTestCase' => 'PhabricatorTestCase',
'AphrontResponse' => 'Phobject',
'AphrontRoutingMap' => 'Phobject',
@@ -5303,6 +5313,7 @@
'PhabricatorActionView' => 'AphrontView',
'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule',
+ 'PhabricatorAjaxRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorAlmanacApplication' => 'PhabricatorApplication',
'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorAnchorView' => 'AphrontView',
@@ -5667,6 +5678,7 @@
'PhabricatorPolicyInterface',
),
'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorConduitTestCase' => 'PhabricatorTestCase',
'PhabricatorConduitToken' => array(
@@ -5729,6 +5741,7 @@
'PhabricatorConfigOptionType' => 'Phobject',
'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule',
'PhabricatorConfigProxySource' => 'PhabricatorConfigSource',
+ 'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule',
'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse',
'PhabricatorConfigSchemaQuery' => 'Phobject',
'PhabricatorConfigSchemaSpec' => 'Phobject',
@@ -5913,6 +5926,7 @@
'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDebugController' => 'PhabricatorController',
+ 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorDestructionEngine' => 'Phobject',
'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions',
@@ -6138,6 +6152,7 @@
'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController',
'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
'PhabricatorHeraldApplication' => 'PhabricatorApplication',
+ 'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorHomeApplication' => 'PhabricatorApplication',
'PhabricatorHomeController' => 'PhabricatorController',
'PhabricatorHomeMainController' => 'PhabricatorHomeController',
@@ -6587,6 +6602,7 @@
'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow',
'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType',
'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorPolicyRule' => 'Phobject',
'PhabricatorPolicyTestCase' => 'PhabricatorTestCase',
'PhabricatorPolicyTestObject' => array(
@@ -6702,6 +6718,7 @@
'Phobject',
'Iterator',
),
+ 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler',
'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType',
'PhabricatorRedirectController' => 'PhabricatorController',
@@ -6828,6 +6845,7 @@
'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase',
'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryVersion' => 'Phobject',
+ 'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler',
'PhabricatorResourceSite' => 'PhabricatorSite',
'PhabricatorRobotsController' => 'PhabricatorController',
'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
diff --git a/src/aphront/configuration/AphrontApplicationConfiguration.php b/src/aphront/configuration/AphrontApplicationConfiguration.php
--- a/src/aphront/configuration/AphrontApplicationConfiguration.php
+++ b/src/aphront/configuration/AphrontApplicationConfiguration.php
@@ -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 @@
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 @@
/**
- * 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
@@ -512,6 +512,36 @@
/**
+ * 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}.
*
* Controllers are permitted to return actual responses of class
@@ -572,4 +602,34 @@
}
+/* -( 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;
+ }
+
+
}
diff --git a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
--- a/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
+++ b/src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
@@ -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 @@
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());
}
diff --git a/src/aphront/handler/AphrontRequestExceptionHandler.php b/src/aphront/handler/AphrontRequestExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/handler/AphrontRequestExceptionHandler.php
@@ -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();
+ }
+
+}
diff --git a/src/aphront/handler/PhabricatorAjaxRequestExceptionHandler.php b/src/aphront/handler/PhabricatorAjaxRequestExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/handler/PhabricatorAjaxRequestExceptionHandler.php
@@ -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;
+ }
+
+}
diff --git a/src/aphront/handler/PhabricatorConduitRequestExceptionHandler.php b/src/aphront/handler/PhabricatorConduitRequestExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/handler/PhabricatorConduitRequestExceptionHandler.php
@@ -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());
+ }
+
+}
diff --git a/src/aphront/handler/PhabricatorDefaultRequestExceptionHandler.php b/src/aphront/handler/PhabricatorDefaultRequestExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/handler/PhabricatorDefaultRequestExceptionHandler.php
@@ -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);
+ }
+
+}
diff --git a/src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php b/src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php
@@ -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;
+ }
+
+}
diff --git a/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
@@ -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;
+ }
+
+}
diff --git a/src/aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php b/src/aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php
@@ -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...'));
+ }
+
+}
diff --git a/src/aphront/handler/PhabricatorRequestExceptionHandler.php b/src/aphront/handler/PhabricatorRequestExceptionHandler.php
new file mode 100644
--- /dev/null
+++ b/src/aphront/handler/PhabricatorRequestExceptionHandler.php
@@ -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();
+ }
+
+}
diff --git a/src/applications/config/module/PhabricatorConfigEdgeModule.php b/src/applications/config/module/PhabricatorConfigEdgeModule.php
--- a/src/applications/config/module/PhabricatorConfigEdgeModule.php
+++ b/src/applications/config/module/PhabricatorConfigEdgeModule.php
@@ -41,7 +41,7 @@
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Edge Types'))
- ->appendChild($table);
+ ->setTable($table);
}
}
diff --git a/src/applications/config/module/PhabricatorConfigPHIDModule.php b/src/applications/config/module/PhabricatorConfigPHIDModule.php
--- a/src/applications/config/module/PhabricatorConfigPHIDModule.php
+++ b/src/applications/config/module/PhabricatorConfigPHIDModule.php
@@ -41,7 +41,7 @@
return id(new PHUIObjectBoxView())
->setHeaderText(pht('PHID Types'))
- ->appendChild($table);
+ ->setTable($table);
}
}
diff --git a/src/applications/config/module/PhabricatorConfigSiteModule.php b/src/applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php
copy from src/applications/config/module/PhabricatorConfigSiteModule.php
copy to src/applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php
--- a/src/applications/config/module/PhabricatorConfigSiteModule.php
+++ b/src/applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php
@@ -1,26 +1,27 @@
<?php
-final class PhabricatorConfigSiteModule extends PhabricatorConfigModule {
+final class PhabricatorConfigRequestExceptionHandlerModule
+ extends PhabricatorConfigModule {
public function getModuleKey() {
- return 'site';
+ return 'exception-handler';
}
public function getModuleName() {
- return pht('Sites');
+ return pht('Exception Handlers');
}
public function renderModuleStatus(AphrontRequest $request) {
$viewer = $request->getViewer();
- $sites = AphrontSite::getAllSites();
+ $handlers = AphrontRequestExceptionHandler::getAllHandlers();
$rows = array();
- foreach ($sites as $key => $site) {
+ foreach ($handlers as $key => $handler) {
$rows[] = array(
- $site->getPriority(),
+ $handler->getRequestExceptionHandlerPriority(),
$key,
- $site->getDescription(),
+ $handler->getRequestExceptionHandlerDescription(),
);
}
@@ -39,8 +40,8 @@
));
return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Sites'))
- ->appendChild($table);
+ ->setHeaderText(pht('Exception Handlers'))
+ ->setTable($table);
}
}
diff --git a/src/applications/config/module/PhabricatorConfigSiteModule.php b/src/applications/config/module/PhabricatorConfigSiteModule.php
--- a/src/applications/config/module/PhabricatorConfigSiteModule.php
+++ b/src/applications/config/module/PhabricatorConfigSiteModule.php
@@ -40,7 +40,7 @@
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Sites'))
- ->appendChild($table);
+ ->setTable($table);
}
}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Dec 25, 11:58 AM (10 h, 45 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
6926110
Default Alt Text
D14049.diff (37 KB)
Attached To
Mode
D14049: Modularize Aphront exception handling
Attached
Detach File
Event Timeline
Log In to Comment