[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
1 <?php 2 3 abstract class PhabricatorController extends AphrontController { 4 5 private $handles; 6 7 public function shouldRequireLogin() { 8 return true; 9 } 10 11 public function shouldRequireAdmin() { 12 return false; 13 } 14 15 public function shouldRequireEnabledUser() { 16 return true; 17 } 18 19 public function shouldAllowPublic() { 20 return false; 21 } 22 23 public function shouldAllowPartialSessions() { 24 return false; 25 } 26 27 public function shouldRequireEmailVerification() { 28 return PhabricatorUserEmail::isEmailVerificationRequired(); 29 } 30 31 public function shouldAllowRestrictedParameter($parameter_name) { 32 return false; 33 } 34 35 public function shouldRequireMultiFactorEnrollment() { 36 if (!$this->shouldRequireLogin()) { 37 return false; 38 } 39 40 if (!$this->shouldRequireEnabledUser()) { 41 return false; 42 } 43 44 if ($this->shouldAllowPartialSessions()) { 45 return false; 46 } 47 48 $user = $this->getRequest()->getUser(); 49 if (!$user->getIsStandardUser()) { 50 return false; 51 } 52 53 return PhabricatorEnv::getEnvConfig('security.require-multi-factor-auth'); 54 } 55 56 public function willBeginExecution() { 57 $request = $this->getRequest(); 58 59 if ($request->getUser()) { 60 // NOTE: Unit tests can set a user explicitly. Normal requests are not 61 // permitted to do this. 62 PhabricatorTestCase::assertExecutingUnitTests(); 63 $user = $request->getUser(); 64 } else { 65 $user = new PhabricatorUser(); 66 $session_engine = new PhabricatorAuthSessionEngine(); 67 68 $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION); 69 if (strlen($phsid)) { 70 $session_user = $session_engine->loadUserForSession( 71 PhabricatorAuthSession::TYPE_WEB, 72 $phsid); 73 if ($session_user) { 74 $user = $session_user; 75 } 76 } else { 77 // If the client doesn't have a session token, generate an anonymous 78 // session. This is used to provide CSRF protection to logged-out users. 79 $phsid = $session_engine->establishSession( 80 PhabricatorAuthSession::TYPE_WEB, 81 null, 82 $partial = false); 83 84 // This may be a resource request, in which case we just don't set 85 // the cookie. 86 if ($request->canSetCookies()) { 87 $request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid); 88 } 89 } 90 91 92 if (!$user->isLoggedIn()) { 93 $user->attachAlternateCSRFString(PhabricatorHash::digest($phsid)); 94 } 95 96 $request->setUser($user); 97 } 98 99 $translation = $user->getTranslation(); 100 if ($translation && 101 $translation != PhabricatorEnv::getEnvConfig('translation.provider')) { 102 $translation = newv($translation, array()); 103 PhutilTranslator::getInstance() 104 ->setLanguage($translation->getLanguage()) 105 ->addTranslations($translation->getTranslations()); 106 } 107 108 $preferences = $user->loadPreferences(); 109 if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) { 110 $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE; 111 if ($preferences->getPreference($dark_console) || 112 PhabricatorEnv::getEnvConfig('darkconsole.always-on')) { 113 $console = new DarkConsoleCore(); 114 $request->getApplicationConfiguration()->setConsole($console); 115 } 116 } 117 118 // NOTE: We want to set up the user first so we can render a real page 119 // here, but fire this before any real logic. 120 $restricted = array( 121 'code', 122 ); 123 foreach ($restricted as $parameter) { 124 if ($request->getExists($parameter)) { 125 if (!$this->shouldAllowRestrictedParameter($parameter)) { 126 throw new Exception( 127 pht( 128 'Request includes restricted parameter "%s", but this '. 129 'controller ("%s") does not whitelist it. Refusing to '. 130 'serve this request because it might be part of a redirection '. 131 'attack.', 132 $parameter, 133 get_class($this))); 134 } 135 } 136 } 137 138 if ($this->shouldRequireEnabledUser()) { 139 if ($user->isLoggedIn() && !$user->getIsApproved()) { 140 $controller = new PhabricatorAuthNeedsApprovalController(); 141 return $this->delegateToController($controller); 142 } 143 if ($user->getIsDisabled()) { 144 $controller = new PhabricatorDisabledUserController(); 145 return $this->delegateToController($controller); 146 } 147 } 148 149 $event = new PhabricatorEvent( 150 PhabricatorEventType::TYPE_CONTROLLER_CHECKREQUEST, 151 array( 152 'request' => $request, 153 'controller' => $this, 154 )); 155 $event->setUser($user); 156 PhutilEventEngine::dispatchEvent($event); 157 $checker_controller = $event->getValue('controller'); 158 if ($checker_controller != $this) { 159 return $this->delegateToController($checker_controller); 160 } 161 162 $auth_class = 'PhabricatorAuthApplication'; 163 $auth_application = PhabricatorApplication::getByClass($auth_class); 164 165 // Require partial sessions to finish login before doing anything. 166 if (!$this->shouldAllowPartialSessions()) { 167 if ($user->hasSession() && 168 $user->getSession()->getIsPartial()) { 169 $login_controller = new PhabricatorAuthFinishController(); 170 $this->setCurrentApplication($auth_application); 171 return $this->delegateToController($login_controller); 172 } 173 } 174 175 // Check if the user needs to configure MFA. 176 $need_mfa = $this->shouldRequireMultiFactorEnrollment(); 177 $have_mfa = $user->getIsEnrolledInMultiFactor(); 178 if ($need_mfa && !$have_mfa) { 179 // Check if the cache is just out of date. Otherwise, roadblock the user 180 // and require MFA enrollment. 181 $user->updateMultiFactorEnrollment(); 182 if (!$user->getIsEnrolledInMultiFactor()) { 183 $mfa_controller = new PhabricatorAuthNeedsMultiFactorController(); 184 $this->setCurrentApplication($auth_application); 185 return $this->delegateToController($mfa_controller); 186 } 187 } 188 189 if ($this->shouldRequireLogin()) { 190 // This actually means we need either: 191 // - a valid user, or a public controller; and 192 // - permission to see the application. 193 194 $allow_public = $this->shouldAllowPublic() && 195 PhabricatorEnv::getEnvConfig('policy.allow-public'); 196 197 // If this controller isn't public, and the user isn't logged in, require 198 // login. 199 if (!$allow_public && !$user->isLoggedIn()) { 200 $login_controller = new PhabricatorAuthStartController(); 201 $this->setCurrentApplication($auth_application); 202 return $this->delegateToController($login_controller); 203 } 204 205 if ($user->isLoggedIn()) { 206 if ($this->shouldRequireEmailVerification()) { 207 if (!$user->getIsEmailVerified()) { 208 $controller = new PhabricatorMustVerifyEmailController(); 209 $this->setCurrentApplication($auth_application); 210 return $this->delegateToController($controller); 211 } 212 } 213 } 214 215 // If the user doesn't have access to the application, don't let them use 216 // any of its controllers. We query the application in order to generate 217 // a policy exception if the viewer doesn't have permission. 218 219 $application = $this->getCurrentApplication(); 220 if ($application) { 221 id(new PhabricatorApplicationQuery()) 222 ->setViewer($user) 223 ->withPHIDs(array($application->getPHID())) 224 ->executeOne(); 225 } 226 } 227 228 // NOTE: We do this last so that users get a login page instead of a 403 229 // if they need to login. 230 if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) { 231 return new Aphront403Response(); 232 } 233 } 234 235 public function buildStandardPageView() { 236 $view = new PhabricatorStandardPageView(); 237 $view->setRequest($this->getRequest()); 238 $view->setController($this); 239 return $view; 240 } 241 242 public function buildStandardPageResponse($view, array $data) { 243 $page = $this->buildStandardPageView(); 244 $page->appendChild($view); 245 $response = new AphrontWebpageResponse(); 246 $response->setContent($page->render()); 247 return $response; 248 } 249 250 public function getApplicationURI($path = '') { 251 if (!$this->getCurrentApplication()) { 252 throw new Exception('No application!'); 253 } 254 return $this->getCurrentApplication()->getApplicationURI($path); 255 } 256 257 public function buildApplicationPage($view, array $options) { 258 $page = $this->buildStandardPageView(); 259 260 $title = PhabricatorEnv::getEnvConfig('phabricator.serious-business') ? 261 'Phabricator' : 262 pht('Bacon Ice Cream for Breakfast'); 263 264 $application = $this->getCurrentApplication(); 265 $page->setTitle(idx($options, 'title', $title)); 266 if ($application) { 267 $page->setApplicationName($application->getName()); 268 if ($application->getTitleGlyph()) { 269 $page->setGlyph($application->getTitleGlyph()); 270 } 271 } 272 273 if (!($view instanceof AphrontSideNavFilterView)) { 274 $nav = new AphrontSideNavFilterView(); 275 $nav->appendChild($view); 276 $view = $nav; 277 } 278 279 $user = $this->getRequest()->getUser(); 280 $view->setUser($user); 281 282 $page->appendChild($view); 283 284 $object_phids = idx($options, 'pageObjects', array()); 285 if ($object_phids) { 286 $page->appendPageObjects($object_phids); 287 foreach ($object_phids as $object_phid) { 288 PhabricatorFeedStoryNotification::updateObjectNotificationViews( 289 $user, 290 $object_phid); 291 } 292 } 293 294 if (idx($options, 'device', true)) { 295 $page->setDeviceReady(true); 296 } 297 298 $page->setShowFooter(idx($options, 'showFooter', true)); 299 $page->setShowChrome(idx($options, 'chrome', true)); 300 301 $application_menu = $this->buildApplicationMenu(); 302 if ($application_menu) { 303 $page->setApplicationMenu($application_menu); 304 } 305 306 $response = new AphrontWebpageResponse(); 307 return $response->setContent($page->render()); 308 } 309 310 public function didProcessRequest($response) { 311 // If a bare DialogView is returned, wrap it in a DialogResponse. 312 if ($response instanceof AphrontDialogView) { 313 $response = id(new AphrontDialogResponse())->setDialog($response); 314 } 315 316 $request = $this->getRequest(); 317 $response->setRequest($request); 318 319 $seen = array(); 320 while ($response instanceof AphrontProxyResponse) { 321 $hash = spl_object_hash($response); 322 if (isset($seen[$hash])) { 323 $seen[] = get_class($response); 324 throw new Exception( 325 'Cycle while reducing proxy responses: '. 326 implode(' -> ', $seen)); 327 } 328 $seen[$hash] = get_class($response); 329 330 $response = $response->reduceProxyResponse(); 331 } 332 333 if ($response instanceof AphrontDialogResponse) { 334 if (!$request->isAjax()) { 335 $dialog = $response->getDialog(); 336 337 $title = $dialog->getTitle(); 338 $short = $dialog->getShortTitle(); 339 340 $crumbs = $this->buildApplicationCrumbs(); 341 $crumbs->addTextCrumb(coalesce($short, $title)); 342 343 $page_content = array( 344 $crumbs, 345 $response->buildResponseString(), 346 ); 347 348 $view = id(new PhabricatorStandardPageView()) 349 ->setRequest($request) 350 ->setController($this) 351 ->setDeviceReady(true) 352 ->setTitle($title) 353 ->appendChild($page_content); 354 355 $response = id(new AphrontWebpageResponse()) 356 ->setContent($view->render()) 357 ->setHTTPResponseCode($response->getHTTPResponseCode()); 358 } else { 359 $response->getDialog()->setIsStandalone(true); 360 361 return id(new AphrontAjaxResponse()) 362 ->setContent(array( 363 'dialog' => $response->buildResponseString(), 364 )); 365 } 366 } else if ($response instanceof AphrontRedirectResponse) { 367 if ($request->isAjax()) { 368 return id(new AphrontAjaxResponse()) 369 ->setContent( 370 array( 371 'redirect' => $response->getURI(), 372 )); 373 } 374 } 375 376 return $response; 377 } 378 379 protected function getHandle($phid) { 380 if (empty($this->handles[$phid])) { 381 throw new Exception( 382 "Attempting to access handle which wasn't loaded: {$phid}"); 383 } 384 return $this->handles[$phid]; 385 } 386 387 protected function loadHandles(array $phids) { 388 $phids = array_filter($phids); 389 $this->handles = $this->loadViewerHandles($phids); 390 return $this; 391 } 392 393 protected function getLoadedHandles() { 394 return $this->handles; 395 } 396 397 protected function loadViewerHandles(array $phids) { 398 return id(new PhabricatorHandleQuery()) 399 ->setViewer($this->getRequest()->getUser()) 400 ->withPHIDs($phids) 401 ->execute(); 402 } 403 404 /** 405 * Render a list of links to handles, identified by PHIDs. The handles must 406 * already be loaded. 407 * 408 * @param list<phid> List of PHIDs to render links to. 409 * @param string Style, one of "\n" (to put each item on its own line) 410 * or "," (to list items inline, separated by commas). 411 * @return string Rendered list of handle links. 412 */ 413 protected function renderHandlesForPHIDs(array $phids, $style = "\n") { 414 $style_map = array( 415 "\n" => phutil_tag('br'), 416 ',' => ', ', 417 ); 418 419 if (empty($style_map[$style])) { 420 throw new Exception("Unknown handle list style '{$style}'!"); 421 } 422 423 return implode_selected_handle_links($style_map[$style], 424 $this->getLoadedHandles(), 425 array_filter($phids)); 426 } 427 428 protected function buildApplicationMenu() { 429 return null; 430 } 431 432 protected function buildApplicationCrumbs() { 433 $crumbs = array(); 434 435 $application = $this->getCurrentApplication(); 436 if ($application) { 437 $sprite = $application->getIconName(); 438 if (!$sprite) { 439 $sprite = 'application'; 440 } 441 442 $crumbs[] = id(new PhabricatorCrumbView()) 443 ->setHref($this->getApplicationURI()) 444 ->setAural($application->getName()) 445 ->setIcon($sprite); 446 } 447 448 $view = new PhabricatorCrumbsView(); 449 foreach ($crumbs as $crumb) { 450 $view->addCrumb($crumb); 451 } 452 453 return $view; 454 } 455 456 protected function hasApplicationCapability($capability) { 457 return PhabricatorPolicyFilter::hasCapability( 458 $this->getRequest()->getUser(), 459 $this->getCurrentApplication(), 460 $capability); 461 } 462 463 protected function requireApplicationCapability($capability) { 464 PhabricatorPolicyFilter::requireCapability( 465 $this->getRequest()->getUser(), 466 $this->getCurrentApplication(), 467 $capability); 468 } 469 470 protected function explainApplicationCapability( 471 $capability, 472 $positive_message, 473 $negative_message) { 474 475 $can_act = $this->hasApplicationCapability($capability); 476 if ($can_act) { 477 $message = $positive_message; 478 $icon_name = 'fa-play-circle-o lightgreytext'; 479 } else { 480 $message = $negative_message; 481 $icon_name = 'fa-lock'; 482 } 483 484 $icon = id(new PHUIIconView()) 485 ->setIconFont($icon_name); 486 487 require_celerity_resource('policy-css'); 488 489 $phid = $this->getCurrentApplication()->getPHID(); 490 $explain_uri = "/policy/explain/{$phid}/{$capability}/"; 491 492 $message = phutil_tag( 493 'div', 494 array( 495 'class' => 'policy-capability-explanation', 496 ), 497 array( 498 $icon, 499 javelin_tag( 500 'a', 501 array( 502 'href' => $explain_uri, 503 'sigil' => 'workflow', 504 ), 505 $message), 506 )); 507 508 return array($can_act, $message); 509 } 510 511 public function getDefaultResourceSource() { 512 return 'phabricator'; 513 } 514 515 /** 516 * Create a new @{class:AphrontDialogView} with defaults filled in. 517 * 518 * @return AphrontDialogView New dialog. 519 */ 520 public function newDialog() { 521 $submit_uri = new PhutilURI($this->getRequest()->getRequestURI()); 522 $submit_uri = $submit_uri->getPath(); 523 524 return id(new AphrontDialogView()) 525 ->setUser($this->getRequest()->getUser()) 526 ->setSubmitURI($submit_uri); 527 } 528 529 protected function buildTransactionTimeline( 530 PhabricatorApplicationTransactionInterface $object, 531 PhabricatorApplicationTransactionQuery $query, 532 PhabricatorMarkupEngine $engine = null) { 533 534 $viewer = $this->getRequest()->getUser(); 535 $xaction = $object->getApplicationTransactionTemplate(); 536 $view = $xaction->getApplicationTransactionViewObject(); 537 538 $xactions = $query 539 ->setViewer($viewer) 540 ->withObjectPHIDs(array($object->getPHID())) 541 ->needComments(true) 542 ->execute(); 543 544 if ($engine) { 545 foreach ($xactions as $xaction) { 546 if ($xaction->getComment()) { 547 $engine->addObject( 548 $xaction->getComment(), 549 PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); 550 } 551 } 552 $engine->process(); 553 $view->setMarkupEngine($engine); 554 } 555 556 $timeline = $view 557 ->setUser($viewer) 558 ->setObjectPHID($object->getPHID()) 559 ->setTransactions($xactions); 560 561 return $timeline; 562 } 563 564 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |