code simplification
This commit is contained in:
91
build.gradle
91
build.gradle
@@ -103,50 +103,6 @@ tasks.named(JavaPlugin.COMPILE_JAVA_TASK_NAME, JavaCompile) {
|
|||||||
options.javaModuleMainClass = mainClassName
|
options.javaModuleMainClass = mainClassName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//tasks.named(JavaPlugin.COMPILE_TEST_JAVA_TASK_NAME, JavaCompile) {
|
|
||||||
// options.compilerArgs << '--patch-module' << 'net.woggioni.gbcs.test=' + project.sourceSets.test.output.asPath
|
|
||||||
// classpath = configurations.testCompileClasspath + files(tasks.jar.archiveFile)
|
|
||||||
// modularity.inferModulePath = true
|
|
||||||
// javaModuleDetector
|
|
||||||
//}
|
|
||||||
|
|
||||||
//tasks.named(JavaPlugin.TEST_TASK_NAME, JavaForkOptions) {
|
|
||||||
// classpath = configurations.testRuntimeClasspath + project.files(tasks.jar.archiveFile) + project.sourceSets.test.output
|
|
||||||
// jvmArgumentProviders << new CommandLineArgumentProvider() {
|
|
||||||
// @CompileClasspath
|
|
||||||
// def kotlinClassesMain = kotlin.sourceSets.main.collect { it.kotlin.classesDirectory }
|
|
||||||
//
|
|
||||||
// @CompileClasspath
|
|
||||||
// def kotlinClassesTest = kotlin.sourceSets.main.collect { it.kotlin.classesDirectory }
|
|
||||||
|
|
||||||
// @Override
|
|
||||||
// Iterable<String> asArguments() {
|
|
||||||
// return [
|
|
||||||
// "--patch-module",
|
|
||||||
// 'net.woggioni.gbcs=' + kotlinClassesMain.collect { it.get().asFile.absolutePath },
|
|
||||||
// "--patch-module",
|
|
||||||
// 'net.woggioni.gbcs.test=' + project.sourceSets.test.output.asPath,
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
//configurations {
|
|
||||||
// integrationTestImplementation {
|
|
||||||
// attributes {
|
|
||||||
// attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements.class, JAR))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// integrationTestCompileClasspath {
|
|
||||||
// attributes {
|
|
||||||
// attribute(LIBRARY_ELEMENTS_ATTRIBUTE, project.objects.named(LibraryElements.class, JAR))
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
|
|
||||||
envelopeJar {
|
envelopeJar {
|
||||||
mainModule = 'net.woggioni.gbcs'
|
mainModule = 'net.woggioni.gbcs'
|
||||||
mainClass = mainClassName
|
mainClass = mainClassName
|
||||||
@@ -154,37 +110,6 @@ envelopeJar {
|
|||||||
extraClasspath = ["plugins"]
|
extraClasspath = ["plugins"]
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
//testing {
|
|
||||||
// suites {
|
|
||||||
// test {
|
|
||||||
// useJUnitJupiter(catalog.versions.junit.jupiter.get())
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// integrationTest(JvmTestSuite) {
|
|
||||||
// dependencies {
|
|
||||||
// implementation project()
|
|
||||||
// implementation catalog.bcprov.jdk18on
|
|
||||||
// implementation catalog.bcpkix.jdk18on
|
|
||||||
// annotationProcessor catalog.lombok
|
|
||||||
// compileOnly catalog.lombok
|
|
||||||
// implementation project('gbcs-base')
|
|
||||||
// implementation project('gbcs-api')
|
|
||||||
//
|
|
||||||
// runtimeOnly project("gbcs-memcached")
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// targets {
|
|
||||||
// all {
|
|
||||||
// testTask.configure {
|
|
||||||
// shouldRunAfter(test)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation catalog.jwo
|
implementation catalog.jwo
|
||||||
implementation catalog.slf4j.api
|
implementation catalog.slf4j.api
|
||||||
@@ -235,19 +160,3 @@ publishing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//tasks.named('check') {
|
|
||||||
// dependsOn(testing.suites.integrationTest)
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//tasks.named("integrationTest", JavaForkOptions) {
|
|
||||||
// jvmArgumentProviders << new CommandLineArgumentProvider() {
|
|
||||||
// @Override
|
|
||||||
// Iterable<String> asArguments() {
|
|
||||||
// return [
|
|
||||||
// "--patch-module",
|
|
||||||
// 'net.woggioni.gbcs.test=' + project.sourceSets.integrationTest.output.asPath,
|
|
||||||
// ]
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
@@ -2,8 +2,6 @@ package net.woggioni.gbcs.api;
|
|||||||
|
|
||||||
|
|
||||||
import lombok.Value;
|
import lombok.Value;
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.security.cert.X509Certificate;
|
import java.security.cert.X509Certificate;
|
||||||
@@ -106,12 +104,6 @@ public class Configuration {
|
|||||||
String getTypeName();
|
String getTypeName();
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Value
|
|
||||||
// public static class FileSystemCache implements Cache {
|
|
||||||
// Path root;
|
|
||||||
// Duration maxAge;
|
|
||||||
// }
|
|
||||||
|
|
||||||
public static Configuration of(
|
public static Configuration of(
|
||||||
String host,
|
String host,
|
||||||
int port,
|
int port,
|
||||||
|
@@ -1,237 +0,0 @@
|
|||||||
package net.woggioni.gbcs.api;
|
|
||||||
|
|
||||||
import org.w3c.dom.Document;
|
|
||||||
import org.w3c.dom.Element;
|
|
||||||
import org.w3c.dom.Node;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.StreamSupport;
|
|
||||||
|
|
||||||
public class ConfigurationParser {
|
|
||||||
|
|
||||||
public static Configuration parse(Document document) {
|
|
||||||
Element root = document.getDocumentElement();
|
|
||||||
Configuration.Cache cache = null;
|
|
||||||
String host = "127.0.0.1";
|
|
||||||
int port = 11080;
|
|
||||||
Map<String, Configuration.User> users = Collections.emptyMap();
|
|
||||||
Map<String, Configuration.Group> groups = Collections.emptyMap();
|
|
||||||
Configuration.Tls tls = null;
|
|
||||||
String serverPath = root.getAttribute("path");
|
|
||||||
boolean useVirtualThread = !root.getAttribute("useVirtualThreads").isEmpty() &&
|
|
||||||
Boolean.parseBoolean(root.getAttribute("useVirtualThreads"));
|
|
||||||
Configuration.Authentication authentication = null;
|
|
||||||
|
|
||||||
for (Node child : iterableOf(root)) {
|
|
||||||
switch (child.getNodeName()) {
|
|
||||||
case "authorization":
|
|
||||||
for (Node gchild : iterableOf((Element) child)) {
|
|
||||||
switch (gchild.getNodeName()) {
|
|
||||||
case "users":
|
|
||||||
users = parseUsers((Element) gchild);
|
|
||||||
break;
|
|
||||||
case "groups":
|
|
||||||
Map.Entry<Map<String, Configuration.User>, Map<String, Configuration.Group>> pair = parseGroups((Element) gchild, users);
|
|
||||||
users = pair.getKey();
|
|
||||||
groups = pair.getValue();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "bind":
|
|
||||||
Element bindEl = (Element) child;
|
|
||||||
host = bindEl.getAttribute("host");
|
|
||||||
port = Integer.parseInt(bindEl.getAttribute("port"));
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "cache":
|
|
||||||
Element cacheEl = (Element) child;
|
|
||||||
cacheEl.getAttributeNode("xs:type").getSchemaTypeInfo();
|
|
||||||
if ("gbcs:fileSystemCacheType".equals(cacheEl.getAttribute("xs:type"))) {
|
|
||||||
String cacheFolder = cacheEl.getAttribute("path");
|
|
||||||
Path cachePath = !cacheFolder.isEmpty()
|
|
||||||
? Paths.get(cacheFolder)
|
|
||||||
: Paths.get(System.getProperty("user.home")).resolve(".gbcs");
|
|
||||||
|
|
||||||
String maxAgeStr = cacheEl.getAttribute("max-age");
|
|
||||||
Duration maxAge = !maxAgeStr.isEmpty()
|
|
||||||
? Duration.parse(maxAgeStr)
|
|
||||||
: Duration.ofDays(1);
|
|
||||||
|
|
||||||
// cache = new Configuration.FileSystemCache(cachePath, maxAge);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "authentication":
|
|
||||||
for (Node gchild : iterableOf((Element) child)) {
|
|
||||||
switch (gchild.getNodeName()) {
|
|
||||||
case "basic":
|
|
||||||
authentication = new Configuration.BasicAuthentication();
|
|
||||||
break;
|
|
||||||
case "client-certificate":
|
|
||||||
Configuration.TlsCertificateExtractor tlsExtractorUser = null;
|
|
||||||
Configuration.TlsCertificateExtractor tlsExtractorGroup = null;
|
|
||||||
|
|
||||||
for (Node authChild : iterableOf((Element) gchild)) {
|
|
||||||
Element authEl = (Element) authChild;
|
|
||||||
switch (authChild.getNodeName()) {
|
|
||||||
case "group-extractor":
|
|
||||||
String groupAttrName = authEl.getAttribute("attribute-name");
|
|
||||||
String groupPattern = authEl.getAttribute("pattern");
|
|
||||||
tlsExtractorGroup = new Configuration.TlsCertificateExtractor(groupAttrName, groupPattern);
|
|
||||||
break;
|
|
||||||
case "user-extractor":
|
|
||||||
String userAttrName = authEl.getAttribute("attribute-name");
|
|
||||||
String userPattern = authEl.getAttribute("pattern");
|
|
||||||
tlsExtractorUser = new Configuration.TlsCertificateExtractor(userAttrName, userPattern);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
authentication = new Configuration.ClientCertificateAuthentication(tlsExtractorUser, tlsExtractorGroup);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "tls":
|
|
||||||
Element tlsEl = (Element) child;
|
|
||||||
boolean verifyClients = !tlsEl.getAttribute("verify-clients").isEmpty() &&
|
|
||||||
Boolean.parseBoolean(tlsEl.getAttribute("verify-clients"));
|
|
||||||
Configuration.KeyStore keyStore = null;
|
|
||||||
Configuration.TrustStore trustStore = null;
|
|
||||||
|
|
||||||
for (Node gchild : iterableOf(tlsEl)) {
|
|
||||||
Element tlsChild = (Element) gchild;
|
|
||||||
switch (gchild.getNodeName()) {
|
|
||||||
case "keystore":
|
|
||||||
Path keyStoreFile = Paths.get(tlsChild.getAttribute("file"));
|
|
||||||
String keyStorePassword = !tlsChild.getAttribute("password").isEmpty()
|
|
||||||
? tlsChild.getAttribute("password")
|
|
||||||
: null;
|
|
||||||
String keyAlias = tlsChild.getAttribute("key-alias");
|
|
||||||
String keyPassword = !tlsChild.getAttribute("key-password").isEmpty()
|
|
||||||
? tlsChild.getAttribute("key-password")
|
|
||||||
: null;
|
|
||||||
keyStore = new Configuration.KeyStore(keyStoreFile, keyStorePassword, keyAlias, keyPassword);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case "truststore":
|
|
||||||
Path trustStoreFile = Paths.get(tlsChild.getAttribute("file"));
|
|
||||||
String trustStorePassword = !tlsChild.getAttribute("password").isEmpty()
|
|
||||||
? tlsChild.getAttribute("password")
|
|
||||||
: null;
|
|
||||||
boolean checkCertificateStatus = !tlsChild.getAttribute("check-certificate-status").isEmpty() &&
|
|
||||||
Boolean.parseBoolean(tlsChild.getAttribute("check-certificate-status"));
|
|
||||||
trustStore = new Configuration.TrustStore(trustStoreFile, trustStorePassword, checkCertificateStatus);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tls = new Configuration.Tls(keyStore, trustStore, verifyClients);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Configuration.of(host, port, serverPath, users, groups, cache, authentication, tls, useVirtualThread);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<Role> parseRoles(Element root) {
|
|
||||||
return StreamSupport.stream(iterableOf(root).spliterator(), false)
|
|
||||||
.map(node -> switch (node.getNodeName()) {
|
|
||||||
case "reader" -> Role.Reader;
|
|
||||||
case "writer" -> Role.Writer;
|
|
||||||
default -> throw new UnsupportedOperationException("Illegal node '" + node.getNodeName() + "'");
|
|
||||||
})
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Set<String> parseUserRefs(Element root) {
|
|
||||||
return StreamSupport.stream(iterableOf(root).spliterator(), false)
|
|
||||||
.filter(node -> "user".equals(node.getNodeName()))
|
|
||||||
.map(node -> ((Element) node).getAttribute("ref"))
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map<String, Configuration.User> parseUsers(Element root) {
|
|
||||||
return StreamSupport.stream(iterableOf(root).spliterator(), false)
|
|
||||||
.filter(node -> "user".equals(node.getNodeName()))
|
|
||||||
.map(node -> {
|
|
||||||
Element el = (Element) node;
|
|
||||||
String username = el.getAttribute("name");
|
|
||||||
String password = !el.getAttribute("password").isEmpty() ? el.getAttribute("password") : null;
|
|
||||||
return new AbstractMap.SimpleEntry<>(username, new Configuration.User(username, password, Collections.emptySet()));
|
|
||||||
})
|
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Map.Entry<Map<String, Configuration.User>, Map<String, Configuration.Group>> parseGroups(Element root, Map<String, Configuration.User> knownUsers) {
|
|
||||||
Map<String, Set<String>> userGroups = new HashMap<>();
|
|
||||||
Map<String, Configuration.Group> groups = StreamSupport.stream(iterableOf(root).spliterator(), false)
|
|
||||||
.filter(node -> "group".equals(node.getNodeName()))
|
|
||||||
.map(node -> {
|
|
||||||
Element el = (Element) node;
|
|
||||||
String groupName = el.getAttribute("name");
|
|
||||||
Set<Role> roles = Collections.emptySet();
|
|
||||||
|
|
||||||
for (Node child : iterableOf(el)) {
|
|
||||||
switch (child.getNodeName()) {
|
|
||||||
case "users":
|
|
||||||
parseUserRefs((Element) child).stream()
|
|
||||||
.map(knownUsers::get)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.forEach(user ->
|
|
||||||
userGroups.computeIfAbsent(user.getName(), k -> new HashSet<>())
|
|
||||||
.add(groupName));
|
|
||||||
break;
|
|
||||||
case "roles":
|
|
||||||
roles = parseRoles((Element) child);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new AbstractMap.SimpleEntry<>(groupName, new Configuration.Group(groupName, roles));
|
|
||||||
})
|
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
|
||||||
|
|
||||||
Map<String, Configuration.User> users = knownUsers.entrySet().stream()
|
|
||||||
.map(entry -> {
|
|
||||||
String name = entry.getKey();
|
|
||||||
Configuration.User user = entry.getValue();
|
|
||||||
Set<Configuration.Group> userGroupSet = userGroups.getOrDefault(name, Collections.emptySet()).stream()
|
|
||||||
.map(groups::get)
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
return new AbstractMap.SimpleEntry<>(name, new Configuration.User(name, user.getPassword(), userGroupSet));
|
|
||||||
})
|
|
||||||
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
|
||||||
|
|
||||||
return new AbstractMap.SimpleEntry<>(users, groups);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Iterable<Node> iterableOf(Element element) {
|
|
||||||
return () -> new Iterator<Node>() {
|
|
||||||
private Node current = element.getFirstChild();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean hasNext() {
|
|
||||||
while (current != null && !(current instanceof Element)) {
|
|
||||||
current = current.getNextSibling();
|
|
||||||
}
|
|
||||||
return current != null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Node next() {
|
|
||||||
if (!hasNext()) {
|
|
||||||
throw new NoSuchElementException();
|
|
||||||
}
|
|
||||||
Node result = current;
|
|
||||||
current = current.getNextSibling();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
@@ -128,8 +128,6 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
fun getSchema(schema: URL): Schema {
|
fun getSchema(schema: URL): Schema {
|
||||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||||
sf.setFeature(FEATURE_SECURE_PROCESSING, false)
|
sf.setFeature(FEATURE_SECURE_PROCESSING, false)
|
||||||
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
|
||||||
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
|
||||||
sf.errorHandler = ErrorHandler(schema)
|
sf.errorHandler = ErrorHandler(schema)
|
||||||
return sf.newSchema(schema)
|
return sf.newSchema(schema)
|
||||||
}
|
}
|
||||||
@@ -137,15 +135,12 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
fun getSchema(inputStream: InputStream): Schema {
|
fun getSchema(inputStream: InputStream): Schema {
|
||||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||||
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
|
||||||
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
|
||||||
return sf.newSchema(StreamSource(inputStream))
|
return sf.newSchema(StreamSource(inputStream))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun newDocumentBuilderFactory(schemaResourceURL: URL?): DocumentBuilderFactory {
|
fun newDocumentBuilderFactory(schemaResourceURL: URL?): DocumentBuilderFactory {
|
||||||
val dbf = DocumentBuilderFactory.newInstance()
|
val dbf = DocumentBuilderFactory.newInstance()
|
||||||
dbf.setFeature(FEATURE_SECURE_PROCESSING, false)
|
dbf.setFeature(FEATURE_SECURE_PROCESSING, false)
|
||||||
// disableProperty(dbf, ACCESS_EXTERNAL_SCHEMA)
|
|
||||||
dbf.setAttribute(ACCESS_EXTERNAL_SCHEMA, "all")
|
dbf.setAttribute(ACCESS_EXTERNAL_SCHEMA, "all")
|
||||||
disableProperty(dbf, ACCESS_EXTERNAL_DTD)
|
disableProperty(dbf, ACCESS_EXTERNAL_DTD)
|
||||||
dbf.isExpandEntityReferences = true
|
dbf.isExpandEntityReferences = true
|
||||||
@@ -175,32 +170,10 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
return sourceStream?.let(db::parse) ?: sourceURL.openStream().use(db::parse)
|
return sourceStream?.let(db::parse) ?: sourceURL.openStream().use(db::parse)
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
// fun newDocumentBuilder(resource: URL): DocumentBuilder {
|
|
||||||
// val db = newDocumentBuilderFactory(null).newDocumentBuilder()
|
|
||||||
// db.setErrorHandler(XmlErrorHandler(resource))
|
|
||||||
// return db
|
|
||||||
// }
|
|
||||||
|
|
||||||
// fun parseXmlResource(resource: URL): Document {
|
|
||||||
// val db = newDocumentBuilder(resource, null)
|
|
||||||
// return resource.openStream().use(db::parse)
|
|
||||||
// }
|
|
||||||
|
|
||||||
fun write(doc: Document, output: OutputStream) {
|
fun write(doc: Document, output: OutputStream) {
|
||||||
val transformerFactory = TransformerFactory.newInstance()
|
val transformerFactory = TransformerFactory.newInstance()
|
||||||
val transformer = transformerFactory.newTransformer()
|
val transformer = transformerFactory.newTransformer()
|
||||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
|
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
|
||||||
// val domImpl = doc.getImplementation()
|
|
||||||
// val docType = domImpl.createDocumentType(
|
|
||||||
// "plist",
|
|
||||||
// "-//Apple//DTD PLIST 1.0//EN",
|
|
||||||
// "http://www.apple.com/DTDs/PropertyList-1.0.dtd"
|
|
||||||
// )
|
|
||||||
// transformer.setOutputProperty(OutputKeys.DOCTYPE_PUBLIC, docType.getPublicId())
|
|
||||||
// transformer.setOutputProperty(OutputKeys.DOCTYPE_SYSTEM, docType.getSystemId())
|
|
||||||
// val transformerFactory = TransformerFactory.newInstance()
|
|
||||||
// val transformer: Transformer = transformerFactory.newTransformer()
|
|
||||||
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
|
transformer.setOutputProperty(OutputKeys.INDENT, "yes")
|
||||||
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")
|
transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4")
|
||||||
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes")
|
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes")
|
||||||
@@ -230,7 +203,6 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
removeChild(firstChild ?: break)
|
removeChild(firstChild ?: break)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun node(
|
fun node(
|
||||||
@@ -250,15 +222,6 @@ class Xml(val doc: Document, val element: Element) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// fun attrs(vararg attributes: Pair<String, String>) {
|
|
||||||
// for (attr in attributes) element.setAttribute(attr.first, attr.second)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// fun attrs(vararg attributes: Pair<Pair<String?, String>, String>) {
|
|
||||||
// for (attr in attributes) element.setAttributeNS(attr.first.first, attr.first.second, attr.second)
|
|
||||||
// }
|
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
@@ -1,15 +0,0 @@
|
|||||||
open module net.woggioni.gbcs.test {
|
|
||||||
requires net.woggioni.gbcs;
|
|
||||||
requires net.woggioni.gbcs.api;
|
|
||||||
requires java.naming;
|
|
||||||
requires org.bouncycastle.pkix;
|
|
||||||
requires org.bouncycastle.provider;
|
|
||||||
requires io.netty.codec.http;
|
|
||||||
requires net.woggioni.gbcs.base;
|
|
||||||
requires java.net.http;
|
|
||||||
requires static lombok;
|
|
||||||
requires org.junit.jupiter.params;
|
|
||||||
|
|
||||||
exports net.woggioni.gbcs.test to org.junit.platform.commons;
|
|
||||||
// opens net.woggioni.gbcs.test to org.junit.platform.commons;
|
|
||||||
}
|
|
@@ -1,51 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test;
|
|
||||||
|
|
||||||
|
|
||||||
import net.woggioni.gbcs.GradleBuildCacheServer;
|
|
||||||
import net.woggioni.gbcs.api.Configuration;
|
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
|
||||||
import org.junit.jupiter.api.TestInstance;
|
|
||||||
import org.junit.jupiter.api.TestMethodOrder;
|
|
||||||
import org.junit.jupiter.api.MethodOrderer;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
|
|
||||||
public abstract class AbstractServerTest {
|
|
||||||
|
|
||||||
protected Configuration cfg;
|
|
||||||
protected Path testDir;
|
|
||||||
private GradleBuildCacheServer.ServerHandle serverHandle;
|
|
||||||
|
|
||||||
@BeforeAll
|
|
||||||
public void setUp0(@TempDir Path tmpDir) {
|
|
||||||
this.testDir = tmpDir;
|
|
||||||
setUp();
|
|
||||||
startServer(cfg);
|
|
||||||
}
|
|
||||||
|
|
||||||
@AfterAll
|
|
||||||
public void tearDown0() {
|
|
||||||
tearDown();
|
|
||||||
stopServer();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void setUp();
|
|
||||||
|
|
||||||
protected abstract void tearDown();
|
|
||||||
|
|
||||||
private void startServer(Configuration cfg) {
|
|
||||||
this.serverHandle = new GradleBuildCacheServer(cfg).run();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopServer() {
|
|
||||||
if (serverHandle != null) {
|
|
||||||
try (GradleBuildCacheServer.ServerHandle handle = serverHandle) {
|
|
||||||
handle.shutdown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,229 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import net.woggioni.gbcs.AbstractNettyHttpAuthenticator;
|
|
||||||
import net.woggioni.gbcs.api.Role;
|
|
||||||
import net.woggioni.gbcs.base.Xml;
|
|
||||||
import net.woggioni.gbcs.api.Configuration;
|
|
||||||
import net.woggioni.gbcs.cache.FileSystemCacheConfiguration;
|
|
||||||
import net.woggioni.gbcs.configuration.Serializer;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Order;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
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.Path;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.Deflater;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class BasicAuthServerTest extends AbstractServerTest {
|
|
||||||
|
|
||||||
private static final String PASSWORD = "password";
|
|
||||||
private Path cacheDir;
|
|
||||||
private final Random random = new Random(101325);
|
|
||||||
private final Map.Entry<String, byte[]> keyValuePair;
|
|
||||||
|
|
||||||
public BasicAuthServerTest() {
|
|
||||||
this.keyValuePair = newEntry(random);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SneakyThrows
|
|
||||||
protected void setUp() {
|
|
||||||
this.cacheDir = testDir.resolve("cache");
|
|
||||||
Configuration.Group readersGroup = new Configuration.Group("readers", Set.of(Role.Reader));
|
|
||||||
Configuration.Group writersGroup = new Configuration.Group("writers", Set.of(Role.Writer));
|
|
||||||
|
|
||||||
List<Configuration.User> users = Arrays.asList(
|
|
||||||
new Configuration.User("user1", AbstractNettyHttpAuthenticator.Companion.hashPassword(PASSWORD, null), Set.of(readersGroup)),
|
|
||||||
new Configuration.User("user2", AbstractNettyHttpAuthenticator.Companion.hashPassword(PASSWORD, null), Set.of(writersGroup)),
|
|
||||||
new Configuration.User("user3", AbstractNettyHttpAuthenticator.Companion.hashPassword(PASSWORD, null), Set.of(readersGroup, writersGroup))
|
|
||||||
);
|
|
||||||
|
|
||||||
Map<String, Configuration.User> usersMap = users.stream()
|
|
||||||
.collect(Collectors.toMap(user -> user.getName(), user -> user));
|
|
||||||
|
|
||||||
Map<String, Configuration.Group> groupsMap = Stream.of(writersGroup, readersGroup)
|
|
||||||
.collect(Collectors.toMap(group -> group.getName(), group -> group));
|
|
||||||
|
|
||||||
cfg = new Configuration(
|
|
||||||
"127.0.0.1",
|
|
||||||
new ServerSocket(0).getLocalPort() + 1,
|
|
||||||
"/",
|
|
||||||
usersMap,
|
|
||||||
groupsMap,
|
|
||||||
new FileSystemCacheConfiguration(
|
|
||||||
this.cacheDir,
|
|
||||||
Duration.ofSeconds(3600 * 24),
|
|
||||||
"MD5",
|
|
||||||
false,
|
|
||||||
Deflater.DEFAULT_COMPRESSION
|
|
||||||
),
|
|
||||||
new Configuration.BasicAuthentication(),
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
Xml.Companion.write(Serializer.INSTANCE.serialize(cfg), System.out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() {
|
|
||||||
// Empty implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildAuthorizationHeader(Configuration.User user, String password) {
|
|
||||||
String credentials = user.getName() + ":" + password;
|
|
||||||
byte[] encodedCredentials = Base64.getEncoder().encode(credentials.getBytes(StandardCharsets.UTF_8));
|
|
||||||
return "Basic " + new String(encodedCredentials, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequest.Builder newRequestBuilder(String key) {
|
|
||||||
return HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(String.format("http://%s:%d/%s", cfg.getHost(), cfg.getPort(), key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map.Entry<String, byte[]> newEntry(Random random) {
|
|
||||||
byte[] keyBytes = new byte[0x10];
|
|
||||||
random.nextBytes(keyBytes);
|
|
||||||
String key = Base64.getUrlEncoder().encodeToString(keyBytes);
|
|
||||||
|
|
||||||
byte[] value = new byte[0x1000];
|
|
||||||
random.nextBytes(value);
|
|
||||||
|
|
||||||
return Map.entry(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
public void putWithNoAuthorizationHeader() throws IOException, InterruptedException {
|
|
||||||
try(HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value));
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
public void putAsAReaderUser() throws IOException, InterruptedException {
|
|
||||||
try(HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Reader) && !u.getRoles().contains(Role.Writer))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Reader user not found"));
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value));
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(3)
|
|
||||||
public void getAsAWriterUser() throws IOException, InterruptedException {
|
|
||||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Writer))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Writer user not found"));
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.GET();
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(4)
|
|
||||||
public void putAsAWriterUser() throws IOException, InterruptedException {
|
|
||||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Writer))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Writer user not found"));
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value));
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.CREATED.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(5)
|
|
||||||
public void getAsAReaderUser() throws IOException, InterruptedException {
|
|
||||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Reader))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Reader user not found"));
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.GET();
|
|
||||||
|
|
||||||
HttpResponse<byte[]> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode());
|
|
||||||
Assertions.assertArrayEquals(value, response.body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(6)
|
|
||||||
public void getMissingKeyAsAReaderUser() throws IOException, InterruptedException {
|
|
||||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
Map.Entry<String, byte[]> entry = newEntry(random);
|
|
||||||
String key = entry.getKey();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Reader))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Reader user not found"));
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.GET();
|
|
||||||
|
|
||||||
HttpResponse<byte[]> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,48 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test;
|
|
||||||
|
|
||||||
import net.woggioni.gbcs.GradleBuildCacheServer;
|
|
||||||
import net.woggioni.gbcs.base.GBCS;
|
|
||||||
import net.woggioni.gbcs.base.Xml;
|
|
||||||
import net.woggioni.gbcs.configuration.Parser;
|
|
||||||
import net.woggioni.gbcs.configuration.Serializer;
|
|
||||||
import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.io.TempDir;
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
|
||||||
import org.junit.jupiter.params.provider.ValueSource;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
|
|
||||||
class ConfigurationTest {
|
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@ValueSource(strings = {
|
|
||||||
// "classpath:net/woggioni/gbcs/gbcs-default.xml",
|
|
||||||
"classpath:net/woggioni/gbcs/test/gbcs-memcached.xml"
|
|
||||||
})
|
|
||||||
void test(String configurationUrl, @TempDir Path testDir) throws IOException {
|
|
||||||
URL.setURLStreamHandlerFactory(new ClasspathUrlStreamHandlerFactoryProvider());
|
|
||||||
// DocumentBuilderFactory dbf = Xml.newDocumentBuilderFactory(GradleBuildCacheServer.CONFIGURATION_SCHEMA_URL);
|
|
||||||
// DocumentBuilder db = dbf.newDocumentBuilder();
|
|
||||||
// URL configurationUrl = GradleBuildCacheServer.DEFAULT_CONFIGURATION_URL;
|
|
||||||
|
|
||||||
var doc = Xml.Companion.parseXml(GBCS.INSTANCE.toUrl(configurationUrl), null, null);
|
|
||||||
var cfg = Parser.INSTANCE.parse(doc);
|
|
||||||
Path configFile = testDir.resolve("gbcs.xml");
|
|
||||||
|
|
||||||
try (var outputStream = Files.newOutputStream(configFile)) {
|
|
||||||
Xml.Companion.write(Serializer.INSTANCE.serialize(cfg), outputStream);
|
|
||||||
}
|
|
||||||
|
|
||||||
Xml.Companion.write(Serializer.INSTANCE.serialize(cfg), System.out);
|
|
||||||
|
|
||||||
var parsed = Parser.INSTANCE.parse(Xml.Companion.parseXml(
|
|
||||||
configFile.toUri().toURL(), null, null
|
|
||||||
));
|
|
||||||
|
|
||||||
Assertions.assertEquals(cfg, parsed);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,128 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import net.woggioni.gbcs.base.Xml;
|
|
||||||
import net.woggioni.gbcs.api.Configuration;
|
|
||||||
import net.woggioni.gbcs.cache.FileSystemCacheConfiguration;
|
|
||||||
import net.woggioni.gbcs.configuration.Serializer;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Order;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
import java.net.URI;
|
|
||||||
import java.net.http.HttpClient;
|
|
||||||
import java.net.http.HttpRequest;
|
|
||||||
import java.net.http.HttpResponse;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.util.Base64;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Random;
|
|
||||||
import java.util.zip.Deflater;
|
|
||||||
import java.io.IOException;
|
|
||||||
|
|
||||||
public class NoAuthServerTest extends AbstractServerTest {
|
|
||||||
|
|
||||||
private Path cacheDir;
|
|
||||||
private final Random random = new Random(101325);
|
|
||||||
private final Map.Entry<String, byte[]> keyValuePair;
|
|
||||||
|
|
||||||
public NoAuthServerTest() {
|
|
||||||
this.keyValuePair = newEntry(random);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SneakyThrows
|
|
||||||
protected void setUp() {
|
|
||||||
this.cacheDir = testDir.resolve("cache");
|
|
||||||
cfg = new Configuration(
|
|
||||||
"127.0.0.1",
|
|
||||||
new ServerSocket(0).getLocalPort() + 1,
|
|
||||||
"/",
|
|
||||||
Collections.emptyMap(),
|
|
||||||
Collections.emptyMap(),
|
|
||||||
new FileSystemCacheConfiguration(
|
|
||||||
this.cacheDir,
|
|
||||||
Duration.ofSeconds(3600 * 24),
|
|
||||||
"MD5",
|
|
||||||
true,
|
|
||||||
Deflater.DEFAULT_COMPRESSION
|
|
||||||
),
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
Xml.Companion.write(Serializer.INSTANCE.serialize(cfg), System.out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() {
|
|
||||||
// Empty implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequest.Builder newRequestBuilder(String key) {
|
|
||||||
return HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(String.format("http://%s:%d/%s", cfg.getHost(), cfg.getPort(), key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map.Entry<String, byte[]> newEntry(Random random) {
|
|
||||||
byte[] keyBytes = new byte[0x10];
|
|
||||||
random.nextBytes(keyBytes);
|
|
||||||
String key = Base64.getUrlEncoder().encodeToString(keyBytes);
|
|
||||||
|
|
||||||
byte[] value = new byte[0x1000];
|
|
||||||
random.nextBytes(value);
|
|
||||||
|
|
||||||
return Map.entry(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
public void putWithNoAuthorizationHeader() throws IOException, InterruptedException {
|
|
||||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value));
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.CREATED.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
public void getWithNoAuthorizationHeader() throws IOException, InterruptedException {
|
|
||||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.GET();
|
|
||||||
|
|
||||||
HttpResponse<byte[]> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode());
|
|
||||||
Assertions.assertArrayEquals(value, response.body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(3)
|
|
||||||
public void getMissingKey() throws IOException, InterruptedException {
|
|
||||||
try (HttpClient client = HttpClient.newHttpClient()) {
|
|
||||||
|
|
||||||
Map.Entry<String, byte[]> entry = newEntry(random);
|
|
||||||
String key = entry.getKey();
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key).GET();
|
|
||||||
|
|
||||||
HttpResponse<byte[]> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,357 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test;
|
|
||||||
|
|
||||||
import io.netty.handler.codec.http.HttpResponseStatus;
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
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 org.bouncycastle.asn1.x500.X500Name;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Order;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import javax.net.ssl.KeyManagerFactory;
|
|
||||||
import javax.net.ssl.SSLContext;
|
|
||||||
import javax.net.ssl.TrustManagerFactory;
|
|
||||||
import java.net.ServerSocket;
|
|
||||||
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.*;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
import java.util.stream.Stream;
|
|
||||||
import java.util.zip.Deflater;
|
|
||||||
|
|
||||||
public class TlsServerTest extends AbstractServerTest {
|
|
||||||
|
|
||||||
private static final String CA_CERTIFICATE_ENTRY = "gbcs-ca";
|
|
||||||
private static final String CLIENT_CERTIFICATE_ENTRY = "gbcs-client";
|
|
||||||
private static final String SERVER_CERTIFICATE_ENTRY = "gbcs-server";
|
|
||||||
private static final String PASSWORD = "password";
|
|
||||||
|
|
||||||
private Path cacheDir;
|
|
||||||
private Path serverKeyStoreFile;
|
|
||||||
private Path clientKeyStoreFile;
|
|
||||||
private Path trustStoreFile;
|
|
||||||
private KeyStore serverKeyStore;
|
|
||||||
private KeyStore clientKeyStore;
|
|
||||||
private KeyStore trustStore;
|
|
||||||
private X509Credentials ca;
|
|
||||||
|
|
||||||
private final Configuration.Group readersGroup = new Configuration.Group("readers", Set.of(Role.Reader));
|
|
||||||
private final Configuration.Group writersGroup = new Configuration.Group("writers", Set.of(Role.Writer));
|
|
||||||
private final Random random = new Random(101325);
|
|
||||||
private final Map.Entry<String, byte[]> keyValuePair;
|
|
||||||
|
|
||||||
private final List<Configuration.User> users = Arrays.asList(
|
|
||||||
new Configuration.User("user1", null, Set.of(readersGroup)),
|
|
||||||
new Configuration.User("user2", null, Set.of(writersGroup)),
|
|
||||||
new Configuration.User("user3", null, Set.of(readersGroup, writersGroup))
|
|
||||||
);
|
|
||||||
|
|
||||||
public TlsServerTest() {
|
|
||||||
this.keyValuePair = newEntry(random);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createKeyStoreAndTrustStore() throws Exception {
|
|
||||||
ca = CertificateUtils.createCertificateAuthority(CA_CERTIFICATE_ENTRY, 30);
|
|
||||||
var serverCert = CertificateUtils.createServerCertificate(ca, new X500Name("CN=" + SERVER_CERTIFICATE_ENTRY), 30);
|
|
||||||
var clientCert = CertificateUtils.createClientCertificate(ca, new X500Name("CN=" + CLIENT_CERTIFICATE_ENTRY), 30);
|
|
||||||
|
|
||||||
serverKeyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
serverKeyStore.load(null, null);
|
|
||||||
serverKeyStore.setEntry(
|
|
||||||
CA_CERTIFICATE_ENTRY,
|
|
||||||
new KeyStore.TrustedCertificateEntry(ca.certificate()),
|
|
||||||
new PasswordProtection(null)
|
|
||||||
);
|
|
||||||
serverKeyStore.setEntry(
|
|
||||||
SERVER_CERTIFICATE_ENTRY,
|
|
||||||
new KeyStore.PrivateKeyEntry(
|
|
||||||
serverCert.keyPair().getPrivate(),
|
|
||||||
new java.security.cert.Certificate[]{serverCert.certificate(), ca.certificate()}
|
|
||||||
),
|
|
||||||
new PasswordProtection(PASSWORD.toCharArray())
|
|
||||||
);
|
|
||||||
|
|
||||||
try (var out = Files.newOutputStream(this.serverKeyStoreFile)) {
|
|
||||||
serverKeyStore.store(out, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
clientKeyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
clientKeyStore.load(null, null);
|
|
||||||
clientKeyStore.setEntry(
|
|
||||||
CA_CERTIFICATE_ENTRY,
|
|
||||||
new KeyStore.TrustedCertificateEntry(ca.certificate()),
|
|
||||||
new PasswordProtection(null)
|
|
||||||
);
|
|
||||||
clientKeyStore.setEntry(
|
|
||||||
CLIENT_CERTIFICATE_ENTRY,
|
|
||||||
new KeyStore.PrivateKeyEntry(
|
|
||||||
clientCert.keyPair().getPrivate(),
|
|
||||||
new java.security.cert.Certificate[]{clientCert.certificate(), ca.certificate()}
|
|
||||||
),
|
|
||||||
new PasswordProtection(PASSWORD.toCharArray())
|
|
||||||
);
|
|
||||||
|
|
||||||
try (var out = Files.newOutputStream(this.clientKeyStoreFile)) {
|
|
||||||
clientKeyStore.store(out, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
trustStore = KeyStore.getInstance("PKCS12");
|
|
||||||
trustStore.load(null, null);
|
|
||||||
trustStore.setEntry(
|
|
||||||
CA_CERTIFICATE_ENTRY,
|
|
||||||
new KeyStore.TrustedCertificateEntry(ca.certificate()),
|
|
||||||
new PasswordProtection(null)
|
|
||||||
);
|
|
||||||
|
|
||||||
try (var out = Files.newOutputStream(this.trustStoreFile)) {
|
|
||||||
trustStore.store(out, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private KeyStore getClientKeyStore(X509Credentials ca, X500Name subject) throws Exception {
|
|
||||||
var clientCert = CertificateUtils.createClientCertificate(ca, subject, 30);
|
|
||||||
KeyStore keyStore = KeyStore.getInstance("PKCS12");
|
|
||||||
keyStore.load(null, null);
|
|
||||||
keyStore.setEntry(
|
|
||||||
CA_CERTIFICATE_ENTRY,
|
|
||||||
new KeyStore.TrustedCertificateEntry(ca.certificate()),
|
|
||||||
new PasswordProtection(null)
|
|
||||||
);
|
|
||||||
keyStore.setEntry(
|
|
||||||
CLIENT_CERTIFICATE_ENTRY,
|
|
||||||
new KeyStore.PrivateKeyEntry(
|
|
||||||
clientCert.keyPair().getPrivate(),
|
|
||||||
new java.security.cert.Certificate[]{clientCert.certificate(), ca.certificate()}
|
|
||||||
),
|
|
||||||
new PasswordProtection(PASSWORD.toCharArray())
|
|
||||||
);
|
|
||||||
return keyStore;
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpClient getHttpClient(KeyStore clientKeyStore) throws Exception {
|
|
||||||
KeyManagerFactory kmf = null;
|
|
||||||
if (clientKeyStore != null) {
|
|
||||||
kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
|
||||||
kmf.init(clientKeyStore, PASSWORD.toCharArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
|
||||||
tmf.init(trustStore);
|
|
||||||
|
|
||||||
SSLContext sslContext = SSLContext.getInstance("TLS");
|
|
||||||
sslContext.init(
|
|
||||||
kmf != null ? kmf.getKeyManagers() : null,
|
|
||||||
tmf.getTrustManagers(),
|
|
||||||
null
|
|
||||||
);
|
|
||||||
|
|
||||||
return HttpClient.newBuilder().sslContext(sslContext).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SneakyThrows
|
|
||||||
protected void 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();
|
|
||||||
|
|
||||||
Map<String, Configuration.User> usersMap = users.stream()
|
|
||||||
.collect(Collectors.toMap(user -> user.getName(), user -> user));
|
|
||||||
|
|
||||||
Map<String, Configuration.Group> groupsMap = Stream.of(writersGroup, readersGroup)
|
|
||||||
.collect(Collectors.toMap(group -> group.getName(), group -> group));
|
|
||||||
|
|
||||||
cfg = new Configuration(
|
|
||||||
"127.0.0.1",
|
|
||||||
new ServerSocket(0).getLocalPort() + 1,
|
|
||||||
"gbcs",
|
|
||||||
usersMap,
|
|
||||||
groupsMap,
|
|
||||||
new FileSystemCacheConfiguration(
|
|
||||||
this.cacheDir,
|
|
||||||
Duration.ofSeconds(3600 * 24),
|
|
||||||
"MD5",
|
|
||||||
true,
|
|
||||||
Deflater.DEFAULT_COMPRESSION
|
|
||||||
),
|
|
||||||
new Configuration.ClientCertificateAuthentication(
|
|
||||||
new Configuration.TlsCertificateExtractor("CN", "(.*)"),
|
|
||||||
null
|
|
||||||
),
|
|
||||||
new Configuration.Tls(
|
|
||||||
new Configuration.KeyStore(this.serverKeyStoreFile, null, SERVER_CERTIFICATE_ENTRY, PASSWORD),
|
|
||||||
new Configuration.TrustStore(this.trustStoreFile, null, false),
|
|
||||||
true
|
|
||||||
),
|
|
||||||
true
|
|
||||||
);
|
|
||||||
|
|
||||||
Xml.Companion.write(Serializer.INSTANCE.serialize(cfg), System.out);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void tearDown() {
|
|
||||||
// Empty implementation
|
|
||||||
}
|
|
||||||
|
|
||||||
private HttpRequest.Builder newRequestBuilder(String key) {
|
|
||||||
return HttpRequest.newBuilder()
|
|
||||||
.uri(URI.create(String.format("https://%s:%d/%s", cfg.getHost(), cfg.getPort(), key)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildAuthorizationHeader(Configuration.User user, String password) {
|
|
||||||
String credentials = user.getName() + ":" + password;
|
|
||||||
byte[] encodedCredentials = Base64.getEncoder().encode(credentials.getBytes(StandardCharsets.UTF_8));
|
|
||||||
return "Basic " + new String(encodedCredentials, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map.Entry<String, byte[]> newEntry(Random random) {
|
|
||||||
byte[] keyBytes = new byte[0x10];
|
|
||||||
random.nextBytes(keyBytes);
|
|
||||||
String key = Base64.getUrlEncoder().encodeToString(keyBytes);
|
|
||||||
|
|
||||||
byte[] value = new byte[0x1000];
|
|
||||||
random.nextBytes(value);
|
|
||||||
|
|
||||||
return Map.entry(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(1)
|
|
||||||
public void putWithNoClientCertificate() throws Exception {
|
|
||||||
try (HttpClient client = getHttpClient(null)) {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value));
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.UNAUTHORIZED.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(2)
|
|
||||||
public void putAsAReaderUser() throws Exception {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Reader) && !u.getRoles().contains(Role.Writer))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Reader user not found"));
|
|
||||||
|
|
||||||
try (HttpClient client = getHttpClient(getClientKeyStore(ca, new X500Name("CN=" + user.getName())))) {
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value));
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(3)
|
|
||||||
public void getAsAWriterUser() throws Exception {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Writer))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Writer user not found"));
|
|
||||||
|
|
||||||
try (HttpClient client = getHttpClient(getClientKeyStore(ca, new X500Name("CN=" + user.getName())))) {
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.GET();
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.FORBIDDEN.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(4)
|
|
||||||
public void putAsAWriterUser() throws Exception {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Writer))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Writer user not found"));
|
|
||||||
|
|
||||||
try (HttpClient client = getHttpClient(getClientKeyStore(ca, new X500Name("CN=" + user.getName())))) {
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Content-Type", "application/octet-stream")
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.PUT(HttpRequest.BodyPublishers.ofByteArray(value));
|
|
||||||
|
|
||||||
HttpResponse<String> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofString());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.CREATED.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(5)
|
|
||||||
public void getAsAReaderUser() throws Exception {
|
|
||||||
String key = keyValuePair.getKey();
|
|
||||||
byte[] value = keyValuePair.getValue();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Reader))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Reader user not found"));
|
|
||||||
|
|
||||||
try (HttpClient client = getHttpClient(getClientKeyStore(ca, new X500Name("CN=" + user.getName())))) {
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.GET();
|
|
||||||
|
|
||||||
HttpResponse<byte[]> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.OK.code(), response.statusCode());
|
|
||||||
Assertions.assertArrayEquals(value, response.body());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@Order(6)
|
|
||||||
public void getMissingKeyAsAReaderUser() throws Exception {
|
|
||||||
Map.Entry<String, byte[]> entry = newEntry(random);
|
|
||||||
String key = entry.getKey();
|
|
||||||
|
|
||||||
Configuration.User user = cfg.getUsers().values().stream()
|
|
||||||
.filter(u -> u.getRoles().contains(Role.Reader))
|
|
||||||
.findFirst()
|
|
||||||
.orElseThrow(() -> new RuntimeException("Reader user not found"));
|
|
||||||
|
|
||||||
try (HttpClient client = getHttpClient(getClientKeyStore(ca, new X500Name("CN=" + user.getName())))) {
|
|
||||||
HttpRequest.Builder requestBuilder = newRequestBuilder(key)
|
|
||||||
.header("Authorization", buildAuthorizationHeader(user, PASSWORD))
|
|
||||||
.GET();
|
|
||||||
|
|
||||||
HttpResponse<byte[]> response = client.send(requestBuilder.build(), HttpResponse.BodyHandlers.ofByteArray());
|
|
||||||
Assertions.assertEquals(HttpResponseStatus.NOT_FOUND.code(), response.statusCode());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,28 +0,0 @@
|
|||||||
package net.woggioni.gbcs.test;
|
|
||||||
|
|
||||||
import lombok.SneakyThrows;
|
|
||||||
import org.junit.jupiter.api.Assertions;
|
|
||||||
import org.junit.jupiter.api.Test;
|
|
||||||
|
|
||||||
import javax.naming.ldap.LdapName;
|
|
||||||
import javax.naming.ldap.Rdn;
|
|
||||||
import java.util.Objects;
|
|
||||||
|
|
||||||
public class X500NameTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
@SneakyThrows
|
|
||||||
void test() {
|
|
||||||
final var name =
|
|
||||||
"C=SG, L=Bugis, CN=woggioni@f6aa5663ef26, emailAddress=oggioni.walter@gmail.com, street=1 Fraser Street\\, Duo Residences #23-05, postalCode=189350, GN=Walter, SN=Oggioni, pseudonym=woggioni";
|
|
||||||
final var ldapName = new LdapName(name);
|
|
||||||
final var value = ldapName.getRdns()
|
|
||||||
.stream()
|
|
||||||
.filter(it -> Objects.equals("CN", it.getType()))
|
|
||||||
.findFirst()
|
|
||||||
.map(Rdn::getValue)
|
|
||||||
.orElseThrow(Assertions::fail);
|
|
||||||
Assertions.assertEquals("woggioni@f6aa5663ef26", value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@@ -1,227 +0,0 @@
|
|||||||
package net.woggioni.gbcs.utils;
|
|
||||||
|
|
||||||
import org.bouncycastle.asn1.DERSequence;
|
|
||||||
import org.bouncycastle.asn1.x500.X500Name;
|
|
||||||
import org.bouncycastle.asn1.x509.BasicConstraints;
|
|
||||||
import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
|
|
||||||
import org.bouncycastle.asn1.x509.Extension;
|
|
||||||
import org.bouncycastle.asn1.x509.GeneralName;
|
|
||||||
import org.bouncycastle.asn1.x509.GeneralNames;
|
|
||||||
import org.bouncycastle.asn1.x509.KeyPurposeId;
|
|
||||||
import org.bouncycastle.asn1.x509.KeyUsage;
|
|
||||||
import org.bouncycastle.asn1.x509.SubjectAltPublicKeyInfo;
|
|
||||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
|
||||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
|
||||||
import org.bouncycastle.operator.ContentSigner;
|
|
||||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
|
||||||
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.SecureRandom;
|
|
||||||
import java.security.cert.X509Certificate;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.temporal.ChronoUnit;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
public class CertificateUtils {
|
|
||||||
|
|
||||||
public record X509Credentials(
|
|
||||||
KeyPair keyPair,
|
|
||||||
X509Certificate certificate
|
|
||||||
){ }
|
|
||||||
public static class CertificateAuthority {
|
|
||||||
private final PrivateKey privateKey;
|
|
||||||
private final X509Certificate certificate;
|
|
||||||
|
|
||||||
public CertificateAuthority(PrivateKey privateKey, X509Certificate certificate) {
|
|
||||||
this.privateKey = privateKey;
|
|
||||||
this.certificate = certificate;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PrivateKey getPrivateKey() { return privateKey; }
|
|
||||||
public X509Certificate getCertificate() { return certificate; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new Certificate Authority (CA)
|
|
||||||
* @param commonName The CA's common name
|
|
||||||
* @param validityDays How long the CA should be valid for
|
|
||||||
* @return The generated CA containing both private key and certificate
|
|
||||||
*/
|
|
||||||
public static X509Credentials createCertificateAuthority(String commonName, int validityDays)
|
|
||||||
throws Exception {
|
|
||||||
// Generate key pair
|
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
|
||||||
keyPairGenerator.initialize(4096);
|
|
||||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
|
||||||
|
|
||||||
// Prepare certificate data
|
|
||||||
X500Name issuerName = new X500Name("CN=" + commonName);
|
|
||||||
BigInteger serialNumber = new BigInteger(160, new SecureRandom());
|
|
||||||
Instant now = Instant.now();
|
|
||||||
Date startDate = Date.from(now);
|
|
||||||
Date endDate = Date.from(now.plus(validityDays, ChronoUnit.DAYS));
|
|
||||||
|
|
||||||
// Create certificate builder
|
|
||||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
|
||||||
issuerName,
|
|
||||||
serialNumber,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
issuerName,
|
|
||||||
keyPair.getPublic()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add CA extensions
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.basicConstraints,
|
|
||||||
true,
|
|
||||||
new BasicConstraints(true)
|
|
||||||
);
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.keyUsage,
|
|
||||||
true,
|
|
||||||
new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sign the certificate
|
|
||||||
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
|
|
||||||
.build(keyPair.getPrivate());
|
|
||||||
X509Certificate cert = new JcaX509CertificateConverter()
|
|
||||||
.getCertificate(certBuilder.build(signer));
|
|
||||||
|
|
||||||
return new X509Credentials(keyPair, cert);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a server certificate signed by the CA
|
|
||||||
* @param ca The Certificate Authority to sign with
|
|
||||||
* @param subjectName The server's common name
|
|
||||||
* @param validityDays How long the certificate should be valid for
|
|
||||||
* @return KeyPair containing the server's private key and certificate
|
|
||||||
*/
|
|
||||||
public static X509Credentials createServerCertificate(X509Credentials ca, X500Name subjectName, int validityDays)
|
|
||||||
throws Exception {
|
|
||||||
// Generate server key pair
|
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
|
||||||
keyPairGenerator.initialize(2048);
|
|
||||||
KeyPair serverKeyPair = keyPairGenerator.generateKeyPair();
|
|
||||||
|
|
||||||
// Prepare certificate data
|
|
||||||
X500Name issuerName = new X500Name(ca.certificate().getSubjectX500Principal().getName());
|
|
||||||
BigInteger serialNumber = new BigInteger(160, new SecureRandom());
|
|
||||||
Instant now = Instant.now();
|
|
||||||
Date startDate = Date.from(now);
|
|
||||||
Date endDate = Date.from(now.plus(validityDays, ChronoUnit.DAYS));
|
|
||||||
|
|
||||||
// Create certificate builder
|
|
||||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
|
||||||
issuerName,
|
|
||||||
serialNumber,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
subjectName,
|
|
||||||
serverKeyPair.getPublic()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add server certificate extensions
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.basicConstraints,
|
|
||||||
true,
|
|
||||||
new BasicConstraints(false)
|
|
||||||
);
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.keyUsage,
|
|
||||||
true,
|
|
||||||
new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment)
|
|
||||||
);
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.extendedKeyUsage,
|
|
||||||
true,
|
|
||||||
new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_serverAuth})
|
|
||||||
);
|
|
||||||
GeneralNames subjectAltNames = GeneralNames.getInstance(
|
|
||||||
new DERSequence(
|
|
||||||
new GeneralName[] {
|
|
||||||
new GeneralName(GeneralName.iPAddress, "127.0.0.1")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.subjectAlternativeName,
|
|
||||||
true,
|
|
||||||
subjectAltNames
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sign the certificate
|
|
||||||
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
|
|
||||||
.build(ca.keyPair().getPrivate());
|
|
||||||
X509Certificate cert = new JcaX509CertificateConverter()
|
|
||||||
.getCertificate(certBuilder.build(signer));
|
|
||||||
|
|
||||||
|
|
||||||
return new X509Credentials(serverKeyPair, cert);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a client certificate signed by the CA
|
|
||||||
* @param ca The Certificate Authority to sign with
|
|
||||||
* @param subjectName The client's common name
|
|
||||||
* @param validityDays How long the certificate should be valid for
|
|
||||||
* @return KeyPair containing the client's private key and certificate
|
|
||||||
*/
|
|
||||||
public static X509Credentials createClientCertificate(X509Credentials ca, X500Name subjectName, int validityDays)
|
|
||||||
throws Exception {
|
|
||||||
// Generate client key pair
|
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
|
||||||
keyPairGenerator.initialize(2048);
|
|
||||||
KeyPair clientKeyPair = keyPairGenerator.generateKeyPair();
|
|
||||||
|
|
||||||
// Prepare certificate data
|
|
||||||
X500Name issuerName = new X500Name(ca.certificate().getSubjectX500Principal().getName());
|
|
||||||
BigInteger serialNumber = new BigInteger(160, new SecureRandom());
|
|
||||||
Instant now = Instant.now();
|
|
||||||
Date startDate = Date.from(now);
|
|
||||||
Date endDate = Date.from(now.plus(validityDays, ChronoUnit.DAYS));
|
|
||||||
|
|
||||||
// Create certificate builder
|
|
||||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
|
||||||
issuerName,
|
|
||||||
serialNumber,
|
|
||||||
startDate,
|
|
||||||
endDate,
|
|
||||||
subjectName,
|
|
||||||
clientKeyPair.getPublic()
|
|
||||||
);
|
|
||||||
|
|
||||||
// Add client certificate extensions
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.basicConstraints,
|
|
||||||
true,
|
|
||||||
new BasicConstraints(false)
|
|
||||||
);
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.keyUsage,
|
|
||||||
true,
|
|
||||||
new KeyUsage(KeyUsage.digitalSignature)
|
|
||||||
);
|
|
||||||
certBuilder.addExtension(
|
|
||||||
Extension.extendedKeyUsage,
|
|
||||||
true,
|
|
||||||
new ExtendedKeyUsage(new KeyPurposeId[]{KeyPurposeId.id_kp_clientAuth})
|
|
||||||
);
|
|
||||||
|
|
||||||
// Sign the certificate
|
|
||||||
ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA")
|
|
||||||
.build(ca.keyPair().getPrivate());
|
|
||||||
X509Certificate cert = new JcaX509CertificateConverter()
|
|
||||||
.getCertificate(certBuilder.build(signer));
|
|
||||||
|
|
||||||
return new X509Credentials(clientKeyPair, cert);
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,13 +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"
|
|
||||||
xmlns:gbcs-memcached="urn:net.woggioni.gbcs-memcached"
|
|
||||||
xs:schemaLocation="urn:net.woggioni.gbcs-memcached classpath:net/woggioni/gbcs/memcached/schema/gbcs-memcached.xsd urn:net.woggioni.gbcs classpath:net/woggioni/gbcs/schema/gbcs.xsd">
|
|
||||||
<bind host="127.0.0.1" port="11443" />
|
|
||||||
<cache xs:type="gbcs-memcached:memcachedCacheType" max-age="P7D" digest="MD5">
|
|
||||||
<server host="127.0.0.1" port="11211"/>
|
|
||||||
</cache>
|
|
||||||
<authentication>
|
|
||||||
<none/>
|
|
||||||
</authentication>
|
|
||||||
</gbcs:server>
|
|
@@ -3,12 +3,6 @@ import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider;
|
|||||||
import net.woggioni.gbcs.cache.FileSystemCacheProvider;
|
import net.woggioni.gbcs.cache.FileSystemCacheProvider;
|
||||||
|
|
||||||
open module net.woggioni.gbcs {
|
open module net.woggioni.gbcs {
|
||||||
// exports net.woggioni.gbcs.cache to net.woggioni.gbcs.test;
|
|
||||||
// exports net.woggioni.gbcs.configuration to net.woggioni.gbcs.test;
|
|
||||||
// exports net.woggioni.gbcs.url to net.woggioni.gbcs.test;
|
|
||||||
// exports net.woggioni.gbcs to net.woggioni.gbcs.test;
|
|
||||||
// opens net.woggioni.gbcs.schema to net.woggioni.gbcs.test;
|
|
||||||
|
|
||||||
requires java.sql;
|
requires java.sql;
|
||||||
requires java.xml;
|
requires java.xml;
|
||||||
requires java.logging;
|
requires java.logging;
|
||||||
@@ -25,12 +19,8 @@ open module net.woggioni.gbcs {
|
|||||||
requires net.woggioni.gbcs.base;
|
requires net.woggioni.gbcs.base;
|
||||||
requires net.woggioni.gbcs.api;
|
requires net.woggioni.gbcs.api;
|
||||||
|
|
||||||
// exports net.woggioni.gbcs;
|
|
||||||
// exports net.woggioni.gbcs.url;
|
|
||||||
// opens net.woggioni.gbcs to net.woggioni.envelope;
|
|
||||||
provides java.net.URLStreamHandlerFactory with ClasspathUrlStreamHandlerFactoryProvider;
|
provides java.net.URLStreamHandlerFactory with ClasspathUrlStreamHandlerFactoryProvider;
|
||||||
uses java.net.URLStreamHandlerFactory;
|
uses java.net.URLStreamHandlerFactory;
|
||||||
// uses net.woggioni.gbcs.api.Cache;
|
|
||||||
uses CacheProvider;
|
uses CacheProvider;
|
||||||
|
|
||||||
provides CacheProvider with FileSystemCacheProvider;
|
provides CacheProvider with FileSystemCacheProvider;
|
||||||
|
@@ -61,14 +61,6 @@ abstract class AbstractNettyHttpAuthenticator(private val authorizer : Authorize
|
|||||||
return String(Base64.getEncoder().encode(concat(hash, actualSalt)))
|
return String(Base64.getEncoder().encode(concat(hash, actualSalt)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// fun decodePasswordHash(passwordHash : String) : Pair<String, String> {
|
|
||||||
// return passwordHash.indexOf(':')
|
|
||||||
// .takeIf { it > 0 }
|
|
||||||
// ?.let { sep ->
|
|
||||||
// passwordHash.substring(0, sep) to passwordHash.substring(sep)
|
|
||||||
// } ?: throw IllegalArgumentException("Failed to decode password hash")
|
|
||||||
// }
|
|
||||||
|
|
||||||
fun decodePasswordHash(passwordHash : String) : Pair<ByteArray, ByteArray> {
|
fun decodePasswordHash(passwordHash : String) : Pair<ByteArray, ByteArray> {
|
||||||
val decoded = Base64.getDecoder().decode(passwordHash)
|
val decoded = Base64.getDecoder().decode(passwordHash)
|
||||||
val hash = ByteArray(KEY_LENGTH / 8)
|
val hash = ByteArray(KEY_LENGTH / 8)
|
||||||
|
@@ -46,9 +46,8 @@ import net.woggioni.gbcs.api.Cache
|
|||||||
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.api.exception.ContentTooLargeException
|
import net.woggioni.gbcs.api.exception.ContentTooLargeException
|
||||||
import net.woggioni.gbcs.base.GBCS
|
|
||||||
import net.woggioni.gbcs.base.Xml
|
|
||||||
import net.woggioni.gbcs.base.GBCS.toUrl
|
import net.woggioni.gbcs.base.GBCS.toUrl
|
||||||
|
import net.woggioni.gbcs.base.Xml
|
||||||
import net.woggioni.gbcs.base.contextLogger
|
import net.woggioni.gbcs.base.contextLogger
|
||||||
import net.woggioni.gbcs.base.debug
|
import net.woggioni.gbcs.base.debug
|
||||||
import net.woggioni.gbcs.base.info
|
import net.woggioni.gbcs.base.info
|
||||||
@@ -76,7 +75,6 @@ import java.util.regex.Pattern
|
|||||||
import javax.naming.ldap.LdapName
|
import javax.naming.ldap.LdapName
|
||||||
import javax.net.ssl.SSLEngine
|
import javax.net.ssl.SSLEngine
|
||||||
import javax.net.ssl.SSLPeerUnverifiedException
|
import javax.net.ssl.SSLPeerUnverifiedException
|
||||||
import kotlin.io.path.absolute
|
|
||||||
|
|
||||||
|
|
||||||
class GradleBuildCacheServer(private val cfg: Configuration) {
|
class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||||
@@ -540,7 +538,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadConfiguration(args: Array<String>): Configuration {
|
fun loadConfiguration(args: Array<String>): Configuration {
|
||||||
// registerUrlProtocolHandler()
|
|
||||||
// Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
|
// Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
|
||||||
val app = Application.builder("gbcs")
|
val app = Application.builder("gbcs")
|
||||||
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
||||||
|
@@ -1,309 +0,0 @@
|
|||||||
package net.woggioni.gbcs.configuration
|
|
||||||
|
|
||||||
import net.woggioni.gbcs.api.Role
|
|
||||||
import java.nio.file.Path
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.time.Duration
|
|
||||||
|
|
||||||
@ConsistentCopyVisibility
|
|
||||||
data class Configuration private constructor(
|
|
||||||
val host: String,
|
|
||||||
val port: Int,
|
|
||||||
val serverPath: String?,
|
|
||||||
val users: Map<String, User>,
|
|
||||||
val groups: Map<String, Group>,
|
|
||||||
val cache: Cache,
|
|
||||||
val authentication : Authentication?,
|
|
||||||
val tls: Tls?,
|
|
||||||
val useVirtualThread: Boolean
|
|
||||||
) {
|
|
||||||
|
|
||||||
data class Group(val name: String, val roles: Set<Role>) {
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return name.hashCode()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
data class User(val name: String, val password: String?, val groups: Set<Group>) {
|
|
||||||
override fun hashCode(): Int {
|
|
||||||
return name.hashCode()
|
|
||||||
}
|
|
||||||
|
|
||||||
val roles : Set<Role>
|
|
||||||
get() = groups.asSequence().flatMap { it.roles }.toSet()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun interface UserExtractor {
|
|
||||||
fun extract(cert :X509Certificate) : User
|
|
||||||
}
|
|
||||||
|
|
||||||
fun interface GroupExtractor {
|
|
||||||
fun extract(cert :X509Certificate) : Group
|
|
||||||
}
|
|
||||||
|
|
||||||
data class Tls(
|
|
||||||
val keyStore: KeyStore?,
|
|
||||||
val trustStore: TrustStore?,
|
|
||||||
val verifyClients: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
data class KeyStore(
|
|
||||||
val file: Path,
|
|
||||||
val password: String?,
|
|
||||||
val keyAlias: String,
|
|
||||||
val keyPassword: String?
|
|
||||||
)
|
|
||||||
|
|
||||||
data class TrustStore(
|
|
||||||
val file: Path,
|
|
||||||
val password: String?,
|
|
||||||
val checkCertificateStatus: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
data class TlsCertificateExtractor(val rdnType : String, val pattern : String)
|
|
||||||
|
|
||||||
interface Authentication
|
|
||||||
|
|
||||||
class BasicAuthentication : Authentication
|
|
||||||
|
|
||||||
data class ClientCertificateAuthentication(
|
|
||||||
val userExtractor: TlsCertificateExtractor?,
|
|
||||||
val groupExtractor: TlsCertificateExtractor?) : Authentication
|
|
||||||
|
|
||||||
|
|
||||||
interface Cache
|
|
||||||
|
|
||||||
data class FileSystemCache(val root: Path, val maxAge: Duration) : Cache
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
|
|
||||||
fun of(
|
|
||||||
host: String,
|
|
||||||
port: Int,
|
|
||||||
serverPath: String?,
|
|
||||||
users: Map<String, User>,
|
|
||||||
groups: Map<String, Group>,
|
|
||||||
cache: Cache,
|
|
||||||
authentication : Authentication?,
|
|
||||||
tls: Tls?,
|
|
||||||
useVirtualThread: Boolean
|
|
||||||
) = Configuration(
|
|
||||||
host,
|
|
||||||
port,
|
|
||||||
serverPath?.takeIf { it.isNotEmpty() && it != "/" },
|
|
||||||
users,
|
|
||||||
groups,
|
|
||||||
cache,
|
|
||||||
authentication,
|
|
||||||
tls,
|
|
||||||
useVirtualThread
|
|
||||||
)
|
|
||||||
|
|
||||||
// fun parse(document: Document): Configuration {
|
|
||||||
// val cacheSerializers = ServiceLoader.load(Configuration::class.java.module.layer, CacheSerializer::class.java)
|
|
||||||
// .asSequence()
|
|
||||||
// .map {
|
|
||||||
// "${it.xmlType}:${it.xmlNamespace}" to it
|
|
||||||
// }.toMap()
|
|
||||||
// val root = document.documentElement
|
|
||||||
// var cache: Cache? = null
|
|
||||||
// var host = "127.0.0.1"
|
|
||||||
// var port = 11080
|
|
||||||
// var users = emptyMap<String, User>()
|
|
||||||
// var groups = emptyMap<String, Group>()
|
|
||||||
// var tls: Tls? = null
|
|
||||||
// val serverPath = root.getAttribute("path")
|
|
||||||
// val useVirtualThread = root.getAttribute("useVirtualThreads")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(String::toBoolean) ?: false
|
|
||||||
// var authentication : Authentication? = null
|
|
||||||
// for (child in root.asIterable()) {
|
|
||||||
// when (child.nodeName) {
|
|
||||||
// "authorization" -> {
|
|
||||||
// for (gchild in child.asIterable()) {
|
|
||||||
// when (child.nodeName) {
|
|
||||||
// "users" -> {
|
|
||||||
// users = parseUsers(child)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// "groups" -> {
|
|
||||||
// val pair = parseGroups(child, users)
|
|
||||||
// users = pair.first
|
|
||||||
// groups = pair.second
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// "bind" -> {
|
|
||||||
// host = child.getAttribute("host")
|
|
||||||
// port = Integer.parseInt(child.getAttribute("port"))
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// "cache" -> {
|
|
||||||
// val type = child.getAttribute("xs:type")
|
|
||||||
// val serializer = cacheSerializers.get(type) ?: throw NotImplementedError()
|
|
||||||
// cache = serializer.deserialize(child)
|
|
||||||
//
|
|
||||||
// when(child.getAttribute("xs:type")) {
|
|
||||||
// "gbcs:fileSystemCacheType" -> {
|
|
||||||
// val cacheFolder = child.getAttribute("path")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(Paths::get)
|
|
||||||
// ?: Paths.get(System.getProperty("user.home")).resolve(".gbcs")
|
|
||||||
// val maxAge = child.getAttribute("max-age")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(Duration::parse)
|
|
||||||
// ?: Duration.ofDays(1)
|
|
||||||
// cache = FileSystemCache(cacheFolder, maxAge)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//// for (gchild in child.asIterable()) {
|
|
||||||
//// when (gchild.nodeName) {
|
|
||||||
//// "file-system-cache" -> {
|
|
||||||
//// val cacheFolder = gchild.getAttribute("path")
|
|
||||||
//// .takeIf(String::isNotEmpty)
|
|
||||||
//// ?.let(Paths::get)
|
|
||||||
//// ?: Paths.get(System.getProperty("user.home")).resolve(".gbcs")
|
|
||||||
//// val maxAge = gchild.getAttribute("max-age")
|
|
||||||
//// .takeIf(String::isNotEmpty)
|
|
||||||
//// ?.let(Duration::parse)
|
|
||||||
//// ?: Duration.ofDays(1)
|
|
||||||
//// cache = FileSystemCache(cacheFolder, maxAge)
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
//// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// "authentication" -> {
|
|
||||||
// for (gchild in child.asIterable()) {
|
|
||||||
// when (gchild.nodeName) {
|
|
||||||
// "basic" -> {
|
|
||||||
// authentication = BasicAuthentication()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// "client-certificate" -> {
|
|
||||||
// var tlsExtractorUser : TlsCertificateExtractor? = null
|
|
||||||
// var tlsExtractorGroup : TlsCertificateExtractor? = null
|
|
||||||
// for (gchild in child.asIterable()) {
|
|
||||||
// when (gchild.nodeName) {
|
|
||||||
// "group-extractor" -> {
|
|
||||||
// val attrName = gchild.getAttribute("attribute-name")
|
|
||||||
// val pattern = gchild.getAttribute("pattern")
|
|
||||||
// tlsExtractorGroup = TlsCertificateExtractor(attrName, pattern)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// "user-extractor" -> {
|
|
||||||
// val attrName = gchild.getAttribute("attribute-name")
|
|
||||||
// val pattern = gchild.getAttribute("pattern")
|
|
||||||
// tlsExtractorUser = TlsCertificateExtractor(attrName, pattern)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// authentication = ClientCertificateAuthentication(tlsExtractorUser, tlsExtractorGroup)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// "tls" -> {
|
|
||||||
// val verifyClients = child.getAttribute("verify-clients")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(String::toBoolean) ?: false
|
|
||||||
// var keyStore: KeyStore? = null
|
|
||||||
// var trustStore: TrustStore? = null
|
|
||||||
// for (granChild in child.asIterable()) {
|
|
||||||
// when (granChild.nodeName) {
|
|
||||||
// "keystore" -> {
|
|
||||||
// val keyStoreFile = Paths.get(granChild.getAttribute("file"))
|
|
||||||
// val keyStorePassword = granChild.getAttribute("password")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// val keyAlias = granChild.getAttribute("key-alias")
|
|
||||||
// val keyPassword = granChild.getAttribute("key-password")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// keyStore = KeyStore(
|
|
||||||
// keyStoreFile,
|
|
||||||
// keyStorePassword,
|
|
||||||
// keyAlias,
|
|
||||||
// keyPassword
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// "truststore" -> {
|
|
||||||
// val trustStoreFile = Paths.get(granChild.getAttribute("file"))
|
|
||||||
// val trustStorePassword = granChild.getAttribute("password")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// val checkCertificateStatus = granChild.getAttribute("check-certificate-status")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(String::toBoolean)
|
|
||||||
// ?: false
|
|
||||||
// trustStore = TrustStore(
|
|
||||||
// trustStoreFile,
|
|
||||||
// trustStorePassword,
|
|
||||||
// checkCertificateStatus
|
|
||||||
// )
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// tls = Tls(keyStore, trustStore, verifyClients)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return of(host, port, serverPath, users, groups, cache!!, authentication, tls, useVirtualThread)
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private fun parseRoles(root: Element) = root.asIterable().asSequence().map {
|
|
||||||
// when (it.nodeName) {
|
|
||||||
// "reader" -> Role.Reader
|
|
||||||
// "writer" -> Role.Writer
|
|
||||||
// else -> throw UnsupportedOperationException("Illegal node '${it.nodeName}'")
|
|
||||||
// }
|
|
||||||
// }.toSet()
|
|
||||||
//
|
|
||||||
// private fun parseUserRefs(root: Element) = root.asIterable().asSequence().filter {
|
|
||||||
// it.nodeName == "user"
|
|
||||||
// }.map {
|
|
||||||
// it.getAttribute("ref")
|
|
||||||
// }.toSet()
|
|
||||||
//
|
|
||||||
// private fun parseUsers(root: Element): Map<String, User> {
|
|
||||||
// return root.asIterable().asSequence().filter {
|
|
||||||
// it.nodeName == "user"
|
|
||||||
// }.map { el ->
|
|
||||||
// val username = el.getAttribute("name")
|
|
||||||
// val password = el.getAttribute("password").takeIf(String::isNotEmpty)
|
|
||||||
// username to User(username, password, emptySet())
|
|
||||||
// }.toMap()
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// private fun parseGroups(root: Element, knownUsers : Map<String, User>): Pair<Map<String, User>, Map<String, Group>> {
|
|
||||||
// val userGroups = mutableMapOf<String, MutableSet<String>>()
|
|
||||||
// val groups = root.asIterable().asSequence().filter {
|
|
||||||
// it.nodeName == "group"
|
|
||||||
// }.map { el ->
|
|
||||||
// val groupName = el.getAttribute("name")
|
|
||||||
// var roles = emptySet<Role>()
|
|
||||||
// for (child in el.asIterable()) {
|
|
||||||
// when (child.nodeName) {
|
|
||||||
// "users" -> {
|
|
||||||
// parseUserRefs(child).mapNotNull(knownUsers::get).forEach { user ->
|
|
||||||
// userGroups.computeIfAbsent(user.name) {
|
|
||||||
// mutableSetOf()
|
|
||||||
// }.add(groupName)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// "roles" -> {
|
|
||||||
// roles = parseRoles(child)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// groupName to Group(groupName, roles)
|
|
||||||
// }.toMap()
|
|
||||||
// val users = knownUsers.map { (name, user) ->
|
|
||||||
// name to User(name, user.password, userGroups[name]?.mapNotNull { groups[it] }?.toSet() ?: emptySet())
|
|
||||||
// }.toMap()
|
|
||||||
// return users to groups
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
@@ -16,6 +16,7 @@ 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 org.w3c.dom.TypeInfo
|
import org.w3c.dom.TypeInfo
|
||||||
|
import java.lang.IllegalArgumentException
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
object Parser {
|
object Parser {
|
||||||
@@ -57,45 +58,12 @@ object Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"cache" -> {
|
"cache" -> {
|
||||||
// val type = child.getAttribute("xs:type").split(":")
|
cache = (child as TypeInfo).let { tf ->
|
||||||
// val namespaceURI = child.lookupNamespaceURI(type[0])
|
|
||||||
// val typeName = type[1]
|
|
||||||
cache = (child as? TypeInfo)?.let { tf ->
|
|
||||||
val typeNamespace = tf.typeNamespace
|
val typeNamespace = tf.typeNamespace
|
||||||
val typeName = tf.typeName
|
val typeName = tf.typeName
|
||||||
CacheSerializers.index[typeNamespace to typeName]
|
CacheSerializers.index[typeNamespace to typeName]
|
||||||
}?.deserialize(child) ?: throw NotImplementedError()
|
?: throw IllegalArgumentException("Cache provider for namespace '$typeNamespace' not found")
|
||||||
|
}.deserialize(child)
|
||||||
// cache = serializer.deserialize(child)
|
|
||||||
|
|
||||||
// when(child.getAttribute("xs:type")) {
|
|
||||||
// "gbcs:fileSystemCacheType" -> {
|
|
||||||
// val cacheFolder = child.getAttribute("path")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(Paths::get)
|
|
||||||
// ?: Paths.get(System.getProperty("user.home")).resolve(".gbcs")
|
|
||||||
// val maxAge = child.getAttribute("max-age")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(Duration::parse)
|
|
||||||
// ?: Duration.ofDays(1)
|
|
||||||
// cache = FileSystemCache(cacheFolder, maxAge)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// for (gchild in child.asIterable()) {
|
|
||||||
// when (gchild.localName) {
|
|
||||||
// "file-system-cache" -> {
|
|
||||||
// val cacheFolder = gchild.getAttribute("path")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(Paths::get)
|
|
||||||
// ?: Paths.get(System.getProperty("user.home")).resolve(".gbcs")
|
|
||||||
// val maxAge = gchild.getAttribute("max-age")
|
|
||||||
// .takeIf(String::isNotEmpty)
|
|
||||||
// ?.let(Duration::parse)
|
|
||||||
// ?: Duration.ofDays(1)
|
|
||||||
// cache = FileSystemCache(cacheFolder, maxAge)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"authentication" -> {
|
"authentication" -> {
|
||||||
|
@@ -4,7 +4,6 @@ import net.woggioni.gbcs.GradleBuildCacheServer
|
|||||||
import net.woggioni.gbcs.api.Configuration
|
import net.woggioni.gbcs.api.Configuration
|
||||||
import org.junit.jupiter.api.AfterAll
|
import org.junit.jupiter.api.AfterAll
|
||||||
import org.junit.jupiter.api.BeforeAll
|
import org.junit.jupiter.api.BeforeAll
|
||||||
import org.junit.jupiter.api.ClassOrderer
|
|
||||||
import org.junit.jupiter.api.MethodOrderer
|
import org.junit.jupiter.api.MethodOrderer
|
||||||
import org.junit.jupiter.api.TestInstance
|
import org.junit.jupiter.api.TestInstance
|
||||||
import org.junit.jupiter.api.TestMethodOrder
|
import org.junit.jupiter.api.TestMethodOrder
|
||||||
@@ -14,7 +13,7 @@ import java.nio.file.Path
|
|||||||
|
|
||||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||||
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
@TestMethodOrder(MethodOrderer.OrderAnnotation::class)
|
||||||
abstract class AbstractServerTestKt {
|
abstract class AbstractServerTest {
|
||||||
|
|
||||||
protected lateinit var cfg : Configuration
|
protected lateinit var cfg : Configuration
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ import java.util.zip.Deflater
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
class BasicAuthServerTestKt : AbstractServerTestKt() {
|
class BasicAuthServerTest : AbstractServerTest() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PASSWORD = "password"
|
private const val PASSWORD = "password"
|
||||||
|
@@ -9,17 +9,10 @@ import org.junit.jupiter.api.Assertions
|
|||||||
import org.junit.jupiter.api.io.TempDir
|
import org.junit.jupiter.api.io.TempDir
|
||||||
import org.junit.jupiter.params.ParameterizedTest
|
import org.junit.jupiter.params.ParameterizedTest
|
||||||
import org.junit.jupiter.params.provider.ValueSource
|
import org.junit.jupiter.params.provider.ValueSource
|
||||||
import java.net.URL
|
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
|
||||||
class ConfigurationTestKt {
|
class ConfigurationTest {
|
||||||
|
|
||||||
// companion object {
|
|
||||||
// init {
|
|
||||||
// URL.setURLStreamHandlerFactory(ClasspathUrlStreamHandlerFactoryProvider())
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@ValueSource(
|
@ValueSource(
|
||||||
strings = [
|
strings = [
|
||||||
|
@@ -20,7 +20,7 @@ import java.util.zip.Deflater
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
class NoAuthServerTestKt : AbstractServerTestKt() {
|
class NoAuthServerTest : AbstractServerTest() {
|
||||||
|
|
||||||
private lateinit var cacheDir : Path
|
private lateinit var cacheDir : Path
|
||||||
|
|
||||||
|
@@ -31,16 +31,13 @@ import javax.net.ssl.TrustManagerFactory
|
|||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
|
||||||
class TlsServerTestKt : AbstractServerTestKt() {
|
class TlsServerTest : AbstractServerTest() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val CA_CERTIFICATE_ENTRY = "gbcs-ca"
|
private const val CA_CERTIFICATE_ENTRY = "gbcs-ca"
|
||||||
private const val CLIENT_CERTIFICATE_ENTRY = "gbcs-client"
|
private const val CLIENT_CERTIFICATE_ENTRY = "gbcs-client"
|
||||||
private const val SERVER_CERTIFICATE_ENTRY = "gbcs-server"
|
private const val SERVER_CERTIFICATE_ENTRY = "gbcs-server"
|
||||||
private const val PASSWORD = "password"
|
private const val PASSWORD = "password"
|
||||||
|
|
||||||
// private fun stripLeadingSlash(s : String) = Path.of("/").root.relativize(Path.of(s).normalize()).toString()
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private lateinit var cacheDir: Path
|
private lateinit var cacheDir: Path
|
||||||
|
@@ -4,7 +4,7 @@ import org.junit.jupiter.api.Assertions
|
|||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
import javax.naming.ldap.LdapName
|
import javax.naming.ldap.LdapName
|
||||||
|
|
||||||
class X500NameTestKt {
|
class X500NameTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun test() {
|
fun test() {
|
||||||
|
Reference in New Issue
Block a user