diff --git a/src/applications/people/conduit/UserDisableConduitAPIMethod.php b/src/applications/people/conduit/UserDisableConduitAPIMethod.php index 3512d3f742..a6f2e2c4f1 100644 --- a/src/applications/people/conduit/UserDisableConduitAPIMethod.php +++ b/src/applications/people/conduit/UserDisableConduitAPIMethod.php @@ -1,53 +1,73 @@ 'required list', ); } protected function defineReturnType() { return 'void'; } protected function defineErrorTypes() { return array( 'ERR-PERMISSIONS' => pht('Only admins can call this method.'), 'ERR-BAD-PHID' => pht('Non existent user PHID.'), ); } protected function execute(ConduitAPIRequest $request) { $actor = $request->getUser(); if (!$actor->getIsAdmin()) { throw new ConduitException('ERR-PERMISSIONS'); } $phids = $request->getValue('phids'); $users = id(new PhabricatorUser())->loadAllWhere( 'phid IN (%Ls)', $phids); if (count($phids) != count($users)) { throw new ConduitException('ERR-BAD-PHID'); } - foreach ($users as $user) { - id(new PhabricatorUserEditor()) - ->setActor($actor) - ->disableUser($user, true); + foreach ($phids as $phid) { + $params = array( + 'transactions' => array( + array( + 'type' => 'disabled', + 'value' => true, + ), + ), + 'objectIdentifier' => $phid, + ); + + id(new ConduitCall('user.edit', $params)) + ->setUser($actor) + ->execute(); } + + return null; } } diff --git a/src/applications/people/conduit/UserEnableConduitAPIMethod.php b/src/applications/people/conduit/UserEnableConduitAPIMethod.php index f7cb117df6..3e4e09b778 100644 --- a/src/applications/people/conduit/UserEnableConduitAPIMethod.php +++ b/src/applications/people/conduit/UserEnableConduitAPIMethod.php @@ -1,53 +1,73 @@ 'required list', ); } protected function defineReturnType() { return 'void'; } protected function defineErrorTypes() { return array( 'ERR-PERMISSIONS' => pht('Only admins can call this method.'), 'ERR-BAD-PHID' => pht('Non existent user PHID.'), ); } protected function execute(ConduitAPIRequest $request) { $actor = $request->getUser(); if (!$actor->getIsAdmin()) { throw new ConduitException('ERR-PERMISSIONS'); } $phids = $request->getValue('phids'); $users = id(new PhabricatorUser())->loadAllWhere( 'phid IN (%Ls)', $phids); if (count($phids) != count($users)) { throw new ConduitException('ERR-BAD-PHID'); } - foreach ($users as $user) { - id(new PhabricatorUserEditor()) - ->setActor($actor) - ->disableUser($user, false); + foreach ($phids as $phid) { + $params = array( + 'transactions' => array( + array( + 'type' => 'disabled', + 'value' => false, + ), + ), + 'objectIdentifier' => $phid, + ); + + id(new ConduitCall('user.edit', $params)) + ->setUser($actor) + ->execute(); } + + return null; } } diff --git a/src/applications/people/editor/PhabricatorUserEditor.php b/src/applications/people/editor/PhabricatorUserEditor.php index 786ead79c3..58c2ba1ff1 100644 --- a/src/applications/people/editor/PhabricatorUserEditor.php +++ b/src/applications/people/editor/PhabricatorUserEditor.php @@ -1,725 +1,686 @@ getID()) { throw new Exception(pht('User has already been created!')); } $is_reassign = false; if ($email->getID()) { if ($allow_reassign) { if ($email->getIsPrimary()) { throw new Exception( pht('Primary email addresses can not be reassigned.')); } $is_reassign = true; } else { throw new Exception(pht('Email has already been created!')); } } if (!PhabricatorUser::validateUsername($user->getUsername())) { $valid = PhabricatorUser::describeValidUsername(); throw new Exception(pht('Username is invalid! %s', $valid)); } // Always set a new user's email address to primary. $email->setIsPrimary(1); // If the primary address is already verified, also set the verified flag // on the user themselves. if ($email->getIsVerified()) { $user->setIsEmailVerified(1); } $this->willAddEmail($email); $user->openTransaction(); try { $user->save(); $email->setUserPHID($user->getPHID()); $email->save(); } catch (AphrontDuplicateKeyQueryException $ex) { // We might have written the user but failed to write the email; if // so, erase the IDs we attached. $user->setID(null); $user->setPHID(null); $user->killTransaction(); throw $ex; } $log = PhabricatorUserLog::initializeNewLog( $this->requireActor(), $user->getPHID(), PhabricatorUserLog::ACTION_CREATE); $log->setNewValue($email->getAddress()); $log->save(); if ($is_reassign) { $log = PhabricatorUserLog::initializeNewLog( $this->requireActor(), $user->getPHID(), PhabricatorUserLog::ACTION_EMAIL_REASSIGN); $log->setNewValue($email->getAddress()); $log->save(); } $user->saveTransaction(); if ($email->getIsVerified()) { $this->didVerifyEmail($user, $email); } return $this; } /** * @task edit */ public function updateUser( PhabricatorUser $user, PhabricatorUserEmail $email = null) { if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } $user->openTransaction(); $user->save(); if ($email) { $email->save(); } $log = PhabricatorUserLog::initializeNewLog( $this->requireActor(), $user->getPHID(), PhabricatorUserLog::ACTION_EDIT); $log->save(); $user->saveTransaction(); return $this; } /** * @task edit */ public function changeUsername(PhabricatorUser $user, $username) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } if (!PhabricatorUser::validateUsername($username)) { $valid = PhabricatorUser::describeValidUsername(); throw new Exception(pht('Username is invalid! %s', $valid)); } $old_username = $user->getUsername(); $user->openTransaction(); $user->reload(); $user->setUsername($username); try { $user->save(); } catch (AphrontDuplicateKeyQueryException $ex) { $user->setUsername($old_username); $user->killTransaction(); throw $ex; } $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_CHANGE_USERNAME); $log->setOldValue($old_username); $log->setNewValue($username); $log->save(); $user->saveTransaction(); // The SSH key cache currently includes usernames, so dirty it. See T12554 // for discussion. PhabricatorAuthSSHKeyQuery::deleteSSHKeyCache(); $user->sendUsernameChangeEmail($actor, $old_username); } /* -( Editing Roles )------------------------------------------------------ */ /** * @task role */ public function makeAdminUser(PhabricatorUser $user, $admin) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsAdmin() == $admin) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_ADMIN); $log->setOldValue($user->getIsAdmin()); $log->setNewValue($admin); $user->setIsAdmin((int)$admin); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task role */ public function makeSystemAgentUser(PhabricatorUser $user, $system_agent) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsSystemAgent() == $system_agent) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_SYSTEM_AGENT); $log->setOldValue($user->getIsSystemAgent()); $log->setNewValue($system_agent); $user->setIsSystemAgent((int)$system_agent); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /** * @task role */ public function makeMailingListUser(PhabricatorUser $user, $mailing_list) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsMailingList() == $mailing_list) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_MAILING_LIST); $log->setOldValue($user->getIsMailingList()); $log->setNewValue($mailing_list); $user->setIsMailingList((int)$mailing_list); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } - /** - * @task role - */ - public function disableUser(PhabricatorUser $user, $disable) { - $actor = $this->requireActor(); - - if (!$user->getID()) { - throw new Exception(pht('User has not been created yet!')); - } - - $user->openTransaction(); - $user->beginWriteLocking(); - - $user->reload(); - if ($user->getIsDisabled() == $disable) { - $user->endWriteLocking(); - $user->killTransaction(); - return $this; - } - - $log = PhabricatorUserLog::initializeNewLog( - $actor, - $user->getPHID(), - PhabricatorUserLog::ACTION_DISABLE); - $log->setOldValue($user->getIsDisabled()); - $log->setNewValue($disable); - - $user->setIsDisabled((int)$disable); - $user->save(); - - $log->save(); - - $user->endWriteLocking(); - $user->saveTransaction(); - - return $this; - } - - /** * @task role */ public function approveUser(PhabricatorUser $user, $approve) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); if ($user->getIsApproved() == $approve) { $user->endWriteLocking(); $user->killTransaction(); return $this; } $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_APPROVE); $log->setOldValue($user->getIsApproved()); $log->setNewValue($approve); $user->setIsApproved($approve); $user->save(); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); return $this; } /* -( Adding, Removing and Changing Email )-------------------------------- */ /** * @task email */ public function addEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } if ($email->getID()) { throw new Exception(pht('Email has already been created!')); } // Use changePrimaryEmail() to change primary email. $email->setIsPrimary(0); $email->setUserPHID($user->getPHID()); $this->willAddEmail($email); $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); try { $email->save(); } catch (AphrontDuplicateKeyQueryException $ex) { $user->endWriteLocking(); $user->killTransaction(); throw $ex; } $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_EMAIL_ADD); $log->setNewValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); // Try and match this new address against unclaimed `RepositoryIdentity`s PhabricatorWorker::scheduleTask( 'PhabricatorRepositoryIdentityChangeWorker', array('userPHID' => $user->getPHID()), array('objectPHID' => $user->getPHID())); return $this; } /** * @task email */ public function removeEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } if (!$email->getID()) { throw new Exception(pht('Email has not been created yet!')); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); $email->reload(); if ($email->getIsPrimary()) { throw new Exception(pht("Can't remove primary email!")); } if ($email->getUserPHID() != $user->getPHID()) { throw new Exception(pht('Email not owned by user!')); } $email->delete(); $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_EMAIL_REMOVE); $log->setOldValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); $this->revokePasswordResetLinks($user); return $this; } /** * @task email */ public function changePrimaryEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } if (!$email->getID()) { throw new Exception(pht('Email has not been created yet!')); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); $email->reload(); if ($email->getUserPHID() != $user->getPHID()) { throw new Exception(pht('User does not own email!')); } if ($email->getIsPrimary()) { throw new Exception(pht('Email is already primary!')); } if (!$email->getIsVerified()) { throw new Exception(pht('Email is not verified!')); } $old_primary = $user->loadPrimaryEmail(); if ($old_primary) { $old_primary->setIsPrimary(0); $old_primary->save(); } $email->setIsPrimary(1); $email->save(); // If the user doesn't have the verified flag set on their account // yet, set it. We've made sure the email is verified above. See // T12635 for discussion. if (!$user->getIsEmailVerified()) { $user->setIsEmailVerified(1); $user->save(); } $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_EMAIL_PRIMARY); $log->setOldValue($old_primary ? $old_primary->getAddress() : null); $log->setNewValue($email->getAddress()); $log->save(); $user->endWriteLocking(); $user->saveTransaction(); if ($old_primary) { $old_primary->sendOldPrimaryEmail($user, $email); } $email->sendNewPrimaryEmail($user); $this->revokePasswordResetLinks($user); return $this; } /** * Verify a user's email address. * * This verifies an individual email address. If the address is the user's * primary address and their account was not previously verified, their * account is marked as email verified. * * @task email */ public function verifyEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } if (!$email->getID()) { throw new Exception(pht('Email has not been created yet!')); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); $email->reload(); if ($email->getUserPHID() != $user->getPHID()) { throw new Exception(pht('User does not own email!')); } if (!$email->getIsVerified()) { $email->setIsVerified(1); $email->save(); $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_EMAIL_VERIFY); $log->setNewValue($email->getAddress()); $log->save(); } if (!$user->getIsEmailVerified()) { // If the user just verified their primary email address, mark their // account as email verified. $user_primary = $user->loadPrimaryEmail(); if ($user_primary->getID() == $email->getID()) { $user->setIsEmailVerified(1); $user->save(); } } $user->endWriteLocking(); $user->saveTransaction(); $this->didVerifyEmail($user, $email); } /** * Reassign an unverified email address. */ public function reassignEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $actor = $this->requireActor(); if (!$user->getID()) { throw new Exception(pht('User has not been created yet!')); } if (!$email->getID()) { throw new Exception(pht('Email has not been created yet!')); } $user->openTransaction(); $user->beginWriteLocking(); $user->reload(); $email->reload(); $old_user = $email->getUserPHID(); if ($old_user != $user->getPHID()) { if ($email->getIsVerified()) { throw new Exception( pht('Verified email addresses can not be reassigned.')); } if ($email->getIsPrimary()) { throw new Exception( pht('Primary email addresses can not be reassigned.')); } $email->setUserPHID($user->getPHID()); $email->save(); $log = PhabricatorUserLog::initializeNewLog( $actor, $user->getPHID(), PhabricatorUserLog::ACTION_EMAIL_REASSIGN); $log->setNewValue($email->getAddress()); $log->save(); } $user->endWriteLocking(); $user->saveTransaction(); } /* -( Internals )---------------------------------------------------------- */ /** * @task internal */ private function willAddEmail(PhabricatorUserEmail $email) { // Hard check before write to prevent creation of disallowed email // addresses. Normally, the application does checks and raises more // user friendly errors for us, but we omit the courtesy checks on some // pathways like administrative scripts for simplicity. if (!PhabricatorUserEmail::isValidAddress($email->getAddress())) { throw new Exception(PhabricatorUserEmail::describeValidAddresses()); } if (!PhabricatorUserEmail::isAllowedAddress($email->getAddress())) { throw new Exception(PhabricatorUserEmail::describeAllowedAddresses()); } $application_email = id(new PhabricatorMetaMTAApplicationEmailQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withAddresses(array($email->getAddress())) ->executeOne(); if ($application_email) { throw new Exception($application_email->getInUseMessage()); } } public function revokePasswordResetLinks(PhabricatorUser $user) { // Revoke any outstanding password reset links. If an attacker compromises // an account, changes the email address, and sends themselves a password // reset link, it could otherwise remain live for a short period of time // and allow them to compromise the account again later. PhabricatorAuthTemporaryToken::revokeTokens( $user, array($user->getPHID()), array( PhabricatorAuthOneTimeLoginTemporaryTokenType::TOKENTYPE, PhabricatorAuthPasswordResetTemporaryTokenType::TOKENTYPE, )); } private function didVerifyEmail( PhabricatorUser $user, PhabricatorUserEmail $email) { $event_type = PhabricatorEventType::TYPE_AUTH_DIDVERIFYEMAIL; $event_data = array( 'user' => $user, 'email' => $email, ); $event = id(new PhabricatorEvent($event_type, $event_data)) ->setUser($user); PhutilEventEngine::dispatchEvent($event); } }