Add a password field to the account identities

Some types of identities, e.g. "username:", now support having
an optional password generated alongside of them.  This can be
used in the future to authenticate the user.

Currently we intend to use this only for authentication over
HTTP, so we generate the password for the user as they would
need to store it into a local ~/.netrc.  We don't want them
to reuse an existing password that might be vulnerable.

Change-Id: I047a97f00249c81638625d7654087ea71f8f386a
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Shawn O. Pearce
2010-01-15 17:22:25 -08:00
parent fbb6029761
commit 37930f8d55
15 changed files with 460 additions and 190 deletions

View File

@@ -120,6 +120,7 @@ public interface GerritCss extends CssResource {
String needsReview();
String negscore();
String noLineLineNumber();
String noborder();
String patchBrowserPopup();
String patchBrowserPopupBody();
String patchComments();
@@ -160,6 +161,7 @@ public interface GerritCss extends CssResource {
String sshKeyPanelEncodedKey();
String sshKeyPanelInvalid();
String sshPanelUsername();
String sshPanelPassword();
String topmenu();
String topmenuMenuLeft();
String topmenuMenuRight();

View File

@@ -46,7 +46,9 @@ public interface AccountConstants extends Constants {
String buttonAddSshKey();
String userName();
String password();
String buttonChangeUserName();
String buttonGeneratePassword();
String invalidUserName();
String sshKeyInvalid();

View File

@@ -26,7 +26,9 @@ buttonOpenSshKey = Open Key ...
buttonAddSshKey = Add
userName = Username
password = Password
buttonChangeUserName = Change Username
buttonGeneratePassword = Regenerate
invalidUserName = Username must contain only letters, numbers, _, - or .
sshKeyInvalid = Invalid Key

View File

@@ -45,6 +45,7 @@ class ExternalIdPanel extends Composite {
ExternalIdPanel() {
final FlowPanel body = new FlowPanel();
body.add(new UsernamePanel());
identites = new IdTable();
body.add(identites);
@@ -80,10 +81,7 @@ class ExternalIdPanel extends Composite {
@Override
protected void onLoad() {
super.onLoad();
refresh();
}
private void refresh() {
Util.ACCOUNT_SEC
.myExternalIds(new GerritCallback<List<AccountExternalId>>() {
public void onSuccess(final List<AccountExternalId> result) {

View File

@@ -14,48 +14,34 @@
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.FancyFlexTable;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.client.ui.TextSaveButtonListener;
import com.google.gerrit.common.data.SshHostKey;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.common.errors.InvalidUserNameException;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountExternalId;
import com.google.gerrit.reviewdb.AccountSshKey;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
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.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.i18n.client.LocaleInfo;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
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.HTML;
import com.google.gwt.user.client.ui.HasHorizontalAlignment;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Panel;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
import com.google.gwtexpui.globalkey.client.NpTextArea;
import com.google.gwtexpui.globalkey.client.NpTextBox;
import com.google.gwtjsonrpc.client.RemoteJsonException;
import com.google.gwtjsonrpc.client.VoidResult;
@@ -69,11 +55,6 @@ class SshPanel extends Composite {
private static String appletErrorInvalidKey;
private static String appletErrorSecurity;
private int labelIdx, fieldIdx;
private NpTextBox userNameTxt;
private Button changeUserName;
private SshKeyTable keys;
private Button showAddKeyBlock;
@@ -89,51 +70,8 @@ class SshPanel extends Composite {
private Panel serverKeys;
SshPanel() {
if (LocaleInfo.getCurrentLocale().isRTL()) {
labelIdx = 1;
fieldIdx = 0;
} else {
labelIdx = 0;
fieldIdx = 1;
}
final FlowPanel body = new FlowPanel();
userNameTxt = new NpTextBox();
userNameTxt.addKeyPressHandler(new UserNameValidator());
userNameTxt.addStyleName(Gerrit.RESOURCES.css().sshPanelUsername());
userNameTxt.setVisibleLength(16);
userNameTxt.setReadOnly(!canEditUserName());
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);
final Grid userInfo = new Grid(1, 2);
userInfo.setStyleName(Gerrit.RESOURCES.css().infoBlock());
userInfo.addStyleName(Gerrit.RESOURCES.css().accountInfoBlock());
body.add(userInfo);
final FlowPanel userNameRow = new FlowPanel();
userNameRow.add(userNameTxt);
userNameRow.add(changeUserName);
row(userInfo, 0, Util.C.userName(), userNameRow);
userInfo.getCellFormatter().addStyleName(0, 0,
Gerrit.RESOURCES.css().topmost());
userInfo.getCellFormatter().addStyleName(0, 0,
Gerrit.RESOURCES.css().topmost());
userInfo.getCellFormatter().addStyleName(0, 1,
Gerrit.RESOURCES.css().topmost());
userInfo.getCellFormatter().addStyleName(0, 0,
Gerrit.RESOURCES.css().bottomheader());
body.add(new UsernamePanel());
showAddKeyBlock = new Button(Util.C.buttonShowAddSshKey());
showAddKeyBlock.addClickHandler(new ClickHandler() {
@@ -224,68 +162,12 @@ class SshPanel extends Composite {
initWidget(body);
}
private boolean canEditUserName() {
return Gerrit.getConfig().canEdit(Account.FieldName.USER_NAME);
}
protected void row(final Grid info, final int row, final String name,
final Widget field) {
info.setText(row, labelIdx, name);
info.setWidget(row, fieldIdx, field);
info.getCellFormatter().addStyleName(row, 0,
Gerrit.RESOURCES.css().header());
}
void setKeyTableVisible(final boolean on) {
keys.setVisible(on);
deleteKey.setVisible(on);
closeAddKeyBlock.setVisible(on);
}
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;
}
userNameTxt.setEnabled(false);
changeUserName.setEnabled(false);
final String newUserName = newName;
Util.ACCOUNT_SEC.changeSshUserName(newUserName,
new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
Gerrit.getUserAccount().setUserName(newUserName);
userNameTxt.setEnabled(true);
changeUserName.setEnabled(false);
}
@Override
public void onFailure(final Throwable caught) {
userNameTxt.setEnabled(true);
changeUserName.setEnabled(true);
if (InvalidUserNameException.MESSAGE.equals(caught.getMessage())) {
invalidUserName();
} else {
super.onFailure(caught);
}
}
});
}
void invalidUserName() {
userNameTxt.setFocus(true);
new ErrorDialog(Util.C.invalidUserName()).center();
}
void doBrowse() {
browse.setEnabled(false);
@@ -431,23 +313,6 @@ class SshPanel extends Composite {
protected void onLoad() {
super.onLoad();
userNameTxt.setEnabled(false);
Util.ACCOUNT_SEC
.myExternalIds(new GerritCallback<List<AccountExternalId>>() {
public void onSuccess(final List<AccountExternalId> result) {
String userName = null;
for (AccountExternalId i : result) {
if (i.isScheme(SCHEME_USERNAME)) {
userName = i.getSchemeRest();
break;
}
}
Gerrit.getUserAccount().setUserName(userName);
userNameTxt.setText(userName);
userNameTxt.setEnabled(true);
}
});
Util.ACCOUNT_SEC.mySshKeys(new GerritCallback<List<AccountSshKey>>() {
public void onSuccess(final List<AccountSshKey> result) {
keys.display(result);
@@ -482,53 +347,6 @@ class SshPanel extends Composite {
addKeyBlock.setVisible(show);
}
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();
}
}
}
}
private class SshKeyTable extends FancyFlexTable<AccountSshKey> {
private ValueChangeHandler<Boolean> updateDeleteHandler;

View File

@@ -0,0 +1,289 @@
// 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());
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

@@ -754,6 +754,10 @@
text-align: right;
}
.infoBlock td.noborder {
border-right: none;
}
.infoBlock td.bottomheader {
border-bottom: 1px solid trim-color;
}
@@ -861,7 +865,10 @@
.sshPanelUsername {
font-family: mono-font;
font-size: small;
margin-right: 0.2em;
}
.sshPanelPassword {
font-family: mono-font;
font-size: small;
}
.sshKeyPanelEncodedKey {
white-space: nowrap;