1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-01 19:22: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:
epriestley 2012-06-06 07:09:56 -07:00
parent 0a7b4591ef
commit 0e1bbbd489
5 changed files with 201 additions and 45 deletions

View file

@ -36,22 +36,6 @@ final class PhabricatorEmailTokenController
return new Aphront400Response(); 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; $token = $this->token;
$email = $request->getStr('email'); $email = $request->getStr('email');
@ -103,10 +87,12 @@ final class PhabricatorEmailTokenController
// enough, without requiring users to go through a second round of email // enough, without requiring users to go through a second round of email
// verification. // verification.
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$target_email->setIsVerified(1); $target_email->setIsVerified(1);
$target_email->save(); $target_email->save();
$session_key = $target_user->establishSession('web'); $session_key = $target_user->establishSession('web');
unset($unguarded);
$request->setCookie('phusr', $target_user->getUsername()); $request->setCookie('phusr', $target_user->getUsername());
$request->setCookie('phsid', $session_key); $request->setCookie('phsid', $session_key);

View file

@ -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 )------------------------------------------------------ */ /* -( Editing Roles )------------------------------------------------------ */

View file

@ -41,23 +41,24 @@ final class PhabricatorPeopleEditController
if (!$user) { if (!$user) {
return new Aphront404Response(); return new Aphront404Response();
} }
$base_uri = '/people/edit/'.$user->getID().'/';
} else { } else {
$user = new PhabricatorUser(); $user = new PhabricatorUser();
$base_uri = '/people/edit/';
} }
$views = array( $nav = new AphrontSideNavFilterView();
'basic' => 'Basic Information', $nav->setBaseURI(new PhutilURI($base_uri));
'role' => 'Edit Roles', $nav->addFilter('basic', 'Basic Information');
'cert' => 'Conduit Certificate', $nav->addFilter('role', 'Edit Roles');
); $nav->addFilter('cert', 'Conduit Certificate');
$nav->addSpacer();
$nav->addFilter('rename', 'Change Username');
if (!$user->getID()) { if (!$user->getID()) {
$view = 'basic'; $this->view = 'basic';
} else if (isset($views[$this->view])) {
$view = $this->view;
} else {
$view = 'basic';
} }
$view = $nav->selectFilter($this->view, 'basic');
$content = array(); $content = array();
@ -79,6 +80,11 @@ final class PhabricatorPeopleEditController
case 'cert': case 'cert':
$response = $this->processCertificateRequest($user); $response = $this->processCertificateRequest($user);
break; break;
case 'rename':
$response = $this->processRenameRequest($user);
break;
default:
return new Aphront404Response();
} }
if ($response instanceof AphrontResponse) { if ($response instanceof AphrontResponse) {
@ -88,21 +94,8 @@ final class PhabricatorPeopleEditController
$content[] = $response; $content[] = $response;
if ($user->getID()) { if ($user->getID()) {
$side_nav = new AphrontSideNavView(); $nav->appendChild($content);
$side_nav->appendChild($content); $content = $nav;
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;
} }
return $this->buildStandardPageResponse( return $this->buildStandardPageResponse(
@ -444,7 +437,6 @@ final class PhabricatorPeopleEditController
$request = $this->getRequest(); $request = $this->getRequest();
$admin = $request->getUser(); $admin = $request->getUser();
$form = new AphrontFormView(); $form = new AphrontFormView();
$form $form
->setUser($admin) ->setUser($admin)
@ -481,6 +473,100 @@ final class PhabricatorPeopleEditController
return array($panel); 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() { private function getRoleInstructions() {
$roles_link = phutil_render_tag( $roles_link = phutil_render_tag(
'a', 'a',

View file

@ -549,6 +549,46 @@ EOBODY;
->saveAndSend(); ->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() { public static function describeValidUsername() {
return 'Usernames must contain only numbers, letters, period, underscore '. return 'Usernames must contain only numbers, letters, period, underscore '.
'and hyphen, and can not end with a period.'; 'and hyphen, and can not end with a period.';

View file

@ -37,6 +37,7 @@ final class PhabricatorUserLog extends PhabricatorUserDAO {
const ACTION_EMAIL_ADD = 'email-add'; const ACTION_EMAIL_ADD = 'email-add';
const ACTION_CHANGE_PASSWORD = 'change-password'; const ACTION_CHANGE_PASSWORD = 'change-password';
const ACTION_CHANGE_USERNAME = 'change-username';
protected $actorPHID; protected $actorPHID;
protected $userPHID; protected $userPHID;