Line data Source code
1 : // Copyright (C) 2017 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.restapi.account; 16 : 17 : import static com.google.gerrit.server.account.externalids.ExternalId.SCHEME_USERNAME; 18 : import static java.util.stream.Collectors.toMap; 19 : import static java.util.stream.Collectors.toSet; 20 : 21 : import com.google.common.collect.ImmutableSet; 22 : import com.google.gerrit.extensions.restapi.AuthException; 23 : import com.google.gerrit.extensions.restapi.BadRequestException; 24 : import com.google.gerrit.extensions.restapi.ResourceConflictException; 25 : import com.google.gerrit.extensions.restapi.Response; 26 : import com.google.gerrit.extensions.restapi.RestApiException; 27 : import com.google.gerrit.extensions.restapi.RestModifyView; 28 : import com.google.gerrit.extensions.restapi.UnprocessableEntityException; 29 : import com.google.gerrit.server.CurrentUser; 30 : import com.google.gerrit.server.account.AccountException; 31 : import com.google.gerrit.server.account.AccountManager; 32 : import com.google.gerrit.server.account.AccountResource; 33 : import com.google.gerrit.server.account.externalids.ExternalId; 34 : import com.google.gerrit.server.account.externalids.ExternalIdKeyFactory; 35 : import com.google.gerrit.server.account.externalids.ExternalIds; 36 : import com.google.gerrit.server.permissions.GlobalPermission; 37 : import com.google.gerrit.server.permissions.PermissionBackend; 38 : import com.google.gerrit.server.permissions.PermissionBackendException; 39 : import com.google.inject.Inject; 40 : import com.google.inject.Provider; 41 : import com.google.inject.Singleton; 42 : import java.io.IOException; 43 : import java.util.ArrayList; 44 : import java.util.List; 45 : import java.util.Map; 46 : import java.util.Optional; 47 : import java.util.function.Function; 48 : import org.eclipse.jgit.errors.ConfigInvalidException; 49 : 50 : /** 51 : * REST endpoint to delete external IDs from an account. 52 : * 53 : * <p>This REST endpoint handles {@code POST /accounts/<account-identifier>/external.ids:delete} 54 : * requests. 55 : */ 56 : @Singleton 57 : public class DeleteExternalIds implements RestModifyView<AccountResource, List<String>> { 58 : private final PermissionBackend permissionBackend; 59 : private final AccountManager accountManager; 60 : private final ExternalIds externalIds; 61 : private final Provider<CurrentUser> self; 62 : private final ExternalIdKeyFactory externalIdKeyFactory; 63 : 64 : @Inject 65 : DeleteExternalIds( 66 : PermissionBackend permissionBackend, 67 : AccountManager accountManager, 68 : ExternalIds externalIds, 69 : Provider<CurrentUser> self, 70 148 : ExternalIdKeyFactory externalIdKeyFactory) { 71 148 : this.permissionBackend = permissionBackend; 72 148 : this.accountManager = accountManager; 73 148 : this.externalIds = externalIds; 74 148 : this.self = self; 75 148 : this.externalIdKeyFactory = externalIdKeyFactory; 76 148 : } 77 : 78 : @Override 79 : public Response<?> apply(AccountResource resource, List<String> extIds) 80 : throws RestApiException, IOException, ConfigInvalidException, PermissionBackendException { 81 3 : if (!self.get().hasSameAccountId(resource.getUser())) { 82 2 : permissionBackend.currentUser().check(GlobalPermission.MODIFY_ACCOUNT); 83 : } 84 : 85 3 : if (extIds == null || extIds.isEmpty()) { 86 0 : throw new BadRequestException("external IDs are required"); 87 : } 88 : 89 3 : Map<ExternalId.Key, ExternalId> externalIdMap = 90 3 : externalIds.byAccount(resource.getUser().getAccountId()).stream() 91 3 : .collect(toMap(ExternalId::key, Function.identity())); 92 : 93 3 : List<ExternalId> toDelete = new ArrayList<>(); 94 3 : Optional<ExternalId.Key> last = resource.getUser().getLastLoginExternalIdKey(); 95 3 : for (String externalIdStr : extIds) { 96 3 : ExternalId id = externalIdMap.get(externalIdKeyFactory.parse(externalIdStr)); 97 : 98 3 : if (id == null) { 99 1 : throw new UnprocessableEntityException( 100 1 : String.format("External id %s does not exist", externalIdStr)); 101 : } 102 : 103 3 : if (!last.isPresent() || !last.get().equals(id.key())) { 104 3 : if (id.isScheme(SCHEME_USERNAME)) { 105 2 : if (self.get().hasSameAccountId(resource.getUser())) { 106 1 : throw new AuthException("User cannot delete its own externalId in 'username:' scheme"); 107 : } 108 2 : permissionBackend 109 2 : .currentUser() 110 2 : .checkAny( 111 2 : ImmutableSet.of( 112 : GlobalPermission.ADMINISTRATE_SERVER, GlobalPermission.MAINTAIN_SERVER)); 113 : } 114 3 : toDelete.add(id); 115 : } else { 116 0 : throw new ResourceConflictException( 117 0 : String.format("External id %s cannot be deleted", externalIdStr)); 118 : } 119 3 : } 120 : 121 : try { 122 3 : accountManager.unlink( 123 3 : resource.getUser().getAccountId(), 124 3 : toDelete.stream().map(ExternalId::key).collect(toSet())); 125 0 : } catch (AccountException e) { 126 0 : throw new ResourceConflictException(e.getMessage()); 127 3 : } 128 : 129 3 : return Response.none(); 130 : } 131 : }