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:
Shawn Pearce
2013-01-16 23:45:49 -08:00
parent 0a8969410f
commit 0d5b397709
11 changed files with 147 additions and 54 deletions

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.client.account;
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;
public class MyGroupsScreen extends SettingsScreen {
@@ -31,9 +31,9 @@ public class MyGroupsScreen extends SettingsScreen {
@Override
protected void onLoad() {
super.onLoad();
GroupMap.my(new ScreenLoadCallback<GroupMap>(this) {
GroupList.my(new ScreenLoadCallback<GroupList>(this) {
@Override
protected void preDisplay(final GroupMap result) {
protected void preDisplay(GroupList result) {
groups.display(result);
groups.finishDisplay();
}});

View File

@@ -17,6 +17,7 @@ package com.google.gerrit.client.admin;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
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.ui.HighlightingInlineHyperlink;
import com.google.gerrit.client.ui.NavigationTable;
@@ -76,15 +77,18 @@ public class GroupTable extends NavigationTable<GroupInfo> {
History.newItem(Dispatcher.toGroup(getRowItem(row).getGroupId()));
}
public void display(final GroupMap groups) {
display(groups, null);
public void display(GroupMap groups, String toHighlight) {
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())
table.removeRow(table.getRowCount() - 1);
List<GroupInfo> list = groups.values().asList();
Collections.sort(list, new Comparator<GroupInfo>() {
@Override
public int compare(GroupInfo a, GroupInfo b) {

View File

@@ -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() {
}
}

View File

@@ -14,7 +14,6 @@
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.RestApi;
import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -26,12 +25,6 @@ public class GroupMap extends NativeMap<GroupInfo> {
.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) {
if (match == null || "".equals(match)) {
all(cb);

View File

@@ -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;
}
}

View File

@@ -30,6 +30,7 @@ public class Module extends RestApiModule {
DynamicMap.mapOf(binder(), CAPABILITY_KIND);
child(ACCOUNT_KIND, "capabilities").to(Capabilities.class);
get(ACCOUNT_KIND, "groups").to(GetGroups.class);
get(CAPABILITY_KIND).to(GetCapabilities.CheckOne.class);
}
}

View File

@@ -14,50 +14,15 @@
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.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
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> {
@Override
public Object apply(GroupResource resource) throws AuthException,
BadRequestException, ResourceConflictException, Exception {
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;
}
}
}

View File

@@ -15,11 +15,10 @@
package com.google.gerrit.server.group;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GetGroup.GroupInfo;
public class GetIncludedGroup implements RestReadView<IncludedGroupResource> {
@Override
public GroupInfo apply(IncludedGroupResource rsrc) {
return new GetGroup.GroupInfo(rsrc.getGroup());
return new GroupInfo(rsrc.getGroup());
}
}

View File

@@ -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;
}
}

View File

@@ -28,7 +28,6 @@ import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.OutputFormat;
import com.google.gerrit.server.account.GroupCache;
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.project.ProjectControl;
import com.google.gson.JsonElement;

View File

@@ -24,7 +24,6 @@ import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.AccountGroupIncludeByUuid;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.group.GetGroup.GroupInfo;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -63,7 +62,7 @@ public class ListIncludedGroups implements RestReadView<GroupResource> {
try {
GroupControl i = controlFactory.controlFor(u.getIncludeUUID());
if (ownerOfParent || i.isVisible()) {
included.add(new GetGroup.GroupInfo(i.getGroup()));
included.add(new GroupInfo(i.getGroup()));
}
} catch (NoSuchGroupException notFound) {
log.warn(String.format("Group %s no longer available, included into ",