Path: blob/master/src/applications/people/editor/PhabricatorUserEditor.php
12256 views
<?php12/**3* Editor class for creating and adjusting users. This class guarantees data4* integrity and writes logs when user information changes.5*6* @task config Configuration7* @task edit Creating and Editing Users8* @task role Editing Roles9* @task email Adding, Removing and Changing Email10* @task internal Internals11*/12final class PhabricatorUserEditor extends PhabricatorEditor {1314private $logs = array();151617/* -( Creating and Editing Users )----------------------------------------- */181920/**21* @task edit22*/23public function createNewUser(24PhabricatorUser $user,25PhabricatorUserEmail $email,26$allow_reassign = false) {2728if ($user->getID()) {29throw new Exception(pht('User has already been created!'));30}3132$is_reassign = false;33if ($email->getID()) {34if ($allow_reassign) {35if ($email->getIsPrimary()) {36throw new Exception(37pht('Primary email addresses can not be reassigned.'));38}39$is_reassign = true;40} else {41throw new Exception(pht('Email has already been created!'));42}43}4445if (!PhabricatorUser::validateUsername($user->getUsername())) {46$valid = PhabricatorUser::describeValidUsername();47throw new Exception(pht('Username is invalid! %s', $valid));48}4950// Always set a new user's email address to primary.51$email->setIsPrimary(1);5253// If the primary address is already verified, also set the verified flag54// on the user themselves.55if ($email->getIsVerified()) {56$user->setIsEmailVerified(1);57}5859$this->willAddEmail($email);6061$user->openTransaction();62try {63$user->save();64$email->setUserPHID($user->getPHID());65$email->save();66} catch (AphrontDuplicateKeyQueryException $ex) {67// We might have written the user but failed to write the email; if68// so, erase the IDs we attached.69$user->setID(null);70$user->setPHID(null);7172$user->killTransaction();73throw $ex;74}7576if ($is_reassign) {77$log = PhabricatorUserLog::initializeNewLog(78$this->requireActor(),79$user->getPHID(),80PhabricatorReassignEmailUserLogType::LOGTYPE);81$log->setNewValue($email->getAddress());82$log->save();83}8485$user->saveTransaction();8687if ($email->getIsVerified()) {88$this->didVerifyEmail($user, $email);89}9091id(new DiffusionRepositoryIdentityEngine())92->didUpdateEmailAddress($email->getAddress());9394return $this;95}969798/* -( Editing Roles )------------------------------------------------------ */99100/**101* @task role102*/103public function makeSystemAgentUser(PhabricatorUser $user, $system_agent) {104$actor = $this->requireActor();105106if (!$user->getID()) {107throw new Exception(pht('User has not been created yet!'));108}109110$user->openTransaction();111$user->beginWriteLocking();112113$user->reload();114if ($user->getIsSystemAgent() == $system_agent) {115$user->endWriteLocking();116$user->killTransaction();117return $this;118}119120$user->setIsSystemAgent((int)$system_agent);121$user->save();122123$user->endWriteLocking();124$user->saveTransaction();125126return $this;127}128129/**130* @task role131*/132public function makeMailingListUser(PhabricatorUser $user, $mailing_list) {133$actor = $this->requireActor();134135if (!$user->getID()) {136throw new Exception(pht('User has not been created yet!'));137}138139$user->openTransaction();140$user->beginWriteLocking();141142$user->reload();143if ($user->getIsMailingList() == $mailing_list) {144$user->endWriteLocking();145$user->killTransaction();146return $this;147}148149$user->setIsMailingList((int)$mailing_list);150$user->save();151152$user->endWriteLocking();153$user->saveTransaction();154155return $this;156}157158/* -( Adding, Removing and Changing Email )-------------------------------- */159160161/**162* @task email163*/164public function addEmail(165PhabricatorUser $user,166PhabricatorUserEmail $email) {167168$actor = $this->requireActor();169170if (!$user->getID()) {171throw new Exception(pht('User has not been created yet!'));172}173if ($email->getID()) {174throw new Exception(pht('Email has already been created!'));175}176177// Use changePrimaryEmail() to change primary email.178$email->setIsPrimary(0);179$email->setUserPHID($user->getPHID());180181$this->willAddEmail($email);182183$user->openTransaction();184$user->beginWriteLocking();185186$user->reload();187188try {189$email->save();190} catch (AphrontDuplicateKeyQueryException $ex) {191$user->endWriteLocking();192$user->killTransaction();193194throw $ex;195}196197$log = PhabricatorUserLog::initializeNewLog(198$actor,199$user->getPHID(),200PhabricatorAddEmailUserLogType::LOGTYPE);201$log->setNewValue($email->getAddress());202$log->save();203204$user->endWriteLocking();205$user->saveTransaction();206207id(new DiffusionRepositoryIdentityEngine())208->didUpdateEmailAddress($email->getAddress());209210return $this;211}212213214/**215* @task email216*/217public function removeEmail(218PhabricatorUser $user,219PhabricatorUserEmail $email) {220221$actor = $this->requireActor();222223if (!$user->getID()) {224throw new Exception(pht('User has not been created yet!'));225}226if (!$email->getID()) {227throw new Exception(pht('Email has not been created yet!'));228}229230$user->openTransaction();231$user->beginWriteLocking();232233$user->reload();234$email->reload();235236if ($email->getIsPrimary()) {237throw new Exception(pht("Can't remove primary email!"));238}239if ($email->getUserPHID() != $user->getPHID()) {240throw new Exception(pht('Email not owned by user!'));241}242243$destruction_engine = id(new PhabricatorDestructionEngine())244->setWaitToFinalizeDestruction(true)245->destroyObject($email);246247$log = PhabricatorUserLog::initializeNewLog(248$actor,249$user->getPHID(),250PhabricatorRemoveEmailUserLogType::LOGTYPE);251$log->setOldValue($email->getAddress());252$log->save();253254$user->endWriteLocking();255$user->saveTransaction();256257$this->revokePasswordResetLinks($user);258$destruction_engine->finalizeDestruction();259260return $this;261}262263264/**265* @task email266*/267public function changePrimaryEmail(268PhabricatorUser $user,269PhabricatorUserEmail $email) {270$actor = $this->requireActor();271272if (!$user->getID()) {273throw new Exception(pht('User has not been created yet!'));274}275if (!$email->getID()) {276throw new Exception(pht('Email has not been created yet!'));277}278279$user->openTransaction();280$user->beginWriteLocking();281282$user->reload();283$email->reload();284285if ($email->getUserPHID() != $user->getPHID()) {286throw new Exception(pht('User does not own email!'));287}288289if ($email->getIsPrimary()) {290throw new Exception(pht('Email is already primary!'));291}292293if (!$email->getIsVerified()) {294throw new Exception(pht('Email is not verified!'));295}296297$old_primary = $user->loadPrimaryEmail();298if ($old_primary) {299$old_primary->setIsPrimary(0);300$old_primary->save();301}302303$email->setIsPrimary(1);304$email->save();305306// If the user doesn't have the verified flag set on their account307// yet, set it. We've made sure the email is verified above. See308// T12635 for discussion.309if (!$user->getIsEmailVerified()) {310$user->setIsEmailVerified(1);311$user->save();312}313314$log = PhabricatorUserLog::initializeNewLog(315$actor,316$user->getPHID(),317PhabricatorPrimaryEmailUserLogType::LOGTYPE);318$log->setOldValue($old_primary ? $old_primary->getAddress() : null);319$log->setNewValue($email->getAddress());320321$log->save();322323$user->endWriteLocking();324$user->saveTransaction();325326if ($old_primary) {327$old_primary->sendOldPrimaryEmail($user, $email);328}329$email->sendNewPrimaryEmail($user);330331$this->revokePasswordResetLinks($user);332333return $this;334}335336337/**338* Verify a user's email address.339*340* This verifies an individual email address. If the address is the user's341* primary address and their account was not previously verified, their342* account is marked as email verified.343*344* @task email345*/346public function verifyEmail(347PhabricatorUser $user,348PhabricatorUserEmail $email) {349$actor = $this->requireActor();350351if (!$user->getID()) {352throw new Exception(pht('User has not been created yet!'));353}354if (!$email->getID()) {355throw new Exception(pht('Email has not been created yet!'));356}357358$user->openTransaction();359$user->beginWriteLocking();360361$user->reload();362$email->reload();363364if ($email->getUserPHID() != $user->getPHID()) {365throw new Exception(pht('User does not own email!'));366}367368if (!$email->getIsVerified()) {369$email->setIsVerified(1);370$email->save();371372$log = PhabricatorUserLog::initializeNewLog(373$actor,374$user->getPHID(),375PhabricatorVerifyEmailUserLogType::LOGTYPE);376$log->setNewValue($email->getAddress());377$log->save();378}379380if (!$user->getIsEmailVerified()) {381// If the user just verified their primary email address, mark their382// account as email verified.383$user_primary = $user->loadPrimaryEmail();384if ($user_primary->getID() == $email->getID()) {385$user->setIsEmailVerified(1);386$user->save();387}388}389390$user->endWriteLocking();391$user->saveTransaction();392393$this->didVerifyEmail($user, $email);394}395396397/**398* Reassign an unverified email address.399*/400public function reassignEmail(401PhabricatorUser $user,402PhabricatorUserEmail $email) {403$actor = $this->requireActor();404405if (!$user->getID()) {406throw new Exception(pht('User has not been created yet!'));407}408409if (!$email->getID()) {410throw new Exception(pht('Email has not been created yet!'));411}412413$user->openTransaction();414$user->beginWriteLocking();415416$user->reload();417$email->reload();418419$old_user = $email->getUserPHID();420421if ($old_user != $user->getPHID()) {422if ($email->getIsVerified()) {423throw new Exception(424pht('Verified email addresses can not be reassigned.'));425}426if ($email->getIsPrimary()) {427throw new Exception(428pht('Primary email addresses can not be reassigned.'));429}430431$email->setUserPHID($user->getPHID());432$email->save();433434$log = PhabricatorUserLog::initializeNewLog(435$actor,436$user->getPHID(),437PhabricatorReassignEmailUserLogType::LOGTYPE);438$log->setNewValue($email->getAddress());439$log->save();440}441442$user->endWriteLocking();443$user->saveTransaction();444445id(new DiffusionRepositoryIdentityEngine())446->didUpdateEmailAddress($email->getAddress());447}448449450/* -( Internals )---------------------------------------------------------- */451452453/**454* @task internal455*/456private function willAddEmail(PhabricatorUserEmail $email) {457458// Hard check before write to prevent creation of disallowed email459// addresses. Normally, the application does checks and raises more460// user friendly errors for us, but we omit the courtesy checks on some461// pathways like administrative scripts for simplicity.462463if (!PhabricatorUserEmail::isValidAddress($email->getAddress())) {464throw new Exception(PhabricatorUserEmail::describeValidAddresses());465}466467if (!PhabricatorUserEmail::isAllowedAddress($email->getAddress())) {468throw new Exception(PhabricatorUserEmail::describeAllowedAddresses());469}470471$application_email = id(new PhabricatorMetaMTAApplicationEmailQuery())472->setViewer(PhabricatorUser::getOmnipotentUser())473->withAddresses(array($email->getAddress()))474->executeOne();475if ($application_email) {476throw new Exception($application_email->getInUseMessage());477}478}479480public function revokePasswordResetLinks(PhabricatorUser $user) {481// Revoke any outstanding password reset links. If an attacker compromises482// an account, changes the email address, and sends themselves a password483// reset link, it could otherwise remain live for a short period of time484// and allow them to compromise the account again later.485486PhabricatorAuthTemporaryToken::revokeTokens(487$user,488array($user->getPHID()),489array(490PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE,491PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE,492));493}494495private function didVerifyEmail(496PhabricatorUser $user,497PhabricatorUserEmail $email) {498499$event_type = PhabricatorEventType::TYPE_AUTH_DIDVERIFYEMAIL;500$event_data = array(501'user' => $user,502'email' => $email,503);504505$event = id(new PhabricatorEvent($event_type, $event_data))506->setUser($user);507PhutilEventEngine::dispatchEvent($event);508}509510511}512513514