Skip to content

Commit

Permalink
Allow a regex pattern to specify which git URLs are allowed to be bui…
Browse files Browse the repository at this point in the history
…lt. The example URL in the UI is now configurable too.
  • Loading branch information
danielflower committed Jun 22, 2024
1 parent 50749a2 commit 73ae81d
Show file tree
Hide file tree
Showing 9 changed files with 86 additions and 21 deletions.
10 changes: 5 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.danielflower.apprunner</groupId>
<artifactId>restabuild</artifactId>
<version>1.1-SNAPSHOT</version>
<version>1.2-SNAPSHOT</version>

<name>Restabuild</name>
<description>Builds Git-hosted projects via a REST API</description>
Expand Down Expand Up @@ -58,7 +58,7 @@
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.5.2</version>
<version>1.5.6</version>
<scope>runtime</scope>
</dependency>
<dependency>
Expand Down Expand Up @@ -86,7 +86,7 @@
<dependency>
<groupId>io.muserver</groupId>
<artifactId>mu-server</artifactId>
<version>2.0.0</version>
<version>2.0.1</version>
</dependency>

<!-- git stuff -->
Expand Down Expand Up @@ -203,7 +203,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.5.3</version>
<version>3.6.0</version>
<executions>
<execution>
<phase>package</phase>
Expand Down Expand Up @@ -236,7 +236,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-javadoc-plugin</artifactId>
<version>3.6.3</version>
<version>3.7.0</version>
<executions>
<execution>
<id>attach-javadocs</id>
Expand Down
13 changes: 11 additions & 2 deletions sample-config.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
# 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
4 changes: 2 additions & 2 deletions src/main/java/com/danielflower/restabuild/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
22 changes: 21 additions & 1 deletion src/main/java/com/danielflower/restabuild/Config.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -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));
Expand All @@ -105,5 +119,11 @@ public static boolean isWindows() {
return File.separatorChar == '\\';
}

public Config clone(String keyToChange, String valueForKey) {
Map<String, String> copy = new HashMap<>(raw);
copy.put(keyToChange, valueForKey);
return new Config(copy);
}

}

11 changes: 10 additions & 1 deletion src/main/java/com/danielflower/restabuild/web/BuildResource.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<String, String> pathParams) throws Exception {
public void handle(MuRequest request, MuResponse response, Map<String, String> pathParams) {
response.contentType(ContentTypes.TEXT_HTML_UTF8);
response.write(template);
}
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/com/danielflower/restabuild/web/WebServer.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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)
Expand Down Expand Up @@ -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();

Expand Down
3 changes: 1 addition & 2 deletions src/main/resources/web/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ <h2>Submit a build</h2>
<form id="createForm" action="api/v1/builds" method="post">
<label>
Git URL:
<input type="text" id="gitUrlBox" name="gitUrl" required
placeholder="https://github.com/3redronin/mu-server-sample.git">
<input type="text" id="gitUrlBox" name="gitUrl" required {{gitUrlTextBoxAttributes}}>
</label>
<label>
Git Branch:
Expand Down
14 changes: 13 additions & 1 deletion src/test/java/com/danielflower/restabuild/SystemTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ public class SystemTest {

@BeforeClass
public static void start() throws Exception {
config = Config.load(new String[]{"sample-config.properties"});
config = Config.load(new String[]{"sample-config.properties"})
.clone("restabuild.git.url.pattern", "file:/.*");
app = new App(config);
app.start();
}
Expand Down Expand Up @@ -70,6 +71,17 @@ public void theRestApiCanBeUsedToBuildStuff() throws Exception {
assertThat(afterBuild.getJSONArray("tagsCreated").get(0), is("my-maven-app-1.0.0"));
}

@Test
public void ifTheGitUrlDoesNotMatchThePatternYouGetA400() throws Exception {
Fields fields = new Fields();
// this test class changes the sample-config.properties file to be file:/ URLs only, so not quite testing the sample-config properties value here
fields.add("gitUrl", "ssh://github.com/blah.git");
ContentResponse resp = client.FORM(buildsUrl(), fields);
assertThat(resp.getStatus(), equalTo(400));
// the message is set in sample-config.properties
assertThat(resp.getContentAsString(), containsString("Only HTTPS github.com URLs are allowed"));
}

private JSONObject waitForBuildToFinish(JSONObject build, BuildStatus expectedStatus) throws InterruptedException, ExecutionException, TimeoutException {
String url = build.getString("url");

Expand Down

0 comments on commit 73ae81d

Please sign in to comment.