diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt index 7eac992371..45c5e34b98 100644 --- a/Documentation/rest-api-groups.txt +++ b/Documentation/rest-api-groups.txt @@ -124,6 +124,40 @@ client so they are generally disabled by default. Optional fields are: * `MEMBERS`: include list of direct group members. -- +==== Find groups that are owned by another group + +By setting `ownedBy` and specifying the link:#group-id[\{group-id\}] of another +group, it is possible to find all the groups for which the owning group is the +given group. + +.Request +---- + GET /groups/?ownedBy=7ca042f4d5847936fcb90ca91057673157fd06fc HTTP/1.0 +---- + +.Response +---- + HTTP/1.1 200 OK + Content-Disposition: attachment + Content-Type: application/json; charset=UTF-8 + + )]}' + { + "MyProject-Committers": { + "id": "9999c971bb4ab872aab759d8c49833ee6b9ff320", + "url": "#/admin/groups/uuid-9999c971bb4ab872aab759d8c49833ee6b9ff320", + "options": { + "visible_to_all": true + }, + "description":"contains all committers for MyProject", + "group_id": 551, + "owner": "MyProject-Owners", + "owner_id": "7ca042f4d5847936fcb90ca91057673157fd06fc", + "created_on": "2013-02-01 09:59:32.126000000" + } + } +---- + ==== Check if a group is owned by the calling user By setting the option `owned` and specifying a group to inspect with the option `group`/`g`, it is possible to find out if this group is diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java index 305a2b0b96..2118f29110 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java @@ -490,6 +490,33 @@ public class GroupsIT extends AbstractDaemonTest { .inOrder(); } + @Test + public void getGroupsByOwner() throws Exception { + String parent = createGroup("test-parent"); + List children = + Arrays.asList(createGroup("test-child1", parent), createGroup("test-child2", parent)); + + // By UUID + List owned = + gApi.groups().list().withOwnedBy(getFromCache(parent).getGroupUUID().get()).get(); + assertThat(owned.stream().map(g -> g.name).collect(toList())) + .containsExactlyElementsIn(children); + + // By name + owned = gApi.groups().list().withOwnedBy(parent).get(); + assertThat(owned.stream().map(g -> g.name).collect(toList())) + .containsExactlyElementsIn(children); + + // By group that does not own any others + owned = gApi.groups().list().withOwnedBy(owned.get(0).id).get(); + assertThat(owned).isEmpty(); + + // By non-existing group + exception.expect(UnprocessableEntityException.class); + exception.expectMessage("Group Not Found: does-not-exist"); + gApi.groups().list().withOwnedBy("does-not-exist").get(); + } + @Test public void onlyVisibleGroupsReturned() throws Exception { String newGroupName = name("newGroup"); diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java index 567d9ba9ec..0243ba3bf0 100644 --- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java +++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/groups/Groups.java @@ -80,6 +80,7 @@ public interface Groups { private String substring; private String suggest; private String regex; + private String ownedBy; public List get() throws RestApiException { Map map = getAsMap(); @@ -160,6 +161,11 @@ public interface Groups { return this; } + public ListRequest withOwnedBy(String ownedBy) { + this.ownedBy = ownedBy; + return this; + } + public EnumSet getOptions() { return options; } @@ -203,6 +209,10 @@ public interface Groups { public String getSuggest() { return suggest; } + + public String getOwnedBy() { + return ownedBy; + } } /** diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java index d267896622..f439f7ded6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/groups/GroupsImpl.java @@ -133,6 +133,10 @@ class GroupsImpl implements Groups { list.setVisibleToAll(req.getVisibleToAll()); + if (req.getOwnedBy() != null) { + list.setOwnedBy(req.getOwnedBy()); + } + if (req.getUser() != null) { try { list.setUser(accounts.parse(req.getUser()).getAccountId()); 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 0f07d829fc..6fbfd67a38 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 @@ -28,6 +28,7 @@ import com.google.gerrit.common.errors.NoSuchGroupException; import com.google.gerrit.extensions.client.ListGroupsOption; import com.google.gerrit.extensions.common.GroupInfo; import com.google.gerrit.extensions.restapi.BadRequestException; +import com.google.gerrit.extensions.restapi.RestApiException; import com.google.gerrit.extensions.restapi.RestReadView; import com.google.gerrit.extensions.restapi.TopLevelResource; import com.google.gerrit.extensions.restapi.Url; @@ -55,6 +56,7 @@ import java.util.Locale; import java.util.Set; import java.util.SortedMap; import java.util.TreeMap; +import java.util.function.Predicate; import java.util.regex.Pattern; import java.util.stream.Stream; import org.kohsuke.args4j.Option; @@ -76,6 +78,7 @@ public class ListGroups implements RestReadView { private final GroupJson json; private final GroupBackend groupBackend; private final Groups groups; + private final GroupsCollection groupsCollection; private final Provider db; private EnumSet options = EnumSet.noneOf(ListGroupsOption.class); @@ -87,6 +90,7 @@ public class ListGroups implements RestReadView { private String matchSubstring; private String matchRegex; private String suggest; + private String ownedBy; @Option( name = "--project", @@ -208,6 +212,11 @@ public class ListGroups implements RestReadView { options.addAll(ListGroupsOption.fromBits(Integer.parseInt(hex, 16))); } + @Option(name = "--owned-by", usage = "list groups owned by the given group uuid") + public void setOwnedBy(String ownedBy) { + this.ownedBy = ownedBy; + } + @Inject protected ListGroups( final GroupCache groupCache, @@ -216,6 +225,7 @@ public class ListGroups implements RestReadView { final Provider identifiedUser, final IdentifiedUser.GenericFactory userFactory, final GetGroups accountGetGroups, + final GroupsCollection groupsCollection, GroupJson json, GroupBackend groupBackend, Groups groups, @@ -229,6 +239,7 @@ public class ListGroups implements RestReadView { this.json = json; this.groupBackend = groupBackend; this.groups = groups; + this.groupsCollection = groupsCollection; this.db = db; } @@ -246,7 +257,7 @@ public class ListGroups implements RestReadView { @Override public SortedMap apply(TopLevelResource resource) - throws OrmException, BadRequestException { + throws OrmException, RestApiException { SortedMap output = new TreeMap<>(); for (GroupInfo info : get()) { output.put(MoreObjects.firstNonNull(info.name, "Group " + Url.decode(info.id)), info); @@ -255,7 +266,7 @@ public class ListGroups implements RestReadView { return output; } - public List get() throws OrmException, BadRequestException { + public List get() throws OrmException, RestApiException { if (!Strings.isNullOrEmpty(suggest)) { return suggestGroups(); } @@ -264,6 +275,10 @@ public class ListGroups implements RestReadView { throw new BadRequestException("Specify one of m/r"); } + if (ownedBy != null) { + return getGroupsOwnedBy(ownedBy); + } + if (owned) { return getGroupsOwnedBy(user != null ? userFactory.create(user) : identifiedUser.get()); } @@ -345,6 +360,9 @@ public class ListGroups implements RestReadView { if (owned) { return true; } + if (ownedBy != null) { + return true; + } if (start != 0) { return true; } @@ -360,14 +378,15 @@ public class ListGroups implements RestReadView { return false; } - private List getGroupsOwnedBy(IdentifiedUser user) throws OrmException { + private List filterGroupsOwnedBy(Predicate filter) + throws OrmException { Pattern pattern = getRegexPattern(); Stream foundGroups = groups .getAll(db.get()) .map(GroupDescriptions::forAccountGroup) .filter(group -> !isNotRelevant(pattern, group)) - .filter(group -> isOwner(user, group)) + .filter(filter) .sorted(GROUP_COMPARATOR) .skip(start); if (limit > 0) { @@ -381,6 +400,15 @@ public class ListGroups implements RestReadView { return groupInfos; } + private List getGroupsOwnedBy(String id) throws OrmException, RestApiException { + String uuid = groupsCollection.parse(id).getGroupUUID().get(); + return filterGroupsOwnedBy(group -> group.getOwnerGroupUUID().get().equals(uuid)); + } + + private List getGroupsOwnedBy(IdentifiedUser user) throws OrmException { + return filterGroupsOwnedBy(group -> isOwner(user, group)); + } + private boolean isOwner(CurrentUser user, GroupDescription.Internal group) { try { return genericGroupControlFactory.controlFor(user, group.getGroupUUID()).isOwner();