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>
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
index a502b3b..358f656 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/PageLinks.java
@@ -26,6 +26,7 @@
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";
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
index 83d31a0..1117455 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccountSecurity.java
@@ -50,6 +50,10 @@
AsyncCallback<AccountExternalId> callback);
@SignInRequired
+ void clearPassword(AccountExternalId.Key key,
+ AsyncCallback<AccountExternalId> gerritCallback);
+
+ @SignInRequired
void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);
@SignInRequired
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index a5cd9df..b313994 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -23,6 +23,7 @@
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.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 @@
return new MyIdentitiesScreen();
}
+ if (token.equals(SETTINGS_HTTP_PASSWORD)) {
+ return new MyPasswordScreen();
+ }
+
if (token.equals(SETTINGS_MYGROUPS)) {
return new MyGroupsScreen();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index 8cea4b6..40fe731 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -25,6 +25,8 @@
String accountDashboard();
String accountInfoBlock();
String accountName();
+ String accountUsername();
+ String accountPassword();
String activeRow();
String addReviewer();
String removeReviewer();
@@ -169,8 +171,6 @@
String sshHostKeyPanelKnownHostEntry();
String sshKeyPanelEncodedKey();
String sshKeyPanelInvalid();
- String sshPanelUsername();
- String sshPanelPassword();
String topmenu();
String topmenuMenuLeft();
String topmenuMenuRight();
@@ -178,5 +178,6 @@
String topmenuTDmenu();
String topmost();
String useridentity();
+ String usernameField();
String version();
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
index fd5e030..e161c14 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.java
@@ -36,6 +36,7 @@
String tabWatchedProjects();
String tabContactInformation();
String tabSshKeys();
+ String tabHttpAccess();
String tabWebIdentities();
String tabMyGroups();
String tabAgreements();
@@ -49,7 +50,9 @@
String userName();
String password();
+ String buttonSetUserName();
String buttonChangeUserName();
+ String buttonClearPassword();
String buttonGeneratePassword();
String invalidUserName();
@@ -117,6 +120,7 @@
String welcomeToGerritCodeReview();
String welcomeReviewContact();
String welcomeContactFrom();
+ String welcomeUsernameHeading();
String welcomeSshKeyHeading();
String welcomeSshKeyText();
String welcomeAgreementHeading();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
index b8cdd07..b74add4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/AccountConstants.properties
@@ -17,6 +17,7 @@
tabWatchedProjects = Watched Projects
tabContactInformation = Contact Information
tabSshKeys = SSH Public Keys
+tabHttpAccess = HTTP Password
tabWebIdentities = Identities
tabMyGroups = Groups
tabAgreements = Agreements
@@ -30,8 +31,10 @@
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 @@
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 \
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
index 373fa19..21c9163 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/ContactPanelShort.java
@@ -20,6 +20,7 @@
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 @@
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);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
index 5afd785..f3816f2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyIdentitiesScreen.java
@@ -46,8 +46,6 @@
protected void onInitUI() {
super.onInitUI();
- add(new UsernamePanel());
-
identites = new IdTable();
add(identites);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
new file mode 100644
index 0000000..2202ac8
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java
@@ -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);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
index 0293b6d..65c2590 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyProfileScreen.java
@@ -69,7 +69,7 @@
}
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()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
index f43d1ac..8c99d45 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/RegisterScreen.java
@@ -20,9 +20,13 @@
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 @@
});
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()));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
index 213ccc3..9356018 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/SettingsScreen.java
@@ -27,6 +27,7 @@
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()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
new file mode 100644
index 0000000..93ccb74
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java
@@ -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();
+ }
+ }
+ }
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernamePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernamePanel.java
deleted file mode 100644
index 2ec20d0..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernamePanel.java
+++ /dev/null
@@ -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();
- }
- }
- }
- }
-}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index 809e412..2b2fbcc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -984,11 +984,14 @@
/** AccountSettings **/
-.sshPanelUsername {
+.usernameField {
+ white-space: nowrap;
+}
+.accountUsername {
font-family: mono-font;
font-size: small;
}
-.sshPanelPassword {
+.accountPassword {
font-family: mono-font;
font-size: small;
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 17a7ba5..93b6d09 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -29,6 +29,7 @@
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 @@
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(
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
index 11c2d9d..77662a1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AccountSecurityImpl.java
@@ -37,6 +37,7 @@
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 @@
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 @@
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 @@
useContactInfo = contactStore != null && contactStore.isEnabled();
+ this.clearPasswordFactory = clearPasswordFactory;
this.generatePasswordFactory = generatePasswordFactory;
this.changeUserNameFactory = changeUserNameFactory;
this.deleteExternalIdsFactory = deleteExternalIdsFactory;
@@ -183,6 +187,12 @@
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);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
index 1509299..e875a19 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ChangeUserName.java
@@ -91,6 +91,9 @@
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()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
new file mode 100644
index 0000000..1fc87fe
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java
@@ -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;
+ }
+}