[ Index ]

PHP Cross Reference of Phabricator

title

Body

[close]

/src/applications/auth/controller/ -> PhabricatorAuthOneTimeLoginController.php (source)

   1  <?php
   2  
   3  final class PhabricatorAuthOneTimeLoginController
   4    extends PhabricatorAuthController {
   5  
   6    private $id;
   7    private $key;
   8    private $emailID;
   9    private $linkType;
  10  
  11    public function shouldRequireLogin() {
  12      return false;
  13    }
  14  
  15    public function willProcessRequest(array $data) {
  16      $this->linkType = $data['type'];
  17      $this->id = $data['id'];
  18      $this->key = $data['key'];
  19      $this->emailID = idx($data, 'emailID');
  20    }
  21  
  22    public function processRequest() {
  23      $request = $this->getRequest();
  24  
  25      if ($request->getUser()->isLoggedIn()) {
  26        return $this->renderError(
  27          pht('You are already logged in.'));
  28      }
  29  
  30      $target_user = id(new PhabricatorPeopleQuery())
  31        ->setViewer(PhabricatorUser::getOmnipotentUser())
  32        ->withIDs(array($this->id))
  33        ->executeOne();
  34      if (!$target_user) {
  35        return new Aphront404Response();
  36      }
  37  
  38      // NOTE: As a convenience to users, these one-time login URIs may also
  39      // be associated with an email address which will be verified when the
  40      // URI is used.
  41  
  42      // This improves the new user experience for users receiving "Welcome"
  43      // emails on installs that require verification: if we did not verify the
  44      // email, they'd immediately get roadblocked with a "Verify Your Email"
  45      // error and have to go back to their email account, wait for a
  46      // "Verification" email, and then click that link to actually get access to
  47      // their account. This is hugely unwieldy, and if the link was only sent
  48      // to the user's email in the first place we can safely verify it as a
  49      // side effect of login.
  50  
  51      // The email hashed into the URI so users can't verify some email they
  52      // do not own by doing this:
  53      //
  54      //  - Add some address you do not own;
  55      //  - request a password reset;
  56      //  - change the URI in the email to the address you don't own;
  57      //  - login via the email link; and
  58      //  - get a "verified" address you don't control.
  59  
  60      $target_email = null;
  61      if ($this->emailID) {
  62        $target_email = id(new PhabricatorUserEmail())->loadOneWhere(
  63          'userPHID = %s AND id = %d',
  64          $target_user->getPHID(),
  65          $this->emailID);
  66        if (!$target_email) {
  67          return new Aphront404Response();
  68        }
  69      }
  70  
  71      $engine = new PhabricatorAuthSessionEngine();
  72      $token = $engine->loadOneTimeLoginKey(
  73        $target_user,
  74        $target_email,
  75        $this->key);
  76  
  77      if (!$token) {
  78        return $this->newDialog()
  79          ->setTitle(pht('Unable to Login'))
  80          ->setShortTitle(pht('Login Failure'))
  81          ->appendParagraph(
  82            pht(
  83              'The login link you clicked is invalid, out of date, or has '.
  84              'already been used.'))
  85          ->appendParagraph(
  86            pht(
  87              'Make sure you are copy-and-pasting the entire link into '.
  88              'your browser. Login links are only valid for 24 hours, and '.
  89              'can only be used once.'))
  90          ->appendParagraph(
  91            pht('You can try again, or request a new link via email.'))
  92          ->addCancelButton('/login/email/', pht('Send Another Email'));
  93      }
  94  
  95      if ($request->isFormPost()) {
  96        // If we have an email bound into this URI, verify email so that clicking
  97        // the link in the "Welcome" email is good enough, without requiring users
  98        // to go through a second round of email verification.
  99  
 100        $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 101          // Nuke the token and all other outstanding password reset tokens.
 102          // There is no particular security benefit to destroying them all, but
 103          // it should reduce HackerOne reports of nebulous harm.
 104  
 105          PhabricatorAuthTemporaryToken::revokeTokens(
 106            $target_user,
 107            array($target_user->getPHID()),
 108            array(
 109              PhabricatorAuthSessionEngine::ONETIME_TEMPORARY_TOKEN_TYPE,
 110              PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE,
 111            ));
 112  
 113          if ($target_email) {
 114            id(new PhabricatorUserEditor())
 115              ->setActor($target_user)
 116              ->verifyEmail($target_user, $target_email);
 117          }
 118        unset($unguarded);
 119  
 120        $next = '/';
 121        if (!PhabricatorPasswordAuthProvider::getPasswordProvider()) {
 122          $next = '/settings/panel/external/';
 123        } else {
 124  
 125          // We're going to let the user reset their password without knowing
 126          // the old one. Generate a one-time token for that.
 127          $key = Filesystem::readRandomCharacters(16);
 128  
 129          $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
 130            id(new PhabricatorAuthTemporaryToken())
 131              ->setObjectPHID($target_user->getPHID())
 132              ->setTokenType(
 133                PhabricatorAuthSessionEngine::PASSWORD_TEMPORARY_TOKEN_TYPE)
 134              ->setTokenExpires(time() + phutil_units('1 hour in seconds'))
 135              ->setTokenCode(PhabricatorHash::digest($key))
 136              ->save();
 137          unset($unguarded);
 138  
 139          $next = (string)id(new PhutilURI('/settings/panel/password/'))
 140            ->setQueryParams(
 141              array(
 142                'key' => $key,
 143              ));
 144  
 145          $request->setTemporaryCookie(PhabricatorCookies::COOKIE_HISEC, 'yes');
 146        }
 147  
 148        PhabricatorCookies::setNextURICookie($request, $next, $force = true);
 149  
 150        return $this->loginUser($target_user);
 151      }
 152  
 153      // NOTE: We need to CSRF here so attackers can't generate an email link,
 154      // then log a user in to an account they control via sneaky invisible
 155      // form submissions.
 156  
 157      switch ($this->linkType) {
 158        case PhabricatorAuthSessionEngine::ONETIME_WELCOME:
 159          $title = pht('Welcome to Phabricator');
 160          break;
 161        case PhabricatorAuthSessionEngine::ONETIME_RECOVER:
 162          $title = pht('Account Recovery');
 163          break;
 164        case PhabricatorAuthSessionEngine::ONETIME_USERNAME:
 165        case PhabricatorAuthSessionEngine::ONETIME_RESET:
 166        default:
 167          $title = pht('Login to Phabricator');
 168          break;
 169      }
 170  
 171      $body = array();
 172      $body[] = pht(
 173        'Use the button below to log in as: %s',
 174        phutil_tag('strong', array(), $target_user->getUsername()));
 175  
 176      if ($target_email && !$target_email->getIsVerified()) {
 177        $body[] = pht(
 178          'Logging in will verify %s as an email address you own.',
 179          phutil_tag('strong', array(), $target_email->getAddress()));
 180  
 181      }
 182  
 183      $body[] = pht(
 184        'After logging in you should set a password for your account, or '.
 185        'link your account to an external account that you can use to '.
 186        'authenticate in the future.');
 187  
 188      $dialog = $this->newDialog()
 189        ->setTitle($title)
 190        ->addSubmitButton(pht('Login (%s)', $target_user->getUsername()))
 191        ->addCancelButton('/');
 192  
 193      foreach ($body as $paragraph) {
 194        $dialog->appendParagraph($paragraph);
 195      }
 196  
 197      return id(new AphrontDialogResponse())->setDialog($dialog);
 198    }
 199  }


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