[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/base/controller/ -> PhabricatorController.php (source)

   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  }


Generated: Sun Nov 30 09:20:46 2014 Cross-referenced by PHPXref 0.7.1