diff --git a/Documentation/cmd-ls-groups.txt b/Documentation/cmd-ls-groups.txt index 91b9e3e590..17ebba1482 100644 --- a/Documentation/cmd-ls-groups.txt +++ b/Documentation/cmd-ls-groups.txt @@ -11,8 +11,10 @@ SYNOPSIS 'ssh' -p 'gerrit ls-groups' [--project | -p ] [--user | -u ] + [--owned] [--visible-to-all] [--type {internal | system}] + [-q ] [--verbose | -v] DESCRIPTION @@ -61,6 +63,11 @@ for other users. + This option can't be used together with the '--project' option. +--owned:: + Lists only the groups that are owned by the user that was specified + by the `--user` option or if no user was specified the groups that + are owned by the calling user. + --visible-to-all:: Displays only groups that are visible to all registered users (groups that are explicitly marked as visible to all registered @@ -75,6 +82,14 @@ This option can't be used together with the '--project' option. `system`:: Any system defined and managed group. -- +-q:: + Group that should be inspected. The `-q` option can be specified + multiple times to define several groups to be inspected. If + specified the listed groups will only contain groups that were + specified to be inspected. This is e.g. useful in combination with + the `--owned` and `--user` options to check whether a group is + owned by a user. + --verbose:: -v:: Enable verbose output with tab-separated columns for the @@ -106,6 +121,21 @@ List all groups for which any permission is set for the project Registered Users ===== +List all groups which are owned by the calling user: +===== + $ ssh -p 29418 review.example.com gerrit ls-groups --owned + MyProject_Committers + MyProject_Verifiers +===== + +Check if the calling user owns the group `MyProject_Committers`. If +`MyProject_Committers` is returned the calling user owns this group. +If the result is empty, the calling user doesn't own the group. +===== + $ ssh -p 29418 review.example.com gerrit ls-groups --owned -q MyProject_Committers + MyProject_Committers +===== + Extract the UUID of the 'Administrators' group: ===== diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java index d8ef33a770..9b4314bf09 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupApi.java @@ -36,6 +36,20 @@ public class GroupApi { new RestApi("/groups/").id(groupName).ifNoneMatch().put(in, cb); } + /** Check if the current user is owner of a group */ + public static void isGroupOwner(String groupName, final AsyncCallback cb) { + GroupMap.myOwned(groupName, new AsyncCallback() { + @Override + public void onSuccess(GroupMap result) { + cb.onSuccess(!result.isEmpty()); + } + @Override + public void onFailure(Throwable caught) { + cb.onFailure(caught); + } + }); + } + /** Rename a group */ public static void renameGroup(AccountGroup.UUID group, String newName, AsyncCallback cb) { diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java index 1d00b128b0..6ba00ae643 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/groups/GroupMap.java @@ -21,20 +21,34 @@ import com.google.gwt.user.client.rpc.AsyncCallback; /** Groups available from {@code /groups/}. */ public class GroupMap extends NativeMap { public static void all(AsyncCallback callback) { - new RestApi("/groups/") - .get(NativeMap.copyKeysIntoChildren(callback)); + groups().get(NativeMap.copyKeysIntoChildren(callback)); } public static void match(String match, AsyncCallback cb) { if (match == null || "".equals(match)) { all(cb); } else { - new RestApi("/groups/") - .addParameter("m", match) - .get(NativeMap.copyKeysIntoChildren(cb)); + groups().addParameter("m", match).get(NativeMap.copyKeysIntoChildren(cb)); } } + public static void myOwned(AsyncCallback cb) { + myOwnedGroups().get(NativeMap.copyKeysIntoChildren(cb)); + } + + public static void myOwned(String groupName, AsyncCallback cb) { + myOwnedGroups().addParameter("q", groupName).get( + NativeMap.copyKeysIntoChildren(cb)); + } + + private static RestApi myOwnedGroups() { + return groups().addParameterTrue("owned"); + } + + private static RestApi groups() { + return new RestApi("/groups/"); + } + protected GroupMap() { } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java index 25b15bc704..44577f26de 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupControl.java @@ -23,9 +23,31 @@ import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; import com.google.inject.Inject; import com.google.inject.Provider; +import com.google.inject.Singleton; /** Access control management for a group of accounts managed in Gerrit. */ public class GroupControl { + + @Singleton + public static class GenericFactory { + private final GroupBackend groupBackend; + + @Inject + GenericFactory(final GroupBackend gb) { + groupBackend = gb; + } + + public GroupControl controlFor(final CurrentUser who, + final AccountGroup.UUID groupId) + throws NoSuchGroupException { + final GroupDescription.Basic group = groupBackend.get(groupId); + if (group == null) { + throw new NoSuchGroupException(groupId); + } + return new GroupControl(who, group); + } + } + public static class Factory { private final GroupCache groupCache; private final Provider user; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index 136dc28307..c9e7a54ba6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java @@ -176,6 +176,7 @@ public class GerritGlobalModule extends FactoryModule { DynamicSet.setOf(binder(), AuthBackend.class); bind(GroupControl.Factory.class).in(SINGLETON); + bind(GroupControl.GenericFactory.class).in(SINGLETON); factory(IncludingGroupMembership.Factory.class); bind(GroupBackend.class).to(UniversalGroupBackend.class).in(SINGLETON); DynamicSet.setOf(binder(), GroupBackend.class); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java index 154444ee7b..160af2f1a5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java @@ -18,6 +18,7 @@ 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; @@ -56,6 +57,7 @@ public class ListGroups implements RestReadView { protected final GroupCache groupCache; private final GroupControl.Factory groupControlFactory; + private final GroupControl.GenericFactory genericGroupControlFactory; private final Provider identifiedUser; private final IdentifiedUser.GenericFactory userFactory; private final Provider accountGetGroups; @@ -74,17 +76,30 @@ public class ListGroups implements RestReadView { 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 groupsToInspect = Sets.newHashSet(); + + @Option(name = "-q", usage = "group to inspect") + void addGroup(final AccountGroup.UUID id) { + groupsToInspect.add(id); + } + @Option(name = "-m", metaVar = "MATCH", usage = "match group substring") private String matchSubstring; @Inject protected ListGroups(final GroupCache groupCache, final GroupControl.Factory groupControlFactory, + final GroupControl.GenericFactory genericGroupControlFactory, final Provider identifiedUser, final IdentifiedUser.GenericFactory userFactory, final Provider accountGetGroups) { this.groupCache = groupCache; this.groupControlFactory = groupControlFactory; + this.genericGroupControlFactory = genericGroupControlFactory; this.identifiedUser = identifiedUser; this.userFactory = userFactory; this.accountGetGroups = accountGetGroups; @@ -115,34 +130,58 @@ public class ListGroups implements RestReadView { public List get() throws NoSuchGroupException { List groupInfos; if (user != null) { - groupInfos = accountGetGroups.get().apply( - new AccountResource(userFactory.create(user))); - } else { - List groupList; - if (!projects.isEmpty()) { - Map groups = Maps.newHashMap(); - for (final ProjectControl projectControl : projects) { - final Set groupsRefs = projectControl.getAllGroups(); - for (final GroupReference groupRef : groupsRefs) { - final AccountGroup group = groupCache.get(groupRef.getUUID()); - if (group == null) { - throw new NoSuchGroupException(groupRef.getUUID()); - } - groups.put(group.getGroupUUID(), group); - } - } - groupList = filterGroups(groups.values()); + if (owned) { + groupInfos = getGroupsOwnedBy(userFactory.create(user)); } else { - groupList = filterGroups(groupCache.all()); + groupInfos = accountGetGroups.get().apply( + new AccountResource(userFactory.create(user))); } - groupInfos = Lists.newArrayListWithCapacity(groupList.size()); - for (AccountGroup group : groupList) { - groupInfos.add(new GroupInfo(GroupDescriptions.forAccountGroup(group))); + } else { + if (owned) { + groupInfos = getGroupsOwnedBy(identifiedUser.get()); + } else { + List groupList; + if (!projects.isEmpty()) { + Map groups = Maps.newHashMap(); + for (final ProjectControl projectControl : projects) { + final Set groupsRefs = projectControl.getAllGroups(); + for (final GroupReference groupRef : groupsRefs) { + final AccountGroup group = groupCache.get(groupRef.getUUID()); + if (group == null) { + throw new NoSuchGroupException(groupRef.getUUID()); + } + groups.put(group.getGroupUUID(), group); + } + } + groupList = filterGroups(groups.values()); + } else { + groupList = filterGroups(groupCache.all()); + } + groupInfos = Lists.newArrayListWithCapacity(groupList.size()); + for (AccountGroup group : groupList) { + groupInfos.add(new GroupInfo(GroupDescriptions.forAccountGroup(group))); + } } } return groupInfos; } + private List getGroupsOwnedBy(IdentifiedUser user) { + List groups = Lists.newArrayList(); + for (AccountGroup g : filterGroups(groupCache.all())) { + GroupControl ctl = groupControlFactory.controlFor(g); + try { + if (genericGroupControlFactory.controlFor(user, g.getGroupUUID()) + .isOwner()) { + groups.add(new GroupInfo(ctl.getGroup())); + } + } catch (NoSuchGroupException e) { + continue; + } + } + return groups; + } + private List filterGroups(final Iterable groups) { final List filteredGroups = Lists.newArrayList(); final boolean isAdmin = @@ -164,6 +203,10 @@ public class ListGroups implements RestReadView { || (groupType != null && !groupType.equals(group.getType()))) { continue; } + if (!groupsToInspect.isEmpty() + && !groupsToInspect.contains(group.getGroupUUID())) { + continue; + } filteredGroups.add(group); } Collections.sort(filteredGroups, new GroupComparator()); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java index 1310e012d3..ef36553e17 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java @@ -68,11 +68,12 @@ public class ListGroupsCommand extends BaseCommand { @Inject MyListGroups(final GroupCache groupCache, final GroupControl.Factory groupControlFactory, + final GroupControl.GenericFactory genericGroupControlFactory, final Provider identifiedUser, final IdentifiedUser.GenericFactory userFactory, final Provider accountGetGroups) { - super(groupCache, groupControlFactory, identifiedUser, userFactory, - accountGetGroups); + super(groupCache, groupControlFactory, genericGroupControlFactory, + identifiedUser, userFactory, accountGetGroups); } void display(final PrintWriter out) throws NoSuchGroupException {