Line data Source code
1 : // Copyright (C) 2020 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.account; 16 : 17 : import com.google.common.flogger.FluentLogger; 18 : import com.google.gerrit.entities.Account; 19 : import com.google.gerrit.entities.AccountGroup; 20 : import com.google.gerrit.entities.InternalGroup; 21 : import com.google.gerrit.server.IdentifiedUser; 22 : import com.google.inject.AbstractModule; 23 : import com.google.inject.Module; 24 : import com.google.inject.Scopes; 25 : import com.google.inject.Singleton; 26 : import java.util.ArrayList; 27 : import java.util.HashSet; 28 : import java.util.List; 29 : import java.util.Optional; 30 : import java.util.Set; 31 : import javax.inject.Inject; 32 : 33 : /** 34 : * An implementation of {@link ServiceUserClassifier} that will consider a user to be a robot if 35 : * they are a member in the {@code Service Users} group. 36 : */ 37 : @Singleton 38 : public class ServiceUserClassifierImpl implements ServiceUserClassifier { 39 152 : private static final FluentLogger logger = FluentLogger.forEnclosingClass(); 40 : 41 : public static Module module() { 42 152 : return new AbstractModule() { 43 : @Override 44 : protected void configure() { 45 152 : bind(ServiceUserClassifier.class).to(ServiceUserClassifierImpl.class).in(Scopes.SINGLETON); 46 152 : } 47 : }; 48 : } 49 : 50 : private final GroupCache groupCache; 51 : private final InternalGroupBackend internalGroupBackend; 52 : private final IdentifiedUser.GenericFactory identifiedUserFactory; 53 : 54 : @Inject 55 : ServiceUserClassifierImpl( 56 : GroupCache groupCache, 57 : InternalGroupBackend internalGroupBackend, 58 150 : IdentifiedUser.GenericFactory identifiedUserFactory) { 59 150 : this.groupCache = groupCache; 60 150 : this.internalGroupBackend = internalGroupBackend; 61 150 : this.identifiedUserFactory = identifiedUserFactory; 62 150 : } 63 : 64 : @Override 65 : public boolean isServiceUser(Account.Id user) { 66 110 : Optional<InternalGroup> maybeGroup = groupCache.get(AccountGroup.nameKey(SERVICE_USERS)); 67 110 : if (!maybeGroup.isPresent()) { 68 1 : return false; 69 : } 70 110 : List<AccountGroup.UUID> toTraverse = new ArrayList<>(); 71 110 : toTraverse.add(maybeGroup.get().getGroupUUID()); 72 110 : Set<AccountGroup.UUID> seen = new HashSet<>(); 73 110 : while (!toTraverse.isEmpty()) { 74 110 : InternalGroup currentGroup = 75 : groupCache 76 110 : .get(toTraverse.remove(0)) 77 110 : .orElseThrow(() -> new IllegalStateException("invalid subgroup")); 78 110 : if (seen.contains(currentGroup.getGroupUUID())) { 79 1 : logger.atFine().log( 80 1 : "Skipping %s because it's a cyclic subgroup", currentGroup.getGroupUUID()); 81 1 : continue; 82 : } 83 110 : seen.add(currentGroup.getGroupUUID()); 84 110 : if (currentGroup.getMembers().contains(user)) { 85 : // The user is a member of the 'Service Users' group or a subgroup. 86 3 : return true; 87 : } 88 110 : boolean hasExternalSubgroup = 89 110 : currentGroup.getSubgroups().stream().anyMatch(g -> !internalGroupBackend.handles(g)); 90 110 : if (hasExternalSubgroup) { 91 : // 'Service Users or a subgroup of Service User' contains an external subgroup, so we have 92 : // to default to the more expensive evaluation of getting all of the user's group 93 : // memberships. 94 1 : return identifiedUserFactory 95 1 : .create(user) 96 1 : .getEffectiveGroups() 97 1 : .contains(maybeGroup.get().getGroupUUID()); 98 : } 99 110 : toTraverse.addAll(currentGroup.getSubgroups()); 100 110 : } 101 110 : return false; 102 : } 103 : }