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:
@@ -43,7 +43,11 @@ public interface AccountSecurity extends RemoteJsonService {
|
||||
AsyncCallback<VoidResult> callback);
|
||||
|
||||
@SignInRequired
|
||||
void changeSshUserName(String newName, AsyncCallback<VoidResult> callback);
|
||||
void changeUserName(String newName, AsyncCallback<VoidResult> callback);
|
||||
|
||||
@SignInRequired
|
||||
void generatePassword(AccountExternalId.Key key,
|
||||
AsyncCallback<AccountExternalId> callback);
|
||||
|
||||
@SignInRequired
|
||||
void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -46,7 +46,9 @@ public interface AccountConstants extends Constants {
|
||||
String buttonAddSshKey();
|
||||
|
||||
String userName();
|
||||
String password();
|
||||
String buttonChangeUserName();
|
||||
String buttonGeneratePassword();
|
||||
String invalidUserName();
|
||||
|
||||
String sshKeyInvalid();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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.GeneratePassword;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.FactoryModule;
|
||||
@@ -136,6 +137,7 @@ public class WebModule extends FactoryModule {
|
||||
bind(AccountManager.class);
|
||||
bind(ChangeUserName.CurrentUser.class);
|
||||
factory(ChangeUserName.Factory.class);
|
||||
factory(GeneratePassword.Factory.class);
|
||||
|
||||
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
|
||||
HttpRemotePeerProvider.class).in(RequestScoped.class);
|
||||
|
||||
@@ -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.GeneratePassword;
|
||||
import com.google.gerrit.server.account.Realm;
|
||||
import com.google.gerrit.server.config.AuthConfig;
|
||||
import com.google.gerrit.server.contact.ContactStore;
|
||||
@@ -74,6 +75,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
private final AccountManager accountManager;
|
||||
private final boolean useContactInfo;
|
||||
|
||||
private final GeneratePassword.Factory generatePasswordFactory;
|
||||
private final ChangeUserName.CurrentUser changeUserNameFactory;
|
||||
private final DeleteExternalIds.Factory deleteExternalIdsFactory;
|
||||
private final ExternalIdDetailFactory.Factory externalIdDetailFactory;
|
||||
@@ -86,6 +88,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
final RegisterNewEmailSender.Factory esf, final SshKeyCache skc,
|
||||
final AccountByEmailCache abec, final AccountCache uac,
|
||||
final AccountManager am,
|
||||
final GeneratePassword.Factory generatePasswordFactory,
|
||||
final ChangeUserName.CurrentUser changeUserNameFactory,
|
||||
final DeleteExternalIds.Factory deleteExternalIdsFactory,
|
||||
final ExternalIdDetailFactory.Factory externalIdDetailFactory,
|
||||
@@ -103,6 +106,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
|
||||
useContactInfo = contactStore != null && contactStore.isEnabled();
|
||||
|
||||
this.generatePasswordFactory = generatePasswordFactory;
|
||||
this.changeUserNameFactory = changeUserNameFactory;
|
||||
this.deleteExternalIdsFactory = deleteExternalIdsFactory;
|
||||
this.externalIdDetailFactory = externalIdDetailFactory;
|
||||
@@ -164,7 +168,7 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeSshUserName(final String newName,
|
||||
public void changeUserName(final String newName,
|
||||
final AsyncCallback<VoidResult> callback) {
|
||||
if (realm.allowsEdit(Account.FieldName.USER_NAME)) {
|
||||
Handler.wrap(changeUserNameFactory.create(newName)).to(callback);
|
||||
@@ -173,6 +177,12 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void generatePassword(AccountExternalId.Key key,
|
||||
AsyncCallback<AccountExternalId> callback) {
|
||||
Handler.wrap(generatePasswordFactory.create(key)).to(callback);
|
||||
}
|
||||
|
||||
public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
|
||||
externalIdDetailFactory.create().to(callback);
|
||||
}
|
||||
|
||||
@@ -79,6 +79,9 @@ public final class AccountExternalId {
|
||||
@Column(id = 3, notNull = false)
|
||||
protected String emailAddress;
|
||||
|
||||
@Column(id = 4, notNull = false)
|
||||
protected String password;
|
||||
|
||||
/** <i>computed value</i> is this identity trusted by the site administrator? */
|
||||
protected boolean trusted;
|
||||
|
||||
@@ -131,6 +134,14 @@ public final class AccountExternalId {
|
||||
return 0 < c ? id.substring(c + 1) : null;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String p) {
|
||||
password = p;
|
||||
}
|
||||
|
||||
public boolean isTrusted() {
|
||||
return trusted;
|
||||
}
|
||||
|
||||
@@ -100,6 +100,13 @@ public class ChangeUserName implements Callable<VoidResult> {
|
||||
try {
|
||||
final AccountExternalId id =
|
||||
new AccountExternalId(user.getAccountId(), key);
|
||||
|
||||
for (AccountExternalId i : old) {
|
||||
if (i.getPassword() != null) {
|
||||
id.setPassword(i.getPassword());
|
||||
}
|
||||
}
|
||||
|
||||
db.accountExternalIds().insert(Collections.singleton(id));
|
||||
} catch (OrmDuplicateKeyException dupeErr) {
|
||||
// If we are using this identity, don't report the exception.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// 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 org.apache.commons.codec.binary.Base64;
|
||||
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
/** Operation to generate a password for an account. */
|
||||
public class GeneratePassword implements Callable<AccountExternalId> {
|
||||
private static final int LEN = 12;
|
||||
private static final SecureRandom rng;
|
||||
|
||||
static {
|
||||
try {
|
||||
rng = SecureRandom.getInstance("SHA1PRNG");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("Cannot create RNG for password generator", e);
|
||||
}
|
||||
}
|
||||
|
||||
public interface Factory {
|
||||
GeneratePassword create(AccountExternalId.Key forUser);
|
||||
}
|
||||
|
||||
private final AccountCache accountCache;
|
||||
private final ReviewDb db;
|
||||
private final IdentifiedUser user;
|
||||
|
||||
private final AccountExternalId.Key forUser;
|
||||
|
||||
@Inject
|
||||
GeneratePassword(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(generate());
|
||||
db.accountExternalIds().update(Collections.singleton(id));
|
||||
accountCache.evict(user.getAccountId());
|
||||
return id;
|
||||
}
|
||||
|
||||
private String generate() {
|
||||
byte[] rand = new byte[LEN];
|
||||
rng.nextBytes(rand);
|
||||
|
||||
byte[] enc = Base64.encodeBase64(rand, false);
|
||||
StringBuilder r = new StringBuilder(LEN);
|
||||
for (int i = 0; i < LEN; i++) {
|
||||
if (enc[i] == '=') {
|
||||
break;
|
||||
}
|
||||
r.append((char) enc[i]);
|
||||
}
|
||||
return r.toString();
|
||||
}
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import java.util.List;
|
||||
/** A version of the database schema. */
|
||||
public abstract class SchemaVersion {
|
||||
/** The current schema version. */
|
||||
private static final Class<? extends SchemaVersion> C = Schema_23.class;
|
||||
private static final Class<? extends SchemaVersion> C = Schema_24.class;
|
||||
|
||||
public static class Module extends AbstractModule {
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// 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.schema;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
class Schema_24 extends SchemaVersion {
|
||||
@Inject
|
||||
Schema_24(Provider<Schema_23> prior) {
|
||||
super(prior);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user