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

@@ -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);

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;

View File

@@ -29,6 +29,7 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.RemotePeer;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.account.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);

View File

@@ -37,6 +37,7 @@ import com.google.gerrit.server.account.AccountException;
import com.google.gerrit.server.account.AccountManager;
import com.google.gerrit.server.account.AuthRequest;
import com.google.gerrit.server.account.ChangeUserName;
import com.google.gerrit.server.account.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);
}

View File

@@ -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;
}

View File

@@ -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.

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -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);
}
}