Added support for included groups

This change adds a new item to the group configuration: a list of
groups whose members should be included in this one.  This makes it
possible to set up a hierarchy of included groups, which can make it
easier to maintain complex access control lists.

To accomplish this, two new database tables were added,
called AccountGroupIncludes and AccountGroupIncludesAudit.
The relevant support code was added around them, largely based on
the existing code for handling indivdual account membership.  In
addition, caches for group information were added, paralleling the
caches that already exist for accounts.

Change-Id: Ib6990c17739f28f38bc13961143db7ce79251567
This commit is contained in:
Matt Fischer
2011-03-22 14:28:23 -05:00
committed by Shawn O. Pearce
parent 0860e1b13c
commit 620255aef7
33 changed files with 1201 additions and 34 deletions

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.client.admin;
import com.google.gerrit.client.Dispatcher;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.rpc.GerritCallback;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
@@ -21,15 +22,20 @@ import com.google.gerrit.client.ui.AccountDashboardLink;
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
import com.google.gerrit.client.ui.AccountScreen;
import com.google.gerrit.client.ui.AddMemberBox;
import com.google.gerrit.client.ui.AddIncludedGroupBox;
import com.google.gerrit.client.ui.FancyFlexTable;
import com.google.gerrit.client.ui.Hyperlink;
import com.google.gerrit.client.ui.OnEditEnabler;
import com.google.gerrit.client.ui.RPCSuggestOracle;
import com.google.gerrit.client.ui.SmallHeading;
import com.google.gerrit.common.data.AccountInfoCache;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.data.GroupOptions;
import com.google.gerrit.common.data.GroupInfo;
import com.google.gerrit.common.data.GroupInfoCache;
import com.google.gerrit.reviewdb.Account;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupInclude;
import com.google.gerrit.reviewdb.AccountGroupMember;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
@@ -59,7 +65,9 @@ import java.util.List;
public class AccountGroupScreen extends AccountScreen {
private final AccountGroup.Id groupId;
private AccountInfoCache accounts = AccountInfoCache.empty();
private GroupInfoCache groups = GroupInfoCache.empty();
private MemberTable members;
private IncludeTable includes;
private NpTextBox groupNameTxt;
private Button saveName;
@@ -79,6 +87,10 @@ public class AccountGroupScreen extends AccountScreen {
private AddMemberBox addMemberBox;
private Button delMember;
private Panel includePanel;
private AddIncludedGroupBox addIncludeBox;
private Button delInclude;
private Panel externalPanel;
private Label externalName;
private NpTextBox externalNameFilter;
@@ -122,6 +134,7 @@ public class AccountGroupScreen extends AccountScreen {
initGroupOptions();
initGroupType();
initMemberList();
initIncludeList();
initExternal();
}
@@ -299,7 +312,7 @@ public class AccountGroupScreen extends AccountScreen {
addMemberBox.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
doAddNew();
doAddNewMember();
}
});
@@ -321,6 +334,34 @@ public class AccountGroupScreen extends AccountScreen {
add(memberPanel);
}
private void initIncludeList() {
addIncludeBox = new AddIncludedGroupBox();
addIncludeBox.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
doAddNewInclude();
}
});
includes = new IncludeTable();
delInclude = new Button(Util.C.buttonDeleteIncludedGroup());
delInclude.addClickHandler(new ClickHandler() {
@Override
public void onClick(final ClickEvent event) {
includes.deleteChecked();
}
});
includePanel = new FlowPanel();
includePanel.add(new SmallHeading(Util.C.headingIncludedGroups()));
includePanel.add(addIncludeBox);
includePanel.add(includes);
includePanel.add(delInclude);
add(includePanel);
}
private void initExternal() {
externalName = new Label();
@@ -366,6 +407,7 @@ public class AccountGroupScreen extends AccountScreen {
typeSelect.setVisible(!system);
saveType.setVisible(!system);
memberPanel.setVisible(newType == AccountGroup.Type.INTERNAL);
includePanel.setVisible(newType == AccountGroup.Type.INTERNAL);
externalPanel.setVisible(newType == AccountGroup.Type.LDAP);
externalNameFilter.setText(groupNameTxt.getText());
@@ -489,7 +531,9 @@ public class AccountGroupScreen extends AccountScreen {
switch (group.getType()) {
case INTERNAL:
accounts = result.accounts;
groups = result.groups;
members.display(result.members);
includes.display(result.includes);
break;
case LDAP:
@@ -503,7 +547,7 @@ public class AccountGroupScreen extends AccountScreen {
visibleToAllCheckBox.setValue(group.isVisibleToAll());
}
void doAddNew() {
void doAddNewMember() {
final String nameEmail = addMemberBox.getText();
if (nameEmail.length() == 0) {
return;
@@ -529,6 +573,32 @@ public class AccountGroupScreen extends AccountScreen {
});
}
void doAddNewInclude() {
final String groupName = addIncludeBox.getText();
if (groupName.length() == 0) {
return;
}
addIncludeBox.setEnabled(false);
Util.GROUP_SVC.addGroupInclude(groupId, groupName,
new GerritCallback<GroupDetail>() {
public void onSuccess(final GroupDetail result) {
addIncludeBox.setEnabled(true);
addIncludeBox.setText("");
if (result.groups != null && result.includes != null) {
groups.merge(result.groups);
includes.display(result.includes);
}
}
@Override
public void onFailure(final Throwable caught) {
addIncludeBox.setEnabled(true);
super.onFailure(caught);
}
});
}
private class MemberTable extends FancyFlexTable<AccountGroupMember> {
private boolean enabled = true;
@@ -613,4 +683,77 @@ public class AccountGroupScreen extends AccountScreen {
setRowItem(row, k);
}
}
private class IncludeTable extends FancyFlexTable<AccountGroupInclude> {
IncludeTable() {
table.setText(0, 2, Util.C.columnGroupName());
table.setText(0, 3, Util.C.columnGroupDescription());
final FlexCellFormatter fmt = table.getFlexCellFormatter();
fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
}
void deleteChecked() {
final HashSet<AccountGroupInclude.Key> keys =
new HashSet<AccountGroupInclude.Key>();
for (int row = 1; row < table.getRowCount(); row++) {
final AccountGroupInclude k = getRowItem(row);
if (k != null && ((CheckBox) table.getWidget(row, 1)).getValue()) {
keys.add(k.getKey());
}
}
if (!keys.isEmpty()) {
Util.GROUP_SVC.deleteGroupIncludes(groupId, keys,
new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
for (int row = 1; row < table.getRowCount();) {
final AccountGroupInclude k = getRowItem(row);
if (k != null && keys.contains(k.getKey())) {
table.removeRow(row);
} else {
row++;
}
}
}
});
}
}
void insertMember(final AccountGroupInclude k) {
final int row = table.getRowCount();
table.insertRow(row);
applyDataRowStyle(row);
populate(row, k);
}
void display(final List<AccountGroupInclude> result) {
while (1 < table.getRowCount())
table.removeRow(table.getRowCount() - 1);
for (final AccountGroupInclude k : result) {
final int row = table.getRowCount();
table.insertRow(row);
applyDataRowStyle(row);
populate(row, k);
}
}
void populate(final int row, final AccountGroupInclude k) {
AccountGroup.Id id = k.getIncludeId();
GroupInfo group = groups.get(id);
table.setWidget(row, 1, new CheckBox());
table.setWidget(row, 2, new Hyperlink(group.getName(), Dispatcher
.toAccountGroup(id)));
table.setText(row, 3, groups.get(id).getDescription());
final FlexCellFormatter fmt = table.getFlexCellFormatter();
fmt.addStyleName(row, 1, Gerrit.RESOURCES.css().iconCell());
fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
setRowItem(row, k);
}
}
}

View File

@@ -22,6 +22,8 @@ public interface AdminConstants extends Constants {
String defaultBranchName();
String defaultRevisionSpec();
String buttonDeleteIncludedGroup();
String buttonAddIncludedGroup();
String buttonDeleteGroupMembers();
String buttonAddGroupMember();
String buttonSaveDescription();
@@ -47,6 +49,7 @@ public interface AdminConstants extends Constants {
String headingProjectOptions();
String headingGroupType();
String headingMembers();
String headingIncludedGroups();
String headingExternalGroup();
String headingCreateGroup();
String headingAccessRights();

View File

@@ -3,6 +3,8 @@ defaultAccountGroupName = Group Name
defaultBranchName = Branch Name
defaultRevisionSpec = Revision (Branch or SHA-1)
buttonDeleteIncludedGroup = Delete
buttonAddIncludedGroup = Add
buttonDeleteGroupMembers = Delete
buttonAddGroupMember = Add
buttonRenameGroup = Rename Group
@@ -28,6 +30,7 @@ headingDescription = Description
headingProjectOptions = Project Options
headingGroupType = Group Type
headingMembers = Members
headingIncludedGroups = Included Groups
headingExternalGroup = Selected External Group
headingCreateGroup = Create New Group
headingAccessRights = Access Rights

View File

@@ -0,0 +1,104 @@
// Copyright (C) 2011 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.ui;
import com.google.gerrit.client.admin.Util;
import com.google.gerrit.client.ui.HintTextBox;
import com.google.gerrit.client.ui.RPCSuggestOracle;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
public class AddIncludedGroupBox extends Composite {
private final FlowPanel addPanel;
private final Button addMember;
private final HintTextBox nameTxtBox;
private final SuggestBox nameTxt;
private boolean submitOnSelection;
public AddIncludedGroupBox() {
addPanel = new FlowPanel();
addMember = new Button(Util.C.buttonAddIncludedGroup());
nameTxtBox = new HintTextBox();
nameTxt = new SuggestBox(new RPCSuggestOracle(
new AccountGroupSuggestOracle()), nameTxtBox);
nameTxtBox.setVisibleLength(50);
nameTxtBox.setHintText(Util.C.defaultAccountGroupName());
nameTxtBox.addKeyPressHandler(new KeyPressHandler() {
@Override
public void onKeyPress(KeyPressEvent event) {
submitOnSelection = false;
if (event.getCharCode() == KeyCodes.KEY_ENTER) {
if (nameTxt.isSuggestionListShowing()) {
submitOnSelection = true;
} else {
doAdd();
}
}
}
});
nameTxt.addSelectionHandler(new SelectionHandler<Suggestion>() {
@Override
public void onSelection(SelectionEvent<Suggestion> event) {
if (submitOnSelection) {
submitOnSelection = false;
doAdd();
}
}
});
addPanel.add(nameTxt);
addPanel.add(addMember);
initWidget(addPanel);
}
public void setAddButtonText(final String text) {
addMember.setText(text);
}
public void addClickHandler(final ClickHandler handler) {
addMember.addClickHandler(handler);
}
public String getText() {
String s = nameTxtBox.getText();
return s == null ? "" : s;
}
public void setEnabled(boolean enabled) {
addMember.setEnabled(enabled);
nameTxtBox.setEnabled(enabled);
}
public void setText(String text) {
nameTxtBox.setText(text);
}
private void doAdd() {
addMember.fireEvent(new ClickEvent() {});
}
}