Add new system group for project owners

Add a new system group 'Project Owners' that permits all owners of a project.
The 'Project Owners' group is always evaluated in the context of a project,
meaning that all rights assigned to the 'Project Owners' group are resolved to
rights for the concrete groups owning the project.
This new group makes it easy for Gerrit administrators to configure a default
access configuration for new projects and so the creation of new projects gets
easier and faster.

As an example let's assume that by default for new projects we want to allow
all project owners to push tags and to vote -2/+2 for Code Review, -1/+1 for
Verified and to submit. Before this change the steps to do this were:
1. Creation of a new group for the team that owns the project, 'Team X' (if not
   already existing)
2. Creation of a new project 'Project Y' with group 'Team X' as project owner.
3. Assigning for 'Project Y' 'Push Tag +2' privilege to 'Team X'
4. Assigning for 'Project Y' 'Code Review -2/+2' privilege to 'Team X'
5. Assigning for 'Project Y' 'Verified -1/+1' privilege to 'Team X'
6. Assigning for 'Project Y' 'Submit +1' privilege to 'Team X'

With the introduction of the 'Project Owner' group things get easier. A Gerrit
Administrator can assign the deault privileges ONCE on a parent project,
e.g. '-- All Projects --', to the 'Project Owners' group.
1. Assigning for '-- All Projects --' 'Push Tag +2' privilege to
   'Project Owners'
2. Assigning for '-- All Projects --' 'Code Review -2/+2' privilege to
   'Project Owners'
3. Assigning for '-- All Projects --' 'Verified -1/+1' privilege to
   'Project Owners'
4. Assigning for '-- All Projects --' 'Submit +1' privilege to 'Project Owners'

Then the creation of new projects can be done very easily within 1 or 2
commands from the command line:
1. Creation of a new group for the team that owns the project, 'Team X' (if not
   already existing)
2. Creation of a new project 'Project Y' with group 'Team X' as project owner.
'Project Y' will inherit the rights assigned to the group 'Project Owner' which
will be resolved for 'Project Y' to rights for the group 'Team X' which is
owning 'Project Y'.

Signed-off-by: Edwin Kempin <edwin.kempin@gmail.com>
Change-Id: Ia233a1ae9138b833aab5d5a53525bbdf6539580e
This commit is contained in:
Edwin Kempin
2010-10-11 08:02:24 +02:00
committed by Shawn O. Pearce
parent 5b50240129
commit d2605edacf
10 changed files with 182 additions and 11 deletions

View File

@@ -10,7 +10,7 @@ be granted to individual users.
System Groups
-------------
Gerrit comes with 3 system groups, with special access privileges
Gerrit comes with 4 system groups, with special access privileges
and membership management. The identity of these groups is set
in the `system_config` table within the database, so the groups
can be renamed after installation if desired.
@@ -65,6 +65,21 @@ cause it to become approved or rejected.
Registered users are always permitted to make and publish comments
on any change in any project they have `Read Access` to.
Project Owners
~~~~~~~~~~~~~~
Access rights assigned to this group are always evaluated within the
context of a project and are resolved to access rights for all users
which own the project.
By assigning access rights to this group on a parent project Gerrit
administrators can define a set of default access rights for project
owners. Child projects inherit these access rights where they are
resolved to the users that own the child project.
Having default access rights for projects owners assigned on a parent
project may avoid the need to initially configure access rights for
newly created child projects.
Account Groups
--------------

View File

@@ -96,6 +96,10 @@ public final class RefRight {
return refPattern.get();
}
public void setGroupId(AccountGroup.Id groupId) {
this.groupId = groupId;
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {refPattern, categoryId,
@@ -119,6 +123,13 @@ public final class RefRight {
this.key = key;
}
public RefRight(final RefRight refRight, final AccountGroup.Id groupId) {
this(new RefRight.Key(refRight.getKey().projectName,
refRight.getKey().refPattern, refRight.getKey().categoryId, groupId));
setMinValue(refRight.getMinValue());
setMaxValue(refRight.getMaxValue());
}
public RefRight.Key getKey() {
return key;
}

View File

@@ -14,7 +14,6 @@
package com.google.gerrit.reviewdb;
import com.google.gerrit.reviewdb.AccountGroup.Id;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.StringKey;
@@ -83,6 +82,10 @@ public final class SystemConfig {
@Column(id = 8)
public AccountGroup.Id batchUsersGroupId;
/** Identity of the owner group, which permits any project owner. */
@Column(id = 9)
public AccountGroup.Id ownerGroupId;
protected SystemConfig() {
}
}

View File

@@ -61,6 +61,7 @@ import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectCacheImpl;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.tools.ToolsCatalog;
import com.google.gerrit.server.util.IdGenerator;
import com.google.gerrit.server.workflow.FunctionState;
@@ -158,6 +159,7 @@ public class GerritGlobalModule extends FactoryModule {
factory(AccountInfoCacheFactory.Factory.class);
factory(ProjectState.Factory.class);
factory(RefControl.Factory.class);
bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class);
bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);

View File

@@ -104,15 +104,18 @@ public class ProjectControl {
private final Set<AccountGroup.Id> uploadGroups;
private final Set<AccountGroup.Id> receiveGroups;
private final RefControl.Factory refControlFactory;
private final CurrentUser user;
private final ProjectState state;
@Inject
ProjectControl(@GitUploadPackGroups Set<AccountGroup.Id> uploadGroups,
@GitReceivePackGroups Set<AccountGroup.Id> receiveGroups,
final RefControl.Factory refControlFactory,
@Assisted CurrentUser who, @Assisted ProjectState ps) {
this.uploadGroups = uploadGroups;
this.receiveGroups = receiveGroups;
this.refControlFactory = refControlFactory;
user = who;
state = ps;
}
@@ -134,7 +137,7 @@ public class ProjectControl {
}
public RefControl controlForRef(String refName) {
return new RefControl(this, refName);
return refControlFactory.create(this, refName);
}
public CurrentUser getCurrentUser() {

View File

@@ -32,8 +32,11 @@ import com.google.gerrit.common.data.ParamertizedString;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import dk.brics.automaton.RegExp;
@@ -50,6 +53,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -60,13 +64,22 @@ import java.util.regex.Pattern;
/** Manages access control for Git references (aka branches, tags). */
public class RefControl {
public interface Factory {
RefControl create(ProjectControl projectControl, String ref);
}
private final SystemConfig systemConfig;
private final ProjectControl projectControl;
private final String refName;
private Boolean canForgeAuthor;
private Boolean canForgeCommitter;
RefControl(final ProjectControl projectControl, String ref) {
@Inject
protected RefControl(final SystemConfig systemConfig,
@Assisted final ProjectControl projectControl,
@Assisted String ref) {
this.systemConfig = systemConfig;
if (isRE(ref)) {
ref = shortestExample(ref);
@@ -462,7 +475,8 @@ public class RefControl {
}
private List<RefRight> getAllRights(ApprovalCategory.Id actionId) {
return filter(getProjectState().getAllRights(actionId, true));
final List<RefRight> allRefRights = filter(getProjectState().getAllRights(actionId, true));
return resolveOwnerGroups(allRefRights);
}
/**
@@ -490,6 +504,47 @@ public class RefControl {
return Collections.unmodifiableList(applicable);
}
/**
* Resolves all refRights which assign privileges to the 'Project Owners'
* group. All other refRights stay unchanged.
*
* @param refRights refRights to be resolved
* @return the resolved refRights
*/
private List<RefRight> resolveOwnerGroups(final List<RefRight> refRights) {
final List<RefRight> resolvedRefRights =
new ArrayList<RefRight>(refRights.size());
for (final RefRight refRight : refRights) {
resolvedRefRights.addAll(resolveOwnerGroups(refRight));
}
return resolvedRefRights;
}
/**
* Checks if the given refRight assigns privileges to the 'Project Owners'
* group.
* If yes, resolves the 'Project Owners' group to the concrete groups that
* own the project and creates new refRights for the concrete owner groups
* which are returned.
* If no, the given refRight is returned unchanged.
*
* @param refRight refRight
* @return the resolved refRights
*/
private Set<RefRight> resolveOwnerGroups(final RefRight refRight) {
final Set<RefRight> resolvedRefRights = new HashSet<RefRight>();
if (refRight.getAccountGroupId().equals(systemConfig.ownerGroupId)) {
for (final AccountGroup.Id ownerGroup : getProjectState().getOwners()) {
if (!ownerGroup.equals(systemConfig.ownerGroupId)) {
resolvedRefRights.add(new RefRight(refRight, ownerGroup));
}
}
} else {
resolvedRefRights.add(refRight);
}
return resolvedRefRights;
}
private List<RefRight> filter(Collection<RefRight> all) {
List<RefRight> mine = new ArrayList<RefRight>(all.size());
for (RefRight right : all) {

View File

@@ -150,12 +150,23 @@ public class SchemaCreator {
c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(batchUsers)));
final AccountGroup owners =
new AccountGroup(new AccountGroup.NameKey("Project Owners"),
new AccountGroup.Id(c.nextAccountGroupId()));
owners.setDescription("Any owner of the project");
owners.setOwnerGroupId(admin.getId());
owners.setType(AccountGroup.Type.SYSTEM);
c.accountGroups().insert(Collections.singleton(owners));
c.accountGroupNames().insert(
Collections.singleton(new AccountGroupName(owners)));
final SystemConfig s = SystemConfig.create();
s.registerEmailPrivateKey = SignedToken.generateRandomKey();
s.adminGroupId = admin.getId();
s.anonymousGroupId = anonymous.getId();
s.registeredGroupId = registered.getId();
s.batchUsersGroupId = batchUsers.getId();
s.ownerGroupId = owners.getId();
s.wildProjectName = DEFAULT_WILD_NAME;
try {
s.sitePath = site_path.getCanonicalPath();

View File

@@ -32,7 +32,7 @@ import java.util.List;
/** A version of the database schema. */
public abstract class SchemaVersion {
/** The current schema version. */
private static final Class<? extends SchemaVersion> C = Schema_45.class;
private static final Class<? extends SchemaVersion> C = Schema_46.class;
public static class Module extends AbstractModule {
@Override

View File

@@ -0,0 +1,62 @@
// Copyright (C) 2010 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.schema;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.AccountGroupName;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.inject.Inject;
import com.google.inject.Provider;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collections;
public class Schema_46 extends SchemaVersion {
@Inject
Schema_46(final Provider<Schema_45> prior) {
super(prior);
}
@Override
protected void migrateData(ReviewDb db, UpdateUI ui) throws SQLException,
OrmException {
AccountGroup.Id groupId = new AccountGroup.Id(db.nextAccountGroupId());
// update system_config
final Connection connection = ((JdbcSchema) db).getConnection();
final Statement stmt = connection.createStatement();
stmt.execute("UPDATE system_config SET OWNER_GROUP_ID = " + groupId.get());
final ResultSet resultSet =
stmt.executeQuery("SELECT ADMIN_GROUP_ID FROM system_config");
resultSet.next();
final int adminGroupId = resultSet.getInt(1);
// create 'Project Owners' group
AccountGroup.NameKey nameKey = new AccountGroup.NameKey("Project Owners");
AccountGroup group = new AccountGroup(nameKey, groupId);
group.setType(AccountGroup.Type.SYSTEM);
group.setOwnerGroupId(new AccountGroup.Id(adminGroupId));
group.setDescription("Any owner of the project");
AccountGroupName gn = new AccountGroupName(group);
db.accountGroupNames().insert(Collections.singleton(gn));
db.accountGroups().insert(Collections.singleton(group));
}
}

View File

@@ -191,18 +191,21 @@ public class RefControlTest extends TestCase {
private final AccountGroup.Id admin = new AccountGroup.Id(1);
private final AccountGroup.Id anonymous = new AccountGroup.Id(2);
private final AccountGroup.Id registered = new AccountGroup.Id(3);
private final AccountGroup.Id owners = new AccountGroup.Id(4);
private final AccountGroup.Id devs = new AccountGroup.Id(4);
private final AccountGroup.Id fixers = new AccountGroup.Id(5);
private final AccountGroup.Id devs = new AccountGroup.Id(5);
private final AccountGroup.Id fixers = new AccountGroup.Id(6);
private final SystemConfig systemConfig;
private final AuthConfig authConfig;
private final AnonymousUser anonymousUser;
public RefControlTest() {
final SystemConfig systemConfig = SystemConfig.create();
systemConfig = SystemConfig.create();
systemConfig.adminGroupId = admin;
systemConfig.anonymousGroupId = anonymous;
systemConfig.registeredGroupId = registered;
systemConfig.ownerGroupId = owners;
systemConfig.batchUsersGroupId = anonymous;
try {
byte[] bin = "abcdefghijklmnopqrstuvwxyz".getBytes("UTF-8");
@@ -268,9 +271,15 @@ public class RefControlTest extends TestCase {
}
private ProjectControl user(AccountGroup.Id... memberOf) {
RefControl.Factory refControlFactory = new RefControl.Factory() {
@Override
public RefControl create(final ProjectControl projectControl, final String ref) {
return new RefControl(systemConfig, projectControl, ref);
}
};
return new ProjectControl(Collections.<AccountGroup.Id> emptySet(),
Collections.<AccountGroup.Id> emptySet(), new MockUser(memberOf),
newProjectState());
Collections.<AccountGroup.Id> emptySet(), refControlFactory,
new MockUser(memberOf), newProjectState());
}
private ProjectState newProjectState() {