LCOV - code coverage report
Current view: top level - server/restapi/group - AddMembers.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 62 86 72.1 %
Date: 2022-11-19 15:00:39 Functions: 13 14 92.9 %

          Line data    Source code
       1             : // Copyright (C) 2013 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.group;
      16             : 
      17             : import com.google.common.base.Strings;
      18             : import com.google.common.collect.Lists;
      19             : import com.google.common.collect.Sets;
      20             : import com.google.gerrit.entities.Account;
      21             : import com.google.gerrit.entities.AccountGroup;
      22             : import com.google.gerrit.entities.GroupDescription;
      23             : import com.google.gerrit.exceptions.NoSuchGroupException;
      24             : import com.google.gerrit.extensions.client.AuthType;
      25             : import com.google.gerrit.extensions.common.AccountInfo;
      26             : import com.google.gerrit.extensions.restapi.AuthException;
      27             : import com.google.gerrit.extensions.restapi.DefaultInput;
      28             : import com.google.gerrit.extensions.restapi.IdString;
      29             : import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
      30             : import com.google.gerrit.extensions.restapi.Response;
      31             : import com.google.gerrit.extensions.restapi.RestApiException;
      32             : import com.google.gerrit.extensions.restapi.RestCollectionCreateView;
      33             : import com.google.gerrit.extensions.restapi.RestModifyView;
      34             : import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
      35             : import com.google.gerrit.server.UserInitiated;
      36             : import com.google.gerrit.server.account.AccountCache;
      37             : import com.google.gerrit.server.account.AccountException;
      38             : import com.google.gerrit.server.account.AccountLoader;
      39             : import com.google.gerrit.server.account.AccountManager;
      40             : import com.google.gerrit.server.account.AccountResolver;
      41             : import com.google.gerrit.server.account.AccountResolver.UnresolvableAccountException;
      42             : import com.google.gerrit.server.account.AccountState;
      43             : import com.google.gerrit.server.account.AuthRequest;
      44             : import com.google.gerrit.server.account.GroupControl;
      45             : import com.google.gerrit.server.account.externalids.ExternalId;
      46             : import com.google.gerrit.server.config.AuthConfig;
      47             : import com.google.gerrit.server.group.GroupResource;
      48             : import com.google.gerrit.server.group.MemberResource;
      49             : import com.google.gerrit.server.group.db.GroupDelta;
      50             : import com.google.gerrit.server.group.db.GroupsUpdate;
      51             : import com.google.gerrit.server.permissions.PermissionBackendException;
      52             : import com.google.gerrit.server.restapi.group.AddMembers.Input;
      53             : import com.google.inject.Inject;
      54             : import com.google.inject.Provider;
      55             : import com.google.inject.Singleton;
      56             : import java.io.IOException;
      57             : import java.util.ArrayList;
      58             : import java.util.LinkedHashSet;
      59             : import java.util.List;
      60             : import java.util.Optional;
      61             : import java.util.Set;
      62             : import org.eclipse.jgit.errors.ConfigInvalidException;
      63             : 
      64             : @Singleton
      65             : public class AddMembers implements RestModifyView<GroupResource, Input> {
      66          13 :   public static class Input {
      67             :     @DefaultInput String _oneMember;
      68             : 
      69             :     List<String> members;
      70             : 
      71             :     public static Input fromMembers(List<String> members) {
      72          12 :       Input in = new Input();
      73          12 :       in.members = members;
      74          12 :       return in;
      75             :     }
      76             : 
      77             :     static Input init(Input in) {
      78          13 :       if (in == null) {
      79           0 :         in = new Input();
      80             :       }
      81          13 :       if (in.members == null) {
      82           2 :         in.members = Lists.newArrayListWithCapacity(1);
      83             :       }
      84          13 :       if (!Strings.isNullOrEmpty(in._oneMember)) {
      85           2 :         in.members.add(in._oneMember);
      86             :       }
      87          13 :       return in;
      88             :     }
      89             :   }
      90             : 
      91             :   private final AccountManager accountManager;
      92             :   private final AuthType authType;
      93             :   private final AccountResolver accountResolver;
      94             :   private final AccountCache accountCache;
      95             :   private final AccountLoader.Factory infoFactory;
      96             :   private final Provider<GroupsUpdate> groupsUpdateProvider;
      97             :   private final AuthRequest.Factory authRequestFactory;
      98             : 
      99             :   @Inject
     100             :   AddMembers(
     101             :       AccountManager accountManager,
     102             :       AuthConfig authConfig,
     103             :       AccountResolver accountResolver,
     104             :       AccountCache accountCache,
     105             :       AccountLoader.Factory infoFactory,
     106             :       @UserInitiated Provider<GroupsUpdate> groupsUpdateProvider,
     107         149 :       AuthRequest.Factory authRequestFactory) {
     108         149 :     this.accountManager = accountManager;
     109         149 :     this.authType = authConfig.getAuthType();
     110         149 :     this.accountResolver = accountResolver;
     111         149 :     this.accountCache = accountCache;
     112         149 :     this.infoFactory = infoFactory;
     113         149 :     this.groupsUpdateProvider = groupsUpdateProvider;
     114         149 :     this.authRequestFactory = authRequestFactory;
     115         149 :   }
     116             : 
     117             :   @Override
     118             :   public Response<List<AccountInfo>> apply(GroupResource resource, Input input)
     119             :       throws AuthException, NotInternalGroupException, UnprocessableEntityException, IOException,
     120             :           ConfigInvalidException, ResourceNotFoundException, PermissionBackendException {
     121          13 :     GroupDescription.Internal internalGroup =
     122          13 :         resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
     123          13 :     input = Input.init(input);
     124             : 
     125          13 :     GroupControl control = resource.getControl();
     126          13 :     if (!control.canAddMember()) {
     127           0 :       throw new AuthException("Cannot add members to group " + internalGroup.getName());
     128             :     }
     129             : 
     130          13 :     Set<Account.Id> newMemberIds = new LinkedHashSet<>();
     131          13 :     for (String nameOrEmailOrId : input.members) {
     132          12 :       Account a = findAccount(nameOrEmailOrId);
     133          12 :       if (!a.isActive()) {
     134           0 :         throw new UnprocessableEntityException(
     135           0 :             String.format("Account Inactive: %s", nameOrEmailOrId));
     136             :       }
     137          12 :       newMemberIds.add(a.id());
     138          12 :     }
     139             : 
     140          12 :     AccountGroup.UUID groupUuid = internalGroup.getGroupUUID();
     141             :     try {
     142          12 :       addMembers(groupUuid, newMemberIds);
     143           0 :     } catch (NoSuchGroupException e) {
     144           0 :       throw new ResourceNotFoundException(String.format("Group %s not found", groupUuid), e);
     145          12 :     }
     146          12 :     return Response.ok(toAccountInfoList(newMemberIds));
     147             :   }
     148             : 
     149             :   Account findAccount(String nameOrEmailOrId)
     150             :       throws UnprocessableEntityException, IOException, ConfigInvalidException {
     151          27 :     AccountResolver.Result result = accountResolver.resolve(nameOrEmailOrId);
     152             :     try {
     153          26 :       return result.asUnique().account();
     154           2 :     } catch (UnresolvableAccountException e) {
     155           2 :       switch (authType) {
     156             :         case HTTP_LDAP:
     157             :         case CLIENT_SSL_CERT_LDAP:
     158             :         case LDAP:
     159           0 :           if (!e.isSelf() && result.asList().isEmpty()) {
     160             :             // Account does not exist, try to create it. This may leak account existence, since we
     161             :             // can't distinguish between a nonexistent account and one that the caller can't see.
     162           0 :             Optional<Account> a = createAccountByLdap(nameOrEmailOrId);
     163           0 :             if (a.isPresent()) {
     164           0 :               return a.get();
     165             :             }
     166           0 :           }
     167             :           break;
     168             :         case CUSTOM_EXTENSION:
     169             :         case DEVELOPMENT_BECOME_ANY_ACCOUNT:
     170             :         case HTTP:
     171             :         case LDAP_BIND:
     172             :         case OAUTH:
     173             :         case OPENID:
     174             :         case OPENID_SSO:
     175             :         default:
     176             :       }
     177           2 :       throw e;
     178             :     }
     179             :   }
     180             : 
     181             :   public void addMembers(AccountGroup.UUID groupUuid, Set<Account.Id> newMemberIds)
     182             :       throws IOException, NoSuchGroupException, ConfigInvalidException {
     183             :     GroupDelta groupDelta =
     184          12 :         GroupDelta.builder()
     185          12 :             .setMemberModification(memberIds -> Sets.union(memberIds, newMemberIds))
     186          12 :             .build();
     187          12 :     groupsUpdateProvider.get().updateGroup(groupUuid, groupDelta);
     188          12 :   }
     189             : 
     190             :   private Optional<Account> createAccountByLdap(String user) throws IOException {
     191           0 :     if (!ExternalId.isValidUsername(user)) {
     192           0 :       return Optional.empty();
     193             :     }
     194             : 
     195             :     try {
     196           0 :       AuthRequest req = authRequestFactory.createForUser(user);
     197           0 :       req.setSkipAuthentication(true);
     198           0 :       return accountCache
     199           0 :           .get(accountManager.authenticate(req).getAccountId())
     200           0 :           .map(AccountState::account);
     201           0 :     } catch (AccountException e) {
     202           0 :       return Optional.empty();
     203             :     }
     204             :   }
     205             : 
     206             :   private List<AccountInfo> toAccountInfoList(Set<Account.Id> accountIds)
     207             :       throws PermissionBackendException {
     208          12 :     List<AccountInfo> result = new ArrayList<>();
     209          12 :     AccountLoader loader = infoFactory.create(true);
     210          12 :     for (Account.Id accId : accountIds) {
     211          12 :       result.add(loader.get(accId));
     212          12 :     }
     213          12 :     loader.fill();
     214          12 :     return result;
     215             :   }
     216             : 
     217             :   @Singleton
     218             :   public static class CreateMember
     219             :       implements RestCollectionCreateView<GroupResource, MemberResource, Input> {
     220             :     private final AddMembers put;
     221             : 
     222             :     @Inject
     223         138 :     public CreateMember(AddMembers put) {
     224         138 :       this.put = put;
     225         138 :     }
     226             : 
     227             :     @Override
     228             :     public Response<AccountInfo> apply(GroupResource resource, IdString id, Input input)
     229             :         throws RestApiException, NotInternalGroupException, IOException, ConfigInvalidException,
     230             :             PermissionBackendException {
     231           1 :       AddMembers.Input in = new AddMembers.Input();
     232           1 :       in._oneMember = id.get();
     233             :       try {
     234           0 :         List<AccountInfo> list = put.apply(resource, in).value();
     235           0 :         if (list.size() == 1) {
     236           0 :           return Response.created(list.get(0));
     237             :         }
     238           0 :         throw new IllegalStateException();
     239           1 :       } catch (UnprocessableEntityException e) {
     240           1 :         throw new ResourceNotFoundException(id, e);
     241             :       }
     242             :     }
     243             :   }
     244             : 
     245             :   @Singleton
     246             :   public static class UpdateMember implements RestModifyView<MemberResource, Input> {
     247             :     private final GetMember get;
     248             : 
     249             :     @Inject
     250         138 :     public UpdateMember(GetMember get) {
     251         138 :       this.get = get;
     252         138 :     }
     253             : 
     254             :     @Override
     255             :     public Response<AccountInfo> apply(MemberResource resource, Input input)
     256             :         throws PermissionBackendException {
     257             :       // Do nothing, the user is already a member.
     258           1 :       return get.apply(resource);
     259             :     }
     260             :   }
     261             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750