Move all static ref pattern related methods into a RefPattern class
The goal of this refactoring is to have a single place where a ref pattern is validated as regular expression. So far this was done in 2 places (in RefControl and in ProjectConfig). For validating a ref pattern as regular expression we must remove placeholder variables such as '${username}' because it would be an invalid regular expression otherwise. We don't want to do this in several places especially because we want to support further placeholder variables in future. Change-Id: I11c76e37f22233d89b631375ac6f2ef60f97801f Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:

committed by
David Pursehouse

parent
4c7b72bb32
commit
88fcd6bcfa
@@ -37,7 +37,7 @@ import com.google.gerrit.server.git.MetaDataUpdate;
|
|||||||
import com.google.gerrit.server.git.ProjectConfig;
|
import com.google.gerrit.server.git.ProjectConfig;
|
||||||
import com.google.gerrit.server.project.NoSuchProjectException;
|
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||||
import com.google.gerrit.server.project.ProjectControl;
|
import com.google.gerrit.server.project.ProjectControl;
|
||||||
import com.google.gerrit.server.project.RefControl;
|
import com.google.gerrit.server.project.RefPattern;
|
||||||
import com.google.gerrit.server.project.SetParent;
|
import com.google.gerrit.server.project.SetParent;
|
||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
@@ -111,7 +111,7 @@ public abstract class ProjectAccessHandler<T> extends Handler<T> {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefControl.validateRefPattern(name);
|
RefPattern.validate(name);
|
||||||
|
|
||||||
replace(config, toDelete, section);
|
replace(config, toDelete, section);
|
||||||
}
|
}
|
||||||
|
@@ -40,6 +40,7 @@ import com.google.gerrit.common.data.PermissionRule;
|
|||||||
import com.google.gerrit.common.data.PermissionRule.Action;
|
import com.google.gerrit.common.data.PermissionRule.Action;
|
||||||
import com.google.gerrit.common.data.RefConfigSection;
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
import com.google.gerrit.common.data.SubscribeSection;
|
import com.google.gerrit.common.data.SubscribeSection;
|
||||||
|
import com.google.gerrit.common.errors.InvalidNameException;
|
||||||
import com.google.gerrit.extensions.client.InheritableBoolean;
|
import com.google.gerrit.extensions.client.InheritableBoolean;
|
||||||
import com.google.gerrit.extensions.client.ProjectState;
|
import com.google.gerrit.extensions.client.ProjectState;
|
||||||
import com.google.gerrit.extensions.client.SubmitType;
|
import com.google.gerrit.extensions.client.SubmitType;
|
||||||
@@ -53,6 +54,7 @@ import com.google.gerrit.server.config.ConfigUtil;
|
|||||||
import com.google.gerrit.server.config.PluginConfig;
|
import com.google.gerrit.server.config.PluginConfig;
|
||||||
import com.google.gerrit.server.mail.Address;
|
import com.google.gerrit.server.mail.Address;
|
||||||
import com.google.gerrit.server.project.CommentLinkInfo;
|
import com.google.gerrit.server.project.CommentLinkInfo;
|
||||||
|
import com.google.gerrit.server.project.RefPattern;
|
||||||
|
|
||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
@@ -645,8 +647,8 @@ public class ProjectConfig extends VersionedMetaData implements ValidationError.
|
|||||||
|
|
||||||
private boolean isValidRegex(String refPattern) {
|
private boolean isValidRegex(String refPattern) {
|
||||||
try {
|
try {
|
||||||
Pattern.compile(refPattern.replace("${username}/", ""));
|
RefPattern.validateRegExp(refPattern);
|
||||||
} catch (PatternSyntaxException e) {
|
} catch (InvalidNameException e) {
|
||||||
error(new ValidationError(PROJECT_CONFIG, "Invalid ref name: "
|
error(new ValidationError(PROJECT_CONFIG, "Invalid ref name: "
|
||||||
+ e.getMessage()));
|
+ e.getMessage()));
|
||||||
return false;
|
return false;
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static com.google.gerrit.server.project.RefControl.isRE;
|
import static com.google.gerrit.server.project.RefPattern.isRE;
|
||||||
|
|
||||||
import com.google.auto.value.AutoValue;
|
import com.google.auto.value.AutoValue;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
@@ -74,7 +74,7 @@ public class PermissionCollection {
|
|||||||
PermissionCollection filter(Iterable<SectionMatcher> matcherList,
|
PermissionCollection filter(Iterable<SectionMatcher> matcherList,
|
||||||
String ref, CurrentUser user) {
|
String ref, CurrentUser user) {
|
||||||
if (isRE(ref)) {
|
if (isRE(ref)) {
|
||||||
ref = RefControl.shortestExample(ref);
|
ref = RefPattern.shortestExample(ref);
|
||||||
} else if (ref.endsWith("/*")) {
|
} else if (ref.endsWith("/*")) {
|
||||||
ref = ref.substring(0, ref.length() - 1);
|
ref = ref.substring(0, ref.length() - 1);
|
||||||
}
|
}
|
||||||
|
@@ -14,20 +14,15 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import com.google.gerrit.common.data.AccessSection;
|
|
||||||
import com.google.gerrit.common.data.Permission;
|
import com.google.gerrit.common.data.Permission;
|
||||||
import com.google.gerrit.common.data.PermissionRange;
|
import com.google.gerrit.common.data.PermissionRange;
|
||||||
import com.google.gerrit.common.data.PermissionRule;
|
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.extensions.client.ProjectState;
|
import com.google.gerrit.extensions.client.ProjectState;
|
||||||
import com.google.gerrit.reviewdb.client.RefNames;
|
import com.google.gerrit.reviewdb.client.RefNames;
|
||||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
import com.google.gerrit.server.CurrentUser;
|
import com.google.gerrit.server.CurrentUser;
|
||||||
import com.google.gerrit.server.group.SystemGroupBackend;
|
import com.google.gerrit.server.group.SystemGroupBackend;
|
||||||
|
|
||||||
import dk.brics.automaton.RegExp;
|
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
@@ -47,8 +42,6 @@ import java.util.HashSet;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
|
||||||
import java.util.regex.PatternSyntaxException;
|
|
||||||
|
|
||||||
|
|
||||||
/** Manages access control for Git references (aka branches, tags). */
|
/** Manages access control for Git references (aka branches, tags). */
|
||||||
@@ -646,53 +639,4 @@ public class RefControl {
|
|||||||
effective.put(permissionName, mine);
|
effective.put(permissionName, mine);
|
||||||
return mine;
|
return mine;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isRE(String refPattern) {
|
|
||||||
return refPattern.startsWith(AccessSection.REGEX_PREFIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String shortestExample(String pattern) {
|
|
||||||
if (isRE(pattern)) {
|
|
||||||
// Since Brics will substitute dot [.] with \0 when generating
|
|
||||||
// shortest example, any usage of dot will fail in
|
|
||||||
// Repository.isValidRefName() if not combined with star [*].
|
|
||||||
// To get around this, we substitute the \0 with an arbitrary
|
|
||||||
// accepted character.
|
|
||||||
return toRegExp(pattern).toAutomaton().getShortestExample(true).replace('\0', '-');
|
|
||||||
} else if (pattern.endsWith("/*")) {
|
|
||||||
return pattern.substring(0, pattern.length() - 1) + '1';
|
|
||||||
} else {
|
|
||||||
return pattern;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
Pattern.compile(refPattern.replace("${username}/", ""));
|
|
||||||
} catch (PatternSyntaxException e) {
|
|
||||||
throw new InvalidNameException(refPattern + " " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@@ -0,0 +1,85 @@
|
|||||||
|
// Copyright (C) 2016 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 com.google.gerrit.common.data.AccessSection;
|
||||||
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
|
import com.google.gerrit.common.errors.InvalidNameException;
|
||||||
|
|
||||||
|
import dk.brics.automaton.RegExp;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
import java.util.regex.PatternSyntaxException;
|
||||||
|
|
||||||
|
public class RefPattern {
|
||||||
|
public static final String USERNAME = "username";
|
||||||
|
|
||||||
|
public static String shortestExample(String refPattern) {
|
||||||
|
if (isRE(refPattern)) {
|
||||||
|
// Since Brics will substitute dot [.] with \0 when generating
|
||||||
|
// shortest example, any usage of dot will fail in
|
||||||
|
// Repository.isValidRefName() if not combined with star [*].
|
||||||
|
// To get around this, we substitute the \0 with an arbitrary
|
||||||
|
// accepted character.
|
||||||
|
return toRegExp(refPattern).toAutomaton().getShortestExample(true)
|
||||||
|
.replace('\0', '-');
|
||||||
|
} else if (refPattern.endsWith("/*")) {
|
||||||
|
return refPattern.substring(0, refPattern.length() - 1) + '1';
|
||||||
|
} else {
|
||||||
|
return refPattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isRE(String refPattern) {
|
||||||
|
return refPattern.startsWith(AccessSection.REGEX_PREFIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegExp toRegExp(String refPattern) {
|
||||||
|
if (isRE(refPattern)) {
|
||||||
|
refPattern = refPattern.substring(1);
|
||||||
|
}
|
||||||
|
return new RegExp(refPattern, RegExp.NONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validate(String refPattern)
|
||||||
|
throws InvalidNameException {
|
||||||
|
if (refPattern.startsWith(RefConfigSection.REGEX_PREFIX)) {
|
||||||
|
if (!Repository.isValidRefName(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);
|
||||||
|
}
|
||||||
|
validateRegExp(refPattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void validateRegExp(String refPattern)
|
||||||
|
throws InvalidNameException {
|
||||||
|
try {
|
||||||
|
Pattern.compile(refPattern.replace("${" + USERNAME + "}/", ""));
|
||||||
|
} catch (PatternSyntaxException e) {
|
||||||
|
throw new InvalidNameException(refPattern + " " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -14,7 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.project;
|
package com.google.gerrit.server.project;
|
||||||
|
|
||||||
import static com.google.gerrit.server.project.RefControl.isRE;
|
import static com.google.gerrit.server.project.RefPattern.isRE;
|
||||||
|
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
@@ -93,12 +93,13 @@ public abstract class RefPatternMatcher {
|
|||||||
// in a reference and the string :USERNAME: is not likely to
|
// in a reference and the string :USERNAME: is not likely to
|
||||||
// be a valid part of the regex. This later allows the pattern
|
// be a valid part of the regex. This later allows the pattern
|
||||||
// prefix to be clipped, saving time on evaluation.
|
// prefix to be clipped, saving time on evaluation.
|
||||||
|
String replacement = ":" + RefPattern.USERNAME.toUpperCase() + ":";
|
||||||
Automaton am =
|
Automaton am =
|
||||||
RefControl.toRegExp(
|
RefPattern.toRegExp(
|
||||||
template.replace(Collections.singletonMap("username",
|
template.replace(Collections.singletonMap(RefPattern.USERNAME,
|
||||||
":USERNAME:"))).toAutomaton();
|
replacement))).toAutomaton();
|
||||||
String rePrefix = am.getCommonPrefix();
|
String rePrefix = am.getCommonPrefix();
|
||||||
prefix = rePrefix.substring(0, rePrefix.indexOf(":USERNAME:"));
|
prefix = rePrefix.substring(0, rePrefix.indexOf(replacement));
|
||||||
} else {
|
} else {
|
||||||
prefix = pattern.substring(0, pattern.indexOf("${"));
|
prefix = pattern.substring(0, pattern.indexOf("${"));
|
||||||
}
|
}
|
||||||
@@ -154,8 +155,8 @@ public abstract class RefPatternMatcher {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String expand(ParameterizedString parameterizedRef, String userName) {
|
private String expand(ParameterizedString parameterizedRef, String userName) {
|
||||||
return parameterizedRef.replace(Collections.singletonMap("username",
|
return parameterizedRef
|
||||||
userName));
|
.replace(Collections.singletonMap(RefPattern.USERNAME, userName));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -146,7 +146,7 @@ public class SetAccess implements
|
|||||||
throw new AuthException("You are not allowed to edit permissions"
|
throw new AuthException("You are not allowed to edit permissions"
|
||||||
+ "for ref: " + name);
|
+ "for ref: " + name);
|
||||||
}
|
}
|
||||||
RefControl.validateRefPattern(name);
|
RefPattern.validate(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check all permissions for soundness
|
// Check all permissions for soundness
|
||||||
|
@@ -15,7 +15,7 @@
|
|||||||
package com.google.gerrit.server.util;
|
package com.google.gerrit.server.util;
|
||||||
|
|
||||||
import com.google.gerrit.common.data.RefConfigSection;
|
import com.google.gerrit.common.data.RefConfigSection;
|
||||||
import com.google.gerrit.server.project.RefControl;
|
import com.google.gerrit.server.project.RefPattern;
|
||||||
|
|
||||||
import org.apache.commons.lang.StringUtils;
|
import org.apache.commons.lang.StringUtils;
|
||||||
|
|
||||||
@@ -82,8 +82,8 @@ public final class MostSpecificComparator implements
|
|||||||
|
|
||||||
private int distance(String pattern) {
|
private int distance(String pattern) {
|
||||||
String example;
|
String example;
|
||||||
if (RefControl.isRE(pattern)) {
|
if (RefPattern.isRE(pattern)) {
|
||||||
example = RefControl.shortestExample(pattern);
|
example = RefPattern.shortestExample(pattern);
|
||||||
|
|
||||||
} else if (pattern.endsWith("/*")) {
|
} else if (pattern.endsWith("/*")) {
|
||||||
example = pattern;
|
example = pattern;
|
||||||
@@ -98,8 +98,8 @@ public final class MostSpecificComparator implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean finite(String pattern) {
|
private boolean finite(String pattern) {
|
||||||
if (RefControl.isRE(pattern)) {
|
if (RefPattern.isRE(pattern)) {
|
||||||
return RefControl.toRegExp(pattern).toAutomaton().isFinite();
|
return RefPattern.toRegExp(pattern).toAutomaton().isFinite();
|
||||||
|
|
||||||
} else if (pattern.endsWith("/*")) {
|
} else if (pattern.endsWith("/*")) {
|
||||||
return false;
|
return false;
|
||||||
@@ -110,8 +110,8 @@ public final class MostSpecificComparator implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
private int transitions(String pattern) {
|
private int transitions(String pattern) {
|
||||||
if (RefControl.isRE(pattern)) {
|
if (RefPattern.isRE(pattern)) {
|
||||||
return RefControl.toRegExp(pattern).toAutomaton()
|
return RefPattern.toRegExp(pattern).toAutomaton()
|
||||||
.getNumberOfTransitions();
|
.getNumberOfTransitions();
|
||||||
|
|
||||||
} else if (pattern.endsWith("/*")) {
|
} else if (pattern.endsWith("/*")) {
|
||||||
|
@@ -823,27 +823,26 @@ public class RefControlTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidateRefPatternsOK() throws Exception {
|
public void testValidateRefPatternsOK() throws Exception {
|
||||||
RefControl.validateRefPattern("refs/*");
|
RefPattern.validate("refs/*");
|
||||||
RefControl.validateRefPattern("^refs/heads/*");
|
RefPattern.validate("^refs/heads/*");
|
||||||
RefControl.validateRefPattern("^refs/tags/[0-9a-zA-Z-_.]+");
|
RefPattern.validate("^refs/tags/[0-9a-zA-Z-_.]+");
|
||||||
RefControl.validateRefPattern("refs/heads/review/${username}/*");
|
RefPattern.validate("refs/heads/review/${username}/*");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = InvalidNameException.class)
|
@Test(expected = InvalidNameException.class)
|
||||||
public void testValidateBadRefPatternDoubleCaret() throws Exception {
|
public void testValidateBadRefPatternDoubleCaret() throws Exception {
|
||||||
RefControl.validateRefPattern("^^refs/*");
|
RefPattern.validate("^^refs/*");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = InvalidNameException.class)
|
@Test(expected = InvalidNameException.class)
|
||||||
public void testValidateBadRefPatternDanglingCharacter() throws Exception {
|
public void testValidateBadRefPatternDanglingCharacter() throws Exception {
|
||||||
RefControl
|
RefPattern
|
||||||
.validateRefPattern("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}*");
|
.validate("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}*");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidateRefPatternNoDanglingCharacter() throws Exception {
|
public void testValidateRefPatternNoDanglingCharacter() throws Exception {
|
||||||
RefControl
|
RefPattern.validate("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}");
|
||||||
.validateRefPattern("^refs/heads/tmp/sdk/[0-9]{3,3}_R[1-9][A-Z][0-9]{3,3}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private InMemoryRepository add(ProjectConfig pc) {
|
private InMemoryRepository add(ProjectConfig pc) {
|
||||||
|
Reference in New Issue
Block a user