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 = $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)
 {
     $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 = $this->getViewer();
     $action = $request->getURIData('action');
     $provider_key = $request->getURIData('pkey');
     $provider = PhabricatorAuthProvider::getEnabledProviderByKey($provider_key);
     if (!$provider) {
         return new Aphront404Response();
     }
     switch ($action) {
         case 'link':
             if (!$provider->shouldAllowAccountLink()) {
                 return $this->renderErrorPage(pht('Account Not Linkable'), array(pht('This provider is not configured to allow linking.')));
             }
             break;
         case 'refresh':
             if (!$provider->shouldAllowAccountRefresh()) {
                 return $this->renderErrorPage(pht('Account Not Refreshable'), array(pht('This provider does not allow refreshing.')));
             }
             break;
         default:
             return new Aphront400Response();
     }
     $account = id(new PhabricatorExternalAccount())->loadOneWhere('accountType = %s AND accountDomain = %s AND userPHID = %s', $provider->getProviderType(), $provider->getProviderDomain(), $viewer->getPHID());
     switch ($action) {
         case 'link':
             if ($account) {
                 return $this->renderErrorPage(pht('Account Already Linked'), array(pht('Your Phabricator account is already linked to an external ' . 'account for this provider.')));
             }
             break;
         case 'refresh':
             if (!$account) {
                 return $this->renderErrorPage(pht('No Account Linked'), array(pht('You do not have a linked account on this provider, and thus ' . 'can not refresh it.')));
             }
             break;
         default:
             return new Aphront400Response();
     }
     $panel_uri = '/settings/panel/external/';
     PhabricatorCookies::setClientIDCookie($request);
     switch ($action) {
         case 'link':
             id(new PhabricatorAuthSessionEngine())->requireHighSecuritySession($viewer, $request, $panel_uri);
             $form = $provider->buildLinkForm($this);
             break;
         case 'refresh':
             $form = $provider->buildRefreshForm($this);
             break;
         default:
             return new Aphront400Response();
     }
     if ($provider->isLoginFormAButton()) {
         require_celerity_resource('auth-css');
         $form = phutil_tag('div', array('class' => 'phabricator-link-button pl'), $form);
     }
     switch ($action) {
         case 'link':
             $name = pht('Link Account');
             $title = pht('Link %s Account', $provider->getProviderName());
             break;
         case 'refresh':
             $name = pht('Refresh Account');
             $title = pht('Refresh %s Account', $provider->getProviderName());
             break;
         default:
             return new Aphront400Response();
     }
     $crumbs = $this->buildApplicationCrumbs();
     $crumbs->addTextCrumb(pht('Link Account'), $panel_uri);
     $crumbs->addTextCrumb($provider->getProviderName($name));
     return $this->buildApplicationPage(array($crumbs, $form), array('title' => $title));
 }
 private function processAjaxRequest()
 {
     $request = $this->getRequest();
     $viewer = $request->getUser();
     // We end up here if the user clicks a workflow link that they need to
     // login to use. We give them a dialog saying "You need to login...".
     if ($request->isDialogFormPost()) {
         return id(new AphrontRedirectResponse())->setURI($request->getRequestURI());
     }
     // Often, users end up here by clicking a disabled action link in the UI
     // (for example, they might click "Edit Blocking Tasks" on a Maniphest
     // task page). After they log in we want to send them back to that main
     // object page if we can, since it's confusing to end up on a standalone
     // page with only a dialog (particularly if that dialog is another error,
     // like a policy exception).
     $via_header = AphrontRequest::getViaHeaderName();
     $via_uri = AphrontRequest::getHTTPHeader($via_header);
     if (strlen($via_uri)) {
         PhabricatorCookies::setNextURICookie($request, $via_uri, $force = true);
     }
     return $this->newDialog()->setTitle(pht('Login Required'))->appendParagraph(pht('You must login to take this action.'))->addSubmitButton(pht('Login'))->addCancelButton('/');
 }