Path: blob/master/src/applications/auth/engine/PhabricatorAuthInviteEngine.php
12256 views
<?php123/**4* This class does an unusual amount of flow control via exceptions. The intent5* is to make the workflows highly testable, because this code is high-stakes6* and difficult to test.7*/8final class PhabricatorAuthInviteEngine extends Phobject {910private $viewer;11private $userHasConfirmedVerify;1213public function setViewer(PhabricatorUser $viewer) {14$this->viewer = $viewer;15return $this;16}1718public function getViewer() {19if (!$this->viewer) {20throw new PhutilInvalidStateException('setViewer');21}22return $this->viewer;23}2425public function setUserHasConfirmedVerify($confirmed) {26$this->userHasConfirmedVerify = $confirmed;27return $this;28}2930private function shouldVerify() {31return $this->userHasConfirmedVerify;32}3334public function processInviteCode($code) {35$viewer = $this->getViewer();3637$invite = id(new PhabricatorAuthInviteQuery())38->setViewer($viewer)39->withVerificationCodes(array($code))40->executeOne();41if (!$invite) {42throw id(new PhabricatorAuthInviteInvalidException(43pht('Bad Invite Code'),44pht(45'The invite code in the link you clicked is invalid. Check that '.46'you followed the link correctly.')))47->setCancelButtonURI('/')48->setCancelButtonText(pht('Curses!'));49}5051$accepted_phid = $invite->getAcceptedByPHID();52if ($accepted_phid) {53if ($accepted_phid == $viewer->getPHID()) {54throw id(new PhabricatorAuthInviteInvalidException(55pht('Already Accepted'),56pht(57'You have already accepted this invitation.')))58->setCancelButtonURI('/')59->setCancelButtonText(pht('Awesome'));60} else {61throw id(new PhabricatorAuthInviteInvalidException(62pht('Already Accepted'),63pht(64'The invite code in the link you clicked has already '.65'been accepted.')))66->setCancelButtonURI('/')67->setCancelButtonText(pht('Continue'));68}69}7071$email = id(new PhabricatorUserEmail())->loadOneWhere(72'address = %s',73$invite->getEmailAddress());7475if ($viewer->isLoggedIn()) {76$this->handleLoggedInInvite($invite, $viewer, $email);77}7879if ($email) {80$other_user = $this->loadUserForEmail($email);8182if ($email->getIsVerified()) {83throw id(new PhabricatorAuthInviteLoginException(84pht('Already Registered'),85pht(86'The email address you just clicked a link from is already '.87'verified and associated with a registered account (%s). Log '.88'in to continue.',89phutil_tag('strong', array(), $other_user->getName()))))90->setCancelButtonText(pht('Log In'))91->setCancelButtonURI($this->getLoginURI());92} else if ($email->getIsPrimary()) {93throw id(new PhabricatorAuthInviteLoginException(94pht('Already Registered'),95pht(96'The email address you just clicked a link from is already '.97'the primary email address for a registered account (%s). Log '.98'in to continue.',99phutil_tag('strong', array(), $other_user->getName()))))100->setCancelButtonText(pht('Log In'))101->setCancelButtonURI($this->getLoginURI());102} else if (!$this->shouldVerify()) {103throw id(new PhabricatorAuthInviteVerifyException(104pht('Already Associated'),105pht(106'The email address you just clicked a link from is already '.107'associated with a registered account (%s), but is not '.108'verified. Log in to that account to continue. If you can not '.109'log in, you can register a new account.',110phutil_tag('strong', array(), $other_user->getName()))))111->setCancelButtonText(pht('Log In'))112->setCancelButtonURI($this->getLoginURI())113->setSubmitButtonText(pht('Register New Account'));114} else {115// NOTE: The address is not verified and not a primary address, so116// we will eventually steal it if the user completes registration.117}118}119120// The invite and email address are OK, but the user needs to register.121return $invite;122}123124private function handleLoggedInInvite(125PhabricatorAuthInvite $invite,126PhabricatorUser $viewer,127PhabricatorUserEmail $email = null) {128129if ($email && ($email->getUserPHID() !== $viewer->getPHID())) {130$other_user = $this->loadUserForEmail($email);131if ($email->getIsVerified()) {132throw id(new PhabricatorAuthInviteAccountException(133pht('Wrong Account'),134pht(135'You are logged in as %s, but the email address you just '.136'clicked a link from is already verified and associated '.137'with another account (%s). Switch accounts, then try again.',138phutil_tag('strong', array(), $viewer->getUsername()),139phutil_tag('strong', array(), $other_user->getName()))))140->setSubmitButtonText(pht('Log Out'))141->setSubmitButtonURI($this->getLogoutURI())142->setCancelButtonURI('/');143} else if ($email->getIsPrimary()) {144// NOTE: We never steal primary addresses from other accounts, even145// if they are unverified. This would leave the other account with146// no address. Users can use password recovery to access the other147// account if they really control the address.148throw id(new PhabricatorAuthInviteAccountException(149pht('Wrong Account'),150pht(151'You are logged in as %s, but the email address you just '.152'clicked a link from is already the primary email address '.153'for another account (%s). Switch accounts, then try again.',154phutil_tag('strong', array(), $viewer->getUsername()),155phutil_tag('strong', array(), $other_user->getName()))))156->setSubmitButtonText(pht('Log Out'))157->setSubmitButtonURI($this->getLogoutURI())158->setCancelButtonURI('/');159} else if (!$this->shouldVerify()) {160throw id(new PhabricatorAuthInviteVerifyException(161pht('Verify Email'),162pht(163'You are logged in as %s, but the email address (%s) you just '.164'clicked a link from is already associated with another '.165'account (%s). You can log out to switch accounts, or verify '.166'the address and attach it to your current account. Attach '.167'email address %s to user account %s?',168phutil_tag('strong', array(), $viewer->getUsername()),169phutil_tag('strong', array(), $invite->getEmailAddress()),170phutil_tag('strong', array(), $other_user->getName()),171phutil_tag('strong', array(), $invite->getEmailAddress()),172phutil_tag('strong', array(), $viewer->getUsername()))))173->setSubmitButtonText(174pht(175'Verify %s',176$invite->getEmailAddress()))177->setCancelButtonText(pht('Log Out'))178->setCancelButtonURI($this->getLogoutURI());179}180}181182if (!$email) {183$email = id(new PhabricatorUserEmail())184->setAddress($invite->getEmailAddress())185->setIsVerified(0)186->setIsPrimary(0);187}188189if (!$email->getIsVerified()) {190// We're doing this check here so that we can verify the address if191// it's already attached to the viewer's account, just not verified.192if (!$this->shouldVerify()) {193throw id(new PhabricatorAuthInviteVerifyException(194pht('Verify Email'),195pht(196'Verify this email address (%s) and attach it to your '.197'account (%s)?',198phutil_tag('strong', array(), $invite->getEmailAddress()),199phutil_tag('strong', array(), $viewer->getUsername()))))200->setSubmitButtonText(201pht(202'Verify %s',203$invite->getEmailAddress()))204->setCancelButtonURI('/');205}206207$editor = id(new PhabricatorUserEditor())208->setActor($viewer);209210// If this is a new email, add it to the user's account.211if (!$email->getUserPHID()) {212$editor->addEmail($viewer, $email);213}214215// If another user added this email (but has not verified it),216// take it from them.217$editor->reassignEmail($viewer, $email);218219$editor->verifyEmail($viewer, $email);220}221222$invite->setAcceptedByPHID($viewer->getPHID());223$invite->save();224225// If we make it here, the user was already logged in with the email226// address attached to their account and verified, or we attached it to227// their account (if it was not already attached) and verified it.228throw new PhabricatorAuthInviteRegisteredException();229}230231private function loadUserForEmail(PhabricatorUserEmail $email) {232$user = id(new PhabricatorHandleQuery())233->setViewer(PhabricatorUser::getOmnipotentUser())234->withPHIDs(array($email->getUserPHID()))235->executeOne();236if (!$user) {237throw new Exception(238pht(239'Email record ("%s") has bad associated user PHID ("%s").',240$email->getAddress(),241$email->getUserPHID()));242}243244return $user;245}246247private function getLoginURI() {248return '/auth/start/';249}250251private function getLogoutURI() {252return '/logout/';253}254255}256257258