summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
diff options
context:
space:
mode:
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.java398
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;
}
}