Account REST API: Add a new endpoint to delete external IDs
The REST endpoint is added as:
'POST /accounts/{account-id}/external.ids:delete'
The target external ids must be provided in the request body.
Change-Id: Ieef322d6bddc1f3659536081b3f3514474b7162a
This commit is contained in:
@@ -1707,6 +1707,32 @@ link:#account-external-id-info[AccountExternalIdInfo] entities.
|
|||||||
]
|
]
|
||||||
----
|
----
|
||||||
|
|
||||||
|
[[delete-account-external-ids]]
|
||||||
|
=== Delete Account External IDs
|
||||||
|
--
|
||||||
|
'POST /accounts/link:#account-id[\{account-id\}]/external.ids:delete'
|
||||||
|
--
|
||||||
|
|
||||||
|
Delete a list of external ids for a user account. The target external ids must
|
||||||
|
be provided as a list in the request body.
|
||||||
|
|
||||||
|
Only external ids belonging to the caller may be deleted.
|
||||||
|
|
||||||
|
.Request
|
||||||
|
----
|
||||||
|
POST /a/accounts/self/external.ids:delete HTTP/1.0
|
||||||
|
Content-Type: application/json;charset=UTF-8
|
||||||
|
|
||||||
|
{
|
||||||
|
"mailto:john.doe@example.com"
|
||||||
|
}
|
||||||
|
----
|
||||||
|
|
||||||
|
.Response
|
||||||
|
----
|
||||||
|
HTTP/1.1 204 No Content
|
||||||
|
----
|
||||||
|
|
||||||
[[default-star-endpoints]]
|
[[default-star-endpoints]]
|
||||||
== Default Star Endpoints
|
== Default Star Endpoints
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
|
|
||||||
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
import com.google.gerrit.acceptance.AbstractDaemonTest;
|
||||||
import com.google.gerrit.acceptance.RestResponse;
|
import com.google.gerrit.acceptance.RestResponse;
|
||||||
|
import com.google.gerrit.acceptance.Sandboxed;
|
||||||
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
|
import com.google.gerrit.extensions.common.AccountExternalIdInfo;
|
||||||
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
import com.google.gerrit.reviewdb.client.AccountExternalId;
|
||||||
import com.google.gson.reflect.TypeToken;
|
import com.google.gson.reflect.TypeToken;
|
||||||
@@ -29,6 +30,7 @@ import java.util.Collection;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@Sandboxed
|
||||||
public class ExternalIdIT extends AbstractDaemonTest {
|
public class ExternalIdIT extends AbstractDaemonTest {
|
||||||
@Test
|
@Test
|
||||||
public void getExternalIDs() throws Exception {
|
public void getExternalIDs() throws Exception {
|
||||||
@@ -54,6 +56,59 @@ public class ExternalIdIT extends AbstractDaemonTest {
|
|||||||
assertThat(results).containsExactlyElementsIn(expectedIdInfos);
|
assertThat(results).containsExactlyElementsIn(expectedIdInfos);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteExternalIDs() throws Exception {
|
||||||
|
setApiUser(user);
|
||||||
|
List<AccountExternalIdInfo> externalIds =
|
||||||
|
gApi.accounts().self().getExternalIds();
|
||||||
|
|
||||||
|
List<String> toDelete = new ArrayList<>();
|
||||||
|
List<AccountExternalIdInfo> expectedIds = new ArrayList<>();
|
||||||
|
for (AccountExternalIdInfo id : externalIds) {
|
||||||
|
if (id.canDelete != null && id.canDelete) {
|
||||||
|
toDelete.add(id.identity);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
expectedIds.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
assertThat(toDelete).hasSize(1);
|
||||||
|
|
||||||
|
RestResponse response = userRestSession.post(
|
||||||
|
"/accounts/self/external.ids:delete", toDelete);
|
||||||
|
response.assertNoContent();
|
||||||
|
List<AccountExternalIdInfo> results =
|
||||||
|
gApi.accounts().self().getExternalIds();
|
||||||
|
// The external ID in WebSession will not be set for tests, resulting that
|
||||||
|
// "mailto:user@example.com" can be deleted while "username:user" can't.
|
||||||
|
assertThat(results).hasSize(1);
|
||||||
|
assertThat(results).containsExactlyElementsIn(expectedIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteExternalIDs_Conflict() throws Exception {
|
||||||
|
List<String> toDelete = new ArrayList<>();
|
||||||
|
String externalIdStr = "username:" + user.username;
|
||||||
|
toDelete.add(externalIdStr);
|
||||||
|
RestResponse response = userRestSession.post(
|
||||||
|
"/accounts/self/external.ids:delete", toDelete);
|
||||||
|
response.assertConflict();
|
||||||
|
assertThat(response.getEntityContent()).isEqualTo(
|
||||||
|
String.format("External id %s cannot be deleted", externalIdStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void deleteExternalIDs_UnprocessableEntity() throws Exception {
|
||||||
|
List<String> toDelete = new ArrayList<>();
|
||||||
|
String externalIdStr = "mailto:user@domain.com";
|
||||||
|
toDelete.add(externalIdStr);
|
||||||
|
RestResponse response = userRestSession.post(
|
||||||
|
"/accounts/self/external.ids:delete", toDelete);
|
||||||
|
response.assertUnprocessableEntity();
|
||||||
|
assertThat(response.getEntityContent()).isEqualTo(
|
||||||
|
String.format("External id %s does not exist", externalIdStr));
|
||||||
|
}
|
||||||
|
|
||||||
private static AccountExternalIdInfo toInfo(AccountExternalId id) {
|
private static AccountExternalIdInfo toInfo(AccountExternalId id) {
|
||||||
AccountExternalIdInfo info = new AccountExternalIdInfo();
|
AccountExternalIdInfo info = new AccountExternalIdInfo();
|
||||||
info.identity = id.getExternalId();
|
info.identity = id.getExternalId();
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ public interface AccountApi {
|
|||||||
void index() throws RestApiException;
|
void index() throws RestApiException;
|
||||||
|
|
||||||
List<AccountExternalIdInfo> getExternalIds() throws RestApiException;
|
List<AccountExternalIdInfo> getExternalIds() throws RestApiException;
|
||||||
|
void deleteExternalIds(List<String> externalIds) throws RestApiException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A default implementation which allows source compatibility
|
* A default implementation which allows source compatibility
|
||||||
@@ -233,5 +234,10 @@ public interface AccountApi {
|
|||||||
public List<AccountExternalIdInfo> getExternalIds() {
|
public List<AccountExternalIdInfo> getExternalIds() {
|
||||||
throw new NotImplementedException();
|
throw new NotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteExternalIds(List<String> externalIds) {
|
||||||
|
throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
// Copyright (C) 2017 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 static com.google.gerrit.reviewdb.client.AccountExternalId.SCHEME_USERNAME;
|
||||||
|
|
||||||
|
import com.google.gerrit.extensions.restapi.AuthException;
|
||||||
|
import com.google.gerrit.extensions.restapi.BadRequestException;
|
||||||
|
import com.google.gerrit.extensions.restapi.ResourceConflictException;
|
||||||
|
import com.google.gerrit.extensions.restapi.Response;
|
||||||
|
import com.google.gerrit.extensions.restapi.RestApiException;
|
||||||
|
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.client.AccountExternalId;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.CurrentUser;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class DeleteExternalIds implements
|
||||||
|
RestModifyView<AccountResource, List<String>> {
|
||||||
|
private final AccountByEmailCache accountByEmailCache;
|
||||||
|
private final AccountCache accountCache;
|
||||||
|
private final ExternalIdCache externalIdCache;
|
||||||
|
private final Provider<CurrentUser> self;
|
||||||
|
private final Provider<ReviewDb> dbProvider;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
DeleteExternalIds(
|
||||||
|
AccountByEmailCache accountByEmailCache,
|
||||||
|
AccountCache accountCache,
|
||||||
|
ExternalIdCache externalIdCache,
|
||||||
|
Provider<CurrentUser> self,
|
||||||
|
Provider<ReviewDb> dbProvider) {
|
||||||
|
this.accountByEmailCache = accountByEmailCache;
|
||||||
|
this.accountCache = accountCache;
|
||||||
|
this.externalIdCache = externalIdCache;
|
||||||
|
this.self = self;
|
||||||
|
this.dbProvider = dbProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response<?> apply(AccountResource resource, List<String> externalIds)
|
||||||
|
throws RestApiException, IOException, OrmException {
|
||||||
|
if (self.get() != resource.getUser()) {
|
||||||
|
throw new AuthException("not allowed to delete external IDs");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (externalIds == null || externalIds.size() == 0) {
|
||||||
|
throw new BadRequestException("external IDs are required");
|
||||||
|
}
|
||||||
|
|
||||||
|
Account.Id accountId = resource.getUser().getAccountId();
|
||||||
|
Map<AccountExternalId.Key, AccountExternalId> externalIdMap =
|
||||||
|
externalIdCache.byAccount(resource.getUser().getAccountId())
|
||||||
|
.stream().collect(Collectors.toMap(i -> i.getKey(), i -> i));
|
||||||
|
|
||||||
|
List<AccountExternalId> toDelete = new ArrayList<>();
|
||||||
|
AccountExternalId.Key last = resource.getUser().getLastLoginExternalIdKey();
|
||||||
|
for (String externalIdStr : externalIds) {
|
||||||
|
AccountExternalId id = externalIdMap.get(
|
||||||
|
new AccountExternalId.Key(externalIdStr));
|
||||||
|
|
||||||
|
if (id == null) {
|
||||||
|
throw new UnprocessableEntityException(String.format(
|
||||||
|
"External id %s does not exist", externalIdStr));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!id.isScheme(SCHEME_USERNAME))
|
||||||
|
&& ((last == null) || (!last.get().equals(id.getExternalId())))) {
|
||||||
|
toDelete.add(id);
|
||||||
|
} else {
|
||||||
|
throw new ResourceConflictException(String.format(
|
||||||
|
"External id %s cannot be deleted", externalIdStr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!toDelete.isEmpty()) {
|
||||||
|
dbProvider.get().accountExternalIds().delete(toDelete);
|
||||||
|
externalIdCache.onRemove(toDelete);
|
||||||
|
accountCache.evict(accountId);
|
||||||
|
for (AccountExternalId e : toDelete) {
|
||||||
|
accountByEmailCache.evict(e.getEmailAddress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Response.none();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -96,6 +96,7 @@ public class Module extends RestApiModule {
|
|||||||
post(STAR_KIND).to(Stars.Post.class);
|
post(STAR_KIND).to(Stars.Post.class);
|
||||||
|
|
||||||
get(ACCOUNT_KIND, "external.ids").to(GetExternalIds.class);
|
get(ACCOUNT_KIND, "external.ids").to(GetExternalIds.class);
|
||||||
|
post(ACCOUNT_KIND, "external.ids:delete").to(DeleteExternalIds.class);
|
||||||
|
|
||||||
factory(CreateAccount.Factory.class);
|
factory(CreateAccount.Factory.class);
|
||||||
factory(CreateEmail.Factory.class);
|
factory(CreateEmail.Factory.class);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import com.google.gerrit.server.account.AccountResource;
|
|||||||
import com.google.gerrit.server.account.AddSshKey;
|
import com.google.gerrit.server.account.AddSshKey;
|
||||||
import com.google.gerrit.server.account.CreateEmail;
|
import com.google.gerrit.server.account.CreateEmail;
|
||||||
import com.google.gerrit.server.account.DeleteActive;
|
import com.google.gerrit.server.account.DeleteActive;
|
||||||
|
import com.google.gerrit.server.account.DeleteExternalIds;
|
||||||
import com.google.gerrit.server.account.DeleteSshKey;
|
import com.google.gerrit.server.account.DeleteSshKey;
|
||||||
import com.google.gerrit.server.account.DeleteWatchedProjects;
|
import com.google.gerrit.server.account.DeleteWatchedProjects;
|
||||||
import com.google.gerrit.server.account.GetActive;
|
import com.google.gerrit.server.account.GetActive;
|
||||||
@@ -113,6 +114,7 @@ public class AccountApiImpl implements AccountApi {
|
|||||||
private final DeleteActive deleteActive;
|
private final DeleteActive deleteActive;
|
||||||
private final Index index;
|
private final Index index;
|
||||||
private final GetExternalIds getExternalIds;
|
private final GetExternalIds getExternalIds;
|
||||||
|
private final DeleteExternalIds deleteExternalIds;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
AccountApiImpl(AccountLoader.Factory ailf,
|
AccountApiImpl(AccountLoader.Factory ailf,
|
||||||
@@ -145,6 +147,7 @@ public class AccountApiImpl implements AccountApi {
|
|||||||
DeleteActive deleteActive,
|
DeleteActive deleteActive,
|
||||||
Index index,
|
Index index,
|
||||||
GetExternalIds getExternalIds,
|
GetExternalIds getExternalIds,
|
||||||
|
DeleteExternalIds deleteExternalIds,
|
||||||
@Assisted AccountResource account) {
|
@Assisted AccountResource account) {
|
||||||
this.account = account;
|
this.account = account;
|
||||||
this.accountLoaderFactory = ailf;
|
this.accountLoaderFactory = ailf;
|
||||||
@@ -177,6 +180,7 @@ public class AccountApiImpl implements AccountApi {
|
|||||||
this.deleteActive = deleteActive;
|
this.deleteActive = deleteActive;
|
||||||
this.index = index;
|
this.index = index;
|
||||||
this.getExternalIds = getExternalIds;
|
this.getExternalIds = getExternalIds;
|
||||||
|
this.deleteExternalIds = deleteExternalIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -457,4 +461,14 @@ public class AccountApiImpl implements AccountApi {
|
|||||||
public List<AccountExternalIdInfo> getExternalIds() throws RestApiException {
|
public List<AccountExternalIdInfo> getExternalIds() throws RestApiException {
|
||||||
return getExternalIds.apply(account);
|
return getExternalIds.apply(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteExternalIds(List<String> externalIds)
|
||||||
|
throws RestApiException {
|
||||||
|
try {
|
||||||
|
deleteExternalIds.apply(account, externalIds);
|
||||||
|
} catch (IOException | OrmException e) {
|
||||||
|
throw new RestApiException("Cannot delete external IDs", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user