Merge changes from topic 'permission-backend'

* changes:
  Replace canUpload with CREATE_CHANGE
  Convert GetAccess and canAddRefs to PermissionBackend
  Convert forge author, committer, server to PermissionBackend
  Push option bypass-review disables validation
This commit is contained in:
David Pursehouse 2017-06-14 06:55:26 +00:00 committed by Gerrit Code Review
commit 64e43c24ef
21 changed files with 432 additions and 272 deletions

View File

@ -42,9 +42,9 @@ import com.google.gerrit.extensions.common.ChangeMessageInfo;
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.MergeInput;
import com.google.gerrit.extensions.common.RevisionInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.reviewdb.client.Branch;
@ -192,7 +192,7 @@ public class CreateChangeIT extends AbstractDaemonTest {
ChangeInput in = newChangeInput(ChangeStatus.NEW);
in.branch = "invisible-branch";
assertCreateFails(in, AuthException.class, "cannot upload review");
assertCreateFails(in, ResourceNotFoundException.class, "");
}
@Test

View File

@ -14,6 +14,10 @@
package com.google.gerrit.httpd.rpc.project;
import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
import static com.google.gerrit.server.permissions.RefPermission.READ;
import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupDescription;
@ -39,10 +43,10 @@ import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
@ -122,10 +126,11 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
}
}
final RefControl metaConfigControl = pc.controlForRef(RefNames.REFS_CONFIG);
List<AccessSection> local = new ArrayList<>();
Set<String> ownerOf = new HashSet<>();
Map<AccountGroup.UUID, Boolean> visibleGroups = new HashMap<>();
PermissionBackend.ForProject perm = permissionBackend.user(user).project(projectName);
boolean checkReadConfig = check(perm, RefNames.REFS_CONFIG, READ);
for (AccessSection section : config.getAccessSections()) {
String name = section.getName();
@ -134,20 +139,19 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
local.add(section);
ownerOf.add(name);
} else if (metaConfigControl.isVisible()) {
} else if (checkReadConfig) {
local.add(section);
}
} else if (RefConfigSection.isValid(name)) {
RefControl rc = pc.controlForRef(name);
if (rc.isOwner()) {
if (pc.controlForRef(name).isOwner()) {
local.add(section);
ownerOf.add(name);
} else if (metaConfigControl.isVisible()) {
} else if (checkReadConfig) {
local.add(section);
} else if (rc.isVisible()) {
} else if (check(perm, name, READ)) {
// Filter the section to only add rules describing groups that
// are visible to the current-user. This includes any group the
// user is a member of, as well as groups they own or that
@ -205,17 +209,17 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
detail.setInheritsFrom(config.getProject().getParent(allProjectsName));
if (projectName.equals(allProjectsName)) {
if (pc.isOwner()) {
ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
}
if (projectName.equals(allProjectsName)
&& permissionBackend.user(user).testOrFalse(ADMINISTRATE_SERVER)) {
ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
}
detail.setLocal(local);
detail.setOwnerOf(ownerOf);
detail.setCanUpload(
metaConfigControl.isVisible() && (pc.isOwner() || metaConfigControl.canUpload()));
detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible());
pc.isOwner()
|| (checkReadConfig && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)));
detail.setConfigVisible(pc.isOwner() || checkReadConfig);
detail.setGroupInfo(buildGroupInfo(local));
detail.setLabelTypes(pc.getLabelTypes());
detail.setFileHistoryLinks(getConfigFileLogLinks(projectName.get()));
@ -257,4 +261,14 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
}
return pc;
}
private static boolean check(PermissionBackend.ForProject ctx, String ref, RefPermission perm)
throws PermissionBackendException {
try {
ctx.ref(ref).check(perm);
return true;
} catch (AuthException denied) {
return false;
}
}
}

View File

@ -22,6 +22,7 @@ import com.google.gerrit.common.data.GlobalCapability;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
@ -39,9 +40,11 @@ import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.group.SystemGroupBackend;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.project.SetParent;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.UpdateException;
@ -68,6 +71,7 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
}
private final ReviewDb db;
private final PermissionBackend permissionBackend;
private final Sequences seq;
private final Provider<PostReviewers> reviewersProvider;
private final ProjectCache projectCache;
@ -78,6 +82,7 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
@Inject
ReviewProjectAccess(
final ProjectControl.Factory projectControlFactory,
PermissionBackend permissionBackend,
GroupBackend groupBackend,
MetaDataUpdate.User metaDataUpdateFactory,
ReviewDb db,
@ -107,6 +112,7 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
message,
false);
this.db = db;
this.permissionBackend = permissionBackend;
this.seq = seq;
this.reviewersProvider = reviewersProvider;
this.projectCache = projectCache;
@ -124,13 +130,23 @@ public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
ProjectConfig config,
MetaDataUpdate md,
boolean parentProjectUpdate)
throws IOException, OrmException, PermissionDeniedException {
RefControl refsMetaConfigControl = projectControl.controlForRef(RefNames.REFS_CONFIG);
if (!refsMetaConfigControl.isVisible()) {
throws IOException, OrmException, PermissionDeniedException, PermissionBackendException {
PermissionBackend.ForRef metaRef =
permissionBackend
.user(projectControl.getUser())
.project(projectControl.getProject().getNameKey())
.ref(RefNames.REFS_CONFIG);
try {
metaRef.check(RefPermission.READ);
} catch (AuthException denied) {
throw new PermissionDeniedException(RefNames.REFS_CONFIG + " not visible");
}
if (!projectControl.isOwner() && !refsMetaConfigControl.canUpload()) {
throw new PermissionDeniedException("cannot upload to " + RefNames.REFS_CONFIG);
if (!projectControl.isOwner()) {
try {
metaRef.check(RefPermission.CREATE_CHANGE);
} catch (AuthException denied) {
throw new PermissionDeniedException("cannot create change for " + RefNames.REFS_CONFIG);
}
}
md.setInsertChangeId(true);

View File

@ -20,6 +20,7 @@ import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.GetAccess;
import com.google.inject.Inject;
import java.io.IOException;
@ -48,11 +49,11 @@ public class ListAccess implements RestReadView<TopLevelResource> {
@Override
public Map<String, ProjectAccessInfo> apply(TopLevelResource resource)
throws ResourceNotFoundException, ResourceConflictException, IOException {
throws ResourceNotFoundException, ResourceConflictException, IOException,
PermissionBackendException {
Map<String, ProjectAccessInfo> access = new TreeMap<>();
for (String p : projects) {
Project.NameKey projectName = new Project.NameKey(p);
access.put(p, getAccess.apply(projectName));
access.put(p, getAccess.apply(new Project.NameKey(p)));
}
return access;
}

View File

@ -53,6 +53,7 @@ import com.google.gerrit.server.notedb.ChangeNotes;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.notedb.NotesMigration;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
@ -90,6 +91,7 @@ public class ChangeInserter implements InsertChangeOp {
private static final Logger log = LoggerFactory.getLogger(ChangeInserter.class);
private final PermissionBackend permissionBackend;
private final ProjectControl.GenericFactory projectControlFactory;
private final IdentifiedUser.GenericFactory userFactory;
private final ChangeControl.GenericFactory changeControlFactory;
@ -138,6 +140,7 @@ public class ChangeInserter implements InsertChangeOp {
@Inject
ChangeInserter(
PermissionBackend permissionBackend,
ProjectControl.GenericFactory projectControlFactory,
IdentifiedUser.GenericFactory userFactory,
ChangeControl.GenericFactory changeControlFactory,
@ -154,6 +157,7 @@ public class ChangeInserter implements InsertChangeOp {
@Assisted Change.Id changeId,
@Assisted ObjectId commitId,
@Assisted String refName) {
this.permissionBackend = permissionBackend;
this.projectControlFactory = projectControlFactory;
this.userFactory = userFactory;
this.changeControlFactory = changeControlFactory;
@ -543,6 +547,8 @@ public class ChangeInserter implements InsertChangeOp {
return;
}
PermissionBackend.ForRef perm =
permissionBackend.user(ctx.getUser()).project(ctx.getProject()).ref(refName);
try {
RefControl refControl =
projectControlFactory.controlFor(ctx.getProject(), ctx.getUser()).controlForRef(refName);
@ -555,7 +561,7 @@ public class ChangeInserter implements InsertChangeOp {
commitId,
ctx.getIdentifiedUser())) {
commitValidatorsFactory
.forGerritCommits(refControl, new NoSshInfo(), ctx.getRevWalk())
.forGerritCommits(perm, refControl, new NoSshInfo(), ctx.getRevWalk())
.validate(event);
}
} catch (CommitValidationException e) {

View File

@ -14,23 +14,21 @@
package com.google.gerrit.server.change;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
@ -45,59 +43,54 @@ import java.io.IOException;
public class CherryPick
extends RetryingRestModifyView<RevisionResource, CherryPickInput, ChangeInfo>
implements UiAction<RevisionResource> {
private final Provider<ReviewDb> dbProvider;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
private final CherryPickChange cherryPickChange;
private final ChangeJson.Factory json;
@Inject
CherryPick(
PermissionBackend permissionBackend,
Provider<CurrentUser> user,
RetryHelper retryHelper,
Provider<ReviewDb> dbProvider,
CherryPickChange cherryPickChange,
ChangeJson.Factory json) {
super(retryHelper);
this.dbProvider = dbProvider;
this.permissionBackend = permissionBackend;
this.user = user;
this.cherryPickChange = cherryPickChange;
this.json = json;
}
@Override
protected ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, RevisionResource revision, CherryPickInput input)
throws OrmException, IOException, UpdateException, RestApiException {
final ChangeControl control = revision.getControl();
public ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, RevisionResource rsrc, CherryPickInput input)
throws OrmException, IOException, UpdateException, RestApiException,
PermissionBackendException {
input.parent = input.parent == null ? 1 : input.parent;
if (input.message == null || input.message.trim().isEmpty()) {
throw new BadRequestException("message must be non-empty");
} else if (input.destination == null || input.destination.trim().isEmpty()) {
throw new BadRequestException("destination must be non-empty");
}
if (!control.isVisible(dbProvider.get())) {
throw new AuthException("Cherry pick not permitted");
}
ProjectControl projectControl = control.getProjectControl();
Capable capable = projectControl.canPushToAtLeastOneRef();
if (capable != Capable.OK) {
throw new AuthException(capable.getMessage());
}
RefControl refControl = projectControl.controlForRef(RefNames.fullName(input.destination));
if (!refControl.canUpload()) {
throw new AuthException(
"Not allowed to cherry pick "
+ revision.getChange().getId().toString()
+ " to "
+ input.destination);
}
String refName = RefNames.fullName(input.destination);
CreateChange.checkValidCLA(rsrc.getControl().getProjectControl());
permissionBackend
.user(user)
.project(rsrc.getChange().getProject())
.ref(refName)
.check(RefPermission.CREATE_CHANGE);
try {
Change.Id cherryPickedChangeId =
cherryPickChange.cherryPick(
updateFactory, revision.getChange(), revision.getPatchSet(), input, refControl);
return json.noOptions().format(revision.getProject(), cherryPickedChangeId);
updateFactory,
rsrc.getChange(),
rsrc.getPatchSet(),
input,
rsrc.getControl().getProjectControl().controlForRef(refName));
return json.noOptions().format(rsrc.getProject(), cherryPickedChangeId);
} catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage());
} catch (IntegrationException | NoSuchChangeException e) {

View File

@ -15,27 +15,28 @@
package com.google.gerrit.server.change;
import com.google.common.base.Strings;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.extensions.api.changes.CherryPickInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.git.IntegrationException;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.CommitResource;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
import com.google.gerrit.server.update.UpdateException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import java.io.IOException;
import org.eclipse.jgit.revwalk.RevCommit;
@ -43,14 +44,21 @@ import org.eclipse.jgit.revwalk.RevCommit;
@Singleton
public class CherryPickCommit
extends RetryingRestModifyView<CommitResource, CherryPickInput, ChangeInfo> {
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
private final CherryPickChange cherryPickChange;
private final ChangeJson.Factory json;
@Inject
CherryPickCommit(
RetryHelper retryHelper, CherryPickChange cherryPickChange, ChangeJson.Factory json) {
RetryHelper retryHelper,
Provider<CurrentUser> user,
CherryPickChange cherryPickChange,
ChangeJson.Factory json,
PermissionBackend permissionBackend) {
super(retryHelper);
this.permissionBackend = permissionBackend;
this.user = user;
this.cherryPickChange = cherryPickChange;
this.json = json;
}
@ -58,35 +66,40 @@ public class CherryPickCommit
@Override
public ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, CommitResource rsrc, CherryPickInput input)
throws OrmException, IOException, UpdateException, RestApiException {
throws OrmException, IOException, UpdateException, RestApiException,
PermissionBackendException {
RevCommit commit = rsrc.getCommit();
String message = Strings.nullToEmpty(input.message).trim();
input.message = message.isEmpty() ? commit.getFullMessage() : message;
String destination = Strings.nullToEmpty(input.destination).trim();
input.parent = input.parent == null ? 1 : input.parent;
Project.NameKey projectName = rsrc.getProject().getProject().getNameKey();
if (destination.isEmpty()) {
throw new BadRequestException("destination must be non-empty");
}
ProjectControl projectControl = rsrc.getProject();
Capable capable = projectControl.canPushToAtLeastOneRef();
if (capable != Capable.OK) {
throw new AuthException(capable.getMessage());
}
String refName = RefNames.fullName(destination);
RefControl refControl = projectControl.controlForRef(refName);
if (!refControl.canUpload()) {
throw new AuthException("Not allowed to cherry pick " + commit + " to " + destination);
}
CreateChange.checkValidCLA(rsrc.getProject());
permissionBackend
.user(user)
.project(projectName)
.ref(refName)
.check(RefPermission.CREATE_CHANGE);
Project.NameKey project = projectControl.getProject().getNameKey();
try {
Change.Id cherryPickedChangeId =
cherryPickChange.cherryPick(
updateFactory, null, null, null, null, project, commit, input, refControl);
return json.noOptions().format(project, cherryPickedChangeId);
updateFactory,
null,
null,
null,
null,
projectName,
commit,
input,
rsrc.getProject().controlForRef(refName));
return json.noOptions().format(projectName, cherryPickedChangeId);
} catch (InvalidChangeOperationException e) {
throw new BadRequestException(e.getMessage());
} catch (IntegrationException e) {

View File

@ -52,13 +52,14 @@ import com.google.gerrit.server.config.AnonymousCowardName;
import com.google.gerrit.server.config.GerritServerConfig;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MergeUtil;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectResource;
import com.google.gerrit.server.project.ProjectsCollection;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.gerrit.server.update.RetryingRestModifyView;
@ -89,13 +90,13 @@ import org.eclipse.jgit.util.ChangeIdUtil;
@Singleton
public class CreateChange
extends RetryingRestModifyView<TopLevelResource, ChangeInput, Response<ChangeInfo>> {
private final String anonymousCowardName;
private final Provider<ReviewDb> db;
private final GitRepositoryManager gitManager;
private final AccountCache accountCache;
private final Sequences seq;
private final TimeZone serverTimeZone;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
private final ProjectsCollection projectsCollection;
private final ChangeInserter.Factory changeInserterFactory;
@ -115,6 +116,7 @@ public class CreateChange
AccountCache accountCache,
Sequences seq,
@GerritPersonIdent PersonIdent myIdent,
PermissionBackend permissionBackend,
Provider<CurrentUser> user,
ProjectsCollection projectsCollection,
ChangeInserter.Factory changeInserterFactory,
@ -132,6 +134,7 @@ public class CreateChange
this.accountCache = accountCache;
this.seq = seq;
this.serverTimeZone = myIdent.getTimeZone();
this.permissionBackend = permissionBackend;
this.user = user;
this.projectsCollection = projectsCollection;
this.changeInserterFactory = changeInserterFactory;
@ -165,26 +168,18 @@ public class CreateChange
if (input.status != ChangeStatus.NEW && input.status != ChangeStatus.DRAFT) {
throw new BadRequestException("unsupported change status");
}
if (!allowDrafts && input.status == ChangeStatus.DRAFT) {
throw new MethodNotAllowedException("draft workflow is disabled");
}
}
String refName = RefNames.fullName(input.branch);
ProjectResource rsrc = projectsCollection.parse(input.project);
Capable r = rsrc.getControl().canPushToAtLeastOneRef();
if (r != Capable.OK) {
throw new AuthException(r.getMessage());
}
RefControl refControl = rsrc.getControl().controlForRef(refName);
if (!refControl.canUpload() || !refControl.isVisible()) {
throw new AuthException("cannot upload review");
}
checkValidCLA(rsrc.getControl());
Project.NameKey project = rsrc.getNameKey();
String refName = RefNames.fullName(input.branch);
permissionBackend.user(user).project(project).ref(refName).check(RefPermission.CREATE_CHANGE);
try (Repository git = gitManager.openRepository(project);
ObjectInserter oi = git.newObjectInserter();
ObjectReader reader = oi.newReader();
@ -345,4 +340,11 @@ public class CreateChange
private static ObjectId emptyTreeId(ObjectInserter inserter) throws IOException {
return inserter.insert(new TreeFormatter());
}
static void checkValidCLA(ProjectControl ctl) throws AuthException {
Capable capable = ctl.canPushToAtLeastOneRef();
if (capable != Capable.OK) {
throw new AuthException(capable.getMessage());
}
}
}

View File

@ -320,6 +320,9 @@ public class PatchSetInserter implements BatchUpdateOp {
return;
}
PermissionBackend.ForRef perm =
permissionBackend.user(ctx.getUser()).ref(origCtl.getChange().getDest());
String refName = getPatchSetId().toRefName();
try (CommitReceivedEvent event =
new CommitReceivedEvent(
@ -333,7 +336,7 @@ public class PatchSetInserter implements BatchUpdateOp {
commitId,
ctx.getIdentifiedUser())) {
commitValidatorsFactory
.forGerritCommits(origCtl.getRefControl(), new NoSshInfo(), ctx.getRevWalk())
.forGerritCommits(perm, origCtl.getRefControl(), new NoSshInfo(), ctx.getRevWalk())
.validate(event);
} catch (CommitValidationException e) {
throw new ResourceConflictException(e.getFullMessage());

View File

@ -14,11 +14,9 @@
package com.google.gerrit.server.change;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.extensions.api.changes.PublishChangeEditInput;
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.AcceptsPost;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ChildCollection;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.NotImplementedException;
@ -88,11 +86,7 @@ public class PublishChangeEdit
protected Response<?> applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, PublishChangeEditInput in)
throws IOException, OrmException, RestApiException, UpdateException {
Capable r = rsrc.getControl().getProjectControl().canPushToAtLeastOneRef();
if (r != Capable.OK) {
throw new AuthException(r.getMessage());
}
CreateChange.checkValidCLA(rsrc.getControl().getProjectControl());
Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
if (!edit.isPresent()) {
throw new ResourceConflictException(

View File

@ -14,19 +14,18 @@
package com.google.gerrit.server.change;
import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
import com.google.common.base.Strings;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.data.Capable;
import com.google.gerrit.extensions.api.changes.RevertInput;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.webui.UiAction;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Change.Status;
import com.google.gerrit.reviewdb.client.ChangeMessage;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.Project;
@ -43,10 +42,10 @@ import com.google.gerrit.server.extensions.events.ChangeReverted;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.mail.send.RevertedSender;
import com.google.gerrit.server.notedb.ReviewerStateInternal;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.update.BatchUpdate;
import com.google.gerrit.server.update.BatchUpdateOp;
import com.google.gerrit.server.update.ChangeContext;
@ -82,6 +81,8 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
private static final Logger log = LoggerFactory.getLogger(Revert.class);
private final Provider<ReviewDb> db;
private final PermissionBackend permissionBackend;
private final Provider<CurrentUser> user;
private final GitRepositoryManager repoManager;
private final ChangeInserter.Factory changeInserterFactory;
private final ChangeMessagesUtil cmUtil;
@ -96,6 +97,8 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
@Inject
Revert(
Provider<ReviewDb> db,
PermissionBackend permissionBackend,
Provider<CurrentUser> user,
GitRepositoryManager repoManager,
ChangeInserter.Factory changeInserterFactory,
ChangeMessagesUtil cmUtil,
@ -109,6 +112,8 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
ChangeReverted changeReverted) {
super(retryHelper);
this.db = db;
this.permissionBackend = permissionBackend;
this.user = user;
this.repoManager = repoManager;
this.changeInserterFactory = changeInserterFactory;
this.cmUtil = cmUtil;
@ -122,27 +127,21 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
}
@Override
protected ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource req, RevertInput input)
throws IOException, OrmException, RestApiException, UpdateException, NoSuchChangeException {
RefControl refControl = req.getControl().getRefControl();
ProjectControl projectControl = req.getControl().getProjectControl();
Capable capable = projectControl.canPushToAtLeastOneRef();
if (capable != Capable.OK) {
throw new AuthException(capable.getMessage());
}
Change change = req.getChange();
if (!refControl.canUpload()) {
throw new AuthException("revert not permitted");
} else if (change.getStatus() != Status.MERGED) {
public ChangeInfo applyImpl(
BatchUpdate.Factory updateFactory, ChangeResource rsrc, RevertInput input)
throws IOException, OrmException, RestApiException, UpdateException, NoSuchChangeException,
PermissionBackendException {
Change change = rsrc.getChange();
if (change.getStatus() != Change.Status.MERGED) {
throw new ResourceConflictException("change is " + ChangeUtil.status(change));
}
Change.Id revertedChangeId =
revert(updateFactory, req.getControl(), Strings.emptyToNull(input.message));
return json.noOptions().format(req.getProject(), revertedChangeId);
CreateChange.checkValidCLA(rsrc.getControl().getProjectControl());
permissionBackend.user(user).ref(change.getDest()).check(CREATE_CHANGE);
Change.Id revertId =
revert(updateFactory, rsrc.getControl(), Strings.emptyToNull(input.message));
return json.noOptions().format(rsrc.getProject(), revertId);
}
private Change.Id revert(BatchUpdate.Factory updateFactory, ChangeControl ctl, String message)
@ -234,13 +233,14 @@ public class Revert extends RetryingRestModifyView<ChangeResource, RevertInput,
}
@Override
public UiAction.Description getDescription(ChangeResource resource) {
public UiAction.Description getDescription(ChangeResource rsrc) {
Change change = rsrc.getChange();
return new UiAction.Description()
.setLabel("Revert")
.setTitle("Revert the change")
.setVisible(
resource.getChange().getStatus() == Status.MERGED
&& resource.getControl().getRefControl().canUpload());
change.getStatus() == Change.Status.MERGED
&& permissionBackend.user(user).ref(change.getDest()).testOrFalse(CREATE_CHANGE));
}
private class NotifyOp implements BatchUpdateOp {

View File

@ -112,7 +112,6 @@ import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.ProjectPermission;
import com.google.gerrit.server.permissions.RefPermission;
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.ProjectControl;
@ -188,6 +187,7 @@ import org.slf4j.LoggerFactory;
/** Receives change upload using the Git receive-pack protocol. */
public class ReceiveCommits {
private static final Logger log = LoggerFactory.getLogger(ReceiveCommits.class);
private static final String BYPASS_REVIEW = "bypass-review";
public static final Pattern NEW_PATCHSET =
Pattern.compile("^" + REFS_CHANGES + "(?:[0-9][0-9]/)?([1-9][0-9]*)(?:/new)?$");
@ -1051,7 +1051,7 @@ public class ReceiveCommits {
}
}
private void parseCreate(ReceiveCommand cmd) {
private void parseCreate(ReceiveCommand cmd) throws PermissionBackendException {
RevObject obj;
try {
obj = rp.getRevWalk().parseAny(cmd.getNewId());
@ -1069,7 +1069,14 @@ public class ReceiveCommits {
}
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
if (ctl.canCreate(db, rp.getRepository(), obj)) {
boolean ok;
try {
permissions.ref(cmd.getRefName()).check(RefPermission.CREATE);
ok = true;
} catch (AuthException err) {
ok = false;
}
if (ok && ctl.canCreate(db, rp.getRepository(), obj)) {
if (!validRefOperation(cmd)) {
return;
}
@ -1202,6 +1209,7 @@ public class ReceiveCommits {
private final boolean defaultPublishComments;
Branch.NameKey dest;
RefControl ctl;
PermissionBackend.ForRef perm;
Set<Account.Id> reviewer = Sets.newLinkedHashSet();
Set<Account.Id> cc = Sets.newLinkedHashSet();
Map<String, Short> labels = new HashMap<>();
@ -1437,7 +1445,7 @@ public class ReceiveCommits {
return ImmutableListMultimap.copyOf(pushOptions);
}
private void parseMagicBranch(ReceiveCommand cmd) {
private void parseMagicBranch(ReceiveCommand cmd) throws PermissionBackendException {
// Permit exactly one new change request per push.
if (magicBranch != null) {
reject(cmd, "duplicate request");
@ -1488,6 +1496,7 @@ public class ReceiveCommits {
magicBranch.dest = new Branch.NameKey(project.getNameKey(), ref);
magicBranch.ctl = projectControl.controlForRef(ref);
magicBranch.perm = permissions.ref(ref);
if (projectControl.getProject().getState()
!= com.google.gerrit.extensions.client.ProjectState.ACTIVE) {
reject(cmd, "project is read only");
@ -1508,9 +1517,11 @@ public class ReceiveCommits {
}
}
if (!magicBranch.ctl.canUpload()) {
try {
magicBranch.perm.check(RefPermission.CREATE_CHANGE);
} catch (AuthException denied) {
errors.put(Error.CODE_REVIEW, ref);
reject(cmd, "cannot upload review");
reject(cmd, denied.getMessage());
return;
}
@ -1829,7 +1840,7 @@ public class ReceiveCommits {
logDebug("Creating new change for {} even though it is already tracked", name);
}
if (!validCommit(rp.getRevWalk(), magicBranch.ctl, magicBranch.cmd, c)) {
if (!validCommit(rp.getRevWalk(), magicBranch.perm, magicBranch.ctl, magicBranch.cmd, c)) {
// Not a change the user can propose? Abort as early as possible.
newChanges = Collections.emptyList();
logDebug("Aborting early due to invalid commit");
@ -2407,8 +2418,9 @@ public class ReceiveCommits {
}
}
ChangeControl changeCtl = projectControl.controlFor(notes);
if (!validCommit(rp.getRevWalk(), changeCtl.getRefControl(), inputCommand, newCommit)) {
PermissionBackend.ForRef perm = permissions.ref(change.getDest().get());
RefControl refctl = projectControl.controlForRef(change.getDest());
if (!validCommit(rp.getRevWalk(), perm, refctl, inputCommand, newCommit)) {
return false;
}
rp.getRevWalk().parseBody(priorCommit);
@ -2704,17 +2716,22 @@ public class ReceiveCommits {
return true;
}
private void validateNewCommits(RefControl ctl, ReceiveCommand cmd) {
if (ctl.canForgeAuthor()
&& ctl.canForgeCommitter()
&& ctl.canForgeGerritServerIdentity()
&& ctl.canUploadMerges()
&& !projectControl.getProjectState().isUseSignedOffBy()
&& Iterables.isEmpty(rejectCommits)
&& !RefNames.REFS_CONFIG.equals(ctl.getRefName())
private void validateNewCommits(RefControl ctl, ReceiveCommand cmd)
throws PermissionBackendException {
PermissionBackend.ForRef perm = permissions.ref(ctl.getRefName());
if (!RefNames.REFS_CONFIG.equals(cmd.getRefName())
&& !(MagicBranch.isMagicBranch(cmd.getRefName())
|| NEW_PATCHSET.matcher(cmd.getRefName()).matches())) {
logDebug("Short-circuiting new commit validation");
|| NEW_PATCHSET.matcher(cmd.getRefName()).matches())
&& pushOptions.containsKey(BYPASS_REVIEW)) {
try {
perm.check(RefPermission.BYPASS_REVIEW);
if (!Iterables.isEmpty(rejectCommits)) {
throw new AuthException("reject-commits prevents " + BYPASS_REVIEW);
}
logDebug("Short-circuiting new commit validation");
} catch (AuthException denied) {
reject(cmd, denied.getMessage());
}
return;
}
@ -2735,7 +2752,7 @@ public class ReceiveCommits {
i++;
if (existing.keySet().contains(c)) {
continue;
} else if (!validCommit(walk, ctl, cmd, c)) {
} else if (!validCommit(walk, perm, ctl, cmd, c)) {
break;
}
@ -2761,7 +2778,8 @@ public class ReceiveCommits {
}
}
private boolean validCommit(RevWalk rw, RefControl ctl, ReceiveCommand cmd, ObjectId id)
private boolean validCommit(
RevWalk rw, PermissionBackend.ForRef perm, RefControl ctl, ReceiveCommand cmd, ObjectId id)
throws IOException {
if (validCommits.contains(id)) {
@ -2779,8 +2797,8 @@ public class ReceiveCommits {
&& magicBranch.merged;
CommitValidators validators =
isMerged
? commitValidatorsFactory.forMergedCommits(ctl)
: commitValidatorsFactory.forReceiveCommits(ctl, sshInfo, repo, rw);
? commitValidatorsFactory.forMergedCommits(perm, ctl)
: commitValidatorsFactory.forReceiveCommits(perm, ctl, sshInfo, repo, rw);
messages.addAll(validators.validate(receiveEvent));
} catch (CommitValidationException e) {
logDebug("Commit validation failed on {}", c.name());

View File

@ -22,6 +22,11 @@ public class CommitValidationException extends ValidationException {
private static final long serialVersionUID = 1L;
private final ImmutableList<CommitValidationMessage> messages;
public CommitValidationException(String reason, CommitValidationMessage message) {
super(reason);
this.messages = ImmutableList.of(message);
}
public CommitValidationException(String reason, List<CommitValidationMessage> messages) {
super(reason);
this.messages = ImmutableList.copyOf(messages);

View File

@ -26,6 +26,7 @@ import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.extensions.api.config.ConsistencyCheckInfo.ConsistencyProblemInfo;
import com.google.gerrit.extensions.registration.DynamicSet;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.GerritPersonIdent;
@ -39,7 +40,11 @@ import com.google.gerrit.server.events.CommitReceivedEvent;
import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.ProjectState;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.ssh.SshInfo;
import com.google.gerrit.server.util.MagicBranch;
@ -96,38 +101,45 @@ public class CommitValidators {
}
public CommitValidators forReceiveCommits(
RefControl refControl, SshInfo sshInfo, Repository repo, RevWalk rw) throws IOException {
PermissionBackend.ForRef perm,
RefControl refctl,
SshInfo sshInfo,
Repository repo,
RevWalk rw)
throws IOException {
NoteMap rejectCommits = BanCommit.loadRejectCommitsMap(repo, rw);
IdentifiedUser user = refctl.getUser().asIdentifiedUser();
return new CommitValidators(
ImmutableList.of(
new UploadMergesPermissionValidator(refControl),
new AmendedGerritMergeCommitValidationListener(refControl, gerritIdent),
new AuthorUploaderValidator(refControl, canonicalWebUrl),
new CommitterUploaderValidator(refControl, canonicalWebUrl),
new SignedOffByValidator(refControl),
new ChangeIdValidator(
refControl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
new ConfigValidator(refControl, rw, allUsers),
new UploadMergesPermissionValidator(refctl),
new AmendedGerritMergeCommitValidationListener(perm, gerritIdent),
new AuthorUploaderValidator(user, perm, canonicalWebUrl),
new CommitterUploaderValidator(user, perm, canonicalWebUrl),
new SignedOffByValidator(user, perm, refctl.getProjectControl().getProjectState()),
new ChangeIdValidator(refctl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
new ConfigValidator(refctl, rw, allUsers),
new BannedCommitsValidator(rejectCommits),
new PluginCommitValidationListener(pluginValidators),
new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker)));
}
public CommitValidators forGerritCommits(RefControl refControl, SshInfo sshInfo, RevWalk rw) {
public CommitValidators forGerritCommits(
PermissionBackend.ForRef perm, RefControl refctl, SshInfo sshInfo, RevWalk rw) {
IdentifiedUser user = refctl.getUser().asIdentifiedUser();
return new CommitValidators(
ImmutableList.of(
new UploadMergesPermissionValidator(refControl),
new AmendedGerritMergeCommitValidationListener(refControl, gerritIdent),
new AuthorUploaderValidator(refControl, canonicalWebUrl),
new SignedOffByValidator(refControl),
new ChangeIdValidator(
refControl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
new ConfigValidator(refControl, rw, allUsers),
new UploadMergesPermissionValidator(refctl),
new AmendedGerritMergeCommitValidationListener(perm, gerritIdent),
new AuthorUploaderValidator(user, perm, canonicalWebUrl),
new SignedOffByValidator(user, perm, refctl.getProjectControl().getProjectState()),
new ChangeIdValidator(refctl, canonicalWebUrl, installCommitMsgHookCommand, sshInfo),
new ConfigValidator(refctl, rw, allUsers),
new PluginCommitValidationListener(pluginValidators),
new ExternalIdUpdateListener(allUsers, externalIdsConsistencyChecker)));
}
public CommitValidators forMergedCommits(RefControl refControl) {
public CommitValidators forMergedCommits(PermissionBackend.ForRef perm, RefControl refControl) {
IdentifiedUser user = refControl.getUser().asIdentifiedUser();
// Generally only include validators that are based on permissions of the
// user creating a change for a merged commit; generally exclude
// validators that would require amending the change in order to correct.
@ -144,8 +156,8 @@ public class CommitValidators {
return new CommitValidators(
ImmutableList.of(
new UploadMergesPermissionValidator(refControl),
new AuthorUploaderValidator(refControl, canonicalWebUrl),
new CommitterUploaderValidator(refControl, canonicalWebUrl)));
new AuthorUploaderValidator(user, perm, canonicalWebUrl),
new CommitterUploaderValidator(user, perm, canonicalWebUrl)));
}
}
@ -441,37 +453,50 @@ public class CommitValidators {
}
public static class SignedOffByValidator implements CommitValidationListener {
private final RefControl refControl;
private final IdentifiedUser user;
private final PermissionBackend.ForRef perm;
private final ProjectState state;
public SignedOffByValidator(RefControl refControl) {
this.refControl = refControl;
public SignedOffByValidator(
IdentifiedUser user, PermissionBackend.ForRef perm, ProjectState state) {
this.user = user;
this.perm = perm;
this.state = state;
}
@Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
throws CommitValidationException {
IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
final PersonIdent author = receiveEvent.commit.getAuthorIdent();
final ProjectControl projectControl = refControl.getProjectControl();
if (!state.isUseSignedOffBy()) {
return Collections.emptyList();
}
if (projectControl.getProjectState().isUseSignedOffBy()) {
boolean sboAuthor = false;
boolean sboCommitter = false;
boolean sboMe = false;
for (final FooterLine footer : receiveEvent.commit.getFooterLines()) {
if (footer.matches(FooterKey.SIGNED_OFF_BY)) {
final String e = footer.getEmailAddress();
if (e != null) {
sboAuthor |= author.getEmailAddress().equals(e);
sboCommitter |= committer.getEmailAddress().equals(e);
sboMe |= currentUser.hasEmailAddress(e);
}
RevCommit commit = receiveEvent.commit;
PersonIdent committer = commit.getCommitterIdent();
PersonIdent author = commit.getAuthorIdent();
boolean sboAuthor = false;
boolean sboCommitter = false;
boolean sboMe = false;
for (FooterLine footer : commit.getFooterLines()) {
if (footer.matches(FooterKey.SIGNED_OFF_BY)) {
String e = footer.getEmailAddress();
if (e != null) {
sboAuthor |= author.getEmailAddress().equals(e);
sboCommitter |= committer.getEmailAddress().equals(e);
sboMe |= user.hasEmailAddress(e);
}
}
if (!sboAuthor && !sboCommitter && !sboMe && !refControl.canForgeCommitter()) {
}
if (!sboAuthor && !sboCommitter && !sboMe) {
try {
perm.check(RefPermission.FORGE_COMMITTER);
} catch (AuthException denied) {
throw new CommitValidationException(
"not Signed-off-by author/committer/uploader in commit message footer");
} catch (PermissionBackendException e) {
log.error("cannot check FORGE_COMMITTER", e);
throw new CommitValidationException("internal auth error");
}
}
return Collections.emptyList();
@ -480,56 +505,69 @@ public class CommitValidators {
/** Require that author matches the uploader. */
public static class AuthorUploaderValidator implements CommitValidationListener {
private final RefControl refControl;
private final IdentifiedUser user;
private final PermissionBackend.ForRef perm;
private final String canonicalWebUrl;
public AuthorUploaderValidator(RefControl refControl, String canonicalWebUrl) {
this.refControl = refControl;
public AuthorUploaderValidator(
IdentifiedUser user, PermissionBackend.ForRef perm, String canonicalWebUrl) {
this.user = user;
this.perm = perm;
this.canonicalWebUrl = canonicalWebUrl;
}
@Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
throws CommitValidationException {
IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
final PersonIdent author = receiveEvent.commit.getAuthorIdent();
if (!currentUser.hasEmailAddress(author.getEmailAddress()) && !refControl.canForgeAuthor()) {
List<CommitValidationMessage> messages = new ArrayList<>();
messages.add(
getInvalidEmailError(
receiveEvent.commit, "author", author, currentUser, canonicalWebUrl));
throw new CommitValidationException("invalid author", messages);
PersonIdent author = receiveEvent.commit.getAuthorIdent();
if (user.hasEmailAddress(author.getEmailAddress())) {
return Collections.emptyList();
}
try {
perm.check(RefPermission.FORGE_AUTHOR);
return Collections.emptyList();
} catch (AuthException e) {
throw new CommitValidationException(
"invalid author",
invalidEmail(receiveEvent.commit, "author", author, user, canonicalWebUrl));
} catch (PermissionBackendException e) {
log.error("cannot check FORGE_AUTHOR", e);
throw new CommitValidationException("internal auth error");
}
return Collections.emptyList();
}
}
/** Require that committer matches the uploader. */
public static class CommitterUploaderValidator implements CommitValidationListener {
private final RefControl refControl;
private final IdentifiedUser user;
private final PermissionBackend.ForRef perm;
private final String canonicalWebUrl;
public CommitterUploaderValidator(RefControl refControl, String canonicalWebUrl) {
this.refControl = refControl;
public CommitterUploaderValidator(
IdentifiedUser user, PermissionBackend.ForRef perm, String canonicalWebUrl) {
this.user = user;
this.perm = perm;
this.canonicalWebUrl = canonicalWebUrl;
}
@Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
throws CommitValidationException {
IdentifiedUser currentUser = refControl.getUser().asIdentifiedUser();
final PersonIdent committer = receiveEvent.commit.getCommitterIdent();
if (!currentUser.hasEmailAddress(committer.getEmailAddress())
&& !refControl.canForgeCommitter()) {
List<CommitValidationMessage> messages = new ArrayList<>();
messages.add(
getInvalidEmailError(
receiveEvent.commit, "committer", committer, currentUser, canonicalWebUrl));
throw new CommitValidationException("invalid committer", messages);
PersonIdent committer = receiveEvent.commit.getCommitterIdent();
if (user.hasEmailAddress(committer.getEmailAddress())) {
return Collections.emptyList();
}
try {
perm.check(RefPermission.FORGE_COMMITTER);
return Collections.emptyList();
} catch (AuthException e) {
throw new CommitValidationException(
"invalid committer",
invalidEmail(receiveEvent.commit, "committer", committer, user, canonicalWebUrl));
} catch (PermissionBackendException e) {
log.error("cannot check FORGE_COMMITTER", e);
throw new CommitValidationException("internal auth error");
}
return Collections.emptyList();
}
}
@ -539,25 +577,30 @@ public class CommitValidators {
*/
public static class AmendedGerritMergeCommitValidationListener
implements CommitValidationListener {
private final PermissionBackend.ForRef perm;
private final PersonIdent gerritIdent;
private final RefControl refControl;
public AmendedGerritMergeCommitValidationListener(
final RefControl refControl, final PersonIdent gerritIdent) {
this.refControl = refControl;
PermissionBackend.ForRef perm, PersonIdent gerritIdent) {
this.perm = perm;
this.gerritIdent = gerritIdent;
}
@Override
public List<CommitValidationMessage> onCommitReceived(CommitReceivedEvent receiveEvent)
throws CommitValidationException {
final PersonIdent author = receiveEvent.commit.getAuthorIdent();
PersonIdent author = receiveEvent.commit.getAuthorIdent();
if (receiveEvent.commit.getParentCount() > 1
&& author.getName().equals(gerritIdent.getName())
&& author.getEmailAddress().equals(gerritIdent.getEmailAddress())
&& !refControl.canForgeGerritServerIdentity()) {
throw new CommitValidationException("do not amend merges not made by you");
&& author.getEmailAddress().equals(gerritIdent.getEmailAddress())) {
try {
perm.check(RefPermission.FORGE_SERVER);
} catch (AuthException denied) {
throw new CommitValidationException("do not amend merges not made by you");
} catch (PermissionBackendException e) {
log.error("cannot check FORGE_SERVER", e);
throw new CommitValidationException("internal auth error");
}
}
return Collections.emptyList();
}
@ -629,7 +672,7 @@ public class CommitValidators {
}
}
private static CommitValidationMessage getInvalidEmailError(
private static CommitValidationMessage invalidEmail(
RevCommit c,
String type,
PersonIdent who,

View File

@ -32,7 +32,22 @@ public enum ProjectPermission {
*
* <p>This is a stronger form of {@link #ACCESS} where no filtering is required.
*/
READ(Permission.READ);
READ(Permission.READ),
/**
* Can create at least one reference in the project.
*
* <p>This project level permission only validates the user may create some type of reference
* within the project. The exact reference name must be checked at creation:
*
* <pre>permissionBackend
* .user(user)
* .project(proj)
* .ref(ref)
* .check(RefPermission.CREATE);
* </pre>
*/
CREATE_REF;
private final String name;

View File

@ -28,6 +28,7 @@ public enum RefPermission {
FORGE_AUTHOR(Permission.FORGE_AUTHOR),
FORGE_COMMITTER(Permission.FORGE_COMMITTER),
FORGE_SERVER(Permission.FORGE_SERVER),
BYPASS_REVIEW,
CREATE_CHANGE;

View File

@ -42,6 +42,7 @@ import com.google.gerrit.server.permissions.ChangePermissionOrLabel;
import com.google.gerrit.server.permissions.LabelPermission;
import com.google.gerrit.server.permissions.PermissionBackend.ForChange;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
@ -281,14 +282,14 @@ public class ChangeControl {
/** Can this user rebase this change? */
private boolean canRebase(ReviewDb db) throws OrmException {
return (isOwner() || getRefControl().canSubmit(isOwner()) || getRefControl().canRebase())
&& getRefControl().canUpload()
&& refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE)
&& !isPatchSetLocked(db);
}
/** Can this user restore this change? */
private boolean canRestore(ReviewDb db) throws OrmException {
return canAbandon(db) // Anyone who can abandon the change can restore it back
&& getRefControl().canUpload(); // as long as you can upload too
// Anyone who can abandon the change can restore it, as long as they can create changes.
return canAbandon(db) && refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE);
}
/** All available label types for this change. */
@ -326,7 +327,7 @@ public class ChangeControl {
/** Can this user add a patch set to this change? */
private boolean canAddPatchSet(ReviewDb db) throws OrmException {
if (!getRefControl().canUpload()
if (!refControl.asForRef().testOrFalse(RefPermission.CREATE_CHANGE)
|| isPatchSetLocked(db)
|| !isPatchVisible(patchSetUtil.current(db, notes), db)) {
return false;

View File

@ -14,6 +14,11 @@
package com.google.gerrit.server.project;
import static com.google.gerrit.server.permissions.GlobalPermission.ADMINISTRATE_SERVER;
import static com.google.gerrit.server.permissions.ProjectPermission.CREATE_REF;
import static com.google.gerrit.server.permissions.RefPermission.CREATE_CHANGE;
import static com.google.gerrit.server.permissions.RefPermission.READ;
import com.google.common.collect.ImmutableBiMap;
import com.google.common.collect.Iterables;
import com.google.gerrit.common.data.AccessSection;
@ -25,6 +30,7 @@ import com.google.gerrit.extensions.api.access.AccessSectionInfo;
import com.google.gerrit.extensions.api.access.PermissionInfo;
import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
@ -37,6 +43,9 @@ import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.config.AllProjectsName;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.permissions.PermissionBackend;
import com.google.gerrit.server.permissions.PermissionBackendException;
import com.google.gerrit.server.permissions.RefPermission;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
@ -63,7 +72,8 @@ public class GetAccess implements RestReadView<ProjectResource> {
PermissionRule.Action.INTERACTIVE,
PermissionRuleInfo.Action.INTERACTIVE);
private final Provider<CurrentUser> self;
private final Provider<CurrentUser> user;
private final PermissionBackend permissionBackend;
private final GroupControl.Factory groupControlFactory;
private final AllProjectsName allProjectsName;
private final ProjectJson projectJson;
@ -75,6 +85,7 @@ public class GetAccess implements RestReadView<ProjectResource> {
@Inject
public GetAccess(
Provider<CurrentUser> self,
PermissionBackend permissionBackend,
GroupControl.Factory groupControlFactory,
AllProjectsName allProjectsName,
ProjectCache projectCache,
@ -82,7 +93,8 @@ public class GetAccess implements RestReadView<ProjectResource> {
ProjectJson projectJson,
ProjectControl.GenericFactory projectControlFactory,
GroupBackend groupBackend) {
this.self = self;
this.user = self;
this.permissionBackend = permissionBackend;
this.groupControlFactory = groupControlFactory;
this.allProjectsName = allProjectsName;
this.projectJson = projectJson;
@ -93,9 +105,10 @@ public class GetAccess implements RestReadView<ProjectResource> {
}
public ProjectAccessInfo apply(Project.NameKey nameKey)
throws ResourceNotFoundException, ResourceConflictException, IOException {
throws ResourceNotFoundException, ResourceConflictException, IOException,
PermissionBackendException {
try {
return apply(new ProjectResource(projectControlFactory.controlFor(nameKey, self.get())));
return apply(new ProjectResource(projectControlFactory.controlFor(nameKey, user.get())));
} catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(nameKey.get());
}
@ -103,16 +116,18 @@ public class GetAccess implements RestReadView<ProjectResource> {
@Override
public ProjectAccessInfo apply(ProjectResource rsrc)
throws ResourceNotFoundException, ResourceConflictException, IOException {
throws ResourceNotFoundException, ResourceConflictException, IOException,
PermissionBackendException {
// Load the current configuration from the repository, ensuring it's the most
// recent version available. If it differs from what was in the project
// state, force a cache flush now.
//
Project.NameKey projectName = rsrc.getNameKey();
ProjectAccessInfo info = new ProjectAccessInfo();
ProjectConfig config;
ProjectControl pc = createProjectControl(projectName);
RefControl metaConfigControl = pc.controlForRef(RefNames.REFS_CONFIG);
PermissionBackend.ForProject perm = permissionBackend.user(user).project(projectName);
ProjectConfig config;
try (MetaDataUpdate md = metaDataUpdateFactory.create(projectName)) {
config = ProjectConfig.read(md);
@ -121,10 +136,12 @@ public class GetAccess implements RestReadView<ProjectResource> {
config.commit(md);
projectCache.evict(config.getProject());
pc = createProjectControl(projectName);
perm = permissionBackend.user(user).project(projectName);
} else if (config.getRevision() != null
&& !config.getRevision().equals(pc.getProjectState().getConfig().getRevision())) {
projectCache.evict(config.getProject());
pc = createProjectControl(projectName);
perm = permissionBackend.user(user).project(projectName);
}
} catch (ConfigInvalidException e) {
throw new ResourceConflictException(e.getMessage());
@ -135,6 +152,7 @@ public class GetAccess implements RestReadView<ProjectResource> {
info.local = new HashMap<>();
info.ownerOf = new HashSet<>();
Map<AccountGroup.UUID, Boolean> visibleGroups = new HashMap<>();
boolean checkReadConfig = check(perm, RefNames.REFS_CONFIG, READ);
for (AccessSection section : config.getAccessSections()) {
String name = section.getName();
@ -143,20 +161,19 @@ public class GetAccess implements RestReadView<ProjectResource> {
info.local.put(name, createAccessSection(section));
info.ownerOf.add(name);
} else if (metaConfigControl.isVisible()) {
} else if (checkReadConfig) {
info.local.put(section.getName(), createAccessSection(section));
}
} else if (RefConfigSection.isValid(name)) {
RefControl rc = pc.controlForRef(name);
if (rc.isOwner()) {
if (pc.controlForRef(name).isOwner()) {
info.local.put(name, createAccessSection(section));
info.ownerOf.add(name);
} else if (metaConfigControl.isVisible()) {
} else if (checkReadConfig) {
info.local.put(name, createAccessSection(section));
} else if (rc.isVisible()) {
} else if (check(perm, name, READ)) {
// Filter the section to only add rules describing groups that
// are visible to the current-user. This includes any group the
// user is a member of, as well as groups they own or that
@ -214,21 +231,32 @@ public class GetAccess implements RestReadView<ProjectResource> {
info.inheritsFrom = projectJson.format(parent.getProject());
}
if (pc.getProject().getNameKey().equals(allProjectsName)) {
if (pc.isOwner()) {
info.ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
}
if (projectName.equals(allProjectsName)
&& permissionBackend.user(user).testOrFalse(ADMINISTRATE_SERVER)) {
info.ownerOf.add(AccessSection.GLOBAL_CAPABILITIES);
}
info.isOwner = toBoolean(pc.isOwner());
info.canUpload =
toBoolean(pc.isOwner() || (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
info.canAdd = toBoolean(pc.canAddRefs());
info.configVisible = pc.isOwner() || metaConfigControl.isVisible();
toBoolean(
pc.isOwner()
|| (checkReadConfig && perm.ref(RefNames.REFS_CONFIG).testOrFalse(CREATE_CHANGE)));
info.canAdd = toBoolean(perm.testOrFalse(CREATE_REF));
info.configVisible = checkReadConfig || pc.isOwner();
return info;
}
private static boolean check(PermissionBackend.ForProject ctx, String ref, RefPermission perm)
throws PermissionBackendException {
try {
ctx.ref(ref).check(perm);
return true;
} catch (AuthException denied) {
return false;
}
}
private AccessSectionInfo createAccessSection(AccessSection section) {
AccessSectionInfo accessSectionInfo = new AccessSectionInfo();
accessSectionInfo.permissions = new HashMap<>();
@ -255,7 +283,7 @@ public class GetAccess implements RestReadView<ProjectResource> {
private ProjectControl createProjectControl(Project.NameKey projectName)
throws IOException, ResourceNotFoundException {
try {
return projectControlFactory.controlFor(projectName, self.get());
return projectControlFactory.controlFor(projectName, user.get());
} catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(projectName.get());
}

View File

@ -249,7 +249,7 @@ public class ProjectControl {
return getProject().getState().equals(com.google.gerrit.extensions.client.ProjectState.HIDDEN);
}
public boolean canAddRefs() {
private boolean canAddRefs() {
return (canPerformOnAnyRef(Permission.CREATE) || isOwnerAnyRef());
}
@ -588,6 +588,9 @@ public class ProjectControl {
case READ:
return !isHidden() && allRefsAreVisible(Collections.emptySet());
case CREATE_REF:
return canAddRefs();
}
throw new PermissionBackendException(perm + " unsupported");
}

View File

@ -151,12 +151,7 @@ public class RefControl {
return blocks.isEmpty() && !allows.isEmpty();
}
/**
* 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() {
private boolean canUpload() {
return projectControl.controlForRef("refs/for/" + getRefName()).canPerform(Permission.PUSH)
&& isProjectStatePermittingWrite();
}
@ -391,7 +386,7 @@ public class RefControl {
}
/** @return true if this user can forge the author line in a commit. */
public boolean canForgeAuthor() {
private boolean canForgeAuthor() {
if (canForgeAuthor == null) {
canForgeAuthor = canPerform(Permission.FORGE_AUTHOR);
}
@ -399,7 +394,7 @@ public class RefControl {
}
/** @return true if this user can forge the committer line in a commit. */
public boolean canForgeCommitter() {
private boolean canForgeCommitter() {
if (canForgeCommitter == null) {
canForgeCommitter = canPerform(Permission.FORGE_COMMITTER);
}
@ -407,7 +402,7 @@ public class RefControl {
}
/** @return true if this user can forge the server on the committer line. */
public boolean canForgeGerritServerIdentity() {
private boolean canForgeGerritServerIdentity() {
return canPerform(Permission.FORGE_SERVER);
}
@ -738,6 +733,13 @@ public class RefControl {
return canForgeGerritServerIdentity();
case CREATE_CHANGE:
return canUpload();
case BYPASS_REVIEW:
return canForgeAuthor()
&& canForgeCommitter()
&& canForgeGerritServerIdentity()
&& canUploadMerges()
&& !projectControl.getProjectState().isUseSignedOffBy();
}
throw new PermissionBackendException(perm + " unsupported");
}

View File

@ -141,16 +141,18 @@ public class RefControlTest {
assertThat(u.canPushToAtLeastOneRef()).named("can upload").isEqualTo(Capable.OK);
}
private void assertCanUpload(String ref, ProjectControl u) {
assertThat(u.controlForRef(ref).canUpload()).named("can upload " + ref).isTrue();
private void assertCreateChange(String ref, ProjectControl u) {
boolean create = u.asForProject().ref(ref).testOrFalse(RefPermission.CREATE_CHANGE);
assertThat(create).named("can create change " + ref).isTrue();
}
private void assertCannotUpload(ProjectControl u) {
assertThat(u.canPushToAtLeastOneRef()).named("cannot upload").isNotEqualTo(Capable.OK);
}
private void assertCannotUpload(String ref, ProjectControl u) {
assertThat(u.controlForRef(ref).canUpload()).named("cannot upload " + ref).isFalse();
private void assertCannotCreateChange(String ref, ProjectControl u) {
boolean create = u.asForProject().ref(ref).testOrFalse(RefPermission.CREATE_CHANGE);
assertThat(create).named("cannot create change " + ref).isFalse();
}
private void assertBlocked(String p, String ref, ProjectControl u) {
@ -405,8 +407,8 @@ public class RefControlTest {
ProjectControl u = user(local);
assertCanUpload(u);
assertCanUpload("refs/heads/master", u);
assertCannotUpload("refs/heads/foobar", u);
assertCreateChange("refs/heads/master", u);
assertCannotCreateChange("refs/heads/foobar", u);
}
@Test
@ -415,7 +417,7 @@ public class RefControlTest {
block(parent, PUSH, ANONYMOUS_USERS, "refs/drafts/*");
ProjectControl u = user(local);
assertCanUpload("refs/heads/master", u);
assertCreateChange("refs/heads/master", u);
assertBlocked(PUSH, "refs/drafts/refs/heads/master", u);
}
@ -438,8 +440,8 @@ public class RefControlTest {
ProjectControl u = user(local);
assertCanUpload(u);
assertCanUpload("refs/heads/master", u);
assertCanUpload("refs/heads/foobar", u);
assertCreateChange("refs/heads/master", u);
assertCreateChange("refs/heads/foobar", u);
}
@Test
@ -508,7 +510,7 @@ public class RefControlTest {
ProjectControl u = user(local);
assertCannotUpload(u);
assertCannotUpload("refs/heads/master", u);
assertCannotCreateChange("refs/heads/master", u);
}
@Test