summaryrefslogtreecommitdiffstats
path: root/java/com/google/gerrit/server/restapi/group/ListMembers.java
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/google/gerrit/server/restapi/group/ListMembers.java')
-rw-r--r--java/com/google/gerrit/server/restapi/group/ListMembers.java170
1 files changed, 170 insertions, 0 deletions
diff --git a/java/com/google/gerrit/server/restapi/group/ListMembers.java b/java/com/google/gerrit/server/restapi/group/ListMembers.java
new file mode 100644
index 0000000000..4742644720
--- /dev/null
+++ b/java/com/google/gerrit/server/restapi/group/ListMembers.java
@@ -0,0 +1,170 @@
+// Copyright (C) 2013 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.restapi.group;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.AccountInfoComparator;
+import com.google.gerrit.server.account.AccountLoader;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.account.GroupControl;
+import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.group.InternalGroup;
+import com.google.gerrit.server.group.InternalGroupDescription;
+import com.google.gerrit.server.permissions.PermissionBackendException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import org.kohsuke.args4j.Option;
+
+public class ListMembers implements RestReadView<GroupResource> {
+ private final GroupCache groupCache;
+ private final GroupControl.Factory groupControlFactory;
+ private final AccountLoader accountLoader;
+
+ @Option(name = "--recursive", usage = "to resolve included groups recursively")
+ private boolean recursive;
+
+ @Inject
+ protected ListMembers(
+ GroupCache groupCache,
+ GroupControl.Factory groupControlFactory,
+ AccountLoader.Factory accountLoaderFactory) {
+ this.groupCache = groupCache;
+ this.groupControlFactory = groupControlFactory;
+ this.accountLoader = accountLoaderFactory.create(true);
+ }
+
+ public ListMembers setRecursive(boolean recursive) {
+ this.recursive = recursive;
+ return this;
+ }
+
+ @Override
+ public List<AccountInfo> apply(GroupResource resource)
+ throws NotInternalGroupException, OrmException, PermissionBackendException {
+ GroupDescription.Internal group =
+ resource.asInternalGroup().orElseThrow(NotInternalGroupException::new);
+ if (recursive) {
+ return getTransitiveMembers(group, resource.getControl());
+ }
+ return getDirectMembers(group, resource.getControl());
+ }
+
+ public List<AccountInfo> getTransitiveMembers(AccountGroup.UUID groupUuid)
+ throws PermissionBackendException {
+ Optional<InternalGroup> group = groupCache.get(groupUuid);
+ if (group.isPresent()) {
+ InternalGroupDescription internalGroup = new InternalGroupDescription(group.get());
+ GroupControl groupControl = groupControlFactory.controlFor(internalGroup);
+ return getTransitiveMembers(internalGroup, groupControl);
+ }
+ return ImmutableList.of();
+ }
+
+ private List<AccountInfo> getTransitiveMembers(
+ GroupDescription.Internal group, GroupControl groupControl)
+ throws PermissionBackendException {
+ checkSameGroup(group, groupControl);
+ Set<Account.Id> members =
+ getTransitiveMemberIds(
+ group, groupControl, new HashSet<>(ImmutableSet.of(group.getGroupUUID())));
+ return toAccountInfos(members);
+ }
+
+ public List<AccountInfo> getDirectMembers(InternalGroup group) throws PermissionBackendException {
+ InternalGroupDescription internalGroup = new InternalGroupDescription(group);
+ return getDirectMembers(internalGroup, groupControlFactory.controlFor(internalGroup));
+ }
+
+ public List<AccountInfo> getDirectMembers(
+ GroupDescription.Internal group, GroupControl groupControl)
+ throws PermissionBackendException {
+ checkSameGroup(group, groupControl);
+ Set<Account.Id> directMembers = getDirectMemberIds(group, groupControl);
+ return toAccountInfos(directMembers);
+ }
+
+ private List<AccountInfo> toAccountInfos(Set<Account.Id> members)
+ throws PermissionBackendException {
+ List<AccountInfo> memberInfos = new ArrayList<>(members.size());
+ for (Account.Id member : members) {
+ memberInfos.add(accountLoader.get(member));
+ }
+ accountLoader.fill();
+ memberInfos.sort(AccountInfoComparator.ORDER_NULLS_FIRST);
+ return memberInfos;
+ }
+
+ private Set<Account.Id> getTransitiveMemberIds(
+ GroupDescription.Internal group,
+ GroupControl groupControl,
+ HashSet<AccountGroup.UUID> seenGroups) {
+ Set<Account.Id> directMembers = getDirectMemberIds(group, groupControl);
+
+ if (!groupControl.canSeeGroup()) {
+ return directMembers;
+ }
+
+ Set<Account.Id> indirectMembers = getIndirectMemberIds(group, seenGroups);
+ return Sets.union(directMembers, indirectMembers);
+ }
+
+ private static Set<Account.Id> getDirectMemberIds(
+ GroupDescription.Internal group, GroupControl groupControl) {
+ return group.getMembers().stream().filter(groupControl::canSeeMember).collect(toImmutableSet());
+ }
+
+ private Set<Account.Id> getIndirectMemberIds(
+ GroupDescription.Internal group, HashSet<AccountGroup.UUID> seenGroups) {
+ Set<Account.Id> indirectMembers = new HashSet<>();
+ for (AccountGroup.UUID subgroupUuid : group.getSubgroups()) {
+ if (!seenGroups.contains(subgroupUuid)) {
+ seenGroups.add(subgroupUuid);
+
+ Set<Account.Id> subgroupMembers =
+ groupCache
+ .get(subgroupUuid)
+ .map(InternalGroupDescription::new)
+ .map(
+ subgroup -> {
+ GroupControl subgroupControl = groupControlFactory.controlFor(subgroup);
+ return getTransitiveMemberIds(subgroup, subgroupControl, seenGroups);
+ })
+ .orElseGet(ImmutableSet::of);
+ indirectMembers.addAll(subgroupMembers);
+ }
+ }
+ return indirectMembers;
+ }
+
+ private static void checkSameGroup(GroupDescription.Internal group, GroupControl groupControl) {
+ checkState(
+ group.equals(groupControl.getGroup()), "Specified group and groupControl do not match");
+ }
+}