diff options
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java')
-rw-r--r-- | gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java | 398 |
1 files changed, 315 insertions, 83 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java index 2a55019761..29a6432456 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java @@ -14,28 +14,51 @@ package com.google.gerrit.server.project; -import static com.google.gerrit.common.CollectionsUtil.*; -import com.google.gerrit.reviewdb.AccountGroup; -import com.google.gerrit.reviewdb.ApprovalCategory; -import com.google.gerrit.reviewdb.Branch; -import com.google.gerrit.reviewdb.Change; -import com.google.gerrit.reviewdb.Project; -import com.google.gerrit.reviewdb.RefRight; -import com.google.gerrit.reviewdb.SystemConfig; +import com.google.gerrit.common.PageLinks; +import com.google.gerrit.common.data.AccessSection; +import com.google.gerrit.common.data.Capable; +import com.google.gerrit.common.data.GroupReference; +import com.google.gerrit.common.data.Permission; +import com.google.gerrit.common.data.PermissionRule; +import com.google.gerrit.reviewdb.client.AbstractAgreement; +import com.google.gerrit.reviewdb.client.AccountAgreement; +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.reviewdb.client.AccountGroupAgreement; +import com.google.gerrit.reviewdb.client.Branch; +import com.google.gerrit.reviewdb.client.Change; +import com.google.gerrit.reviewdb.client.ContributorAgreement; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.CurrentUser; +import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.ReplicationUser; +import com.google.gerrit.server.account.GroupCache; +import com.google.gerrit.server.config.CanonicalWebUrl; import com.google.gerrit.server.config.GitReceivePackGroups; import com.google.gerrit.server.config.GitUploadPackGroups; +import com.google.gwtorm.server.OrmException; +import com.google.gwtorm.server.SchemaFactory; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.assistedinject.Assisted; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.List; +import java.util.Map; import java.util.Set; +import javax.annotation.Nullable; + /** Access control management for a user accessing a project's data. */ public class ProjectControl { + private static final Logger log = + LoggerFactory.getLogger(ProjectControl.class); + public static final int VISIBLE = 1 << 0; public static final int OWNER = 1 << 1; @@ -58,22 +81,16 @@ public class ProjectControl { } public static class Factory { - private final ProjectCache projectCache; - private final Provider<CurrentUser> user; + private final Provider<PerRequestProjectControlCache> userCache; @Inject - Factory(final ProjectCache pc, final Provider<CurrentUser> cu) { - projectCache = pc; - user = cu; + Factory(Provider<PerRequestProjectControlCache> uc) { + userCache = uc; } public ProjectControl controlFor(final Project.NameKey nameKey) throws NoSuchProjectException { - final ProjectState p = projectCache.get(nameKey); - if (p == null) { - throw new NoSuchProjectException(nameKey); - } - return p.controlFor(user.get()); + return userCache.get().get(nameKey); } public ProjectControl validateFor(final Project.NameKey nameKey) @@ -103,34 +120,42 @@ public class ProjectControl { ProjectControl create(CurrentUser who, ProjectState ps); } - private final SystemConfig systemConfig; - private final Set<AccountGroup.Id> uploadGroups; - private final Set<AccountGroup.Id> receiveGroups; + private final Set<AccountGroup.UUID> uploadGroups; + private final Set<AccountGroup.UUID> receiveGroups; - private final RefControl.Factory refControlFactory; + private final String canonicalWebUrl; + private final SchemaFactory<ReviewDb> schema; private final CurrentUser user; private final ProjectState state; + private final GroupCache groupCache; + private final PermissionCollection.Factory permissionFilter; + + private List<SectionMatcher> allSections; + private Map<String, RefControl> refControls; + private Boolean declaredOwner; @Inject - ProjectControl(final SystemConfig systemConfig, - @GitUploadPackGroups Set<AccountGroup.Id> uploadGroups, - @GitReceivePackGroups Set<AccountGroup.Id> receiveGroups, - final RefControl.Factory refControlFactory, + ProjectControl(@GitUploadPackGroups Set<AccountGroup.UUID> uploadGroups, + @GitReceivePackGroups Set<AccountGroup.UUID> receiveGroups, + final SchemaFactory<ReviewDb> schema, final GroupCache groupCache, + final PermissionCollection.Factory permissionFilter, + @CanonicalWebUrl @Nullable final String canonicalWebUrl, @Assisted CurrentUser who, @Assisted ProjectState ps) { - this.systemConfig = systemConfig; this.uploadGroups = uploadGroups; this.receiveGroups = receiveGroups; - this.refControlFactory = refControlFactory; + this.schema = schema; + this.groupCache = groupCache; + this.permissionFilter = permissionFilter; + this.canonicalWebUrl = canonicalWebUrl; user = who; state = ps; } - public ProjectControl forAnonymousUser() { - return state.controlForAnonymousUser(); - } - - public ProjectControl forUser(final CurrentUser who) { - return state.controlFor(who); + public ProjectControl forUser(CurrentUser who) { + ProjectControl r = state.controlFor(who); + // Not per-user, and reusing saves lookup time. + r.allSections = allSections; + return r; } public ChangeControl controlFor(final Change change) { @@ -142,7 +167,17 @@ public class ProjectControl { } public RefControl controlForRef(String refName) { - return refControlFactory.create(this, refName); + if (refControls == null) { + refControls = new HashMap<String, RefControl>(); + } + RefControl ctl = refControls.get(refName); + if (ctl == null) { + PermissionCollection relevant = + permissionFilter.filter(access(), refName, user.getUserName()); + ctl = new RefControl(this, refName, relevant); + refControls.put(refName, ctl); + } + return ctl; } public CurrentUser getCurrentUser() { @@ -154,93 +189,257 @@ public class ProjectControl { } public Project getProject() { - return getProjectState().getProject(); + return state.getProject(); + } + + private boolean isHidden() { + return getProject().getState().equals(Project.State.HIDDEN); } /** Can this user see this project exists? */ public boolean isVisible() { - return visibleForReplication() - || canPerformOnAnyRef(ApprovalCategory.READ, (short) 1); + return (visibleForReplication() + || canPerformOnAnyRef(Permission.READ)) && !isHidden(); } public boolean canAddRefs() { - return (canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, ApprovalCategory.PUSH_HEAD_CREATE) + return (canPerformOnAnyRef(Permission.CREATE) || isOwnerAnyRef()); } /** Can this user see all the refs in this projects? */ public boolean allRefsAreVisible() { return visibleForReplication() - || canPerformOnAllRefs(ApprovalCategory.READ, (short) 1); + || canPerformOnAllRefs(Permission.READ); } /** Is this project completely visible for replication? */ boolean visibleForReplication() { - return getCurrentUser() instanceof ReplicationUser - && ((ReplicationUser) getCurrentUser()).isEverythingVisible(); + return user instanceof ReplicationUser + && ((ReplicationUser) user).isEverythingVisible(); } /** Is this user a project owner? Ownership does not imply {@link #isVisible()} */ public boolean isOwner() { - return controlForRef(RefRight.ALL).isOwner() - || getCurrentUser().isAdministrator(); + return isDeclaredOwner() + || user.getCapabilities().canAdministrateServer(); + } + + private boolean isDeclaredOwner() { + if (declaredOwner == null) { + declaredOwner = state.isOwner(user.getEffectiveGroups()); + } + return declaredOwner; } /** Does this user have ownership on at least one reference name? */ public boolean isOwnerAnyRef() { - return canPerformOnAnyRef(ApprovalCategory.OWN, (short) 1) - || getCurrentUser().isAdministrator(); + return canPerformOnAnyRef(Permission.OWNER) + || user.getCapabilities().canAdministrateServer(); } /** @return true if the user can upload to at least one reference */ - public boolean canPushToAtLeastOneRef() { - return canPerformOnAnyRef(ApprovalCategory.READ, (short) 2) - || canPerformOnAnyRef(ApprovalCategory.PUSH_HEAD, (short) 1) - || canPerformOnAnyRef(ApprovalCategory.PUSH_TAG, (short) 1); + public Capable canPushToAtLeastOneRef() { + if (! canPerformOnAnyRef(Permission.PUSH) && + ! canPerformOnAnyRef(Permission.PUSH_TAG)) { + String pName = state.getProject().getName(); + return new Capable("Upload denied for project '" + pName + "'"); + } + Project project = state.getProject(); + if (project.isUseContributorAgreements()) { + try { + return verifyActiveContributorAgreement(); + } catch (OrmException e) { + log.error("Cannot query database for agreements", e); + return new Capable("Cannot verify contribution agreement"); + } + } + return Capable.OK; } - // TODO (anatol.pomazau): Try to merge this method with similar RefRightsForPattern#canPerform - private boolean canPerformOnAnyRef(ApprovalCategory.Id actionId, - short requireValue) { - final Set<AccountGroup.Id> groups = getEffectiveUserGroups(); + public Set<GroupReference> getAllGroups() { + final Set<GroupReference> all = new HashSet<GroupReference>(); + for (final SectionMatcher matcher : access()) { + final AccessSection section = matcher.section; + for (final Permission permission : section.getPermissions()) { + for (final PermissionRule rule : permission.getRules()) { + all.add(rule.getGroup()); + } + } + } + return all; + } - for (final RefRight pr : state.getAllRights(actionId, true)) { - if (groups.contains(pr.getAccountGroupId()) - && pr.getMaxValue() >= requireValue) { - return true; + private Capable verifyActiveContributorAgreement() throws OrmException { + if (! (user instanceof IdentifiedUser)) { + return new Capable("Must be logged in to verify Contributor Agreement"); + } + final IdentifiedUser iUser = (IdentifiedUser) user; + final ReviewDb db = schema.open(); + + AbstractAgreement bestAgreement = null; + ContributorAgreement bestCla = null; + try { + + OUTER: for (AccountGroup.UUID groupUUID : iUser.getEffectiveGroups().getKnownGroups()) { + AccountGroup group = groupCache.get(groupUUID); + if (group == null) { + continue; + } + + final List<AccountGroupAgreement> temp = + db.accountGroupAgreements().byGroup(group.getId()).toList(); + + Collections.reverse(temp); + + for (final AccountGroupAgreement a : temp) { + final ContributorAgreement cla = + db.contributorAgreements().get(a.getAgreementId()); + if (cla == null) { + continue; + } + + bestAgreement = a; + bestCla = cla; + break OUTER; + } + } + + if (bestAgreement == null) { + final List<AccountAgreement> temp = + db.accountAgreements().byAccount(iUser.getAccountId()).toList(); + + Collections.reverse(temp); + + for (final AccountAgreement a : temp) { + final ContributorAgreement cla = + db.contributorAgreements().get(a.getAgreementId()); + if (cla == null) { + continue; + } + + bestAgreement = a; + bestCla = cla; + break; + } } + } finally { + db.close(); } - return false; - } - /** - * @return the effective groups of the current user for this project - */ - private Set<AccountGroup.Id> getEffectiveUserGroups() { - final Set<AccountGroup.Id> userGroups = user.getEffectiveGroups(); - if (isOwner()) { - final Set<AccountGroup.Id> userGroupsOnProject = - new HashSet<AccountGroup.Id>(userGroups.size() + 1); - userGroupsOnProject.addAll(userGroups); - userGroupsOnProject.add(systemConfig.ownerGroupId); - return Collections.unmodifiableSet(userGroupsOnProject); + if (bestCla != null && !bestCla.isActive()) { + final StringBuilder msg = new StringBuilder(); + msg.append(bestCla.getShortName()); + msg.append(" contributor agreement is expired.\n"); + if (canonicalWebUrl != null) { + msg.append("\nPlease complete a new agreement"); + msg.append(":\n\n "); + msg.append(canonicalWebUrl); + msg.append("#"); + msg.append(PageLinks.SETTINGS_AGREEMENTS); + msg.append("\n"); + } + msg.append("\n"); + return new Capable(msg.toString()); + } + + if (bestCla != null && bestCla.isRequireContactInformation()) { + boolean fail = false; + fail |= missing(iUser.getAccount().getFullName()); + fail |= missing(iUser.getAccount().getPreferredEmail()); + fail |= !iUser.getAccount().isContactFiled(); + + if (fail) { + final StringBuilder msg = new StringBuilder(); + msg.append(bestCla.getShortName()); + msg.append(" contributor agreement requires"); + msg.append(" current contact information.\n"); + if (canonicalWebUrl != null) { + msg.append("\nPlease review your contact information"); + msg.append(":\n\n "); + msg.append(canonicalWebUrl); + msg.append("#"); + msg.append(PageLinks.SETTINGS_CONTACT); + msg.append("\n"); + } + msg.append("\n"); + return new Capable(msg.toString()); + } + } + + if (bestAgreement != null) { + switch (bestAgreement.getStatus()) { + case VERIFIED: + return Capable.OK; + case REJECTED: + return new Capable(bestCla.getShortName() + + " contributor agreement was rejected." + + "\n (rejected on " + bestAgreement.getReviewedOn() + + ")\n"); + case NEW: + return new Capable(bestCla.getShortName() + + " contributor agreement is still pending review.\n"); + } + } + + final StringBuilder msg = new StringBuilder(); + msg.append(" A Contributor Agreement must be completed before uploading"); + if (canonicalWebUrl != null) { + msg.append(":\n\n "); + msg.append(canonicalWebUrl); + msg.append("#"); + msg.append(PageLinks.SETTINGS_AGREEMENTS); + msg.append("\n"); } else { - return userGroups; + msg.append("."); + } + msg.append("\n"); + return new Capable(msg.toString()); + } + + private static boolean missing(final String value) { + return value == null || value.trim().equals(""); + } + + private boolean canPerformOnAnyRef(String permissionName) { + for (SectionMatcher matcher : access()) { + AccessSection section = matcher.section; + Permission permission = section.getPermission(permissionName); + if (permission == null) { + continue; + } + + for (PermissionRule rule : permission.getRules()) { + if (rule.isBlock() || rule.isDeny() || !match(rule)) { + continue; + } + + // Being in a group that was granted this permission is only an + // approximation. There might be overrides and doNotInherit + // that would render this to be false. + // + if (controlForRef(section.getName()).canPerform(permissionName)) { + return true; + } else { + break; + } + } } + + return false; } - private boolean canPerformOnAllRefs(ApprovalCategory.Id actionId, - short requireValue) { + private boolean canPerformOnAllRefs(String permission) { boolean canPerform = false; - final Set<String> patterns = allRefPatterns(actionId); - if (patterns.contains(RefRight.ALL)) { + Set<String> patterns = allRefPatterns(permission); + if (patterns.contains(AccessSection.ALL)) { // Only possible if granted on the pattern that // matches every possible reference. Check all // patterns also have the permission. // for (final String pattern : patterns) { - if (controlForRef(pattern).canPerform(actionId, requireValue)) { + if (controlForRef(pattern).canPerform(permission)) { canPerform = true; } else { return false; @@ -250,19 +449,52 @@ public class ProjectControl { return canPerform; } - private Set<String> allRefPatterns(ApprovalCategory.Id actionId) { - final Set<String> all = new HashSet<String>(); - for (final RefRight pr : state.getAllRights(actionId, true)) { - all.add(pr.getRefPattern()); + private Set<String> allRefPatterns(String permissionName) { + Set<String> all = new HashSet<String>(); + for (SectionMatcher matcher : access()) { + AccessSection section = matcher.section; + Permission permission = section.getPermission(permissionName); + if (permission != null) { + all.add(section.getName()); + } } return all; } + private List<SectionMatcher> access() { + if (allSections == null) { + allSections = state.getAllSections(); + } + return allSections; + } + + boolean match(PermissionRule rule) { + return match(rule.getGroup().getUUID()); + } + + boolean match(AccountGroup.UUID uuid) { + if (AccountGroup.PROJECT_OWNERS.equals(uuid)) { + return isDeclaredOwner(); + } else { + return user.getEffectiveGroups().contains(uuid); + } + } + public boolean canRunUploadPack() { - return isAnyIncludedIn(uploadGroups, getEffectiveUserGroups()); + for (AccountGroup.UUID group : uploadGroups) { + if (match(group)) { + return true; + } + } + return false; } public boolean canRunReceivePack() { - return isAnyIncludedIn(receiveGroups, getEffectiveUserGroups()); + for (AccountGroup.UUID group : receiveGroups) { + if (match(group)) { + return true; + } + } + return false; } } |