Allow per-branch OWN +1 to delegate branch ownership

We now correctly honor OWN +1 on reference names other than refs/* to
delegate ownership (aka access control management) to a subnamespace
within the larger reference namespace of the project.

This permits a project owner to hand out control over a particular
namespace to another group, e.g. refs/heads/qa/* can be delegated
fully to the QA group, allowing its members to manage the Submit
and Push Branch access control rules only within that subspace.

The client UI is still really crude here.  For example, it allows
you to try to change global project settings, but then throws back
the server exception when the server rejects your edit.  It also
allows you to try to delete access rights which you can't delete,
and again just throws back the server error message.  We really
could be smarter here in the future, but the client doesn't have
the RefControl processing logic required to understand which rows
it can change, and which it cannot.

Change-Id: I83c607eaea92d9fd7e40ed93f38b2c4478ec842f
Signed-off-by: Shawn O. Pearce <sop@google.com>
Reviewed-by: Nico Sallembien <nsallembien@google.com>
This commit is contained in:
Shawn O. Pearce 2010-01-27 15:55:09 -08:00
parent 5aa47fee31
commit d9d7fe02b0
9 changed files with 78 additions and 10 deletions

View File

@ -18,6 +18,7 @@ import com.google.gerrit.common.errors.CorruptEntityException;
import com.google.gerrit.common.errors.NoSuchEntityException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.NoSuchRefException;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
@ -68,6 +69,9 @@ public abstract class Handler<T> implements Callable<T> {
} catch (NoSuchProjectException e) {
callback.onFailure(new NoSuchEntityException());
} catch (NoSuchRefException e) {
callback.onFailure(new NoSuchEntityException());
} catch (NoSuchChangeException e) {
callback.onFailure(new NoSuchEntityException());

View File

@ -88,8 +88,7 @@ class AddBranch extends Handler<List<Branch>> {
public List<Branch> call() throws NoSuchProjectException,
InvalidNameException, InvalidRevisionException, IOException {
final ProjectControl projectControl =
projectControlFactory.validateFor(projectName, ProjectControl.OWNER
| ProjectControl.VISIBLE);
projectControlFactory.controlFor(projectName);
String refname = branchName;
while (refname.startsWith("/")) {

View File

@ -27,8 +27,10 @@ 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.project.NoSuchProjectException;
import com.google.gerrit.server.project.NoSuchRefException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
@ -97,9 +99,9 @@ class AddRefRight extends Handler<ProjectDetail> {
@Override
public ProjectDetail call() throws NoSuchProjectException, OrmException,
NoSuchGroupException, InvalidNameException {
NoSuchGroupException, InvalidNameException, NoSuchRefException {
final ProjectControl projectControl =
projectControlFactory.ownerFor(projectName);
projectControlFactory.controlFor(projectName);
final ApprovalType at = approvalTypes.getApprovalType(categoryId);
if (at == null || at.getValue(min) == null || at.getValue(max) == null) {
@ -150,6 +152,10 @@ class AddRefRight extends Handler<ProjectDetail> {
}
}
if (!controlForRef(projectControl, refPattern).isOwner()) {
throw new NoSuchRefException(refPattern);
}
// TODO Support per-branch READ access.
if (ApprovalCategory.READ.equals(categoryId)
&& !refPattern.equals("refs/*")) {
@ -178,4 +184,11 @@ class AddRefRight extends Handler<ProjectDetail> {
projectCache.evict(projectControl.getProject());
return projectDetailFactory.create(projectName).call();
}
private RefControl controlForRef(ProjectControl p, String ref) {
if (ref.endsWith("/*")) {
ref = ref.substring(0, ref.length() - 1);
}
return p.controlForRef(ref);
}
}

View File

@ -68,8 +68,7 @@ class DeleteBranches extends Handler<Set<Branch.NameKey>> {
public Set<Branch.NameKey> call() throws NoSuchProjectException,
RepositoryNotFoundException {
final ProjectControl projectControl =
projectControlFactory.validateFor(projectName, ProjectControl.OWNER
| ProjectControl.VISIBLE);
projectControlFactory.controlFor(projectName);
for (Branch.NameKey k : toRemove) {
if (!projectName.equals(k.getParentKey())) {

View File

@ -19,8 +19,10 @@ import com.google.gerrit.reviewdb.Project;
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.NoSuchRefException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtjsonrpc.client.VoidResult;
import com.google.gwtorm.client.OrmException;
import com.google.inject.Inject;
@ -57,14 +59,18 @@ class DeleteRefRights extends Handler<VoidResult> {
}
@Override
public VoidResult call() throws NoSuchProjectException, OrmException {
public VoidResult call() throws NoSuchProjectException, OrmException,
NoSuchRefException {
final ProjectControl projectControl =
projectControlFactory.ownerFor(projectName);
projectControlFactory.controlFor(projectName);
for (final RefRight.Key k : toRemove) {
if (!projectName.equals(k.getProjectNameKey())) {
throw new IllegalArgumentException("All keys must be from same project");
}
if (!controlForRef(projectControl, k.getRefPattern()).isOwner()) {
throw new NoSuchRefException(k.getRefPattern());
}
}
for (final RefRight.Key k : toRemove) {
@ -76,4 +82,11 @@ class DeleteRefRights extends Handler<VoidResult> {
projectCache.evict(projectControl.getProject());
return VoidResult.INSTANCE;
}
private RefControl controlForRef(ProjectControl p, String ref) {
if (ref.endsWith("/*")) {
ref = ref.substring(0, ref.length() - 1);
}
return p.controlForRef(ref);
}
}

View File

@ -66,8 +66,10 @@ class OwnedProjects extends Handler<List<Project>> {
continue;
}
try {
ProjectControl c = projectControlFactory.ownerFor(name);
result.add(c.getProject());
ProjectControl c = projectControlFactory.controlFor(name);
if (c.isOwnerAnyRef()) {
result.add(c.getProject());
}
} catch (NoSuchProjectException e) {
continue;
}

View File

@ -84,6 +84,10 @@ public final class RefRight {
return projectName;
}
public String getRefPattern() {
return refPattern.get();
}
@Override
public com.google.gwtorm.client.Key<?>[] members() {
return new com.google.gwtorm.client.Key<?>[] {refPattern, categoryId,

View File

@ -0,0 +1,28 @@
// 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;
/** Indicates the reference does not exist. */
public class NoSuchRefException extends Exception {
private static final long serialVersionUID = 1L;
public NoSuchRefException(final String ref) {
this(ref, null);
}
public NoSuchRefException(final String ref, final Throwable why) {
super(ref, why);
}
}

View File

@ -124,6 +124,12 @@ public class ProjectControl {
|| getCurrentUser().isAdministrator();
}
/** Does this user have ownership on at least one reference name? */
public boolean isOwnerAnyRef() {
return canPerformOnAnyRef(ApprovalCategory.OWN, (short) 1)
|| getCurrentUser().isAdministrator();
}
/** @return true if the user can upload to at least one reference */
public boolean canUploadToAtLeastOneRef() {
return canPerformOnAnyRef(ApprovalCategory.READ, (short) 2);