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 | 619 |
1 files changed, 218 insertions, 401 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 index 8ddf5850d0..db370e0727 100644 --- 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 @@ -14,34 +14,21 @@ 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.FORGE_SERVER; -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_CREATE; -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_TAG; -import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_ANNOTATED; -import static com.google.gerrit.reviewdb.ApprovalCategory.PUSH_TAG_SIGNED; -import static com.google.gerrit.reviewdb.ApprovalCategory.READ; - -import com.google.gerrit.common.data.ParamertizedString; -import com.google.gerrit.reviewdb.AccountGroup; -import com.google.gerrit.reviewdb.ApprovalCategory; -import com.google.gerrit.reviewdb.RefRight; -import com.google.gerrit.reviewdb.SystemConfig; +import com.google.gerrit.common.data.AccessSection; +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.common.data.RefConfigSection; +import com.google.gerrit.common.errors.InvalidNameException; +import com.google.gerrit.reviewdb.client.Project; import com.google.gerrit.server.CurrentUser; import com.google.gerrit.server.IdentifiedUser; -import com.google.inject.Inject; -import com.google.inject.assistedinject.Assisted; +import com.google.gerrit.server.git.GitRepositoryManager; import dk.brics.automaton.RegExp; -import org.apache.commons.lang.StringUtils; import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevTag; @@ -49,47 +36,33 @@ import org.eclipse.jgit.revwalk.RevWalk; import java.io.IOException; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.Comparator; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import java.util.SortedMap; -import java.util.TreeMap; -import java.util.regex.Pattern; /** Manages access control for Git references (aka branches, tags). */ public class RefControl { - public interface Factory { - RefControl create(ProjectControl projectControl, String ref); - } - - private final SystemConfig systemConfig; private final ProjectControl projectControl; private final String refName; - private Boolean canForgeAuthor; - private Boolean canForgeCommitter; - - @Inject - protected RefControl(final SystemConfig systemConfig, - @Assisted final ProjectControl projectControl, - @Assisted String ref) { - this.systemConfig = systemConfig; - if (isRE(ref)) { - ref = shortestExample(ref); + /** All permissions that apply to this reference. */ + private final PermissionCollection relevant; - } else if (ref.endsWith("/*")) { - ref = ref.substring(0, ref.length() - 1); + /** Cached set of permissions matching this user. */ + private final Map<String, List<PermissionRule>> effective; - } + private Boolean owner; + private Boolean canForgeAuthor; + private Boolean canForgeCommitter; + RefControl(ProjectControl projectControl, String ref, + PermissionCollection relevant) { this.projectControl = projectControl; this.refName = ref; + this.relevant = relevant; + this.effective = new HashMap<String, List<PermissionRule>>(); } public String getRefName() { @@ -101,39 +74,35 @@ public class RefControl { } public CurrentUser getCurrentUser() { - return getProjectControl().getCurrentUser(); - } - - public RefControl forAnonymousUser() { - return getProjectControl().forAnonymousUser().controlForRef(getRefName()); + return projectControl.getCurrentUser(); } - public RefControl forUser(final CurrentUser who) { - return getProjectControl().forUser(who).controlForRef(getRefName()); + public RefControl forUser(CurrentUser who) { + ProjectControl newCtl = projectControl.forUser(who); + if (relevant.isUserSpecific()) { + return newCtl.controlForRef(getRefName()); + } else { + return new RefControl(newCtl, getRefName(), relevant); + } } /** Is this user a ref owner? */ public boolean isOwner() { - if (canPerform(OWN, (short) 1)) { - return true; - } + if (owner == null) { + if (canPerform(Permission.OWNER)) { + owner = true; - // We have to prevent infinite recursion here, the project control - // calls us to find out if there is ownership of all references in - // order to determine project level ownership. - // - if (getRefName().equals( - RefRight.ALL.substring(0, RefRight.ALL.length() - 1))) { - return getCurrentUser().isAdministrator(); - } else { - return getProjectControl().isOwner(); + } else { + owner = projectControl.isOwner(); + } } + return owner; } /** Can this user see this reference exists? */ public boolean isVisible() { - return getProjectControl().visibleForReplication() - || canPerform(READ, (short) 1); + return (projectControl.visibleForReplication() || canPerform(Permission.READ)) + && canRead(); } /** @@ -144,27 +113,88 @@ public class RefControl { * ref */ public boolean canUpload() { - return canPerform(READ, (short) 2); + return projectControl.controlForRef("refs/for/" + getRefName()) + .canPerform(Permission.PUSH) + && canWrite(); } /** @return true if this user can submit merge patch sets to this ref */ public boolean canUploadMerges() { - return canPerform(READ, (short) 3); + return projectControl.controlForRef("refs/for/" + getRefName()) + .canPerform(Permission.PUSH_MERGE) + && canWrite(); + } + + /** @return true if this user can rebase changes on this ref */ + public boolean canRebase() { + return canPerform(Permission.REBASE) + && canWrite(); } /** @return true if this user can submit patch sets to this ref */ public boolean canSubmit() { - return canPerform(ApprovalCategory.SUBMIT, (short) 1); + if (GitRepositoryManager.REF_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) + && canWrite(); } /** @return true if the user can update the reference as a fast-forward. */ public boolean canUpdate() { - return canPerform(PUSH_HEAD, PUSH_HEAD_UPDATE); + if (GitRepositoryManager.REF_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 canPerform(Permission.PUSH) + && canWrite(); } /** @return true if the user can rewind (force push) the reference. */ public boolean canForceUpdate() { - return canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE) || canDelete(); + return (canPushWithForce() || canDelete()) && canWrite(); + } + + public boolean canWrite() { + return getProjectControl().getProject().getState().equals( + Project.State.ACTIVE); + } + + public boolean canRead() { + return getProjectControl().getProject().getState().equals( + Project.State.READ_ONLY) || canWrite(); + } + + private boolean canPushWithForce() { + if (!canWrite() || (GitRepositoryManager.REF_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; + } + boolean result = false; + for (PermissionRule rule : access(Permission.PUSH)) { + if (rule.isBlock()) { + return false; + } + if (rule.getForce()) { + result = true; + } + } + return result; } /** @@ -175,6 +205,9 @@ public class RefControl { * @return {@code true} if the user specified can create a new Git ref */ public boolean canCreate(RevWalk rw, RevObject object) { + if (!canWrite()) { + return false; + } boolean owner; switch (getCurrentUser().getAccessPath()) { case WEB_UI: @@ -186,7 +219,7 @@ public class RefControl { } if (object instanceof RevCommit) { - return owner || canPerform(PUSH_HEAD, PUSH_HEAD_CREATE); + return owner || canPerform(Permission.CREATE); } else if (object instanceof RevTag) { final RevTag tag = (RevTag) object; @@ -208,7 +241,7 @@ public class RefControl { } else { valid = false; } - if (!valid && !owner && !canPerform(FORGE_IDENTITY, FORGE_COMMITTER)) { + if (!valid && !owner && !canForgeCommitter()) { return false; } } @@ -217,9 +250,9 @@ public class RefControl { // than if it doesn't have a PGP signature. // if (tag.getFullMessage().contains("-----BEGIN PGP SIGNATURE-----\n")) { - return owner || canPerform(PUSH_TAG, PUSH_TAG_SIGNED); + return owner || canPerform(Permission.PUSH_TAG); } else { - return owner || canPerform(PUSH_TAG, PUSH_TAG_ANNOTATED); + return owner || canPerform(Permission.PUSH_TAG); } } else { @@ -234,12 +267,21 @@ public class RefControl { * @return {@code true} if the user specified can delete a Git ref. */ public boolean canDelete() { + if (!canWrite() || (GitRepositoryManager.REF_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 (getCurrentUser().getAccessPath()) { case WEB_UI: - return isOwner() || canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE); + return isOwner() || canPushWithForce(); case GIT: - return canPerform(PUSH_HEAD, PUSH_HEAD_REPLACE); + return canPushWithForce(); default: return false; @@ -249,7 +291,7 @@ 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); + canForgeAuthor = canPerform(Permission.FORGE_AUTHOR); } return canForgeAuthor; } @@ -257,361 +299,118 @@ public class RefControl { /** @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); + canForgeCommitter = canPerform(Permission.FORGE_COMMITTER); } return canForgeCommitter; } /** @return true if this user can forge the server on the committer line. */ public boolean canForgeGerritServerIdentity() { - return canPerform(FORGE_IDENTITY, FORGE_SERVER); - } - - public short normalize(ApprovalCategory.Id category, short score) { - short minAllowed = 0, maxAllowed = 0; - for (RefRight r : getApplicableRights(category)) { - if (getCurrentUser().getEffectiveGroups().contains(r.getAccountGroupId())) { - minAllowed = (short) Math.min(minAllowed, r.getMinValue()); - maxAllowed = (short) Math.max(maxAllowed, r.getMaxValue()); + return canPerform(Permission.FORGE_SERVER); + } + + /** All value ranges of any allowed label permission. */ + public List<PermissionRange> getLabelRanges() { + List<PermissionRange> r = new ArrayList<PermissionRange>(); + for (Map.Entry<String, List<PermissionRule>> e : relevant.getDeclaredPermissions()) { + if (Permission.isLabel(e.getKey())) { + int min = 0; + int max = 0; + for (PermissionRule rule : e.getValue()) { + if (projectControl.match(rule)) { + min = Math.min(min, rule.getMin()); + max = Math.max(max, rule.getMax()); + } + } + if (min != 0 || max != 0) { + r.add(new PermissionRange(e.getKey(), min, max)); + } } } - - if (score < minAllowed) { - score = minAllowed; - } - if (score > maxAllowed) { - score = maxAllowed; - } - return score; + return r; } - /** - * Convenience holder class used to map a ref pattern to the list of - * {@code RefRight}s that use it in the database. - */ - public final static class RefRightsForPattern { - private final List<RefRight> rights; - private boolean containsExclusive; - - public RefRightsForPattern() { - rights = new ArrayList<RefRight>(); - containsExclusive = false; + /** The range of permitted values associated with a label permission. */ + public PermissionRange getRange(String permission) { + if (Permission.isLabel(permission)) { + return toRange(permission, access(permission)); } - - public void addRight(RefRight right) { - rights.add(right); - if (right.isExclusive()) { - containsExclusive = true; + return null; + } + + private static PermissionRange toRange(String permissionName, + List<PermissionRule> ruleList) { + int min = 0; + int max = 0; + int blockMin = Integer.MIN_VALUE; + int blockMax = Integer.MAX_VALUE; + for (PermissionRule rule : ruleList) { + if (rule.isBlock()) { + blockMin = Math.max(blockMin, rule.getMin()); + blockMax = Math.min(blockMax, rule.getMax()); + } else { + min = Math.min(min, rule.getMin()); + max = Math.max(max, rule.getMax()); } } - - public List<RefRight> getRights() { - return Collections.unmodifiableList(rights); + if (blockMin > Integer.MIN_VALUE) { + min = Math.max(min, blockMin + 1); } - - public boolean containsExclusive() { - return containsExclusive; + if (blockMax < Integer.MAX_VALUE) { + max = Math.min(max, blockMax - 1); } - - /** - * Returns The max allowed value for this ref pattern for all specified - * groups. - * - * @param groups The groups of the user - * @return The allowed value for this ref for all the specified groups - */ - private boolean allowedValueForRef(Set<AccountGroup.Id> groups, short level) { - for (RefRight right : rights) { - if (groups.contains(right.getAccountGroupId()) - && right.getMaxValue() >= level) { - return true; - } - } - return false; - } - } - - boolean canPerform(ApprovalCategory.Id actionId, short level) { - final Set<AccountGroup.Id> groups = getCurrentUser().getEffectiveGroups(); - - List<RefRight> allRights = new ArrayList<RefRight>(); - allRights.addAll(getAllRights(actionId)); - - SortedMap<String, RefRightsForPattern> perPatternRights = - sortedRightsByPattern(allRights); - - for (RefRightsForPattern right : perPatternRights.values()) { - if (right.allowedValueForRef(groups, level)) { - return true; - } - if (right.containsExclusive() && !actionId.equals(OWN)) { - break; - } - } - return false; + return new PermissionRange(permissionName, min, max); } - /** - * Order the Ref Pattern by the most specific. This sort is done by: - * <ul> - * <li>1 - The minor value of Levenshtein string distance between the branch - * name and the regex string shortest example. A shorter distance is a more - * specific match. - * <li>2 - Finites first, infinities after. - * <li>3 - Number of transitions. - * <li>4 - Length of the expression text. - * </ul> - * - * Levenshtein distance is a measure of the similarity between two strings. - * The distance is the number of deletions, insertions, or substitutions - * required to transform one string into another. - * - * For example, if given refs/heads/m* and refs/heads/*, the distances are 5 - * and 6. It means that refs/heads/m* is more specific because it's closer to - * refs/heads/master than refs/heads/*. - * - * Another example could be refs/heads/* and refs/heads/[a-zA-Z]*, the - * distances are both 6. Both are infinite, but refs/heads/[a-zA-Z]* has more - * transitions, which after all turns it more specific. - */ - private final Comparator<String> BY_MOST_SPECIFIC_SORT = - new Comparator<String>() { - public int compare(final String pattern1, final String pattern2) { - int cmp = distance(pattern1) - distance(pattern2); - if (cmp == 0) { - boolean p1_finite = finite(pattern1); - boolean p2_finite = finite(pattern2); - - if (p1_finite && !p2_finite) { - cmp = -1; - } else if (!p1_finite && p2_finite) { - cmp = 1; - } else /* if (f1 == f2) */{ - cmp = 0; - } - } - if (cmp == 0) { - cmp = transitions(pattern1) - transitions(pattern2); - } - if (cmp == 0) { - cmp = pattern2.length() - pattern1.length(); - } - return cmp; - } - - private int distance(String pattern) { - String example; - if (isRE(pattern)) { - example = shortestExample(pattern); - - } else if (pattern.endsWith("/*")) { - example = pattern.substring(0, pattern.length() - 1) + '1'; - - } else if (pattern.equals(getRefName())) { - return 0; - - } else { - return Math.max(pattern.length(), getRefName().length()); - } - return StringUtils.getLevenshteinDistance(example, getRefName()); - } - - private boolean finite(String pattern) { - if (isRE(pattern)) { - return toRegExp(pattern).toAutomaton().isFinite(); - - } else if (pattern.endsWith("/*")) { - return false; - - } else { - return true; - } - } - - private int transitions(String pattern) { - if (isRE(pattern)) { - return toRegExp(pattern).toAutomaton().getNumberOfTransitions(); - - } else if (pattern.endsWith("/*")) { - return pattern.length(); - - } else { - return pattern.length(); - } - } - }; - - /** - * Sorts all given rights into a map, ordered by descending length of - * ref pattern. - * - * For example, if given the following rights in argument: - * - * ["refs/heads/master", group1, -1, +1], - * ["refs/heads/master", group2, -2, +2], - * ["refs/heads/*", group3, -1, +1] - * ["refs/heads/stable", group2, -1, +1] - * - * Then the following map is returned: - * "refs/heads/master" => { - * ["refs/heads/master", group1, -1, +1], - * ["refs/heads/master", group2, -2, +2] - * } - * "refs/heads/stable" => {["refs/heads/stable", group2, -1, +1]} - * "refs/heads/*" => {["refs/heads/*", group3, -1, +1]} - * - * @param actionRights - * @return A sorted map keyed off the ref pattern of all rights. - */ - private SortedMap<String, RefRightsForPattern> sortedRightsByPattern( - List<RefRight> actionRights) { - SortedMap<String, RefRightsForPattern> rights = - new TreeMap<String, RefRightsForPattern>(BY_MOST_SPECIFIC_SORT); - for (RefRight actionRight : actionRights) { - RefRightsForPattern patternRights = - rights.get(actionRight.getRefPattern()); - if (patternRights == null) { - patternRights = new RefRightsForPattern(); - rights.put(actionRight.getRefPattern(), patternRights); + /** True if the user has this permission. Works only for non labels. */ + boolean canPerform(String permissionName) { + List<PermissionRule> access = access(permissionName); + for (PermissionRule rule : access) { + if (rule.isBlock() && !rule.getForce()) { + return false; } - patternRights.addRight(actionRight); } - return rights; - } - - private List<RefRight> getAllRights(ApprovalCategory.Id actionId) { - final List<RefRight> allRefRights = filter(getProjectState().getAllRights(actionId, true)); - return resolveOwnerGroups(allRefRights); + return !access.isEmpty(); } - /** - * Returns all applicable rights for a given approval category. - * - * Applicable rights are defined as the list of {@code RefRight}s which match - * the ref for which this object was created, stopping the ref wildcard - * matching when an exclusive ref right was encountered, for the given - * approval category. - * @param id The {@link ApprovalCategory.Id}. - * @return All applicable rights. - */ - public List<RefRight> getApplicableRights(final ApprovalCategory.Id id) { - List<RefRight> l = new ArrayList<RefRight>(); - l.addAll(getAllRights(id)); - SortedMap<String, RefRightsForPattern> perPatternRights = - sortedRightsByPattern(l); - List<RefRight> applicable = new ArrayList<RefRight>(); - for (RefRightsForPattern patternRights : perPatternRights.values()) { - applicable.addAll(patternRights.getRights()); - if (patternRights.containsExclusive()) { - break; - } + /** Rules for the given permission, or the empty list. */ + private List<PermissionRule> access(String permissionName) { + List<PermissionRule> rules = effective.get(permissionName); + if (rules != null) { + return rules; } - return Collections.unmodifiableList(applicable); - } - /** - * Resolves all refRights which assign privileges to the 'Project Owners' - * group. All other refRights stay unchanged. - * - * @param refRights refRights to be resolved - * @return the resolved refRights - */ - private List<RefRight> resolveOwnerGroups(final List<RefRight> refRights) { - final List<RefRight> resolvedRefRights = - new ArrayList<RefRight>(refRights.size()); - for (final RefRight refRight : refRights) { - resolvedRefRights.addAll(resolveOwnerGroups(refRight)); - } - return resolvedRefRights; - } + rules = relevant.getPermission(permissionName); - /** - * Checks if the given refRight assigns privileges to the 'Project Owners' - * group. - * If yes, resolves the 'Project Owners' group to the concrete groups that - * own the project and creates new refRights for the concrete owner groups - * which are returned. - * If no, the given refRight is returned unchanged. - * - * @param refRight refRight - * @return the resolved refRights - */ - private Set<RefRight> resolveOwnerGroups(final RefRight refRight) { - final Set<RefRight> resolvedRefRights = new HashSet<RefRight>(); - if (refRight.getAccountGroupId().equals(systemConfig.ownerGroupId)) { - for (final AccountGroup.Id ownerGroup : getProjectState().getAllOwners()) { - if (!ownerGroup.equals(systemConfig.ownerGroupId)) { - resolvedRefRights.add(new RefRight(refRight, ownerGroup)); - } - } - } else { - resolvedRefRights.add(refRight); + if (rules.isEmpty()) { + effective.put(permissionName, rules); + return rules; } - return resolvedRefRights; - } - private List<RefRight> filter(Collection<RefRight> all) { - List<RefRight> mine = new ArrayList<RefRight>(all.size()); - for (RefRight right : all) { - if (matches(right.getRefPattern())) { - mine.add(right); + if (rules.size() == 1) { + if (!projectControl.match(rules.get(0))) { + rules = Collections.emptyList(); } + effective.put(permissionName, rules); + return rules; } - return mine; - } - - private ProjectState getProjectState() { - return projectControl.getProjectState(); - } - - private boolean matches(String refPattern) { - if (isTemplate(refPattern)) { - ParamertizedString template = new ParamertizedString(refPattern); - HashMap<String, String> p = new HashMap<String, String>(); - - if (getCurrentUser() instanceof IdentifiedUser) { - p.put("username", ((IdentifiedUser) getCurrentUser()).getUserName()); - } else { - // Right now we only template the username. If not available - // this rule cannot be matched at all. - // - return false; - } - if (isRE(refPattern)) { - for (Map.Entry<String, String> ent : p.entrySet()) { - ent.setValue(escape(ent.getValue())); - } + List<PermissionRule> mine = new ArrayList<PermissionRule>(rules.size()); + for (PermissionRule rule : rules) { + if (projectControl.match(rule)) { + mine.add(rule); } - - refPattern = template.replace(p); } - if (isRE(refPattern)) { - return Pattern.matches(refPattern, getRefName()); - - } else if (refPattern.endsWith("/*")) { - String prefix = refPattern.substring(0, refPattern.length() - 1); - return getRefName().startsWith(prefix); - - } else { - return getRefName().equals(refPattern); + if (mine.isEmpty()) { + mine = Collections.emptyList(); } + effective.put(permissionName, mine); + return mine; } - private static boolean isTemplate(String refPattern) { - return 0 <= refPattern.indexOf("${"); - } - - private static String escape(String value) { - // Right now the only special character allowed in a - // variable value is a . in the username. - // - return value.replace(".", "\\."); - } - - private static boolean isRE(String refPattern) { - return refPattern.startsWith(RefRight.REGEX_PREFIX); + public static boolean isRE(String refPattern) { + return refPattern.startsWith(AccessSection.REGEX_PREFIX); } public static String shortestExample(String pattern) { @@ -624,10 +423,28 @@ public class RefControl { } } - private static RegExp toRegExp(String refPattern) { + public static RegExp toRegExp(String refPattern) { if (isRE(refPattern)) { refPattern = refPattern.substring(1); } return new RegExp(refPattern, RegExp.NONE); } + + public static void validateRefPattern(String refPattern) + throws InvalidNameException { + if (refPattern.startsWith(RefConfigSection.REGEX_PREFIX)) { + if (!Repository.isValidRefName(RefControl.shortestExample(refPattern))) { + throw new InvalidNameException(refPattern); + } + } else if (refPattern.equals(RefConfigSection.ALL)) { + // This is a special case we have to allow, it fails below. + } else if (refPattern.endsWith("/*")) { + String prefix = refPattern.substring(0, refPattern.length() - 2); + if (!Repository.isValidRefName(prefix)) { + throw new InvalidNameException(refPattern); + } + } else if (!Repository.isValidRefName(refPattern)) { + throw new InvalidNameException(refPattern); + } + } } |