Add REST endpoint to set username
This REST endpoint only allows to set the initial username. Once set the username cannot be changed or deleted. Use the new REST endpoint from the UI instead of the old AccountSecurity.changeUserName(...) RPC. The AccountSecurity.changeUserName(...) RPC is removed since it is no longer used. Change-Id: I48f4d7642b551e17ceef7772563a229aa83fc1ad Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
This commit is contained in:
@@ -253,6 +253,30 @@ Retrieves the username of an account.
|
||||
|
||||
If the account does not have a username the response is "`404 Not Found`".
|
||||
|
||||
[[set-username]]
|
||||
=== Set Username
|
||||
--
|
||||
'PUT /accounts/link:#account-id[\{account-id\}]/username'
|
||||
--
|
||||
|
||||
The new username must be provided in the request body inside
|
||||
a link:#username-input[UsernameInput] entity.
|
||||
|
||||
Once set, the username cannot be changed or deleted. If attempted this
|
||||
fails with "`405 Method Not Allowed`".
|
||||
|
||||
.Request
|
||||
----
|
||||
PUT /accounts/self/name HTTP/1.0
|
||||
Content-Type: application/json; charset=UTF-8
|
||||
|
||||
{
|
||||
"username": "jdoe"
|
||||
}
|
||||
----
|
||||
|
||||
As response the new username is returned.
|
||||
|
||||
[[get-active]]
|
||||
=== Get Active
|
||||
--
|
||||
@@ -1690,6 +1714,17 @@ user.
|
||||
|`valid` ||Whether the SSH key is valid.
|
||||
|=============================
|
||||
|
||||
[[username-input]]
|
||||
=== UsernameInput
|
||||
The `UsernameInput` entity contains information for setting the
|
||||
username for an account.
|
||||
|
||||
[options="header",cols="1,6"]
|
||||
|=======================
|
||||
|Field Name |Description
|
||||
|`username` |The new username of the account.
|
||||
|=======================
|
||||
|
||||
|
||||
GERRIT
|
||||
------
|
||||
|
@@ -0,0 +1,82 @@
|
||||
// Copyright (C) 2015 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.acceptance.rest.account;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||
import com.google.gerrit.acceptance.RestResponse;
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.account.PutUsername;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
public class PutUsernameIT extends AbstractDaemonTest {
|
||||
@Inject
|
||||
private SchemaFactory<ReviewDb> reviewDbProvider;
|
||||
|
||||
@Test
|
||||
public void set() throws Exception {
|
||||
PutUsername.Input in = new PutUsername.Input();
|
||||
in.username = "myUsername";
|
||||
RestResponse r =
|
||||
adminSession.put("/accounts/" + createUser().get() + "/username", in);
|
||||
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_OK);
|
||||
assertThat(newGson().fromJson(r.getReader(), String.class)).isEqualTo(
|
||||
in.username);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setExisting_Conflict() throws Exception {
|
||||
PutUsername.Input in = new PutUsername.Input();
|
||||
in.username = admin.username;
|
||||
RestResponse r =
|
||||
adminSession.put("/accounts/" + createUser().get() + "/username", in);
|
||||
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_CONFLICT);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setNew_MethodNotAllowed() throws Exception {
|
||||
PutUsername.Input in = new PutUsername.Input();
|
||||
in.username = "newUsername";
|
||||
RestResponse r =
|
||||
adminSession.put("/accounts/" + admin.username + "/username", in);
|
||||
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void delete_MethodNotAllowed() throws Exception {
|
||||
RestResponse r =
|
||||
adminSession.put("/accounts/" + admin.username + "/username");
|
||||
assertThat(r.getStatusCode()).isEqualTo(HttpStatus.SC_METHOD_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
private Account.Id createUser() throws OrmException {
|
||||
try (ReviewDb db = reviewDbProvider.open()) {
|
||||
Account.Id id = new Account.Id(db.nextAccountId());
|
||||
Account a = new Account(id, TimeUtil.nowTs());
|
||||
db.accounts().insert(Collections.singleton(a));
|
||||
return id;
|
||||
}
|
||||
}
|
||||
}
|
@@ -30,10 +30,6 @@ import java.util.Set;
|
||||
|
||||
@RpcImpl(version = Version.V2_0)
|
||||
public interface AccountSecurity extends RemoteJsonService {
|
||||
@Audit
|
||||
@SignInRequired
|
||||
void changeUserName(String newName, AsyncCallback<VoidResult> callback);
|
||||
|
||||
@SignInRequired
|
||||
void myExternalIds(AsyncCallback<List<AccountExternalId>> callback);
|
||||
|
||||
|
@@ -53,6 +53,14 @@ public class AccountApi {
|
||||
new RestApi("/accounts/").id(account).view("username").get(cb);
|
||||
}
|
||||
|
||||
/** Set the username */
|
||||
public static void setUsername(String account, String username,
|
||||
AsyncCallback<NativeString> cb) {
|
||||
UsernameInput input = UsernameInput.create();
|
||||
input.username(username);
|
||||
new RestApi("/accounts/").id(account).view("username").put(input, cb);
|
||||
}
|
||||
|
||||
/** Retrieve email addresses */
|
||||
public static void getEmails(String account,
|
||||
AsyncCallback<JsArray<EmailInfo>> cb) {
|
||||
@@ -128,4 +136,15 @@ public class AccountApi {
|
||||
protected HttpPasswordInput() {
|
||||
}
|
||||
}
|
||||
|
||||
private static class UsernameInput extends JavaScriptObject {
|
||||
final native void username(String u) /*-{ if(u)this.username=u; }-*/;
|
||||
|
||||
static UsernameInput create() {
|
||||
return createObject().cast();
|
||||
}
|
||||
|
||||
protected UsernameInput() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -19,8 +19,9 @@ import com.google.gerrit.client.ConfirmationDialog;
|
||||
import com.google.gerrit.client.ErrorDialog;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.rpc.NativeString;
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gerrit.client.ui.OnEditEnabler;
|
||||
import com.google.gerrit.common.errors.InvalidUserNameException;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
@@ -34,7 +35,6 @@ 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.gwtexpui.safehtml.client.SafeHtmlBuilder;
|
||||
import com.google.gwtjsonrpc.common.VoidResult;
|
||||
|
||||
class UsernameField extends Composite {
|
||||
private CopyableLabel userNameLbl;
|
||||
@@ -114,27 +114,27 @@ class UsernameField extends Composite {
|
||||
}
|
||||
final String newUserName = newName;
|
||||
|
||||
Util.ACCOUNT_SEC.changeUserName(newUserName,
|
||||
new GerritCallback<VoidResult>() {
|
||||
@Override
|
||||
public void onSuccess(VoidResult result) {
|
||||
Gerrit.getUserAccount().username(newUserName);
|
||||
userNameLbl.setText(newUserName);
|
||||
userNameLbl.setVisible(true);
|
||||
userNameTxt.setVisible(false);
|
||||
setUserName.setVisible(false);
|
||||
}
|
||||
AccountApi.setUsername("self", newUserName,
|
||||
new GerritCallback<NativeString>() {
|
||||
@Override
|
||||
public void onSuccess(NativeString result) {
|
||||
Gerrit.getUserAccount().username(newUserName);
|
||||
userNameLbl.setText(newUserName);
|
||||
userNameLbl.setVisible(true);
|
||||
userNameTxt.setVisible(false);
|
||||
setUserName.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(final Throwable caught) {
|
||||
enableUI(true);
|
||||
if (caught instanceof InvalidUserNameException) {
|
||||
new ErrorDialog(Util.C.invalidUserName()).center();
|
||||
} else {
|
||||
super.onFailure(caught);
|
||||
}
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
enableUI(true);
|
||||
if (RestApi.isExpected(422 /* Unprocessable Entity */)) {
|
||||
new ErrorDialog(Util.C.invalidUserName()).center();
|
||||
} else {
|
||||
super.onFailure(caught);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void enableUI(final boolean on) {
|
||||
|
@@ -21,11 +21,9 @@ import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.common.data.AccountSecurity;
|
||||
import com.google.gerrit.common.data.ContributorAgreement;
|
||||
import com.google.gerrit.common.errors.ContactInformationStoreException;
|
||||
import com.google.gerrit.common.errors.InvalidUserNameException;
|
||||
import com.google.gerrit.common.errors.NoSuchEntityException;
|
||||
import com.google.gerrit.common.errors.PermissionDeniedException;
|
||||
import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
|
||||
import com.google.gerrit.httpd.rpc.Handler;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
@@ -38,7 +36,6 @@ import com.google.gerrit.server.account.AccountByEmailCache;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.AccountException;
|
||||
import com.google.gerrit.server.account.AccountManager;
|
||||
import com.google.gerrit.server.account.ChangeUserName;
|
||||
import com.google.gerrit.server.account.GroupCache;
|
||||
import com.google.gerrit.server.account.Realm;
|
||||
import com.google.gerrit.server.contact.ContactStore;
|
||||
@@ -66,7 +63,6 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
private final AccountManager accountManager;
|
||||
private final boolean useContactInfo;
|
||||
|
||||
private final ChangeUserName.CurrentUser changeUserNameFactory;
|
||||
private final DeleteExternalIds.Factory deleteExternalIdsFactory;
|
||||
private final ExternalIdDetailFactory.Factory externalIdDetailFactory;
|
||||
|
||||
@@ -81,7 +77,6 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
final EmailTokenVerifier etv, final ProjectCache pc,
|
||||
final AccountByEmailCache abec, final AccountCache uac,
|
||||
final AccountManager am,
|
||||
final ChangeUserName.CurrentUser changeUserNameFactory,
|
||||
final DeleteExternalIds.Factory deleteExternalIdsFactory,
|
||||
final ExternalIdDetailFactory.Factory externalIdDetailFactory,
|
||||
final ChangeHooks hooks, final GroupCache groupCache,
|
||||
@@ -99,27 +94,12 @@ class AccountSecurityImpl extends BaseServiceImplementation implements
|
||||
|
||||
useContactInfo = contactStore != null && contactStore.isEnabled();
|
||||
|
||||
this.changeUserNameFactory = changeUserNameFactory;
|
||||
this.deleteExternalIdsFactory = deleteExternalIdsFactory;
|
||||
this.externalIdDetailFactory = externalIdDetailFactory;
|
||||
this.hooks = hooks;
|
||||
this.groupCache = groupCache;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void changeUserName(final String newName,
|
||||
final AsyncCallback<VoidResult> callback) {
|
||||
if (realm.allowsEdit(Account.FieldName.USER_NAME)) {
|
||||
if (newName == null || !newName.matches(Account.USER_NAME_PATTERN)) {
|
||||
callback.onFailure(new InvalidUserNameException());
|
||||
}
|
||||
Handler.wrap(changeUserNameFactory.create(newName)).to(callback);
|
||||
} else {
|
||||
callback.onFailure(
|
||||
new PermissionDeniedException("Not allowed to change username"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void myExternalIds(AsyncCallback<List<AccountExternalId>> callback) {
|
||||
externalIdDetailFactory.create().to(callback);
|
||||
|
@@ -28,7 +28,6 @@ import com.google.gwtjsonrpc.common.VoidResult;
|
||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -39,28 +38,12 @@ import java.util.regex.Pattern;
|
||||
|
||||
/** Operation to change the username of an account. */
|
||||
public class ChangeUserName implements Callable<VoidResult> {
|
||||
public static final String USERNAME_CANNOT_BE_CHANGED =
|
||||
"Username cannot be changed.";
|
||||
|
||||
private static final Pattern USER_NAME_PATTERN =
|
||||
Pattern.compile(Account.USER_NAME_PATTERN);
|
||||
|
||||
/** Factory to change the username for the current user. */
|
||||
public static class CurrentUser {
|
||||
private final Factory factory;
|
||||
private final Provider<ReviewDb> db;
|
||||
private final Provider<IdentifiedUser> user;
|
||||
|
||||
@Inject
|
||||
CurrentUser(Factory factory, Provider<ReviewDb> db,
|
||||
Provider<IdentifiedUser> user) {
|
||||
this.factory = factory;
|
||||
this.db = db;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
public ChangeUserName create(String newUsername) {
|
||||
return factory.create(db.get(), user.get(), newUsername);
|
||||
}
|
||||
}
|
||||
|
||||
/** Generic factory to change any user's username. */
|
||||
public interface Factory {
|
||||
ChangeUserName create(ReviewDb db, IdentifiedUser user, String newUsername);
|
||||
@@ -92,7 +75,7 @@ public class ChangeUserName implements Callable<VoidResult> {
|
||||
InvalidUserNameException {
|
||||
final Collection<AccountExternalId> old = old();
|
||||
if (!old.isEmpty()) {
|
||||
throw new IllegalStateException("Username cannot be changed.");
|
||||
throw new IllegalStateException(USERNAME_CANNOT_BE_CHANGED);
|
||||
}
|
||||
|
||||
if (newUsername != null && !newUsername.isEmpty()) {
|
||||
|
@@ -43,6 +43,7 @@ public class Module extends RestApiModule {
|
||||
put(ACCOUNT_KIND, "name").to(PutName.class);
|
||||
delete(ACCOUNT_KIND, "name").to(PutName.class);
|
||||
get(ACCOUNT_KIND, "username").to(GetUsername.class);
|
||||
put(ACCOUNT_KIND, "username").to(PutUsername.class);
|
||||
get(ACCOUNT_KIND, "active").to(GetActive.class);
|
||||
put(ACCOUNT_KIND, "active").to(PutActive.class);
|
||||
delete(ACCOUNT_KIND, "active").to(DeleteActive.class);
|
||||
|
@@ -0,0 +1,90 @@
|
||||
// Copyright (C) 2015 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.InvalidUserNameException;
|
||||
import com.google.gerrit.common.errors.NameAlreadyUsedException;
|
||||
import com.google.gerrit.extensions.restapi.AuthException;
|
||||
import com.google.gerrit.extensions.restapi.DefaultInput;
|
||||
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
|
||||
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||
import com.google.gerrit.extensions.restapi.RestModifyView;
|
||||
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.account.PutUsername.Input;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class PutUsername implements RestModifyView<AccountResource, Input> {
|
||||
public static class Input {
|
||||
@DefaultInput
|
||||
public String username;
|
||||
}
|
||||
|
||||
private final Provider<CurrentUser> self;
|
||||
private final ChangeUserName.Factory changeUserNameFactory;
|
||||
private final Realm realm;
|
||||
private final Provider<ReviewDb> db;
|
||||
|
||||
@Inject
|
||||
PutUsername(Provider<CurrentUser> self,
|
||||
ChangeUserName.Factory changeUserNameFactory,
|
||||
Realm realm,
|
||||
Provider<ReviewDb> db) {
|
||||
this.self = self;
|
||||
this.changeUserNameFactory = changeUserNameFactory;
|
||||
this.realm = realm;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String apply(AccountResource rsrc, Input input) throws AuthException,
|
||||
MethodNotAllowedException, UnprocessableEntityException,
|
||||
ResourceConflictException, OrmException {
|
||||
if (self.get() != rsrc.getUser()
|
||||
&& !self.get().getCapabilities().canAdministrateServer()) {
|
||||
throw new AuthException("not allowed to set username");
|
||||
}
|
||||
|
||||
if (!realm.allowsEdit(Account.FieldName.USER_NAME)) {
|
||||
throw new MethodNotAllowedException("realm does not allow editing username");
|
||||
}
|
||||
|
||||
if (input == null) {
|
||||
input = new Input();
|
||||
}
|
||||
|
||||
try {
|
||||
changeUserNameFactory.create(db.get(), rsrc.getUser(), input.username).call();
|
||||
} catch (IllegalStateException e) {
|
||||
if (ChangeUserName.USERNAME_CANNOT_BE_CHANGED.equals(e.getMessage())) {
|
||||
throw new MethodNotAllowedException(e.getMessage());
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
} catch (InvalidUserNameException e) {
|
||||
throw new UnprocessableEntityException("invalid username");
|
||||
} catch (NameAlreadyUsedException e) {
|
||||
throw new ResourceConflictException("username already used");
|
||||
}
|
||||
|
||||
return input.username;
|
||||
}
|
||||
}
|
@@ -302,7 +302,6 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
factory(SubmoduleSectionParser.Factory.class);
|
||||
|
||||
bind(AccountManager.class);
|
||||
bind(ChangeUserName.CurrentUser.class);
|
||||
factory(ChangeUserName.Factory.class);
|
||||
|
||||
bind(new TypeLiteral<List<CommentLinkInfo>>() {})
|
||||
|
Reference in New Issue
Block a user