@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

+550 -218
+18
src/__phutil_library_map__.php
··· 157 157 'AphrontRedirectResponseTestCase' => 'aphront/response/__tests__/AphrontRedirectResponseTestCase.php', 158 158 'AphrontReloadResponse' => 'aphront/response/AphrontReloadResponse.php', 159 159 'AphrontRequest' => 'aphront/AphrontRequest.php', 160 + 'AphrontRequestExceptionHandler' => 'aphront/handler/AphrontRequestExceptionHandler.php', 160 161 'AphrontRequestTestCase' => 'aphront/__tests__/AphrontRequestTestCase.php', 161 162 'AphrontResponse' => 'aphront/response/AphrontResponse.php', 162 163 'AphrontResponseProducerInterface' => 'aphront/interface/AphrontResponseProducerInterface.php', ··· 1484 1485 'PhabricatorActionView' => 'view/layout/PhabricatorActionView.php', 1485 1486 'PhabricatorActivitySettingsPanel' => 'applications/settings/panel/PhabricatorActivitySettingsPanel.php', 1486 1487 'PhabricatorAdministratorsPolicyRule' => 'applications/policy/rule/PhabricatorAdministratorsPolicyRule.php', 1488 + 'PhabricatorAjaxRequestExceptionHandler' => 'aphront/handler/PhabricatorAjaxRequestExceptionHandler.php', 1487 1489 'PhabricatorAlmanacApplication' => 'applications/almanac/application/PhabricatorAlmanacApplication.php', 1488 1490 'PhabricatorAmazonAuthProvider' => 'applications/auth/provider/PhabricatorAmazonAuthProvider.php', 1489 1491 'PhabricatorAnchorView' => 'view/layout/PhabricatorAnchorView.php', ··· 1786 1788 'PhabricatorConduitLogQuery' => 'applications/conduit/query/PhabricatorConduitLogQuery.php', 1787 1789 'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/PhabricatorConduitMethodCallLog.php', 1788 1790 'PhabricatorConduitMethodQuery' => 'applications/conduit/query/PhabricatorConduitMethodQuery.php', 1791 + 'PhabricatorConduitRequestExceptionHandler' => 'aphront/handler/PhabricatorConduitRequestExceptionHandler.php', 1789 1792 'PhabricatorConduitSearchEngine' => 'applications/conduit/query/PhabricatorConduitSearchEngine.php', 1790 1793 'PhabricatorConduitTestCase' => '__tests__/PhabricatorConduitTestCase.php', 1791 1794 'PhabricatorConduitToken' => 'applications/conduit/storage/PhabricatorConduitToken.php', ··· 1838 1841 'PhabricatorConfigOptionType' => 'applications/config/custom/PhabricatorConfigOptionType.php', 1839 1842 'PhabricatorConfigPHIDModule' => 'applications/config/module/PhabricatorConfigPHIDModule.php', 1840 1843 'PhabricatorConfigProxySource' => 'infrastructure/env/PhabricatorConfigProxySource.php', 1844 + 'PhabricatorConfigRequestExceptionHandlerModule' => 'applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php', 1841 1845 'PhabricatorConfigResponse' => 'applications/config/response/PhabricatorConfigResponse.php', 1842 1846 'PhabricatorConfigSchemaQuery' => 'applications/config/schema/PhabricatorConfigSchemaQuery.php', 1843 1847 'PhabricatorConfigSchemaSpec' => 'applications/config/schema/PhabricatorConfigSchemaSpec.php', ··· 1993 1997 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', 1994 1998 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 1995 1999 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', 2000 + 'PhabricatorDefaultRequestExceptionHandler' => 'aphront/handler/PhabricatorDefaultRequestExceptionHandler.php', 1996 2001 'PhabricatorDesktopNotificationsSettingsPanel' => 'applications/settings/panel/PhabricatorDesktopNotificationsSettingsPanel.php', 1997 2002 'PhabricatorDestructibleInterface' => 'applications/system/interface/PhabricatorDestructibleInterface.php', 1998 2003 'PhabricatorDestructionEngine' => 'applications/system/engine/PhabricatorDestructionEngine.php', ··· 2183 2188 'PhabricatorHelpEditorProtocolController' => 'applications/help/controller/PhabricatorHelpEditorProtocolController.php', 2184 2189 'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/PhabricatorHelpKeyboardShortcutController.php', 2185 2190 'PhabricatorHeraldApplication' => 'applications/herald/application/PhabricatorHeraldApplication.php', 2191 + 'PhabricatorHighSecurityRequestExceptionHandler' => 'aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php', 2186 2192 'PhabricatorHomeApplication' => 'applications/home/application/PhabricatorHomeApplication.php', 2187 2193 'PhabricatorHomeController' => 'applications/home/controller/PhabricatorHomeController.php', 2188 2194 'PhabricatorHomeMainController' => 'applications/home/controller/PhabricatorHomeMainController.php', ··· 2580 2586 'PhabricatorPolicyManagementWorkflow' => 'applications/policy/management/PhabricatorPolicyManagementWorkflow.php', 2581 2587 'PhabricatorPolicyPHIDTypePolicy' => 'applications/policy/phid/PhabricatorPolicyPHIDTypePolicy.php', 2582 2588 'PhabricatorPolicyQuery' => 'applications/policy/query/PhabricatorPolicyQuery.php', 2589 + 'PhabricatorPolicyRequestExceptionHandler' => 'aphront/handler/PhabricatorPolicyRequestExceptionHandler.php', 2583 2590 'PhabricatorPolicyRule' => 'applications/policy/rule/PhabricatorPolicyRule.php', 2584 2591 'PhabricatorPolicyTestCase' => 'applications/policy/__tests__/PhabricatorPolicyTestCase.php', 2585 2592 'PhabricatorPolicyTestObject' => 'applications/policy/__tests__/PhabricatorPolicyTestObject.php', ··· 2667 2674 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 2668 2675 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 2669 2676 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', 2677 + 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', 2670 2678 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', 2671 2679 'PhabricatorRecipientHasBadgeEdgeType' => 'applications/badges/edge/PhabricatorRecipientHasBadgeEdgeType.php', 2672 2680 'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php', ··· 2758 2766 'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php', 2759 2767 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', 2760 2768 'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php', 2769 + 'PhabricatorRequestExceptionHandler' => 'aphront/handler/PhabricatorRequestExceptionHandler.php', 2761 2770 'PhabricatorResourceSite' => 'aphront/site/PhabricatorResourceSite.php', 2762 2771 'PhabricatorRobotsController' => 'applications/system/controller/PhabricatorRobotsController.php', 2763 2772 'PhabricatorS3FileStorageEngine' => 'applications/files/engine/PhabricatorS3FileStorageEngine.php', ··· 3788 3797 'AphrontRedirectResponseTestCase' => 'PhabricatorTestCase', 3789 3798 'AphrontReloadResponse' => 'AphrontRedirectResponse', 3790 3799 'AphrontRequest' => 'Phobject', 3800 + 'AphrontRequestExceptionHandler' => 'Phobject', 3791 3801 'AphrontRequestTestCase' => 'PhabricatorTestCase', 3792 3802 'AphrontResponse' => 'Phobject', 3793 3803 'AphrontRoutingMap' => 'Phobject', ··· 5303 5313 'PhabricatorActionView' => 'AphrontView', 5304 5314 'PhabricatorActivitySettingsPanel' => 'PhabricatorSettingsPanel', 5305 5315 'PhabricatorAdministratorsPolicyRule' => 'PhabricatorPolicyRule', 5316 + 'PhabricatorAjaxRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 5306 5317 'PhabricatorAlmanacApplication' => 'PhabricatorApplication', 5307 5318 'PhabricatorAmazonAuthProvider' => 'PhabricatorOAuth2AuthProvider', 5308 5319 'PhabricatorAnchorView' => 'AphrontView', ··· 5667 5678 'PhabricatorPolicyInterface', 5668 5679 ), 5669 5680 'PhabricatorConduitMethodQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 5681 + 'PhabricatorConduitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 5670 5682 'PhabricatorConduitSearchEngine' => 'PhabricatorApplicationSearchEngine', 5671 5683 'PhabricatorConduitTestCase' => 'PhabricatorTestCase', 5672 5684 'PhabricatorConduitToken' => array( ··· 5729 5741 'PhabricatorConfigOptionType' => 'Phobject', 5730 5742 'PhabricatorConfigPHIDModule' => 'PhabricatorConfigModule', 5731 5743 'PhabricatorConfigProxySource' => 'PhabricatorConfigSource', 5744 + 'PhabricatorConfigRequestExceptionHandlerModule' => 'PhabricatorConfigModule', 5732 5745 'PhabricatorConfigResponse' => 'AphrontStandaloneHTMLResponse', 5733 5746 'PhabricatorConfigSchemaQuery' => 'Phobject', 5734 5747 'PhabricatorConfigSchemaSpec' => 'Phobject', ··· 5913 5926 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', 5914 5927 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorSettingsPanel', 5915 5928 'PhabricatorDebugController' => 'PhabricatorController', 5929 + 'PhabricatorDefaultRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 5916 5930 'PhabricatorDesktopNotificationsSettingsPanel' => 'PhabricatorSettingsPanel', 5917 5931 'PhabricatorDestructionEngine' => 'Phobject', 5918 5932 'PhabricatorDeveloperConfigOptions' => 'PhabricatorApplicationConfigOptions', ··· 6138 6152 'PhabricatorHelpEditorProtocolController' => 'PhabricatorHelpController', 6139 6153 'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController', 6140 6154 'PhabricatorHeraldApplication' => 'PhabricatorApplication', 6155 + 'PhabricatorHighSecurityRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 6141 6156 'PhabricatorHomeApplication' => 'PhabricatorApplication', 6142 6157 'PhabricatorHomeController' => 'PhabricatorController', 6143 6158 'PhabricatorHomeMainController' => 'PhabricatorHomeController', ··· 6587 6602 'PhabricatorPolicyManagementWorkflow' => 'PhabricatorManagementWorkflow', 6588 6603 'PhabricatorPolicyPHIDTypePolicy' => 'PhabricatorPHIDType', 6589 6604 'PhabricatorPolicyQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 6605 + 'PhabricatorPolicyRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 6590 6606 'PhabricatorPolicyRule' => 'Phobject', 6591 6607 'PhabricatorPolicyTestCase' => 'PhabricatorTestCase', 6592 6608 'PhabricatorPolicyTestObject' => array( ··· 6702 6718 'Phobject', 6703 6719 'Iterator', 6704 6720 ), 6721 + 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 6705 6722 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 6706 6723 'PhabricatorRecipientHasBadgeEdgeType' => 'PhabricatorEdgeType', 6707 6724 'PhabricatorRedirectController' => 'PhabricatorController', ··· 6828 6845 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', 6829 6846 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', 6830 6847 'PhabricatorRepositoryVersion' => 'Phobject', 6848 + 'PhabricatorRequestExceptionHandler' => 'AphrontRequestExceptionHandler', 6831 6849 'PhabricatorResourceSite' => 'PhabricatorSite', 6832 6850 'PhabricatorRobotsController' => 'PhabricatorController', 6833 6851 'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
+62 -2
src/aphront/configuration/AphrontApplicationConfiguration.php
··· 3 3 /** 4 4 * @task routing URI Routing 5 5 * @task response Response Handling 6 + * @task exception Exception Handling 6 7 */ 7 8 abstract class AphrontApplicationConfiguration extends Phobject { 8 9 ··· 11 12 private $path; 12 13 private $console; 13 14 14 - abstract public function getApplicationName(); 15 15 abstract public function buildRequest(); 16 16 abstract public function build404Controller(); 17 17 abstract public function buildRedirectController($uri, $external); ··· 482 482 483 483 484 484 /** 485 - * Verifies that the erturn value from an 485 + * Verifies that the return value from an 486 486 * @{class:AphrontResponseProducerInterface} is of an allowed type. 487 487 * 488 488 * @param AphrontResponseProducerInterface Object which produced ··· 512 512 513 513 514 514 /** 515 + * Verifies that the return value from an 516 + * @{class:AphrontRequestExceptionHandler} is of an allowed type. 517 + * 518 + * @param AphrontRequestExceptionHandler Object which produced this 519 + * response. 520 + * @param wild Supposedly valid response. 521 + * @return void 522 + * @task response 523 + */ 524 + private function validateErrorHandlerResponse( 525 + AphrontRequestExceptionHandler $handler, 526 + $response) { 527 + 528 + if ($this->isValidResponseObject($response)) { 529 + return; 530 + } 531 + 532 + throw new Exception( 533 + pht( 534 + 'Exception handler "%s" returned an invalid response from call to '. 535 + '"%s". This method must return an object of class "%s", or an object '. 536 + 'which implements the "%s" interface.', 537 + get_class($handler), 538 + 'handleRequestException()', 539 + 'AphrontResponse', 540 + 'AphrontResponseProducerInterface')); 541 + } 542 + 543 + 544 + /** 515 545 * Resolves a response object into an @{class:AphrontResponse}. 516 546 * 517 547 * Controllers are permitted to return actual responses of class ··· 569 599 } 570 600 571 601 return $response; 602 + } 603 + 604 + 605 + /* -( Error Handling )----------------------------------------------------- */ 606 + 607 + 608 + /** 609 + * Convert an exception which has escaped the controller into a response. 610 + * 611 + * This method delegates exception handling to available subclasses of 612 + * @{class:AphrontRequestExceptionHandler}. 613 + * 614 + * @param Exception Exception which needs to be handled. 615 + * @return wild Response or response producer, or null if no available 616 + * handler can produce a response. 617 + * @task exception 618 + */ 619 + private function handleException(Exception $ex) { 620 + $handlers = AphrontRequestExceptionHandler::getAllHandlers(); 621 + 622 + $request = $this->getRequest(); 623 + foreach ($handlers as $handler) { 624 + if ($handler->canHandleRequestException($request, $ex)) { 625 + $response = $handler->handleRequestException($request, $ex); 626 + $this->validateErrorHandlerResponse($handler, $response); 627 + return $response; 628 + } 629 + } 630 + 631 + throw $ex; 572 632 } 573 633 574 634
-213
src/aphront/configuration/AphrontDefaultApplicationConfiguration.php
··· 8 8 class AphrontDefaultApplicationConfiguration 9 9 extends AphrontApplicationConfiguration { 10 10 11 - public function __construct() {} 12 - 13 - public function getApplicationName() { 14 - return 'aphront-default'; 15 - } 16 - 17 11 /** 18 12 * @phutil-external-symbol class PhabricatorStartup 19 13 */ ··· 48 42 $request->setCookiePrefix($cookie_prefix); 49 43 50 44 return $request; 51 - } 52 - 53 - public function handleException(Exception $ex) { 54 - $request = $this->getRequest(); 55 - 56 - // For Conduit requests, return a Conduit response. 57 - if ($request->isConduit()) { 58 - $response = new ConduitAPIResponse(); 59 - $response->setErrorCode(get_class($ex)); 60 - $response->setErrorInfo($ex->getMessage()); 61 - 62 - return id(new AphrontJSONResponse()) 63 - ->setAddJSONShield(false) 64 - ->setContent($response->toDictionary()); 65 - } 66 - 67 - // For non-workflow requests, return a Ajax response. 68 - if ($request->isAjax() && !$request->isWorkflow()) { 69 - // Log these; they don't get shown on the client and can be difficult 70 - // to debug. 71 - phlog($ex); 72 - 73 - $response = new AphrontAjaxResponse(); 74 - $response->setError( 75 - array( 76 - 'code' => get_class($ex), 77 - 'info' => $ex->getMessage(), 78 - )); 79 - return $response; 80 - } 81 - 82 - $user = $request->getUser(); 83 - if (!$user) { 84 - // If we hit an exception very early, we won't have a user. 85 - $user = new PhabricatorUser(); 86 - } 87 - 88 - if ($ex instanceof PhabricatorSystemActionRateLimitException) { 89 - $dialog = id(new AphrontDialogView()) 90 - ->setTitle(pht('Slow Down!')) 91 - ->setUser($user) 92 - ->setErrors(array(pht('You are being rate limited.'))) 93 - ->appendParagraph($ex->getMessage()) 94 - ->appendParagraph($ex->getRateExplanation()) 95 - ->addCancelButton('/', pht('Okaaaaaaaaaaaaaay...')); 96 - 97 - $response = new AphrontDialogResponse(); 98 - $response->setDialog($dialog); 99 - return $response; 100 - } 101 - 102 - if ($ex instanceof PhabricatorAuthHighSecurityRequiredException) { 103 - 104 - $form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm( 105 - $ex->getFactors(), 106 - $ex->getFactorValidationResults(), 107 - $user, 108 - $request); 109 - 110 - $dialog = id(new AphrontDialogView()) 111 - ->setUser($user) 112 - ->setTitle(pht('Entering High Security')) 113 - ->setShortTitle(pht('Security Checkpoint')) 114 - ->setWidth(AphrontDialogView::WIDTH_FORM) 115 - ->addHiddenInput(AphrontRequest::TYPE_HISEC, true) 116 - ->setErrors( 117 - array( 118 - pht( 119 - 'You are taking an action which requires you to enter '. 120 - 'high security.'), 121 - )) 122 - ->appendParagraph( 123 - pht( 124 - 'High security mode helps protect your account from security '. 125 - 'threats, like session theft or someone messing with your stuff '. 126 - 'while you\'re grabbing a coffee. To enter high security mode, '. 127 - 'confirm your credentials.')) 128 - ->appendChild($form->buildLayoutView()) 129 - ->appendParagraph( 130 - pht( 131 - 'Your account will remain in high security mode for a short '. 132 - 'period of time. When you are finished taking sensitive '. 133 - 'actions, you should leave high security.')) 134 - ->setSubmitURI($request->getPath()) 135 - ->addCancelButton($ex->getCancelURI()) 136 - ->addSubmitButton(pht('Enter High Security')); 137 - 138 - $request_parameters = $request->getPassthroughRequestParameters( 139 - $respect_quicksand = true); 140 - foreach ($request_parameters as $key => $value) { 141 - $dialog->addHiddenInput($key, $value); 142 - } 143 - 144 - $response = new AphrontDialogResponse(); 145 - $response->setDialog($dialog); 146 - return $response; 147 - } 148 - 149 - if ($ex instanceof PhabricatorPolicyException) { 150 - if (!$user->isLoggedIn()) { 151 - // If the user isn't logged in, just give them a login form. This is 152 - // probably a generally more useful response than a policy dialog that 153 - // they have to click through to get a login form. 154 - // 155 - // Possibly we should add a header here like "you need to login to see 156 - // the thing you are trying to look at". 157 - $login_controller = new PhabricatorAuthStartController(); 158 - $login_controller->setRequest($request); 159 - 160 - $auth_app_class = 'PhabricatorAuthApplication'; 161 - $auth_app = PhabricatorApplication::getByClass($auth_app_class); 162 - $login_controller->setCurrentApplication($auth_app); 163 - 164 - return $login_controller->handleRequest($request); 165 - } 166 - 167 - $content = array( 168 - phutil_tag( 169 - 'div', 170 - array( 171 - 'class' => 'aphront-policy-rejection', 172 - ), 173 - $ex->getRejection()), 174 - ); 175 - 176 - $list = null; 177 - if ($ex->getCapabilityName()) { 178 - $list = $ex->getMoreInfo(); 179 - foreach ($list as $key => $item) { 180 - $list[$key] = $item; 181 - } 182 - 183 - $content[] = phutil_tag( 184 - 'div', 185 - array( 186 - 'class' => 'aphront-capability-details', 187 - ), 188 - pht('Users with the "%s" capability:', $ex->getCapabilityName())); 189 - 190 - } 191 - 192 - $dialog = id(new AphrontDialogView()) 193 - ->setTitle($ex->getTitle()) 194 - ->setClass('aphront-access-dialog') 195 - ->setUser($user) 196 - ->appendChild($content); 197 - 198 - if ($list) { 199 - $dialog->appendList($list); 200 - } 201 - 202 - if ($this->getRequest()->isAjax()) { 203 - $dialog->addCancelButton('/', pht('Close')); 204 - } else { 205 - $dialog->addCancelButton('/', pht('OK')); 206 - } 207 - 208 - $response = new AphrontDialogResponse(); 209 - $response->setDialog($dialog); 210 - return $response; 211 - } 212 - 213 - // Always log the unhandled exception. 214 - phlog($ex); 215 - 216 - $class = get_class($ex); 217 - $message = $ex->getMessage(); 218 - 219 - if ($ex instanceof AphrontSchemaQueryException) { 220 - $message .= "\n\n".pht( 221 - "NOTE: This usually indicates that the MySQL schema has not been ". 222 - "properly upgraded. Run '%s' to ensure your schema is up to date.", 223 - 'bin/storage upgrade'); 224 - } 225 - 226 - if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { 227 - $trace = id(new AphrontStackTraceView()) 228 - ->setUser($user) 229 - ->setTrace($ex->getTrace()); 230 - } else { 231 - $trace = null; 232 - } 233 - 234 - $content = phutil_tag( 235 - 'div', 236 - array('class' => 'aphront-unhandled-exception'), 237 - array( 238 - phutil_tag('div', array('class' => 'exception-message'), $message), 239 - $trace, 240 - )); 241 - 242 - $dialog = new AphrontDialogView(); 243 - $dialog 244 - ->setTitle(pht('Unhandled Exception ("%s")', $class)) 245 - ->setClass('aphront-exception-dialog') 246 - ->setUser($user) 247 - ->appendChild($content); 248 - 249 - if ($this->getRequest()->isAjax()) { 250 - $dialog->addCancelButton('/', pht('Close')); 251 - } 252 - 253 - $response = new AphrontDialogResponse(); 254 - $response->setDialog($dialog); 255 - $response->setHTTPResponseCode(500); 256 - 257 - return $response; 258 45 } 259 46 260 47 public function build404Controller() {
+36
src/aphront/handler/AphrontRequestExceptionHandler.php
··· 1 + <?php 2 + 3 + /** 4 + * React to an unhandled exception escaping request handling in a controller 5 + * and convert it into a response. 6 + * 7 + * These handlers are generally used to render error pages, but they may 8 + * also perform more specialized handling in situations where an error page 9 + * is not appropriate. 10 + */ 11 + abstract class AphrontRequestExceptionHandler extends Phobject { 12 + 13 + abstract public function getRequestExceptionHandlerPriority(); 14 + 15 + public function shouldLogException( 16 + AphrontRequest $request, 17 + Exception $ex) { 18 + return null; 19 + } 20 + 21 + abstract public function canHandleRequestException( 22 + AphrontRequest $request, 23 + Exception $ex); 24 + 25 + abstract public function handleRequestException( 26 + AphrontRequest $request, 27 + Exception $ex); 28 + 29 + final public static function getAllHandlers() { 30 + return id(new PhutilClassMapQuery()) 31 + ->setAncestorClass(__CLASS__) 32 + ->setSortMethod('getRequestExceptionHandlerPriority') 33 + ->execute(); 34 + } 35 + 36 + }
+38
src/aphront/handler/PhabricatorAjaxRequestExceptionHandler.php
··· 1 + <?php 2 + 3 + final class PhabricatorAjaxRequestExceptionHandler 4 + extends PhabricatorRequestExceptionHandler { 5 + 6 + public function getRequestExceptionHandlerPriority() { 7 + return 110000; 8 + } 9 + 10 + public function getRequestExceptionHandlerDescription() { 11 + return pht('Responds to requests made by AJAX clients.'); 12 + } 13 + 14 + public function canHandleRequestException( 15 + AphrontRequest $request, 16 + Exception $ex) { 17 + // For non-workflow requests, return a Ajax response. 18 + return ($request->isAjax() && !$request->isWorkflow()); 19 + } 20 + 21 + public function handleRequestException( 22 + AphrontRequest $request, 23 + Exception $ex) { 24 + 25 + // Log these; they don't get shown on the client and can be difficult 26 + // to debug. 27 + phlog($ex); 28 + 29 + $response = new AphrontAjaxResponse(); 30 + $response->setError( 31 + array( 32 + 'code' => get_class($ex), 33 + 'info' => $ex->getMessage(), 34 + )); 35 + return $response; 36 + } 37 + 38 + }
+33
src/aphront/handler/PhabricatorConduitRequestExceptionHandler.php
··· 1 + <?php 2 + 3 + final class PhabricatorConduitRequestExceptionHandler 4 + extends PhabricatorRequestExceptionHandler { 5 + 6 + public function getRequestExceptionHandlerPriority() { 7 + return 100000; 8 + } 9 + 10 + public function getRequestExceptionHandlerDescription() { 11 + return pht('Responds to requests made by Conduit clients.'); 12 + } 13 + 14 + public function canHandleRequestException( 15 + AphrontRequest $request, 16 + Exception $ex) { 17 + return $request->isConduit(); 18 + } 19 + 20 + public function handleRequestException( 21 + AphrontRequest $request, 22 + Exception $ex) { 23 + 24 + $response = id(new ConduitAPIResponse()) 25 + ->setErrorCode(get_class($ex)) 26 + ->setErrorInfo($ex->getMessage()); 27 + 28 + return id(new AphrontJSONResponse()) 29 + ->setAddJSONShield(false) 30 + ->setContent($response->toDictionary()); 31 + } 32 + 33 + }
+76
src/aphront/handler/PhabricatorDefaultRequestExceptionHandler.php
··· 1 + <?php 2 + 3 + final class PhabricatorDefaultRequestExceptionHandler 4 + extends PhabricatorRequestExceptionHandler { 5 + 6 + public function getRequestExceptionHandlerPriority() { 7 + return 900000; 8 + } 9 + 10 + public function getRequestExceptionHandlerDescription() { 11 + return pht('Handles all other exceptions.'); 12 + } 13 + 14 + public function canHandleRequestException( 15 + AphrontRequest $request, 16 + Exception $ex) { 17 + 18 + if (!$this->isPhabricatorSite($request)) { 19 + return false; 20 + } 21 + 22 + return true; 23 + } 24 + 25 + public function handleRequestException( 26 + AphrontRequest $request, 27 + Exception $ex) { 28 + 29 + $viewer = $this->getViewer($request); 30 + 31 + // Always log the unhandled exception. 32 + phlog($ex); 33 + 34 + $class = get_class($ex); 35 + $message = $ex->getMessage(); 36 + 37 + if ($ex instanceof AphrontSchemaQueryException) { 38 + $message .= "\n\n".pht( 39 + "NOTE: This usually indicates that the MySQL schema has not been ". 40 + "properly upgraded. Run '%s' to ensure your schema is up to date.", 41 + 'bin/storage upgrade'); 42 + } 43 + 44 + if (PhabricatorEnv::getEnvConfig('phabricator.developer-mode')) { 45 + $trace = id(new AphrontStackTraceView()) 46 + ->setUser($viewer) 47 + ->setTrace($ex->getTrace()); 48 + } else { 49 + $trace = null; 50 + } 51 + 52 + $content = phutil_tag( 53 + 'div', 54 + array('class' => 'aphront-unhandled-exception'), 55 + array( 56 + phutil_tag('div', array('class' => 'exception-message'), $message), 57 + $trace, 58 + )); 59 + 60 + $dialog = new AphrontDialogView(); 61 + $dialog 62 + ->setTitle(pht('Unhandled Exception ("%s")', $class)) 63 + ->setClass('aphront-exception-dialog') 64 + ->setUser($viewer) 65 + ->appendChild($content); 66 + 67 + if ($request->isAjax()) { 68 + $dialog->addCancelButton('/', pht('Close')); 69 + } 70 + 71 + return id(new AphrontDialogResponse()) 72 + ->setDialog($dialog) 73 + ->setHTTPResponseCode(500); 74 + } 75 + 76 + }
+76
src/aphront/handler/PhabricatorHighSecurityRequestExceptionHandler.php
··· 1 + <?php 2 + 3 + final class PhabricatorHighSecurityRequestExceptionHandler 4 + extends PhabricatorRequestExceptionHandler { 5 + 6 + public function getRequestExceptionHandlerPriority() { 7 + return 310000; 8 + } 9 + 10 + public function getRequestExceptionHandlerDescription() { 11 + return pht( 12 + 'Handles high security exceptions which occur when a user needs '. 13 + 'to present MFA credentials to take an action.'); 14 + } 15 + 16 + public function canHandleRequestException( 17 + AphrontRequest $request, 18 + Exception $ex) { 19 + 20 + if (!$this->isPhabricatorSite($request)) { 21 + return false; 22 + } 23 + 24 + return ($ex instanceof PhabricatorAuthHighSecurityRequiredException); 25 + } 26 + 27 + public function handleRequestException( 28 + AphrontRequest $request, 29 + Exception $ex) { 30 + 31 + $viewer = $this->getViewer($request); 32 + 33 + $form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm( 34 + $ex->getFactors(), 35 + $ex->getFactorValidationResults(), 36 + $viewer, 37 + $request); 38 + 39 + $dialog = id(new AphrontDialogView()) 40 + ->setUser($viewer) 41 + ->setTitle(pht('Entering High Security')) 42 + ->setShortTitle(pht('Security Checkpoint')) 43 + ->setWidth(AphrontDialogView::WIDTH_FORM) 44 + ->addHiddenInput(AphrontRequest::TYPE_HISEC, true) 45 + ->setErrors( 46 + array( 47 + pht( 48 + 'You are taking an action which requires you to enter '. 49 + 'high security.'), 50 + )) 51 + ->appendParagraph( 52 + pht( 53 + 'High security mode helps protect your account from security '. 54 + 'threats, like session theft or someone messing with your stuff '. 55 + 'while you\'re grabbing a coffee. To enter high security mode, '. 56 + 'confirm your credentials.')) 57 + ->appendChild($form->buildLayoutView()) 58 + ->appendParagraph( 59 + pht( 60 + 'Your account will remain in high security mode for a short '. 61 + 'period of time. When you are finished taking sensitive '. 62 + 'actions, you should leave high security.')) 63 + ->setSubmitURI($request->getPath()) 64 + ->addCancelButton($ex->getCancelURI()) 65 + ->addSubmitButton(pht('Enter High Security')); 66 + 67 + $request_parameters = $request->getPassthroughRequestParameters( 68 + $respect_quicksand = true); 69 + foreach ($request_parameters as $key => $value) { 70 + $dialog->addHiddenInput($key, $value); 71 + } 72 + 73 + return $dialog; 74 + } 75 + 76 + }
+93
src/aphront/handler/PhabricatorPolicyRequestExceptionHandler.php
··· 1 + <?php 2 + 3 + final class PhabricatorPolicyRequestExceptionHandler 4 + extends PhabricatorRequestExceptionHandler { 5 + 6 + public function getRequestExceptionHandlerPriority() { 7 + return 320000; 8 + } 9 + 10 + public function getRequestExceptionHandlerDescription() { 11 + return pht( 12 + 'Handles policy exceptions which occur when a user tries to '. 13 + 'do something they do not have permission to do.'); 14 + } 15 + 16 + public function canHandleRequestException( 17 + AphrontRequest $request, 18 + Exception $ex) { 19 + 20 + if (!$this->isPhabricatorSite($request)) { 21 + return false; 22 + } 23 + 24 + return ($ex instanceof PhabricatorPolicyException); 25 + } 26 + 27 + public function handleRequestException( 28 + AphrontRequest $request, 29 + Exception $ex) { 30 + 31 + $viewer = $this->getViewer($request); 32 + 33 + if (!$viewer->isLoggedIn()) { 34 + // If the user isn't logged in, just give them a login form. This is 35 + // probably a generally more useful response than a policy dialog that 36 + // they have to click through to get a login form. 37 + // 38 + // Possibly we should add a header here like "you need to login to see 39 + // the thing you are trying to look at". 40 + $auth_app_class = 'PhabricatorAuthApplication'; 41 + $auth_app = PhabricatorApplication::getByClass($auth_app_class); 42 + 43 + return id(new PhabricatorAuthStartController()) 44 + ->setRequest($request) 45 + ->setCurrentApplication($auth_app) 46 + ->handleRequest($request); 47 + } 48 + 49 + $content = array( 50 + phutil_tag( 51 + 'div', 52 + array( 53 + 'class' => 'aphront-policy-rejection', 54 + ), 55 + $ex->getRejection()), 56 + ); 57 + 58 + $list = null; 59 + if ($ex->getCapabilityName()) { 60 + $list = $ex->getMoreInfo(); 61 + foreach ($list as $key => $item) { 62 + $list[$key] = $item; 63 + } 64 + 65 + $content[] = phutil_tag( 66 + 'div', 67 + array( 68 + 'class' => 'aphront-capability-details', 69 + ), 70 + pht('Users with the "%s" capability:', $ex->getCapabilityName())); 71 + 72 + } 73 + 74 + $dialog = id(new AphrontDialogView()) 75 + ->setTitle($ex->getTitle()) 76 + ->setClass('aphront-access-dialog') 77 + ->setUser($viewer) 78 + ->appendChild($content); 79 + 80 + if ($list) { 81 + $dialog->appendList($list); 82 + } 83 + 84 + if ($request->isAjax()) { 85 + $dialog->addCancelButton('/', pht('Close')); 86 + } else { 87 + $dialog->addCancelButton('/', pht('OK')); 88 + } 89 + 90 + return $dialog; 91 + } 92 + 93 + }
+42
src/aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php
··· 1 + <?php 2 + 3 + final class PhabricatorRateLimitRequestExceptionHandler 4 + extends PhabricatorRequestExceptionHandler { 5 + 6 + public function getRequestExceptionHandlerPriority() { 7 + return 300000; 8 + } 9 + 10 + public function getRequestExceptionHandlerDescription() { 11 + return pht( 12 + 'Handles action rate limiting exceptions which occur when a user '. 13 + 'does something too frequently.'); 14 + } 15 + 16 + public function canHandleRequestException( 17 + AphrontRequest $request, 18 + Exception $ex) { 19 + 20 + if (!$this->isPhabricatorSite($request)) { 21 + return false; 22 + } 23 + 24 + return ($ex instanceof PhabricatorSystemActionRateLimitException); 25 + } 26 + 27 + public function handleRequestException( 28 + AphrontRequest $request, 29 + Exception $ex) { 30 + 31 + $viewer = $this->getViewer($request); 32 + 33 + return id(new AphrontDialogView()) 34 + ->setTitle(pht('Slow Down!')) 35 + ->setUser($viewer) 36 + ->setErrors(array(pht('You are being rate limited.'))) 37 + ->appendParagraph($ex->getMessage()) 38 + ->appendParagraph($ex->getRateExplanation()) 39 + ->addCancelButton('/', pht('Okaaaaaaaaaaaaaay...')); 40 + } 41 + 42 + }
+26
src/aphront/handler/PhabricatorRequestExceptionHandler.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorRequestExceptionHandler 4 + extends AphrontRequestExceptionHandler { 5 + 6 + protected function isPhabricatorSite(AphrontRequest $request) { 7 + $site = $request->getSite(); 8 + if (!$site) { 9 + return false; 10 + } 11 + 12 + return ($site instanceof PhabricatorSite); 13 + } 14 + 15 + protected function getViewer(AphrontRequest $request) { 16 + $viewer = $request->getUser(); 17 + 18 + if ($viewer) { 19 + return $viewer; 20 + } 21 + 22 + // If we hit an exception very early, we won't have a user yet. 23 + return new PhabricatorUser(); 24 + } 25 + 26 + }
+1 -1
src/applications/config/module/PhabricatorConfigEdgeModule.php
··· 41 41 42 42 return id(new PHUIObjectBoxView()) 43 43 ->setHeaderText(pht('Edge Types')) 44 - ->appendChild($table); 44 + ->setTable($table); 45 45 } 46 46 47 47 }
+1 -1
src/applications/config/module/PhabricatorConfigPHIDModule.php
··· 41 41 42 42 return id(new PHUIObjectBoxView()) 43 43 ->setHeaderText(pht('PHID Types')) 44 - ->appendChild($table); 44 + ->setTable($table); 45 45 } 46 46 47 47 }
+47
src/applications/config/module/PhabricatorConfigRequestExceptionHandlerModule.php
··· 1 + <?php 2 + 3 + final class PhabricatorConfigRequestExceptionHandlerModule 4 + extends PhabricatorConfigModule { 5 + 6 + public function getModuleKey() { 7 + return 'exception-handler'; 8 + } 9 + 10 + public function getModuleName() { 11 + return pht('Exception Handlers'); 12 + } 13 + 14 + public function renderModuleStatus(AphrontRequest $request) { 15 + $viewer = $request->getViewer(); 16 + 17 + $handlers = AphrontRequestExceptionHandler::getAllHandlers(); 18 + 19 + $rows = array(); 20 + foreach ($handlers as $key => $handler) { 21 + $rows[] = array( 22 + $handler->getRequestExceptionHandlerPriority(), 23 + $key, 24 + $handler->getRequestExceptionHandlerDescription(), 25 + ); 26 + } 27 + 28 + $table = id(new AphrontTableView($rows)) 29 + ->setHeaders( 30 + array( 31 + pht('Priority'), 32 + pht('Class'), 33 + pht('Description'), 34 + )) 35 + ->setColumnClasses( 36 + array( 37 + null, 38 + 'pri', 39 + 'wide', 40 + )); 41 + 42 + return id(new PHUIObjectBoxView()) 43 + ->setHeaderText(pht('Exception Handlers')) 44 + ->setTable($table); 45 + } 46 + 47 + }
+1 -1
src/applications/config/module/PhabricatorConfigSiteModule.php
··· 40 40 41 41 return id(new PHUIObjectBoxView()) 42 42 ->setHeaderText(pht('Sites')) 43 - ->appendChild($table); 43 + ->setTable($table); 44 44 } 45 45 46 46 }