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
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt index b45853d..7c9fcbf 100644 --- a/Documentation/access-control.txt +++ b/Documentation/access-control.txt
@@ -10,7 +10,7 @@ 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 @@ 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 --------------
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java index ec70051..97ee219 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java
@@ -96,6 +96,10 @@ 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 @@ 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; }
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java index f9b6a2d..6ff23ed 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/SystemConfig.java
@@ -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 @@ @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() { } }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java index cf7ff2201..f77550e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -61,6 +61,7 @@ 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 @@ factory(AccountInfoCacheFactory.Factory.class); factory(ProjectState.Factory.class); + factory(RefControl.Factory.class); bind(GitRepositoryManager.class).to(LocalDiskRepositoryManager.class); bind(FileTypeRegistry.class).to(MimeUtilFileTypeRegistry.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java index d36a72c..25778a6 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -104,15 +104,18 @@ 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 RefControl controlForRef(String refName) { - return new RefControl(this, refName); + return refControlFactory.create(this, refName); } public CurrentUser getCurrentUser() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java index af92092..7a27835 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -32,8 +32,11 @@ 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.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 @@ /** 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 @@ } 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 @@ 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) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java index 302e22b..9a266ab 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java
@@ -150,12 +150,23 @@ 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();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java index b50cec8..9837108 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -32,7 +32,7 @@ /** 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
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java new file mode 100644 index 0000000..8730b4e --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_46.java
@@ -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)); + } +}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java index d3ec6e9..81b1762 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -191,18 +191,21 @@ 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 @@ } 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() {