// 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.group; import com.google.common.base.Objects; import com.google.common.base.Strings; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.gerrit.common.data.GroupDescriptions; import com.google.gerrit.common.data.GroupReference; import com.google.gerrit.common.errors.NoSuchGroupException; import com.google.gerrit.common.groups.ListGroupsOption; import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.OutputFormat; import com.google.gerrit.server.account.AccountResource; import com.google.gerrit.server.account.GetGroups; import com.google.gerrit.server.account.GroupCache; import com.google.gerrit.server.account.GroupComparator; import com.google.gerrit.server.account.GroupControl; import com.google.gerrit.server.group.GroupJson.GroupInfo; import com.google.gerrit.server.project.ProjectControl; import com.google.gson.reflect.TypeToken; import com.google.gwtorm.server.OrmException; import com.google.inject.Inject; import com.google.inject.Provider; import org.kohsuke.args4j.Option; import java.util.ArrayList; import java.util.Collections; import java.util.EnumSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; /** List groups visible to the calling user. */ public class ListGroups implements RestReadView<TopLevelResource> { protected final GroupCache groupCache; private final GroupControl.Factory groupControlFactory; private final GroupControl.GenericFactory genericGroupControlFactory; private final Provider<IdentifiedUser> identifiedUser; private final IdentifiedUser.GenericFactory userFactory; private final Provider<GetGroups> accountGetGroups; private final GroupJson json; private EnumSet<ListGroupsOption> options; @Option(name = "--project", aliases = {"-p"}, usage = "projects for which the groups should be listed") private final List<ProjectControl> projects = new ArrayList<>(); @Option(name = "--visible-to-all", usage = "to list only groups that are visible to all registered users") private boolean visibleToAll; @Option(name = "--user", aliases = {"-u"}, usage = "user for which the groups should be listed") private Account.Id user; @Option(name = "--owned", usage = "to list only groups that are owned by the specified user" + " or by the calling user if no user was specifed") private boolean owned; private Set<AccountGroup.UUID> groupsToInspect = Sets.newHashSet(); @Option(name = "-q", usage = "group to inspect") void addGroup(final AccountGroup.UUID id) { groupsToInspect.add(id); } @Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of groups to list") private int limit; @Option(name = "-S", metaVar = "CNT", usage = "number of groups to skip") private int start; @Option(name = "-m", metaVar = "MATCH", usage = "match group substring") private String matchSubstring; @Option(name = "-o", usage = "Output options per group") public void addOption(ListGroupsOption o) { options.add(o); } @Option(name = "-O", usage = "Output option flags, in hex") void setOptionFlagsHex(String hex) { options.addAll(ListGroupsOption.fromBits(Integer.parseInt(hex, 16))); } @Inject protected ListGroups(final GroupCache groupCache, final GroupControl.Factory groupControlFactory, final GroupControl.GenericFactory genericGroupControlFactory, final Provider<IdentifiedUser> identifiedUser, final IdentifiedUser.GenericFactory userFactory, final Provider<GetGroups> accountGetGroups, GroupJson json) { this.groupCache = groupCache; this.groupControlFactory = groupControlFactory; this.genericGroupControlFactory = genericGroupControlFactory; this.identifiedUser = identifiedUser; this.userFactory = userFactory; this.accountGetGroups = accountGetGroups; this.json = json; this.options = EnumSet.noneOf(ListGroupsOption.class); } public Account.Id getUser() { return user; } public List<ProjectControl> getProjects() { return projects; } @Override public Object apply(TopLevelResource resource) throws OrmException { final Map<String, GroupInfo> output = Maps.newTreeMap(); for (GroupInfo info : get()) { output.put(Objects.firstNonNull( info.name, "Group " + Url.decode(info.id)), info); info.name = null; } return OutputFormat.JSON.newGson().toJsonTree(output, new TypeToken<Map<String, GroupInfo>>() {}.getType()); } public List<GroupInfo> get() throws OrmException { List<GroupInfo> groupInfos; if (user != null) { if (owned) { groupInfos = getGroupsOwnedBy(userFactory.create(user)); } else { groupInfos = accountGetGroups.get().apply( new AccountResource(userFactory.create(user))); } } else { if (owned) { groupInfos = getGroupsOwnedBy(identifiedUser.get()); } else { List<AccountGroup> groupList; if (!projects.isEmpty()) { Map<AccountGroup.UUID, AccountGroup> groups = Maps.newHashMap(); for (final ProjectControl projectControl : projects) { final Set<GroupReference> groupsRefs = projectControl.getAllGroups(); for (final GroupReference groupRef : groupsRefs) { final AccountGroup group = groupCache.get(groupRef.getUUID()); if (group != null) { groups.put(group.getGroupUUID(), group); } } } groupList = filterGroups(groups.values()); } else { groupList = filterGroups(groupCache.all()); } groupInfos = Lists.newArrayListWithCapacity(groupList.size()); int found = 0; int foundIndex = 0; for (AccountGroup group : groupList) { if (foundIndex++ < start) { continue; } if (limit > 0 && ++found > limit) { break; } groupInfos.add(json.addOptions(options).format( GroupDescriptions.forAccountGroup(group))); } } } return groupInfos; } private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user) throws OrmException { List<GroupInfo> groups = Lists.newArrayList(); int found = 0; int foundIndex = 0; for (AccountGroup g : filterGroups(groupCache.all())) { GroupControl ctl = groupControlFactory.controlFor(g); try { if (genericGroupControlFactory.controlFor(user, g.getGroupUUID()) .isOwner()) { if (foundIndex++ < start) { continue; } if (limit > 0 && ++found > limit) { break; } groups.add(json.addOptions(options).format(ctl.getGroup())); } } catch (NoSuchGroupException e) { continue; } } return groups; } private List<AccountGroup> filterGroups(final Iterable<AccountGroup> groups) { final List<AccountGroup> filteredGroups = Lists.newArrayList(); final boolean isAdmin = identifiedUser.get().getCapabilities().canAdministrateServer(); for (final AccountGroup group : groups) { if (!Strings.isNullOrEmpty(matchSubstring)) { if (!group.getName().toLowerCase(Locale.US) .contains(matchSubstring.toLowerCase(Locale.US))) { continue; } } if (!isAdmin) { final GroupControl c = groupControlFactory.controlFor(group); if (!c.isVisible()) { continue; } } if (visibleToAll && !group.isVisibleToAll()) { continue; } if (!groupsToInspect.isEmpty() && !groupsToInspect.contains(group.getGroupUUID())) { continue; } filteredGroups.add(group); } Collections.sort(filteredGroups, new GroupComparator()); return filteredGroups; } }