@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.

Modernize OAuthserver and provide more context on "no permission" exception

Summary:
Ref T7173. Depends on D14049. Now that Phacility can install custom exception handlers, this puts enough information on the exception so that we can figure out what to do with it.

- Generally modernize some of this code.
- Add some more information to PolicyExceptions so the new RequestExceptionHandler can handle them properly.

Test Plan: Failed authorizations, then succeeded authorizations. See next diff.

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T7173

Differential Revision: https://secure.phabricator.com/D14050

+106 -130
+2 -2
src/__phutil_library_map__.php
··· 6376 6376 'PhabricatorOAuthServer' => 'Phobject', 6377 6377 'PhabricatorOAuthServerAccessToken' => 'PhabricatorOAuthServerDAO', 6378 6378 'PhabricatorOAuthServerApplication' => 'PhabricatorApplication', 6379 - 'PhabricatorOAuthServerAuthController' => 'PhabricatorAuthController', 6379 + 'PhabricatorOAuthServerAuthController' => 'PhabricatorOAuthServerController', 6380 6380 'PhabricatorOAuthServerAuthorizationCode' => 'PhabricatorOAuthServerDAO', 6381 6381 'PhabricatorOAuthServerAuthorizationsSettingsPanel' => 'PhabricatorSettingsPanel', 6382 6382 'PhabricatorOAuthServerClient' => array( ··· 6394 6394 'PhabricatorOAuthServerScope' => 'Phobject', 6395 6395 'PhabricatorOAuthServerTestCase' => 'PhabricatorTestCase', 6396 6396 'PhabricatorOAuthServerTestController' => 'PhabricatorOAuthServerController', 6397 - 'PhabricatorOAuthServerTokenController' => 'PhabricatorAuthController', 6397 + 'PhabricatorOAuthServerTokenController' => 'PhabricatorOAuthServerController', 6398 6398 'PhabricatorObjectHandle' => array( 6399 6399 'Phobject', 6400 6400 'PhabricatorPolicyInterface',
+9 -4
src/applications/fund/phortune/FundBackerProduct.php
··· 21 21 22 22 public function getName(PhortuneProduct $product) { 23 23 $initiative = $this->getInitiative(); 24 - return pht( 25 - 'Fund %s %s', 26 - $initiative->getMonogram(), 27 - $initiative->getName()); 24 + 25 + if (!$initiative) { 26 + return pht('Fund <Unknown Initiative>'); 27 + } else { 28 + return pht( 29 + 'Fund %s %s', 30 + $initiative->getMonogram(), 31 + $initiative->getName()); 32 + } 28 33 } 29 34 30 35 public function getPriceAsCurrency(PhortuneProduct $product) {
+24 -38
src/applications/oauthserver/controller/PhabricatorOAuthServerAuthController.php
··· 1 1 <?php 2 2 3 3 final class PhabricatorOAuthServerAuthController 4 - extends PhabricatorAuthController { 4 + extends PhabricatorOAuthServerController { 5 5 6 - public function shouldRequireLogin() { 7 - return true; 8 - } 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 9 8 10 - public function processRequest() { 11 - $request = $this->getRequest(); 12 - $viewer = $request->getUser(); 13 - 14 - $server = new PhabricatorOAuthServer(); 15 - $client_phid = $request->getStr('client_id'); 16 - $scope = $request->getStr('scope'); 17 - $redirect_uri = $request->getStr('redirect_uri'); 9 + $server = new PhabricatorOAuthServer(); 10 + $client_phid = $request->getStr('client_id'); 11 + $scope = $request->getStr('scope'); 12 + $redirect_uri = $request->getStr('redirect_uri'); 18 13 $response_type = $request->getStr('response_type'); 19 14 20 15 // state is an opaque value the client sent us for their own purposes ··· 30 25 phutil_tag('strong', array(), 'client_id'))); 31 26 } 32 27 28 + // We require that users must be able to see an OAuth application 29 + // in order to authorize it. This allows an application's visibility 30 + // policy to be used to restrict authorized users. 31 + try { 32 + $client = id(new PhabricatorOAuthServerClientQuery()) 33 + ->setViewer($viewer) 34 + ->withPHIDs(array($client_phid)) 35 + ->executeOne(); 36 + } catch (PhabricatorPolicyException $ex) { 37 + $ex->setContext(self::CONTEXT_AUTHORIZE); 38 + throw $ex; 39 + } 40 + 33 41 $server->setUser($viewer); 34 42 $is_authorized = false; 35 43 $authorization = null; ··· 39 47 // one giant try / catch around all the exciting database stuff so we 40 48 // can return a 'server_error' response if something goes wrong! 41 49 try { 42 - try { 43 - $client = id(new PhabricatorOAuthServerClientQuery()) 44 - ->setViewer($viewer) 45 - ->withPHIDs(array($client_phid)) 46 - ->executeOne(); 47 - } catch (PhabricatorPolicyException $ex) { 48 - // We require that users must be able to see an OAuth application 49 - // in order to authorize it. This allows an application's visibility 50 - // policy to be used to restrict authorized users. 51 - 52 - // None of the OAuth error responses are a perfect fit for this, but 53 - // 'invalid_client' seems closest. 54 - return $this->buildErrorResponse( 55 - 'invalid_client', 56 - pht('Not Authorized'), 57 - pht('You are not authorized to authenticate.')); 58 - } 59 - 60 50 if (!$client) { 61 51 return $this->buildErrorResponse( 62 52 'invalid_request', ··· 211 201 } else { 212 202 $desired_scopes = $scope; 213 203 } 204 + 214 205 if (!PhabricatorOAuthServerScope::validateScopesDict($desired_scopes)) { 215 206 return $this->buildErrorResponse( 216 207 'invalid_scope', ··· 236 227 'error_description' => $cancel_msg, 237 228 )); 238 229 239 - $dialog = id(new AphrontDialogView()) 240 - ->setUser($viewer) 230 + return $this->newDialog() 231 + ->setShortTitle(pht('Authorize Access')) 241 232 ->setTitle(pht('Authorize "%s"?', $name)) 242 233 ->setSubmitURI($request->getRequestURI()->getPath()) 243 234 ->setWidth(AphrontDialogView::WIDTH_FORM) ··· 250 241 ->appendChild($form->buildLayoutView()) 251 242 ->addSubmitButton(pht('Authorize Access')) 252 243 ->addCancelButton((string)$cancel_uri, pht('Do Not Authorize')); 253 - 254 - return id(new AphrontDialogResponse())->setDialog($dialog); 255 244 } 256 245 257 246 258 247 private function buildErrorResponse($code, $title, $message) { 259 248 $viewer = $this->getRequest()->getUser(); 260 249 261 - $dialog = id(new AphrontDialogView()) 262 - ->setUser($viewer) 250 + return $this->newDialog() 263 251 ->setTitle(pht('OAuth: %s', $title)) 264 252 ->appendParagraph($message) 265 253 ->appendParagraph( 266 254 pht('OAuth Error Code: %s', phutil_tag('tt', array(), $code))) 267 255 ->addCancelButton('/', pht('Alas!')); 268 - 269 - return id(new AphrontDialogResponse())->setDialog($dialog); 270 256 } 271 257 272 258
+6 -51
src/applications/oauthserver/controller/PhabricatorOAuthServerController.php
··· 3 3 abstract class PhabricatorOAuthServerController 4 4 extends PhabricatorController { 5 5 6 - public function buildStandardPageResponse($view, array $data) { 7 - $user = $this->getRequest()->getUser(); 8 - $page = $this->buildStandardPageView(); 9 - $page->setApplicationName(pht('OAuth Server')); 10 - $page->setBaseURI('/oauthserver/'); 11 - $page->setTitle(idx($data, 'title')); 6 + const CONTEXT_AUTHORIZE = 'oauthserver.authorize'; 12 7 13 - $nav = new AphrontSideNavFilterView(); 14 - $nav->setBaseURI(new PhutilURI('/oauthserver/')); 15 - $nav->addLabel(pht('Clients')); 16 - $nav->addFilter('client/create', pht('Create Client')); 17 - foreach ($this->getExtraClientFilters() as $filter) { 18 - $nav->addFilter($filter['url'], $filter['label']); 19 - } 20 - $nav->addFilter('client', pht('My Clients')); 21 - $nav->selectFilter($this->getFilter(), 'clientauthorization'); 22 - 23 - $nav->appendChild($view); 24 - 25 - $page->appendChild($nav); 26 - 27 - $response = new AphrontWebpageResponse(); 28 - return $response->setContent($page->render()); 8 + protected function buildApplicationCrumbs() { 9 + // We're specifically not putting an "OAuth Server" application crumb 10 + // on these pages because it doesn't make sense to send users there on 11 + // the auth workflows. 12 + return new PHUICrumbsView(); 29 13 } 30 14 31 - protected function getFilter() { 32 - return 'clientauthorization'; 33 - } 34 - 35 - protected function getExtraClientFilters() { 36 - return array(); 37 - } 38 - 39 - protected function getHighlightPHIDs() { 40 - $phids = array(); 41 - $request = $this->getRequest(); 42 - $edited = $request->getStr('edited'); 43 - $new = $request->getStr('new'); 44 - if ($edited) { 45 - $phids[$edited] = $edited; 46 - } 47 - if ($new) { 48 - $phids[$new] = $new; 49 - } 50 - return $phids; 51 - } 52 - 53 - protected function buildErrorView($error_message) { 54 - $error = new PHUIInfoView(); 55 - $error->setSeverity(PHUIInfoView::SEVERITY_ERROR); 56 - $error->setTitle($error_message); 57 - 58 - return $error; 59 - } 60 15 }
+6 -25
src/applications/oauthserver/controller/PhabricatorOAuthServerTestController.php
··· 3 3 final class PhabricatorOAuthServerTestController 4 4 extends PhabricatorOAuthServerController { 5 5 6 - private $id; 7 - 8 - public function shouldRequireLogin() { 9 - return true; 10 - } 11 - 12 - public function willProcessRequest(array $data) { 13 - $this->id = $data['id']; 14 - } 15 - 16 - public function processRequest() { 17 - $request = $this->getRequest(); 18 - $viewer = $request->getUser(); 19 - 20 - $panels = array(); 21 - $results = array(); 6 + public function handleRequest(AphrontRequest $request) { 7 + $viewer = $this->getViewer(); 8 + $id = $request->getURIData('id'); 22 9 23 10 $client = id(new PhabricatorOAuthServerClientQuery()) 24 11 ->setViewer($viewer) 25 - ->withIDs(array($this->id)) 12 + ->withIDs(array($id)) 26 13 ->executeOne(); 27 14 if (!$client) { 28 15 return new Aphront404Response(); ··· 37 24 ->withClientPHIDs(array($client->getPHID())) 38 25 ->executeOne(); 39 26 if ($authorization) { 40 - $dialog = id(new AphrontDialogView()) 41 - ->setUser($viewer) 27 + return $this->newDialog() 42 28 ->setTitle(pht('Already Authorized')) 43 29 ->appendParagraph( 44 30 pht( 45 31 'You have already authorized this application to access your '. 46 32 'account.')) 47 33 ->addCancelButton($view_uri, pht('Close')); 48 - 49 - return id(new AphrontDialogResponse())->setDialog($dialog); 50 34 } 51 35 52 36 if ($request->isFormPost()) { ··· 65 49 66 50 // TODO: It would be nice to put scope options in this dialog, maybe? 67 51 68 - $dialog = id(new AphrontDialogView()) 69 - ->setUser($viewer) 52 + return $this->newDialog() 70 53 ->setTitle(pht('Authorize Application?')) 71 54 ->appendParagraph( 72 55 pht( ··· 75 58 phutil_tag('strong', array(), $client->getName()))) 76 59 ->addCancelButton($view_uri) 77 60 ->addSubmitButton(pht('Authorize Application')); 78 - 79 - return id(new AphrontDialogResponse())->setDialog($dialog); 80 61 } 81 62 }
+13 -9
src/applications/oauthserver/controller/PhabricatorOAuthServerTokenController.php
··· 1 1 <?php 2 2 3 3 final class PhabricatorOAuthServerTokenController 4 - extends PhabricatorAuthController { 4 + extends PhabricatorOAuthServerController { 5 5 6 6 public function shouldRequireLogin() { 7 7 return false; ··· 14 14 return parent::shouldAllowRestrictedParameter($parameter_name); 15 15 } 16 16 17 - public function processRequest() { 18 - $request = $this->getRequest(); 19 - $grant_type = $request->getStr('grant_type'); 20 - $code = $request->getStr('code'); 21 - $redirect_uri = $request->getStr('redirect_uri'); 22 - $client_phid = $request->getStr('client_id'); 17 + public function handleRequest(AphrontRequest $request) { 18 + $grant_type = $request->getStr('grant_type'); 19 + $code = $request->getStr('code'); 20 + $redirect_uri = $request->getStr('redirect_uri'); 21 + $client_phid = $request->getStr('client_id'); 23 22 $client_secret = $request->getStr('client_secret'); 24 - $response = new PhabricatorOAuthResponse(); 25 - $server = new PhabricatorOAuthServer(); 23 + $response = new PhabricatorOAuthResponse(); 24 + $server = new PhabricatorOAuthServer(); 25 + 26 26 if ($grant_type != 'authorization_code') { 27 27 $response->setError('unsupported_grant_type'); 28 28 $response->setErrorDescription( ··· 32 32 'authorization_code')); 33 33 return $response; 34 34 } 35 + 35 36 if (!$code) { 36 37 $response->setError('invalid_request'); 37 38 $response->setErrorDescription(pht('Required parameter code missing.')); 38 39 return $response; 39 40 } 41 + 40 42 if (!$client_phid) { 41 43 $response->setError('invalid_request'); 42 44 $response->setErrorDescription( ··· 45 47 'client_id')); 46 48 return $response; 47 49 } 50 + 48 51 if (!$client_secret) { 49 52 $response->setError('invalid_request'); 50 53 $response->setErrorDescription( ··· 53 56 'client_secret')); 54 57 return $response; 55 58 } 59 + 56 60 // one giant try / catch around all the exciting database stuff so we 57 61 // can return a 'server_error' response if something goes wrong! 58 62 try {
+30
src/applications/policy/exception/PhabricatorPolicyException.php
··· 6 6 private $rejection; 7 7 private $capabilityName; 8 8 private $moreInfo = array(); 9 + private $objectPHID; 10 + private $context; 11 + private $capability; 9 12 10 13 public function setTitle($title) { 11 14 $this->title = $title; ··· 41 44 42 45 public function getMoreInfo() { 43 46 return $this->moreInfo; 47 + } 48 + 49 + public function setObjectPHID($object_phid) { 50 + $this->objectPHID = $object_phid; 51 + return $this; 52 + } 53 + 54 + public function getObjectPHID() { 55 + return $this->objectPHID; 56 + } 57 + 58 + public function setContext($context) { 59 + $this->context = $context; 60 + return $this; 61 + } 62 + 63 + public function getContext() { 64 + return $this->context; 65 + } 66 + 67 + public function setCapability($capability) { 68 + $this->capability = $capability; 69 + return $this; 70 + } 71 + 72 + public function getCapability() { 73 + return $this->capability; 44 74 } 45 75 46 76 }
+10 -1
src/applications/policy/filter/PhabricatorPolicyFilter.php
··· 589 589 590 590 $exception = id(new PhabricatorPolicyException($full_message)) 591 591 ->setTitle($access_denied) 592 + ->setObjectPHID($object->getPHID()) 592 593 ->setRejection($rejection) 594 + ->setCapability($capability) 593 595 ->setCapabilityName($capability_name) 594 596 ->setMoreInfo($details); 595 597 ··· 710 712 $objects = $policy->getRuleObjects(); 711 713 $action = null; 712 714 foreach ($policy->getRules() as $rule) { 715 + if (!is_array($rule)) { 716 + // Reject, this policy rule is invalid. 717 + return false; 718 + } 719 + 713 720 $rule_object = idx($objects, idx($rule, 'rule')); 714 721 if (!$rule_object) { 715 722 // Reject, this policy has a bogus rule. ··· 831 838 832 839 $exception = id(new PhabricatorPolicyException($full_message)) 833 840 ->setTitle($access_denied) 834 - ->setRejection($rejection); 841 + ->setObjectPHID($object->getPHID()) 842 + ->setRejection($rejection) 843 + ->setCapability(PhabricatorPolicyCapability::CAN_VIEW); 835 844 836 845 throw $exception; 837 846 }
+6
src/applications/policy/storage/PhabricatorPolicy.php
··· 321 321 $classes = array(); 322 322 323 323 foreach ($this->getRules() as $rule) { 324 + if (!is_array($rule)) { 325 + // This rule is invalid. We'll reject it later, but don't need to 326 + // extract anything from it for now. 327 + continue; 328 + } 329 + 324 330 $class = idx($rule, 'rule'); 325 331 try { 326 332 if (class_exists($class)) {