Support regular expressions for ref access rules
This change considers the rights of the most specific ref pattern. If the ref pattern starts with ^ it is considered to be a regular expression, otherwise the older glob style or exact match rules are used. Change-Id: Ie060d3758e5184a7cedd38883253f60817a04e1b Portions-by: carloseduardo.baldacin <carloseduardo.baldacin@sonyericsson.com> Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -34,6 +34,9 @@ import com.google.gerrit.reviewdb.RefRight;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
|
||||
import dk.brics.automaton.RegExp;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevObject;
|
||||
@@ -49,6 +52,7 @@ import java.util.List;
|
||||
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). */
|
||||
@@ -59,9 +63,17 @@ public class RefControl {
|
||||
private Boolean canForgeAuthor;
|
||||
private Boolean canForgeCommitter;
|
||||
|
||||
RefControl(final ProjectControl projectControl, final String refName) {
|
||||
RefControl(final ProjectControl projectControl, String ref) {
|
||||
if (ref.startsWith(RefRight.REGEX_PREFIX)) {
|
||||
ref = shortestExample(ref);
|
||||
|
||||
} else if (ref.endsWith("/*")) {
|
||||
ref = ref.substring(0, ref.length() - 1);
|
||||
|
||||
}
|
||||
|
||||
this.projectControl = projectControl;
|
||||
this.refName = refName;
|
||||
this.refName = ref;
|
||||
}
|
||||
|
||||
public String getRefName() {
|
||||
@@ -94,7 +106,9 @@ public class RefControl {
|
||||
// calls us to find out if there is ownership of all references in
|
||||
// order to determine project level ownership.
|
||||
//
|
||||
if (!RefRight.ALL.equals(getRefName()) && getProjectControl().isOwner()) {
|
||||
if (getRefName().equals(
|
||||
RefRight.ALL.substring(0, RefRight.ALL.length() - 1))
|
||||
&& getProjectControl().isOwner()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -302,19 +316,95 @@ public class RefControl {
|
||||
return val >= level;
|
||||
}
|
||||
|
||||
public static final Comparator<String> DESCENDING_SORT =
|
||||
/**
|
||||
* 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(pattern2) - distance(pattern1);
|
||||
if (cmp == 0) {
|
||||
boolean p1_finite = finite(pattern1);
|
||||
boolean p2_finite = finite(pattern2);
|
||||
|
||||
@Override
|
||||
public int compare(String a, String b) {
|
||||
int aLength = a.length();
|
||||
int bLength = b.length();
|
||||
if (bLength == aLength) {
|
||||
return a.compareTo(b);
|
||||
}
|
||||
return bLength - aLength;
|
||||
}
|
||||
};
|
||||
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 (pattern.startsWith(RefRight.REGEX_PREFIX)) {
|
||||
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 (pattern.startsWith(RefRight.REGEX_PREFIX)) {
|
||||
return toRegExp(pattern).toAutomaton().isFinite();
|
||||
|
||||
} else if (pattern.endsWith("/*")) {
|
||||
return false;
|
||||
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private int transitions(String pattern) {
|
||||
if (pattern.startsWith(RefRight.REGEX_PREFIX)) {
|
||||
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
|
||||
@@ -338,10 +428,10 @@ public class RefControl {
|
||||
* @param actionRights
|
||||
* @return A sorted map keyed off the ref pattern of all rights.
|
||||
*/
|
||||
private static SortedMap<String, RefRightsForPattern> sortedRightsByPattern(
|
||||
private SortedMap<String, RefRightsForPattern> sortedRightsByPattern(
|
||||
List<RefRight> actionRights) {
|
||||
SortedMap<String, RefRightsForPattern> rights =
|
||||
new TreeMap<String, RefRightsForPattern>(DESCENDING_SORT);
|
||||
new TreeMap<String, RefRightsForPattern>(BY_MOST_SPECIFIC_SORT);
|
||||
for (RefRight actionRight : actionRights) {
|
||||
RefRightsForPattern patternRights =
|
||||
rights.get(actionRight.getRefPattern());
|
||||
@@ -403,6 +493,10 @@ public class RefControl {
|
||||
}
|
||||
|
||||
public static boolean matches(String refName, String refPattern) {
|
||||
if (refPattern.startsWith(RefRight.REGEX_PREFIX)) {
|
||||
return Pattern.matches(refPattern, refName);
|
||||
}
|
||||
|
||||
if (refPattern.endsWith("/*")) {
|
||||
String prefix = refPattern.substring(0, refPattern.length() - 1);
|
||||
return refName.startsWith(prefix);
|
||||
@@ -411,4 +505,21 @@ public class RefControl {
|
||||
return refName.equals(refPattern);
|
||||
}
|
||||
}
|
||||
|
||||
public static String shortestExample(String pattern) {
|
||||
if (pattern.startsWith(RefRight.REGEX_PREFIX)) {
|
||||
return toRegExp(pattern).toAutomaton().getShortestExample(true);
|
||||
} else if (pattern.endsWith("/*")) {
|
||||
return pattern.substring(0, pattern.length() - 1) + '1';
|
||||
} else {
|
||||
return pattern;
|
||||
}
|
||||
}
|
||||
|
||||
private static RegExp toRegExp(String refPattern) {
|
||||
if (refPattern.startsWith(RefRight.REGEX_PREFIX)) {
|
||||
refPattern = refPattern.substring(1);
|
||||
}
|
||||
return new RegExp(refPattern);
|
||||
}
|
||||
}
|
||||
|
@@ -19,19 +19,33 @@ import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.RefRight;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.reviewdb.RefRight.RefPattern;
|
||||
import com.google.gerrit.server.project.RefControl;
|
||||
import com.google.gerrit.server.project.RefControl.RefRightsForPattern;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.TreeMap;
|
||||
|
||||
public class Schema_34 extends SchemaVersion {
|
||||
private static final Comparator<String> DESCENDING_SORT =
|
||||
new Comparator<String>() {
|
||||
|
||||
@Override
|
||||
public int compare(String a, String b) {
|
||||
int aLength = a.length();
|
||||
int bLength = b.length();
|
||||
if (bLength == aLength) {
|
||||
return a.compareTo(b);
|
||||
}
|
||||
return bLength - aLength;
|
||||
}
|
||||
};
|
||||
|
||||
@Inject
|
||||
Schema_34(Provider<Schema_33> prior) {
|
||||
super(prior);
|
||||
@@ -54,7 +68,7 @@ public class Schema_34 extends SchemaVersion {
|
||||
ApprovalCategory.Id cat = right.getApprovalCategoryId();
|
||||
if (r.get(cat) == null) {
|
||||
Map<String, RefRightsForPattern> m =
|
||||
new TreeMap<String, RefRightsForPattern>(RefControl.DESCENDING_SORT);
|
||||
new TreeMap<String, RefRightsForPattern>(DESCENDING_SORT);
|
||||
r.put(cat, m);
|
||||
}
|
||||
if (r.get(cat).get(right.getRefPattern()) == null) {
|
||||
|
Reference in New Issue
Block a user