diff options
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java')
-rw-r--r-- | gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java | 591 |
1 files changed, 0 insertions, 591 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java deleted file mode 100644 index cde2c8098e..0000000000 --- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java +++ /dev/null @@ -1,591 +0,0 @@ -// 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.project; - -import static com.google.common.base.Preconditions.checkArgument; - -import com.google.gerrit.common.data.Permission; -import com.google.gerrit.common.data.PermissionRange; -import com.google.gerrit.common.data.PermissionRule; -import com.google.gerrit.extensions.restapi.AuthException; -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.notedb.ChangeNotes; -import com.google.gerrit.server.permissions.FailedPermissionBackend; -import com.google.gerrit.server.permissions.PermissionBackend.ForChange; -import com.google.gerrit.server.permissions.PermissionBackend.ForRef; -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.util.Providers; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.EnumSet; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -/** Manages access control for Git references (aka branches, tags). */ -public class RefControl { - private final ProjectControl projectControl; - private final String refName; - - /** All permissions that apply to this reference. */ - private final PermissionCollection relevant; - - /** Cached set of permissions matching this user. */ - private final Map<String, List<PermissionRule>> effective; - - private Boolean owner; - private Boolean canForgeAuthor; - private Boolean canForgeCommitter; - private Boolean isVisible; - - RefControl(ProjectControl projectControl, String ref, PermissionCollection relevant) { - this.projectControl = projectControl; - this.refName = ref; - this.relevant = relevant; - this.effective = new HashMap<>(); - } - - public String getRefName() { - return refName; - } - - public ProjectControl getProjectControl() { - return projectControl; - } - - public CurrentUser getUser() { - return projectControl.getUser(); - } - - public RefControl forUser(CurrentUser who) { - ProjectControl newCtl = projectControl.forUser(who); - if (relevant.isUserSpecific()) { - return newCtl.controlForRef(getRefName()); - } - return new RefControl(newCtl, getRefName(), relevant); - } - - /** Is this user a ref owner? */ - public boolean isOwner() { - if (owner == null) { - if (canPerform(Permission.OWNER)) { - owner = true; - - } else { - owner = projectControl.isOwner(); - } - } - return owner; - } - - /** Can this user see this reference exists? */ - boolean isVisible() { - if (isVisible == null) { - isVisible = - (getUser().isInternalUser() || canPerform(Permission.READ)) - && isProjectStatePermittingRead(); - } - return isVisible; - } - - /** Can this user see other users change edits? */ - public boolean isEditVisible() { - return canViewPrivateChanges(); - } - - private boolean canUpload() { - return projectControl.controlForRef("refs/for/" + getRefName()).canPerform(Permission.PUSH) - && isProjectStatePermittingWrite(); - } - - /** @return true if this user can add a new patch set to this ref */ - boolean canAddPatchSet() { - return projectControl - .controlForRef("refs/for/" + getRefName()) - .canPerform(Permission.ADD_PATCH_SET) - && isProjectStatePermittingWrite(); - } - - /** @return true if this user can submit merge patch sets to this ref */ - private boolean canUploadMerges() { - return projectControl - .controlForRef("refs/for/" + getRefName()) - .canPerform(Permission.PUSH_MERGE) - && isProjectStatePermittingWrite(); - } - - /** @return true if this user can rebase changes on this ref */ - boolean canRebase() { - return canPerform(Permission.REBASE) && isProjectStatePermittingWrite(); - } - - /** @return true if this user can submit patch sets to this ref */ - boolean canSubmit(boolean isChangeOwner) { - if (RefNames.REFS_CONFIG.equals(refName)) { - // Always allow project owners to submit configuration changes. - // Submitting configuration changes modifies the access control - // rules. Allowing this to be done by a non-project-owner opens - // a security hole enabling editing of access rules, and thus - // granting of powers beyond submitting to the configuration. - return projectControl.isOwner(); - } - return canPerform(Permission.SUBMIT, isChangeOwner) && isProjectStatePermittingWrite(); - } - - /** @return true if the user can update the reference as a fast-forward. */ - private boolean canUpdate() { - if (RefNames.REFS_CONFIG.equals(refName) && !projectControl.isOwner()) { - // Pushing requires being at least project owner, in addition to push. - // Pushing configuration changes modifies the access control - // rules. Allowing this to be done by a non-project-owner opens - // a security hole enabling editing of access rules, and thus - // granting of powers beyond pushing to the configuration. - - // On the AllProjects project the owner access right cannot be assigned, - // this why for the AllProjects project we allow administrators to push - // configuration changes if they have push without being project owner. - if (!(projectControl.getProjectState().isAllProjects() && projectControl.isAdmin())) { - return false; - } - } - return canPerform(Permission.PUSH) && isProjectStatePermittingWrite(); - } - - /** @return true if the user can rewind (force push) the reference. */ - private boolean canForceUpdate() { - if (!isProjectStatePermittingWrite()) { - return false; - } - - if (canPushWithForce()) { - return true; - } - - switch (getUser().getAccessPath()) { - case GIT: - return false; - - case JSON_RPC: - case REST_API: - case SSH_COMMAND: - case UNKNOWN: - case WEB_BROWSER: - default: - return (isOwner() && !isForceBlocked(Permission.PUSH)) || projectControl.isAdmin(); - } - } - - private boolean isProjectStatePermittingWrite() { - return getProjectControl().getProject().getState().permitsWrite(); - } - - private boolean isProjectStatePermittingRead() { - return getProjectControl().getProject().getState().permitsRead(); - } - - private boolean canPushWithForce() { - if (!isProjectStatePermittingWrite() - || (RefNames.REFS_CONFIG.equals(refName) && !projectControl.isOwner())) { - // Pushing requires being at least project owner, in addition to push. - // Pushing configuration changes modifies the access control - // rules. Allowing this to be done by a non-project-owner opens - // a security hole enabling editing of access rules, and thus - // granting of powers beyond pushing to the configuration. - return false; - } - return canForcePerform(Permission.PUSH); - } - - /** - * Determines whether the user can delete the Git ref controlled by this object. - * - * @return {@code true} if the user specified can delete a Git ref. - */ - private boolean canDelete() { - if (!isProjectStatePermittingWrite() || (RefNames.REFS_CONFIG.equals(refName))) { - // Never allow removal of the refs/meta/config branch. - // Deleting the branch would destroy all Gerrit specific - // metadata about the project, including its access rules. - // If a project is to be removed from Gerrit, its repository - // should be removed first. - return false; - } - - switch (getUser().getAccessPath()) { - case GIT: - return canPushWithForce() || canPerform(Permission.DELETE); - - case JSON_RPC: - case REST_API: - case SSH_COMMAND: - case UNKNOWN: - case WEB_BROWSER: - default: - return (isOwner() && !isForceBlocked(Permission.PUSH)) - || canPushWithForce() - || canPerform(Permission.DELETE) - || projectControl.isAdmin(); - } - } - - /** @return true if this user can forge the author line in a commit. */ - private boolean canForgeAuthor() { - if (canForgeAuthor == null) { - canForgeAuthor = canPerform(Permission.FORGE_AUTHOR); - } - return canForgeAuthor; - } - - /** @return true if this user can forge the committer line in a commit. */ - private boolean canForgeCommitter() { - if (canForgeCommitter == null) { - canForgeCommitter = canPerform(Permission.FORGE_COMMITTER); - } - return canForgeCommitter; - } - - /** @return true if this user can forge the server on the committer line. */ - private boolean canForgeGerritServerIdentity() { - return canPerform(Permission.FORGE_SERVER); - } - - /** @return true if this user can abandon a change for this ref */ - boolean canAbandon() { - return canPerform(Permission.ABANDON); - } - - /** @return true if this user can remove a reviewer for a change. */ - boolean canRemoveReviewer() { - return canPerform(Permission.REMOVE_REVIEWER); - } - - /** @return true if this user can view private changes. */ - boolean canViewPrivateChanges() { - return canPerform(Permission.VIEW_PRIVATE_CHANGES); - } - - /** @return true if this user can delete changes. */ - boolean canDeleteChanges(boolean isChangeOwner) { - return canPerform(Permission.DELETE_CHANGES) - || (isChangeOwner && canPerform(Permission.DELETE_OWN_CHANGES, isChangeOwner)); - } - - /** @return true if this user can edit topic names. */ - boolean canEditTopicName() { - return canPerform(Permission.EDIT_TOPIC_NAME); - } - - /** @return true if this user can edit hashtag names. */ - boolean canEditHashtags() { - return canPerform(Permission.EDIT_HASHTAGS); - } - - boolean canEditAssignee() { - return canPerform(Permission.EDIT_ASSIGNEE); - } - - /** @return true if this user can force edit topic names. */ - boolean canForceEditTopicName() { - return canForcePerform(Permission.EDIT_TOPIC_NAME); - } - - /** The range of permitted values associated with a label permission. */ - PermissionRange getRange(String permission) { - return getRange(permission, false); - } - - /** The range of permitted values associated with a label permission. */ - PermissionRange getRange(String permission, boolean isChangeOwner) { - if (Permission.hasRange(permission)) { - return toRange(permission, access(permission, isChangeOwner)); - } - return null; - } - - private static class AllowedRange { - private int allowMin; - private int allowMax; - private int blockMin = Integer.MIN_VALUE; - private int blockMax = Integer.MAX_VALUE; - - void update(PermissionRule rule) { - if (rule.isBlock()) { - blockMin = Math.max(blockMin, rule.getMin()); - blockMax = Math.min(blockMax, rule.getMax()); - } else { - allowMin = Math.min(allowMin, rule.getMin()); - allowMax = Math.max(allowMax, rule.getMax()); - } - } - - int getAllowMin() { - return allowMin; - } - - int getAllowMax() { - return allowMax; - } - - int getBlockMin() { - // ALLOW wins over BLOCK on the same project - return Math.min(blockMin, allowMin - 1); - } - - int getBlockMax() { - // ALLOW wins over BLOCK on the same project - return Math.max(blockMax, allowMax + 1); - } - } - - private PermissionRange toRange(String permissionName, List<PermissionRule> ruleList) { - Map<ProjectRef, AllowedRange> ranges = new HashMap<>(); - for (PermissionRule rule : ruleList) { - ProjectRef p = relevant.getRuleProps(rule); - AllowedRange r = ranges.get(p); - if (r == null) { - r = new AllowedRange(); - ranges.put(p, r); - } - r.update(rule); - } - int allowMin = 0; - int allowMax = 0; - int blockMin = Integer.MIN_VALUE; - int blockMax = Integer.MAX_VALUE; - for (AllowedRange r : ranges.values()) { - allowMin = Math.min(allowMin, r.getAllowMin()); - allowMax = Math.max(allowMax, r.getAllowMax()); - blockMin = Math.max(blockMin, r.getBlockMin()); - blockMax = Math.min(blockMax, r.getBlockMax()); - } - - // BLOCK wins over ALLOW across projects - int min = Math.max(allowMin, blockMin + 1); - int max = Math.min(allowMax, blockMax - 1); - return new PermissionRange(permissionName, min, max); - } - - /** True if the user has this permission. Works only for non labels. */ - boolean canPerform(String permissionName) { - return canPerform(permissionName, false); - } - - boolean canPerform(String permissionName, boolean isChangeOwner) { - return doCanPerform(permissionName, isChangeOwner, false); - } - - /** True if the user is blocked from using this permission. */ - public boolean isBlocked(String permissionName) { - return !doCanPerform(permissionName, false, true); - } - - private boolean doCanPerform(String permissionName, boolean isChangeOwner, boolean blockOnly) { - List<PermissionRule> access = access(permissionName, isChangeOwner); - List<PermissionRule> overridden = relevant.getOverridden(permissionName); - Set<ProjectRef> allows = new HashSet<>(); - Set<ProjectRef> blocks = new HashSet<>(); - for (PermissionRule rule : access) { - if (rule.isBlock() && !rule.getForce()) { - blocks.add(relevant.getRuleProps(rule)); - } else { - allows.add(relevant.getRuleProps(rule)); - } - } - for (PermissionRule rule : overridden) { - blocks.remove(relevant.getRuleProps(rule)); - } - blocks.removeAll(allows); - return blocks.isEmpty() && (!allows.isEmpty() || blockOnly); - } - - /** True if the user has force this permission. Works only for non labels. */ - private boolean canForcePerform(String permissionName) { - List<PermissionRule> access = access(permissionName); - List<PermissionRule> overridden = relevant.getOverridden(permissionName); - Set<ProjectRef> allows = new HashSet<>(); - Set<ProjectRef> blocks = new HashSet<>(); - for (PermissionRule rule : access) { - if (rule.isBlock()) { - blocks.add(relevant.getRuleProps(rule)); - } else if (rule.getForce()) { - allows.add(relevant.getRuleProps(rule)); - } - } - for (PermissionRule rule : overridden) { - if (rule.getForce()) { - blocks.remove(relevant.getRuleProps(rule)); - } - } - blocks.removeAll(allows); - return blocks.isEmpty() && !allows.isEmpty(); - } - - /** True if for this permission force is blocked for the user. Works only for non labels. */ - private boolean isForceBlocked(String permissionName) { - List<PermissionRule> access = access(permissionName); - List<PermissionRule> overridden = relevant.getOverridden(permissionName); - Set<ProjectRef> allows = new HashSet<>(); - Set<ProjectRef> blocks = new HashSet<>(); - for (PermissionRule rule : access) { - if (rule.isBlock()) { - blocks.add(relevant.getRuleProps(rule)); - } else if (rule.getForce()) { - allows.add(relevant.getRuleProps(rule)); - } - } - for (PermissionRule rule : overridden) { - if (rule.getForce()) { - blocks.remove(relevant.getRuleProps(rule)); - } - } - blocks.removeAll(allows); - return !blocks.isEmpty(); - } - - /** Rules for the given permission, or the empty list. */ - private List<PermissionRule> access(String permissionName) { - return access(permissionName, false); - } - - /** Rules for the given permission, or the empty list. */ - private List<PermissionRule> access(String permissionName, boolean isChangeOwner) { - List<PermissionRule> rules = effective.get(permissionName); - if (rules != null) { - return rules; - } - - rules = relevant.getPermission(permissionName); - - List<PermissionRule> mine = new ArrayList<>(rules.size()); - for (PermissionRule rule : rules) { - if (projectControl.match(rule, isChangeOwner)) { - mine.add(rule); - } - } - - if (mine.isEmpty()) { - mine = Collections.emptyList(); - } - effective.put(permissionName, mine); - return mine; - } - - ForRef asForRef() { - return new ForRefImpl(); - } - - private class ForRefImpl extends ForRef { - @Override - public ForRef user(CurrentUser user) { - return forUser(user).asForRef().database(db); - } - - @Override - public ForChange change(ChangeData cd) { - try { - // TODO(hiesel) Force callers to call database() and use db instead of cd.db() - return getProjectControl() - .controlFor(cd.db(), cd.change()) - .asForChange(cd, Providers.of(cd.db())); - } catch (OrmException e) { - return FailedPermissionBackend.change("unavailable", e); - } - } - - @Override - public ForChange change(ChangeNotes notes) { - Project.NameKey project = getProjectControl().getProject().getNameKey(); - Change change = notes.getChange(); - checkArgument( - project.equals(change.getProject()), - "expected change in project %s, not %s", - project, - change.getProject()); - return getProjectControl().controlFor(notes).asForChange(null, db); - } - - @Override - public ForChange indexedChange(ChangeData cd, ChangeNotes notes) { - return getProjectControl().controlFor(notes).asForChange(cd, db); - } - - @Override - public void check(RefPermission perm) throws AuthException, PermissionBackendException { - if (!can(perm)) { - throw new AuthException(perm.describeForException() + " not permitted for " + getRefName()); - } - } - - @Override - public Set<RefPermission> test(Collection<RefPermission> permSet) - throws PermissionBackendException { - EnumSet<RefPermission> ok = EnumSet.noneOf(RefPermission.class); - for (RefPermission perm : permSet) { - if (can(perm)) { - ok.add(perm); - } - } - return ok; - } - - private boolean can(RefPermission perm) throws PermissionBackendException { - switch (perm) { - case READ: - return isVisible(); - case CREATE: - // TODO This isn't an accurate test. - return canPerform(perm.permissionName().get()); - case DELETE: - return canDelete(); - case UPDATE: - return canUpdate(); - case FORCE_UPDATE: - return canForceUpdate(); - - case FORGE_AUTHOR: - return canForgeAuthor(); - case FORGE_COMMITTER: - return canForgeCommitter(); - case FORGE_SERVER: - return canForgeGerritServerIdentity(); - case MERGE: - return canUploadMerges(); - - case CREATE_CHANGE: - return canUpload(); - - case UPDATE_BY_SUBMIT: - return projectControl.controlForRef("refs/for/" + getRefName()).canSubmit(true); - - case SKIP_VALIDATION: - return canForgeAuthor() - && canForgeCommitter() - && canForgeGerritServerIdentity() - && canUploadMerges() - && !projectControl.getProjectState().isUseSignedOffBy(); - } - throw new PermissionBackendException(perm + " unsupported"); - } - } -} |