From 75afdfdc843b7dfb4c9dc2676e162b21d7df7684 Mon Sep 17 00:00:00 2001 From: Nico Sallembien Date: Mon, 25 Jan 2010 09:05:17 -0800 Subject: [PATCH] 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 --- .../common/data/ProjectAdminService.java | 6 +- .../gerrit/common/data/ProjectDetail.java | 6 +- .../gerrit/client/admin/AdminConstants.java | 1 + .../client/admin/AdminConstants.properties | 1 + .../client/admin/ProjectRightsPanel.java | 199 ++++++++------ .../PatchSetPublishDetailFactory.java | 10 +- .../gerrit/httpd/rpc/project/AddBranch.java | 13 +- ...{AddProjectRight.java => AddRefRight.java} | 114 +++++--- .../httpd/rpc/project/DeleteBranches.java | 2 +- ...rojectRights.java => DeleteRefRights.java} | 24 +- .../httpd/rpc/project/OwnedProjects.java | 4 +- .../rpc/project/ProjectAdminServiceImpl.java | 35 ++- .../rpc/project/ProjectDetailFactory.java | 29 ++- .../httpd/rpc/project/ProjectModule.java | 4 +- .../{ProjectRight.java => RefRight.java} | 67 +++-- ...ctRightAccess.java => RefRightAccess.java} | 11 +- .../com/google/gerrit/reviewdb/ReviewDb.java | 6 +- .../google/gerrit/reviewdb/index_generic.sql | 6 +- .../google/gerrit/reviewdb/index_postgres.sql | 6 +- .../gerrit/server/git/ReceiveCommits.java | 138 +++++----- .../gerrit/server/project/ChangeControl.java | 26 +- .../server/project/ProjectCacheImpl.java | 8 +- .../gerrit/server/project/ProjectControl.java | 100 +++---- .../gerrit/server/project/ProjectState.java | 16 +- .../gerrit/server/project/RefControl.java | 243 ++++++++++++++++++ .../gerrit/server/schema/SchemaCreator.java | 34 +-- .../gerrit/server/schema/SchemaVersion.java | 2 +- .../gerrit/server/schema/Schema_25.java | 110 ++++++++ .../server/workflow/CategoryFunction.java | 4 +- .../gerrit/server/workflow/FunctionState.java | 74 +++--- .../server/workflow/SubmitFunction.java | 4 +- .../server/schema/SchemaCreatorTest.java | 27 +- .../sshd/commands/AdminCreateProject.java | 11 +- 33 files changed, 901 insertions(+), 440 deletions(-) rename gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/{AddProjectRight.java => AddRefRight.java} (53%) rename gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/{DeleteProjectRights.java => DeleteRefRights.java} (75%) rename gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/{ProjectRight.java => RefRight.java} (58%) rename gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/{ProjectRightAccess.java => RefRightAccess.java} (73%) create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java create mode 100644 gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_25.java diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java index def13a20ee..b3c7183c0a 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java @@ -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 callback); @SignInRequired - void deleteRight(Project.NameKey projectName, Set ids, + void deleteRight(Project.NameKey projectName, Set ids, AsyncCallback 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 callback); @SignInRequired diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java index 9c4619a8b4..ca33d1639c 100644 --- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java +++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectDetail.java @@ -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 groups; - public List rights; + public List rights; public ProjectDetail() { } @@ -37,7 +37,7 @@ public class ProjectDetail { groups = g; } - public void setRights(final List r) { + public void setRights(final List r) { rights = r; } } diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java index bdbbd41714..00c41119f1 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java @@ -62,6 +62,7 @@ public interface AdminConstants extends Constants { String columnProjectDescription(); String columnApprovalCategory(); String columnRightRange(); + String columnRefName(); String columnBranchName(); String columnBranchRevision(); diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties index 4b24fe6a1c..23d25395de 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties @@ -43,6 +43,7 @@ columnGroupDescription = Description columnProjectDescription = Description columnApprovalCategory = Category columnRightRange = Permitted Range +columnRefName = Reference Name columnBranchName = Branch Name columnBranchRevision = Revision diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java index 824940055f..001c7f28cc 100644 --- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java +++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectRightsPanel.java @@ -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() { + refPattern, min.getValue(), max.getValue(), + new GerritCallback() { 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 { + private class RightsTable extends FancyFlexTable { 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 ids = new HashSet(); + final HashSet refRightIds = new HashSet(); 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() { - 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 updateTable = + new GerritCallback() { + @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 groups, - final List result) { + final List 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 groups, final ProjectRight k) { + final Map 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) { diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java index ca12b8fa36..53fe746245 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java @@ -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 } private void computeAllowed(final Set am, - final Collection list) { - for (final ProjectRight r : list) { + final Collection list) { + for (final RefRight r : list) { if (!am.contains(r.getAccountGroupId())) { continue; } + if (!RefControl.matches(change.getDest().get(), r.getRefPattern())) { + continue; + } Set s = allowed.get(r.getApprovalCategoryId()); if (s == null) { diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java index 5d77d2d3e0..0b6557ad9e 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddBranch.java @@ -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> { @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> { 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); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddProjectRight.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java similarity index 53% rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddProjectRight.java rename to gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java index b12c92f489..3d3ba3b2c4 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddProjectRight.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/AddRefRight.java @@ -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 { +class AddRefRight extends Handler { 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 { 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 { 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 { @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(); } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java index 526afe528c..772a2fd2e3 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java @@ -75,7 +75,7 @@ class DeleteBranches extends Handler> { 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()); } } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteProjectRights.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java similarity index 75% rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteProjectRights.java rename to gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java index d476d8926e..323a5a65c0 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteProjectRights.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteRefRights.java @@ -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 { +class DeleteRefRights extends Handler { interface Factory { - DeleteProjectRights create(@Assisted Project.NameKey projectName, - @Assisted Set toRemove); + DeleteRefRights create(@Assisted Project.NameKey projectName, + @Assisted Set toRemove); } private final ProjectControl.Factory projectControlFactory; @@ -40,14 +40,14 @@ class DeleteProjectRights extends Handler { private final ReviewDb db; private final Project.NameKey projectName; - private final Set toRemove; + private final Set 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 toRemove) { + @Assisted final Set toRemove) { this.projectControlFactory = projectControlFactory; this.projectCache = projectCache; this.db = db; @@ -61,16 +61,16 @@ class DeleteProjectRights extends Handler { 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()); diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/OwnedProjects.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/OwnedProjects.java index 4e7267f00f..d53d845349 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/OwnedProjects.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/OwnedProjects.java @@ -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> { final HashSet seen = new HashSet(); result = new ArrayList(); 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)) { diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java index 7a9ba03a33..80e1949a55 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java @@ -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> callback) { ownedProjectsFactory.create().to(callback); } + @Override public void projectDetail(final Project.NameKey projectName, final AsyncCallback callback) { projectDetailFactory.create(projectName).to(callback); } + @Override public void changeProjectSettings(final Project update, final AsyncCallback callback) { changeProjectSettingsFactory.create(update).to(callback); } + @Override public void deleteRight(final Project.NameKey projectName, - final Set toRemove, - final AsyncCallback callback) { - deleteProjectRightsFactory.create(projectName, toRemove).to(callback); + final Set toRemove, final AsyncCallback 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 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> callback) { listBranchesFactory.create(projectName).to(callback); } + @Override public void deleteBranch(final Project.NameKey projectName, final Set toRemove, final AsyncCallback> callback) { deleteBranchesFactory.create(projectName, toRemove).to(callback); } + @Override public void addBranch(final Project.NameKey projectName, final String branchName, final String startingRevision, final AsyncCallback> callback) { diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java index 7106f32283..4187525162 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectDetailFactory.java @@ -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 { detail.setProject(projectState.getProject()); groups = new HashMap(); - final List rights = new ArrayList(); - for (final ProjectRight p : projectState.getLocalRights()) { - rights.add(p); - wantGroup(p.getAccountGroupId()); + final List refRights = new ArrayList(); + 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() { + Collections.sort(refRights, new Comparator() { @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 { 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; } diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java index 34de2fd666..9c8affdb55 100644 --- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java +++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java @@ -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); diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectRight.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java similarity index 58% rename from gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectRight.java rename to gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java index af78d4c2dc..6667f540ee 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectRight.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRight.java @@ -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> { + 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 { 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() { diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectRightAccess.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRightAccess.java similarity index 73% rename from gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectRightAccess.java rename to gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRightAccess.java index 49ccda72a0..a42ff2cd2c 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ProjectRightAccess.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/RefRightAccess.java @@ -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 { +public interface RefRightAccess extends Access { @PrimaryKey("key") - ProjectRight get(ProjectRight.Key key) throws OrmException; + RefRight get(RefRight.Key refRight) throws OrmException; @Query("WHERE key.projectName = ?") - ResultSet byProject(Project.NameKey name) throws OrmException; + ResultSet byProject(Project.NameKey project) throws OrmException; @Query("WHERE key.categoryId = ? AND key.groupId = ?") - ResultSet byCategoryGroup(ApprovalCategory.Id cat, + ResultSet byCategoryGroup(ApprovalCategory.Id cat, AccountGroup.Id group) throws OrmException; } diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java index 168112e4fe..287960f380 100644 --- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java +++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/ReviewDb.java @@ -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; diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql index ea6b9d9c80..d09adb85b4 100644 --- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql +++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_generic.sql @@ -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); -- ********************************************************************* diff --git a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql index a74e2f725c..86f5a93dd5 100644 --- a/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql +++ b/gerrit-reviewdb/src/main/resources/com/google/gerrit/reviewdb/index_postgres.sql @@ -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); -- ********************************************************************* diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java index aaf81dc057..5f5c74ab5c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java @@ -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"); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java index fd6fc85d1c..306eee16d0 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java @@ -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) { diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java index 389415ac4d..3e3e9bf13d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java @@ -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 get() { + public Collection get() { return ProjectCacheImpl.this.get(wildProject).getLocalRights(); } }; @@ -90,8 +90,8 @@ public class ProjectCacheImpl implements ProjectCache { return null; } - final Collection rights = - Collections.unmodifiableCollection(db.projectRights().byProject( + final Collection rights = + Collections.unmodifiableCollection(db.refRights().byProject( p.getNameKey()).toList()); return projectStateFactory.create(p, rights, inheritedRights); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java index c3e54c9cf6..2d8a1d786b 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java @@ -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 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); - } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java index 5e827fbdc3..307377a830 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java @@ -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 localRights, + ProjectState create(Project project, Collection localRights, InheritedRights inheritedRights); } public interface InheritedRights { - Collection get(); + Collection get(); } private final AnonymousUser anonymousUser; private final Project.NameKey wildProject; private final Project project; - private final Collection localRights; + private final Collection localRights; private final InheritedRights inheritedRights; private final Set 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 rights, + @Assisted final Collection rights, @Assisted final InheritedRights inheritedRights) { this.anonymousUser = anonymousUser; this.wildProject = wildProject; @@ -62,7 +62,7 @@ public class ProjectState { this.inheritedRights = inheritedRights; final HashSet groups = new HashSet(); - 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 getLocalRights() { + public Collection getLocalRights() { return localRights; } /** Get the rights this project inherits from the wild project. */ - public Collection getInheritedRights() { + public Collection getInheritedRights() { if (isSpecialWildProject()) { return Collections.emptyList(); } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java new file mode 100644 index 0000000000..32df41e374 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java @@ -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 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 getLocalRights() { + return filter(projectControl.getProjectState().getLocalRights()); + } + + private Collection getInheritedRights() { + return filter(projectControl.getProjectState().getInheritedRights()); + } + + private Collection filter(Collection all) { + List mine = new ArrayList(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); + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java index ca3b3b36b2..ca9e16a552 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaCreator.java @@ -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)); } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java index d9ec7b555b..115e2f5f46 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java @@ -32,7 +32,7 @@ import java.util.List; /** A version of the database schema. */ public abstract class SchemaVersion { /** The current schema version. */ - private static final Class C = Schema_24.class; + private static final Class C = Schema_25.class; public static class Module extends AbstractModule { @Override diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_25.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_25.java new file mode 100644 index 0000000000..063f676fc1 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_25.java @@ -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 nonActions; + + @Inject + Schema_25(Provider prior) { + super(prior); + } + + @Override + protected void migrateData(ReviewDb db) throws OrmException, SQLException { + nonActions = new HashSet(); + for (ApprovalCategory c : db.approvalCategories().all()) { + if (!c.isAction()) { + nonActions.add(c.getId()); + } + } + + List rights = new ArrayList(); + 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; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java index 38a9fd4730..0c4abe566d 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/CategoryFunction.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java index 69f571d47c..0b85db1ce5 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/FunctionState.java @@ -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(); private final Change change; private final ProjectState project; - private final Map> allRights = - new HashMap>(); - private Map> projectRights; - private Map> inheritedRights; + private final Map> allRights = + new HashMap>(); + private Map> RefRights; + private Map> inheritedRights; private Set modified; @Inject @@ -135,39 +136,39 @@ public class FunctionState { return Collections.emptySet(); } - public Collection getProjectRights(final ApprovalType at) { - return getProjectRights(id(at)); + public Collection getRefRights(final ApprovalType at) { + return getRefRights(id(at)); } - public Collection getProjectRights(final ApprovalCategory.Id id) { - if (projectRights == null) { - projectRights = index(project.getLocalRights()); + public Collection getRefRights(final ApprovalCategory.Id id) { + if (RefRights == null) { + RefRights = index(project.getLocalRights()); } - final Collection l = projectRights.get(id); - return l != null ? l : Collections. emptySet(); + final Collection l = RefRights.get(id); + return l != null ? l : Collections. emptySet(); } - public Collection getWildcardRights(final ApprovalType at) { + public Collection getWildcardRights(final ApprovalType at) { return getWildcardRights(id(at)); } - public Collection getWildcardRights(final ApprovalCategory.Id id) { + public Collection getWildcardRights(final ApprovalCategory.Id id) { if (inheritedRights == null) { inheritedRights = index(project.getInheritedRights()); } - final Collection l = inheritedRights.get(id); - return l != null ? l : Collections. emptySet(); + final Collection l = inheritedRights.get(id); + return l != null ? l : Collections. emptySet(); } - public Collection getAllRights(final ApprovalType at) { + public Collection getAllRights(final ApprovalType at) { return getAllRights(id(at)); } - public Collection getAllRights(final ApprovalCategory.Id id) { - Collection l = allRights.get(id); + public Collection getAllRights(final ApprovalCategory.Id id) { + Collection l = allRights.get(id); if (l == null) { - l = new ArrayList(); - l.addAll(getProjectRights(id)); + l = new ArrayList(); + 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> index( - final Collection rights) { - final HashMap> r; + private Map> index(final Collection rights) { + final HashMap> r; - r = new HashMap>(); - for (final ProjectRight pr : rights) { - Collection l = r.get(pr.getApprovalCategoryId()); - if (l == null) { - l = new ArrayList(); - r.put(pr.getApprovalCategoryId(), l); + r = new HashMap>(); + for (final RefRight pr : rights) { + if (RefControl.matches(change.getDest().get(), pr.getRefPattern())) { + Collection l = r.get(pr.getApprovalCategoryId()); + if (l == null) { + l = new ArrayList(); + 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. *

- * 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. *

* 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()); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/SubmitFunction.java b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/SubmitFunction.java index b9805796f2..48c7f71ff1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/workflow/SubmitFunction.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/workflow/SubmitFunction.java @@ -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; diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java index 7164e99de8..c014692fc3 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java @@ -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()); diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateProject.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateProject.java index b4e626f373..2562f8ab36 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateProject.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminCreateProject.java @@ -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);