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:
		
				
					committed by
					
						
						Shawn O. Pearce
					
				
			
			
				
	
			
			
			
						parent
						
							0860e1b13c
						
					
				
				
					commit
					620255aef7
				
			@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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() {});
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user