GET /accounts/{id}/groups
Obtain the list of groups a target user is a member of from
within the user resource, rather than /groups/?user={id} query.
This is a different projection of essentially the same data,
but I think it makes sense to allow a user to ask for their
groups within their account.
A nice benefit is we can now ask /accounts/self/groups to see
our own groups at any time, whereas /groups/?user={id} does not
accept self as an argument.
Unlike /groups/?user={id} query any user can see a subset of
another user's groups if two things are true:
  - The caller can see the other user.
    Often true when both users have a group in common.
  - The caller can see the group.
    True when the caller is an owner of that group, or
    the group is visible to everyone.
  - The caller can see the members of the group.
    True if the caller is an owner, or the group
    is visible to everyone.
Change-Id: I43d43f2f774dafb1f77fd047b50b6fa050f82d07
			
			
This commit is contained in:
		| @@ -15,7 +15,7 @@ | |||||||
| package com.google.gerrit.client.account; | package com.google.gerrit.client.account; | ||||||
|  |  | ||||||
| import com.google.gerrit.client.admin.GroupTable; | import com.google.gerrit.client.admin.GroupTable; | ||||||
| import com.google.gerrit.client.groups.GroupMap; | import com.google.gerrit.client.groups.GroupList; | ||||||
| import com.google.gerrit.client.rpc.ScreenLoadCallback; | import com.google.gerrit.client.rpc.ScreenLoadCallback; | ||||||
|  |  | ||||||
| public class MyGroupsScreen extends SettingsScreen { | public class MyGroupsScreen extends SettingsScreen { | ||||||
| @@ -31,9 +31,9 @@ public class MyGroupsScreen extends SettingsScreen { | |||||||
|   @Override |   @Override | ||||||
|   protected void onLoad() { |   protected void onLoad() { | ||||||
|     super.onLoad(); |     super.onLoad(); | ||||||
|     GroupMap.my(new ScreenLoadCallback<GroupMap>(this) { |     GroupList.my(new ScreenLoadCallback<GroupList>(this) { | ||||||
|       @Override |       @Override | ||||||
|       protected void preDisplay(final GroupMap result) { |       protected void preDisplay(GroupList result) { | ||||||
|         groups.display(result); |         groups.display(result); | ||||||
|         groups.finishDisplay(); |         groups.finishDisplay(); | ||||||
|       }}); |       }}); | ||||||
|   | |||||||
| @@ -17,6 +17,7 @@ package com.google.gerrit.client.admin; | |||||||
| import com.google.gerrit.client.Dispatcher; | import com.google.gerrit.client.Dispatcher; | ||||||
| import com.google.gerrit.client.Gerrit; | import com.google.gerrit.client.Gerrit; | ||||||
| import com.google.gerrit.client.groups.GroupInfo; | import com.google.gerrit.client.groups.GroupInfo; | ||||||
|  | import com.google.gerrit.client.groups.GroupList; | ||||||
| import com.google.gerrit.client.groups.GroupMap; | import com.google.gerrit.client.groups.GroupMap; | ||||||
| import com.google.gerrit.client.ui.HighlightingInlineHyperlink; | import com.google.gerrit.client.ui.HighlightingInlineHyperlink; | ||||||
| import com.google.gerrit.client.ui.NavigationTable; | import com.google.gerrit.client.ui.NavigationTable; | ||||||
| @@ -76,15 +77,18 @@ public class GroupTable extends NavigationTable<GroupInfo> { | |||||||
|     History.newItem(Dispatcher.toGroup(getRowItem(row).getGroupId())); |     History.newItem(Dispatcher.toGroup(getRowItem(row).getGroupId())); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public void display(final GroupMap groups) { |   public void display(GroupMap groups, String toHighlight) { | ||||||
|     display(groups, null); |     display(groups.values().asList(), toHighlight); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public void display(final GroupMap groups, final String toHighlight) { |   public void display(GroupList groups) { | ||||||
|  |     display(groups.asList(), null); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   public void display(List<GroupInfo> list, String toHighlight) { | ||||||
|     while (1 < table.getRowCount()) |     while (1 < table.getRowCount()) | ||||||
|       table.removeRow(table.getRowCount() - 1); |       table.removeRow(table.getRowCount() - 1); | ||||||
|  |  | ||||||
|     List<GroupInfo> list = groups.values().asList(); |  | ||||||
|     Collections.sort(list, new Comparator<GroupInfo>() { |     Collections.sort(list, new Comparator<GroupInfo>() { | ||||||
|       @Override |       @Override | ||||||
|       public int compare(GroupInfo a, GroupInfo b) { |       public int compare(GroupInfo a, GroupInfo b) { | ||||||
|   | |||||||
| @@ -0,0 +1,29 @@ | |||||||
|  | // 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.client.groups; | ||||||
|  |  | ||||||
|  | import com.google.gerrit.client.rpc.NativeList; | ||||||
|  | import com.google.gerrit.client.rpc.RestApi; | ||||||
|  | import com.google.gwt.user.client.rpc.AsyncCallback; | ||||||
|  |  | ||||||
|  | /** Groups available from {@code /groups/} or {@code /accounts/{id}/groups}. */ | ||||||
|  | public class GroupList extends NativeList<GroupInfo> { | ||||||
|  |   public static void my(AsyncCallback<GroupList> callback) { | ||||||
|  |     new RestApi("/accounts/self/groups").get(callback); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   protected GroupList() { | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -14,7 +14,6 @@ | |||||||
|  |  | ||||||
| package com.google.gerrit.client.groups; | package com.google.gerrit.client.groups; | ||||||
|  |  | ||||||
| import com.google.gerrit.client.Gerrit; |  | ||||||
| import com.google.gerrit.client.rpc.NativeMap; | import com.google.gerrit.client.rpc.NativeMap; | ||||||
| import com.google.gerrit.client.rpc.RestApi; | import com.google.gerrit.client.rpc.RestApi; | ||||||
| import com.google.gwt.user.client.rpc.AsyncCallback; | import com.google.gwt.user.client.rpc.AsyncCallback; | ||||||
| @@ -26,12 +25,6 @@ public class GroupMap extends NativeMap<GroupInfo> { | |||||||
|         .get(NativeMap.copyKeysIntoChildren(callback)); |         .get(NativeMap.copyKeysIntoChildren(callback)); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public static void my(AsyncCallback<GroupMap> callback) { |  | ||||||
|     new RestApi("/groups/") |  | ||||||
|         .addParameter("user", Gerrit.getUserAccount().getId().get()) |  | ||||||
|         .get(NativeMap.copyKeysIntoChildren(callback)); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public static void match(String match, AsyncCallback<GroupMap> cb) { |   public static void match(String match, AsyncCallback<GroupMap> cb) { | ||||||
|     if (match == null || "".equals(match)) { |     if (match == null || "".equals(match)) { | ||||||
|       all(cb); |       all(cb); | ||||||
|   | |||||||
| @@ -0,0 +1,54 @@ | |||||||
|  | // 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.account; | ||||||
|  |  | ||||||
|  | import com.google.common.collect.Lists; | ||||||
|  | import com.google.gerrit.common.errors.NoSuchGroupException; | ||||||
|  | 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.IdentifiedUser; | ||||||
|  | import com.google.gerrit.server.group.GroupInfo; | ||||||
|  | import com.google.inject.Inject; | ||||||
|  |  | ||||||
|  | import java.util.List; | ||||||
|  |  | ||||||
|  | class GetGroups implements RestReadView<AccountResource> { | ||||||
|  |   private final GroupControl.Factory groupControlFactory; | ||||||
|  |  | ||||||
|  |   @Inject | ||||||
|  |   GetGroups(GroupControl.Factory groupControlFactory) { | ||||||
|  |     this.groupControlFactory = groupControlFactory; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @Override | ||||||
|  |   public List<GroupInfo> apply(AccountResource resource) { | ||||||
|  |     IdentifiedUser user = resource.getUser(); | ||||||
|  |     Account.Id userId = user.getAccountId(); | ||||||
|  |     List<GroupInfo> groups = Lists.newArrayList(); | ||||||
|  |     for (AccountGroup.UUID uuid : user.getEffectiveGroups().getKnownGroups()) { | ||||||
|  |       GroupControl ctl; | ||||||
|  |       try { | ||||||
|  |         ctl = groupControlFactory.controlFor(uuid); | ||||||
|  |       } catch (NoSuchGroupException e) { | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       if (ctl.isVisible() && ctl.canSeeMember(userId)) { | ||||||
|  |         groups.add(new GroupInfo(ctl.getGroup())); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return groups; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -30,6 +30,7 @@ public class Module extends RestApiModule { | |||||||
|     DynamicMap.mapOf(binder(), CAPABILITY_KIND); |     DynamicMap.mapOf(binder(), CAPABILITY_KIND); | ||||||
|  |  | ||||||
|     child(ACCOUNT_KIND, "capabilities").to(Capabilities.class); |     child(ACCOUNT_KIND, "capabilities").to(Capabilities.class); | ||||||
|  |     get(ACCOUNT_KIND, "groups").to(GetGroups.class); | ||||||
|     get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class); |     get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -14,50 +14,15 @@ | |||||||
|  |  | ||||||
| package com.google.gerrit.server.group; | package com.google.gerrit.server.group; | ||||||
|  |  | ||||||
| import com.google.common.base.Strings; |  | ||||||
| import com.google.gerrit.common.data.GroupDescription; |  | ||||||
| import com.google.gerrit.extensions.restapi.AuthException; | import com.google.gerrit.extensions.restapi.AuthException; | ||||||
| import com.google.gerrit.extensions.restapi.BadRequestException; | import com.google.gerrit.extensions.restapi.BadRequestException; | ||||||
| import com.google.gerrit.extensions.restapi.ResourceConflictException; | import com.google.gerrit.extensions.restapi.ResourceConflictException; | ||||||
| import com.google.gerrit.extensions.restapi.RestReadView; | import com.google.gerrit.extensions.restapi.RestReadView; | ||||||
| import com.google.gerrit.reviewdb.client.AccountGroup; |  | ||||||
| import com.google.gerrit.server.util.Url; |  | ||||||
|  |  | ||||||
| class GetGroup implements RestReadView<GroupResource> { | class GetGroup implements RestReadView<GroupResource> { | ||||||
|  |  | ||||||
|   @Override |   @Override | ||||||
|   public Object apply(GroupResource resource) throws AuthException, |   public Object apply(GroupResource resource) throws AuthException, | ||||||
|       BadRequestException, ResourceConflictException, Exception { |       BadRequestException, ResourceConflictException, Exception { | ||||||
|     return new GroupInfo(resource.getControl().getGroup()); |     return new GroupInfo(resource.getControl().getGroup()); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   public static class GroupInfo { |  | ||||||
|     final String kind = "gerritcodereview#group"; |  | ||||||
|     String id; |  | ||||||
|     String name; |  | ||||||
|     Boolean visibleToAll; |  | ||||||
|  |  | ||||||
|     // These fields are only supplied for internal groups. |  | ||||||
|     String description; |  | ||||||
|     Integer groupId; |  | ||||||
|     String ownerId; |  | ||||||
|  |  | ||||||
|     GroupInfo(GroupDescription.Basic group) { |  | ||||||
|       id = Url.encode(group.getGroupUUID().get()); |  | ||||||
|       name = Strings.emptyToNull(group.getName()); |  | ||||||
|       visibleToAll = group.isVisibleToAll() ? true : null; |  | ||||||
|  |  | ||||||
|       if (group instanceof GroupDescription.Internal) { |  | ||||||
|         set(((GroupDescription.Internal) group).getAccountGroup()); |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     private void set(AccountGroup d) { |  | ||||||
|       description = Strings.emptyToNull(d.getDescription()); |  | ||||||
|       groupId = d.getId().get(); |  | ||||||
|       ownerId = d.getOwnerGroupUUID() != null |  | ||||||
|           ? Url.encode(d.getOwnerGroupUUID().get()) |  | ||||||
|           : null; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -15,11 +15,10 @@ | |||||||
| package com.google.gerrit.server.group; | package com.google.gerrit.server.group; | ||||||
|  |  | ||||||
| import com.google.gerrit.extensions.restapi.RestReadView; | import com.google.gerrit.extensions.restapi.RestReadView; | ||||||
| import com.google.gerrit.server.group.GetGroup.GroupInfo; |  | ||||||
|  |  | ||||||
| public class GetIncludedGroup implements RestReadView<IncludedGroupResource>  { | public class GetIncludedGroup implements RestReadView<IncludedGroupResource>  { | ||||||
|   @Override |   @Override | ||||||
|   public GroupInfo apply(IncludedGroupResource rsrc) { |   public GroupInfo apply(IncludedGroupResource rsrc) { | ||||||
|     return new GetGroup.GroupInfo(rsrc.getGroup()); |     return new GroupInfo(rsrc.getGroup()); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -0,0 +1,50 @@ | |||||||
|  | // 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.Strings; | ||||||
|  | import com.google.gerrit.common.data.GroupDescription; | ||||||
|  | import com.google.gerrit.reviewdb.client.AccountGroup; | ||||||
|  | import com.google.gerrit.server.util.Url; | ||||||
|  |  | ||||||
|  | public class GroupInfo { | ||||||
|  |   final String kind = "gerritcodereview#group"; | ||||||
|  |   public String id; | ||||||
|  |   public String name; | ||||||
|  |   Boolean visibleToAll; | ||||||
|  |  | ||||||
|  |   // These fields are only supplied for internal groups. | ||||||
|  |   String description; | ||||||
|  |   Integer groupId; | ||||||
|  |   String ownerId; | ||||||
|  |  | ||||||
|  |   public GroupInfo(GroupDescription.Basic group) { | ||||||
|  |     id = Url.encode(group.getGroupUUID().get()); | ||||||
|  |     name = Strings.emptyToNull(group.getName()); | ||||||
|  |     visibleToAll = group.isVisibleToAll() ? true : null; | ||||||
|  |  | ||||||
|  |     if (group instanceof GroupDescription.Internal) { | ||||||
|  |       set(((GroupDescription.Internal) group).getAccountGroup()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private void set(AccountGroup d) { | ||||||
|  |     description = Strings.emptyToNull(d.getDescription()); | ||||||
|  |     groupId = d.getId().get(); | ||||||
|  |     ownerId = d.getOwnerGroupUUID() != null | ||||||
|  |         ? Url.encode(d.getOwnerGroupUUID().get()) | ||||||
|  |         : null; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -28,7 +28,6 @@ import com.google.gerrit.server.IdentifiedUser; | |||||||
| import com.google.gerrit.server.OutputFormat; | import com.google.gerrit.server.OutputFormat; | ||||||
| import com.google.gerrit.server.account.GroupCache; | import com.google.gerrit.server.account.GroupCache; | ||||||
| import com.google.gerrit.server.account.VisibleGroups; | import com.google.gerrit.server.account.VisibleGroups; | ||||||
| import com.google.gerrit.server.group.GetGroup.GroupInfo; |  | ||||||
| import com.google.gerrit.server.ioutil.ColumnFormatter; | import com.google.gerrit.server.ioutil.ColumnFormatter; | ||||||
| import com.google.gerrit.server.project.ProjectControl; | import com.google.gerrit.server.project.ProjectControl; | ||||||
| import com.google.gson.JsonElement; | import com.google.gson.JsonElement; | ||||||
|   | |||||||
| @@ -24,7 +24,6 @@ import com.google.gerrit.reviewdb.client.AccountGroup; | |||||||
| import com.google.gerrit.reviewdb.client.AccountGroupIncludeByUuid; | import com.google.gerrit.reviewdb.client.AccountGroupIncludeByUuid; | ||||||
| import com.google.gerrit.reviewdb.server.ReviewDb; | import com.google.gerrit.reviewdb.server.ReviewDb; | ||||||
| import com.google.gerrit.server.account.GroupControl; | import com.google.gerrit.server.account.GroupControl; | ||||||
| import com.google.gerrit.server.group.GetGroup.GroupInfo; |  | ||||||
| import com.google.gwtorm.server.OrmException; | import com.google.gwtorm.server.OrmException; | ||||||
| import com.google.inject.Inject; | import com.google.inject.Inject; | ||||||
| import com.google.inject.Provider; | import com.google.inject.Provider; | ||||||
| @@ -63,7 +62,7 @@ public class ListIncludedGroups implements RestReadView<GroupResource> { | |||||||
|       try { |       try { | ||||||
|         GroupControl i = controlFactory.controlFor(u.getIncludeUUID()); |         GroupControl i = controlFactory.controlFor(u.getIncludeUUID()); | ||||||
|         if (ownerOfParent || i.isVisible()) { |         if (ownerOfParent || i.isVisible()) { | ||||||
|           included.add(new GetGroup.GroupInfo(i.getGroup())); |           included.add(new GroupInfo(i.getGroup())); | ||||||
|         } |         } | ||||||
|       } catch (NoSuchGroupException notFound) { |       } catch (NoSuchGroupException notFound) { | ||||||
|         log.warn(String.format("Group %s no longer available, included into ", |         log.warn(String.format("Group %s no longer available, included into ", | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Shawn Pearce
					Shawn Pearce