From c25b07b89c5009f0818f9558a4784165627e14dd Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Thu, 15 Jul 2010 01:05:13 -0700 Subject: [PATCH] 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 --- .../com/google/gerrit/common/PageLinks.java | 1 + .../gerrit/common/data/AccountSecurity.java | 4 + .../com/google/gerrit/client/Dispatcher.java | 6 + .../com/google/gerrit/client/GerritCss.java | 5 +- .../client/account/AccountConstants.java | 4 + .../account/AccountConstants.properties | 7 +- .../client/account/ContactPanelShort.java | 13 +- .../client/account/MyIdentitiesScreen.java | 2 - .../client/account/MyPasswordScreen.java | 176 +++++++++++ .../client/account/MyProfileScreen.java | 2 +- .../gerrit/client/account/RegisterScreen.java | 34 ++ .../gerrit/client/account/SettingsScreen.java | 1 + .../gerrit/client/account/UsernameField.java | 183 +++++++++++ .../gerrit/client/account/UsernamePanel.java | 297 ------------------ .../java/com/google/gerrit/client/gerrit.css | 7 +- .../com/google/gerrit/httpd/WebModule.java | 2 + .../rpc/account/AccountSecurityImpl.java | 10 + .../gerrit/server/account/ChangeUserName.java | 3 + .../gerrit/server/account/ClearPassword.java | 63 ++++ 19 files changed, 512 insertions(+), 308 deletions(-) create mode 100644 gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPasswordScreen.java create mode 100644 gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernameField.java delete mode 100644 gerrit-gwtui/src/main/java/com/google/gerrit/client/account/UsernamePanel.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/account/ClearPassword.java 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 a502b3b100..358f6566a3 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 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"; 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 83d31a06ba..1117455ede 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 @@ -49,6 +49,10 @@ public interface AccountSecurity extends RemoteJsonService { void generatePassword(AccountExternalId.Key key, AsyncCallback callback); + @SignInRequired + void clearPassword(AccountExternalId.Key key, + AsyncCallback gerritCallback); + @SignInRequired void myExternalIds(AsyncCallback> callback); 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 a5cd9df39b..b31399438c 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.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(); } 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 8cea4b693e..40fe73117e 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 @@ 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(); } 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 fd5e030c91..e161c14237 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 @@ 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(); 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 b8cdd076f6..b74add47d7 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 @@ 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.

+welcomeUsernameHeading = Select a unique username: + welcomeSshKeyHeading = Register an SSH public key: welcomeSshKeyText = \

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 373fa19065..21c9163f69 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.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); 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 5afd785181..f3816f2fc4 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 @@ public class MyIdentitiesScreen extends SettingsScreen { 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 0000000000..2202ac88c0 --- /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>(this) { + public void preDisplay(final List 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() { + 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() { + 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 0293b6d9c8..65c2590b0e 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 @@ 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())); 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 f43d1ac2a2..8c99d45389 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.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())); 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 213ccc3a71..93560182ef 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 @@ 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()) { 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 0000000000..93ccb7431a --- /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() { + 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 2ec20d0f5c..0000000000 --- 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>() { - public void onSuccess(final List 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() { - 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() { - 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 809e412068..2b2fbcccc2 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 @@ 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; } 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 17a7ba5eeb..93b6d09a0d 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.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( 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 11c2d9dda7..77662a18a3 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.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 callback) { + Handler.wrap(clearPasswordFactory.create(key)).to(callback); + } + public void myExternalIds(AsyncCallback> 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 1509299447..e875a19933 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 class ChangeUserName implements Callable { public VoidResult call() throws OrmException, NameAlreadyUsedException, InvalidUserNameException { final Collection 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 0000000000..1fc87fe411 --- /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 { + 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; + } +}