public function execute(PhutilArgumentParser $args)
 {
     $can_recover = id(new PhabricatorPeopleQuery())->setViewer($this->getViewer())->withIsAdmin(true)->execute();
     if (!$can_recover) {
         throw new PhutilArgumentUsageException(pht('This Phabricator installation has no recoverable administrator ' . 'accounts. You can use `bin/accountadmin` to create a new ' . 'administrator account or make an existing user an administrator.'));
     }
     $can_recover = mpull($can_recover, 'getUsername');
     sort($can_recover);
     $can_recover = implode(', ', $can_recover);
     $usernames = $args->getArg('username');
     if (!$usernames) {
         throw new PhutilArgumentUsageException(pht('You must specify the username of the account to recover.'));
     } else {
         if (count($usernames) > 1) {
             throw new PhutilArgumentUsageException(pht('You can only recover the username for one account.'));
         }
     }
     $username = head($usernames);
     $user = id(new PhabricatorPeopleQuery())->setViewer($this->getViewer())->withUsernames(array($username))->executeOne();
     if (!$user) {
         throw new PhutilArgumentUsageException(pht('No such user "%s". Recoverable administrator accounts are: %s.', $username, $can_recover));
     }
     if (!$user->getIsAdmin()) {
         throw new PhutilArgumentUsageException(pht('You can only recover administrator accounts, but %s is not an ' . 'administrator. Recoverable administrator accounts are: %s.', $username, $can_recover));
     }
     $engine = new PhabricatorAuthSessionEngine();
     $onetime_uri = $engine->getOneTimeLoginURI($user, null, PhabricatorAuthSessionEngine::ONETIME_RECOVER);
     $console = PhutilConsole::getConsole();
     $console->writeOut(pht('Use this link to recover access to the "%s" account from the web ' . 'interface:', $username));
     $console->writeOut("\n\n");
     $console->writeOut('    %s', $onetime_uri);
     $console->writeOut("\n\n");
     $console->writeOut(pht('After logging in, you can use the "Auth" application to add or ' . 'restore authentication providers and allow normal logins to ' . 'succeed.') . "\n");
     return 0;
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     // If the user already has a full session, just kick them out of here.
     $has_partial_session = $viewer->hasSession() && $viewer->getSession()->getIsPartial();
     if (!$has_partial_session) {
         return id(new AphrontRedirectResponse())->setURI('/');
     }
     $engine = new PhabricatorAuthSessionEngine();
     // If this cookie is set, the user is headed into a high security area
     // after login (normally because of a password reset) so if they are
     // able to pass the checkpoint we just want to put their account directly
     // into high security mode, rather than prompt them again for the same
     // set of credentials.
     $jump_into_hisec = $request->getCookie(PhabricatorCookies::COOKIE_HISEC);
     try {
         $token = $engine->requireHighSecuritySession($viewer, $request, '/logout/', $jump_into_hisec);
     } catch (PhabricatorAuthHighSecurityRequiredException $ex) {
         $form = id(new PhabricatorAuthSessionEngine())->renderHighSecurityForm($ex->getFactors(), $ex->getFactorValidationResults(), $viewer, $request);
         return $this->newDialog()->setTitle(pht('Provide Multi-Factor Credentials'))->setShortTitle(pht('Multi-Factor Login'))->setWidth(AphrontDialogView::WIDTH_FORM)->addHiddenInput(AphrontRequest::TYPE_HISEC, true)->appendParagraph(pht('Welcome, %s. To complete the login process, provide your ' . 'multi-factor credentials.', phutil_tag('strong', array(), $viewer->getUsername())))->appendChild($form->buildLayoutView())->setSubmitURI($request->getPath())->addCancelButton($ex->getCancelURI())->addSubmitButton(pht('Continue'));
     }
     // Upgrade the partial session to a full session.
     $engine->upgradePartialSession($viewer);
     // TODO: It might be nice to add options like "bind this session to my IP"
     // here, even for accounts without multi-factor auth attached to them.
     $next = PhabricatorCookies::getNextURICookie($request);
     $request->clearCookie(PhabricatorCookies::COOKIE_NEXTURI);
     $request->clearCookie(PhabricatorCookies::COOKIE_HISEC);
     if (!PhabricatorEnv::isValidLocalURIForLink($next)) {
         $next = '/';
     }
     return id(new AphrontRedirectResponse())->setURI($next);
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     if ($request->isFormPost()) {
         // Destroy the user's session in the database so logout works even if
         // their cookies have some issues. We'll detect cookie issues when they
         // try to login again and tell them to clear any junk.
         $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
         if (strlen($phsid)) {
             $session = id(new PhabricatorAuthSessionQuery())->setViewer($viewer)->withSessionKeys(array($phsid))->executeOne();
             if ($session) {
                 $engine = new PhabricatorAuthSessionEngine();
                 $engine->logoutSession($viewer, $session);
             }
         }
         $request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
         return id(new AphrontRedirectResponse())->setURI('/auth/loggedout/');
     }
     if ($viewer->getPHID()) {
         return $this->newDialog()->setTitle(pht('Log out of Phabricator?'))->appendChild(pht('Are you sure you want to log out?'))->addSubmitButton(pht('Logout'))->addCancelButton('/');
     }
     return id(new AphrontRedirectResponse())->setURI('/');
 }
 public function willBeginExecution()
 {
     $request = $this->getRequest();
     if ($request->getUser()) {
         // NOTE: Unit tests can set a user explicitly. Normal requests are not
         // permitted to do this.
         PhabricatorTestCase::assertExecutingUnitTests();
         $user = $request->getUser();
     } else {
         $user = new PhabricatorUser();
         $session_engine = new PhabricatorAuthSessionEngine();
         $phsid = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
         if (strlen($phsid)) {
             $session_user = $session_engine->loadUserForSession(PhabricatorAuthSession::TYPE_WEB, $phsid);
             if ($session_user) {
                 $user = $session_user;
             }
         } else {
             // If the client doesn't have a session token, generate an anonymous
             // session. This is used to provide CSRF protection to logged-out users.
             $phsid = $session_engine->establishSession(PhabricatorAuthSession::TYPE_WEB, null, $partial = false);
             // This may be a resource request, in which case we just don't set
             // the cookie.
             if ($request->canSetCookies()) {
                 $request->setCookie(PhabricatorCookies::COOKIE_SESSION, $phsid);
             }
         }
         if (!$user->isLoggedIn()) {
             $user->attachAlternateCSRFString(PhabricatorHash::digest($phsid));
         }
         $request->setUser($user);
     }
     PhabricatorEnv::setLocaleCode($user->getTranslation());
     $preferences = $user->loadPreferences();
     if (PhabricatorEnv::getEnvConfig('darkconsole.enabled')) {
         $dark_console = PhabricatorUserPreferences::PREFERENCE_DARK_CONSOLE;
         if ($preferences->getPreference($dark_console) || PhabricatorEnv::getEnvConfig('darkconsole.always-on')) {
             $console = new DarkConsoleCore();
             $request->getApplicationConfiguration()->setConsole($console);
         }
     }
     // NOTE: We want to set up the user first so we can render a real page
     // here, but fire this before any real logic.
     $restricted = array('code');
     foreach ($restricted as $parameter) {
         if ($request->getExists($parameter)) {
             if (!$this->shouldAllowRestrictedParameter($parameter)) {
                 throw new Exception(pht('Request includes restricted parameter "%s", but this ' . 'controller ("%s") does not whitelist it. Refusing to ' . 'serve this request because it might be part of a redirection ' . 'attack.', $parameter, get_class($this)));
             }
         }
     }
     if ($this->shouldRequireEnabledUser()) {
         if ($user->isLoggedIn() && !$user->getIsApproved()) {
             $controller = new PhabricatorAuthNeedsApprovalController();
             return $this->delegateToController($controller);
         }
         if ($user->getIsDisabled()) {
             $controller = new PhabricatorDisabledUserController();
             return $this->delegateToController($controller);
         }
     }
     $auth_class = 'PhabricatorAuthApplication';
     $auth_application = PhabricatorApplication::getByClass($auth_class);
     // Require partial sessions to finish login before doing anything.
     if (!$this->shouldAllowPartialSessions()) {
         if ($user->hasSession() && $user->getSession()->getIsPartial()) {
             $login_controller = new PhabricatorAuthFinishController();
             $this->setCurrentApplication($auth_application);
             return $this->delegateToController($login_controller);
         }
     }
     // Check if the user needs to configure MFA.
     $need_mfa = $this->shouldRequireMultiFactorEnrollment();
     $have_mfa = $user->getIsEnrolledInMultiFactor();
     if ($need_mfa && !$have_mfa) {
         // Check if the cache is just out of date. Otherwise, roadblock the user
         // and require MFA enrollment.
         $user->updateMultiFactorEnrollment();
         if (!$user->getIsEnrolledInMultiFactor()) {
             $mfa_controller = new PhabricatorAuthNeedsMultiFactorController();
             $this->setCurrentApplication($auth_application);
             return $this->delegateToController($mfa_controller);
         }
     }
     if ($this->shouldRequireLogin()) {
         // This actually means we need either:
         //   - a valid user, or a public controller; and
         //   - permission to see the application; and
         //   - permission to see at least one Space if spaces are configured.
         $allow_public = $this->shouldAllowPublic() && PhabricatorEnv::getEnvConfig('policy.allow-public');
         // If this controller isn't public, and the user isn't logged in, require
         // login.
         if (!$allow_public && !$user->isLoggedIn()) {
             $login_controller = new PhabricatorAuthStartController();
             $this->setCurrentApplication($auth_application);
             return $this->delegateToController($login_controller);
         }
         if ($user->isLoggedIn()) {
             if ($this->shouldRequireEmailVerification()) {
                 if (!$user->getIsEmailVerified()) {
                     $controller = new PhabricatorMustVerifyEmailController();
                     $this->setCurrentApplication($auth_application);
                     return $this->delegateToController($controller);
                 }
             }
         }
         // If Spaces are configured, require that the user have access to at
         // least one. If we don't do this, they'll get confusing error messages
         // later on.
         $spaces = PhabricatorSpacesNamespaceQuery::getSpacesExist();
         if ($spaces) {
             $viewer_spaces = PhabricatorSpacesNamespaceQuery::getViewerSpaces($user);
             if (!$viewer_spaces) {
                 $controller = new PhabricatorSpacesNoAccessController();
                 return $this->delegateToController($controller);
             }
         }
         // If the user doesn't have access to the application, don't let them use
         // any of its controllers. We query the application in order to generate
         // a policy exception if the viewer doesn't have permission.
         $application = $this->getCurrentApplication();
         if ($application) {
             id(new PhabricatorApplicationQuery())->setViewer($user)->withPHIDs(array($application->getPHID()))->executeOne();
         }
     }
     if (!$this->shouldAllowLegallyNonCompliantUsers()) {
         $legalpad_class = 'PhabricatorLegalpadApplication';
         $legalpad = id(new PhabricatorApplicationQuery())->setViewer($user)->withClasses(array($legalpad_class))->withInstalled(true)->execute();
         $legalpad = head($legalpad);
         $doc_query = id(new LegalpadDocumentQuery())->setViewer($user)->withSignatureRequired(1)->needViewerSignatures(true);
         if ($user->hasSession() && !$user->getSession()->getIsPartial() && !$user->getSession()->getSignedLegalpadDocuments() && $user->isLoggedIn() && $legalpad) {
             $sign_docs = $doc_query->execute();
             $must_sign_docs = array();
             foreach ($sign_docs as $sign_doc) {
                 if (!$sign_doc->getUserSignature($user->getPHID())) {
                     $must_sign_docs[] = $sign_doc;
                 }
             }
             if ($must_sign_docs) {
                 $controller = new LegalpadDocumentSignController();
                 $this->getRequest()->setURIMap(array('id' => head($must_sign_docs)->getID()));
                 $this->setCurrentApplication($legalpad);
                 return $this->delegateToController($controller);
             } else {
                 $engine = id(new PhabricatorAuthSessionEngine())->signLegalpadDocuments($user, $sign_docs);
             }
         }
     }
     // NOTE: We do this last so that users get a login page instead of a 403
     // if they need to login.
     if ($this->shouldRequireAdmin() && !$user->getIsAdmin()) {
         return new Aphront403Response();
     }
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getUser();
     if ($viewer->isLoggedIn()) {
         // Kick the user home if they are already logged in.
         return id(new AphrontRedirectResponse())->setURI('/');
     }
     if ($request->isAjax()) {
         return $this->processAjaxRequest();
     }
     if ($request->isConduit()) {
         return $this->processConduitRequest();
     }
     // If the user gets this far, they aren't logged in, so if they have a
     // user session token we can conclude that it's invalid: if it was valid,
     // they'd have been logged in above and never made it here. Try to clear
     // it and warn the user they may need to nuke their cookies.
     $session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
     if (strlen($session_token)) {
         $kind = PhabricatorAuthSessionEngine::getSessionKindFromToken($session_token);
         switch ($kind) {
             case PhabricatorAuthSessionEngine::KIND_ANONYMOUS:
                 // If this is an anonymous session. It's expected that they won't
                 // be logged in, so we can just continue.
                 break;
             default:
                 // The session cookie is invalid, so clear it.
                 $request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
                 $request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
                 return $this->renderError(pht('Your login session is invalid. Try reloading the page and ' . 'logging in again. If that does not work, clear your browser ' . 'cookies.'));
         }
     }
     $providers = PhabricatorAuthProvider::getAllEnabledProviders();
     foreach ($providers as $key => $provider) {
         if (!$provider->shouldAllowLogin()) {
             unset($providers[$key]);
         }
     }
     if (!$providers) {
         if ($this->isFirstTimeSetup()) {
             // If this is a fresh install, let the user register their admin
             // account.
             return id(new AphrontRedirectResponse())->setURI($this->getApplicationURI('/register/'));
         }
         return $this->renderError(pht('This Phabricator install is not configured with any enabled ' . 'authentication providers which can be used to log in. If you ' . 'have accidentally locked yourself out by disabling all providers, ' . 'you can use `%s` to recover access to an administrative account.', 'phabricator/bin/auth recover <username>'));
     }
     $next_uri = $request->getStr('next');
     if (!strlen($next_uri)) {
         if ($this->getDelegatingController()) {
             // Only set a next URI from the request path if this controller was
             // delegated to, which happens when a user tries to view a page which
             // requires them to login.
             // If this controller handled the request directly, we're on the main
             // login page, and never want to redirect the user back here after they
             // login.
             $next_uri = (string) $this->getRequest()->getRequestURI();
         }
     }
     if (!$request->isFormPost()) {
         if (strlen($next_uri)) {
             PhabricatorCookies::setNextURICookie($request, $next_uri);
         }
         PhabricatorCookies::setClientIDCookie($request);
     }
     if (!$request->getURIData('loggedout') && count($providers) == 1) {
         $auto_login_provider = head($providers);
         $auto_login_config = $auto_login_provider->getProviderConfig();
         if ($auto_login_provider instanceof PhabricatorPhabricatorAuthProvider && $auto_login_config->getShouldAutoLogin()) {
             $auto_login_adapter = $provider->getAdapter();
             $auto_login_adapter->setState($provider->getAuthCSRFCode($request));
             return id(new AphrontRedirectResponse())->setIsExternal(true)->setURI($provider->getAdapter()->getAuthenticateURI());
         }
     }
     $invite = $this->loadInvite();
     $not_buttons = array();
     $are_buttons = array();
     $providers = msort($providers, 'getLoginOrder');
     foreach ($providers as $provider) {
         if ($invite) {
             $form = $provider->buildInviteForm($this);
         } else {
             $form = $provider->buildLoginForm($this);
         }
         if ($provider->isLoginFormAButton()) {
             $are_buttons[] = $form;
         } else {
             $not_buttons[] = $form;
         }
     }
     $out = array();
     $out[] = $not_buttons;
     if ($are_buttons) {
         require_celerity_resource('auth-css');
         foreach ($are_buttons as $key => $button) {
             $are_buttons[$key] = phutil_tag('div', array('class' => 'phabricator-login-button mmb'), $button);
         }
         // If we only have one button, add a second pretend button so that we
         // always have two columns. This makes it easier to get the alignments
         // looking reasonable.
         if (count($are_buttons) == 1) {
             $are_buttons[] = null;
         }
         $button_columns = id(new AphrontMultiColumnView())->setFluidLayout(true);
         $are_buttons = array_chunk($are_buttons, ceil(count($are_buttons) / 2));
         foreach ($are_buttons as $column) {
             $button_columns->addColumn($column);
         }
         $out[] = phutil_tag('div', array('class' => 'phabricator-login-buttons'), $button_columns);
     }
     $login_message = PhabricatorEnv::getEnvConfig('auth.login-message');
     $login_message = phutil_safe_html($login_message);
     $invite_message = null;
     if ($invite) {
         $invite_message = $this->renderInviteHeader($invite);
     }
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Login'));
     $crumbs->setBorder(true);
     return $this->buildApplicationPage(array($crumbs, $login_message, $invite_message, $out), array('title' => pht('Login to Phabricator')));
 }
 public function handleRequest(AphrontRequest $request)
 {
     if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
         return new Aphront400Response();
     }
     $e_email = true;
     $e_captcha = true;
     $errors = array();
     $is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
     if ($request->isFormPost()) {
         $e_email = null;
         $e_captcha = pht('Again');
         $captcha_ok = AphrontFormRecaptchaControl::processCaptcha($request);
         if (!$captcha_ok) {
             $errors[] = pht('Captcha response is incorrect, try again.');
             $e_captcha = pht('Invalid');
         }
         $email = $request->getStr('email');
         if (!strlen($email)) {
             $errors[] = pht('You must provide an email address.');
             $e_email = pht('Required');
         }
         if (!$errors) {
             // NOTE: Don't validate the email unless the captcha is good; this makes
             // it expensive to fish for valid email addresses while giving the user
             // a better error if they goof their email.
             $target_email = id(new PhabricatorUserEmail())->loadOneWhere('address = %s', $email);
             $target_user = null;
             if ($target_email) {
                 $target_user = id(new PhabricatorUser())->loadOneWhere('phid = %s', $target_email->getUserPHID());
             }
             if (!$target_user) {
                 $errors[] = pht('There is no account associated with that email address.');
                 $e_email = pht('Invalid');
             }
             // If this address is unverified, only send a reset link to it if
             // the account has no verified addresses. This prevents an opportunistic
             // attacker from compromising an account if a user adds an email
             // address but mistypes it and doesn't notice.
             // (For a newly created account, all the addresses may be unverified,
             // which is why we'll send to an unverified address in that case.)
             if ($target_email && !$target_email->getIsVerified()) {
                 $verified_addresses = id(new PhabricatorUserEmail())->loadAllWhere('userPHID = %s AND isVerified = 1', $target_email->getUserPHID());
                 if ($verified_addresses) {
                     $errors[] = pht('That email address is not verified. You can only send ' . 'password reset links to a verified address.');
                     $e_email = pht('Unverified');
                 }
             }
             if (!$errors) {
                 $engine = new PhabricatorAuthSessionEngine();
                 $uri = $engine->getOneTimeLoginURI($target_user, null, PhabricatorAuthSessionEngine::ONETIME_RESET);
                 if ($is_serious) {
                     $body = pht("You can use this link to reset your Phabricator password:"******"\n\n  %s\n", $uri);
                 } else {
                     $body = pht("Condolences on forgetting your password. You can use this " . "link to reset it:\n\n" . "  %s\n\n" . "After you set a new password, consider writing it down on a " . "sticky note and attaching it to your monitor so you don't " . "forget again! Choosing a very short, easy-to-remember password " . "like \"cat\" or \"1234\" might also help.\n\n" . "Best Wishes,\nPhabricator\n", $uri);
                 }
                 $mail = id(new PhabricatorMetaMTAMail())->setSubject(pht('[Phabricator] Password Reset'))->setForceDelivery(true)->addRawTos(array($target_email->getAddress()))->setBody($body)->saveAndSend();
                 return $this->newDialog()->setTitle(pht('Check Your Email'))->setShortTitle(pht('Email Sent'))->appendParagraph(pht('An email has been sent with a link you can use to login.'))->addCancelButton('/', pht('Done'));
             }
         }
     }
     $error_view = null;
     if ($errors) {
         $error_view = new PHUIInfoView();
         $error_view->setErrors($errors);
     }
     $email_auth = new PHUIFormLayoutView();
     $email_auth->appendChild($error_view);
     $email_auth->setUser($request->getUser())->setFullWidth(true)->appendChild(id(new AphrontFormTextControl())->setLabel(pht('Email'))->setName('email')->setValue($request->getStr('email'))->setError($e_email))->appendChild(id(new AphrontFormRecaptchaControl())->setLabel(pht('Captcha'))->setError($e_captcha));
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Reset Password'));
     $dialog = new AphrontDialogView();
     $dialog->setUser($request->getUser());
     $dialog->setTitle(pht('Forgot Password / Email Login'));
     $dialog->appendChild($email_auth);
     $dialog->addSubmitButton(pht('Send Email'));
     $dialog->setSubmitURI('/login/email/');
     return $this->buildApplicationPage(array($crumbs, $dialog), array('title' => pht('Forgot Password')));
 }
Ejemplo n.º 7
0
    public function sendUsernameChangeEmail(PhabricatorUser $admin, $old_username)
    {
        $admin_username = $admin->getUserName();
        $admin_realname = $admin->getRealName();
        $new_username = $this->getUserName();
        $password_instructions = null;
        if (PhabricatorPasswordAuthProvider::getPasswordProvider()) {
            $engine = new PhabricatorAuthSessionEngine();
            $uri = $engine->getOneTimeLoginURI($this, null, PhabricatorAuthSessionEngine::ONETIME_USERNAME);
            $password_instructions = <<<EOTXT
If you use a password to login, you'll need to reset it before you can login
again. You can reset your password by following this link:

  {$uri}

And, of course, you'll need to use your new username to login from now on. If
you use OAuth to login, nothing should change.

EOTXT;
        }
        $body = <<<EOBODY
{$admin_username} ({$admin_realname}) has changed your Phabricator username.

  Old Username: {$old_username}
  New Username: {$new_username}

{$password_instructions}
EOBODY;
        $mail = id(new PhabricatorMetaMTAMail())->addTos(array($this->getPHID()))->setForceDelivery(true)->setSubject('[Phabricator] Username Changed')->setBody($body)->saveAndSend();
    }
Ejemplo n.º 8
0
 public function sendUsernameChangeEmail(PhabricatorUser $admin, $old_username)
 {
     $admin_username = $admin->getUserName();
     $admin_realname = $admin->getRealName();
     $new_username = $this->getUserName();
     $password_instructions = null;
     if (PhabricatorPasswordAuthProvider::getPasswordProvider()) {
         $engine = new PhabricatorAuthSessionEngine();
         $uri = $engine->getOneTimeLoginURI($this, null, PhabricatorAuthSessionEngine::ONETIME_USERNAME);
         $password_instructions = sprintf("%s\n\n  %s\n\n%s\n", pht("If you use a password to login, you'll need to reset it " . "before you can login again. You can reset your password by " . "following this link:"), $uri, pht("And, of course, you'll need to use your new username to login " . "from now on. If you use OAuth to login, nothing should change."));
     }
     $body = sprintf("%s\n\n  %s\n  %s\n\n%s", pht('%s (%s) has changed your Phabricator username.', $admin_username, $admin_realname), pht('Old Username: %s', $old_username), pht('New Username: %s', $new_username), $password_instructions);
     $mail = id(new PhabricatorMetaMTAMail())->addTos(array($this->getPHID()))->setForceDelivery(true)->setSubject(pht('[Phabricator] Username Changed'))->setBody($body)->saveAndSend();
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getUser();
     $document = id(new LegalpadDocumentQuery())->setViewer($viewer)->withIDs(array($request->getURIData('id')))->needDocumentBodies(true)->executeOne();
     if (!$document) {
         return new Aphront404Response();
     }
     $information = $this->readSignerInformation($document, $request);
     if ($information instanceof AphrontResponse) {
         return $information;
     }
     list($signer_phid, $signature_data) = $information;
     $signature = null;
     $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
     $is_individual = $document->getSignatureType() == $type_individual;
     switch ($document->getSignatureType()) {
         case LegalpadDocument::SIGNATURE_TYPE_NONE:
             // nothing to sign means this should be true
             $has_signed = true;
             // this is a status UI element
             $signed_status = null;
             break;
         case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
             if ($signer_phid) {
                 // TODO: This is odd and should probably be adjusted after
                 // grey/external accounts work better, but use the omnipotent
                 // viewer to check for a signature so we can pick up
                 // anonymous/grey signatures.
                 $signature = id(new LegalpadDocumentSignatureQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withDocumentPHIDs(array($document->getPHID()))->withSignerPHIDs(array($signer_phid))->executeOne();
                 if ($signature && !$viewer->isLoggedIn()) {
                     return $this->newDialog()->setTitle(pht('Already Signed'))->appendParagraph(pht('You have already signed this document!'))->addCancelButton('/' . $document->getMonogram(), pht('Okay'));
                 }
             }
             $signed_status = null;
             if (!$signature) {
                 $has_signed = false;
                 $signature = id(new LegalpadDocumentSignature())->setSignerPHID($signer_phid)->setDocumentPHID($document->getPHID())->setDocumentVersion($document->getVersions());
                 // If the user is logged in, show a notice that they haven't signed.
                 // If they aren't logged in, we can't be as sure, so don't show
                 // anything.
                 if ($viewer->isLoggedIn()) {
                     $signed_status = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setErrors(array(pht('You have not signed this document yet.')));
                 }
             } else {
                 $has_signed = true;
                 $signature_data = $signature->getSignatureData();
                 // In this case, we know they've signed.
                 $signed_at = $signature->getDateCreated();
                 if ($signature->getIsExemption()) {
                     $exemption_phid = $signature->getExemptionPHID();
                     $handles = $this->loadViewerHandles(array($exemption_phid));
                     $exemption_handle = $handles[$exemption_phid];
                     $signed_text = pht('You do not need to sign this document. ' . '%s added a signature exemption for you on %s.', $exemption_handle->renderLink(), phabricator_datetime($signed_at, $viewer));
                 } else {
                     $signed_text = pht('You signed this document on %s.', phabricator_datetime($signed_at, $viewer));
                 }
                 $signed_status = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_NOTICE)->setErrors(array($signed_text));
             }
             $field_errors = array('name' => true, 'email' => true, 'agree' => true);
             $signature->setSignatureData($signature_data);
             break;
         case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
             $signature = id(new LegalpadDocumentSignature())->setDocumentPHID($document->getPHID())->setDocumentVersion($document->getVersions());
             if ($viewer->isLoggedIn()) {
                 $has_signed = false;
                 $signed_status = null;
             } else {
                 // This just hides the form.
                 $has_signed = true;
                 $login_text = pht('This document requires a corporate signatory. You must log in to ' . 'accept this document on behalf of a company you represent.');
                 $signed_status = id(new PHUIInfoView())->setSeverity(PHUIInfoView::SEVERITY_WARNING)->setErrors(array($login_text));
             }
             $field_errors = array('name' => true, 'address' => true, 'contact.name' => true, 'email' => true);
             $signature->setSignatureData($signature_data);
             break;
     }
     $errors = array();
     if ($request->isFormOrHisecPost() && !$has_signed) {
         // Require two-factor auth to sign legal documents.
         if ($viewer->isLoggedIn()) {
             $engine = new PhabricatorAuthSessionEngine();
             $engine->requireHighSecuritySession($viewer, $request, '/' . $document->getMonogram());
         }
         list($form_data, $errors, $field_errors) = $this->readSignatureForm($document, $request);
         $signature_data = $form_data + $signature_data;
         $signature->setSignatureData($signature_data);
         $signature->setSignatureType($document->getSignatureType());
         $signature->setSignerName((string) idx($signature_data, 'name'));
         $signature->setSignerEmail((string) idx($signature_data, 'email'));
         $agree = $request->getExists('agree');
         if (!$agree) {
             $errors[] = pht('You must check "I agree to the terms laid forth above."');
             $field_errors['agree'] = pht('Required');
         }
         if ($viewer->isLoggedIn() && $is_individual) {
             $verified = LegalpadDocumentSignature::VERIFIED;
         } else {
             $verified = LegalpadDocumentSignature::UNVERIFIED;
         }
         $signature->setVerified($verified);
         if (!$errors) {
             $signature->save();
             // If the viewer is logged in, signing for themselves, send them to
             // the document page, which will show that they have signed the
             // document. Unless of course they were required to sign the
             // document to use Phabricator; in that case try really hard to
             // re-direct them to where they wanted to go.
             //
             // Otherwise, send them to a completion page.
             if ($viewer->isLoggedIn() && $is_individual) {
                 $next_uri = '/' . $document->getMonogram();
                 if ($document->getRequireSignature()) {
                     $request_uri = $request->getRequestURI();
                     $next_uri = (string) $request_uri;
                 }
             } else {
                 $this->sendVerifySignatureEmail($document, $signature);
                 $next_uri = $this->getApplicationURI('done/');
             }
             return id(new AphrontRedirectResponse())->setURI($next_uri);
         }
     }
     $document_body = $document->getDocumentBody();
     $engine = id(new PhabricatorMarkupEngine())->setViewer($viewer);
     $engine->addObject($document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT);
     $engine->process();
     $document_markup = $engine->getOutput($document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT);
     $title = $document_body->getTitle();
     $manage_uri = $this->getApplicationURI('view/' . $document->getID() . '/');
     $can_edit = PhabricatorPolicyFilter::hasCapability($viewer, $document, PhabricatorPolicyCapability::CAN_EDIT);
     // Use the last content update as the modified date. We don't want to
     // show that a document like a TOS was "updated" by an incidental change
     // to a field like the preamble or privacy settings which does not acutally
     // affect the content of the agreement.
     $content_updated = $document_body->getDateCreated();
     // NOTE: We're avoiding `setPolicyObject()` here so we don't pick up
     // extra UI elements that are unnecessary and clutter the signature page.
     // These details are available on the "Manage" page.
     $header = id(new PHUIHeaderView())->setHeader($title)->setUser($viewer)->setEpoch($content_updated)->addActionLink(id(new PHUIButtonView())->setTag('a')->setIcon(id(new PHUIIconView())->setIconFont('fa-pencil'))->setText(pht('Manage'))->setHref($manage_uri)->setDisabled(!$can_edit)->setWorkflow(!$can_edit));
     $preamble_box = null;
     if (strlen($document->getPreamble())) {
         $preamble_text = PhabricatorMarkupEngine::renderOneObject(id(new PhabricatorMarkupOneOff())->setContent($document->getPreamble()), 'default', $viewer);
         // NOTE: We're avoiding `setObject()` here so we don't pick up extra UI
         // elements like "Subscribers". This information is available on the
         // "Manage" page, but just clutters up the "Signature" page.
         $preamble = id(new PHUIPropertyListView())->setUser($viewer)->addSectionHeader(pht('Preamble'))->addTextContent($preamble_text);
         $preamble_box = new PHUIPropertyGroupView();
         $preamble_box->addPropertyList($preamble);
     }
     $content = id(new PHUIDocumentViewPro())->addClass('legalpad')->setHeader($header)->appendChild(array($signed_status, $preamble_box, $document_markup));
     $signature_box = null;
     if (!$has_signed) {
         $error_view = null;
         if ($errors) {
             $error_view = id(new PHUIInfoView())->setErrors($errors);
         }
         $signature_form = $this->buildSignatureForm($document, $signature, $field_errors);
         switch ($document->getSignatureType()) {
             default:
                 break;
             case LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL:
             case LegalpadDocument::SIGNATURE_TYPE_CORPORATION:
                 $box = id(new PHUIObjectBoxView())->setHeaderText(pht('Agree and Sign Document'))->setForm($signature_form);
                 if ($error_view) {
                     $box->setInfoView($error_view);
                 }
                 $signature_box = phutil_tag_div('phui-document-view-pro-box', $box);
                 break;
         }
     }
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->setBorder(true);
     $crumbs->addTextCrumb($document->getMonogram());
     return $this->buildApplicationPage(array($crumbs, $content, $signature_box), array('title' => $title, 'pageObjects' => array($document->getPHID())));
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $this->getViewer();
     $id = $request->getURIData('id');
     $link_type = $request->getURIData('type');
     $key = $request->getURIData('key');
     $email_id = $request->getURIData('emailID');
     if ($request->getUser()->isLoggedIn()) {
         return $this->renderError(pht('You are already logged in.'));
     }
     $target_user = id(new PhabricatorPeopleQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withIDs(array($id))->executeOne();
     if (!$target_user) {
         return new Aphront404Response();
     }
     // NOTE: As a convenience to users, these one-time login URIs may also
     // be associated with an email address which will be verified when the
     // URI is used.
     // This improves the new user experience for users receiving "Welcome"
     // emails on installs that require verification: if we did not verify the
     // email, they'd immediately get roadblocked with a "Verify Your Email"
     // error and have to go back to their email account, wait for a
     // "Verification" email, and then click that link to actually get access to
     // their account. This is hugely unwieldy, and if the link was only sent
     // to the user's email in the first place we can safely verify it as a
     // side effect of login.
     // The email hashed into the URI so users can't verify some email they
     // do not own by doing this:
     //
     //  - Add some address you do not own;
     //  - request a password reset;
     //  - change the URI in the email to the address you don't own;
     //  - login via the email link; and
     //  - get a "verified" address you don't control.
     $target_email = null;
     if ($email_id) {
         $target_email = id(new PhabricatorUserEmail())->loadOneWhere('userPHID = %s AND id = %d', $target_user->getPHID(), $email_id);
         if (!$target_email) {
             return new Aphront404Response();
         }
     }
     $engine = new PhabricatorAuthSessionEngine();
     $token = $engine->loadOneTimeLoginKey($target_user, $target_email, $key);
     if (!$token) {
         return $this->newDialog()->setTitle(pht('Unable to Login'))->setShortTitle(pht('Login Failure'))->appendParagraph(pht('The login link you clicked is invalid, out of date, or has ' . 'already been used.'))->appendParagraph(pht('Make sure you are copy-and-pasting the entire link into ' . 'your browser. Login links are only valid for 24 hours, and ' . 'can only be used once.'))->appendParagraph(pht('You can try again, or request a new link via email.'))->addCancelButton('/login/email/', pht('Send Another Email'));
     }
     if ($request->isFormPost()) {
         // If we have an email bound into this URI, verify email so that clicking
         // the link in the "Welcome" email is good enough, without requiring users
         // to go through a second round of email verification.
         $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
         // Nuke the token and all other outstanding password reset tokens.
         // There is no particular security benefit to destroying them all, but
         // it should reduce HackerOne reports of nebulous harm.
         PhabricatorAuthTemporaryToken::revokeTokens($target_user, array($target_user->getPHID()), array(PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE, PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE));
         if ($target_email) {
             id(new PhabricatorUserEditor())->setActor($target_user)->verifyEmail($target_user, $target_email);
         }
         unset($unguarded);
         $next = '/';
         if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
             $next = '/settings/panel/external/';
         } else {
             // We're going to let the user reset their password without knowing
             // the old one. Generate a one-time token for that.
             $key = Filesystem::readRandomCharacters(16);
             $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             id(new PhabricatorAuthTemporaryToken())->setObjectPHID($target_user->getPHID())->setTokenType(PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE)->setTokenExpires(time() + phutil_units('1 hour in seconds'))->setTokenCode(PhabricatorHash::digest($key))->save();
             unset($unguarded);
             $next = (string) id(new PhutilURI('/settings/panel/password/'))->setQueryParams(array('key' => $key));
             $request->setTemporaryCookie(PhabricatorCookies::COOKIE_HISEC, 'yes');
         }
         PhabricatorCookies::setNextURICookie($request, $next, $force = true);
         return $this->loginUser($target_user);
     }
     // NOTE: We need to CSRF here so attackers can't generate an email link,
     // then log a user in to an account they control via sneaky invisible
     // form submissions.
     switch ($link_type) {
         case PhabricatorAuthSessionEngine::ONETIME_WELCOME:
             $title = pht('Welcome to Phabricator');
             break;
         case PhabricatorAuthSessionEngine::ONETIME_RECOVER:
             $title = pht('Account Recovery');
             break;
         case PhabricatorAuthSessionEngine::ONETIME_USERNAME:
         case PhabricatorAuthSessionEngine::ONETIME_RESET:
         default:
             $title = pht('Login to Phabricator');
             break;
     }
     $body = array();
     $body[] = pht('Use the button below to log in as: %s', phutil_tag('strong', array(), $target_user->getUsername()));
     if ($target_email && !$target_email->getIsVerified()) {
         $body[] = pht('Logging in will verify %s as an email address you own.', phutil_tag('strong', array(), $target_email->getAddress()));
     }
     $body[] = pht('After logging in you should set a password for your account, or ' . 'link your account to an external account that you can use to ' . 'authenticate in the future.');
     $dialog = $this->newDialog()->setTitle($title)->addSubmitButton(pht('Login (%s)', $target_user->getUsername()))->addCancelButton('/');
     foreach ($body as $paragraph) {
         $dialog->appendParagraph($paragraph);
     }
     return id(new AphrontDialogResponse())->setDialog($dialog);
 }
 public function handleRequest(AphrontRequest $request)
 {
     $viewer = $request->getUser();
     if ($viewer->isLoggedIn()) {
         // Kick the user home if they are already logged in.
         return id(new AphrontRedirectResponse())->setURI('/');
     }
     if ($request->isAjax()) {
         return $this->processAjaxRequest();
     }
     if ($request->isConduit()) {
         return $this->processConduitRequest();
     }
     // If the user gets this far, they aren't logged in, so if they have a
     // user session token we can conclude that it's invalid: if it was valid,
     // they'd have been logged in above and never made it here. Try to clear
     // it and warn the user they may need to nuke their cookies.
     $session_token = $request->getCookie(PhabricatorCookies::COOKIE_SESSION);
     $did_clear = $request->getStr('cleared');
     if (strlen($session_token)) {
         $kind = PhabricatorAuthSessionEngine::getSessionKindFromToken($session_token);
         switch ($kind) {
             case PhabricatorAuthSessionEngine::KIND_ANONYMOUS:
                 // If this is an anonymous session. It's expected that they won't
                 // be logged in, so we can just continue.
                 break;
             default:
                 // The session cookie is invalid, so try to clear it.
                 $request->clearCookie(PhabricatorCookies::COOKIE_USERNAME);
                 $request->clearCookie(PhabricatorCookies::COOKIE_SESSION);
                 // We've previously tried to clear the cookie but we ended up back
                 // here, so it didn't work. Hard fatal instead of trying again.
                 if ($did_clear) {
                     return $this->renderError(pht('Your login session is invalid, and clearing the session ' . 'cookie was unsuccessful. Try clearing your browser cookies.'));
                 }
                 $redirect_uri = $request->getRequestURI();
                 $redirect_uri->setQueryParam('cleared', 1);
                 return id(new AphrontRedirectResponse())->setURI($redirect_uri);
         }
     }
     // If we just cleared the session cookie and it worked, clean up after
     // ourselves by redirecting to get rid of the "cleared" parameter. The
     // the workflow will continue normally.
     if ($did_clear) {
         $redirect_uri = $request->getRequestURI();
         $redirect_uri->setQueryParam('cleared', null);
         return id(new AphrontRedirectResponse())->setURI($redirect_uri);
     }
     $providers = PhabricatorAuthProvider::getAllEnabledProviders();
     foreach ($providers as $key => $provider) {
         if (!$provider->shouldAllowLogin()) {
             unset($providers[$key]);
         }
     }
     if (!$providers) {
         if ($this->isFirstTimeSetup()) {
             // If this is a fresh install, let the user register their admin
             // account.
             return id(new AphrontRedirectResponse())->setURI($this->getApplicationURI('/register/'));
         }
         return $this->renderError(pht('This Phabricator install is not configured with any enabled ' . 'authentication providers which can be used to log in. If you ' . 'have accidentally locked yourself out by disabling all providers, ' . 'you can use `%s` to recover access to an administrative account.', 'phabricator/bin/auth recover <username>'));
     }
     $next_uri = $request->getStr('next');
     if (!strlen($next_uri)) {
         if ($this->getDelegatingController()) {
             // Only set a next URI from the request path if this controller was
             // delegated to, which happens when a user tries to view a page which
             // requires them to login.
             // If this controller handled the request directly, we're on the main
             // login page, and never want to redirect the user back here after they
             // login.
             $next_uri = (string) $this->getRequest()->getRequestURI();
         }
     }
     if (!$request->isFormPost()) {
         if (strlen($next_uri)) {
             PhabricatorCookies::setNextURICookie($request, $next_uri);
         }
         PhabricatorCookies::setClientIDCookie($request);
     }
     $auto_response = $this->tryAutoLogin($providers);
     if ($auto_response) {
         return $auto_response;
     }
     $invite = $this->loadInvite();
     $not_buttons = array();
     $are_buttons = array();
     $providers = msort($providers, 'getLoginOrder');
     foreach ($providers as $provider) {
         if ($invite) {
             $form = $provider->buildInviteForm($this);
         } else {
             $form = $provider->buildLoginForm($this);
         }
         if ($provider->isLoginFormAButton()) {
             $are_buttons[] = $form;
         } else {
             $not_buttons[] = $form;
         }
     }
     $out = array();
     $out[] = $not_buttons;
     if ($are_buttons) {
         require_celerity_resource('auth-css');
         foreach ($are_buttons as $key => $button) {
             $are_buttons[$key] = phutil_tag('div', array('class' => 'phabricator-login-button mmb'), $button);
         }
         // If we only have one button, add a second pretend button so that we
         // always have two columns. This makes it easier to get the alignments
         // looking reasonable.
         if (count($are_buttons) == 1) {
             $are_buttons[] = null;
         }
         $button_columns = id(new AphrontMultiColumnView())->setFluidLayout(true);
         $are_buttons = array_chunk($are_buttons, ceil(count($are_buttons) / 2));
         foreach ($are_buttons as $column) {
             $button_columns->addColumn($column);
         }
         $out[] = phutil_tag('div', array('class' => 'phabricator-login-buttons'), $button_columns);
     }
     $handlers = PhabricatorAuthLoginHandler::getAllHandlers();
     $delegating_controller = $this->getDelegatingController();
     $header = array();
     foreach ($handlers as $handler) {
         $handler = clone $handler;
         $handler->setRequest($request);
         if ($delegating_controller) {
             $handler->setDelegatingController($delegating_controller);
         }
         $header[] = $handler->getAuthLoginHeaderContent();
     }
     $invite_message = null;
     if ($invite) {
         $invite_message = $this->renderInviteHeader($invite);
     }
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Login'));
     $crumbs->setBorder(true);
     $title = pht('Login to Phabricator');
     $view = array($header, $invite_message, $out);
     return $this->newPage()->setTitle($title)->setCrumbs($crumbs)->appendChild($view);
 }
 public function processRequest()
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     $document = id(new LegalpadDocumentQuery())->setViewer($viewer)->withIDs(array($this->id))->needDocumentBodies(true)->executeOne();
     if (!$document) {
         return new Aphront404Response();
     }
     list($signer_phid, $signature_data) = $this->readSignerInformation($document, $request);
     $signature = null;
     $type_individual = LegalpadDocument::SIGNATURE_TYPE_INDIVIDUAL;
     $is_individual = $document->getSignatureType() == $type_individual;
     if ($is_individual) {
         if ($signer_phid) {
             // TODO: This is odd and should probably be adjusted after grey/external
             // accounts work better, but use the omnipotent viewer to check for a
             // signature so we can pick up anonymous/grey signatures.
             $signature = id(new LegalpadDocumentSignatureQuery())->setViewer(PhabricatorUser::getOmnipotentUser())->withDocumentPHIDs(array($document->getPHID()))->withSignerPHIDs(array($signer_phid))->executeOne();
             if ($signature && !$viewer->isLoggedIn()) {
                 return $this->newDialog()->setTitle(pht('Already Signed'))->appendParagraph(pht('You have already signed this document!'))->addCancelButton('/' . $document->getMonogram(), pht('Okay'));
             }
         }
         $signed_status = null;
         if (!$signature) {
             $has_signed = false;
             $signature = id(new LegalpadDocumentSignature())->setSignerPHID($signer_phid)->setDocumentPHID($document->getPHID())->setDocumentVersion($document->getVersions());
             // If the user is logged in, show a notice that they haven't signed.
             // If they aren't logged in, we can't be as sure, so don't show
             // anything.
             if ($viewer->isLoggedIn()) {
                 $signed_status = id(new AphrontErrorView())->setSeverity(AphrontErrorView::SEVERITY_WARNING)->setErrors(array(pht('You have not signed this document yet.')));
             }
         } else {
             $has_signed = true;
             $signature_data = $signature->getSignatureData();
             // In this case, we know they've signed.
             $signed_at = $signature->getDateCreated();
             if ($signature->getIsExemption()) {
                 $exemption_phid = $signature->getExemptionPHID();
                 $handles = $this->loadViewerHandles(array($exemption_phid));
                 $exemption_handle = $handles[$exemption_phid];
                 $signed_text = pht('You do not need to sign this document. ' . '%s added a signature exemption for you on %s.', $exemption_handle->renderLink(), phabricator_datetime($signed_at, $viewer));
             } else {
                 $signed_text = pht('You signed this document on %s.', phabricator_datetime($signed_at, $viewer));
             }
             $signed_status = id(new AphrontErrorView())->setSeverity(AphrontErrorView::SEVERITY_NOTICE)->setErrors(array($signed_text));
         }
         $field_errors = array('name' => true, 'email' => true, 'agree' => true);
     } else {
         $signature = id(new LegalpadDocumentSignature())->setDocumentPHID($document->getPHID())->setDocumentVersion($document->getVersions());
         if ($viewer->isLoggedIn()) {
             $has_signed = false;
             $signed_status = null;
         } else {
             // This just hides the form.
             $has_signed = true;
             $login_text = pht('This document requires a corporate signatory. You must log in to ' . 'accept this document on behalf of a company you represent.');
             $signed_status = id(new AphrontErrorView())->setSeverity(AphrontErrorView::SEVERITY_WARNING)->setErrors(array($login_text));
         }
         $field_errors = array('name' => true, 'address' => true, 'contact.name' => true, 'email' => true);
     }
     $signature->setSignatureData($signature_data);
     $errors = array();
     if ($request->isFormOrHisecPost() && !$has_signed) {
         // Require two-factor auth to sign legal documents.
         if ($viewer->isLoggedIn()) {
             $engine = new PhabricatorAuthSessionEngine();
             $engine->requireHighSecuritySession($viewer, $request, '/' . $document->getMonogram());
         }
         list($form_data, $errors, $field_errors) = $this->readSignatureForm($document, $request);
         $signature_data = $form_data + $signature_data;
         $signature->setSignatureData($signature_data);
         $signature->setSignatureType($document->getSignatureType());
         $signature->setSignerName((string) idx($signature_data, 'name'));
         $signature->setSignerEmail((string) idx($signature_data, 'email'));
         $agree = $request->getExists('agree');
         if (!$agree) {
             $errors[] = pht('You must check "I agree to the terms laid forth above."');
             $field_errors['agree'] = pht('Required');
         }
         if ($viewer->isLoggedIn() && $is_individual) {
             $verified = LegalpadDocumentSignature::VERIFIED;
         } else {
             $verified = LegalpadDocumentSignature::UNVERIFIED;
         }
         $signature->setVerified($verified);
         if (!$errors) {
             $signature->save();
             // If the viewer is logged in, send them to the document page, which
             // will show that they have signed the document. Otherwise, send them
             // to a completion page.
             if ($viewer->isLoggedIn() && $is_individual) {
                 $next_uri = '/' . $document->getMonogram();
             } else {
                 $this->sendVerifySignatureEmail($document, $signature);
                 $next_uri = $this->getApplicationURI('done/');
             }
             return id(new AphrontRedirectResponse())->setURI($next_uri);
         }
     }
     $document_body = $document->getDocumentBody();
     $engine = id(new PhabricatorMarkupEngine())->setViewer($viewer);
     $engine->addObject($document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT);
     $engine->process();
     $document_markup = $engine->getOutput($document_body, LegalpadDocumentBody::MARKUP_FIELD_TEXT);
     $title = $document_body->getTitle();
     $manage_uri = $this->getApplicationURI('view/' . $document->getID() . '/');
     $can_edit = PhabricatorPolicyFilter::hasCapability($viewer, $document, PhabricatorPolicyCapability::CAN_EDIT);
     $header = id(new PHUIHeaderView())->setHeader($title)->addActionLink(id(new PHUIButtonView())->setTag('a')->setIcon(id(new PHUIIconView())->setIconFont('fa-pencil'))->setText(pht('Manage Document'))->setHref($manage_uri)->setDisabled(!$can_edit)->setWorkflow(!$can_edit));
     $preamble = null;
     if (strlen($document->getPreamble())) {
         $preamble_text = PhabricatorMarkupEngine::renderOneObject(id(new PhabricatorMarkupOneOff())->setContent($document->getPreamble()), 'default', $viewer);
         $preamble = id(new PHUIPropertyListView())->addSectionHeader(pht('Preamble'))->addTextContent($preamble_text);
     }
     $content = id(new PHUIDocumentView())->addClass('legalpad')->setHeader($header)->setFontKit(PHUIDocumentView::FONT_SOURCE_SANS)->appendChild(array($signed_status, $preamble, $document_markup));
     if (!$has_signed) {
         $error_view = null;
         if ($errors) {
             $error_view = id(new AphrontErrorView())->setErrors($errors);
         }
         $signature_form = $this->buildSignatureForm($document, $signature, $field_errors);
         $subheader = id(new PHUIHeaderView())->setHeader(pht('Agree and Sign Document'))->setBleedHeader(true);
         $content->appendChild(array($subheader, $error_view, $signature_form));
     }
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb($document->getMonogram());
     return $this->buildApplicationPage(array($crumbs, $content), array('title' => $title, 'pageObjects' => array($document->getPHID())));
 }