Change permissions to be branch based

The RefControl class introduces per-branch access control rules.
ProjectRight is replaced by RefRight in the database, shifting all
current access records to include a reference pattern that matches
the previously assumed target namespace.  For example, PUSH_HEAD
is now matched against refs/heads/*, as is SUBMIT.

Although this implementation starts the foundation for per-branch
level READ access, it is not fully supported.  The Git native
protocol exposes all branches to readers, which means users can
still fetch the Git objects even if the web UI wouldn't allow them
to see the change.

This work was a joint effort between Nico and Shawn.  Nico started
the change and did the bulk of the implementation.  Shawn did a
bunch of cleanup work near the tail end.  Consequently all bugs
are Shawn's fault.

Bug: issue 60
Change-Id: I62401d80cbb885180614a4f20a945f5611de8986
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
Nico Sallembien
2010-01-25 09:05:17 -08:00
committed by Shawn O. Pearce
parent 55f3363fce
commit 75afdfdc84
33 changed files with 901 additions and 440 deletions

View File

@@ -18,7 +18,7 @@ import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.RemoteJsonService;
import com.google.gwtjsonrpc.client.RpcImpl;
@@ -42,12 +42,12 @@ public interface ProjectAdminService extends RemoteJsonService {
AsyncCallback<ProjectDetail> callback);
@SignInRequired
void deleteRight(Project.NameKey projectName, Set<ProjectRight.Key> ids,
void deleteRight(Project.NameKey projectName, Set<RefRight.Key> ids,
AsyncCallback<VoidResult> callback);
@SignInRequired
void addRight(Project.NameKey projectName, ApprovalCategory.Id categoryId,
String groupName, short min, short max,
String groupName, String refName, short min, short max,
AsyncCallback<ProjectDetail> callback);
@SignInRequired

View File

@@ -16,7 +16,7 @@ package com.google.gerrit.common.data;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import java.util.List;
import java.util.Map;
@@ -24,7 +24,7 @@ import java.util.Map;
public class ProjectDetail {
public Project project;
public Map<AccountGroup.Id, AccountGroup> groups;
public List<ProjectRight> rights;
public List<RefRight> rights;
public ProjectDetail() {
}
@@ -37,7 +37,7 @@ public class ProjectDetail {
groups = g;
}
public void setRights(final List<ProjectRight> r) {
public void setRights(final List<RefRight> r) {
rights = r;
}
}

View File

@@ -62,6 +62,7 @@ public interface AdminConstants extends Constants {
String columnProjectDescription();
String columnApprovalCategory();
String columnRightRange();
String columnRefName();
String columnBranchName();
String columnBranchRevision();

View File

@@ -43,6 +43,7 @@ columnGroupDescription = Description
columnProjectDescription = Description
columnApprovalCategory = Category
columnRightRange = Permitted Range
columnRefName = Reference Name
columnBranchName = Branch Name
columnBranchRevision = Revision

View File

@@ -26,7 +26,7 @@ import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gwt.event.dom.client.BlurEvent;
import com.google.gwt.event.dom.client.BlurHandler;
import com.google.gwt.event.dom.client.ChangeEvent;
@@ -64,6 +64,7 @@ public class ProjectRightsPanel extends Composite {
private ListBox rangeMaxBox;
private NpTextBox nameTxtBox;
private SuggestBox nameTxt;
private NpTextBox referenceTxt;
public ProjectRightsPanel(final Project.NameKey toShow) {
projectName = toShow;
@@ -93,6 +94,7 @@ public class ProjectRightsPanel extends Composite {
final boolean canAdd = on && catBox.getItemCount() > 0;
addRight.setEnabled(canAdd);
nameTxtBox.setEnabled(canAdd);
referenceTxt.setEnabled(canAdd);
catBox.setEnabled(canAdd);
rangeMinBox.setEnabled(canAdd);
rangeMaxBox.setEnabled(canAdd);
@@ -102,7 +104,7 @@ public class ProjectRightsPanel extends Composite {
final FlowPanel addPanel = new FlowPanel();
addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
final Grid addGrid = new Grid(4, 2);
final Grid addGrid = new Grid(5, 2);
catBox = new ListBox();
rangeMinBox = new ListBox();
@@ -111,7 +113,7 @@ public class ProjectRightsPanel extends Composite {
catBox.addChangeHandler(new ChangeHandler() {
@Override
public void onChange(final ChangeEvent event) {
populateRangeBoxes();
updateCategorySelection();
}
});
for (final ApprovalType at : Gerrit.getConfig().getApprovalTypes()
@@ -135,7 +137,7 @@ public class ProjectRightsPanel extends Composite {
}
if (catBox.getItemCount() > 0) {
catBox.setSelectedIndex(0);
populateRangeBoxes();
updateCategorySelection();
}
addGrid.setText(0, 0, Util.C.columnApprovalCategory() + ":");
@@ -151,7 +153,8 @@ public class ProjectRightsPanel extends Composite {
public void onFocus(FocusEvent event) {
if (Util.C.defaultAccountGroupName().equals(nameTxtBox.getText())) {
nameTxtBox.setText("");
nameTxtBox.removeStyleName(Gerrit.RESOURCES.css().inputFieldTypeHint());
nameTxtBox.removeStyleName(Gerrit.RESOURCES.css()
.inputFieldTypeHint());
}
}
});
@@ -167,11 +170,18 @@ public class ProjectRightsPanel extends Composite {
addGrid.setText(1, 0, Util.C.columnGroupName() + ":");
addGrid.setWidget(1, 1, nameTxt);
addGrid.setText(2, 0, Util.C.columnRightRange() + ":");
addGrid.setWidget(2, 1, rangeMinBox);
referenceTxt = new NpTextBox();
referenceTxt.setVisibleLength(50);
referenceTxt.setText("");
addGrid.setText(3, 0, "");
addGrid.setWidget(3, 1, rangeMaxBox);
addGrid.setText(2, 0, Util.C.columnRefName() + ":");
addGrid.setWidget(2, 1, referenceTxt);
addGrid.setText(3, 0, Util.C.columnRightRange() + ":");
addGrid.setWidget(3, 1, rangeMinBox);
addGrid.setText(4, 0, "");
addGrid.setWidget(4, 1, rangeMaxBox);
addRight = new Button(Util.C.buttonAddProjectRight());
addRight.addClickHandler(new ClickHandler() {
@@ -241,6 +251,8 @@ public class ProjectRightsPanel extends Composite {
return;
}
final String refPattern = referenceTxt.getText();
if (min.getValue() > max.getValue()) {
// If the user selects it backwards in the web UI, help them out
// by reversing the order to what we would expect.
@@ -253,10 +265,12 @@ public class ProjectRightsPanel extends Composite {
addRight.setEnabled(false);
Util.PROJECT_SVC.addRight(projectName, at.getCategory().getId(), groupName,
min.getValue(), max.getValue(), new GerritCallback<ProjectDetail>() {
refPattern, min.getValue(), max.getValue(),
new GerritCallback<ProjectDetail>() {
public void onSuccess(final ProjectDetail result) {
addRight.setEnabled(true);
nameTxt.setText("");
referenceTxt.setText("");
display(result);
}
@@ -268,7 +282,7 @@ public class ProjectRightsPanel extends Composite {
});
}
private void populateRangeBoxes() {
private void updateCategorySelection() {
final int idx = catBox.getSelectedIndex();
final ApprovalType at;
if (idx >= 0) {
@@ -279,133 +293,148 @@ public class ProjectRightsPanel extends Composite {
at = null;
}
if (at != null && !at.getValues().isEmpty()) {
int curIndex = 0, minIndex = -1, maxIndex = -1;
rangeMinBox.clear();
rangeMaxBox.clear();
for (final ApprovalCategoryValue v : at.getValues()) {
final String vStr = String.valueOf(v.getValue());
String nStr = vStr + ": " + v.getName();
if (v.getValue() > 0) {
nStr = "+" + nStr;
}
rangeMinBox.addItem(nStr, vStr);
rangeMaxBox.addItem(nStr, vStr);
if (v.getValue() < 0) {
minIndex = curIndex;
}
if (maxIndex < 0 && v.getValue() > 0) {
maxIndex = curIndex;
}
curIndex++;
}
if (ApprovalCategory.READ.equals(at.getCategory().getId())) {
// Special case; for READ the most logical range is just
// +1 READ, so assume that as the default for both.
minIndex = maxIndex;
}
rangeMinBox.setSelectedIndex(minIndex >= 0 ? minIndex : 0);
rangeMaxBox.setSelectedIndex(maxIndex >= 0 ? maxIndex : curIndex - 1);
} else {
if (at == null || at.getValues().isEmpty()) {
rangeMinBox.setEnabled(false);
rangeMaxBox.setEnabled(false);
referenceTxt.setEnabled(false);
addRight.setEnabled(false);
return;
}
// TODO Support per-branch READ access.
if (ApprovalCategory.READ.equals(at.getCategory().getId())) {
referenceTxt.setText("");
referenceTxt.setEnabled(false);
} else {
referenceTxt.setEnabled(true);
}
int curIndex = 0, minIndex = -1, maxIndex = -1;
rangeMinBox.clear();
rangeMaxBox.clear();
for (final ApprovalCategoryValue v : at.getValues()) {
final String vStr = String.valueOf(v.getValue());
String nStr = vStr + ": " + v.getName();
if (v.getValue() > 0) {
nStr = "+" + nStr;
}
rangeMinBox.addItem(nStr, vStr);
rangeMaxBox.addItem(nStr, vStr);
if (v.getValue() < 0) {
minIndex = curIndex;
}
if (maxIndex < 0 && v.getValue() > 0) {
maxIndex = curIndex;
}
curIndex++;
}
if (ApprovalCategory.READ.equals(at.getCategory().getId())) {
// Special case; for READ the most logical range is just
// +1 READ, so assume that as the default for both.
minIndex = maxIndex;
}
rangeMinBox.setSelectedIndex(minIndex >= 0 ? minIndex : 0);
rangeMaxBox.setSelectedIndex(maxIndex >= 0 ? maxIndex : curIndex - 1);
addRight.setEnabled(true);
}
private class RightsTable extends FancyFlexTable<ProjectRight> {
private class RightsTable extends FancyFlexTable<RefRight> {
RightsTable() {
table.setWidth("");
table.setText(0, 2, Util.C.columnApprovalCategory());
table.setText(0, 3, Util.C.columnGroupName());
table.setText(0, 4, Util.C.columnRightRange());
table.setText(0, 4, Util.C.columnRefName());
table.setText(0, 5, Util.C.columnRightRange());
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());
fmt.addStyleName(0, 4, Gerrit.RESOURCES.css().dataHeader());
fmt.addStyleName(0, 5, Gerrit.RESOURCES.css().dataHeader());
}
void deleteChecked() {
final HashSet<ProjectRight.Key> ids = new HashSet<ProjectRight.Key>();
final HashSet<RefRight.Key> refRightIds = new HashSet<RefRight.Key>();
for (int row = 1; row < table.getRowCount(); row++) {
final ProjectRight k = getRowItem(row);
if (k != null && table.getWidget(row, 1) instanceof CheckBox
RefRight r = getRowItem(row);
if (r != null && table.getWidget(row, 1) instanceof CheckBox
&& ((CheckBox) table.getWidget(row, 1)).getValue()) {
ids.add(k.getKey());
refRightIds.add(r.getKey());
}
}
if (!ids.isEmpty()) {
Util.PROJECT_SVC.deleteRight(projectName, ids,
new GerritCallback<VoidResult>() {
public void onSuccess(final VoidResult result) {
for (int row = 1; row < table.getRowCount();) {
final ProjectRight k = getRowItem(row);
if (k != null && ids.contains(k.getKey())) {
table.removeRow(row);
} else {
row++;
}
GerritCallback<VoidResult> updateTable =
new GerritCallback<VoidResult>() {
@Override
public void onSuccess(final VoidResult result) {
for (int row = 1; row < table.getRowCount();) {
RefRight r = getRowItem(row);
if (r != null && refRightIds.contains(r.getKey())) {
table.removeRow(row);
} else {
row++;
}
}
});
}
};
if (!refRightIds.isEmpty()) {
Util.PROJECT_SVC.deleteRight(projectName, refRightIds, updateTable);
}
}
void display(final Map<AccountGroup.Id, AccountGroup> groups,
final List<ProjectRight> result) {
final List<RefRight> refRights) {
while (1 < table.getRowCount())
table.removeRow(table.getRowCount() - 1);
for (final ProjectRight k : result) {
for (final RefRight r : refRights) {
final int row = table.getRowCount();
table.insertRow(row);
applyDataRowStyle(row);
populate(row, groups, k);
populate(row, groups, r);
}
}
void populate(final int row,
final Map<AccountGroup.Id, AccountGroup> groups, final ProjectRight k) {
final Map<AccountGroup.Id, AccountGroup> groups, final RefRight r) {
final GerritConfig config = Gerrit.getConfig();
final ApprovalType ar =
config.getApprovalTypes().getApprovalType(k.getApprovalCategoryId());
final AccountGroup group = groups.get(k.getAccountGroupId());
config.getApprovalTypes().getApprovalType(r.getApprovalCategoryId());
final AccountGroup group = groups.get(r.getAccountGroupId());
if (Gerrit.getConfig().getWildProject().equals(k.getProjectNameKey())
&& !Gerrit.getConfig().getWildProject().equals(projectName)) {
table.setText(row, 1, "");
} else {
table.setWidget(row, 1, new CheckBox());
}
table.setWidget(row, 1, new CheckBox());
if (ar != null) {
table.setText(row, 2, ar.getCategory().getName());
} else {
table.setText(row, 2, k.getApprovalCategoryId().get());
table.setText(row, 2, r.getApprovalCategoryId().get());
}
if (group != null) {
table.setText(row, 3, group.getName());
} else {
table.setText(row, 3, Util.M.deletedGroup(k.getAccountGroupId().get()));
table.setText(row, 3, Util.M.deletedGroup(r.getAccountGroupId().get()));
}
table.setText(row, 4, r.getRefPattern());
{
final SafeHtmlBuilder m = new SafeHtmlBuilder();
final ApprovalCategoryValue min, max;
min = ar != null ? ar.getValue(k.getMinValue()) : null;
max = ar != null ? ar.getValue(k.getMaxValue()) : null;
min = ar != null ? ar.getValue(r.getMinValue()) : null;
max = ar != null ? ar.getValue(r.getMaxValue()) : null;
formatValue(m, k.getMinValue(), min);
if (k.getMinValue() != k.getMaxValue()) {
formatValue(m, r.getMinValue(), min);
if (r.getMinValue() != r.getMaxValue()) {
m.br();
formatValue(m, k.getMaxValue(), max);
formatValue(m, r.getMaxValue(), max);
}
SafeHtml.set(table, row, 4, m);
SafeHtml.set(table, row, 5, m);
}
final FlexCellFormatter fmt = table.getFlexCellFormatter();
@@ -413,15 +442,19 @@ public class ProjectRightsPanel extends Composite {
fmt.addStyleName(row, 2, Gerrit.RESOURCES.css().dataCell());
fmt.addStyleName(row, 3, Gerrit.RESOURCES.css().dataCell());
fmt.addStyleName(row, 4, Gerrit.RESOURCES.css().dataCell());
fmt.addStyleName(row, 4, Gerrit.RESOURCES.css().projectAdminApprovalCategoryRangeLine());
fmt.addStyleName(row, 5, Gerrit.RESOURCES.css().dataCell());
fmt.addStyleName(row, 5, Gerrit.RESOURCES.css()
.projectAdminApprovalCategoryRangeLine());
setRowItem(row, k);
setRowItem(row, r);
}
private void formatValue(final SafeHtmlBuilder m, final short v,
final ApprovalCategoryValue e) {
m.openSpan();
m.setStyleName(Gerrit.RESOURCES.css().projectAdminApprovalCategoryValue());
m
.setStyleName(Gerrit.RESOURCES.css()
.projectAdminApprovalCategoryValue());
if (v == 0) {
m.append(' ');
} else if (v > 0) {

View File

@@ -27,7 +27,7 @@ import com.google.gerrit.reviewdb.PatchLineComment;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.PatchSetInfo;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.AccountInfoCacheFactory;
@@ -37,6 +37,7 @@ import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -130,11 +131,14 @@ final class PatchSetPublishDetailFactory extends Handler<PatchSetPublishDetail>
}
private void computeAllowed(final Set<AccountGroup.Id> am,
final Collection<ProjectRight> list) {
for (final ProjectRight r : list) {
final Collection<RefRight> list) {
for (final RefRight r : list) {
if (!am.contains(r.getAccountGroupId())) {
continue;
}
if (!RefControl.matches(change.getDest().get(), r.getRefPattern())) {
continue;
}
Set<ApprovalCategoryValue.Id> s = allowed.get(r.getApprovalCategoryId());
if (s == null) {

View File

@@ -24,6 +24,7 @@ import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ReplicationQueue;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -35,6 +36,7 @@ import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -64,7 +66,8 @@ class AddBranch extends Handler<List<Branch>> {
@Inject
AddBranch(final ProjectControl.Factory projectControlFactory,
final ListBranches.Factory listBranchesFactory,
final IdentifiedUser identifiedUser, final GitRepositoryManager repoManager,
final IdentifiedUser identifiedUser,
final GitRepositoryManager repoManager,
final ReplicationQueue replication,
@Assisted Project.NameKey projectName,
@@ -98,15 +101,17 @@ class AddBranch extends Handler<List<Branch>> {
if (!Repository.isValidRefName(refname)) {
throw new InvalidNameException();
}
if (!projectControl.canCreateRef(refname)) {
throw new IllegalStateException("Cannot create " + refname);
}
final Branch.NameKey name = new Branch.NameKey(projectName, refname);
final RefControl refControl = projectControl.controlForRef(name);
final Repository repo = repoManager.openRepository(projectName.get());
try {
final ObjectId revid = parseStartingRevision(repo);
final RevWalk rw = verifyConnected(repo, revid);
final RevObject object = rw.parseAny(revid);
if (!refControl.canCreate(rw, object)) {
throw new IllegalStateException("Cannot create " + refname);
}
try {
final RefUpdate u = repo.updateRef(refname);

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2009 The Android Open Source Project
// 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.
@@ -17,14 +17,16 @@ package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.NoSuchGroupException;
import com.google.gerrit.server.config.Nullable;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
@@ -32,12 +34,17 @@ import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import java.util.Collections;
class AddProjectRight extends Handler<ProjectDetail> {
class AddRefRight extends Handler<ProjectDetail> {
interface Factory {
AddProjectRight create(@Assisted Project.NameKey projectName,
@Assisted ApprovalCategory.Id categoryId, @Assisted String groupName,
AddRefRight create(@Assisted Project.NameKey projectName,
@Assisted ApprovalCategory.Id categoryId,
@Assisted("groupName") String groupName,
@Nullable @Assisted("refPattern") String refPattern,
@Assisted("min") short min, @Assisted("max") short max);
}
@@ -51,19 +58,21 @@ class AddProjectRight extends Handler<ProjectDetail> {
private final Project.NameKey projectName;
private final ApprovalCategory.Id categoryId;
private final AccountGroup.NameKey groupName;
private final String refPattern;
private final short min;
private final short max;
@Inject
AddProjectRight(final ProjectDetailFactory.Factory projectDetailFactory,
AddRefRight(final ProjectDetailFactory.Factory projectDetailFactory,
final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final GroupCache groupCache,
final ReviewDb db, final ApprovalTypes approvalTypes,
@Assisted final Project.NameKey projectName,
@Assisted final ApprovalCategory.Id categoryId,
@Assisted final String groupName, @Assisted("min") final short min,
@Assisted("max") final short max) {
@Assisted("groupName") final String groupName,
@Nullable @Assisted("refPattern") final String refPattern,
@Assisted("min") final short min, @Assisted("max") final short max) {
this.projectDetailFactory = projectDetailFactory;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
@@ -74,6 +83,7 @@ class AddProjectRight extends Handler<ProjectDetail> {
this.projectName = projectName;
this.categoryId = categoryId;
this.groupName = new AccountGroup.NameKey(groupName);
this.refPattern = refPattern;
if (min <= max) {
this.min = min;
@@ -86,46 +96,84 @@ class AddProjectRight extends Handler<ProjectDetail> {
@Override
public ProjectDetail call() throws NoSuchProjectException, OrmException,
NoSuchGroupException {
NoSuchGroupException, InvalidNameException {
final ProjectControl projectControl =
projectControlFactory.ownerFor(projectName);
if (projectControl.getProjectState().isSpecialWildProject()
&& 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.
//
throw new IllegalArgumentException("Cannot give " + categoryId.get()
+ " on " + projectName + " " + groupName);
}
final ApprovalType at = approvalTypes.getApprovalType(categoryId);
if (at == null || at.getValue(min) == null || at.getValue(max) == null) {
throw new IllegalArgumentException("Invalid category " + categoryId
+ " or range " + min + ".." + max);
}
String refPattern = this.refPattern;
if (refPattern == null || refPattern.isEmpty()) {
if (categoryId.equals(ApprovalCategory.SUBMIT)
|| categoryId.equals(ApprovalCategory.PUSH_HEAD)) {
// Explicitly related to a branch head.
refPattern = "refs/heads/*";
} else if (!at.getCategory().isAction()) {
// Non actions are approval votes on a change, assume these apply
// to branch heads only.
refPattern = "refs/heads/*";
} else if (categoryId.equals(ApprovalCategory.PUSH_TAG)) {
// Explicitly related to the tag namespace.
refPattern = "refs/tags/*";
} else if (categoryId.equals(ApprovalCategory.READ)
|| categoryId.equals(ApprovalCategory.OWN)) {
// Currently these are project-wide rights, so apply that way.
refPattern = "refs/*";
} else {
// Assume project wide for the default.
refPattern = "refs/*";
}
}
while (refPattern.startsWith("/")) {
refPattern = refPattern.substring(1);
}
if (!refPattern.startsWith(Constants.R_REFS)) {
refPattern = Constants.R_HEADS + refPattern;
}
if (refPattern.endsWith("/*")) {
final String prefix = refPattern.substring(0, refPattern.length() - 2);
if (!Repository.isValidRefName(prefix)) {
throw new InvalidNameException();
}
} else {
if (!Repository.isValidRefName(refPattern)) {
throw new InvalidNameException();
}
}
// TODO Support per-branch READ access.
if (ApprovalCategory.READ.equals(categoryId)
&& !refPattern.equals("refs/*")) {
throw new UnsupportedOperationException("READ on " + refPattern
+ " not yet supported.");
}
final AccountGroup group = groupCache.get(groupName);
if (group == null) {
throw new NoSuchGroupException(groupName);
}
final ProjectRight.Key key =
new ProjectRight.Key(projectName, categoryId, group.getId());
ProjectRight pr = db.projectRights().get(key);
if (pr == null) {
pr = new ProjectRight(key);
pr.setMinValue(min);
pr.setMaxValue(max);
db.projectRights().insert(Collections.singleton(pr));
final RefRight.Key key =
new RefRight.Key(projectName, new RefRight.RefPattern(refPattern),
categoryId, group.getId());
RefRight rr = db.refRights().get(key);
if (rr == null) {
rr = new RefRight(key);
rr.setMinValue(min);
rr.setMaxValue(max);
db.refRights().insert(Collections.singleton(rr));
} else {
pr.setMinValue(min);
pr.setMaxValue(max);
db.projectRights().update(Collections.singleton(pr));
rr.setMinValue(min);
rr.setMaxValue(max);
db.refRights().update(Collections.singleton(rr));
}
projectCache.evict(projectControl.getProject());
return projectDetailFactory.create(projectName).call();
}

View File

@@ -75,7 +75,7 @@ class DeleteBranches extends Handler<Set<Branch.NameKey>> {
if (!projectName.equals(k.getParentKey())) {
throw new IllegalArgumentException("All keys must be from same project");
}
if (!projectControl.canDeleteRef(k.get())) {
if (!projectControl.controlForRef(k).canDelete()) {
throw new IllegalStateException("Cannot delete " + k.getShortName());
}
}

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2009 The Android Open Source Project
// 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.
@@ -16,7 +16,7 @@ package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
@@ -29,10 +29,10 @@ import com.google.inject.assistedinject.Assisted;
import java.util.Collections;
import java.util.Set;
class DeleteProjectRights extends Handler<VoidResult> {
class DeleteRefRights extends Handler<VoidResult> {
interface Factory {
DeleteProjectRights create(@Assisted Project.NameKey projectName,
@Assisted Set<ProjectRight.Key> toRemove);
DeleteRefRights create(@Assisted Project.NameKey projectName,
@Assisted Set<RefRight.Key> toRemove);
}
private final ProjectControl.Factory projectControlFactory;
@@ -40,14 +40,14 @@ class DeleteProjectRights extends Handler<VoidResult> {
private final ReviewDb db;
private final Project.NameKey projectName;
private final Set<ProjectRight.Key> toRemove;
private final Set<RefRight.Key> toRemove;
@Inject
DeleteProjectRights(final ProjectControl.Factory projectControlFactory,
DeleteRefRights(final ProjectControl.Factory projectControlFactory,
final ProjectCache projectCache, final ReviewDb db,
@Assisted final Project.NameKey projectName,
@Assisted final Set<ProjectRight.Key> toRemove) {
@Assisted final Set<RefRight.Key> toRemove) {
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.db = db;
@@ -61,16 +61,16 @@ class DeleteProjectRights extends Handler<VoidResult> {
final ProjectControl projectControl =
projectControlFactory.ownerFor(projectName);
for (final ProjectRight.Key k : toRemove) {
for (final RefRight.Key k : toRemove) {
if (!projectName.equals(k.getProjectNameKey())) {
throw new IllegalArgumentException("All keys must be from same project");
}
}
for (final ProjectRight.Key k : toRemove) {
final ProjectRight m = db.projectRights().get(k);
for (final RefRight.Key k : toRemove) {
final RefRight m = db.refRights().get(k);
if (m != null) {
db.projectRights().delete(Collections.singleton(m));
db.refRights().delete(Collections.singleton(m));
}
}
projectCache.evict(projectControl.getProject());

View File

@@ -18,7 +18,7 @@ import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.project.NoSuchProjectException;
@@ -59,7 +59,7 @@ class OwnedProjects extends Handler<List<Project>> {
final HashSet<Project.NameKey> seen = new HashSet<Project.NameKey>();
result = new ArrayList<Project>();
for (final AccountGroup.Id groupId : user.getEffectiveGroups()) {
for (final ProjectRight r : db.projectRights().byCategoryGroup(
for (final RefRight r : db.refRights().byCategoryGroup(
ApprovalCategory.OWN, groupId)) {
final Project.NameKey name = r.getProjectNameKey();
if (!seen.add(name)) {

View File

@@ -19,7 +19,7 @@ import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.inject.Inject;
@@ -29,72 +29,79 @@ import java.util.Set;
class ProjectAdminServiceImpl implements ProjectAdminService {
private final AddBranch.Factory addBranchFactory;
private final AddProjectRight.Factory addProjectRightFactory;
private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
private final DeleteBranches.Factory deleteBranchesFactory;
private final DeleteProjectRights.Factory deleteProjectRightsFactory;
private final ListBranches.Factory listBranchesFactory;
private final OwnedProjects.Factory ownedProjectsFactory;
private final ProjectDetailFactory.Factory projectDetailFactory;
private final AddRefRight.Factory addRefRightFactory;
private final DeleteRefRights.Factory deleteRefRightsFactory;
@Inject
ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
final AddProjectRight.Factory addProjectRightFactory,
final ChangeProjectSettings.Factory changeProjectSettingsFactory,
final DeleteBranches.Factory deleteBranchesFactory,
final DeleteProjectRights.Factory deleteProjectRightFactory,
final ListBranches.Factory listBranchesFactory,
final OwnedProjects.Factory ownedProjectsFactory,
final ProjectDetailFactory.Factory projectDetailFactory) {
final ProjectDetailFactory.Factory projectDetailFactory,
final AddRefRight.Factory addRefRightFactory,
final DeleteRefRights.Factory deleteRefRightsFactory) {
this.addBranchFactory = addBranchFactory;
this.addProjectRightFactory = addProjectRightFactory;
this.changeProjectSettingsFactory = changeProjectSettingsFactory;
this.deleteBranchesFactory = deleteBranchesFactory;
this.deleteProjectRightsFactory = deleteProjectRightFactory;
this.listBranchesFactory = listBranchesFactory;
this.ownedProjectsFactory = ownedProjectsFactory;
this.projectDetailFactory = projectDetailFactory;
this.addRefRightFactory = addRefRightFactory;
this.deleteRefRightsFactory = deleteRefRightsFactory;
}
@Override
public void ownedProjects(final AsyncCallback<List<Project>> callback) {
ownedProjectsFactory.create().to(callback);
}
@Override
public void projectDetail(final Project.NameKey projectName,
final AsyncCallback<ProjectDetail> callback) {
projectDetailFactory.create(projectName).to(callback);
}
@Override
public void changeProjectSettings(final Project update,
final AsyncCallback<ProjectDetail> callback) {
changeProjectSettingsFactory.create(update).to(callback);
}
@Override
public void deleteRight(final Project.NameKey projectName,
final Set<ProjectRight.Key> toRemove,
final AsyncCallback<VoidResult> callback) {
deleteProjectRightsFactory.create(projectName, toRemove).to(callback);
final Set<RefRight.Key> toRemove, final AsyncCallback<VoidResult> callback) {
deleteRefRightsFactory.create(projectName, toRemove).to(callback);
}
@Override
public void addRight(final Project.NameKey projectName,
final ApprovalCategory.Id categoryId, final String groupName,
final short min, final short max,
final String refPattern, final short min, final short max,
final AsyncCallback<ProjectDetail> callback) {
addProjectRightFactory.create(projectName, categoryId, groupName, min, max)
.to(callback);
addRefRightFactory.create(projectName, categoryId, groupName, refPattern,
min, max).to(callback);
}
@Override
public void listBranches(final Project.NameKey projectName,
final AsyncCallback<List<Branch>> callback) {
listBranchesFactory.create(projectName).to(callback);
}
@Override
public void deleteBranch(final Project.NameKey projectName,
final Set<Branch.NameKey> toRemove,
final AsyncCallback<Set<Branch.NameKey>> callback) {
deleteBranchesFactory.create(projectName, toRemove).to(callback);
}
@Override
public void addBranch(final Project.NameKey projectName,
final String branchName, final String startingRevision,
final AsyncCallback<List<Branch>> callback) {

View File

@@ -20,7 +20,7 @@ import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
@@ -71,28 +71,31 @@ class ProjectDetailFactory extends Handler<ProjectDetail> {
detail.setProject(projectState.getProject());
groups = new HashMap<AccountGroup.Id, AccountGroup>();
final List<ProjectRight> rights = new ArrayList<ProjectRight>();
for (final ProjectRight p : projectState.getLocalRights()) {
rights.add(p);
wantGroup(p.getAccountGroupId());
final List<RefRight> refRights = new ArrayList<RefRight>();
for (final RefRight r : projectState.getLocalRights()) {
refRights.add(r);
wantGroup(r.getAccountGroupId());
}
for (final ProjectRight p : projectState.getInheritedRights()) {
rights.add(p);
wantGroup(p.getAccountGroupId());
for (final RefRight r : projectState.getInheritedRights()) {
refRights.add(r);
wantGroup(r.getAccountGroupId());
}
loadGroups();
Collections.sort(rights, new Comparator<ProjectRight>() {
Collections.sort(refRights, new Comparator<RefRight>() {
@Override
public int compare(final ProjectRight a, final ProjectRight b) {
public int compare(final RefRight a, final RefRight b) {
int rc = categoryOf(a).compareTo(categoryOf(b));
if (rc == 0) {
rc = a.getRefPattern().compareTo(b.getRefPattern());
}
if (rc == 0) {
rc = groupOf(a).compareTo(groupOf(b));
}
return rc;
}
private String categoryOf(final ProjectRight r) {
private String categoryOf(final RefRight r) {
final ApprovalType type =
approvalTypes.getApprovalType(r.getApprovalCategoryId());
if (type == null) {
@@ -101,12 +104,12 @@ class ProjectDetailFactory extends Handler<ProjectDetail> {
return type.getCategory().getName();
}
private String groupOf(final ProjectRight r) {
private String groupOf(final RefRight r) {
return groups.get(r.getAccountGroupId()).getName();
}
});
detail.setRights(rights);
detail.setRights(refRights);
detail.setGroups(groups);
return detail;
}

View File

@@ -29,10 +29,10 @@ public class ProjectModule extends RpcServletModule {
@Override
protected void configure() {
factory(AddBranch.Factory.class);
factory(AddProjectRight.Factory.class);
factory(AddRefRight.Factory.class);
factory(ChangeProjectSettings.Factory.class);
factory(DeleteBranches.Factory.class);
factory(DeleteProjectRights.Factory.class);
factory(DeleteRefRights.Factory.class);
factory(ListBranches.Factory.class);
factory(OwnedProjects.Factory.class);
factory(ProjectDetailFactory.Factory.class);

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2008 The Android Open Source Project
// 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.
@@ -14,11 +14,38 @@
package com.google.gerrit.reviewdb;
import com.google.gerrit.reviewdb.Project.NameKey;
import com.google.gwtorm.client.Column;
import com.google.gwtorm.client.CompoundKey;
import com.google.gwtorm.client.StringKey;
/** Grant to use an {@link ApprovalCategory} in the scope of a git ref. */
public final class RefRight {
public static class RefPattern extends
StringKey<com.google.gwtorm.client.Key<?>> {
private static final long serialVersionUID = 1L;
@Column(id = 1)
protected String pattern;
protected RefPattern() {
}
public RefPattern(final String pattern) {
this.pattern = pattern;
}
@Override
public String get() {
return pattern;
}
@Override
protected void set(String pattern) {
this.pattern = pattern;
}
}
/** Grant to use an {@link ApprovalCategory} in the scope of a {@link Project}. */
public final class ProjectRight {
public static class Key extends CompoundKey<Project.NameKey> {
private static final long serialVersionUID = 1L;
@@ -26,22 +53,27 @@ public final class ProjectRight {
protected Project.NameKey projectName;
@Column(id = 2)
protected ApprovalCategory.Id categoryId;
protected RefPattern refPattern;
@Column(id = 3)
protected ApprovalCategory.Id categoryId;
@Column(id = 4)
protected AccountGroup.Id groupId;
protected Key() {
projectName = new Project.NameKey();
refPattern = new RefPattern();
categoryId = new ApprovalCategory.Id();
groupId = new AccountGroup.Id();
}
public Key(final Project.NameKey p, final ApprovalCategory.Id a,
final AccountGroup.Id g) {
projectName = p;
categoryId = a;
groupId = g;
public Key(final Project.NameKey projectName, final RefPattern refPattern,
final ApprovalCategory.Id categoryId, final AccountGroup.Id groupId) {
this.projectName = projectName;
this.refPattern = refPattern;
this.categoryId = categoryId;
this.groupId = groupId;
}
@Override
@@ -55,7 +87,8 @@ public final class ProjectRight {
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {categoryId, groupId};
return new com.google.gwtorm.client.Key<?>[] {refPattern, categoryId,
groupId};
}
}
@@ -68,19 +101,23 @@ public final class ProjectRight {
@Column(id = 3)
protected short maxValue;
protected ProjectRight() {
protected RefRight() {
}
public ProjectRight(final ProjectRight.Key k) {
key = k;
public RefRight(RefRight.Key key) {
this.key = key;
}
public ProjectRight.Key getKey() {
public RefRight.Key getKey() {
return key;
}
public String getRefPattern() {
return key.refPattern.get();
}
public Project.NameKey getProjectNameKey() {
return key.projectName;
return getKey().getProjectNameKey();
}
public ApprovalCategory.Id getApprovalCategoryId() {

View File

@@ -1,4 +1,4 @@
// Copyright (C) 2008 The Android Open Source Project
// 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.
@@ -20,15 +20,14 @@ import com.google.gwtorm.client.PrimaryKey;
import com.google.gwtorm.client.Query;
import com.google.gwtorm.client.ResultSet;
public interface ProjectRightAccess extends
Access<ProjectRight, ProjectRight.Key> {
public interface RefRightAccess extends Access<RefRight, RefRight.Key> {
@PrimaryKey("key")
ProjectRight get(ProjectRight.Key key) throws OrmException;
RefRight get(RefRight.Key refRight) throws OrmException;
@Query("WHERE key.projectName = ?")
ResultSet<ProjectRight> byProject(Project.NameKey name) throws OrmException;
ResultSet<RefRight> byProject(Project.NameKey project) throws OrmException;
@Query("WHERE key.categoryId = ? AND key.groupId = ?")
ResultSet<ProjectRight> byCategoryGroup(ApprovalCategory.Id cat,
ResultSet<RefRight> byCategoryGroup(ApprovalCategory.Id cat,
AccountGroup.Id group) throws OrmException;
}

View File

@@ -87,9 +87,6 @@ public interface ReviewDb extends Schema {
@Relation
ProjectAccess projects();
@Relation
ProjectRightAccess projectRights();
@Relation
ChangeAccess changes();
@@ -108,6 +105,9 @@ public interface ReviewDb extends Schema {
@Relation
PatchLineCommentAccess patchComments();
@Relation
RefRightAccess refRights();
/** Create the next unique id for an {@link Account}. */
@Sequence(startWith = 1000000)
int nextAccountId() throws OrmException;

View File

@@ -155,11 +155,11 @@ ON patch_set_ancestors (ancestor_revision);
-- *********************************************************************
-- ProjectRightAccess
-- RefRightAccess
-- @PrimaryKey covers: byProject
-- covers: byCategoryGroup
CREATE INDEX project_rights_byCatGroup
ON project_rights (category_id, group_id);
CREATE INDEX ref_rights_byCatGroup
ON ref_rights (category_id, group_id);
-- *********************************************************************

View File

@@ -207,11 +207,11 @@ ON patch_set_ancestors (ancestor_revision);
-- *********************************************************************
-- ProjectRightAccess
-- RefRightAccess
-- @PrimaryKey covers: byProject
-- covers: byCategoryGroup
CREATE INDEX project_rights_byCatGroup
ON project_rights (category_id, group_id);
CREATE INDEX ref_rights_byCatGroup
ON ref_rights (category_id, group_id);
-- *********************************************************************

View File

@@ -14,13 +14,6 @@
package com.google.gerrit.server.git;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_REPLACE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_UPDATE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_ANNOTATED;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_ANY;
import com.google.gerrit.common.ChangeHookRunner;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.ApprovalType;
@@ -55,6 +48,7 @@ import com.google.gerrit.server.mail.MergedSender;
import com.google.gerrit.server.mail.ReplacePatchSetSender;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.AtomicUpdate;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@@ -74,14 +68,12 @@ import org.eclipse.jgit.revwalk.RevFlag;
import org.eclipse.jgit.revwalk.RevFlagSet;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevSort;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PostReceiveHook;
import org.eclipse.jgit.transport.PreReceiveHook;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.eclipse.jgit.transport.ReceivePack;
import org.eclipse.jgit.transport.ReceiveCommand.Result;
import org.eclipse.jgit.transport.ReceiveCommand.Type;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -234,7 +226,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
/** Determine if the user can upload commits. */
public Capable canUpload() {
if (!projectControl.canPerform(ApprovalCategory.READ, (short) 2)) {
if (!projectControl.canUploadToAtLeastOneRef()) {
String reqName = project.getName();
return new Capable("Upload denied for project '" + reqName + "'");
}
@@ -444,8 +436,11 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
continue;
case DELETE:
parseDelete(cmd);
continue;
case UPDATE_NONFASTFORWARD:
parseRewindOrDelete(cmd);
parseRewind(cmd);
continue;
}
@@ -456,88 +451,71 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
}
private void parseCreate(final ReceiveCommand cmd) {
if (projectControl.canCreateRef(cmd.getRefName())) {
if (isTag(cmd)) {
parseCreateTag(cmd);
}
final RevObject obj;
try {
obj = rp.getRevWalk().parseAny(cmd.getNewId());
} catch (IOException err) {
log.error("Invalid object " + cmd.getNewId().name() + " for "
+ cmd.getRefName() + " creation", err);
reject(cmd, "invalid object");
return;
}
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (ctl.canCreate(rp.getRevWalk(), obj)) {
// Let the core receive process handle it
} else {
reject(cmd);
}
}
private void parseCreateTag(final ReceiveCommand cmd) {
try {
final RevObject obj = rp.getRevWalk().parseAny(cmd.getNewId());
if (!(obj instanceof RevTag)) {
reject(cmd, "not annotated tag");
return;
}
if (canPerform(PUSH_TAG, PUSH_TAG_ANY)) {
// If we can push any tag, validation is sufficient at this point.
//
return;
}
final RevTag tag = (RevTag) obj;
final PersonIdent tagger = tag.getTaggerIdent();
if (tagger == null) {
reject(cmd, "no tagger");
return;
}
final String email = tagger.getEmailAddress();
if (!currentUser.getEmailAddresses().contains(email)) {
reject(cmd, "invalid tagger " + email);
return;
}
if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) {
// Signed tags are currently assumed valid, as we don't have a GnuPG
// key ring to validate them against, and we might be missing the
// necessary (but currently optional) BouncyCastle Crypto libraries.
//
} else if (canPerform(PUSH_TAG, PUSH_TAG_ANNOTATED)) {
// User is permitted to push an unsigned annotated tag.
//
} else {
reject(cmd, "must be signed");
return;
}
// Let the core receive process handle it
//
} catch (IOException e) {
log.error("Bad tag " + cmd.getRefName() + " " + cmd.getNewId().name(), e);
reject(cmd, "invalid object");
}
}
private void parseUpdate(final ReceiveCommand cmd) {
if (isHead(cmd) && canPerform(PUSH_HEAD, PUSH_HEAD_UPDATE)) {
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (ctl.canUpdate()) {
// Let the core receive process handle it
} else {
reject(cmd);
}
}
private void parseRewindOrDelete(final ReceiveCommand cmd) {
if (isHead(cmd) && cmd.getType() == Type.DELETE
&& projectControl.canDeleteRef(cmd.getRefName())) {
private void parseDelete(final ReceiveCommand cmd) {
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (ctl.canDelete()) {
// Let the core receive process handle it
} else if (isHead(cmd) && canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE)) {
// Let the core receive process handle it
} else if (isHead(cmd) && cmd.getType() == Type.UPDATE_NONFASTFORWARD) {
cmd.setResult(ReceiveCommand.Result.REJECTED_NONFASTFORWARD);
} else {
reject(cmd);
}
}
private void parseRewind(final ReceiveCommand cmd) {
final RevObject oldObject, newObject;
try {
oldObject = rp.getRevWalk().parseAny(cmd.getOldId());
} catch (IOException err) {
log.error("Invalid object " + cmd.getOldId().name() + " for "
+ cmd.getRefName() + " forced update", err);
reject(cmd, "invalid object");
return;
}
try {
newObject = rp.getRevWalk().parseAny(cmd.getNewId());
} catch (IOException err) {
log.error("Invalid object " + cmd.getNewId().name() + " for "
+ cmd.getRefName() + " forced update", err);
reject(cmd, "invalid object");
return;
}
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (oldObject instanceof RevCommit && newObject instanceof RevCommit
&& ctl.canForceUpdate()) {
// Let the core receive process handle it
} else {
cmd.setResult(ReceiveCommand.Result.REJECTED_NONFASTFORWARD);
}
}
private void parseNewChangeCommand(final ReceiveCommand cmd) {
// Permit exactly one new change request per push.
//
@@ -584,6 +562,10 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
return;
}
if (!projectControl.controlForRef(destBranch).canUpload()) {
reject(cmd);
}
// Validate that the new commits are connected with the existing heads
// or tags of this repository. If they aren't, we want to abort. We do
// this check by coloring the tip CONNECTED and letting a RevWalk push
@@ -957,6 +939,10 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
reject(request.cmd, "change " + request.ontoChange + " closed");
return null;
}
if (!projectControl.controlFor(change).canAddPatchSet()) {
reject(request.cmd, "cannot replace " + request.ontoChange);
return null;
}
final PatchSet.Id priorPatchSet = change.currentPatchSetId();
for (final PatchSet ps : db.patchSets().byChange(request.ontoChange)) {
@@ -1488,10 +1474,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
return new RevId(src.getId().name());
}
private boolean canPerform(final ApprovalCategory.Id actionId, final short val) {
return projectControl.canPerform(actionId, val);
}
private static void reject(final ReceiveCommand cmd) {
reject(cmd, "prohibited by Gerrit");
}

View File

@@ -78,28 +78,32 @@ public class ChangeControl {
}
}
private final ProjectControl projectControl;
private final RefControl refControl;
private final Change change;
ChangeControl(final ProjectControl p, final Change c) {
this.projectControl = p;
ChangeControl(final RefControl r, final Change c) {
this.refControl = r;
this.change = c;
}
public ChangeControl forAnonymousUser() {
return new ChangeControl(projectControl.forAnonymousUser(), change);
return new ChangeControl(getRefControl().forAnonymousUser(), getChange());
}
public ChangeControl forUser(final CurrentUser who) {
return new ChangeControl(projectControl.forUser(who), change);
return new ChangeControl(getRefControl().forUser(who), getChange());
}
public RefControl getRefControl() {
return refControl;
}
public CurrentUser getCurrentUser() {
return getProjectControl().getCurrentUser();
return getRefControl().getCurrentUser();
}
public ProjectControl getProjectControl() {
return projectControl;
return getRefControl().getProjectControl();
}
public Project getProject() {
@@ -112,17 +116,23 @@ public class ChangeControl {
/** Can this user see this change? */
public boolean isVisible() {
return getProjectControl().isVisible();
return getRefControl().isVisible();
}
/** Can this user abandon this change? */
public boolean canAbandon() {
return isOwner() // owner (aka creator) of the change can abandon
|| getRefControl().isOwner() // branch owner can abandon
|| getProjectControl().isOwner() // project owner can abandon
|| getCurrentUser().isAdministrator() // site administers are god
;
}
/** Can this user add a patch set to this change? */
public boolean canAddPatchSet() {
return getRefControl().canUpload();
}
/** Is this user the owner of the change? */
public boolean isOwner() {
if (getCurrentUser() instanceof IdentifiedUser) {

View File

@@ -15,7 +15,7 @@
package com.google.gerrit.server.project;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.server.cache.Cache;
import com.google.gerrit.server.cache.CacheModule;
@@ -76,7 +76,7 @@ public class ProjectCacheImpl implements ProjectCache {
this.inheritedRights = new ProjectState.InheritedRights() {
@Override
public Collection<ProjectRight> get() {
public Collection<RefRight> get() {
return ProjectCacheImpl.this.get(wildProject).getLocalRights();
}
};
@@ -90,8 +90,8 @@ public class ProjectCacheImpl implements ProjectCache {
return null;
}
final Collection<ProjectRight> rights =
Collections.unmodifiableCollection(db.projectRights().byProject(
final Collection<RefRight> rights =
Collections.unmodifiableCollection(db.refRights().byProject(
p.getNameKey()).toList());
return projectStateFactory.create(p, rights, inheritedRights);

View File

@@ -14,22 +14,16 @@
package com.google.gerrit.server.project;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_CREATE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_REPLACE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Branch;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
import com.google.inject.Inject;
import com.google.inject.Provider;
import org.eclipse.jgit.lib.Constants;
import java.util.Set;
/** Access control management for a user accessing a project's data. */
@@ -96,7 +90,15 @@ public class ProjectControl {
}
public ChangeControl controlFor(final Change change) {
return new ChangeControl(this, change);
return new ChangeControl(controlForRef(change.getDest()), change);
}
public RefControl controlForRef(Branch.NameKey ref) {
return controlForRef(ref.get());
}
public RefControl controlForRef(String refName) {
return new RefControl(this, refName);
}
public CurrentUser getCurrentUser() {
@@ -113,62 +115,28 @@ public class ProjectControl {
/** Can this user see this project exists? */
public boolean isVisible() {
return canPerform(ApprovalCategory.READ, (short) 1);
return canPerformOnAnyRef(ApprovalCategory.READ, (short) 1);
}
/** Is this user a project owner? Ownership does not imply {@link #isVisible()} */
public boolean isOwner() {
return canPerform(ApprovalCategory.OWN, (short) 1)
return canPerformOnAllRefs(ApprovalCategory.OWN, (short) 1)
|| getCurrentUser().isAdministrator();
}
/** Can this user create the given ref through this access path? */
public boolean canCreateRef(final String refname) {
switch (user.getAccessPath()) {
case WEB:
if (isOwner()) {
return true;
}
if (isHead(refname) && canPerform(PUSH_HEAD, PUSH_HEAD_CREATE)) {
return true;
}
return false;
case SSH:
if (isHead(refname) && canPerform(PUSH_HEAD, PUSH_HEAD_CREATE)) {
return true;
}
if (isTag(refname) && canPerform(PUSH_TAG, (short) 1)) {
return true;
}
return false;
default:
return false;
}
/** @return true if the user can upload to at least one reference */
public boolean canUploadToAtLeastOneRef() {
return canPerformOnAnyRef(ApprovalCategory.READ, (short) 2);
}
/** Can this user delete the given ref through this access path? */
public boolean canDeleteRef(final String refname) {
switch (user.getAccessPath()) {
case WEB:
if (isOwner()) {
return true;
}
if (isHead(refname) && canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE)) {
return true;
}
return false;
private boolean canPerformOnAnyRef(ApprovalCategory.Id actionId,
short requireValue) {
return canPerform(actionId, requireValue, null /* any ref */);
}
case SSH:
if (isHead(refname) && canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE)) {
return true;
}
return false;
default:
return false;
}
private boolean canPerformOnAllRefs(ApprovalCategory.Id actionId,
short requireValue) {
return canPerform(actionId, requireValue, "refs/*");
}
/**
@@ -181,15 +149,19 @@ public class ProjectControl {
* @param actionId unique action id.
* @param requireValue minimum value the application needs to perform this
* action.
* @param refPattern if null, matches any RefRight, otherwise matches only
* those RefRights with the pattern exactly equal to the input.
* @return true if the action can be performed; false if the user lacks the
* necessary permission.
*/
public boolean canPerform(final ApprovalCategory.Id actionId,
final short requireValue) {
private boolean canPerform(final ApprovalCategory.Id actionId,
final short requireValue, final String refPattern) {
final Set<AccountGroup.Id> groups = user.getEffectiveGroups();
int val = Integer.MIN_VALUE;
for (final ProjectRight pr : state.getLocalRights()) {
for (final RefRight pr : state.getLocalRights()) {
if (actionId.equals(pr.getApprovalCategoryId())
&& (refPattern == null || refPattern.equals(pr.getRefPattern()))
&& groups.contains(pr.getAccountGroupId())) {
if (val < 0 && pr.getMaxValue() > 0) {
// If one of the user's groups had denied them access, but
@@ -205,9 +177,11 @@ public class ProjectControl {
}
}
}
if (val == Integer.MIN_VALUE && actionId.canInheritFromWildProject()) {
for (final ProjectRight pr : state.getInheritedRights()) {
for (final RefRight pr : state.getInheritedRights()) {
if (actionId.equals(pr.getApprovalCategoryId())
&& (refPattern == null || refPattern.equals(pr.getRefPattern()))
&& groups.contains(pr.getAccountGroupId())) {
val = Math.max(pr.getMaxValue(), val);
}
@@ -216,12 +190,4 @@ public class ProjectControl {
return val >= requireValue;
}
private static boolean isHead(final String refname) {
return refname.startsWith(Constants.R_HEADS);
}
private static boolean isTag(final String refname) {
return refname.startsWith(Constants.R_TAGS);
}
}

View File

@@ -17,7 +17,7 @@ package com.google.gerrit.server.project;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.AnonymousUser;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.config.WildProjectName;
@@ -32,19 +32,19 @@ import java.util.Set;
/** Cached information on a project. */
public class ProjectState {
public interface Factory {
ProjectState create(Project project, Collection<ProjectRight> localRights,
ProjectState create(Project project, Collection<RefRight> localRights,
InheritedRights inheritedRights);
}
public interface InheritedRights {
Collection<ProjectRight> get();
Collection<RefRight> get();
}
private final AnonymousUser anonymousUser;
private final Project.NameKey wildProject;
private final Project project;
private final Collection<ProjectRight> localRights;
private final Collection<RefRight> localRights;
private final InheritedRights inheritedRights;
private final Set<AccountGroup.Id> owners;
@@ -52,7 +52,7 @@ public class ProjectState {
protected ProjectState(final AnonymousUser anonymousUser,
@WildProjectName final Project.NameKey wildProject,
@Assisted final Project project,
@Assisted final Collection<ProjectRight> rights,
@Assisted final Collection<RefRight> rights,
@Assisted final InheritedRights inheritedRights) {
this.anonymousUser = anonymousUser;
this.wildProject = wildProject;
@@ -62,7 +62,7 @@ public class ProjectState {
this.inheritedRights = inheritedRights;
final HashSet<AccountGroup.Id> groups = new HashSet<AccountGroup.Id>();
for (final ProjectRight right : rights) {
for (final RefRight right : rights) {
if (ApprovalCategory.OWN.equals(right.getApprovalCategoryId())
&& right.getMaxValue() > 0) {
groups.add(right.getAccountGroupId());
@@ -76,12 +76,12 @@ public class ProjectState {
}
/** Get the rights that pertain only to this project. */
public Collection<ProjectRight> getLocalRights() {
public Collection<RefRight> getLocalRights() {
return localRights;
}
/** Get the rights this project inherits from the wild project. */
public Collection<ProjectRight> getInheritedRights() {
public Collection<RefRight> getInheritedRights() {
if (isSpecialWildProject()) {
return Collections.emptyList();
}

View File

@@ -0,0 +1,243 @@
// 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.project;
import static com.google.gerrit.reviewdb.ApprovalCategory.OWN;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_CREATE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_REPLACE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_UPDATE;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_ANNOTATED;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_ANY;
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_SIGNED;
import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/** Manages access control for Git references (aka branches, tags). */
public class RefControl {
private final ProjectControl projectControl;
private final String refName;
RefControl(final ProjectControl projectControl, final String refName) {
this.projectControl = projectControl;
this.refName = refName;
}
public String getRefName() {
return refName;
}
public ProjectControl getProjectControl() {
return projectControl;
}
public CurrentUser getCurrentUser() {
return getProjectControl().getCurrentUser();
}
public RefControl forAnonymousUser() {
return getProjectControl().forAnonymousUser().controlForRef(getRefName());
}
public RefControl forUser(final CurrentUser who) {
return getProjectControl().forUser(who).controlForRef(getRefName());
}
/** Is this user a ref owner? */
public boolean isOwner() {
return canPerform(OWN, (short) 1) || getProjectControl().isOwner();
}
/** Can this user see this reference exists? */
public boolean isVisible() {
return canPerform(READ, (short) 1);
}
/**
* Determines whether the user can upload a change to the ref controlled by
* this object.
*
* @return {@code true} if the user specified can upload a change to the Git
* ref
*/
public boolean canUpload() {
return canPerform(READ, (short) 2);
}
/** @return true if the user can update the reference as a fast-forward. */
public boolean canUpdate() {
return canPerform(PUSH_HEAD, PUSH_HEAD_UPDATE);
}
/** @return true if the user can rewind (force push) the reference. */
public boolean canForceUpdate() {
return canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE) || canDelete();
}
/**
* Determines whether the user can create a new Git ref.
*
* @param rw revision pool {@code object} was parsed in.
* @param object the object the user will start the reference with.
* @return {@code true} if the user specified can create a new Git ref
*/
public boolean canCreate(RevWalk rw, RevObject object) {
boolean owner;
switch (getCurrentUser().getAccessPath()) {
case WEB:
owner = isOwner();
break;
default:
owner = false;
}
if (object instanceof RevCommit) {
return owner || canPerform(PUSH_HEAD, PUSH_HEAD_CREATE);
} else if (object instanceof RevTag) {
try {
rw.parseBody(object);
} catch (IOException e) {
return false;
}
final RevTag tag = (RevTag) object;
// Require the tagger to be present and match the current user's
// email address, unless PUSH_ANY_TAG was granted.
//
final PersonIdent tagger = tag.getTaggerIdent();
if (tagger == null || !(getCurrentUser() instanceof IdentifiedUser)) {
return owner || canPerform(PUSH_TAG, PUSH_TAG_ANY);
}
final IdentifiedUser user = (IdentifiedUser) getCurrentUser();
if (!user.getEmailAddresses().contains(tagger.getEmailAddress())) {
return owner || canPerform(PUSH_TAG, PUSH_TAG_ANY);
}
// If the tag has a PGP signature, allow a lower level of permission
// than if it doesn't have a PGP signature.
//
if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) {
return owner || canPerform(PUSH_TAG, PUSH_TAG_SIGNED);
} else {
return owner || canPerform(PUSH_TAG, PUSH_TAG_ANNOTATED);
}
} else {
return false;
}
}
/**
* Determines whether the user can delete the Git ref controlled by this
* object.
*
* @return {@code true} if the user specified can delete a Git ref.
*/
public boolean canDelete() {
switch (getCurrentUser().getAccessPath()) {
case WEB:
return isOwner() || canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE);
case SSH:
return canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE);
default:
return false;
}
}
private boolean canPerform(ApprovalCategory.Id actionId, short level) {
final Set<AccountGroup.Id> groups = getCurrentUser().getEffectiveGroups();
int val = Integer.MIN_VALUE;
for (final RefRight right : getLocalRights()) {
if (right.getApprovalCategoryId().equals(actionId)
&& groups.contains(right.getAccountGroupId())) {
if (val < 0 && right.getMaxValue() > 0) {
// If one of the user's groups had denied them access, but
// this group grants them access, prefer the grant over
// the denial. We have to break the tie somehow and we
// prefer being "more open" to being "more closed".
//
val = right.getMaxValue();
} else {
// Otherwise we use the largest value we can get.
//
val = Math.max(right.getMaxValue(), val);
}
}
}
if (val == Integer.MIN_VALUE && actionId.canInheritFromWildProject()) {
for (final RefRight pr : getInheritedRights()) {
if (actionId.equals(pr.getApprovalCategoryId())
&& groups.contains(pr.getAccountGroupId())) {
val = Math.max(pr.getMaxValue(), val);
}
}
}
return val >= level;
}
private Collection<RefRight> getLocalRights() {
return filter(projectControl.getProjectState().getLocalRights());
}
private Collection<RefRight> getInheritedRights() {
return filter(projectControl.getProjectState().getInheritedRights());
}
private Collection<RefRight> filter(Collection<RefRight> all) {
List<RefRight> mine = new ArrayList<RefRight>(all.size());
for (RefRight right : all) {
if (matches(getRefName(), right.getRefPattern())) {
mine.add(right);
}
}
return mine;
}
public static boolean matches(String refName, String refPattern) {
if (refPattern.endsWith("/*")) {
String prefix = refPattern.substring(0, refPattern.length() - 1);
return refName.startsWith(prefix);
} else {
return refName.equals(refPattern);
}
}
}

View File

@@ -19,7 +19,7 @@ import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.CurrentSchemaVersion;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.config.SitePath;
@@ -189,12 +189,13 @@ public class SchemaCreator {
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
final ProjectRight approve =
new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(),
final RefRight approve =
new RefRight(new RefRight.Key(DEFAULT_WILD_NAME,
new RefRight.RefPattern("refs/heads/*"), cat.getId(),
sConfig.registeredGroupId));
approve.setMaxValue((short) 1);
approve.setMinValue((short) -1);
c.projectRights().insert(Collections.singleton(approve));
c.refRights().insert(Collections.singleton(approve));
}
private void initOwnerCategory(final ReviewDb c) throws OrmException {
@@ -225,29 +226,30 @@ public class SchemaCreator {
c.approvalCategories().insert(Collections.singleton(cat));
c.approvalCategoryValues().insert(vals);
final RefRight.RefPattern pattern = new RefRight.RefPattern("refs/*");
{
final ProjectRight read =
new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(),
sConfig.anonymousGroupId));
final RefRight read =
new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
cat.getId(), sConfig.anonymousGroupId));
read.setMaxValue((short) 1);
read.setMinValue((short) 1);
c.projectRights().insert(Collections.singleton(read));
c.refRights().insert(Collections.singleton(read));
}
{
final ProjectRight read =
new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(),
sConfig.registeredGroupId));
final RefRight read =
new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
cat.getId(), sConfig.registeredGroupId));
read.setMaxValue((short) 2);
read.setMinValue((short) 1);
c.projectRights().insert(Collections.singleton(read));
c.refRights().insert(Collections.singleton(read));
}
{
final ProjectRight read =
new ProjectRight(new ProjectRight.Key(DEFAULT_WILD_NAME, cat.getId(),
sConfig.adminGroupId));
final RefRight read =
new RefRight(new RefRight.Key(DEFAULT_WILD_NAME, pattern,
cat.getId(), sConfig.adminGroupId));
read.setMaxValue((short) 1);
read.setMinValue((short) 1);
c.projectRights().insert(Collections.singleton(read));
c.refRights().insert(Collections.singleton(read));
}
}

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_24.class;
private static final Class<? extends SchemaVersion> C = Schema_25.class;
public static class Module extends AbstractModule {
@Override

View File

@@ -0,0 +1,110 @@
// 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.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.RefRight;
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.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
class Schema_25 extends SchemaVersion {
private Set<ApprovalCategory.Id> nonActions;
@Inject
Schema_25(Provider<Schema_24> prior) {
super(prior);
}
@Override
protected void migrateData(ReviewDb db) throws OrmException, SQLException {
nonActions = new HashSet<ApprovalCategory.Id>();
for (ApprovalCategory c : db.approvalCategories().all()) {
if (!c.isAction()) {
nonActions.add(c.getId());
}
}
List<RefRight> rights = new ArrayList<RefRight>();
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
try {
ResultSet rs = stmt.executeQuery("SELECT * FROM project_rights");
try {
while (rs.next()) {
rights.add(toRefRight(rs));
}
} finally {
rs.close();
}
db.refRights().insert(rights);
stmt.execute("CREATE INDEX ref_rights_byCatGroup"
+ " ON ref_rights (category_id, group_id)");
} finally {
stmt.close();
}
}
private RefRight toRefRight(ResultSet rs) throws SQLException {
short min_value = rs.getShort("min_value");
short max_value = rs.getShort("max_value");
String category_id = rs.getString("category_id");
int group_id = rs.getInt("group_id");
String project_name = rs.getString("project_name");
ApprovalCategory.Id category = new ApprovalCategory.Id(category_id);
Project.NameKey project = new Project.NameKey(project_name);
AccountGroup.Id group = new AccountGroup.Id(group_id);
RefRight.RefPattern ref;
if (category.equals(ApprovalCategory.SUBMIT)
|| category.equals(ApprovalCategory.PUSH_HEAD)
|| nonActions.contains(category)) {
// Explicitly related to a branch head.
ref = new RefRight.RefPattern("refs/heads/*");
} else if (category.equals(ApprovalCategory.PUSH_TAG)) {
// Explicitly related to the tag namespace.
ref = new RefRight.RefPattern("refs/tags/*");
} else if (category.equals(ApprovalCategory.READ)
|| category.equals(ApprovalCategory.OWN)) {
// Currently these are project-wide rights, so apply that way.
ref = new RefRight.RefPattern("refs/*");
} else {
// Assume project wide for the default.
ref = new RefRight.RefPattern("refs/*");
}
RefRight.Key key = new RefRight.Key(project, ref, category, group);
RefRight r = new RefRight(key);
r.setMinValue(min_value);
r.setMaxValue(max_value);
return r;
}
}

View File

@@ -17,7 +17,7 @@ package com.google.gerrit.server.workflow;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
import java.util.HashMap;
@@ -89,7 +89,7 @@ public abstract class CategoryFunction {
public boolean isValid(final CurrentUser user, final ApprovalType at,
final FunctionState state) {
for (final ProjectRight pr : state.getAllRights(at)) {
for (final RefRight pr : state.getAllRights(at)) {
if (user.getEffectiveGroups().contains(pr.getAccountGroupId())
&& (pr.getMinValue() < 0 || pr.getMaxValue() > 0)) {
return true;

View File

@@ -23,12 +23,13 @@ import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.PatchSet;
import com.google.gerrit.reviewdb.PatchSetApproval;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ApprovalCategory.Id;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@@ -57,10 +58,10 @@ public class FunctionState {
new HashMap<ApprovalCategory.Id, Boolean>();
private final Change change;
private final ProjectState project;
private final Map<ApprovalCategory.Id, Collection<ProjectRight>> allRights =
new HashMap<ApprovalCategory.Id, Collection<ProjectRight>>();
private Map<ApprovalCategory.Id, Collection<ProjectRight>> projectRights;
private Map<ApprovalCategory.Id, Collection<ProjectRight>> inheritedRights;
private final Map<ApprovalCategory.Id, Collection<RefRight>> allRights =
new HashMap<ApprovalCategory.Id, Collection<RefRight>>();
private Map<ApprovalCategory.Id, Collection<RefRight>> RefRights;
private Map<ApprovalCategory.Id, Collection<RefRight>> inheritedRights;
private Set<PatchSetApproval> modified;
@Inject
@@ -135,39 +136,39 @@ public class FunctionState {
return Collections.emptySet();
}
public Collection<ProjectRight> getProjectRights(final ApprovalType at) {
return getProjectRights(id(at));
public Collection<RefRight> getRefRights(final ApprovalType at) {
return getRefRights(id(at));
}
public Collection<ProjectRight> getProjectRights(final ApprovalCategory.Id id) {
if (projectRights == null) {
projectRights = index(project.getLocalRights());
public Collection<RefRight> getRefRights(final ApprovalCategory.Id id) {
if (RefRights == null) {
RefRights = index(project.getLocalRights());
}
final Collection<ProjectRight> l = projectRights.get(id);
return l != null ? l : Collections.<ProjectRight> emptySet();
final Collection<RefRight> l = RefRights.get(id);
return l != null ? l : Collections.<RefRight> emptySet();
}
public Collection<ProjectRight> getWildcardRights(final ApprovalType at) {
public Collection<RefRight> getWildcardRights(final ApprovalType at) {
return getWildcardRights(id(at));
}
public Collection<ProjectRight> getWildcardRights(final ApprovalCategory.Id id) {
public Collection<RefRight> getWildcardRights(final ApprovalCategory.Id id) {
if (inheritedRights == null) {
inheritedRights = index(project.getInheritedRights());
}
final Collection<ProjectRight> l = inheritedRights.get(id);
return l != null ? l : Collections.<ProjectRight> emptySet();
final Collection<RefRight> l = inheritedRights.get(id);
return l != null ? l : Collections.<RefRight> emptySet();
}
public Collection<ProjectRight> getAllRights(final ApprovalType at) {
public Collection<RefRight> getAllRights(final ApprovalType at) {
return getAllRights(id(at));
}
public Collection<ProjectRight> getAllRights(final ApprovalCategory.Id id) {
Collection<ProjectRight> l = allRights.get(id);
public Collection<RefRight> getAllRights(final ApprovalCategory.Id id) {
Collection<RefRight> l = allRights.get(id);
if (l == null) {
l = new ArrayList<ProjectRight>();
l.addAll(getProjectRights(id));
l = new ArrayList<RefRight>();
l.addAll(getRefRights(id));
l.addAll(getWildcardRights(id));
l = Collections.unmodifiableCollection(l);
allRights.put(id, l);
@@ -175,18 +176,19 @@ public class FunctionState {
return l;
}
private static Map<Id, Collection<ProjectRight>> index(
final Collection<ProjectRight> rights) {
final HashMap<ApprovalCategory.Id, Collection<ProjectRight>> r;
private Map<Id, Collection<RefRight>> index(final Collection<RefRight> rights) {
final HashMap<ApprovalCategory.Id, Collection<RefRight>> r;
r = new HashMap<ApprovalCategory.Id, Collection<ProjectRight>>();
for (final ProjectRight pr : rights) {
Collection<ProjectRight> l = r.get(pr.getApprovalCategoryId());
if (l == null) {
l = new ArrayList<ProjectRight>();
r.put(pr.getApprovalCategoryId(), l);
r = new HashMap<ApprovalCategory.Id, Collection<RefRight>>();
for (final RefRight pr : rights) {
if (RefControl.matches(change.getDest().get(), pr.getRefPattern())) {
Collection<RefRight> l = r.get(pr.getApprovalCategoryId());
if (l == null) {
l = new ArrayList<RefRight>();
r.put(pr.getApprovalCategoryId(), l);
}
l.add(pr);
}
l.add(pr);
}
return r;
}
@@ -214,11 +216,11 @@ public class FunctionState {
/**
* Normalize the approval record to be inside the maximum range permitted by
* the ProjectRights granted to groups the account is a member of.
* the RefRights granted to groups the account is a member of.
* <p>
* If multiple ProjectRights are matched (assigned to different groups the
* account is a member of) the lowest minValue and the highest maxValue of the
* union of them is used.
* If multiple RefRights are matched (assigned to different groups the account
* is a member of) the lowest minValue and the highest maxValue of the union
* of them is used.
* <p>
* If the record's value was modified, its automatically marked as dirty.
*/
@@ -228,7 +230,7 @@ public class FunctionState {
// Find the maximal range actually granted to the user.
//
short minAllowed = 0, maxAllowed = 0;
for (final ProjectRight r : getAllRights(a.getCategoryId())) {
for (final RefRight r : getAllRights(a.getCategoryId())) {
final AccountGroup.Id grp = r.getAccountGroupId();
if (user.getEffectiveGroups().contains(grp)) {
minAllowed = (short) Math.min(minAllowed, r.getMinValue());

View File

@@ -17,7 +17,7 @@ package com.google.gerrit.server.workflow;
import com.google.gerrit.common.data.ApprovalType;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Change;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.server.CurrentUser;
/**
@@ -42,7 +42,7 @@ public class SubmitFunction extends CategoryFunction {
public boolean isValid(final CurrentUser user, final ApprovalType at,
final FunctionState state) {
if (valid(at, state)) {
for (final ProjectRight pr : state.getAllRights(at)) {
for (final RefRight pr : state.getAllRights(at)) {
if (user.getEffectiveGroups().contains(pr.getAccountGroupId())
&& pr.getMaxValue() > 0) {
return true;

View File

@@ -18,7 +18,7 @@ import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.SystemConfig;
import com.google.gerrit.server.workflow.NoOpFunction;
@@ -319,36 +319,43 @@ public class SchemaCreatorTest extends TestCase {
throws OrmException {
db.create();
final SystemConfig config = db.getSystemConfig();
assertDefaultRight(config.anonymousGroupId, ApprovalCategory.READ, 1, 1);
assertDefaultRight("refs/*", config.anonymousGroupId,
ApprovalCategory.READ, 1, 1);
}
public void testCreateSchema_DefaultAccess_RegisteredUsers()
throws OrmException {
db.create();
final SystemConfig config = db.getSystemConfig();
assertDefaultRight(config.registeredGroupId, ApprovalCategory.READ, 1, 2);
assertDefaultRight(config.registeredGroupId, codeReview, -1, 1);
assertDefaultRight("refs/*", config.registeredGroupId,
ApprovalCategory.READ, 1, 2);
assertDefaultRight("refs/heads/*", config.registeredGroupId, codeReview,
-1, 1);
}
public void testCreateSchema_DefaultAccess_Administrators()
throws OrmException {
db.create();
final SystemConfig config = db.getSystemConfig();
assertDefaultRight(config.adminGroupId, ApprovalCategory.READ, 1, 1);
assertDefaultRight("refs/*", config.adminGroupId, ApprovalCategory.READ, 1,
1);
}
private void assertDefaultRight(final AccountGroup.Id group,
final ApprovalCategory.Id category, int min, int max) throws OrmException {
private void assertDefaultRight(final String pattern,
final AccountGroup.Id group, final ApprovalCategory.Id category, int min,
int max) throws OrmException {
final ReviewDb c = db.open();
try {
final SystemConfig cfg;
final Project all;
final ProjectRight right;
final RefRight right;
cfg = c.systemConfig().get(new SystemConfig.Key());
all = c.projects().get(cfg.wildProjectName);
right = c.projectRights().get( //
new ProjectRight.Key(all.getNameKey(), category, group));
right =
c.refRights().get(
new RefRight.Key(all.getNameKey(), new RefRight.RefPattern(
pattern), category, group));
assertNotNull(right);
assertEquals(all.getNameKey(), right.getProjectNameKey());

View File

@@ -17,7 +17,7 @@ package com.google.gerrit.sshd.commands;
import com.google.gerrit.reviewdb.AccountGroup;
import com.google.gerrit.reviewdb.ApprovalCategory;
import com.google.gerrit.reviewdb.Project;
import com.google.gerrit.reviewdb.ProjectRight;
import com.google.gerrit.reviewdb.RefRight;
import com.google.gerrit.reviewdb.ReviewDb;
import com.google.gerrit.reviewdb.Project.SubmitType;
import com.google.gerrit.server.config.AuthConfig;
@@ -109,12 +109,13 @@ final class AdminCreateProject extends BaseCommand {
private void createProject() throws OrmException {
final Project.NameKey newProjectNameKey = new Project.NameKey(projectName);
final ProjectRight.Key prk =
new ProjectRight.Key(newProjectNameKey, ApprovalCategory.OWN, ownerId);
final ProjectRight pr = new ProjectRight(prk);
final RefRight.Key prk =
new RefRight.Key(newProjectNameKey, new RefRight.RefPattern("refs/*"),
ApprovalCategory.OWN, ownerId);
final RefRight pr = new RefRight(prk);
pr.setMaxValue((short) 1);
pr.setMinValue((short) 1);
db.projectRights().insert(Collections.singleton(pr));
db.refRights().insert(Collections.singleton(pr));
final Project newProject = new Project(newProjectNameKey);
newProject.setDescription(projectDescription);