mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-26 00:32:42 +01:00
Allow administrators to change usernames
Summary: Give them a big essay about how it's dangerous, but allow them to do it formally. Because the username is part of the password salt, users must change their passwords after a username change. Make password reset links work for already-logged-in-users since there's no reason not to (if you have a reset link, you can log out and use it) and it's much less confusing if you get this email and are already logged in. Depends on: D2651 Test Plan: Changed a user's username to all kinds of crazy things. Clicked reset links in email. Tried to make invalid/nonsense name changes. Reviewers: btrahan, vrana Reviewed By: btrahan CC: aran Maniphest Tasks: T1303 Differential Revision: https://secure.phabricator.com/D2657
This commit is contained in:
parent
0a7b4591ef
commit
0e1bbbd489
5 changed files with 201 additions and 45 deletions
|
@ -36,22 +36,6 @@ final class PhabricatorEmailTokenController
|
|||
return new Aphront400Response();
|
||||
}
|
||||
|
||||
if ($request->getUser()->getPHID()) {
|
||||
$view = new AphrontRequestFailureView();
|
||||
$view->setHeader('Already Logged In');
|
||||
$view->appendChild(
|
||||
'<p>You are already logged in.</p>');
|
||||
$view->appendChild(
|
||||
'<div class="aphront-failure-continue">'.
|
||||
'<a class="button" href="/">Return Home</a>'.
|
||||
'</div>');
|
||||
return $this->buildStandardPageResponse(
|
||||
$view,
|
||||
array(
|
||||
'title' => 'Already Logged In',
|
||||
));
|
||||
}
|
||||
|
||||
$token = $this->token;
|
||||
$email = $request->getStr('email');
|
||||
|
||||
|
@ -103,10 +87,12 @@ final class PhabricatorEmailTokenController
|
|||
// enough, without requiring users to go through a second round of email
|
||||
// verification.
|
||||
|
||||
$target_email->setIsVerified(1);
|
||||
$target_email->save();
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$target_email->setIsVerified(1);
|
||||
$target_email->save();
|
||||
$session_key = $target_user->establishSession('web');
|
||||
unset($unguarded);
|
||||
|
||||
$session_key = $target_user->establishSession('web');
|
||||
$request->setCookie('phusr', $target_user->getUsername());
|
||||
$request->setCookie('phsid', $session_key);
|
||||
|
||||
|
|
|
@ -148,6 +148,49 @@ final class PhabricatorUserEditor {
|
|||
}
|
||||
|
||||
|
||||
/**
|
||||
* @task edit
|
||||
*/
|
||||
public function changeUsername(PhabricatorUser $user, $username) {
|
||||
$actor = $this->requireActor();
|
||||
|
||||
if (!$user->getID()) {
|
||||
throw new Exception("User has not been created yet!");
|
||||
}
|
||||
|
||||
if (!PhabricatorUser::validateUsername($username)) {
|
||||
$valid = PhabricatorUser::describeValidUsername();
|
||||
throw new Exception("Username is invalid! {$valid}");
|
||||
}
|
||||
|
||||
$old_username = $user->getUsername();
|
||||
|
||||
$user->openTransaction();
|
||||
$user->reload();
|
||||
$user->setUsername($username);
|
||||
|
||||
try {
|
||||
$user->save();
|
||||
} catch (AphrontQueryDuplicateKeyException $ex) {
|
||||
$user->setUsername($old_username);
|
||||
$user->killTransaction();
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
$log = PhabricatorUserLog::newLog(
|
||||
$this->actor,
|
||||
$user,
|
||||
PhabricatorUserLog::ACTION_CHANGE_USERNAME);
|
||||
$log->setOldValue($old_username);
|
||||
$log->setNewValue($username);
|
||||
$log->save();
|
||||
|
||||
$user->saveTransaction();
|
||||
|
||||
$user->sendUsernameChangeEmail($actor, $old_username);
|
||||
}
|
||||
|
||||
|
||||
/* -( Editing Roles )------------------------------------------------------ */
|
||||
|
||||
|
||||
|
|
|
@ -41,23 +41,24 @@ final class PhabricatorPeopleEditController
|
|||
if (!$user) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
$base_uri = '/people/edit/'.$user->getID().'/';
|
||||
} else {
|
||||
$user = new PhabricatorUser();
|
||||
$base_uri = '/people/edit/';
|
||||
}
|
||||
|
||||
$views = array(
|
||||
'basic' => 'Basic Information',
|
||||
'role' => 'Edit Roles',
|
||||
'cert' => 'Conduit Certificate',
|
||||
);
|
||||
$nav = new AphrontSideNavFilterView();
|
||||
$nav->setBaseURI(new PhutilURI($base_uri));
|
||||
$nav->addFilter('basic', 'Basic Information');
|
||||
$nav->addFilter('role', 'Edit Roles');
|
||||
$nav->addFilter('cert', 'Conduit Certificate');
|
||||
$nav->addSpacer();
|
||||
$nav->addFilter('rename', 'Change Username');
|
||||
|
||||
if (!$user->getID()) {
|
||||
$view = 'basic';
|
||||
} else if (isset($views[$this->view])) {
|
||||
$view = $this->view;
|
||||
} else {
|
||||
$view = 'basic';
|
||||
$this->view = 'basic';
|
||||
}
|
||||
$view = $nav->selectFilter($this->view, 'basic');
|
||||
|
||||
$content = array();
|
||||
|
||||
|
@ -79,6 +80,11 @@ final class PhabricatorPeopleEditController
|
|||
case 'cert':
|
||||
$response = $this->processCertificateRequest($user);
|
||||
break;
|
||||
case 'rename':
|
||||
$response = $this->processRenameRequest($user);
|
||||
break;
|
||||
default:
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if ($response instanceof AphrontResponse) {
|
||||
|
@ -88,21 +94,8 @@ final class PhabricatorPeopleEditController
|
|||
$content[] = $response;
|
||||
|
||||
if ($user->getID()) {
|
||||
$side_nav = new AphrontSideNavView();
|
||||
$side_nav->appendChild($content);
|
||||
foreach ($views as $key => $name) {
|
||||
$side_nav->addNavItem(
|
||||
phutil_render_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/people/edit/'.$user->getID().'/'.$key.'/',
|
||||
'class' => ($key == $view)
|
||||
? 'aphront-side-nav-selected'
|
||||
: null,
|
||||
),
|
||||
phutil_escape_html($name)));
|
||||
}
|
||||
$content = $side_nav;
|
||||
$nav->appendChild($content);
|
||||
$content = $nav;
|
||||
}
|
||||
|
||||
return $this->buildStandardPageResponse(
|
||||
|
@ -444,7 +437,6 @@ final class PhabricatorPeopleEditController
|
|||
$request = $this->getRequest();
|
||||
$admin = $request->getUser();
|
||||
|
||||
|
||||
$form = new AphrontFormView();
|
||||
$form
|
||||
->setUser($admin)
|
||||
|
@ -481,6 +473,100 @@ final class PhabricatorPeopleEditController
|
|||
return array($panel);
|
||||
}
|
||||
|
||||
private function processRenameRequest(PhabricatorUser $user) {
|
||||
$request = $this->getRequest();
|
||||
$admin = $request->getUser();
|
||||
|
||||
$e_username = true;
|
||||
$username = $user->getUsername();
|
||||
|
||||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
|
||||
$username = $request->getStr('username');
|
||||
if (!strlen($username)) {
|
||||
$e_username = 'Required';
|
||||
$errors[] = 'New username is required.';
|
||||
} else if ($username == $user->getUsername()) {
|
||||
$e_username = 'Invalid';
|
||||
$errors[] = 'New username must be different from old username.';
|
||||
} else if (!PhabricatorUser::validateUsername($username)) {
|
||||
$e_username = 'Invalid';
|
||||
$errors[] = PhabricatorUser::describeValidUsername();
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
try {
|
||||
|
||||
id(new PhabricatorUserEditor())
|
||||
->setActor($admin)
|
||||
->changeUsername($user, $username);
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($request->getRequestURI()->alter('saved', true));
|
||||
} catch (AphrontQueryDuplicateKeyException $ex) {
|
||||
$e_username = 'Not Unique';
|
||||
$errors[] = 'Another user already has that username.';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
$errors = id(new AphrontErrorView())
|
||||
->setTitle('Form Errors')
|
||||
->setErrors($errors);
|
||||
} else {
|
||||
$errors = null;
|
||||
}
|
||||
|
||||
$form = new AphrontFormView();
|
||||
$form
|
||||
->setUser($admin)
|
||||
->setAction($request->getRequestURI())
|
||||
->appendChild(
|
||||
'<p class="aphront-form-instructions">'.
|
||||
'<strong>Be careful when renaming users!</strong> '.
|
||||
'The old username will no longer be tied to the user, so anything '.
|
||||
'which uses it (like old commit messages) will no longer associate '.
|
||||
'correctly. And if you give a user a username which some other user '.
|
||||
'used to have, username lookups will begin returning the wrong '.
|
||||
'user.'.
|
||||
'</p>'.
|
||||
'<p class="aphront-form-instructions">'.
|
||||
'It is generally safe to rename newly created users (and test users '.
|
||||
'and so on), but less safe to rename established users and unsafe '.
|
||||
'to reissue a username.'.
|
||||
'</p>'.
|
||||
'<p class="aphront-form-instructions">'.
|
||||
'Users who rely on password auth will need to reset their password '.
|
||||
'after their username is changed (their username is part of the '.
|
||||
'salt in the password hash). They will receive an email with '.
|
||||
'instructions on how to do this.'.
|
||||
'</p>')
|
||||
->appendChild(
|
||||
id(new AphrontFormStaticControl())
|
||||
->setLabel('Old Username')
|
||||
->setValue($user->getUsername()))
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel('New Username')
|
||||
->setValue($username)
|
||||
->setName('username')
|
||||
->setError($e_username))
|
||||
->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue('Change Username'));
|
||||
|
||||
$panel = new AphrontPanelView();
|
||||
$panel->setHeader('Change Username');
|
||||
$panel->setWidth(AphrontPanelView::WIDTH_FORM);
|
||||
$panel->appendChild($form);
|
||||
|
||||
return array($errors, $panel);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private function getRoleInstructions() {
|
||||
$roles_link = phutil_render_tag(
|
||||
'a',
|
||||
|
|
|
@ -549,6 +549,46 @@ EOBODY;
|
|||
->saveAndSend();
|
||||
}
|
||||
|
||||
public function sendUsernameChangeEmail(
|
||||
PhabricatorUser $admin,
|
||||
$old_username) {
|
||||
|
||||
$admin_username = $admin->getUserName();
|
||||
$admin_realname = $admin->getRealName();
|
||||
$new_username = $this->getUserName();
|
||||
|
||||
$password_instructions = null;
|
||||
if (PhabricatorEnv::getEnvConfig('auth.password-auth-enabled')) {
|
||||
$uri = $this->getEmailLoginURI();
|
||||
$password_instructions = <<<EOTXT
|
||||
If you use a password to login, you'll need to reset it before you can login
|
||||
again. You can reset your password by following this link:
|
||||
|
||||
{$uri}
|
||||
|
||||
And, of course, you'll need to use your new username to login from now on. If
|
||||
you use OAuth to login, nothing should change.
|
||||
|
||||
EOTXT;
|
||||
}
|
||||
|
||||
$body = <<<EOBODY
|
||||
{$admin_username} ({$admin_realname}) has changed your Phabricator username.
|
||||
|
||||
Old Username: {$old_username}
|
||||
New Username: {$new_username}
|
||||
|
||||
{$password_instructions}
|
||||
EOBODY;
|
||||
|
||||
$mail = id(new PhabricatorMetaMTAMail())
|
||||
->addTos(array($this->getPHID()))
|
||||
->setSubject('[Phabricator] Username Changed')
|
||||
->setBody($body)
|
||||
->setFrom($admin->getPHID())
|
||||
->saveAndSend();
|
||||
}
|
||||
|
||||
public static function describeValidUsername() {
|
||||
return 'Usernames must contain only numbers, letters, period, underscore '.
|
||||
'and hyphen, and can not end with a period.';
|
||||
|
|
|
@ -37,6 +37,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
|
|||
const ACTION_EMAIL_ADD = 'email-add';
|
||||
|
||||
const ACTION_CHANGE_PASSWORD = 'change-password';
|
||||
const ACTION_CHANGE_USERNAME = 'change-username';
|
||||
|
||||
protected $actorPHID;
|
||||
protected $userPHID;
|
||||
|
|
Loading…
Reference in a new issue