diff --git a/pom.xml b/pom.xml index 4a2684a..31e5bb9 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ com.danielflower.apprunner restabuild - 1.1-SNAPSHOT + 1.2-SNAPSHOT Restabuild Builds Git-hosted projects via a REST API @@ -58,7 +58,7 @@ ch.qos.logback logback-classic - 1.5.2 + 1.5.6 runtime @@ -86,7 +86,7 @@ io.muserver mu-server - 2.0.0 + 2.0.1 @@ -203,7 +203,7 @@ org.apache.maven.plugins maven-shade-plugin - 3.5.3 + 3.6.0 package @@ -236,7 +236,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 3.6.3 + 3.7.0 attach-javadocs diff --git a/sample-config.properties b/sample-config.properties index 65529ec..89049a3 100644 --- a/sample-config.properties +++ b/sample-config.properties @@ -14,5 +14,14 @@ restabuild.context=/restabuild # The timeout threshold per run (unit in minute) restabuild.timeout=30 -# Controls whether or not to delete the instance directories of each build upon completion. Valid values: ALWAYS, NEVER, ON_SUCCESS -restabuild.delete.policy=ON_SUCCESS \ No newline at end of file +# Controls whether to delete the instance directories of each build upon completion. Valid values: ALWAYS, NEVER, ON_SUCCESS +restabuild.delete.policy=ON_SUCCESS + +# An optional regular expression pattern that git URLs must match. Empty to allow any URLs. +restabuild.git.url.pattern=https://github\.com/.*\.git + +# The message to return to users if the git URL specified does not match the allowed regular expression +restabuild.git.url.validation.message=Only HTTPS github.com URLs are allowed + +# The example URL to show in the UI +restabuild.example.url=https://github.com/3redronin/mu-server-sample.git \ No newline at end of file diff --git a/src/main/java/com/danielflower/restabuild/App.java b/src/main/java/com/danielflower/restabuild/App.java index 025a284..30e571a 100644 --- a/src/main/java/com/danielflower/restabuild/App.java +++ b/src/main/java/com/danielflower/restabuild/App.java @@ -48,9 +48,9 @@ public void start() throws IOException { buildQueue = new BuildQueue(numberOfConcurrentBuilds, buildTimeoutMinutes, config.deletePolicy()); - BuildResource buildResource = new BuildResource(fileSandbox, database, buildQueue, executorService); + BuildResource buildResource = new BuildResource(fileSandbox, database, buildQueue, executorService, config.allowedRepoUrlPattern(), config.allowedRepoUrlValidationMessage()); String context = Mutils.trim(config.get(Config.CONTEXT, "restabuild"), "/"); - webServer = WebServer.start(appRunnerPort, context, buildResource, buildTimeoutMinutes); + webServer = WebServer.start(appRunnerPort, context, buildResource, buildTimeoutMinutes, config); } private void deleteOldTempFiles(File tempDir) { diff --git a/src/main/java/com/danielflower/restabuild/Config.java b/src/main/java/com/danielflower/restabuild/Config.java index 9d3ee71..b5eed23 100644 --- a/src/main/java/com/danielflower/restabuild/Config.java +++ b/src/main/java/com/danielflower/restabuild/Config.java @@ -1,8 +1,8 @@ package com.danielflower.restabuild; import com.danielflower.restabuild.build.DeletePolicy; -import com.danielflower.restabuild.build.RestaBuildException; import com.danielflower.restabuild.build.InvalidConfigException; +import com.danielflower.restabuild.build.RestaBuildException; import org.apache.commons.io.FileUtils; import java.io.File; @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.Map; import java.util.Properties; +import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -90,6 +91,19 @@ public int getInt(String name, int defaultValue) { } } + public Pattern allowedRepoUrlPattern() { + String exp = get("restabuild.git.url.pattern", ".*"); + return Pattern.compile(exp); + } + + public String allowedRepoUrlValidationMessage() { + return get("restabuild.git.url.validation.message", "Sorry, that URL is not allowed."); + } + + public String exampleURl() { + return get("restabuild.example.url", ""); + } + public File getOrCreateDir(String name) { File f = new File(get(name)); @@ -105,5 +119,11 @@ public static boolean isWindows() { return File.separatorChar == '\\'; } + public Config clone(String keyToChange, String valueForKey) { + Map copy = new HashMap<>(raw); + copy.put(keyToChange, valueForKey); + return new Config(copy); + } + } diff --git a/src/main/java/com/danielflower/restabuild/web/BuildResource.java b/src/main/java/com/danielflower/restabuild/web/BuildResource.java index fbbec89..a4e9e5a 100644 --- a/src/main/java/com/danielflower/restabuild/web/BuildResource.java +++ b/src/main/java/com/danielflower/restabuild/web/BuildResource.java @@ -26,6 +26,7 @@ import java.util.Optional; import java.util.UUID; import java.util.concurrent.ExecutorService; +import java.util.regex.Pattern; import java.util.stream.Collectors; @Path("api/v1/builds") @@ -42,12 +43,16 @@ public class BuildResource { private final BuildDatabase database; private final BuildQueue buildQueue; private final ExecutorService executorService; + private final Pattern allowedUrlPattern; + private final String urlPatternValidationErrorMessage; - public BuildResource(FileSandbox fileSandbox, BuildDatabase database, BuildQueue buildQueue, ExecutorService executorService) { + public BuildResource(FileSandbox fileSandbox, BuildDatabase database, BuildQueue buildQueue, ExecutorService executorService, Pattern allowedUrlPattern, String urlPatternValidationErrorMessage) { this.fileSandbox = fileSandbox; this.buildQueue = buildQueue; this.database = database; this.executorService = executorService; + this.allowedUrlPattern = allowedUrlPattern; + this.urlPatternValidationErrorMessage = urlPatternValidationErrorMessage; } @POST @@ -104,6 +109,10 @@ private URIish validateGitUrl(String gitUrl) { } catch (URISyntaxException e) { throw new BadRequestException("An invalid Git URL was specified: " + e.getMessage()); } + if (!allowedUrlPattern.matcher(gitUrl).matches()) { + System.out.println("gitUrl = " + gitUrl); + throw new BadRequestException(urlPatternValidationErrorMessage); + } return gitURIish; } diff --git a/src/main/java/com/danielflower/restabuild/web/IndexHtmlHandler.java b/src/main/java/com/danielflower/restabuild/web/IndexHtmlHandler.java index ead9495..2dbcbd4 100644 --- a/src/main/java/com/danielflower/restabuild/web/IndexHtmlHandler.java +++ b/src/main/java/com/danielflower/restabuild/web/IndexHtmlHandler.java @@ -1,10 +1,12 @@ package com.danielflower.restabuild.web; +import com.danielflower.restabuild.Config; import com.danielflower.restabuild.build.BuildResult; -import com.danielflower.restabuild.build.RemoteGitRepo; import io.muserver.*; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.Map; import static io.muserver.Mutils.coalesce; @@ -13,17 +15,30 @@ public class IndexHtmlHandler implements RouteHandler { private final String template; - public IndexHtmlHandler() throws IOException { + public IndexHtmlHandler(Config config) throws IOException { String version = coalesce(getClass().getPackage().getImplementationVersion(), "dev"); - template = new String(Mutils.toByteArray(IndexHtmlHandler.class.getResourceAsStream("/web/index.html"), 8192), "UTF-8") + StringBuilder inputBoxAttributes = new StringBuilder(); + String urlPattern = config.allowedRepoUrlPattern().pattern(); + if (!urlPattern.equals(".*")) { + inputBoxAttributes.append(" pattern=\"").append(Mutils.htmlEncode(urlPattern)).append("\" title=\"").append(Mutils.htmlEncode(config.allowedRepoUrlValidationMessage())).append("\""); + } + if (!Mutils.nullOrEmpty(config.exampleURl())) { + inputBoxAttributes.append(" placeholder=\"").append(Mutils.htmlEncode(config.exampleURl())).append("\""); + } + + InputStream template = IndexHtmlHandler.class.getResourceAsStream("/web/index.html"); + this.template = new String(Mutils.toByteArray(template, 8192), StandardCharsets.UTF_8) .replace("{{buildfilename}}", BuildResult.buildFile) - .replace("{{restabuildversion}}", version); + .replace("{{restabuildversion}}", version) + .replace("{{gitUrlTextBoxAttributes}}", inputBoxAttributes.toString()) + + ; } @Override - public void handle(MuRequest request, MuResponse response, Map pathParams) throws Exception { + public void handle(MuRequest request, MuResponse response, Map pathParams) { response.contentType(ContentTypes.TEXT_HTML_UTF8); response.write(template); } diff --git a/src/main/java/com/danielflower/restabuild/web/WebServer.java b/src/main/java/com/danielflower/restabuild/web/WebServer.java index ff0c7dc..c7f6043 100644 --- a/src/main/java/com/danielflower/restabuild/web/WebServer.java +++ b/src/main/java/com/danielflower/restabuild/web/WebServer.java @@ -1,5 +1,6 @@ package com.danielflower.restabuild.web; +import com.danielflower.restabuild.Config; import io.muserver.Method; import io.muserver.MuServer; import io.muserver.Mutils; @@ -25,7 +26,7 @@ private WebServer(MuServer server) { this.server = server; } - public static WebServer start(int port, String context, BuildResource buildResource, int buildTimeoutMinutes) throws IOException { + public static WebServer start(int port, String context, BuildResource buildResource, int buildTimeoutMinutes, Config config) throws IOException { boolean hasContext = !Mutils.nullOrEmpty(context); MuServer server = muServer() .withHttpPort(port) @@ -55,7 +56,7 @@ public static WebServer start(int port, String context, BuildResource buildResou .build()) ) ) - .addHandler(Method.GET, "/", new IndexHtmlHandler()) + .addHandler(Method.GET, "/", new IndexHtmlHandler(config)) .addHandler(fileOrClasspath("src/main/resources/web", "/web"))) .start(); diff --git a/src/main/resources/web/index.html b/src/main/resources/web/index.html index 8da9e9c..06c169c 100644 --- a/src/main/resources/web/index.html +++ b/src/main/resources/web/index.html @@ -49,8 +49,7 @@

Submit a build