Verify the user as a valid, accepted contributor agreement before upload
We cannot accept changes into most open source projects unless the contributor has acknowledged they are permitted and willing to make the code available under the necessary licenses. Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
		| @@ -38,7 +38,7 @@ public class Link implements HistoryListener { | |||||||
|   public static final String SETTINGS_SSHKEYS = "settings,ssh-keys"; |   public static final String SETTINGS_SSHKEYS = "settings,ssh-keys"; | ||||||
|   public static final String SETTINGS_WEBIDENT = "settings,web-identities"; |   public static final String SETTINGS_WEBIDENT = "settings,web-identities"; | ||||||
|   public static final String SETTINGS_AGREEMENTS = "settings,agreements"; |   public static final String SETTINGS_AGREEMENTS = "settings,agreements"; | ||||||
|   public static final String SETTINGS_CONTACT = "settings,contact-information"; |   public static final String SETTINGS_CONTACT = "settings,contact"; | ||||||
|  |  | ||||||
|   public static final String MINE = "mine"; |   public static final String MINE = "mine"; | ||||||
|   public static final String MINE_UNCLAIMED = "mine,unclaimed"; |   public static final String MINE_UNCLAIMED = "mine,unclaimed"; | ||||||
|   | |||||||
| @@ -106,6 +106,10 @@ public final class AccountAgreement { | |||||||
|     status = Status.NEW.getCode(); |     status = Status.NEW.getCode(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   public ContributorAgreement.Id getAgreementId() { | ||||||
|  |     return key.claId; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   public Timestamp getAcceptedOn() { |   public Timestamp getAcceptedOn() { | ||||||
|     return acceptedOn; |     return acceptedOn; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -16,8 +16,11 @@ package com.google.gerrit.server.ssh; | |||||||
|  |  | ||||||
| import com.google.gerrit.client.Link; | import com.google.gerrit.client.Link; | ||||||
| import com.google.gerrit.client.reviewdb.Account; | import com.google.gerrit.client.reviewdb.Account; | ||||||
|  | import com.google.gerrit.client.reviewdb.AccountAgreement; | ||||||
| import com.google.gerrit.client.reviewdb.Branch; | import com.google.gerrit.client.reviewdb.Branch; | ||||||
| import com.google.gerrit.client.reviewdb.Change; | import com.google.gerrit.client.reviewdb.Change; | ||||||
|  | import com.google.gerrit.client.reviewdb.ContactInformation; | ||||||
|  | import com.google.gerrit.client.reviewdb.ContributorAgreement; | ||||||
| import com.google.gerrit.client.reviewdb.PatchSet; | import com.google.gerrit.client.reviewdb.PatchSet; | ||||||
| import com.google.gerrit.git.PatchSetImporter; | import com.google.gerrit.git.PatchSetImporter; | ||||||
| import com.google.gerrit.server.GerritServer; | import com.google.gerrit.server.GerritServer; | ||||||
| @@ -78,11 +81,10 @@ class Receive extends AbstractGitCommand { | |||||||
|   @Override |   @Override | ||||||
|   protected void runImpl() throws IOException, Failure { |   protected void runImpl() throws IOException, Failure { | ||||||
|     server = getGerritServer(); |     server = getGerritServer(); | ||||||
|  |     verifyActiveContributorAgreement(); | ||||||
|     lookup(reviewerId, "reviewer", reviewerEmail); |     lookup(reviewerId, "reviewer", reviewerEmail); | ||||||
|     lookup(ccId, "cc", ccEmail); |     lookup(ccId, "cc", ccEmail); | ||||||
|  |  | ||||||
|     // TODO verify user has signed a CLA for this project |  | ||||||
|  |  | ||||||
|     rp = new ReceivePack(repo); |     rp = new ReceivePack(repo); | ||||||
|     rp.setAllowCreates(true); |     rp.setAllowCreates(true); | ||||||
|     rp.setAllowDeletes(true); |     rp.setAllowDeletes(true); | ||||||
| @@ -116,6 +118,92 @@ class Receive extends AbstractGitCommand { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   private void verifyActiveContributorAgreement() throws Failure { | ||||||
|  |     AccountAgreement bestAgreement = null; | ||||||
|  |     ContributorAgreement bestCla = null; | ||||||
|  |     try { | ||||||
|  |       for (final AccountAgreement a : db.accountAgreements().byAccount( | ||||||
|  |           userAccount.getId()).toList()) { | ||||||
|  |         final ContributorAgreement cla = | ||||||
|  |             db.contributorAgreements().get(a.getAgreementId()); | ||||||
|  |         if (cla == null || !cla.isActive()) { | ||||||
|  |           continue; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         if (bestAgreement == null | ||||||
|  |             || bestAgreement.getStatus() != AccountAgreement.Status.VERIFIED) { | ||||||
|  |           bestAgreement = a; | ||||||
|  |           bestCla = cla; | ||||||
|  |         } | ||||||
|  |         if (bestAgreement.getStatus() == AccountAgreement.Status.VERIFIED) { | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } catch (OrmException e) { | ||||||
|  |       throw new Failure(1, "database error"); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (bestCla != null && bestCla.isRequireContactInformation()) { | ||||||
|  |       final ContactInformation info = userAccount.getContactInformation(); | ||||||
|  |       boolean fail = false; | ||||||
|  |       fail |= missing(userAccount.getFullName()); | ||||||
|  |       fail |= missing(userAccount.getPreferredEmail()); | ||||||
|  |       fail |= info == null || missing(info.getAddress()); | ||||||
|  |  | ||||||
|  |       if (fail) { | ||||||
|  |         final StringBuilder msg = new StringBuilder(); | ||||||
|  |         msg.append("\nfatal: "); | ||||||
|  |         msg.append(bestCla.getShortName()); | ||||||
|  |         msg.append(" contributor agreement requires"); | ||||||
|  |         msg.append(" current contact information.\n"); | ||||||
|  |         if (server.getCanonicalURL() != null) { | ||||||
|  |           msg.append("\nPlease review your contact information"); | ||||||
|  |           msg.append(":\n\n  "); | ||||||
|  |           msg.append(server.getCanonicalURL()); | ||||||
|  |           msg.append("Gerrit#"); | ||||||
|  |           msg.append(Link.SETTINGS_CONTACT); | ||||||
|  |           msg.append("\n"); | ||||||
|  |         } | ||||||
|  |         msg.append("\n"); | ||||||
|  |         throw new Failure(1, msg.toString()); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (bestAgreement != null) { | ||||||
|  |       switch (bestAgreement.getStatus()) { | ||||||
|  |         case VERIFIED: | ||||||
|  |           return; | ||||||
|  |         case REJECTED: | ||||||
|  |           throw new Failure(1, "\nfatal: " + bestCla.getShortName() | ||||||
|  |               + " contributor agreement was rejected." | ||||||
|  |               + "\n       (rejected on " + bestAgreement.getReviewedOn() | ||||||
|  |               + ")\n"); | ||||||
|  |         case NEW: | ||||||
|  |           throw new Failure(1, "\nfatal: " + bestCla.getShortName() | ||||||
|  |               + " contributor agreement is still pending review.\n"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     final StringBuilder msg = new StringBuilder(); | ||||||
|  |     msg.append("\nfatal: A Contributor Agreement" | ||||||
|  |         + " must be completed before uploading"); | ||||||
|  |     if (server.getCanonicalURL() != null) { | ||||||
|  |       msg.append(":\n\n  "); | ||||||
|  |       msg.append(server.getCanonicalURL()); | ||||||
|  |       msg.append("Gerrit#"); | ||||||
|  |       msg.append(Link.SETTINGS_AGREEMENTS); | ||||||
|  |       msg.append("\n"); | ||||||
|  |     } else { | ||||||
|  |       msg.append("."); | ||||||
|  |     } | ||||||
|  |     msg.append("\n"); | ||||||
|  |     throw new Failure(1, msg.toString()); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   private static boolean missing(final String value) { | ||||||
|  |     return value == null || value.trim().equals(""); | ||||||
|  |   } | ||||||
|  |  | ||||||
|   private void lookup(final Set<Account.Id> accountIds, |   private void lookup(final Set<Account.Id> accountIds, | ||||||
|       final String addressType, final Set<String> emails) throws Failure { |       final String addressType, final Set<String> emails) throws Failure { | ||||||
|     final StringBuilder errors = new StringBuilder(); |     final StringBuilder errors = new StringBuilder(); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Shawn O. Pearce
					Shawn O. Pearce