[ Index ] |
PHP Cross Reference of Phabricator |
[Summary view] [Print] [Text view]
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 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Sun Nov 30 09:20:46 2014 | Cross-referenced by PHPXref 0.7.1 |