Compare commits
1 Commits
225f156864
...
0.0.2
Author | SHA1 | Date | |
---|---|---|---|
9de393c6ae
|
@@ -5,6 +5,8 @@ on:
|
|||||||
- '*'
|
- '*'
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
|
env:
|
||||||
|
RUNNER_TOOL_CACHE: /toolcache
|
||||||
runs-on: hostinger
|
runs-on: hostinger
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout sources
|
- name: Checkout sources
|
||||||
|
@@ -1,26 +0,0 @@
|
|||||||
plugins {
|
|
||||||
alias catalog.plugins.gradle.jmh
|
|
||||||
alias catalog.plugins.lombok
|
|
||||||
}
|
|
||||||
|
|
||||||
import me.champeau.jmh.JMHTask
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation rootProject
|
|
||||||
|
|
||||||
implementation catalog.jwo
|
|
||||||
implementation catalog.xz
|
|
||||||
implementation catalog.jackson.databind
|
|
||||||
|
|
||||||
jmhAnnotationProcessor catalog.lombok
|
|
||||||
}
|
|
||||||
|
|
||||||
jmh {
|
|
||||||
threads = 4
|
|
||||||
iterations = 2
|
|
||||||
fork = 1
|
|
||||||
warmupIterations = 1
|
|
||||||
warmupForks = 0
|
|
||||||
resultFormat = 'JSON'
|
|
||||||
}
|
|
||||||
|
|
@@ -1,262 +0,0 @@
|
|||||||
package net.woggioni.gbcs.benchmark;
|
|
||||||
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import net.woggioni.jwo.Fun;
|
|
||||||
import org.openjdk.jmh.annotations.Benchmark;
|
|
||||||
import org.openjdk.jmh.annotations.BenchmarkMode;
|
|
||||||
import org.openjdk.jmh.annotations.Level;
|
|
||||||
import org.openjdk.jmh.annotations.Mode;
|
|
||||||
import org.openjdk.jmh.annotations.OutputTimeUnit;
|
|
||||||
import org.openjdk.jmh.annotations.Scope;
|
|
||||||
import org.openjdk.jmh.annotations.Setup;
|
|
||||||
import org.openjdk.jmh.annotations.State;
|
|
||||||
import org.openjdk.jmh.annotations.TearDown;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.TrustManager;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Properties;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
import java.util.function.Predicate;
|
|
||||||
|
|
||||||
public class Main {
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private static Properties loadProperties() {
|
|
||||||
Properties properties = new Properties();
|
|
||||||
try (final var is = Main.class.getResourceAsStream("/benchmark.properties")) {
|
|
||||||
properties.load(is);
|
|
||||||
}
|
|
||||||
return properties;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final Properties properties = loadProperties();
|
|
||||||
|
|
||||||
@State(Scope.Thread)
|
|
||||||
public static class ExecutionPlan {
|
|
||||||
private final Random random = new Random(101325);
|
|
||||||
|
|
||||||
@Getter
|
|
||||||
private final HttpClient client = createHttpClient();
|
|
||||||
|
|
||||||
private final Map<String, byte[]> entries = new HashMap<>();
|
|
||||||
|
|
||||||
|
|
||||||
private HttpClient createHttpClient() {
|
|
||||||
final var clientBuilder = HttpClient.newBuilder();
|
|
||||||
getSslContext().ifPresent(clientBuilder::sslContext);
|
|
||||||
return clientBuilder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Map<String, byte[]> getEntries() {
|
|
||||||
return Collections.unmodifiableMap(entries);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map.Entry<String, byte[]> newEntry() {
|
|
||||||
final var keyBuffer = new byte[0x20];
|
|
||||||
random.nextBytes(keyBuffer);
|
|
||||||
final var key = Base64.getUrlEncoder().encodeToString(keyBuffer);
|
|
||||||
final var value = new byte[0x1000];
|
|
||||||
random.nextBytes(value);
|
|
||||||
return Map.entry(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public HttpRequest.Builder newRequestBuilder(String key) {
|
|
||||||
final var requestBuilder = HttpRequest.newBuilder()
|
|
||||||
.uri(getServerURI().resolve(key));
|
|
||||||
String user = getUser();
|
|
||||||
if (user != null) {
|
|
||||||
requestBuilder.header("Authorization", buildAuthorizationHeader(user, getPassword()));
|
|
||||||
}
|
|
||||||
return requestBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public URI getServerURI() {
|
|
||||||
return new URI(properties.getProperty("gbcs.server.url"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<String> getClientTrustStorePassword() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.truststore.password"))
|
|
||||||
.filter(Predicate.not(String::isEmpty));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<KeyStore> getClientTrustStore() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.truststore.file"))
|
|
||||||
.filter(Predicate.not(String::isEmpty))
|
|
||||||
.map(Path::of)
|
|
||||||
.map((Fun<Path, KeyStore>) keyStoreFile -> {
|
|
||||||
final var keyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
try (final var is = Files.newInputStream(keyStoreFile)) {
|
|
||||||
keyStore.load(is, getClientTrustStorePassword().map(String::toCharArray).orElse(null));
|
|
||||||
}
|
|
||||||
return keyStore;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<KeyStore> getClientKeyStore() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.keystore.file"))
|
|
||||||
.filter(Predicate.not(String::isEmpty))
|
|
||||||
.map(Path::of)
|
|
||||||
.map((Fun<Path, KeyStore>) keyStoreFile -> {
|
|
||||||
final var keyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
try (final var is = Files.newInputStream(keyStoreFile)) {
|
|
||||||
keyStore.load(is, getClientKeyStorePassword().map(String::toCharArray).orElse(null));
|
|
||||||
}
|
|
||||||
return keyStore;
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<String> getClientKeyStorePassword() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.keystore.password"))
|
|
||||||
.filter(Predicate.not(String::isEmpty));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public Optional<String> getClientKeyPassword() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.client.ssl.key.password"))
|
|
||||||
.filter(Predicate.not(String::isEmpty));
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public String getUser() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.server.username"))
|
|
||||||
.filter(Predicate.not(String::isEmpty))
|
|
||||||
.orElse(null);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
public String getPassword() {
|
|
||||||
return Optional.ofNullable(properties.getProperty("gbcs.server.password"))
|
|
||||||
.filter(Predicate.not(String::isEmpty))
|
|
||||||
.orElse(null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildAuthorizationHeader(String user, String password) {
|
|
||||||
final var b64 = Base64.getEncoder().encode(String.format("%s:%s", user, password).getBytes(StandardCharsets.UTF_8));
|
|
||||||
return "Basic " + new String(b64);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
private Optional<SSLContext> getSslContext() {
|
|
||||||
return getClientKeyStore().map((Fun<KeyStore, SSLContext>) clientKeyStore -> {
|
|
||||||
final var kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
|
||||||
kmf.init(clientKeyStore, getClientKeyStorePassword().map(String::toCharArray).orElse(null));
|
|
||||||
|
|
||||||
|
|
||||||
// Set up trust manager factory with the truststore
|
|
||||||
final var trustManagers = getClientTrustStore().map((Fun<KeyStore, TrustManager[]>) ts -> {
|
|
||||||
final var tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
tmf.init(ts);
|
|
||||||
return tmf.getTrustManagers();
|
|
||||||
}).orElse(new TrustManager[0]);
|
|
||||||
|
|
||||||
// Create SSL context with the key and trust managers
|
|
||||||
final var sslContext = SSLContext.getInstance("TLS");
|
|
||||||
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
|
|
||||||
return sslContext;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Setup(Level.Trial)
|
|
||||||
public void setUp() {
|
|
||||||
final var client = getClient();
|
|
||||||
for (int i = 0; i < 1000; i++) {
|
|
||||||
final var pair = newEntry();
|
|
||||||
final var requestBuilder = newRequestBuilder(pair.getKey())
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(pair.getValue()));
|
|
||||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
if (201 != response.statusCode()) {
|
|
||||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
|
||||||
} else {
|
|
||||||
entries.put(pair.getKey(), pair.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TearDown
|
|
||||||
public void tearDown() {
|
|
||||||
client.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private Iterator<Map.Entry<String, byte[]>> it = null;
|
|
||||||
|
|
||||||
private Map.Entry<String, byte[]> nextEntry() {
|
|
||||||
if (it == null || !it.hasNext()) {
|
|
||||||
it = getEntries().entrySet().iterator();
|
|
||||||
}
|
|
||||||
return it.next();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Benchmark
|
|
||||||
@BenchmarkMode(Mode.Throughput)
|
|
||||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
|
||||||
public void get(ExecutionPlan plan) {
|
|
||||||
final var client = plan.getClient();
|
|
||||||
final var entry = plan.nextEntry();
|
|
||||||
final var requestBuilder = plan.newRequestBuilder(entry.getKey())
|
|
||||||
.header("Accept", "application/octet-stream")
|
|
||||||
.GET();
|
|
||||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
if (200 != response.statusCode()) {
|
|
||||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
|
||||||
} else {
|
|
||||||
if (!Arrays.equals(entry.getValue(), response.body())) {
|
|
||||||
throw new IllegalStateException("Retrieved unexpected value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SneakyThrows
|
|
||||||
@Benchmark
|
|
||||||
@BenchmarkMode(Mode.Throughput)
|
|
||||||
@OutputTimeUnit(TimeUnit.SECONDS)
|
|
||||||
public void put(Main.ExecutionPlan plan) {
|
|
||||||
final var client = plan.getClient();
|
|
||||||
final var entry = plan.nextEntry();
|
|
||||||
|
|
||||||
final var requestBuilder = plan.newRequestBuilder(entry.getKey())
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(entry.getValue()));
|
|
||||||
|
|
||||||
final var response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
if (201 != response.statusCode()) {
|
|
||||||
throw new IllegalStateException(Integer.toString(response.statusCode()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1 +0,0 @@
|
|||||||
gbcs.server.url= http://localhost:8080
|
|
@@ -1,21 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
|
||||||
<!DOCTYPE configuration>
|
|
||||||
|
|
||||||
<configuration>
|
|
||||||
<import class="ch.qos.logback.classic.encoder.PatternLayoutEncoder"/>
|
|
||||||
<import class="ch.qos.logback.core.ConsoleAppender"/>
|
|
||||||
|
|
||||||
<appender name="console" class="ConsoleAppender">
|
|
||||||
<target>System.err</target>
|
|
||||||
<encoder class="PatternLayoutEncoder">
|
|
||||||
<pattern>%d [%highlight(%-5level)] \(%thread\) %logger{36} -%kvp- %msg %n</pattern>
|
|
||||||
</encoder>
|
|
||||||
</appender>
|
|
||||||
|
|
||||||
<root level="info">
|
|
||||||
<appender-ref ref="console"/>
|
|
||||||
</root>
|
|
||||||
<logger name="io.netty" level="debug"/>
|
|
||||||
<logger name="com.google.code.yanf4j" level="warn"/>
|
|
||||||
<logger name="net.rubyeye.xmemcached" level="warn"/>
|
|
||||||
</configuration>
|
|
@@ -146,8 +146,8 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
dbf.isExpandEntityReferences = true
|
dbf.isExpandEntityReferences = true
|
||||||
dbf.isIgnoringComments = true
|
dbf.isIgnoringComments = true
|
||||||
dbf.isNamespaceAware = true
|
dbf.isNamespaceAware = true
|
||||||
dbf.isValidating = schemaResourceURL == null
|
dbf.isValidating = false
|
||||||
dbf.setFeature("http://apache.org/xml/features/validation/schema", true)
|
dbf.setFeature("http://apache.org/xml/features/validation/schema", true);
|
||||||
schemaResourceURL?.let {
|
schemaResourceURL?.let {
|
||||||
dbf.schema = getSchema(it)
|
dbf.schema = getSchema(it)
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
return resource.openStream().use(db::parse)
|
return resource.openStream().use(db::parse)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun parseXml(sourceURL: URL, sourceStream: InputStream? = null, schemaResourceURL: URL? = null): Document {
|
fun parseXml(sourceURL : URL, sourceStream: InputStream? = null, schemaResourceURL: URL? = null): Document {
|
||||||
val db = newDocumentBuilder(sourceURL, schemaResourceURL)
|
val db = newDocumentBuilder(sourceURL, schemaResourceURL)
|
||||||
return sourceStream?.let(db::parse) ?: sourceURL.openStream().use(db::parse)
|
return sourceStream?.let(db::parse) ?: sourceURL.openStream().use(db::parse)
|
||||||
}
|
}
|
||||||
@@ -183,12 +183,7 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
transformer.transform(source, result)
|
transformer.transform(source, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun of(
|
fun of(namespaceURI: String, qualifiedName: String, schemaResourceURL: URL? = null, cb: Xml.(el: Element) -> Unit): Document {
|
||||||
namespaceURI: String,
|
|
||||||
qualifiedName: String,
|
|
||||||
schemaResourceURL: URL? = null,
|
|
||||||
cb: Xml.(el: Element) -> Unit
|
|
||||||
): Document {
|
|
||||||
val dbf = newDocumentBuilderFactory(schemaResourceURL)
|
val dbf = newDocumentBuilderFactory(schemaResourceURL)
|
||||||
val db = dbf.newDocumentBuilder()
|
val db = dbf.newDocumentBuilder()
|
||||||
val doc = db.newDocument()
|
val doc = db.newDocument()
|
||||||
@@ -212,7 +207,7 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
|
|
||||||
fun node(
|
fun node(
|
||||||
name: String,
|
name: String,
|
||||||
namespaceURI: String? = null,
|
namespaceURI : String? = null,
|
||||||
attrs: Map<String, String> = emptyMap(),
|
attrs: Map<String, String> = emptyMap(),
|
||||||
cb: Xml.(el: Element) -> Unit = {}
|
cb: Xml.(el: Element) -> Unit = {}
|
||||||
): Element {
|
): Element {
|
||||||
@@ -227,7 +222,7 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun attr(key: String, value: String, namespaceURI: String? = null) {
|
fun attr(key: String, value: String, namespaceURI : String? = null) {
|
||||||
element.setAttributeNS(namespaceURI, key, value)
|
element.setAttributeNS(namespaceURI, key, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -5,9 +5,9 @@ import net.woggioni.gbcs.cli.impl.GbcsCommand
|
|||||||
import net.woggioni.gbcs.cli.impl.converters.OutputStreamConverter
|
import net.woggioni.gbcs.cli.impl.converters.OutputStreamConverter
|
||||||
import net.woggioni.jwo.UncloseableOutputStream
|
import net.woggioni.jwo.UncloseableOutputStream
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
|
import java.io.BufferedWriter
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.io.OutputStreamWriter
|
import java.io.OutputStreamWriter
|
||||||
import java.io.PrintWriter
|
|
||||||
|
|
||||||
|
|
||||||
@CommandLine.Command(
|
@CommandLine.Command(
|
||||||
@@ -20,7 +20,7 @@ class PasswordHashCommand : GbcsCommand() {
|
|||||||
names = ["-o", "--output-file"],
|
names = ["-o", "--output-file"],
|
||||||
description = ["Write the output to a file instead of stdout"],
|
description = ["Write the output to a file instead of stdout"],
|
||||||
converter = [OutputStreamConverter::class],
|
converter = [OutputStreamConverter::class],
|
||||||
showDefaultValue = CommandLine.Help.Visibility.NEVER,
|
defaultValue = "stdout",
|
||||||
paramLabel = "OUTPUT_FILE"
|
paramLabel = "OUTPUT_FILE"
|
||||||
)
|
)
|
||||||
private var outputStream: OutputStream = UncloseableOutputStream(System.out)
|
private var outputStream: OutputStream = UncloseableOutputStream(System.out)
|
||||||
@@ -30,8 +30,9 @@ class PasswordHashCommand : GbcsCommand() {
|
|||||||
val password2 = String(System.console().readPassword("Type your password again for confirmation:"))
|
val password2 = String(System.console().readPassword("Type your password again for confirmation:"))
|
||||||
if(password1 != password2) throw IllegalArgumentException("Passwords do not match")
|
if(password1 != password2) throw IllegalArgumentException("Passwords do not match")
|
||||||
|
|
||||||
PrintWriter(OutputStreamWriter(outputStream, Charsets.UTF_8)).use {
|
BufferedWriter(OutputStreamWriter(outputStream, Charsets.UTF_8)).use {
|
||||||
it.println(hashPassword(password1))
|
it.write(hashPassword(password1))
|
||||||
|
it.newLine()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -12,11 +12,10 @@
|
|||||||
</encoder>
|
</encoder>
|
||||||
</appender>
|
</appender>
|
||||||
|
|
||||||
<root level="info">
|
<root level="debug">
|
||||||
<appender-ref ref="console"/>
|
<appender-ref ref="console"/>
|
||||||
</root>
|
</root>
|
||||||
<logger name="io.netty" level="debug"/>
|
<logger name="io.netty" level="debug"/>
|
||||||
<logger name="io.netty.handler.ssl.BouncyCastlePemReader" level="info"/>
|
|
||||||
<logger name="com.google.code.yanf4j" level="warn"/>
|
<logger name="com.google.code.yanf4j" level="warn"/>
|
||||||
<logger name="net.rubyeye.xmemcached" level="warn"/>
|
<logger name="net.rubyeye.xmemcached" level="warn"/>
|
||||||
</configuration>
|
</configuration>
|
@@ -9,16 +9,14 @@ import net.woggioni.gbcs.base.Xml.Companion.asIterable
|
|||||||
import org.w3c.dom.Document
|
import org.w3c.dom.Document
|
||||||
import org.w3c.dom.Element
|
import org.w3c.dom.Element
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
import java.util.zip.Deflater
|
||||||
|
|
||||||
class MemcachedCacheProvider : CacheProvider<MemcachedCacheConfiguration> {
|
class MemcachedCacheProvider : CacheProvider<MemcachedCacheConfiguration> {
|
||||||
override fun getXmlSchemaLocation() = "classpath:net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd"
|
override fun getXmlSchemaLocation() = "classpath:net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd"
|
||||||
|
|
||||||
override fun getXmlType() = "memcachedCacheType"
|
override fun getXmlType() = "memcachedCacheType"
|
||||||
|
|
||||||
override fun getXmlNamespace() = "urn:net.woggioni.gbcs-memcached"
|
override fun getXmlNamespace()= "urn:net.woggioni.gbcs-memcached"
|
||||||
|
|
||||||
val xmlNamespacePrefix : String
|
|
||||||
get() = "gbcs-memcached"
|
|
||||||
|
|
||||||
override fun deserialize(el: Element): MemcachedCacheConfiguration {
|
override fun deserialize(el: Element): MemcachedCacheConfiguration {
|
||||||
val servers = mutableListOf<HostAndPort>()
|
val servers = mutableListOf<HostAndPort>()
|
||||||
@@ -37,7 +35,7 @@ class MemcachedCacheProvider : CacheProvider<MemcachedCacheConfiguration> {
|
|||||||
val compressionMode = el.getAttribute("compression-mode")
|
val compressionMode = el.getAttribute("compression-mode")
|
||||||
.takeIf(String::isNotEmpty)
|
.takeIf(String::isNotEmpty)
|
||||||
?.let {
|
?.let {
|
||||||
when (it) {
|
when(it) {
|
||||||
"gzip" -> CompressionMode.GZIP
|
"gzip" -> CompressionMode.GZIP
|
||||||
"zip" -> CompressionMode.ZIP
|
"zip" -> CompressionMode.ZIP
|
||||||
else -> CompressionMode.ZIP
|
else -> CompressionMode.ZIP
|
||||||
@@ -62,14 +60,12 @@ class MemcachedCacheProvider : CacheProvider<MemcachedCacheConfiguration> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun serialize(doc: Document, cache: MemcachedCacheConfiguration) = cache.run {
|
override fun serialize(doc: Document, cache : MemcachedCacheConfiguration) = cache.run {
|
||||||
val result = doc.createElement("cache")
|
val result = doc.createElementNS(xmlNamespace,"cache")
|
||||||
Xml.of(doc, result) {
|
Xml.of(doc, result) {
|
||||||
attr("xmlns:${xmlNamespacePrefix}", xmlNamespace, namespaceURI = "http://www.w3.org/2000/xmlns/")
|
attr("xs:type", xmlType, GBCS.XML_SCHEMA_NAMESPACE_URI)
|
||||||
|
|
||||||
attr("xs:type", "${xmlNamespacePrefix}:$xmlType", GBCS.XML_SCHEMA_NAMESPACE_URI)
|
|
||||||
for (server in servers) {
|
for (server in servers) {
|
||||||
node("server") {
|
node("server", xmlNamespace) {
|
||||||
attr("host", server.host)
|
attr("host", server.host)
|
||||||
attr("port", server.port.toString())
|
attr("port", server.port.toString())
|
||||||
}
|
}
|
||||||
@@ -79,12 +75,10 @@ class MemcachedCacheProvider : CacheProvider<MemcachedCacheConfiguration> {
|
|||||||
digestAlgorithm?.let { digestAlgorithm ->
|
digestAlgorithm?.let { digestAlgorithm ->
|
||||||
attr("digest", digestAlgorithm)
|
attr("digest", digestAlgorithm)
|
||||||
}
|
}
|
||||||
attr(
|
attr("compression-mode", when(compressionMode) {
|
||||||
"compression-mode", when (compressionMode) {
|
|
||||||
CompressionMode.GZIP -> "gzip"
|
CompressionMode.GZIP -> "gzip"
|
||||||
CompressionMode.ZIP -> "zip"
|
CompressionMode.ZIP -> "zip"
|
||||||
}
|
})
|
||||||
)
|
|
||||||
}
|
}
|
||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
@@ -20,14 +20,14 @@
|
|||||||
<xs:attribute name="max-age" type="xs:duration" default="P1D"/>
|
<xs:attribute name="max-age" type="xs:duration" default="P1D"/>
|
||||||
<xs:attribute name="max-size" type="xs:unsignedInt" default="1048576"/>
|
<xs:attribute name="max-size" type="xs:unsignedInt" default="1048576"/>
|
||||||
<xs:attribute name="digest" type="xs:token" />
|
<xs:attribute name="digest" type="xs:token" />
|
||||||
<xs:attribute name="compression-mode" type="gbcs-memcached:compressionType" default="zip"/>
|
<xs:attribute name="compression-type" type="gbcs-memcached:compressionType" default="deflate"/>
|
||||||
</xs:extension>
|
</xs:extension>
|
||||||
</xs:complexContent>
|
</xs:complexContent>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
<xs:simpleType name="compressionType">
|
<xs:simpleType name="compressionType">
|
||||||
<xs:restriction base="xs:token">
|
<xs:restriction base="xs:token">
|
||||||
<xs:enumeration value="zip"/>
|
<xs:enumeration value="deflate"/>
|
||||||
<xs:enumeration value="gzip"/>
|
<xs:enumeration value="gzip"/>
|
||||||
</xs:restriction>
|
</xs:restriction>
|
||||||
</xs:simpleType>
|
</xs:simpleType>
|
||||||
|
@@ -31,5 +31,5 @@ include 'gbcs-base'
|
|||||||
include 'gbcs-memcached'
|
include 'gbcs-memcached'
|
||||||
include 'gbcs-cli'
|
include 'gbcs-cli'
|
||||||
include 'docker'
|
include 'docker'
|
||||||
include 'benchmark'
|
|
||||||
|
|
||||||
|
@@ -102,7 +102,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
private class ClientCertificateAuthenticator(
|
private class ClientCertificateAuthenticator(
|
||||||
authorizer: Authorizer,
|
authorizer: Authorizer,
|
||||||
private val sslEngine: SSLEngine,
|
private val sslEngine: SSLEngine,
|
||||||
private val anonymousUserRoles: Set<Role>?,
|
|
||||||
private val userExtractor: Configuration.UserExtractor?,
|
private val userExtractor: Configuration.UserExtractor?,
|
||||||
private val groupExtractor: Configuration.GroupExtractor?,
|
private val groupExtractor: Configuration.GroupExtractor?,
|
||||||
) : AbstractNettyHttpAuthenticator(authorizer) {
|
) : AbstractNettyHttpAuthenticator(authorizer) {
|
||||||
@@ -113,16 +112,16 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
|
|
||||||
override fun authenticate(ctx: ChannelHandlerContext, req: HttpRequest): Set<Role>? {
|
override fun authenticate(ctx: ChannelHandlerContext, req: HttpRequest): Set<Role>? {
|
||||||
return try {
|
return try {
|
||||||
sslEngine.session.peerCertificates.takeIf {
|
sslEngine.session.peerCertificates
|
||||||
|
} catch (es: SSLPeerUnverifiedException) {
|
||||||
|
null
|
||||||
|
}?.takeIf {
|
||||||
it.isNotEmpty()
|
it.isNotEmpty()
|
||||||
}?.let { peerCertificates ->
|
}?.let { peerCertificates ->
|
||||||
val clientCertificate = peerCertificates.first() as X509Certificate
|
val clientCertificate = peerCertificates.first() as X509Certificate
|
||||||
val user = userExtractor?.extract(clientCertificate)
|
val user = userExtractor?.extract(clientCertificate)
|
||||||
val group = groupExtractor?.extract(clientCertificate)
|
val group = groupExtractor?.extract(clientCertificate)
|
||||||
(group?.roles ?: emptySet()) + (user?.roles ?: emptySet())
|
(group?.roles ?: emptySet()) + (user?.roles ?: emptySet())
|
||||||
} ?: anonymousUserRoles
|
|
||||||
} catch (es: SSLPeerUnverifiedException) {
|
|
||||||
anonymousUserRoles
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,21 +139,21 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Missing Authorization header"
|
"Missing Authorization header"
|
||||||
}
|
}
|
||||||
return users[""]?.roles
|
return null
|
||||||
}
|
}
|
||||||
val cursor = authorizationHeader.indexOf(' ')
|
val cursor = authorizationHeader.indexOf(' ')
|
||||||
if (cursor < 0) {
|
if (cursor < 0) {
|
||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Invalid Authorization header: '$authorizationHeader'"
|
"Invalid Authorization header: '$authorizationHeader'"
|
||||||
}
|
}
|
||||||
return users[""]?.roles
|
return null
|
||||||
}
|
}
|
||||||
val authenticationType = authorizationHeader.substring(0, cursor)
|
val authenticationType = authorizationHeader.substring(0, cursor)
|
||||||
if ("Basic" != authenticationType) {
|
if ("Basic" != authenticationType) {
|
||||||
log.debug(ctx) {
|
log.debug(ctx) {
|
||||||
"Invalid authentication type header: '$authenticationType'"
|
"Invalid authentication type header: '$authenticationType'"
|
||||||
}
|
}
|
||||||
return users[""]?.roles
|
return null
|
||||||
}
|
}
|
||||||
val (username, password) = Base64.getDecoder().decode(authorizationHeader.substring(cursor + 1))
|
val (username, password) = Base64.getDecoder().decode(authorizationHeader.substring(cursor + 1))
|
||||||
.let(::String)
|
.let(::String)
|
||||||
@@ -191,7 +190,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
} else {
|
} else {
|
||||||
val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
|
val javaKeyStore = loadKeystore(keyStore.file, keyStore.password)
|
||||||
val serverKey = javaKeyStore.getKey(
|
val serverKey = javaKeyStore.getKey(
|
||||||
keyStore.keyAlias, (keyStore.keyPassword ?: "").let(String::toCharArray)
|
keyStore.keyAlias, keyStore.keyPassword?.let(String::toCharArray)
|
||||||
) as PrivateKey
|
) as PrivateKey
|
||||||
val serverCert: Array<X509Certificate> =
|
val serverCert: Array<X509Certificate> =
|
||||||
Arrays.stream(javaKeyStore.getCertificateChain(keyStore.keyAlias))
|
Arrays.stream(javaKeyStore.getCertificateChain(keyStore.keyAlias))
|
||||||
@@ -200,7 +199,8 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
SslContextBuilder.forServer(serverKey, *serverCert).apply {
|
SslContextBuilder.forServer(serverKey, *serverCert).apply {
|
||||||
if (tls.isVerifyClients) {
|
if (tls.isVerifyClients) {
|
||||||
clientAuth(ClientAuth.OPTIONAL)
|
clientAuth(ClientAuth.OPTIONAL)
|
||||||
tls.trustStore?.let { trustStore ->
|
val trustStore = tls.trustStore
|
||||||
|
if (trustStore != null) {
|
||||||
val ts = loadKeystore(trustStore.file, trustStore.password)
|
val ts = loadKeystore(trustStore.file, trustStore.password)
|
||||||
trustManager(
|
trustManager(
|
||||||
ClientCertificateValidator.getTrustManager(ts, trustStore.isCheckCertificateStatus)
|
ClientCertificateValidator.getTrustManager(ts, trustStore.isCheckCertificateStatus)
|
||||||
@@ -268,17 +268,18 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
val auth = cfg.authentication
|
val auth = cfg.authentication
|
||||||
var authenticator: AbstractNettyHttpAuthenticator? = null
|
var authenticator: AbstractNettyHttpAuthenticator? = null
|
||||||
if (auth is Configuration.BasicAuthentication) {
|
if (auth is Configuration.BasicAuthentication) {
|
||||||
authenticator = (NettyHttpBasicAuthenticator(cfg.users, RoleAuthorizer()))
|
val roleAuthorizer = RoleAuthorizer()
|
||||||
|
authenticator = (NettyHttpBasicAuthenticator(cfg.users, roleAuthorizer))
|
||||||
}
|
}
|
||||||
if (sslContext != null) {
|
if (sslContext != null) {
|
||||||
val sslHandler = sslContext.newHandler(ch.alloc())
|
val sslHandler = sslContext.newHandler(ch.alloc())
|
||||||
pipeline.addLast(sslHandler)
|
pipeline.addLast(sslHandler)
|
||||||
|
|
||||||
if (auth is Configuration.ClientCertificateAuthentication) {
|
if (auth is Configuration.ClientCertificateAuthentication) {
|
||||||
|
val roleAuthorizer = RoleAuthorizer()
|
||||||
authenticator = ClientCertificateAuthenticator(
|
authenticator = ClientCertificateAuthenticator(
|
||||||
RoleAuthorizer(),
|
roleAuthorizer,
|
||||||
sslHandler.engine(),
|
sslHandler.engine(),
|
||||||
cfg.users[""]?.roles,
|
|
||||||
userExtractor(auth),
|
userExtractor(auth),
|
||||||
groupExtractor(auth)
|
groupExtractor(auth)
|
||||||
)
|
)
|
||||||
@@ -445,7 +446,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
|
|
||||||
class ServerHandle(
|
class ServerHandle(
|
||||||
httpChannelFuture: ChannelFuture,
|
httpChannelFuture: ChannelFuture,
|
||||||
private val executorGroups: Iterable<EventExecutorGroup>
|
private val executorGroups : Iterable<EventExecutorGroup>
|
||||||
) : AutoCloseable {
|
) : AutoCloseable {
|
||||||
private val httpChannel: Channel = httpChannelFuture.channel()
|
private val httpChannel: Channel = httpChannelFuture.channel()
|
||||||
|
|
||||||
@@ -475,7 +476,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
val serverSocketChannel = NioServerSocketChannel::class.java
|
val serverSocketChannel = NioServerSocketChannel::class.java
|
||||||
val workerGroup = bossGroup
|
val workerGroup = bossGroup
|
||||||
val eventExecutorGroup = run {
|
val eventExecutorGroup = run {
|
||||||
val threadFactory = if (cfg.isUseVirtualThread) {
|
val threadFactory = if(cfg.isUseVirtualThread) {
|
||||||
Thread.ofVirtual().factory()
|
Thread.ofVirtual().factory()
|
||||||
} else {
|
} else {
|
||||||
null
|
null
|
||||||
|
@@ -1,18 +1,16 @@
|
|||||||
package net.woggioni.gbcs.auth
|
package net.woggioni.gbcs.auth
|
||||||
|
|
||||||
import io.netty.channel.ChannelHandlerContext
|
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter
|
|
||||||
import io.netty.handler.ssl.SslHandler
|
|
||||||
import io.netty.handler.ssl.SslHandshakeCompletionEvent
|
|
||||||
import java.security.KeyStore
|
import java.security.KeyStore
|
||||||
import java.security.cert.CertPathValidator
|
import java.security.cert.CertPathValidator
|
||||||
import java.security.cert.CertPathValidatorException
|
|
||||||
import java.security.cert.CertificateException
|
|
||||||
import java.security.cert.CertificateFactory
|
import java.security.cert.CertificateFactory
|
||||||
import java.security.cert.PKIXParameters
|
import java.security.cert.PKIXParameters
|
||||||
import java.security.cert.PKIXRevocationChecker
|
import java.security.cert.PKIXRevocationChecker
|
||||||
import java.security.cert.X509Certificate
|
import java.security.cert.X509Certificate
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
import io.netty.channel.ChannelHandlerContext
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter
|
||||||
|
import io.netty.handler.ssl.SslHandler
|
||||||
|
import io.netty.handler.ssl.SslHandshakeCompletionEvent
|
||||||
import javax.net.ssl.SSLSession
|
import javax.net.ssl.SSLSession
|
||||||
import javax.net.ssl.TrustManagerFactory
|
import javax.net.ssl.TrustManagerFactory
|
||||||
import javax.net.ssl.X509TrustManager
|
import javax.net.ssl.X509TrustManager
|
||||||
@@ -50,11 +48,7 @@ class ClientCertificateValidator private constructor(
|
|||||||
object : X509TrustManager {
|
object : X509TrustManager {
|
||||||
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
|
override fun checkClientTrusted(chain: Array<out X509Certificate>, authType: String) {
|
||||||
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
|
val clientCertificateChain = certificateFactory.generateCertPath(chain.toList())
|
||||||
try {
|
|
||||||
validator.validate(clientCertificateChain, params)
|
validator.validate(clientCertificateChain, params)
|
||||||
} catch (ex : CertPathValidatorException) {
|
|
||||||
throw CertificateException(ex)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
|
override fun checkServerTrusted(chain: Array<out X509Certificate>, authType: String) {
|
||||||
|
@@ -23,11 +23,10 @@ object Parser {
|
|||||||
|
|
||||||
fun parse(document: Document): Configuration {
|
fun parse(document: Document): Configuration {
|
||||||
val root = document.documentElement
|
val root = document.documentElement
|
||||||
val anonymousUser = User("", null, emptySet())
|
|
||||||
var cache: Cache? = null
|
var cache: Cache? = null
|
||||||
var host = "127.0.0.1"
|
var host = "127.0.0.1"
|
||||||
var port = 11080
|
var port = 11080
|
||||||
var users : Map<String, User> = mapOf(anonymousUser.name to anonymousUser)
|
var users = emptyMap<String, User>()
|
||||||
var groups = emptyMap<String, Group>()
|
var groups = emptyMap<String, Group>()
|
||||||
var tls: Tls? = null
|
var tls: Tls? = null
|
||||||
val serverPath = root.getAttribute("path")
|
val serverPath = root.getAttribute("path")
|
||||||
@@ -36,17 +35,16 @@ object Parser {
|
|||||||
?.let(String::toBoolean) ?: true
|
?.let(String::toBoolean) ?: true
|
||||||
var authentication: Authentication? = null
|
var authentication: Authentication? = null
|
||||||
for (child in root.asIterable()) {
|
for (child in root.asIterable()) {
|
||||||
val tagName = child.localName
|
when (child.localName) {
|
||||||
when (tagName) {
|
|
||||||
"authorization" -> {
|
"authorization" -> {
|
||||||
var knownUsers = sequenceOf(anonymousUser)
|
|
||||||
for (gchild in child.asIterable()) {
|
for (gchild in child.asIterable()) {
|
||||||
when (gchild.localName) {
|
when (child.localName) {
|
||||||
"users" -> {
|
"users" -> {
|
||||||
knownUsers += parseUsers(gchild)
|
users = parseUsers(child)
|
||||||
}
|
}
|
||||||
|
|
||||||
"groups" -> {
|
"groups" -> {
|
||||||
val pair = parseGroups(gchild, knownUsers)
|
val pair = parseGroups(child, users)
|
||||||
users = pair.first
|
users = pair.first
|
||||||
groups = pair.second
|
groups = pair.second
|
||||||
}
|
}
|
||||||
@@ -78,17 +76,17 @@ object Parser {
|
|||||||
"client-certificate" -> {
|
"client-certificate" -> {
|
||||||
var tlsExtractorUser: TlsCertificateExtractor? = null
|
var tlsExtractorUser: TlsCertificateExtractor? = null
|
||||||
var tlsExtractorGroup: TlsCertificateExtractor? = null
|
var tlsExtractorGroup: TlsCertificateExtractor? = null
|
||||||
for (ggchild in gchild.asIterable()) {
|
for (gchild in child.asIterable()) {
|
||||||
when (ggchild.localName) {
|
when (gchild.localName) {
|
||||||
"group-extractor" -> {
|
"group-extractor" -> {
|
||||||
val attrName = ggchild.getAttribute("attribute-name")
|
val attrName = gchild.getAttribute("attribute-name")
|
||||||
val pattern = ggchild.getAttribute("pattern")
|
val pattern = gchild.getAttribute("pattern")
|
||||||
tlsExtractorGroup = TlsCertificateExtractor(attrName, pattern)
|
tlsExtractorGroup = TlsCertificateExtractor(attrName, pattern)
|
||||||
}
|
}
|
||||||
|
|
||||||
"user-extractor" -> {
|
"user-extractor" -> {
|
||||||
val attrName = ggchild.getAttribute("attribute-name")
|
val attrName = gchild.getAttribute("attribute-name")
|
||||||
val pattern = ggchild.getAttribute("pattern")
|
val pattern = gchild.getAttribute("pattern")
|
||||||
tlsExtractorUser = TlsCertificateExtractor(attrName, pattern)
|
tlsExtractorUser = TlsCertificateExtractor(attrName, pattern)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,22 +151,23 @@ object Parser {
|
|||||||
}
|
}
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().map {
|
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().filter {
|
||||||
|
it.localName == "user"
|
||||||
|
}.map {
|
||||||
it.getAttribute("ref")
|
it.getAttribute("ref")
|
||||||
}.toSet()
|
}.toSet()
|
||||||
|
|
||||||
private fun parseUsers(root: Element): Sequence<User> {
|
private fun parseUsers(root: Element): Map<String, User> {
|
||||||
return root.asIterable().asSequence().filter {
|
return root.asIterable().asSequence().filter {
|
||||||
it.localName == "user"
|
it.localName == "user"
|
||||||
}.map { el ->
|
}.map { el ->
|
||||||
val username = el.getAttribute("name")
|
val username = el.getAttribute("name")
|
||||||
val password = el.getAttribute("password").takeIf(String::isNotEmpty)
|
val password = el.getAttribute("password").takeIf(String::isNotEmpty)
|
||||||
User(username, password, emptySet())
|
username to User(username, password, emptySet())
|
||||||
}
|
}.toMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseGroups(root: Element, knownUsers: Sequence<User>): Pair<Map<String, User>, Map<String, Group>> {
|
private fun parseGroups(root: Element, knownUsers: Map<String, User>): Pair<Map<String, User>, Map<String, Group>> {
|
||||||
val knownUsersMap = knownUsers.associateBy(User::getName)
|
|
||||||
val userGroups = mutableMapOf<String, MutableSet<String>>()
|
val userGroups = mutableMapOf<String, MutableSet<String>>()
|
||||||
val groups = root.asIterable().asSequence().filter {
|
val groups = root.asIterable().asSequence().filter {
|
||||||
it.localName == "group"
|
it.localName == "group"
|
||||||
@@ -178,7 +177,7 @@ object Parser {
|
|||||||
for (child in el.asIterable()) {
|
for (child in el.asIterable()) {
|
||||||
when (child.localName) {
|
when (child.localName) {
|
||||||
"users" -> {
|
"users" -> {
|
||||||
parseUserRefs(child).mapNotNull(knownUsersMap::get).forEach { user ->
|
parseUserRefs(child).mapNotNull(knownUsers::get).forEach { user ->
|
||||||
userGroups.computeIfAbsent(user.name) {
|
userGroups.computeIfAbsent(user.name) {
|
||||||
mutableSetOf()
|
mutableSetOf()
|
||||||
}.add(groupName)
|
}.add(groupName)
|
||||||
@@ -192,7 +191,7 @@ object Parser {
|
|||||||
}
|
}
|
||||||
groupName to Group(groupName, roles)
|
groupName to Group(groupName, roles)
|
||||||
}.toMap()
|
}.toMap()
|
||||||
val users = knownUsersMap.map { (name, user) ->
|
val users = knownUsers.map { (name, user) ->
|
||||||
name to User(name, user.password, userGroups[name]?.mapNotNull { groups[it] }?.toSet() ?: emptySet())
|
name to User(name, user.password, userGroups[name]?.mapNotNull { groups[it] }?.toSet() ?: emptySet())
|
||||||
}.toMap()
|
}.toMap()
|
||||||
return users to groups
|
return users to groups
|
||||||
|
@@ -17,7 +17,7 @@ object Serializer {
|
|||||||
attr("useVirtualThreads", conf.isUseVirtualThread.toString())
|
attr("useVirtualThreads", conf.isUseVirtualThread.toString())
|
||||||
// attr("xmlns:xs", GradleBuildCacheServer.XML_SCHEMA_NAMESPACE_URI)
|
// attr("xmlns:xs", GradleBuildCacheServer.XML_SCHEMA_NAMESPACE_URI)
|
||||||
val value = schemaLocations.asSequence().map { (k, v) -> "$k $v" }.joinToString(" ")
|
val value = schemaLocations.asSequence().map { (k, v) -> "$k $v" }.joinToString(" ")
|
||||||
attr("xs:schemaLocation", value , namespaceURI = GBCS.XML_SCHEMA_NAMESPACE_URI)
|
attr("xs:schemaLocation",value , namespaceURI = GBCS.XML_SCHEMA_NAMESPACE_URI)
|
||||||
|
|
||||||
conf.serverPath
|
conf.serverPath
|
||||||
?.takeIf(String::isNotEmpty)
|
?.takeIf(String::isNotEmpty)
|
||||||
@@ -35,7 +35,6 @@ object Serializer {
|
|||||||
node("authorization") {
|
node("authorization") {
|
||||||
node("users") {
|
node("users") {
|
||||||
for(user in conf.users.values) {
|
for(user in conf.users.values) {
|
||||||
if(user.name.isNotEmpty()) {
|
|
||||||
node("user") {
|
node("user") {
|
||||||
attr("name", user.name)
|
attr("name", user.name)
|
||||||
user.password?.let { password ->
|
user.password?.let { password ->
|
||||||
@@ -44,7 +43,6 @@ object Serializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
node("groups") {
|
node("groups") {
|
||||||
val groups = conf.users.values.asSequence()
|
val groups = conf.users.values.asSequence()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
@@ -57,18 +55,10 @@ object Serializer {
|
|||||||
attr("name", group.name)
|
attr("name", group.name)
|
||||||
if(users.isNotEmpty()) {
|
if(users.isNotEmpty()) {
|
||||||
node("users") {
|
node("users") {
|
||||||
var anonymousUser : Configuration.User? = null
|
|
||||||
for(user in users) {
|
for(user in users) {
|
||||||
if(user.name.isNotEmpty()) {
|
|
||||||
node("user") {
|
node("user") {
|
||||||
attr("ref", user.name)
|
attr("ref", user.name)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
anonymousUser = user
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(anonymousUser != null) {
|
|
||||||
node("anonymous")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -92,12 +82,6 @@ object Serializer {
|
|||||||
}
|
}
|
||||||
is Configuration.ClientCertificateAuthentication -> {
|
is Configuration.ClientCertificateAuthentication -> {
|
||||||
node("client-certificate") {
|
node("client-certificate") {
|
||||||
authentication.groupExtractor?.let { extractor ->
|
|
||||||
node("group-extractor") {
|
|
||||||
attr("attribute-name", extractor.rdnType)
|
|
||||||
attr("pattern", extractor.pattern)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
authentication.userExtractor?.let { extractor ->
|
authentication.userExtractor?.let { extractor ->
|
||||||
node("user-extractor") {
|
node("user-extractor") {
|
||||||
attr("attribute-name", extractor.rdnType)
|
attr("attribute-name", extractor.rdnType)
|
||||||
|
@@ -21,6 +21,7 @@
|
|||||||
</xs:keyref>
|
</xs:keyref>
|
||||||
</xs:element>
|
</xs:element>
|
||||||
<xs:element name="authentication" type="gbcs:authenticationType" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="authentication" type="gbcs:authenticationType" minOccurs="0" maxOccurs="1"/>
|
||||||
|
<xs:element name="tls-certificate-authorization" type="gbcs:tlsCertificateAuthorizationType" minOccurs="0" maxOccurs="1"/>
|
||||||
<xs:element name="tls" type="gbcs:tlsType" minOccurs="0" maxOccurs="1"/>
|
<xs:element name="tls" type="gbcs:tlsType" minOccurs="0" maxOccurs="1"/>
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
<xs:attribute name="path" type="xs:string" use="optional"/>
|
<xs:attribute name="path" type="xs:string" use="optional"/>
|
||||||
@@ -127,7 +128,6 @@
|
|||||||
<xs:complexType name="userRefsType">
|
<xs:complexType name="userRefsType">
|
||||||
<xs:sequence>
|
<xs:sequence>
|
||||||
<xs:element name="user" type="gbcs:userRefType" maxOccurs="unbounded" minOccurs="0"/>
|
<xs:element name="user" type="gbcs:userRefType" maxOccurs="unbounded" minOccurs="0"/>
|
||||||
<xs:element name="anonymous" minOccurs="0" maxOccurs="1"/>
|
|
||||||
</xs:sequence>
|
</xs:sequence>
|
||||||
</xs:complexType>
|
</xs:complexType>
|
||||||
|
|
||||||
|
@@ -1,76 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test
|
|
||||||
|
|
||||||
import net.woggioni.gbcs.api.Configuration
|
|
||||||
import net.woggioni.gbcs.api.Role
|
|
||||||
import net.woggioni.gbcs.base.Xml
|
|
||||||
import net.woggioni.gbcs.cache.FileSystemCacheConfiguration
|
|
||||||
import net.woggioni.gbcs.configuration.Serializer
|
|
||||||
import net.woggioni.gbcs.utils.NetworkUtils
|
|
||||||
import java.net.URI
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.time.Duration
|
|
||||||
import java.util.Base64
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
|
|
||||||
abstract class AbstractBasicAuthServerTest : AbstractServerTest() {
|
|
||||||
|
|
||||||
private lateinit var cacheDir : Path
|
|
||||||
|
|
||||||
protected val random = Random(101325)
|
|
||||||
protected val keyValuePair = newEntry(random)
|
|
||||||
protected val serverPath = "gbcs"
|
|
||||||
protected val readersGroup = Configuration.Group("readers", setOf(Role.Reader))
|
|
||||||
protected val writersGroup = Configuration.Group("writers", setOf(Role.Writer))
|
|
||||||
|
|
||||||
abstract protected val users : List<Configuration.User>
|
|
||||||
|
|
||||||
override fun setUp() {
|
|
||||||
this.cacheDir = testDir.resolve("cache")
|
|
||||||
cfg = Configuration(
|
|
||||||
"127.0.0.1",
|
|
||||||
NetworkUtils.getFreePort(),
|
|
||||||
serverPath,
|
|
||||||
users.asSequence().map { it.name to it}.toMap(),
|
|
||||||
sequenceOf(writersGroup, readersGroup).map { it.name to it}.toMap(),
|
|
||||||
FileSystemCacheConfiguration(this.cacheDir,
|
|
||||||
maxAge = Duration.ofSeconds(3600 * 24),
|
|
||||||
digestAlgorithm = "MD5",
|
|
||||||
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
|
||||||
compressionEnabled = false
|
|
||||||
),
|
|
||||||
Configuration.BasicAuthentication(),
|
|
||||||
null,
|
|
||||||
true,
|
|
||||||
)
|
|
||||||
Xml.write(Serializer.serialize(cfg), System.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tearDown() {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun buildAuthorizationHeader(user : Configuration.User, password : String) : String {
|
|
||||||
val b64 = Base64.getEncoder().encode("${user.name}:${password}".toByteArray(Charsets.UTF_8)).let{
|
|
||||||
String(it, StandardCharsets.UTF_8)
|
|
||||||
}
|
|
||||||
return "Basic $b64"
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun newRequestBuilder(key : String) = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create("http://${cfg.host}:${cfg.port}/$serverPath/$key"))
|
|
||||||
|
|
||||||
|
|
||||||
protected fun newEntry(random : Random) : Pair<String, ByteArray> {
|
|
||||||
val key = ByteArray(0x10).let {
|
|
||||||
random.nextBytes(it)
|
|
||||||
Base64.getUrlEncoder().encodeToString(it)
|
|
||||||
}
|
|
||||||
val value = ByteArray(0x1000).also {
|
|
||||||
random.nextBytes(it)
|
|
||||||
}
|
|
||||||
return key to value
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,192 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
|
||||||
import net.woggioni.gbcs.api.Configuration
|
|
||||||
import net.woggioni.gbcs.api.Role
|
|
||||||
import net.woggioni.gbcs.base.Xml
|
|
||||||
import net.woggioni.gbcs.cache.FileSystemCacheConfiguration
|
|
||||||
import net.woggioni.gbcs.configuration.Serializer
|
|
||||||
import net.woggioni.gbcs.utils.CertificateUtils
|
|
||||||
import net.woggioni.gbcs.utils.CertificateUtils.X509Credentials
|
|
||||||
import net.woggioni.gbcs.utils.NetworkUtils
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.Order
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import java.net.URI
|
|
||||||
import java.net.http.HttpClient
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.net.http.HttpResponse
|
|
||||||
import java.nio.charset.StandardCharsets
|
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.security.KeyStore
|
|
||||||
import java.security.KeyStore.PasswordProtection
|
|
||||||
import java.time.Duration
|
|
||||||
import java.util.Base64
|
|
||||||
import java.util.zip.Deflater
|
|
||||||
import javax.net.ssl.KeyManagerFactory
|
|
||||||
import javax.net.ssl.SSLContext
|
|
||||||
import javax.net.ssl.TrustManagerFactory
|
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
|
|
||||||
abstract class AbstractTlsServerTest : AbstractServerTest() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val CA_CERTIFICATE_ENTRY = "gbcs-ca"
|
|
||||||
private const val CLIENT_CERTIFICATE_ENTRY = "gbcs-client"
|
|
||||||
private const val SERVER_CERTIFICATE_ENTRY = "gbcs-server"
|
|
||||||
private const val PASSWORD = "password"
|
|
||||||
}
|
|
||||||
|
|
||||||
private lateinit var cacheDir: Path
|
|
||||||
private lateinit var serverKeyStoreFile: Path
|
|
||||||
private lateinit var clientKeyStoreFile: Path
|
|
||||||
private lateinit var trustStoreFile: Path
|
|
||||||
private lateinit var serverKeyStore: KeyStore
|
|
||||||
private lateinit var clientKeyStore: KeyStore
|
|
||||||
private lateinit var trustStore: KeyStore
|
|
||||||
protected lateinit var ca: X509Credentials
|
|
||||||
|
|
||||||
protected val readersGroup = Configuration.Group("readers", setOf(Role.Reader))
|
|
||||||
protected val writersGroup = Configuration.Group("writers", setOf(Role.Writer))
|
|
||||||
protected val random = Random(101325)
|
|
||||||
protected val keyValuePair = newEntry(random)
|
|
||||||
private val serverPath : String? = null
|
|
||||||
|
|
||||||
protected abstract val users : List<Configuration.User>
|
|
||||||
|
|
||||||
protected fun createKeyStoreAndTrustStore() {
|
|
||||||
ca = CertificateUtils.createCertificateAuthority(CA_CERTIFICATE_ENTRY, 30)
|
|
||||||
val serverCert = CertificateUtils.createServerCertificate(ca, X500Name("CN=$SERVER_CERTIFICATE_ENTRY"), 30)
|
|
||||||
val clientCert = CertificateUtils.createClientCertificate(ca, X500Name("CN=$CLIENT_CERTIFICATE_ENTRY"), 30)
|
|
||||||
|
|
||||||
serverKeyStore = KeyStore.getInstance("PKCS12").apply {
|
|
||||||
load(null, null)
|
|
||||||
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
|
|
||||||
setEntry(
|
|
||||||
SERVER_CERTIFICATE_ENTRY,
|
|
||||||
KeyStore.PrivateKeyEntry(
|
|
||||||
serverCert.keyPair().private,
|
|
||||||
arrayOf(serverCert.certificate(), ca.certificate)
|
|
||||||
),
|
|
||||||
PasswordProtection(PASSWORD.toCharArray())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Files.newOutputStream(this.serverKeyStoreFile).use {
|
|
||||||
serverKeyStore.store(it, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
clientKeyStore = KeyStore.getInstance("PKCS12").apply {
|
|
||||||
load(null, null)
|
|
||||||
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
|
|
||||||
setEntry(
|
|
||||||
CLIENT_CERTIFICATE_ENTRY,
|
|
||||||
KeyStore.PrivateKeyEntry(
|
|
||||||
clientCert.keyPair().private,
|
|
||||||
arrayOf(clientCert.certificate(), ca.certificate)
|
|
||||||
),
|
|
||||||
PasswordProtection(PASSWORD.toCharArray())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
Files.newOutputStream(this.clientKeyStoreFile).use {
|
|
||||||
clientKeyStore.store(it, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
trustStore = KeyStore.getInstance("PKCS12").apply {
|
|
||||||
load(null, null)
|
|
||||||
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
|
|
||||||
}
|
|
||||||
Files.newOutputStream(this.trustStoreFile).use {
|
|
||||||
trustStore.store(it, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun getClientKeyStore(ca: X509Credentials, subject: X500Name) = KeyStore.getInstance("PKCS12").apply {
|
|
||||||
val clientCert = CertificateUtils.createClientCertificate(ca, subject, 30)
|
|
||||||
|
|
||||||
load(null, null)
|
|
||||||
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
|
|
||||||
setEntry(
|
|
||||||
CLIENT_CERTIFICATE_ENTRY,
|
|
||||||
KeyStore.PrivateKeyEntry(clientCert.keyPair().private, arrayOf(clientCert.certificate(), ca.certificate)),
|
|
||||||
PasswordProtection(PASSWORD.toCharArray())
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun getHttpClient(clientKeyStore: KeyStore?): HttpClient {
|
|
||||||
val kmf = clientKeyStore?.let {
|
|
||||||
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply {
|
|
||||||
init(it, PASSWORD.toCharArray())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Set up trust manager factory with the truststore
|
|
||||||
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
|
||||||
tmf.init(trustStore)
|
|
||||||
|
|
||||||
// Create SSL context with the key and trust managers
|
|
||||||
val sslContext = SSLContext.getInstance("TLS").apply {
|
|
||||||
init(kmf?.keyManagers ?: emptyArray(), tmf.trustManagers, null)
|
|
||||||
}
|
|
||||||
return HttpClient.newBuilder().sslContext(sslContext).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun setUp() {
|
|
||||||
this.clientKeyStoreFile = testDir.resolve("client-keystore.p12")
|
|
||||||
this.serverKeyStoreFile = testDir.resolve("server-keystore.p12")
|
|
||||||
this.trustStoreFile = testDir.resolve("truststore.p12")
|
|
||||||
this.cacheDir = testDir.resolve("cache")
|
|
||||||
createKeyStoreAndTrustStore()
|
|
||||||
cfg = Configuration(
|
|
||||||
"127.0.0.1",
|
|
||||||
NetworkUtils.getFreePort(),
|
|
||||||
serverPath,
|
|
||||||
users.asSequence().map { it.name to it }.toMap(),
|
|
||||||
sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(),
|
|
||||||
FileSystemCacheConfiguration(this.cacheDir,
|
|
||||||
maxAge = Duration.ofSeconds(3600 * 24),
|
|
||||||
compressionEnabled = true,
|
|
||||||
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
|
||||||
digestAlgorithm = "MD5"
|
|
||||||
),
|
|
||||||
Configuration.ClientCertificateAuthentication(
|
|
||||||
Configuration.TlsCertificateExtractor("CN", "(.*)"),
|
|
||||||
null
|
|
||||||
),
|
|
||||||
Configuration.Tls(
|
|
||||||
Configuration.KeyStore(this.serverKeyStoreFile, null, SERVER_CERTIFICATE_ENTRY, PASSWORD),
|
|
||||||
Configuration.TrustStore(this.trustStoreFile, null, false),
|
|
||||||
true
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
)
|
|
||||||
Xml.write(Serializer.serialize(cfg), System.out)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun tearDown() {
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun newRequestBuilder(key: String) = HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create("https://${cfg.host}:${cfg.port}/${serverPath ?: ""}/$key"))
|
|
||||||
|
|
||||||
private fun buildAuthorizationHeader(user: Configuration.User, password: String): String {
|
|
||||||
val b64 = Base64.getEncoder().encode("${user.name}:${password}".toByteArray(Charsets.UTF_8)).let {
|
|
||||||
String(it, StandardCharsets.UTF_8)
|
|
||||||
}
|
|
||||||
return "Basic $b64"
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun newEntry(random: Random): Pair<String, ByteArray> {
|
|
||||||
val key = ByteArray(0x10).let {
|
|
||||||
random.nextBytes(it)
|
|
||||||
Base64.getUrlEncoder().encodeToString(it)
|
|
||||||
}
|
|
||||||
val value = ByteArray(0x1000).also {
|
|
||||||
random.nextBytes(it)
|
|
||||||
}
|
|
||||||
return key to value
|
|
||||||
}
|
|
||||||
}
|
|
@@ -4,26 +4,90 @@ import io.netty.handler.codec.http.HttpResponseStatus
|
|||||||
import net.woggioni.gbcs.api.Configuration
|
import net.woggioni.gbcs.api.Configuration
|
||||||
import net.woggioni.gbcs.api.Role
|
import net.woggioni.gbcs.api.Role
|
||||||
import net.woggioni.gbcs.base.PasswordSecurity.hashPassword
|
import net.woggioni.gbcs.base.PasswordSecurity.hashPassword
|
||||||
|
import net.woggioni.gbcs.base.Xml
|
||||||
|
import net.woggioni.gbcs.cache.FileSystemCacheConfiguration
|
||||||
|
import net.woggioni.gbcs.configuration.Serializer
|
||||||
|
import net.woggioni.gbcs.utils.NetworkUtils
|
||||||
import org.junit.jupiter.api.Assertions
|
import org.junit.jupiter.api.Assertions
|
||||||
import org.junit.jupiter.api.Order
|
import org.junit.jupiter.api.Order
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.ServerSocket
|
||||||
|
import java.net.URI
|
||||||
import java.net.http.HttpClient
|
import java.net.http.HttpClient
|
||||||
import java.net.http.HttpRequest
|
import java.net.http.HttpRequest
|
||||||
import java.net.http.HttpResponse
|
import java.net.http.HttpResponse
|
||||||
|
import java.nio.charset.StandardCharsets
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.time.Duration
|
||||||
|
import java.util.Base64
|
||||||
|
import java.util.zip.Deflater
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
class BasicAuthServerTest : AbstractBasicAuthServerTest() {
|
class BasicAuthServerTest : AbstractServerTest() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PASSWORD = "password"
|
private const val PASSWORD = "password"
|
||||||
}
|
}
|
||||||
|
|
||||||
override val users = listOf(
|
private lateinit var cacheDir : Path
|
||||||
|
|
||||||
|
private val random = Random(101325)
|
||||||
|
private val keyValuePair = newEntry(random)
|
||||||
|
private val serverPath = "gbcs"
|
||||||
|
|
||||||
|
override fun setUp() {
|
||||||
|
this.cacheDir = testDir.resolve("cache")
|
||||||
|
val readersGroup = Configuration.Group("readers", setOf(Role.Reader))
|
||||||
|
val writersGroup = Configuration.Group("writers", setOf(Role.Writer))
|
||||||
|
cfg = Configuration(
|
||||||
|
"127.0.0.1",
|
||||||
|
NetworkUtils.getFreePort(),
|
||||||
|
serverPath,
|
||||||
|
listOf(
|
||||||
Configuration.User("user1", hashPassword(PASSWORD), setOf(readersGroup)),
|
Configuration.User("user1", hashPassword(PASSWORD), setOf(readersGroup)),
|
||||||
Configuration.User("user2", hashPassword(PASSWORD), setOf(writersGroup)),
|
Configuration.User("user2", hashPassword(PASSWORD), setOf(writersGroup)),
|
||||||
Configuration.User("user3", hashPassword(PASSWORD), setOf(readersGroup, writersGroup)),
|
Configuration.User("user3", hashPassword(PASSWORD), setOf(readersGroup, writersGroup))
|
||||||
Configuration.User("", null, setOf(readersGroup))
|
).asSequence().map { it.name to it}.toMap(),
|
||||||
|
sequenceOf(writersGroup, readersGroup).map { it.name to it}.toMap(),
|
||||||
|
FileSystemCacheConfiguration(this.cacheDir,
|
||||||
|
maxAge = Duration.ofSeconds(3600 * 24),
|
||||||
|
digestAlgorithm = "MD5",
|
||||||
|
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
||||||
|
compressionEnabled = false
|
||||||
|
),
|
||||||
|
Configuration.BasicAuthentication(),
|
||||||
|
null,
|
||||||
|
true,
|
||||||
)
|
)
|
||||||
|
Xml.write(Serializer.serialize(cfg), System.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tearDown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildAuthorizationHeader(user : Configuration.User, password : String) : String {
|
||||||
|
val b64 = Base64.getEncoder().encode("${user.name}:${password}".toByteArray(Charsets.UTF_8)).let{
|
||||||
|
String(it, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
return "Basic $b64"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newRequestBuilder(key : String) = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create("http://${cfg.host}:${cfg.port}/$serverPath/$key"))
|
||||||
|
|
||||||
|
|
||||||
|
fun newEntry(random : Random) : Pair<String, ByteArray> {
|
||||||
|
val key = ByteArray(0x10).let {
|
||||||
|
random.nextBytes(it)
|
||||||
|
Base64.getUrlEncoder().encodeToString(it)
|
||||||
|
}
|
||||||
|
val value = ByteArray(0x1000).also {
|
||||||
|
random.nextBytes(it)
|
||||||
|
}
|
||||||
|
return key to value
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
@@ -36,7 +100,7 @@ class BasicAuthServerTest : AbstractBasicAuthServerTest() {
|
|||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
||||||
|
|
||||||
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
||||||
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
|
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -115,20 +179,6 @@ class BasicAuthServerTest : AbstractBasicAuthServerTest() {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(6)
|
@Order(6)
|
||||||
fun getAsAnonymousUser() {
|
|
||||||
val client: HttpClient = HttpClient.newHttpClient()
|
|
||||||
val (key, value) = keyValuePair
|
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
|
||||||
.GET()
|
|
||||||
|
|
||||||
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
|
|
||||||
Assertions.assertArrayEquals(value, response.body())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(7)
|
|
||||||
fun getMissingKeyAsAReaderUser() {
|
fun getMissingKeyAsAReaderUser() {
|
||||||
val client: HttpClient = HttpClient.newHttpClient()
|
val client: HttpClient = HttpClient.newHttpClient()
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
package net.woggioni.gbcs.test
|
package net.woggioni.gbcs.test
|
||||||
|
|
||||||
import net.woggioni.gbcs.base.GBCS.toUrl
|
|
||||||
import net.woggioni.gbcs.base.GbcsUrlStreamHandlerFactory
|
import net.woggioni.gbcs.base.GbcsUrlStreamHandlerFactory
|
||||||
|
import net.woggioni.gbcs.base.GBCS.toUrl
|
||||||
import net.woggioni.gbcs.base.Xml
|
import net.woggioni.gbcs.base.Xml
|
||||||
import net.woggioni.gbcs.configuration.Parser
|
import net.woggioni.gbcs.configuration.Parser
|
||||||
import net.woggioni.gbcs.configuration.Serializer
|
import net.woggioni.gbcs.configuration.Serializer
|
||||||
@@ -18,7 +18,6 @@ class ConfigurationTest {
|
|||||||
strings = [
|
strings = [
|
||||||
"classpath:net/woggioni/gbcs/test/gbcs-default.xml",
|
"classpath:net/woggioni/gbcs/test/gbcs-default.xml",
|
||||||
"classpath:net/woggioni/gbcs/test/gbcs-memcached.xml",
|
"classpath:net/woggioni/gbcs/test/gbcs-memcached.xml",
|
||||||
"classpath:net/woggioni/gbcs/test/gbcs-tls.xml",
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
|
@@ -1,53 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
|
||||||
import net.woggioni.gbcs.api.Configuration
|
|
||||||
import net.woggioni.gbcs.api.Role
|
|
||||||
import net.woggioni.gbcs.base.PasswordSecurity.hashPassword
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.Order
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import java.net.http.HttpClient
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.net.http.HttpResponse
|
|
||||||
|
|
||||||
|
|
||||||
class NoAnonymousUserBasicAuthServerTest : AbstractBasicAuthServerTest() {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val PASSWORD = "anotherPassword"
|
|
||||||
}
|
|
||||||
|
|
||||||
override val users = listOf(
|
|
||||||
Configuration.User("user1", hashPassword(PASSWORD), setOf(readersGroup)),
|
|
||||||
Configuration.User("user2", hashPassword(PASSWORD), setOf(writersGroup)),
|
|
||||||
Configuration.User("user3", hashPassword(PASSWORD), setOf(readersGroup, writersGroup)),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
fun putWithNoAuthorizationHeader() {
|
|
||||||
val client: HttpClient = HttpClient.newHttpClient()
|
|
||||||
val (key, value) = keyValuePair
|
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
|
||||||
|
|
||||||
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
fun getWithNoAuthorizationHeader() {
|
|
||||||
val client: HttpClient = HttpClient.newHttpClient()
|
|
||||||
val (key, value) = keyValuePair
|
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
|
||||||
.GET()
|
|
||||||
|
|
||||||
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,47 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus
|
|
||||||
import net.woggioni.gbcs.api.Configuration
|
|
||||||
import org.junit.jupiter.api.Assertions
|
|
||||||
import org.junit.jupiter.api.Order
|
|
||||||
import org.junit.jupiter.api.Test
|
|
||||||
import java.net.http.HttpClient
|
|
||||||
import java.net.http.HttpRequest
|
|
||||||
import java.net.http.HttpResponse
|
|
||||||
|
|
||||||
class NoAnonymousUserTlsServerTest : AbstractTlsServerTest() {
|
|
||||||
|
|
||||||
override val users = listOf(
|
|
||||||
Configuration.User("user1", null, setOf(readersGroup)),
|
|
||||||
Configuration.User("user2", null, setOf(writersGroup)),
|
|
||||||
Configuration.User("user3", null, setOf(readersGroup, writersGroup)),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
fun getAsAnonymousUser() {
|
|
||||||
val (key, _) = keyValuePair
|
|
||||||
val client: HttpClient = getHttpClient(null)
|
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.GET()
|
|
||||||
|
|
||||||
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
fun putAsAnonymousUser() {
|
|
||||||
val (key, value) = keyValuePair
|
|
||||||
val client: HttpClient = getHttpClient(null)
|
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
|
||||||
|
|
||||||
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
|
|
||||||
}
|
|
||||||
}
|
|
@@ -32,17 +32,185 @@ import javax.net.ssl.TrustManagerFactory
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
class TlsServerTest : AbstractTlsServerTest() {
|
class TlsServerTest : AbstractServerTest() {
|
||||||
|
|
||||||
override val users = listOf(
|
companion object {
|
||||||
|
private const val CA_CERTIFICATE_ENTRY = "gbcs-ca"
|
||||||
|
private const val CLIENT_CERTIFICATE_ENTRY = "gbcs-client"
|
||||||
|
private const val SERVER_CERTIFICATE_ENTRY = "gbcs-server"
|
||||||
|
private const val PASSWORD = "password"
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var cacheDir: Path
|
||||||
|
private lateinit var serverKeyStoreFile: Path
|
||||||
|
private lateinit var clientKeyStoreFile: Path
|
||||||
|
private lateinit var trustStoreFile: Path
|
||||||
|
private lateinit var serverKeyStore: KeyStore
|
||||||
|
private lateinit var clientKeyStore: KeyStore
|
||||||
|
private lateinit var trustStore: KeyStore
|
||||||
|
private lateinit var ca: X509Credentials
|
||||||
|
|
||||||
|
private val readersGroup = Configuration.Group("readers", setOf(Role.Reader))
|
||||||
|
private val writersGroup = Configuration.Group("writers", setOf(Role.Writer))
|
||||||
|
private val random = Random(101325)
|
||||||
|
private val keyValuePair = newEntry(random)
|
||||||
|
private val serverPath : String? = null
|
||||||
|
|
||||||
|
private val users = listOf(
|
||||||
Configuration.User("user1", null, setOf(readersGroup)),
|
Configuration.User("user1", null, setOf(readersGroup)),
|
||||||
Configuration.User("user2", null, setOf(writersGroup)),
|
Configuration.User("user2", null, setOf(writersGroup)),
|
||||||
Configuration.User("user3", null, setOf(readersGroup, writersGroup)),
|
Configuration.User("user3", null, setOf(readersGroup, writersGroup))
|
||||||
Configuration.User("", null, setOf(readersGroup))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun createKeyStoreAndTrustStore() {
|
||||||
|
ca = CertificateUtils.createCertificateAuthority(CA_CERTIFICATE_ENTRY, 30)
|
||||||
|
val serverCert = CertificateUtils.createServerCertificate(ca, X500Name("CN=$SERVER_CERTIFICATE_ENTRY"), 30)
|
||||||
|
val clientCert = CertificateUtils.createClientCertificate(ca, X500Name("CN=$CLIENT_CERTIFICATE_ENTRY"), 30)
|
||||||
|
|
||||||
|
serverKeyStore = KeyStore.getInstance("PKCS12").apply {
|
||||||
|
load(null, null)
|
||||||
|
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
|
||||||
|
setEntry(
|
||||||
|
SERVER_CERTIFICATE_ENTRY,
|
||||||
|
KeyStore.PrivateKeyEntry(
|
||||||
|
serverCert.keyPair().private,
|
||||||
|
arrayOf(serverCert.certificate(), ca.certificate)
|
||||||
|
),
|
||||||
|
PasswordProtection(PASSWORD.toCharArray())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Files.newOutputStream(this.serverKeyStoreFile).use {
|
||||||
|
serverKeyStore.store(it, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
clientKeyStore = KeyStore.getInstance("PKCS12").apply {
|
||||||
|
load(null, null)
|
||||||
|
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
|
||||||
|
setEntry(
|
||||||
|
CLIENT_CERTIFICATE_ENTRY,
|
||||||
|
KeyStore.PrivateKeyEntry(
|
||||||
|
clientCert.keyPair().private,
|
||||||
|
arrayOf(clientCert.certificate(), ca.certificate)
|
||||||
|
),
|
||||||
|
PasswordProtection(PASSWORD.toCharArray())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Files.newOutputStream(this.clientKeyStoreFile).use {
|
||||||
|
clientKeyStore.store(it, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
trustStore = KeyStore.getInstance("PKCS12").apply {
|
||||||
|
load(null, null)
|
||||||
|
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
|
||||||
|
}
|
||||||
|
Files.newOutputStream(this.trustStoreFile).use {
|
||||||
|
trustStore.store(it, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getClientKeyStore(ca: X509Credentials, subject: X500Name) = KeyStore.getInstance("PKCS12").apply {
|
||||||
|
val clientCert = CertificateUtils.createClientCertificate(ca, subject, 30)
|
||||||
|
|
||||||
|
load(null, null)
|
||||||
|
setEntry(CA_CERTIFICATE_ENTRY, KeyStore.TrustedCertificateEntry(ca.certificate), PasswordProtection(null))
|
||||||
|
setEntry(
|
||||||
|
CLIENT_CERTIFICATE_ENTRY,
|
||||||
|
KeyStore.PrivateKeyEntry(clientCert.keyPair().private, arrayOf(clientCert.certificate(), ca.certificate)),
|
||||||
|
PasswordProtection(PASSWORD.toCharArray())
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getHttpClient(clientKeyStore: KeyStore?): HttpClient {
|
||||||
|
val kmf = clientKeyStore?.let {
|
||||||
|
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply {
|
||||||
|
init(it, PASSWORD.toCharArray())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Set up trust manager factory with the truststore
|
||||||
|
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||||
|
tmf.init(trustStore)
|
||||||
|
|
||||||
|
// Create SSL context with the key and trust managers
|
||||||
|
val sslContext = SSLContext.getInstance("TLS").apply {
|
||||||
|
init(kmf?.keyManagers ?: emptyArray(), tmf.trustManagers, null)
|
||||||
|
}
|
||||||
|
return HttpClient.newBuilder().sslContext(sslContext).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setUp() {
|
||||||
|
this.clientKeyStoreFile = testDir.resolve("client-keystore.p12")
|
||||||
|
this.serverKeyStoreFile = testDir.resolve("server-keystore.p12")
|
||||||
|
this.trustStoreFile = testDir.resolve("truststore.p12")
|
||||||
|
this.cacheDir = testDir.resolve("cache")
|
||||||
|
createKeyStoreAndTrustStore()
|
||||||
|
cfg = Configuration(
|
||||||
|
"127.0.0.1",
|
||||||
|
NetworkUtils.getFreePort(),
|
||||||
|
serverPath,
|
||||||
|
users.asSequence().map { it.name to it }.toMap(),
|
||||||
|
sequenceOf(writersGroup, readersGroup).map { it.name to it }.toMap(),
|
||||||
|
FileSystemCacheConfiguration(this.cacheDir,
|
||||||
|
maxAge = Duration.ofSeconds(3600 * 24),
|
||||||
|
compressionEnabled = true,
|
||||||
|
compressionLevel = Deflater.DEFAULT_COMPRESSION,
|
||||||
|
digestAlgorithm = "MD5"
|
||||||
|
),
|
||||||
|
Configuration.ClientCertificateAuthentication(
|
||||||
|
Configuration.TlsCertificateExtractor("CN", "(.*)"),
|
||||||
|
null
|
||||||
|
),
|
||||||
|
Configuration.Tls(
|
||||||
|
Configuration.KeyStore(this.serverKeyStoreFile, null, SERVER_CERTIFICATE_ENTRY, PASSWORD),
|
||||||
|
Configuration.TrustStore(this.trustStoreFile, null, false),
|
||||||
|
true
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
Xml.write(Serializer.serialize(cfg), System.out)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun tearDown() {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newRequestBuilder(key: String) = HttpRequest.newBuilder()
|
||||||
|
.uri(URI.create("https://${cfg.host}:${cfg.port}/${serverPath ?: ""}/$key"))
|
||||||
|
|
||||||
|
fun buildAuthorizationHeader(user: Configuration.User, password: String): String {
|
||||||
|
val b64 = Base64.getEncoder().encode("${user.name}:${password}".toByteArray(Charsets.UTF_8)).let {
|
||||||
|
String(it, StandardCharsets.UTF_8)
|
||||||
|
}
|
||||||
|
return "Basic $b64"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newEntry(random: Random): Pair<String, ByteArray> {
|
||||||
|
val key = ByteArray(0x10).let {
|
||||||
|
random.nextBytes(it)
|
||||||
|
Base64.getUrlEncoder().encodeToString(it)
|
||||||
|
}
|
||||||
|
val value = ByteArray(0x1000).also {
|
||||||
|
random.nextBytes(it)
|
||||||
|
}
|
||||||
|
return key to value
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(1)
|
@Order(1)
|
||||||
|
fun putWithNoClientCertificate() {
|
||||||
|
val client: HttpClient = getHttpClient(null)
|
||||||
|
val (key, value) = keyValuePair
|
||||||
|
|
||||||
|
val requestBuilder = newRequestBuilder(key)
|
||||||
|
.header("Content-Type", "application/octet-stream")
|
||||||
|
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
||||||
|
|
||||||
|
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
||||||
|
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Order(2)
|
||||||
fun putAsAReaderUser() {
|
fun putAsAReaderUser() {
|
||||||
val (key, value) = keyValuePair
|
val (key, value) = keyValuePair
|
||||||
val user = cfg.users.values.find {
|
val user = cfg.users.values.find {
|
||||||
@@ -58,7 +226,7 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(2)
|
@Order(3)
|
||||||
fun getAsAWriterUser() {
|
fun getAsAWriterUser() {
|
||||||
val (key, _) = keyValuePair
|
val (key, _) = keyValuePair
|
||||||
val user = cfg.users.values.find {
|
val user = cfg.users.values.find {
|
||||||
@@ -67,6 +235,7 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
|
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
val requestBuilder = newRequestBuilder(key)
|
||||||
|
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
||||||
.GET()
|
.GET()
|
||||||
|
|
||||||
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
||||||
@@ -74,7 +243,7 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(3)
|
@Order(4)
|
||||||
fun putAsAWriterUser() {
|
fun putAsAWriterUser() {
|
||||||
val (key, value) = keyValuePair
|
val (key, value) = keyValuePair
|
||||||
val user = cfg.users.values.find {
|
val user = cfg.users.values.find {
|
||||||
@@ -84,6 +253,7 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
val requestBuilder = newRequestBuilder(key)
|
||||||
.header("Content-Type", "application/octet-stream")
|
.header("Content-Type", "application/octet-stream")
|
||||||
|
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
||||||
|
|
||||||
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
||||||
@@ -91,7 +261,7 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(4)
|
@Order(5)
|
||||||
fun getAsAReaderUser() {
|
fun getAsAReaderUser() {
|
||||||
val (key, value) = keyValuePair
|
val (key, value) = keyValuePair
|
||||||
val user = cfg.users.values.find {
|
val user = cfg.users.values.find {
|
||||||
@@ -100,6 +270,7 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
|
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
val requestBuilder = newRequestBuilder(key)
|
||||||
|
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
||||||
.GET()
|
.GET()
|
||||||
|
|
||||||
val response: HttpResponse<ByteArray> =
|
val response: HttpResponse<ByteArray> =
|
||||||
@@ -109,7 +280,7 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(5)
|
@Order(6)
|
||||||
fun getMissingKeyAsAReaderUser() {
|
fun getMissingKeyAsAReaderUser() {
|
||||||
val (key, _) = newEntry(random)
|
val (key, _) = newEntry(random)
|
||||||
val user = cfg.users.values.find {
|
val user = cfg.users.values.find {
|
||||||
@@ -118,39 +289,11 @@ class TlsServerTest : AbstractTlsServerTest() {
|
|||||||
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
|
val client: HttpClient = getHttpClient(getClientKeyStore(ca, X500Name("CN=${user.name}")))
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
val requestBuilder = newRequestBuilder(key)
|
||||||
|
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
||||||
.GET()
|
.GET()
|
||||||
|
|
||||||
val response: HttpResponse<ByteArray> =
|
val response: HttpResponse<ByteArray> =
|
||||||
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
|
client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
|
||||||
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
|
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(6)
|
|
||||||
fun getAsAnonymousUser() {
|
|
||||||
val (key, value) = keyValuePair
|
|
||||||
val client: HttpClient = getHttpClient(null)
|
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.GET()
|
|
||||||
|
|
||||||
val response: HttpResponse<ByteArray> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray())
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode())
|
|
||||||
Assertions.assertArrayEquals(value, response.body())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(7)
|
|
||||||
fun putAsAnonymousUser() {
|
|
||||||
val (key, value) = keyValuePair
|
|
||||||
val client: HttpClient = getHttpClient(null)
|
|
||||||
|
|
||||||
val requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value))
|
|
||||||
|
|
||||||
val response: HttpResponse<String> = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString())
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode())
|
|
||||||
}
|
|
||||||
}
|
}
|
@@ -4,7 +4,7 @@
|
|||||||
xmlns:gbcs-memcached="urn:net.woggioni.gbcs-memcached"
|
xmlns:gbcs-memcached="urn:net.woggioni.gbcs-memcached"
|
||||||
xs:schemaLocation="urn:net.woggioni.gbcs-memcached jpms://net.woggioni.gbcs.memcached/net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd">
|
xs:schemaLocation="urn:net.woggioni.gbcs-memcached jpms://net.woggioni.gbcs.memcached/net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd">
|
||||||
<bind host="127.0.0.1" port="11443" />
|
<bind host="127.0.0.1" port="11443" />
|
||||||
<cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="101325" digest="SHA-256">
|
<cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" max-size="101325" compression-mode="gzip" digest="SHA-256">
|
||||||
<server host="127.0.0.1" port="11211"/>
|
<server host="127.0.0.1" port="11211"/>
|
||||||
</cache>
|
</cache>
|
||||||
<authentication>
|
<authentication>
|
||||||
|
@@ -1,53 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
|
||||||
<gbcs:server useVirtualThreads="false" xmlns:xs="http://www.w3.org/2001/XMLSchema-instance"
|
|
||||||
xmlns:gbcs="urn:net.woggioni.gbcs"
|
|
||||||
xs:schemaLocation="urn:net.woggioni.gbcs jpms://net.woggioni.gbcs/net/woggioni/gbcs/schema/gbcs.xsd">
|
|
||||||
<bind host="127.0.0.1" port="11443"/>
|
|
||||||
<cache xs:type="gbcs:fileSystemCacheType" path="/tmp/gbcs" max-age="P7D"/>
|
|
||||||
<authorization>
|
|
||||||
<users>
|
|
||||||
<user name="user1" password="password1"/>
|
|
||||||
<user name="user2" password="password2"/>
|
|
||||||
<user name="user3" password="password3"/>
|
|
||||||
</users>
|
|
||||||
<groups>
|
|
||||||
<group name="readers">
|
|
||||||
<users>
|
|
||||||
<user ref="user1"/>
|
|
||||||
<!-- <user ref="user5"/>-->
|
|
||||||
<anonymous/>
|
|
||||||
</users>
|
|
||||||
<roles>
|
|
||||||
<reader/>
|
|
||||||
</roles>
|
|
||||||
</group>
|
|
||||||
<group name="writers">
|
|
||||||
<users>
|
|
||||||
<user ref="user2"/>
|
|
||||||
</users>
|
|
||||||
<roles>
|
|
||||||
<writer/>
|
|
||||||
</roles>
|
|
||||||
</group>
|
|
||||||
<group name="readers-writers">
|
|
||||||
<users>
|
|
||||||
<user ref="user3"/>
|
|
||||||
</users>
|
|
||||||
<roles>
|
|
||||||
<reader/>
|
|
||||||
<writer/>
|
|
||||||
</roles>
|
|
||||||
</group>
|
|
||||||
</groups>
|
|
||||||
</authorization>
|
|
||||||
<authentication>
|
|
||||||
<client-certificate>
|
|
||||||
<group-extractor pattern="group-pattern" attribute-name="O"/>
|
|
||||||
<user-extractor pattern="user-pattern" attribute-name="CN"/>
|
|
||||||
</client-certificate>
|
|
||||||
</authentication>
|
|
||||||
<tls>
|
|
||||||
<keystore file="keystore.pfx" key-alias="key1" password="password" key-password="key-password"/>
|
|
||||||
<truststore file="truststore.pfx" password="password" check-certificate-status="true" />
|
|
||||||
</tls>
|
|
||||||
</gbcs:server>
|
|
Reference in New Issue
Block a user