diff --git a/src/applications/people/PhabricatorUserEditor.php b/src/applications/people/PhabricatorUserEditor.php index ba2347bf49..0752a4a937 100644 --- a/src/applications/people/PhabricatorUserEditor.php +++ b/src/applications/people/PhabricatorUserEditor.php @@ -271,6 +271,77 @@ final class PhabricatorUserEditor { } + /** + * @task role + */ + public function deleteUser(PhabricatorUser $user, $disable) { + $actor = $this->requireActor(); + + if (!$user->getID()) { + throw new Exception("User has not been created yet!"); + } + + if ($actor->getPHID() == $user->getPHID()) { + throw new Exception("You can not delete yourself!"); + } + + $user->openTransaction(); + $ldaps = id(new PhabricatorUserLDAPInfo())->loadAllWhere( + 'userID = %d', + $user->getID()); + foreach ($ldaps as $ldap) { + $ldap->delete(); + } + + $oauths = id(new PhabricatorUserOAuthInfo())->loadAllWhere( + 'userID = %d', + $user->getID()); + foreach ($oauths as $oauth) { + $oauth->delete(); + } + + $prefs = id(new PhabricatorUserPreferences())->loadAllWhere( + 'userPHID = %s', + $user->getPHID()); + foreach ($prefs as $pref) { + $pref->delete(); + } + + $profiles = id(new PhabricatorUserProfile())->loadAllWhere( + 'userPHID = %s', + $user->getPHID()); + foreach ($profiles as $profile) { + $profile->delete(); + } + + $keys = id(new PhabricatorUserSSHKey())->loadAllWhere( + 'userPHID = %s', + $user->getPHID()); + foreach ($keys as $key) { + $key->delete(); + } + + $emails = id(new PhabricatorUserEmail())->loadAllWhere( + 'userPHID = %s', + $user->getPHID()); + foreach ($emails as $email) { + $email->delete(); + } + + $log = PhabricatorUserLog::newLog( + $actor, + $user, + PhabricatorUserLog::ACTION_DELETE); + $log->save(); + + $user->delete(); + + $user->saveTransaction(); + + return $this; + } + + /* -( Adding, Removing and Changing Email )-------------------------------- */ diff --git a/src/applications/people/controller/PhabricatorPeopleEditController.php b/src/applications/people/controller/PhabricatorPeopleEditController.php index 69f67b74de..13c3a5a1fc 100644 --- a/src/applications/people/controller/PhabricatorPeopleEditController.php +++ b/src/applications/people/controller/PhabricatorPeopleEditController.php @@ -54,6 +54,7 @@ final class PhabricatorPeopleEditController $nav->addFilter('cert', 'Conduit Certificate'); $nav->addSpacer(); $nav->addFilter('rename', 'Change Username'); + $nav->addFilter('delete', 'Delete User'); if (!$user->getID()) { $this->view = 'basic'; @@ -83,6 +84,9 @@ final class PhabricatorPeopleEditController case 'rename': $response = $this->processRenameRequest($user); break; + case 'delete': + $response = $this->processDeleteRequest($user); + break; default: return new Aphront404Response(); } @@ -231,8 +235,7 @@ final class PhabricatorPeopleEditController ->setName('username') ->setValue($user->getUsername()) ->setError($e_username) - ->setDisabled($is_immutable) - ->setCaption('Usernames are permanent and can not be changed later!')) + ->setDisabled($is_immutable)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Real Name') @@ -565,7 +568,93 @@ final class PhabricatorPeopleEditController return array($errors, $panel); } + private function processDeleteRequest(PhabricatorUser $user) { + $request = $this->getRequest(); + $admin = $request->getUser(); + if ($user->getPHID() == $admin->getPHID()) { + $error = new AphrontErrorView(); + $error->setTitle('You Shall Journey No Farther'); + $error->appendChild( + '
As you stare into the gaping maw of the abyss, something holds '. + 'you back.
'. + 'You can not delete your own account.
'); + return $error; + } + + $e_username = true; + $username = null; + + $errors = array(); + if ($request->isFormPost()) { + + $username = $request->getStr('username'); + if (!strlen($username)) { + $e_username = 'Required'; + $errors[] = 'You must type the username to confirm deletion.'; + } else if ($username != $user->getUsername()) { + $e_username = 'Invalid'; + $errors[] = 'You must type the username correctly.'; + } + + if (!$errors) { + id(new PhabricatorUserEditor()) + ->setActor($admin) + ->deleteUser($user); + + return id(new AphrontRedirectResponse())->setURI('/people/'); + } + } + + if ($errors) { + $errors = id(new AphrontErrorView()) + ->setTitle('Form Errors') + ->setErrors($errors); + } else { + $errors = null; + } + + $form = new AphrontFormView(); + $form + ->setUser($admin) + ->setAction($request->getRequestURI()) + ->appendChild( + ''. + 'Be careful when deleting users! '. + 'If this user interacted with anything, it is generally better '. + 'to disable them, not delete them. If you delete them, it will '. + 'no longer be possible to search for their objects, for example, '. + 'and you will lose other information about their history. Disabling '. + 'them instead will prevent them from logging in but not destroy '. + 'any of their data.'. + '
'. + ''. + 'It is generally safe to delete newly created users (and test users '. + 'and so on), but less safe to delete established users. If '. + 'possible, disable them instead.'. + '
') + ->appendChild( + id(new AphrontFormStaticControl()) + ->setLabel('Username') + ->setValue($user->getUsername())) + ->appendChild( + id(new AphrontFormTextControl()) + ->setLabel('Confirm') + ->setValue($username) + ->setName('username') + ->setCaption("Type the username again to confirm deletion.") + ->setError($e_username)) + ->appendChild( + id(new AphrontFormSubmitControl()) + ->setValue('Delete User')); + + $panel = new AphrontPanelView(); + $panel->setHeader('Delete User'); + $panel->setWidth(AphrontPanelView::WIDTH_FORM); + $panel->appendChild($form); + + return array($errors, $panel); + } private function getRoleInstructions() { $roles_link = phutil_render_tag( diff --git a/src/applications/people/storage/PhabricatorUserLog.php b/src/applications/people/storage/PhabricatorUserLog.php index 989832872d..7e8a7df5ee 100644 --- a/src/applications/people/storage/PhabricatorUserLog.php +++ b/src/applications/people/storage/PhabricatorUserLog.php @@ -28,6 +28,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO { const ACTION_ADMIN = 'admin'; const ACTION_DISABLE = 'disable'; + const ACTION_DELETE = 'delete'; const ACTION_CONDUIT_CERTIFICATE = 'conduit-cert'; const ACTION_CONDUIT_CERTIFICATE_FAILURE = 'conduit-cert-fail';