Allow multiple groups to own a project
Project ownership is now managed as an access right rather than as part of the project entity's basic properties. This permits more than one group to be granted the Owner access value, thus allowing more than group to manage the properties of the project. Bug: GERRIT-247 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -473,7 +473,6 @@ public class GerritServer {
|
||||
new Project(new Project.NameKey("-- All Projects --"),
|
||||
ProjectRight.WILD_PROJECT);
|
||||
proj.setDescription("Rights inherited by all other projects");
|
||||
proj.setOwnerGroupId(sConfig.adminGroupId);
|
||||
proj.setUseContributorAgreements(false);
|
||||
c.projects().insert(Collections.singleton(proj));
|
||||
}
|
||||
@@ -519,6 +518,21 @@ public class GerritServer {
|
||||
c.projectRights().insert(Collections.singleton(approve));
|
||||
}
|
||||
|
||||
private void initOwnerCategory(final ReviewDb c) throws OrmException {
|
||||
final Transaction txn = c.beginTransaction();
|
||||
final ApprovalCategory cat;
|
||||
final ArrayList<ApprovalCategoryValue> vals;
|
||||
|
||||
cat = new ApprovalCategory(ApprovalCategory.OWN, "Owner");
|
||||
cat.setPosition((short) -1);
|
||||
cat.setFunctionName(NoOpFunction.NAME);
|
||||
vals = new ArrayList<ApprovalCategoryValue>();
|
||||
vals.add(value(cat, 1, "Administer All Settings"));
|
||||
c.approvalCategories().insert(Collections.singleton(cat), txn);
|
||||
c.approvalCategoryValues().insert(vals, txn);
|
||||
txn.commit();
|
||||
}
|
||||
|
||||
private void initReadCategory(final ReviewDb c) throws OrmException {
|
||||
final Transaction txn = c.beginTransaction();
|
||||
final ApprovalCategory cat;
|
||||
@@ -631,13 +645,14 @@ public class GerritServer {
|
||||
|
||||
initSystemConfig(c);
|
||||
sConfig = c.systemConfig().get(new SystemConfig.Key());
|
||||
initWildCardProject(c);
|
||||
initOwnerCategory(c);
|
||||
initReadCategory(c);
|
||||
initVerifiedCategory(c);
|
||||
initCodeReviewCategory(c);
|
||||
initSubmitCategory(c);
|
||||
initPushTagCategory(c);
|
||||
initPushUpdateBranchCategory(c);
|
||||
initWildCardProject(c);
|
||||
}
|
||||
|
||||
if (sVer.versionNbr == 2) {
|
||||
|
@@ -25,6 +25,7 @@ import com.google.gerrit.client.reviewdb.Branch;
|
||||
import com.google.gerrit.client.reviewdb.Project;
|
||||
import com.google.gerrit.client.reviewdb.ProjectRight;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup.Id;
|
||||
import com.google.gerrit.client.rpc.BaseServiceImplementation;
|
||||
import com.google.gerrit.client.rpc.Common;
|
||||
import com.google.gerrit.client.rpc.InvalidNameException;
|
||||
@@ -74,17 +75,12 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
|
||||
public void ownedProjects(final AsyncCallback<List<Project>> callback) {
|
||||
run(callback, new Action<List<Project>>() {
|
||||
public List<Project> run(ReviewDb db) throws OrmException {
|
||||
final List<Project> result;
|
||||
if (Common.getGroupCache().isAdministrator(Common.getAccountId())) {
|
||||
result = db.projects().all().toList();
|
||||
} else {
|
||||
result = myOwnedProjects(db);
|
||||
Collections.sort(result, new Comparator<Project>() {
|
||||
public int compare(final Project a, final Project b) {
|
||||
return a.getName().compareTo(b.getName());
|
||||
}
|
||||
});
|
||||
}
|
||||
final List<Project> result = myOwnedProjects(db);
|
||||
Collections.sort(result, new Comparator<Project>() {
|
||||
public int compare(final Project a, final Project b) {
|
||||
return a.getName().compareTo(b.getName());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
});
|
||||
@@ -153,50 +149,14 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
|
||||
});
|
||||
}
|
||||
|
||||
public void changeProjectOwner(final Project.Id projectId,
|
||||
final String newOwnerName, final AsyncCallback<VoidResult> callback) {
|
||||
run(callback, new Action<VoidResult>() {
|
||||
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
|
||||
assertAmProjectOwner(db, projectId);
|
||||
final Project project = db.projects().get(projectId);
|
||||
if (project == null) {
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
if (ProjectRight.WILD_PROJECT.equals(projectId)) {
|
||||
// This is *not* a good idea to change away from administrators.
|
||||
//
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
|
||||
final AccountGroup owner =
|
||||
db.accountGroups().get(new AccountGroup.NameKey(newOwnerName));
|
||||
if (owner == null) {
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
|
||||
project.setOwnerGroupId(owner.getId());
|
||||
db.projects().update(Collections.singleton(project));
|
||||
Common.getProjectCache().invalidate(project);
|
||||
return VoidResult.INSTANCE;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void deleteRight(final Set<ProjectRight.Key> keys,
|
||||
final AsyncCallback<VoidResult> callback) {
|
||||
run(callback, new Action<VoidResult>() {
|
||||
public VoidResult run(final ReviewDb db) throws OrmException, Failure {
|
||||
final Set<Project.Id> owned = ids(myOwnedProjects(db));
|
||||
Boolean amAdmin = null;
|
||||
for (final ProjectRight.Key k : keys) {
|
||||
if (!owned.contains(k.getProjectId())) {
|
||||
if (amAdmin == null) {
|
||||
amAdmin =
|
||||
Common.getGroupCache().isAdministrator(Common.getAccountId());
|
||||
}
|
||||
if (!amAdmin) {
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
}
|
||||
for (final ProjectRight.Key k : keys) {
|
||||
@@ -215,6 +175,17 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
|
||||
final ApprovalCategory.Id categoryId, final String groupName,
|
||||
final short amin, final short amax,
|
||||
final AsyncCallback<ProjectDetail> callback) {
|
||||
if (ProjectRight.WILD_PROJECT.equals(projectId)
|
||||
&& ApprovalCategory.OWN.equals(categoryId)) {
|
||||
// Giving out control of the WILD_PROJECT to other groups beyond
|
||||
// Administrators is dangerous. Having control over WILD_PROJECT
|
||||
// is about the same as having Administrator access as users are
|
||||
// able to affect grants in all projects on the system.
|
||||
//
|
||||
callback.onFailure(new NoSuchEntityException());
|
||||
return;
|
||||
}
|
||||
|
||||
final short min, max;
|
||||
if (amin <= amax) {
|
||||
min = amin;
|
||||
@@ -295,7 +266,6 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
|
||||
public Set<Branch.NameKey> run(ReviewDb db) throws OrmException, Failure {
|
||||
final Set<Branch.NameKey> deleted = new HashSet<Branch.NameKey>();
|
||||
final Set<Project.Id> owned = ids(myOwnedProjects(db));
|
||||
Boolean amAdmin = null;
|
||||
for (final Branch.NameKey k : ids) {
|
||||
final ProjectCache.Entry e;
|
||||
|
||||
@@ -304,13 +274,7 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
if (!owned.contains(e.getProject().getId())) {
|
||||
if (amAdmin == null) {
|
||||
amAdmin =
|
||||
Common.getGroupCache().isAdministrator(Common.getAccountId());
|
||||
}
|
||||
if (!amAdmin) {
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
}
|
||||
for (final Branch.NameKey k : ids) {
|
||||
@@ -475,25 +439,44 @@ public class ProjectAdminServiceImpl extends BaseServiceImplementation
|
||||
if (p == null) {
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
final Account.Id me = Common.getAccountId();
|
||||
if (!Common.getGroupCache().isInGroup(me, p.getProject().getOwnerGroupId())
|
||||
&& !Common.getGroupCache().isAdministrator(me)) {
|
||||
if (Common.getGroupCache().isAdministrator(Common.getAccountId())) {
|
||||
return;
|
||||
}
|
||||
final Set<Id> myGroups = myGroups();
|
||||
if (!canPerform(myGroups, p, ApprovalCategory.OWN, (short) 1)) {
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
}
|
||||
|
||||
private List<Project> myOwnedProjects(final ReviewDb db) throws OrmException {
|
||||
final Account.Id me = Common.getAccountId();
|
||||
if (Common.getGroupCache().isAdministrator(Common.getAccountId())) {
|
||||
return db.projects().all().toList();
|
||||
}
|
||||
|
||||
final Set<AccountGroup.Id> myGroups = myGroups();
|
||||
final HashSet<Project.Id> projects = new HashSet<Project.Id>();
|
||||
for (final AccountGroup.Id groupId : myGroups) {
|
||||
for (final ProjectRight r : db.projectRights().byCategoryGroup(
|
||||
ApprovalCategory.OWN, groupId)) {
|
||||
projects.add(r.getProjectId());
|
||||
}
|
||||
}
|
||||
|
||||
final ProjectCache projectCache = Common.getProjectCache();
|
||||
final List<Project> own = new ArrayList<Project>();
|
||||
for (final AccountGroup.Id groupId : Common.getGroupCache()
|
||||
.getEffectiveGroups(me)) {
|
||||
for (final Project g : db.projects().ownedByGroup(groupId)) {
|
||||
own.add(g);
|
||||
for (Project.Id id : projects) {
|
||||
final ProjectCache.Entry cacheEntry = projectCache.get(id);
|
||||
if (canPerform(myGroups, cacheEntry, ApprovalCategory.OWN, (short) 1)) {
|
||||
own.add(cacheEntry.getProject());
|
||||
}
|
||||
}
|
||||
return own;
|
||||
}
|
||||
|
||||
private Set<Id> myGroups() {
|
||||
return Common.getGroupCache().getEffectiveGroups(Common.getAccountId());
|
||||
}
|
||||
|
||||
private static Set<Project.Id> ids(final Collection<Project> projectList) {
|
||||
final HashSet<Project.Id> r = new HashSet<Project.Id>();
|
||||
for (final Project project : projectList) {
|
||||
|
@@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.server.mail;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroupMember;
|
||||
import com.google.gerrit.client.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.client.reviewdb.Change;
|
||||
@@ -48,9 +49,10 @@ public class CreateChangeSender extends NewChangeSender {
|
||||
// Try to mark interested owners with a TO and not a BCC line.
|
||||
//
|
||||
final Set<Account.Id> owners = new HashSet<Account.Id>();
|
||||
for (AccountGroupMember m : db.accountGroupMembers().byGroup(
|
||||
project.getOwnerGroupId())) {
|
||||
owners.add(m.getAccountId());
|
||||
for (AccountGroup.Id g : getProjectOwners()) {
|
||||
for (AccountGroupMember m : db.accountGroupMembers().byGroup(g)) {
|
||||
owners.add(m.getAccountId());
|
||||
}
|
||||
}
|
||||
|
||||
// BCC anyone who has interest in this project's changes
|
||||
|
@@ -16,6 +16,7 @@ package com.google.gerrit.server.mail;
|
||||
|
||||
import com.google.gerrit.client.data.ProjectCache;
|
||||
import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.client.reviewdb.Change;
|
||||
import com.google.gerrit.client.reviewdb.ChangeApproval;
|
||||
@@ -44,12 +45,14 @@ import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
@@ -515,6 +518,14 @@ public abstract class OutgoingEmail {
|
||||
return r != null ? r.getProject() : null;
|
||||
}
|
||||
|
||||
/** Get the groups which own the project. */
|
||||
protected Set<AccountGroup.Id> getProjectOwners() {
|
||||
final ProjectCache.Entry r;
|
||||
|
||||
r = Common.getProjectCache().get(change.getDest().getParentKey());
|
||||
return r != null ? r.getOwners() : Collections.<AccountGroup.Id> emptySet();
|
||||
}
|
||||
|
||||
/** Schedule this message for delivery to the listed accounts. */
|
||||
protected void add(final RecipientType rt, final Collection<Account.Id> list) {
|
||||
for (final Account.Id id : list) {
|
||||
|
@@ -14,7 +14,6 @@
|
||||
|
||||
package com.google.gerrit.server.patch;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.data.ApprovalType;
|
||||
import com.google.gerrit.client.data.PatchScript;
|
||||
import com.google.gerrit.client.data.PatchScriptSettings;
|
||||
@@ -408,7 +407,7 @@ public class PatchDetailServiceImpl extends BaseServiceImplementation implements
|
||||
|
||||
if (!me.equals(change.getOwner()) && !me.equals(patch.getUploader())
|
||||
&& !Common.getGroupCache().isAdministrator(me)
|
||||
&& !Common.getGroupCache().isInGroup(me, proj.getOwnerGroupId())) {
|
||||
&& !canPerform(me, projEnt, ApprovalCategory.OWN, (short) 1)) {
|
||||
// The user doesn't have permission to abandon the change
|
||||
throw new Failure(new NoSuchEntityException());
|
||||
}
|
||||
|
@@ -147,7 +147,7 @@ abstract class AbstractCommand implements Command, SessionAware {
|
||||
protected boolean canPerform(final ProjectCache.Entry project,
|
||||
final ApprovalCategory.Id actionId, final short val) {
|
||||
return BaseServiceImplementation.canPerform(getGroups(), project, actionId,
|
||||
val, false);
|
||||
val);
|
||||
}
|
||||
|
||||
protected void assertIsAdministrator() throws Failure {
|
||||
|
Reference in New Issue
Block a user