Introduce an optional configuration to define a regex for naming rules

Before this change, the plugin made checks on the name to verify that
the new project name is not null and that it doesn't exist. Therefore,
the new project name can have a name with any set of characters while
complying to the naming rules. However, due to IRI (not URI) and
backward compatibility, the non-ASCII characters may be represented in a
way that can cause issues [1].

This change introduces a configurable parameter that can be set to
define a regex that is then enforced upon renaming a project to ensure
backward compatibility.

[1] https://en.wikipedia.org/wiki/Internationalized_Resource_Identifier

Bug: Issue 14297
Change-Id: Ia049cb399afe1091626cafebfb3d64f72f41e61f
diff --git a/src/main/java/com/googlesource/gerrit/plugins/renameproject/Configuration.java b/src/main/java/com/googlesource/gerrit/plugins/renameproject/Configuration.java
index 1a3570d..a74c4cb 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/renameproject/Configuration.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/renameproject/Configuration.java
@@ -33,6 +33,7 @@
   private final int indexThreads;
   private final int sshCommandTimeout;
   private final int sshConnectionTimeout;
+  private final String renameRegex;
 
   private final Set<String> urls;
 
@@ -42,6 +43,7 @@
     indexThreads = cfg.getInt("indexThreads", 4);
     sshCommandTimeout = cfg.getInt("sshCommandTimeout", 0);
     sshConnectionTimeout = cfg.getInt("sshConnectionTimeout", DEFAULT_SSH_CONNECTION_TIMEOUT_MS);
+    renameRegex = cfg.getString("renameRegex", ".+");
 
     urls =
         Arrays.stream(cfg.getStringList(URL_KEY))
@@ -66,4 +68,8 @@
   public int getSshConnectionTimeout() {
     return sshConnectionTimeout;
   }
+
+  public String getRenameRegex() {
+    return renameRegex;
+  }
 }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java b/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
index f4a5418..0cf1ca4 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/renameproject/RenameProject.java
@@ -151,6 +151,14 @@
     }
   }
 
+  private void assertNewNameMatchesRegex(Input input) throws BadRequestException {
+    if (!input.name.matches(cfg.getRenameRegex())) {
+      throw new BadRequestException(
+          String.format(
+              "Name of the repo should match the expected regex: %s", cfg.getRenameRegex()));
+    }
+  }
+
   private void assertRenamePermission(ProjectResource rsrc) throws AuthException {
     if (!canRename(rsrc)) {
       throw new AuthException("Not allowed to rename project");
@@ -195,6 +203,7 @@
       pm.ifPresent(progressMonitor -> progressMonitor.beginTask("Checking preconditions"));
 
       assertNewNameNotNull(input);
+      assertNewNameMatchesRegex(input);
       assertRenamePermission(rsrc);
       renamePreconditions.assertCanRename(rsrc, Project.nameKey(input.name));
       log.debug("Rename preconditions check successful.");
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index 7a5faeb..80872a1 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -43,3 +43,14 @@
 the client waits indefinitely. By default, 0.
 * `sshConnectionTimeout` : Timeout for SSH connections in minutes. If 0, there is no timeout, and
 the client waits indefinitely. By default, 2 minutes.
+
+Also, this plugin offers a way to restrict the new names of the projects to match an optionally
+configured regex. For example:
+
+```
+  [plugin "@PLUGIN@"]
+    renameRegex = [a-z0-9]+
+```
+
+In this example the new names for projects will be restricted to only non-capital letters and
+numbers.
diff --git a/src/test/java/com/googlesource/gerrit/plugins/renameproject/RenameIT.java b/src/test/java/com/googlesource/gerrit/plugins/renameproject/RenameIT.java
index a69ddc9..4669900 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/renameproject/RenameIT.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/renameproject/RenameIT.java
@@ -57,6 +57,7 @@
   private static final String CACHE_NAME = "changeid_project";
   private static final String REPLICATION_OPTION = "--replication";
   private static final String URL = "ssh://localhost:29418";
+  private static final String RENAME_REGEX = "[a-zA-Z]+";
 
   @Inject private RequestScopeOperations requestScopeOperations;
 
@@ -253,6 +254,32 @@
     assertThat(projectState).isNull();
   }
 
+  @Test
+  @UseLocalDisk
+  @GerritConfig(name = "plugin.rename-project.renameRegex", value = RENAME_REGEX)
+  public void testRenameViaHttpWithNonMatchingNameFail() throws Exception {
+    createChange();
+    RestResponse r = renameProjectTo(NEW_PROJECT_NAME + "1");
+    r.assertBadRequest();
+
+    ProjectState projectState = projectCache.get(Project.nameKey(NEW_PROJECT_NAME));
+    assertThat(projectState).isNull();
+  }
+
+  @Test
+  @UseLocalDisk
+  @GerritConfig(name = "plugin.rename-project.renameRegex", value = RENAME_REGEX)
+  public void testRenameViaHttpWithMatchingNameSuccess() throws Exception {
+    createChange();
+    RestResponse r = renameProjectTo(NEW_PROJECT_NAME);
+    r.assertOK();
+
+    ProjectState projectState = projectCache.get(Project.nameKey(NEW_PROJECT_NAME));
+    assertThat(projectState).isNotNull();
+    assertThat(queryProvider.get().byProject(project)).isEmpty();
+    assertThat(queryProvider.get().byProject(Project.nameKey(NEW_PROJECT_NAME))).isNotEmpty();
+  }
+
   private RestResponse renameProjectTo(String newName) throws Exception {
     requestScopeOperations.setApiUser(user.id());
     sender.clear();