diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 665f63487d..5bb50db06f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -248,7 +248,8 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per /** * Returns true if the {@link Repository} is valid. * @@ -257,9 +258,10 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per */ @Override public boolean isValid() { - return ValidationUtil.isRepositoryNameValid(name) && Util.isNotEmpty(type) - && ((Util.isEmpty(contact)) - || ValidationUtil.isMailAddressValid(contact)); + return ValidationUtil.isRepositoryNameValid(namespace) + && ValidationUtil.isRepositoryNameValid(name) + && Util.isNotEmpty(type) + && ((Util.isEmpty(contact)) || ValidationUtil.isMailAddressValid(contact)); } /** diff --git a/scm-core/src/main/java/sonia/scm/util/ValidationUtil.java b/scm-core/src/main/java/sonia/scm/util/ValidationUtil.java index 2dbf55287d..e354b7efc6 100644 --- a/scm-core/src/main/java/sonia/scm/util/ValidationUtil.java +++ b/scm-core/src/main/java/sonia/scm/util/ValidationUtil.java @@ -35,14 +35,12 @@ package sonia.scm.util; //~--- non-JDK imports -------------------------------------------------------- -import com.google.common.base.Splitter; - import sonia.scm.Validateable; -//~--- JDK imports ------------------------------------------------------------ - import java.util.regex.Pattern; +//~--- JDK imports ------------------------------------------------------------ + /** * * @author Sebastian Sdorra @@ -58,10 +56,10 @@ public final class ValidationUtil private static final String REGEX_NAME = "^[A-z0-9\\.\\-_@]|[^ ]([A-z0-9\\.\\-_@ ]*[A-z0-9\\.\\-_@]|[^ ])?$"; + public static final String REGEX_REPOSITORYNAME = "(?!^\\.\\.$)(?!^\\.$)(?!.*[\\\\\\[\\]])^[A-z0-9\\.][A-z0-9\\.\\-_]*$"; + /** Field description */ - private static final Pattern REGEX_REPOSITORYNAME = Pattern.compile( - "(?!^\\.\\.$)(?!^\\.$)(?!.*[\\\\\\[\\]])^[A-z0-9\\.][A-z0-9\\.\\-_]*$" - ); + private static final Pattern PATTERN_REPOSITORYNAME = Pattern.compile(REGEX_REPOSITORYNAME); //~--- constructors --------------------------------------------------------- @@ -151,7 +149,7 @@ public final class ValidationUtil * @return {@code true} if repository name is valid */ public static boolean isRepositoryNameValid(String name) { - return REGEX_REPOSITORYNAME.matcher(name).matches(); + return PATTERN_REPOSITORYNAME.matcher(name).matches(); } /** diff --git a/scm-ui/src/repos/components/form/repositoryValidation.js b/scm-ui/src/repos/components/form/repositoryValidation.js index c7f6fdb8b2..cdd180ad47 100644 --- a/scm-ui/src/repos/components/form/repositoryValidation.js +++ b/scm-ui/src/repos/components/form/repositoryValidation.js @@ -1,8 +1,10 @@ // @flow import { validation } from "@scm-manager/ui-components"; +const nameRegex = /(?!^\.\.$)(?!^\.$)(?!.*[\\\[\]])^[A-z0-9\.][A-z0-9\.\-_]*$/; + export const isNameValid = (name: string) => { - return validation.isNameValid(name); + return nameRegex.test(name); }; export function isContactValid(mail: string) { diff --git a/scm-ui/src/repos/components/form/repositoryValidation.test.js b/scm-ui/src/repos/components/form/repositoryValidation.test.js index bcb29f3ef7..f0b524f2a9 100644 --- a/scm-ui/src/repos/components/form/repositoryValidation.test.js +++ b/scm-ui/src/repos/components/form/repositoryValidation.test.js @@ -11,6 +11,81 @@ describe("repository name validation", () => { expect(validator.isNameValid("scm/manager")).toBe(false); expect(validator.isNameValid("scm/ma/nager")).toBe(false); }); + + it("should allow same names as the backend", () => { + const validPaths = [ + "scm", + "s", + "sc", + ".hiddenrepo", + "b.", + "...", + "..c", + "d..", + "a..c" + ]; + + validPaths.forEach((path) => + expect(validator.isNameValid(path)).toBe(true) + ); + }); + + it("should deny same names as the backend", () => { + const invalidPaths = [ + ".", + "/", + "//", + "..", + "/.", + "/..", + "./", + "../", + "/../", + "/./", + "/...", + "/abc", + ".../", + "/sdf/", + "asdf/", + "./b", + "scm/plugins/.", + "scm/../plugins", + "scm/main/", + "/scm/main/", + "scm/./main", + "scm//main", + "scm\\main", + "scm/main-$HOME", + "scm/main-${HOME}-home", + "scm/main-%HOME-home", + "scm/main-%HOME%-home", + "abc$abc", + "abc%abc", + "abcabc", + "abc#abc", + "abc+abc", + "abc{abc", + "abc}abc", + "abc(abc", + "abc)abc", + "abc[abc", + "abc]abc", + "abc|abc", + "scm/main", + "scm/plugins/git-plugin", + ".scm/plugins", + "a/b..", + "a/..b", + "scm/main", + "scm/plugins/git-plugin", + "scm/plugins/git-plugin" + ]; + + invalidPaths.forEach((path) => + expect(validator.isNameValid(path)).toBe(false) + ); + }); }); describe("repository contact validation", () => { diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java index 8b48311bba..f3f28191ae 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/RepositoryDto.java @@ -9,6 +9,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.hibernate.validator.constraints.Email; import org.hibernate.validator.constraints.NotEmpty; +import sonia.scm.util.ValidationUtil; import javax.validation.constraints.Pattern; import java.time.Instant; @@ -25,8 +26,9 @@ public class RepositoryDto extends HalRepresentation { private List healthCheckFailures; @JsonInclude(JsonInclude.Include.NON_NULL) private Instant lastModified; + // we could not validate the namespace, this must be done by the namespace strategy private String namespace; - @Pattern(regexp = "^[A-z0-9\\-_]+$") + @Pattern(regexp = ValidationUtil.REGEX_REPOSITORYNAME) private String name; private boolean archived = false; @NotEmpty