Shift around username/password editing

The username is now made more visible on the register screen,
encouraging the user to create a new username before they set
an SSH public key on their account.

Username and password were removed from most tabs and put into their
own HTTP Password tab, just below the SSH Public Keys.  We also now
have a clear password button, to permit erasing a password that was
assigned and isn't actually needed by the user.

The username can only be set once per account now, and once set is
not permitted to be changed.  This change simplifies our UI, but it
also sets the stage for supporting ${user} variables in access rules
for references, and renaming an account once it has branches owned by
it would complicate the account rename process.

Change-Id: I2c0f26bb4501b88faa451105dd3d74e3830e632c
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce 2010-07-15 01:05:13 -07:00
parent 270c5941d4
commit c25b07b89c
19 changed files with 512 additions and 308 deletions

View File

@ -26,6 +26,7 @@ public class PageLinks {
public static final String SETTINGS = "settings";
public static final String SETTINGS_PREFERENCES = "settings,preferences";
public static final String SETTINGS_SSHKEYS = "settings,ssh-keys";
public static final String SETTINGS_HTTP_PASSWORD = "settings,http-password";
public static final String SETTINGS_WEBIDENT = "settings,web-identities";
public static final String SETTINGS_MYGROUPS = "settings,group-memberships";
public static final String SETTINGS_AGREEMENTS = "settings,agreements";

View File

@ -49,6 +49,10 @@ public interface AccountSecurity extends RemoteJsonService {
void generatePassword(AccountExternalId.Key key,
AsyncCallback<AccountExternalId> callback);
@SignInRequired
void clearPassword(AccountExternalId.Key key,
AsyncCallback<AccountExternalId> gerritCallback);
@SignInRequired
void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);

View File

@ -23,6 +23,7 @@ import static com.google.gerrit.common.PageLinks.REGISTER;
import static com.google.gerrit.common.PageLinks.SETTINGS;
import static com.google.gerrit.common.PageLinks.SETTINGS_AGREEMENTS;
import static com.google.gerrit.common.PageLinks.SETTINGS_CONTACT;
import static com.google.gerrit.common.PageLinks.SETTINGS_HTTP_PASSWORD;
import static com.google.gerrit.common.PageLinks.SETTINGS_MYGROUPS;
import static com.google.gerrit.common.PageLinks.SETTINGS_NEW_AGREEMENT;
import static com.google.gerrit.common.PageLinks.SETTINGS_PREFERENCES;
@ -35,6 +36,7 @@ import com.google.gerrit.client.account.MyAgreementsScreen;
import com.google.gerrit.client.account.MyContactInformationScreen;
import com.google.gerrit.client.account.MyGroupsScreen;
import com.google.gerrit.client.account.MyIdentitiesScreen;
import com.google.gerrit.client.account.MyPasswordScreen;
import com.google.gerrit.client.account.MyPreferencesScreen;
import com.google.gerrit.client.account.MyProfileScreen;
import com.google.gerrit.client.account.MySshKeysScreen;
@ -331,6 +333,10 @@ public class Dispatcher {
return new MyIdentitiesScreen();
}
if (token.equals(SETTINGS_HTTP_PASSWORD)) {
return new MyPasswordScreen();
}
if (token.equals(SETTINGS_MYGROUPS)) {
return new MyGroupsScreen();
}

View File

@ -25,6 +25,8 @@ public interface GerritCss extends CssResource {
String accountDashboard();
String accountInfoBlock();
String accountName();
String accountUsername();
String accountPassword();
String activeRow();
String addReviewer();
String removeReviewer();
@ -169,8 +171,6 @@ public interface GerritCss extends CssResource {
String sshHostKeyPanelKnownHostEntry();
String sshKeyPanelEncodedKey();
String sshKeyPanelInvalid();
String sshPanelUsername();
String sshPanelPassword();
String topmenu();
String topmenuMenuLeft();
String topmenuMenuRight();
@ -178,5 +178,6 @@ public interface GerritCss extends CssResource {
String topmenuTDmenu();
String topmost();
String useridentity();
String usernameField();
String version();
}

View File

@ -36,6 +36,7 @@ public interface AccountConstants extends Constants {
String tabWatchedProjects();
String tabContactInformation();
String tabSshKeys();
String tabHttpAccess();
String tabWebIdentities();
String tabMyGroups();
String tabAgreements();
@ -49,7 +50,9 @@ public interface AccountConstants extends Constants {
String userName();
String password();
String buttonSetUserName();
String buttonChangeUserName();
String buttonClearPassword();
String buttonGeneratePassword();
String invalidUserName();
@ -117,6 +120,7 @@ public interface AccountConstants extends Constants {
String welcomeToGerritCodeReview();
String welcomeReviewContact();
String welcomeContactFrom();
String welcomeUsernameHeading();
String welcomeSshKeyHeading();
String welcomeSshKeyText();
String welcomeAgreementHeading();

View File

@ -17,6 +17,7 @@ tabPreferences = Preferences
tabWatchedProjects = Watched Projects
tabContactInformation = Contact Information
tabSshKeys = SSH Public Keys
tabHttpAccess = HTTP Password
tabWebIdentities = Identities
tabMyGroups = Groups
tabAgreements = Agreements
@ -30,8 +31,10 @@ buttonAddSshKey = Add
userName = Username
password = Password
buttonSetUserName = Select Username
buttonChangeUserName = Change Username
buttonGeneratePassword = Regenerate
buttonClearPassword = Clear Password
buttonGeneratePassword = Generate Password
invalidUserName = Username must contain only letters, numbers, _, - or .
sshKeyInvalid = Invalid Key
@ -113,6 +116,8 @@ welcomeContactFrom = \
you are to others, and to send updates to code reviews you have either \
started or subscribed to.</p>
welcomeUsernameHeading = Select a unique username:
welcomeSshKeyHeading = Register an SSH public key:
welcomeSshKeyText = \
<p>Gerrit Code Review uses \

View File

@ -20,6 +20,7 @@ import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ContactInformation;
import com.google.gerrit.reviewdb.Account.FieldName;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.dom.client.ClickEvent;
@ -98,12 +99,18 @@ class ContactPanelShort extends Composite {
emailLine.add(registerNewEmail);
}
row(infoPlainText, 0, Util.C.contactFieldFullName(), nameTxt);
row(infoPlainText, 1, Util.C.contactFieldEmail(), emailLine);
int row = 0;
if (!Gerrit.getConfig().canEdit(FieldName.USER_NAME)) {
infoPlainText.resizeRows(infoPlainText.getRowCount() + 1);
row(infoPlainText, row++, Util.C.userName(), new UsernameField());
}
row(infoPlainText, row++, Util.C.contactFieldFullName(), nameTxt);
row(infoPlainText, row++, Util.C.contactFieldEmail(), emailLine);
infoPlainText.getCellFormatter().addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
infoPlainText.getCellFormatter().addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
infoPlainText.getCellFormatter().addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
infoPlainText.getCellFormatter().addStyleName(row - 1, 0, Gerrit.RESOURCES.css().bottomheader());
save = new Button(Util.C.buttonSaveChanges());
save.setEnabled(false);

View File

@ -46,8 +46,6 @@ public class MyIdentitiesScreen extends SettingsScreen {
protected void onInitUI() {
super.onInitUI();
add(new UsernamePanel());
identites = new IdTable();
add(identites);

View File

@ -0,0 +1,176 @@
// Copyright (C) 2008 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.account;
import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import java.util.List;
public class MyPasswordScreen extends SettingsScreen {
private CopyableLabel password;
private Button generatePassword;
private Button clearPassword;
private AccountExternalId id;
@Override
protected void onInitUI() {
super.onInitUI();
password = new CopyableLabel("");
password.addStyleName(Gerrit.RESOURCES.css().accountPassword());
generatePassword = new Button(Util.C.buttonGeneratePassword());
generatePassword.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
doGeneratePassword();
}
});
clearPassword = new Button(Util.C.buttonClearPassword());
clearPassword.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
doClearPassword();
}
});
final Grid userInfo = new Grid(2, 2);
final CellFormatter fmt = userInfo.getCellFormatter();
userInfo.setStyleName(Gerrit.RESOURCES.css().infoBlock());
userInfo.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
add(userInfo);
row(userInfo, 0, Util.C.userName(), new UsernameField());
row(userInfo, 1, Util.C.password(), password);
fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
final FlowPanel buttons = new FlowPanel();
buttons.add(generatePassword);
buttons.add(clearPassword);
add(buttons);
}
@Override
protected void onLoad() {
super.onLoad();
enableUI(false);
Util.ACCOUNT_SEC
.myExternalIds(new ScreenLoadCallback<List<AccountExternalId>>(this) {
public void preDisplay(final List<AccountExternalId> result) {
AccountExternalId id = null;
for (AccountExternalId i : result) {
if (i.isScheme(SCHEME_USERNAME)) {
id = i;
break;
}
}
display(id);
}
});
}
private void display(AccountExternalId id) {
String user, pass;
if (id != null) {
user = id.getSchemeRest();
pass = id.getPassword();
} else {
user = null;
pass = null;
}
this.id = id;
Gerrit.getUserAccount().setUserName(user);
password.setText(pass != null ? pass : "");
password.setVisible(pass != null);
enableUI(true);
}
private void row(final Grid info, final int row, final String name,
final Widget field) {
final CellFormatter fmt = info.getCellFormatter();
if (LocaleInfo.getCurrentLocale().isRTL()) {
info.setText(row, 1, name);
info.setWidget(row, 0, field);
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().header());
} else {
info.setText(row, 0, name);
info.setWidget(row, 1, field);
fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().header());
}
}
private void doGeneratePassword() {
if (id != null) {
enableUI(false);
Util.ACCOUNT_SEC.generatePassword(id.getKey(),
new GerritCallback<AccountExternalId>() {
public void onSuccess(final AccountExternalId result) {
display(result);
}
@Override
public void onFailure(final Throwable caught) {
enableUI(true);
}
});
}
}
private void doClearPassword() {
if (id != null) {
enableUI(false);
Util.ACCOUNT_SEC.clearPassword(id.getKey(),
new GerritCallback<AccountExternalId>() {
public void onSuccess(final AccountExternalId result) {
display(result);
}
@Override
public void onFailure(final Throwable caught) {
enableUI(true);
}
});
}
}
private void enableUI(boolean on) {
on &= id != null;
generatePassword.setEnabled(on);
clearPassword.setVisible(on && id.getPassword() != null);
}
}

View File

@ -69,7 +69,7 @@ public class MyProfileScreen extends SettingsScreen {
}
void display(final Account account) {
info.setText(0, fieldIdx, account.getUserName());
info.setWidget(0, fieldIdx, new UsernameField());
info.setText(1, fieldIdx, account.getFullName());
info.setText(2, fieldIdx, account.getPreferredEmail());
info.setText(3, fieldIdx, mediumFormat(account.getRegisteredOn()));

View File

@ -20,9 +20,13 @@ import com.google.gerrit.client.ui.InlineHyperlink;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.Account.FieldName;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.FormPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
public class RegisterScreen extends AccountScreen {
private final String nextToken;
@ -65,6 +69,36 @@ public class RegisterScreen extends AccountScreen {
});
formBody.add(contactGroup);
if (Gerrit.getUserAccount().getUserName() == null
&& Gerrit.getConfig().canEdit(FieldName.USER_NAME)) {
final FlowPanel fp = new FlowPanel();
fp.setStyleName(Gerrit.RESOURCES.css().registerScreenSection());
fp.add(new SmallHeading(Util.C.welcomeUsernameHeading()));
final Grid userInfo = new Grid(1, 2);
final CellFormatter fmt = userInfo.getCellFormatter();
userInfo.setStyleName(Gerrit.RESOURCES.css().infoBlock());
userInfo.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
fp.add(userInfo);
fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().bottomheader());
UsernameField field = new UsernameField();
if (LocaleInfo.getCurrentLocale().isRTL()) {
userInfo.setText(0, 1, Util.C.userName());
userInfo.setWidget(0, 0, field);
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().header());
} else {
userInfo.setText(0, 0, Util.C.userName());
userInfo.setWidget(0, 1, field);
fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().header());
}
formBody.add(fp);
}
final FlowPanel sshKeyGroup = new FlowPanel();
sshKeyGroup.setStyleName(Gerrit.RESOURCES.css().registerScreenSection());
sshKeyGroup.add(new SmallHeading(Util.C.welcomeSshKeyHeading()));

View File

@ -27,6 +27,7 @@ public abstract class SettingsScreen extends MenuScreen {
link(Util.C.tabWatchedProjects(), PageLinks.SETTINGS_PROJECTS);
link(Util.C.tabContactInformation(), PageLinks.SETTINGS_CONTACT);
link(Util.C.tabSshKeys(), PageLinks.SETTINGS_SSHKEYS);
link(Util.C.tabHttpAccess(), PageLinks.SETTINGS_HTTP_PASSWORD);
link(Util.C.tabWebIdentities(), PageLinks.SETTINGS_WEBIDENT);
link(Util.C.tabMyGroups(), PageLinks.SETTINGS_MYGROUPS);
if (Gerrit.getConfig().isUseContributorAgreements()) {

View File

@ -0,0 +1,183 @@
// Copyright (C) 2008 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.account;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.common.errors.InvalidUserNameException;
import com.google.gerrit.reviewdb.Account;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.client.VoidResult;
class UsernameField extends Composite {
private CopyableLabel userNameLbl;
private NpTextBox userNameTxt;
private Button setUserName;
UsernameField() {
String user = Gerrit.getUserAccount().getUserName();
userNameLbl = new CopyableLabel(user != null ? user : "");
userNameLbl.setStyleName(Gerrit.RESOURCES.css().accountUsername());
if (user != null || !canEditUserName()) {
initWidget(userNameLbl);
} else {
final FlowPanel body = new FlowPanel();
initWidget(body);
setStyleName(Gerrit.RESOURCES.css().usernameField());
userNameTxt = new NpTextBox();
userNameTxt.addKeyPressHandler(new UserNameValidator());
userNameTxt.addStyleName(Gerrit.RESOURCES.css().accountUsername());
userNameTxt.setVisibleLength(16);
userNameTxt.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
if (event.getCharCode() == KeyCodes.KEY_ENTER) {
doSetUserName();
}
}
});
setUserName = new Button(Util.C.buttonSetUserName());
setUserName.setVisible(canEditUserName());
setUserName.setEnabled(false);
setUserName.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
doSetUserName();
}
});
new TextSaveButtonListener(userNameTxt, setUserName);
userNameLbl.setVisible(false);
body.add(userNameLbl);
body.add(userNameTxt);
body.add(setUserName);
}
}
private boolean canEditUserName() {
return Gerrit.getConfig().canEdit(Account.FieldName.USER_NAME);
}
private void doSetUserName() {
if (!canEditUserName()) {
return;
}
String newName = userNameTxt.getText();
if ("".equals(newName)) {
newName = null;
}
if (newName != null && !newName.matches(Account.USER_NAME_PATTERN)) {
invalidUserName();
return;
}
enableUI(false);
final String newUserName = newName;
Util.ACCOUNT_SEC.changeUserName(newUserName,
new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
Gerrit.getUserAccount().setUserName(newUserName);
userNameLbl.setText(newUserName);
userNameLbl.setVisible(true);
userNameTxt.setVisible(false);
setUserName.setVisible(false);
}
@Override
public void onFailure(final Throwable caught) {
enableUI(true);
if (InvalidUserNameException.MESSAGE.equals(caught.getMessage())) {
invalidUserName();
} else {
super.onFailure(caught);
}
}
});
}
private void invalidUserName() {
new ErrorDialog(Util.C.invalidUserName()).center();
}
private void enableUI(final boolean on) {
userNameTxt.setEnabled(on);
setUserName.setEnabled(on);
}
private final class UserNameValidator implements KeyPressHandler {
@Override
public void onKeyPress(final KeyPressEvent event) {
final char code = event.getCharCode();
switch (code) {
case KeyCodes.KEY_ALT:
case KeyCodes.KEY_BACKSPACE:
case KeyCodes.KEY_CTRL:
case KeyCodes.KEY_DELETE:
case KeyCodes.KEY_DOWN:
case KeyCodes.KEY_END:
case KeyCodes.KEY_ENTER:
case KeyCodes.KEY_ESCAPE:
case KeyCodes.KEY_HOME:
case KeyCodes.KEY_LEFT:
case KeyCodes.KEY_PAGEDOWN:
case KeyCodes.KEY_PAGEUP:
case KeyCodes.KEY_RIGHT:
case KeyCodes.KEY_SHIFT:
case KeyCodes.KEY_TAB:
case KeyCodes.KEY_UP:
// Allow these, even if one of their assigned codes is
// identical to an ASCII character we do not want to
// allow in the box.
//
// We still want to let the user move around the input box
// with their arrow keys, or to move between fields using tab.
// Invalid characters introduced will be caught through the
// server's own validation of the input data.
//
break;
default:
final TextBox box = (TextBox) event.getSource();
final String re;
if (box.getCursorPos() == 0)
re = Account.USER_NAME_PATTERN_FIRST;
else
re = Account.USER_NAME_PATTERN_REST;
if (!String.valueOf(code).matches("^" + re + "$")) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}

View File

@ -1,297 +0,0 @@
// Copyright (C) 2008 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.account;
import static com.google.gerrit.reviewdb.AccountExternalId.SCHEME_USERNAME;
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.common.errors.InvalidUserNameException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
import com.google.gwtexpui.clippy.client.CopyableLabel;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.client.VoidResult;
import java.util.List;
public class UsernamePanel extends Composite {
private NpTextBox userNameTxt;
private Button changeUserName;
private CopyableLabel password;
private Button generatePassword;
private AccountExternalId.Key idKey;
UsernamePanel() {
final FlowPanel body = new FlowPanel();
initWidget(body);
userNameTxt = new NpTextBox();
userNameTxt.addKeyPressHandler(new UserNameValidator());
userNameTxt.addStyleName(Gerrit.RESOURCES.css().sshPanelUsername());
userNameTxt.setVisibleLength(16);
userNameTxt.setReadOnly(!canEditUserName());
userNameTxt.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
if (event.getCharCode() == KeyCodes.KEY_ENTER) {
doChangeUserName();
}
}
});
changeUserName = new Button(Util.C.buttonChangeUserName());
changeUserName.setVisible(canEditUserName());
changeUserName.setEnabled(false);
changeUserName.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
doChangeUserName();
}
});
new TextSaveButtonListener(userNameTxt, changeUserName);
password = new CopyableLabel("");
password.addStyleName(Gerrit.RESOURCES.css().sshPanelPassword());
password.setVisible(false);
generatePassword = new Button(Util.C.buttonGeneratePassword());
generatePassword.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
doGeneratePassword();
}
});
final Grid userInfo = new Grid(2, 3);
final CellFormatter fmt = userInfo.getCellFormatter();
userInfo.setStyleName(Gerrit.RESOURCES.css().infoBlock());
userInfo.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
body.add(userInfo);
row(userInfo, 0, Util.C.userName(), userNameTxt, changeUserName);
row(userInfo, 1, Util.C.password(), password, generatePassword);
fmt.addStyleName(0, 0, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().topmost());
fmt.addStyleName(1, 0, Gerrit.RESOURCES.css().bottomheader());
}
@Override
protected void onLoad() {
super.onLoad();
enableUI(false);
Util.ACCOUNT_SEC
.myExternalIds(new GerritCallback<List<AccountExternalId>>() {
public void onSuccess(final List<AccountExternalId> result) {
AccountExternalId id = null;
for (AccountExternalId i : result) {
if (i.isScheme(SCHEME_USERNAME)) {
id = i;
break;
}
}
display(id);
}
});
}
private void display(AccountExternalId id) {
String user, pass;
if (id != null) {
idKey = id.getKey();
user = id.getSchemeRest();
pass = id.getPassword();
} else {
idKey = null;
user = null;
pass = null;
}
Gerrit.getUserAccount().setUserName(user);
userNameTxt.setText(user);
userNameTxt.setEnabled(true);
generatePassword.setEnabled(idKey != null);
if (pass != null) {
password.setText(pass);
password.setVisible(true);
} else {
password.setVisible(false);
}
}
private void row(final Grid info, final int row, final String name,
final Widget field1, final Widget field2) {
final CellFormatter fmt = info.getCellFormatter();
if (LocaleInfo.getCurrentLocale().isRTL()) {
info.setText(row, 2, name);
info.setWidget(row, 1, field1);
info.setWidget(row, 0, field2);
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().noborder());
fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().header());
} else {
info.setText(row, 0, name);
info.setWidget(row, 1, field1);
info.setWidget(row, 2, field2);
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().noborder());
fmt.addStyleName(row, 0, Gerrit.RESOURCES.css().header());
}
}
private boolean canEditUserName() {
return Gerrit.getConfig().canEdit(Account.FieldName.USER_NAME);
}
void doChangeUserName() {
if (!canEditUserName()) {
return;
}
String newName = userNameTxt.getText();
if ("".equals(newName)) {
newName = null;
}
if (newName != null && !newName.matches(Account.USER_NAME_PATTERN)) {
invalidUserName();
return;
}
enableUI(false);
final String newUserName = newName;
Util.ACCOUNT_SEC.changeUserName(newUserName,
new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
Gerrit.getUserAccount().setUserName(newUserName);
enableUI(true);
}
@Override
public void onFailure(final Throwable caught) {
enableUI(true);
if (InvalidUserNameException.MESSAGE.equals(caught.getMessage())) {
invalidUserName();
} else {
super.onFailure(caught);
}
}
});
}
void invalidUserName() {
userNameTxt.setFocus(true);
new ErrorDialog(Util.C.invalidUserName()).center();
}
void doGeneratePassword() {
if (idKey == null) {
return;
}
enableUI(false);
Util.ACCOUNT_SEC.generatePassword(idKey,
new GerritCallback<AccountExternalId>() {
public void onSuccess(final AccountExternalId result) {
enableUI(true);
display(result);
}
@Override
public void onFailure(final Throwable caught) {
enableUI(true);
if (InvalidUserNameException.MESSAGE.equals(caught.getMessage())) {
invalidUserName();
} else {
super.onFailure(caught);
}
}
});
}
private void enableUI(final boolean on) {
userNameTxt.setEnabled(on);
changeUserName.setEnabled(on);
generatePassword.setEnabled(on && idKey != null);
}
private final class UserNameValidator implements KeyPressHandler {
@Override
public void onKeyPress(final KeyPressEvent event) {
final char code = event.getCharCode();
switch (code) {
case KeyCodes.KEY_ALT:
case KeyCodes.KEY_BACKSPACE:
case KeyCodes.KEY_CTRL:
case KeyCodes.KEY_DELETE:
case KeyCodes.KEY_DOWN:
case KeyCodes.KEY_END:
case KeyCodes.KEY_ENTER:
case KeyCodes.KEY_ESCAPE:
case KeyCodes.KEY_HOME:
case KeyCodes.KEY_LEFT:
case KeyCodes.KEY_PAGEDOWN:
case KeyCodes.KEY_PAGEUP:
case KeyCodes.KEY_RIGHT:
case KeyCodes.KEY_SHIFT:
case KeyCodes.KEY_TAB:
case KeyCodes.KEY_UP:
// Allow these, even if one of their assigned codes is
// identical to an ASCII character we do not want to
// allow in the box.
//
// We still want to let the user move around the input box
// with their arrow keys, or to move between fields using tab.
// Invalid characters introduced will be caught through the
// server's own validation of the input data.
//
break;
default:
final TextBox box = (TextBox) event.getSource();
final String re;
if (box.getCursorPos() == 0)
re = Account.USER_NAME_PATTERN_FIRST;
else
re = Account.USER_NAME_PATTERN_REST;
if (!String.valueOf(code).matches("^" + re + "$")) {
event.preventDefault();
event.stopPropagation();
}
}
}
}
}

View File

@ -984,11 +984,14 @@ a:hover.downloadLink {
/** AccountSettings **/
.sshPanelUsername {
.usernameField {
white-space: nowrap;
}
.accountUsername {
font-family: mono-font;
font-size: small;
}
.sshPanelPassword {
.accountPassword {
font-family: mono-font;
font-size: small;
}

View File

@ -29,6 +29,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.account.ClearPassword;
import com.google.gerrit.server.account.GeneratePassword;
import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.server.config.CanonicalWebUrl;
@ -139,6 +140,7 @@ public class WebModule extends FactoryModule {
bind(AccountManager.class);
bind(ChangeUserName.CurrentUser.class);
factory(ChangeUserName.Factory.class);
factory(ClearPassword.Factory.class);
factory(GeneratePassword.Factory.class);
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(

View File

@ -37,6 +37,7 @@ import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.account.ClearPassword;
import com.google.gerrit.server.account.GeneratePassword;
import com.google.gerrit.server.account.Realm;
import com.google.gerrit.server.config.AuthConfig;
@ -75,6 +76,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
private final AccountManager accountManager;
private final boolean useContactInfo;
private final ClearPassword.Factory clearPasswordFactory;
private final GeneratePassword.Factory generatePasswordFactory;
private final ChangeUserName.CurrentUser changeUserNameFactory;
private final DeleteExternalIds.Factory deleteExternalIdsFactory;
@ -88,6 +90,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
final RegisterNewEmailSender.Factory esf, final SshKeyCache skc,
final AccountByEmailCache abec, final AccountCache uac,
final AccountManager am,
final ClearPassword.Factory clearPasswordFactory,
final GeneratePassword.Factory generatePasswordFactory,
final ChangeUserName.CurrentUser changeUserNameFactory,
final DeleteExternalIds.Factory deleteExternalIdsFactory,
@ -106,6 +109,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
useContactInfo = contactStore != null && contactStore.isEnabled();
this.clearPasswordFactory = clearPasswordFactory;
this.generatePasswordFactory = generatePasswordFactory;
this.changeUserNameFactory = changeUserNameFactory;
this.deleteExternalIdsFactory = deleteExternalIdsFactory;
@ -183,6 +187,12 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
Handler.wrap(generatePasswordFactory.create(key)).to(callback);
}
@Override
public void clearPassword(AccountExternalId.Key key,
AsyncCallback<AccountExternalId> callback) {
Handler.wrap(clearPasswordFactory.create(key)).to(callback);
}
public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
externalIdDetailFactory.create().to(callback);
}

View File

@ -91,6 +91,9 @@ public class ChangeUserName implements Callable<VoidResult> {
public VoidResult call() throws OrmException, NameAlreadyUsedException,
InvalidUserNameException {
final Collection<AccountExternalId> old = old();
if (!old.isEmpty()) {
throw new IllegalStateException("Username cannot be changed.");
}
if (newUsername != null && !newUsername.isEmpty()) {
if (!USER_NAME_PATTERN.matcher(newUsername).matches()) {

View File

@ -0,0 +1,63 @@
// Copyright (C) 2010 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.server.account;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
import java.util.concurrent.Callable;
/** Operation to clear a password for an account. */
public class ClearPassword implements Callable<AccountExternalId> {
public interface Factory {
ClearPassword create(AccountExternalId.Key forUser);
}
private final AccountCache accountCache;
private final ReviewDb db;
private final IdentifiedUser user;
private final AccountExternalId.Key forUser;
@Inject
ClearPassword(final AccountCache accountCache, final ReviewDb db,
final IdentifiedUser user,
@Assisted AccountExternalId.Key forUser) {
this.accountCache = accountCache;
this.db = db;
this.user = user;
this.forUser = forUser;
}
public AccountExternalId call() throws OrmException, NoSuchEntityException {
AccountExternalId id = db.accountExternalIds().get(forUser);
if (id == null || !user.getAccountId().equals(id.getAccountId())) {
throw new NoSuchEntityException();
}
id.setPassword(null);
db.accountExternalIds().update(Collections.singleton(id));
accountCache.evict(user.getAccountId());
return id;
}
}