Force validation of the author and committer lines during push
The new forge identity category can be used to control who can forge the author and/or the committer lines in a commit, or the tagger line in an annotated tag. The access control Push Tag +3 was removed, as it previously had been used to imply what Forge Identity +2 now supplies, a way to work around the check for tagger matching the current user. Documentation relating to Push Tag has been updated, including describing the simple case that lightweight (non annotated) tags are now supported through Push Branch +2 on the refs/tags/* namespace. Bug: issue 421 Change-Id: Ie0dc8adc2538803021dec766e00ef6ea34d4d14c Signed-off-by: Shawn O. Pearce <sop@google.com> Reviewed-by: Nico Sallembien <nsallembien@google.com>
This commit is contained in:
parent
a517cb5510
commit
f6a13f4eed
@ -217,8 +217,8 @@ be suitable in a corporate deployment if the HTTP access control
|
|||||||
is already restricted to the correct set of users.
|
is already restricted to the correct set of users.
|
||||||
|
|
||||||
[[category_pTAG]]
|
[[category_pTAG]]
|
||||||
Push Annotated Tag
|
Push Tag
|
||||||
~~~~~~~~~~~~~~~~~~
|
~~~~~~~~
|
||||||
|
|
||||||
This category permits users to push an annotated tag object over
|
This category permits users to push an annotated tag object over
|
||||||
SSH into the project's repository. Typically this would be done
|
SSH into the project's repository. Typically this would be done
|
||||||
@ -229,9 +229,7 @@ with a command line such as:
|
|||||||
====
|
====
|
||||||
|
|
||||||
Tags must be annotated (created with `git tag -a` or `git tag -s`),
|
Tags must be annotated (created with `git tag -a` or `git tag -s`),
|
||||||
must exist in the `refs/tags/` namespace, and must be new. Deleting
|
should exist in the `refs/tags/` namespace, and should be new.
|
||||||
and overwriting existing tags is not permitted. Lightweight tags
|
|
||||||
are not permitted.
|
|
||||||
|
|
||||||
This category is intended to be used to publish tags when a project
|
This category is intended to be used to publish tags when a project
|
||||||
reaches a stable release point worth remembering in history.
|
reaches a stable release point worth remembering in history.
|
||||||
@ -248,12 +246,17 @@ verified for the current user.
|
|||||||
A new annotated (unsigned) tag may be created. The tagger email
|
A new annotated (unsigned) tag may be created. The tagger email
|
||||||
address must be verified for the current user.
|
address must be verified for the current user.
|
||||||
|
|
||||||
* +3 Create Any Tag
|
To push tags created by users other than the current user (such
|
||||||
+
|
as tags mirrored from an upstream project), `Forge Identity +2`
|
||||||
Any new annotated tag may be created. A PGP signature is optional,
|
must be also granted in addition to `Push Tag >= +1`.
|
||||||
and any tagger email address may be used. This level is primarily
|
|
||||||
useful for mirroring tags made by an upstream maintainer, outside
|
To push lightweight (non annotated) tags, grant `Push Branch +2
|
||||||
of Gerrit.
|
Create Branch` for reference name `refs/tags/*`, as lightweight
|
||||||
|
tags are implemented just like branches in Git.
|
||||||
|
|
||||||
|
To delete or overwrite an existing tag, grant `Push Branch +3
|
||||||
|
Force Push Branch; Delete Branch` for reference name `refs/tags/*`,
|
||||||
|
as deleting a tag requires the same permission as deleting a branch.
|
||||||
|
|
||||||
[[category_pHD]]
|
[[category_pHD]]
|
||||||
Push Branch
|
Push Branch
|
||||||
@ -264,24 +267,24 @@ bypassing any code review process that would otherwise be used.
|
|||||||
|
|
||||||
This category has several possible values:
|
This category has several possible values:
|
||||||
|
|
||||||
* Update Branch
|
* +1 Update Branch
|
||||||
+
|
+
|
||||||
Any existing branch can be fast-forwarded to a new commit.
|
Any existing branch can be fast-forwarded to a new commit.
|
||||||
Creation of new branches is rejected. Deletion of existing branches
|
Creation of new branches is rejected. Deletion of existing branches
|
||||||
is rejected. This is the safest mode as commits cannot be discarded.
|
is rejected. This is the safest mode as commits cannot be discarded.
|
||||||
|
|
||||||
* Create Branch
|
* +2 Create Branch
|
||||||
+
|
+
|
||||||
Implies 'Update Branch', but also allows the creation of a new branch
|
Implies 'Update Branch', but also allows the creation of a new branch
|
||||||
if the name does not not already designate an existing branch name.
|
if the name does not not already designate an existing branch name.
|
||||||
Like update branch, existing commits cannot be discarded.
|
Like update branch, existing commits cannot be discarded.
|
||||||
|
|
||||||
* Force Push Branch; Delete Branch
|
* +3 Force Push Branch; Delete Branch
|
||||||
+
|
+
|
||||||
Implies both 'Update Branch' and 'Create Branch', but also allows an
|
Implies both 'Update Branch' and 'Create Branch', but also allows an
|
||||||
existing branch to be deleted. Since a force push is effectively a
|
existing branch to be deleted. Since a force push is effectively a
|
||||||
delete immediately followed by a create, but performed atomically on
|
delete immediately followed by a create, but performed atomically on
|
||||||
the server and logged, this also level permits forced push updates
|
the server and logged, this level also permits forced push updates
|
||||||
to branches. This level may allow existing commits to be discarded
|
to branches. This level may allow existing commits to be discarded
|
||||||
from a project history.
|
from a project history.
|
||||||
|
|
||||||
@ -290,6 +293,35 @@ take advantage of Gerrit's access control features and do not need
|
|||||||
its code review functionality. Projects that need to require code
|
its code review functionality. Projects that need to require code
|
||||||
reviews should not grant this category.
|
reviews should not grant this category.
|
||||||
|
|
||||||
|
[[category_FORG]]
|
||||||
|
Forge Identity
|
||||||
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Normally Gerrit requires the author and the committer identity
|
||||||
|
lines in a Git commit object (or tagger line in an annotated tag) to
|
||||||
|
match one of the registered email addresses of the uploading user.
|
||||||
|
This permission allows users to bypass that validation, which may
|
||||||
|
be necessary when mirroring changes from an upstream project.
|
||||||
|
|
||||||
|
* +1 Forge Author Identity
|
||||||
|
+
|
||||||
|
Permits the use of an unverified author line in commit objects.
|
||||||
|
This can be useful when applying patches received by email from
|
||||||
|
3rd parties, when cherry-picking changes written by others across
|
||||||
|
branches, or when amending someone else's commit to fix up a minor
|
||||||
|
problem before submitting.
|
||||||
|
+
|
||||||
|
By default this is granted to `Registered Users` in all projects,
|
||||||
|
but a site administrator may disable it if verified authorship
|
||||||
|
is required.
|
||||||
|
|
||||||
|
* +2 Forge Committer or Tagger Identity
|
||||||
|
+
|
||||||
|
Implies 'Forge Author Identity', but also allows the use of an
|
||||||
|
unverified committer line in commit objects, or an unverified tagger
|
||||||
|
line in annotated tag objects. Typically this is only required
|
||||||
|
when mirroring commits from an upstream project repository.
|
||||||
|
|
||||||
[[category_VRIF]]
|
[[category_VRIF]]
|
||||||
Verified
|
Verified
|
||||||
~~~~~~~~
|
~~~~~~~~
|
||||||
|
@ -36,7 +36,6 @@ public final class ApprovalCategory {
|
|||||||
new ApprovalCategory.Id("pTAG");
|
new ApprovalCategory.Id("pTAG");
|
||||||
public static final short PUSH_TAG_SIGNED = 1;
|
public static final short PUSH_TAG_SIGNED = 1;
|
||||||
public static final short PUSH_TAG_ANNOTATED = 2;
|
public static final short PUSH_TAG_ANNOTATED = 2;
|
||||||
public static final short PUSH_TAG_ANY = 3;
|
|
||||||
|
|
||||||
/** Id of the special "Push Branch" action (and category). */
|
/** Id of the special "Push Branch" action (and category). */
|
||||||
public static final ApprovalCategory.Id PUSH_HEAD =
|
public static final ApprovalCategory.Id PUSH_HEAD =
|
||||||
@ -45,6 +44,12 @@ public final class ApprovalCategory {
|
|||||||
public static final short PUSH_HEAD_CREATE = 2;
|
public static final short PUSH_HEAD_CREATE = 2;
|
||||||
public static final short PUSH_HEAD_REPLACE = 3;
|
public static final short PUSH_HEAD_REPLACE = 3;
|
||||||
|
|
||||||
|
/** Id of the special "Forge Identity" category. */
|
||||||
|
public static final ApprovalCategory.Id FORGE_IDENTITY =
|
||||||
|
new ApprovalCategory.Id("FORG");
|
||||||
|
public static final short FORGE_AUTHOR = 1;
|
||||||
|
public static final short FORGE_COMMITTER = 2;
|
||||||
|
|
||||||
public static class Id extends StringKey<Key<?>> {
|
public static class Id extends StringKey<Key<?>> {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
@ -46,6 +46,7 @@ import com.google.gerrit.server.mail.EmailException;
|
|||||||
import com.google.gerrit.server.mail.MergedSender;
|
import com.google.gerrit.server.mail.MergedSender;
|
||||||
import com.google.gerrit.server.mail.ReplacePatchSetSender;
|
import com.google.gerrit.server.mail.ReplacePatchSetSender;
|
||||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||||
|
import com.google.gerrit.server.project.ChangeControl;
|
||||||
import com.google.gerrit.server.project.ProjectControl;
|
import com.google.gerrit.server.project.ProjectControl;
|
||||||
import com.google.gerrit.server.project.RefControl;
|
import com.google.gerrit.server.project.RefControl;
|
||||||
import com.google.gwtorm.client.AtomicUpdate;
|
import com.google.gwtorm.client.AtomicUpdate;
|
||||||
@ -156,6 +157,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
|
|
||||||
private ReceiveCommand newChange;
|
private ReceiveCommand newChange;
|
||||||
private Branch.NameKey destBranch;
|
private Branch.NameKey destBranch;
|
||||||
|
private RefControl destBranchCtl;
|
||||||
|
|
||||||
private final List<Change.Id> allNewChanges = new ArrayList<Change.Id>();
|
private final List<Change.Id> allNewChanges = new ArrayList<Change.Id>();
|
||||||
private final Map<Change.Id, ReplaceRequest> replaceByChange =
|
private final Map<Change.Id, ReplaceRequest> replaceByChange =
|
||||||
@ -464,6 +466,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
|
|
||||||
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
|
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
|
||||||
if (ctl.canCreate(rp.getRevWalk(), obj)) {
|
if (ctl.canCreate(rp.getRevWalk(), obj)) {
|
||||||
|
validateNewCommits(ctl, cmd);
|
||||||
// Let the core receive process handle it
|
// Let the core receive process handle it
|
||||||
} else {
|
} else {
|
||||||
reject(cmd);
|
reject(cmd);
|
||||||
@ -473,6 +476,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
private void parseUpdate(final ReceiveCommand cmd) {
|
private void parseUpdate(final ReceiveCommand cmd) {
|
||||||
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
|
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
|
||||||
if (ctl.canUpdate()) {
|
if (ctl.canUpdate()) {
|
||||||
|
validateNewCommits(ctl, cmd);
|
||||||
// Let the core receive process handle it
|
// Let the core receive process handle it
|
||||||
} else {
|
} else {
|
||||||
reject(cmd);
|
reject(cmd);
|
||||||
@ -511,6 +515,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
|
RefControl ctl = projectControl.controlForRef(cmd.getRefName());
|
||||||
if (oldObject instanceof RevCommit && newObject instanceof RevCommit
|
if (oldObject instanceof RevCommit && newObject instanceof RevCommit
|
||||||
&& ctl.canForceUpdate()) {
|
&& ctl.canForceUpdate()) {
|
||||||
|
validateNewCommits(ctl, cmd);
|
||||||
// Let the core receive process handle it
|
// Let the core receive process handle it
|
||||||
} else {
|
} else {
|
||||||
cmd.setResult(ReceiveCommand.Result.REJECTED_NONFASTFORWARD);
|
cmd.setResult(ReceiveCommand.Result.REJECTED_NONFASTFORWARD);
|
||||||
@ -563,7 +568,8 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!projectControl.controlForRef(destBranch).canUpload()) {
|
destBranchCtl = projectControl.controlForRef(destBranch);
|
||||||
|
if (!destBranchCtl.canUpload()) {
|
||||||
reject(cmd);
|
reject(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -710,7 +716,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
//
|
//
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (!validCommitter(newChange, c)) {
|
if (!validCommit(destBranchCtl, newChange, c)) {
|
||||||
// Not a change the user can propose? Abort as early as possible.
|
// Not a change the user can propose? Abort as early as possible.
|
||||||
//
|
//
|
||||||
return;
|
return;
|
||||||
@ -905,9 +911,6 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
throws IOException, OrmException {
|
throws IOException, OrmException {
|
||||||
final RevCommit c = request.newCommit;
|
final RevCommit c = request.newCommit;
|
||||||
rp.getRevWalk().parseBody(c);
|
rp.getRevWalk().parseBody(c);
|
||||||
if (!validCommitter(request.cmd, c)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Account.Id me = currentUser.getAccountId();
|
final Account.Id me = currentUser.getAccountId();
|
||||||
final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
|
final Set<Account.Id> reviewers = new HashSet<Account.Id>(reviewerId);
|
||||||
@ -940,10 +943,15 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
reject(request.cmd, "change " + request.ontoChange + " closed");
|
reject(request.cmd, "change " + request.ontoChange + " closed");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
if (!projectControl.controlFor(change).canAddPatchSet()) {
|
|
||||||
|
final ChangeControl changeCtl = projectControl.controlFor(change);
|
||||||
|
if (!changeCtl.canAddPatchSet()) {
|
||||||
reject(request.cmd, "cannot replace " + request.ontoChange);
|
reject(request.cmd, "cannot replace " + request.ontoChange);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
if (!validCommit(changeCtl.getRefControl(), request.cmd, c)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
final PatchSet.Id priorPatchSet = change.currentPatchSetId();
|
final PatchSet.Id priorPatchSet = change.currentPatchSetId();
|
||||||
for (final PatchSet ps : db.patchSets().byChange(request.ontoChange)) {
|
for (final PatchSet ps : db.patchSets().byChange(request.ontoChange)) {
|
||||||
@ -1244,8 +1252,34 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
String mergedIntoRef;
|
String mergedIntoRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validCommitter(final ReceiveCommand cmd, final RevCommit c)
|
private void validateNewCommits(RefControl ctl, ReceiveCommand cmd) {
|
||||||
throws MissingObjectException, IOException {
|
final RevWalk walk = rp.getRevWalk();
|
||||||
|
walk.reset();
|
||||||
|
walk.sort(RevSort.NONE);
|
||||||
|
try {
|
||||||
|
walk.markStart(walk.parseCommit(cmd.getNewId()));
|
||||||
|
for (final Ref r : rp.getAdvertisedRefs().values()) {
|
||||||
|
try {
|
||||||
|
walk.markUninteresting(walk.parseCommit(r.getObjectId()));
|
||||||
|
} catch (IOException e) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RevCommit c;
|
||||||
|
while ((c = walk.next()) != null) {
|
||||||
|
if (!validCommit(ctl, cmd, c)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException err) {
|
||||||
|
cmd.setResult(Result.REJECTED_MISSING_OBJECT);
|
||||||
|
log.error("Invalid pack upload; one or more objects weren't sent", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean validCommit(final RefControl ctl, final ReceiveCommand cmd,
|
||||||
|
final RevCommit c) throws MissingObjectException, IOException {
|
||||||
rp.getRevWalk().parseBody(c);
|
rp.getRevWalk().parseBody(c);
|
||||||
final PersonIdent committer = c.getCommitterIdent();
|
final PersonIdent committer = c.getCommitterIdent();
|
||||||
final PersonIdent author = c.getAuthorIdent();
|
final PersonIdent author = c.getAuthorIdent();
|
||||||
@ -1261,9 +1295,18 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Require that author matches the uploader.
|
||||||
|
//
|
||||||
|
if (!currentUser.getEmailAddresses().contains(author.getEmailAddress())
|
||||||
|
&& !ctl.canForgeAuthor()) {
|
||||||
|
reject(cmd, "you are not author " + author.getEmailAddress());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Require that committer matches the uploader.
|
// Require that committer matches the uploader.
|
||||||
//
|
//
|
||||||
if (!currentUser.getEmailAddresses().contains(committer.getEmailAddress())) {
|
if (!currentUser.getEmailAddresses().contains(committer.getEmailAddress())
|
||||||
|
&& !ctl.canForgeCommitter()) {
|
||||||
reject(cmd, "you are not committer " + committer.getEmailAddress());
|
reject(cmd, "you are not committer " + committer.getEmailAddress());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1283,7 +1326,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!sboAuthor && !sboCommitter && !sboMe) {
|
if (!sboAuthor && !sboCommitter && !sboMe && !ctl.canForgeCommitter()) {
|
||||||
reject(cmd, "not Signed-off-by author/committer/uploader");
|
reject(cmd, "not Signed-off-by author/committer/uploader");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1308,6 +1351,7 @@ public class ReceiveCommits implements PreReceiveHook, PostReceiveHook {
|
|||||||
while ((c = rw.next()) != null) {
|
while ((c = rw.next()) != null) {
|
||||||
final Ref ref = byCommit.get(c.copy());
|
final Ref ref = byCommit.get(c.copy());
|
||||||
if (ref != null) {
|
if (ref != null) {
|
||||||
|
rw.parseBody(c);
|
||||||
closeChange(cmd, PatchSet.Id.fromRef(ref.getName()), c);
|
closeChange(cmd, PatchSet.Id.fromRef(ref.getName()), c);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,9 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
|
import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_AUTHOR;
|
||||||
|
import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_COMMITTER;
|
||||||
|
import static com.google.gerrit.reviewdb.ApprovalCategory.FORGE_IDENTITY;
|
||||||
import static com.google.gerrit.reviewdb.ApprovalCategory.OWN;
|
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;
|
||||||
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_CREATE;
|
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_HEAD_CREATE;
|
||||||
@ -21,7 +24,6 @@ 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_HEAD_UPDATE;
|
||||||
import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG;
|
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_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.PUSH_TAG_SIGNED;
|
||||||
import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
|
import static com.google.gerrit.reviewdb.ApprovalCategory.READ;
|
||||||
|
|
||||||
@ -50,6 +52,9 @@ public class RefControl {
|
|||||||
private final ProjectControl projectControl;
|
private final ProjectControl projectControl;
|
||||||
private final String refName;
|
private final String refName;
|
||||||
|
|
||||||
|
private Boolean canForgeAuthor;
|
||||||
|
private Boolean canForgeCommitter;
|
||||||
|
|
||||||
RefControl(final ProjectControl projectControl, final String refName) {
|
RefControl(final ProjectControl projectControl, final String refName) {
|
||||||
this.projectControl = projectControl;
|
this.projectControl = projectControl;
|
||||||
this.refName = refName;
|
this.refName = refName;
|
||||||
@ -128,24 +133,28 @@ public class RefControl {
|
|||||||
return owner || canPerform(PUSH_HEAD, PUSH_HEAD_CREATE);
|
return owner || canPerform(PUSH_HEAD, PUSH_HEAD_CREATE);
|
||||||
|
|
||||||
} else if (object instanceof RevTag) {
|
} else if (object instanceof RevTag) {
|
||||||
|
final RevTag tag = (RevTag) object;
|
||||||
try {
|
try {
|
||||||
rw.parseBody(object);
|
rw.parseBody(tag);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final RevTag tag = (RevTag) object;
|
// If tagger is present, require it matches the user's email.
|
||||||
|
|
||||||
// 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();
|
final PersonIdent tagger = tag.getTaggerIdent();
|
||||||
if (tagger == null || !(getCurrentUser() instanceof IdentifiedUser)) {
|
if (tagger != null) {
|
||||||
return owner || canPerform(PUSH_TAG, PUSH_TAG_ANY);
|
boolean valid;
|
||||||
}
|
if (getCurrentUser() instanceof IdentifiedUser) {
|
||||||
final IdentifiedUser user = (IdentifiedUser) getCurrentUser();
|
final IdentifiedUser user = (IdentifiedUser) getCurrentUser();
|
||||||
if (!user.getEmailAddresses().contains(tagger.getEmailAddress())) {
|
final String addr = tagger.getEmailAddress();
|
||||||
return owner || canPerform(PUSH_TAG, PUSH_TAG_ANY);
|
valid = user.getEmailAddresses().contains(addr);
|
||||||
|
} else {
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (!valid && !owner && !canPerform(FORGE_IDENTITY, FORGE_COMMITTER)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If the tag has a PGP signature, allow a lower level of permission
|
// If the tag has a PGP signature, allow a lower level of permission
|
||||||
@ -181,6 +190,22 @@ public class RefControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @return true if this user can forge the author line in a commit. */
|
||||||
|
public boolean canForgeAuthor() {
|
||||||
|
if (canForgeAuthor == null) {
|
||||||
|
canForgeAuthor = canPerform(FORGE_IDENTITY, FORGE_AUTHOR);
|
||||||
|
}
|
||||||
|
return canForgeAuthor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @return true if this user can forge the committer line in a commit. */
|
||||||
|
public boolean canForgeCommitter() {
|
||||||
|
if (canForgeCommitter == null) {
|
||||||
|
canForgeCommitter = canPerform(FORGE_IDENTITY, FORGE_COMMITTER);
|
||||||
|
}
|
||||||
|
return canForgeCommitter;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean canPerform(ApprovalCategory.Id actionId, short level) {
|
private boolean canPerform(ApprovalCategory.Id actionId, short level) {
|
||||||
final Set<AccountGroup.Id> groups = getCurrentUser().getEffectiveGroups();
|
final Set<AccountGroup.Id> groups = getCurrentUser().getEffectiveGroups();
|
||||||
int val = Integer.MIN_VALUE;
|
int val = Integer.MIN_VALUE;
|
||||||
|
@ -91,6 +91,7 @@ public class SchemaCreator {
|
|||||||
initSubmitCategory(db);
|
initSubmitCategory(db);
|
||||||
initPushTagCategory(db);
|
initPushTagCategory(db);
|
||||||
initPushUpdateBranchCategory(db);
|
initPushUpdateBranchCategory(db);
|
||||||
|
initForgeIdentityCategory(db, sConfig);
|
||||||
initWildCardProject(db);
|
initWildCardProject(db);
|
||||||
|
|
||||||
final SqlDialect d = jdbc.getDialect();
|
final SqlDialect d = jdbc.getDialect();
|
||||||
@ -277,14 +278,13 @@ public class SchemaCreator {
|
|||||||
final ApprovalCategory cat;
|
final ApprovalCategory cat;
|
||||||
final ArrayList<ApprovalCategoryValue> vals;
|
final ArrayList<ApprovalCategoryValue> vals;
|
||||||
|
|
||||||
cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Annotated Tag");
|
cat = new ApprovalCategory(ApprovalCategory.PUSH_TAG, "Push Tag");
|
||||||
cat.setPosition((short) -1);
|
cat.setPosition((short) -1);
|
||||||
cat.setFunctionName(NoOpFunction.NAME);
|
cat.setFunctionName(NoOpFunction.NAME);
|
||||||
vals = new ArrayList<ApprovalCategoryValue>();
|
vals = new ArrayList<ApprovalCategoryValue>();
|
||||||
vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag"));
|
vals.add(value(cat, ApprovalCategory.PUSH_TAG_SIGNED, "Create Signed Tag"));
|
||||||
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED,
|
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANNOTATED,
|
||||||
"Create Annotated Tag"));
|
"Create Annotated Tag"));
|
||||||
vals.add(value(cat, ApprovalCategory.PUSH_TAG_ANY, "Create Any Tag"));
|
|
||||||
c.approvalCategories().insert(Collections.singleton(cat));
|
c.approvalCategories().insert(Collections.singleton(cat));
|
||||||
c.approvalCategoryValues().insert(vals);
|
c.approvalCategoryValues().insert(vals);
|
||||||
}
|
}
|
||||||
@ -306,6 +306,32 @@ public class SchemaCreator {
|
|||||||
c.approvalCategoryValues().insert(vals);
|
c.approvalCategoryValues().insert(vals);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initForgeIdentityCategory(final ReviewDb c,
|
||||||
|
final SystemConfig sConfig) throws OrmException {
|
||||||
|
final ApprovalCategory cat;
|
||||||
|
final ArrayList<ApprovalCategoryValue> values;
|
||||||
|
|
||||||
|
cat =
|
||||||
|
new ApprovalCategory(ApprovalCategory.FORGE_IDENTITY, "Forge Identity");
|
||||||
|
cat.setPosition((short) -1);
|
||||||
|
cat.setFunctionName(NoOpFunction.NAME);
|
||||||
|
values = new ArrayList<ApprovalCategoryValue>();
|
||||||
|
values.add(value(cat, ApprovalCategory.FORGE_AUTHOR,
|
||||||
|
"Forge Author Identity"));
|
||||||
|
values.add(value(cat, ApprovalCategory.FORGE_COMMITTER,
|
||||||
|
"Forge Committer or Tagger Identity"));
|
||||||
|
c.approvalCategories().insert(Collections.singleton(cat));
|
||||||
|
c.approvalCategoryValues().insert(values);
|
||||||
|
|
||||||
|
RefRight right =
|
||||||
|
new RefRight(new RefRight.Key(sConfig.wildProjectName,
|
||||||
|
new RefRight.RefPattern("refs/*"), ApprovalCategory.FORGE_IDENTITY,
|
||||||
|
sConfig.registeredGroupId));
|
||||||
|
right.setMinValue(ApprovalCategory.FORGE_AUTHOR);
|
||||||
|
right.setMaxValue(ApprovalCategory.FORGE_AUTHOR);
|
||||||
|
c.refRights().insert(Collections.singleton(right));
|
||||||
|
}
|
||||||
|
|
||||||
private static ApprovalCategoryValue value(final ApprovalCategory cat,
|
private static ApprovalCategoryValue value(final ApprovalCategory cat,
|
||||||
final int value, final String name) {
|
final int value, final String name) {
|
||||||
return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(),
|
return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(),
|
||||||
|
@ -32,7 +32,7 @@ import java.util.List;
|
|||||||
/** A version of the database schema. */
|
/** A version of the database schema. */
|
||||||
public abstract class SchemaVersion {
|
public abstract class SchemaVersion {
|
||||||
/** The current schema version. */
|
/** The current schema version. */
|
||||||
private static final Class<? extends SchemaVersion> C = Schema_27.class;
|
private static final Class<? extends SchemaVersion> C = Schema_28.class;
|
||||||
|
|
||||||
public static class Module extends AbstractModule {
|
public static class Module extends AbstractModule {
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
// 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.ApprovalCategory;
|
||||||
|
import com.google.gerrit.reviewdb.ApprovalCategoryValue;
|
||||||
|
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;
|
||||||
|
import com.google.gwtorm.client.OrmException;
|
||||||
|
import com.google.gwtorm.jdbc.JdbcSchema;
|
||||||
|
import com.google.gwtorm.schema.sql.DialectH2;
|
||||||
|
import com.google.gwtorm.schema.sql.DialectMySQL;
|
||||||
|
import com.google.gwtorm.schema.sql.DialectPostgreSQL;
|
||||||
|
import com.google.gwtorm.schema.sql.SqlDialect;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
class Schema_28 extends SchemaVersion {
|
||||||
|
@Inject
|
||||||
|
Schema_28(Provider<Schema_27> prior) {
|
||||||
|
super(prior);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void migrateData(ReviewDb db) throws OrmException, SQLException {
|
||||||
|
final SystemConfig cfg = db.systemConfig().get(new SystemConfig.Key());
|
||||||
|
ApprovalCategory cat;
|
||||||
|
|
||||||
|
initForgeIdentityCategory(db, cfg);
|
||||||
|
|
||||||
|
// Don't grant FORGE_COMMITTER to existing PUSH_HEAD rights. That
|
||||||
|
// is considered a bug that we are fixing with this schema upgrade.
|
||||||
|
// Administrators might need to relax permissions manually after the
|
||||||
|
// upgrade if that forgery is critical to their workflow.
|
||||||
|
|
||||||
|
cat = db.approvalCategories().get(ApprovalCategory.PUSH_TAG);
|
||||||
|
if (cat != null && "Push Annotated Tag".equals(cat.getName())) {
|
||||||
|
cat.setName("Push Tag");
|
||||||
|
db.approvalCategories().update(Collections.singleton(cat));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Since we deleted Push Tags +3, drop anything using +3 down to +2.
|
||||||
|
//
|
||||||
|
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
|
||||||
|
try {
|
||||||
|
stmt.execute("UPDATE ref_rights SET max_value = "
|
||||||
|
+ ApprovalCategory.PUSH_TAG_ANNOTATED + " WHERE max_value >= 3");
|
||||||
|
stmt.execute("UPDATE ref_rights SET min_value = "
|
||||||
|
+ ApprovalCategory.PUSH_TAG_ANNOTATED + " WHERE min_value >= 3");
|
||||||
|
} finally {
|
||||||
|
stmt.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initForgeIdentityCategory(final ReviewDb c,
|
||||||
|
final SystemConfig sConfig) throws OrmException {
|
||||||
|
final ApprovalCategory cat;
|
||||||
|
final ArrayList<ApprovalCategoryValue> values;
|
||||||
|
|
||||||
|
cat =
|
||||||
|
new ApprovalCategory(ApprovalCategory.FORGE_IDENTITY, "Forge Identity");
|
||||||
|
cat.setPosition((short) -1);
|
||||||
|
cat.setFunctionName(NoOpFunction.NAME);
|
||||||
|
values = new ArrayList<ApprovalCategoryValue>();
|
||||||
|
values.add(value(cat, ApprovalCategory.FORGE_AUTHOR,
|
||||||
|
"Forge Author Identity"));
|
||||||
|
values.add(value(cat, ApprovalCategory.FORGE_COMMITTER,
|
||||||
|
"Forge Committer or Tagger Identity"));
|
||||||
|
c.approvalCategories().insert(Collections.singleton(cat));
|
||||||
|
c.approvalCategoryValues().insert(values);
|
||||||
|
|
||||||
|
RefRight right =
|
||||||
|
new RefRight(new RefRight.Key(sConfig.wildProjectName,
|
||||||
|
new RefRight.RefPattern("refs/*"), ApprovalCategory.FORGE_IDENTITY,
|
||||||
|
sConfig.registeredGroupId));
|
||||||
|
right.setMinValue(ApprovalCategory.FORGE_AUTHOR);
|
||||||
|
right.setMaxValue(ApprovalCategory.FORGE_AUTHOR);
|
||||||
|
c.refRights().insert(Collections.singleton(right));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ApprovalCategoryValue value(final ApprovalCategory cat,
|
||||||
|
final int value, final String name) {
|
||||||
|
return new ApprovalCategoryValue(new ApprovalCategoryValue.Id(cat.getId(),
|
||||||
|
(short) value), name);
|
||||||
|
}
|
||||||
|
}
|
@ -233,7 +233,7 @@ public class SchemaCreatorTest extends TestCase {
|
|||||||
cat = c.approvalCategories().get(ApprovalCategory.PUSH_TAG);
|
cat = c.approvalCategories().get(ApprovalCategory.PUSH_TAG);
|
||||||
assertNotNull(cat);
|
assertNotNull(cat);
|
||||||
assertEquals(ApprovalCategory.PUSH_TAG, cat.getId());
|
assertEquals(ApprovalCategory.PUSH_TAG, cat.getId());
|
||||||
assertEquals("Push Annotated Tag", cat.getName());
|
assertEquals("Push Tag", cat.getName());
|
||||||
assertNull(cat.getAbbreviatedName());
|
assertNull(cat.getAbbreviatedName());
|
||||||
assertEquals(NoOpFunction.NAME, cat.getFunctionName());
|
assertEquals(NoOpFunction.NAME, cat.getFunctionName());
|
||||||
assertTrue(cat.isAction());
|
assertTrue(cat.isAction());
|
||||||
@ -242,8 +242,7 @@ public class SchemaCreatorTest extends TestCase {
|
|||||||
}
|
}
|
||||||
assertValueRange(ApprovalCategory.PUSH_TAG, //
|
assertValueRange(ApprovalCategory.PUSH_TAG, //
|
||||||
ApprovalCategory.PUSH_TAG_SIGNED, //
|
ApprovalCategory.PUSH_TAG_SIGNED, //
|
||||||
ApprovalCategory.PUSH_TAG_ANNOTATED, //
|
ApprovalCategory.PUSH_TAG_ANNOTATED);
|
||||||
ApprovalCategory.PUSH_TAG_ANY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testCreateSchema_ApprovalCategory_PushHead() throws OrmException {
|
public void testCreateSchema_ApprovalCategory_PushHead() throws OrmException {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user