Merge "Add REST endpoint to set username"
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