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 static com.google.common.base.Preconditions.checkState; 18 : import static com.google.common.collect.ImmutableSet.toImmutableSet; 19 : 20 : import com.google.common.collect.ImmutableList; 21 : import com.google.common.collect.ImmutableSet; 22 : import com.google.common.collect.Sets; 23 : import com.google.gerrit.entities.Account; 24 : import com.google.gerrit.entities.AccountGroup; 25 : import com.google.gerrit.entities.GroupDescription; 26 : import com.google.gerrit.entities.InternalGroup; 27 : import com.google.gerrit.extensions.common.AccountInfo; 28 : import com.google.gerrit.extensions.restapi.Response; 29 : import com.google.gerrit.extensions.restapi.RestReadView; 30 : import com.google.gerrit.server.account.AccountInfoComparator; 31 : import com.google.gerrit.server.account.AccountLoader; 32 : import com.google.gerrit.server.account.GroupCache; 33 : import com.google.gerrit.server.account.GroupControl; 34 : import com.google.gerrit.server.group.GroupResource; 35 : import com.google.gerrit.server.group.InternalGroupDescription; 36 : import com.google.gerrit.server.permissions.PermissionBackendException; 37 : import com.google.inject.Inject; 38 : import java.util.ArrayList; 39 : import java.util.HashSet; 40 : import java.util.List; 41 : import java.util.Optional; 42 : import java.util.Set; 43 : import org.kohsuke.args4j.Option; 44 : 45 : public class ListMembers implements RestReadView<GroupResource> { 46 : private final GroupCache groupCache; 47 : private final GroupControl.Factory groupControlFactory; 48 : private final AccountLoader.Factory accountLoaderFactory; 49 : 50 : @Option(name = "--recursive", usage = "to resolve included groups recursively") 51 : private boolean recursive; 52 : 53 : @Inject 54 : protected ListMembers( 55 : GroupCache groupCache, 56 : GroupControl.Factory groupControlFactory, 57 30 : AccountLoader.Factory accountLoaderFactory) { 58 30 : this.groupCache = groupCache; 59 30 : this.groupControlFactory = groupControlFactory; 60 30 : this.accountLoaderFactory = accountLoaderFactory; 61 30 : } 62 : 63 : public ListMembers setRecursive(boolean recursive) { 64 5 : this.recursive = recursive; 65 5 : return this; 66 : } 67 : 68 : @Override 69 : public Response<List<AccountInfo>> apply(GroupResource resource) 70 : throws NotInternalGroupException, PermissionBackendException { 71 6 : GroupDescription.Internal group = 72 6 : resource.asInternalGroup().orElseThrow(NotInternalGroupException::new); 73 6 : if (recursive) { 74 1 : return Response.ok(getTransitiveMembers(group, resource.getControl())); 75 : } 76 6 : return Response.ok(getDirectMembers(group, resource.getControl())); 77 : } 78 : 79 : public List<AccountInfo> getTransitiveMembers(AccountGroup.UUID groupUuid) 80 : throws PermissionBackendException { 81 0 : Optional<InternalGroup> group = groupCache.get(groupUuid); 82 0 : if (group.isPresent()) { 83 0 : return getTransitiveMembers(group.get()); 84 : } 85 0 : return ImmutableList.of(); 86 : } 87 : 88 : public List<AccountInfo> getTransitiveMembers(InternalGroup group) 89 : throws PermissionBackendException { 90 0 : InternalGroupDescription internalGroup = new InternalGroupDescription(group); 91 0 : GroupControl groupControl = groupControlFactory.controlFor(internalGroup); 92 0 : return getTransitiveMembers(internalGroup, groupControl); 93 : } 94 : 95 : private List<AccountInfo> getTransitiveMembers( 96 : GroupDescription.Internal group, GroupControl groupControl) 97 : throws PermissionBackendException { 98 1 : checkSameGroup(group, groupControl); 99 1 : Set<Account.Id> members = 100 1 : getTransitiveMemberIds( 101 1 : group, groupControl, new HashSet<>(ImmutableSet.of(group.getGroupUUID()))); 102 1 : return toAccountInfos(members); 103 : } 104 : 105 : public List<AccountInfo> getDirectMembers(InternalGroup group) throws PermissionBackendException { 106 0 : InternalGroupDescription internalGroup = new InternalGroupDescription(group); 107 0 : return getDirectMembers(internalGroup, groupControlFactory.controlFor(internalGroup)); 108 : } 109 : 110 : public List<AccountInfo> getDirectMembers( 111 : GroupDescription.Internal group, GroupControl groupControl) 112 : throws PermissionBackendException { 113 9 : checkSameGroup(group, groupControl); 114 9 : Set<Account.Id> directMembers = getDirectMemberIds(group, groupControl); 115 9 : return toAccountInfos(directMembers); 116 : } 117 : 118 : protected List<AccountInfo> getMembers(InternalGroup group) throws PermissionBackendException { 119 0 : if (recursive) { 120 0 : return getTransitiveMembers(group); 121 : } 122 0 : return getDirectMembers(group); 123 : } 124 : 125 : private List<AccountInfo> toAccountInfos(Set<Account.Id> members) 126 : throws PermissionBackendException { 127 9 : AccountLoader accountLoader = accountLoaderFactory.create(true); 128 9 : List<AccountInfo> memberInfos = new ArrayList<>(members.size()); 129 9 : for (Account.Id member : members) { 130 8 : memberInfos.add(accountLoader.get(member)); 131 8 : } 132 9 : accountLoader.fill(); 133 9 : memberInfos.sort(AccountInfoComparator.ORDER_NULLS_FIRST); 134 9 : return memberInfos; 135 : } 136 : 137 : private Set<Account.Id> getTransitiveMemberIds( 138 : GroupDescription.Internal group, 139 : GroupControl groupControl, 140 : HashSet<AccountGroup.UUID> seenGroups) { 141 1 : Set<Account.Id> directMembers = getDirectMemberIds(group, groupControl); 142 : 143 1 : if (!groupControl.canSeeGroup()) { 144 1 : return directMembers; 145 : } 146 : 147 1 : Set<Account.Id> indirectMembers = getIndirectMemberIds(group, seenGroups); 148 1 : return Sets.union(directMembers, indirectMembers); 149 : } 150 : 151 : private static Set<Account.Id> getDirectMemberIds( 152 : GroupDescription.Internal group, GroupControl groupControl) { 153 9 : return group.getMembers().stream().filter(groupControl::canSeeMember).collect(toImmutableSet()); 154 : } 155 : 156 : private Set<Account.Id> getIndirectMemberIds( 157 : GroupDescription.Internal group, HashSet<AccountGroup.UUID> seenGroups) { 158 1 : Set<Account.Id> indirectMembers = new HashSet<>(); 159 1 : Set<AccountGroup.UUID> subgroupMembersToLoad = new HashSet<>(); 160 1 : for (AccountGroup.UUID subgroupUuid : group.getSubgroups()) { 161 1 : if (!seenGroups.contains(subgroupUuid)) { 162 1 : seenGroups.add(subgroupUuid); 163 1 : subgroupMembersToLoad.add(subgroupUuid); 164 : } 165 1 : } 166 1 : groupCache.get(subgroupMembersToLoad).values().stream() 167 1 : .map(InternalGroupDescription::new) 168 1 : .forEach( 169 : subgroup -> { 170 1 : GroupControl subgroupControl = groupControlFactory.controlFor(subgroup); 171 1 : indirectMembers.addAll(getTransitiveMemberIds(subgroup, subgroupControl, seenGroups)); 172 1 : }); 173 : 174 1 : return indirectMembers; 175 : } 176 : 177 : private static void checkSameGroup(GroupDescription.Internal group, GroupControl groupControl) { 178 9 : checkState( 179 9 : group.equals(groupControl.getGroup()), "Specified group and groupControl do not match"); 180 9 : } 181 : }