forked from woggioni/rbcs
temporary commit
This commit is contained in:
@@ -11,6 +11,7 @@ import io.netty.handler.codec.http.HttpRequest
|
||||
import io.netty.handler.codec.http.HttpResponseStatus
|
||||
import io.netty.handler.codec.http.HttpVersion
|
||||
import io.netty.util.ReferenceCountUtil
|
||||
import net.woggioni.gbcs.api.Role
|
||||
import java.security.SecureRandom
|
||||
import java.security.spec.KeySpec
|
||||
import java.util.Base64
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import io.netty.handler.codec.http.HttpRequest
|
||||
import net.woggioni.gbcs.api.Role
|
||||
|
||||
fun interface Authorizer {
|
||||
fun authorize(roles : Set<Role>, request: HttpRequest) : Boolean
|
||||
|
||||
@@ -42,22 +42,30 @@ import io.netty.handler.stream.ChunkedNioStream
|
||||
import io.netty.handler.stream.ChunkedWriteHandler
|
||||
import io.netty.util.concurrent.DefaultEventExecutorGroup
|
||||
import io.netty.util.concurrent.EventExecutorGroup
|
||||
import net.woggioni.gbcs.cache.Cache
|
||||
import net.woggioni.gbcs.cache.FileSystemCache
|
||||
import net.woggioni.gbcs.configuration.Configuration
|
||||
import net.woggioni.gbcs.api.Cache
|
||||
import net.woggioni.gbcs.api.Configuration
|
||||
import net.woggioni.gbcs.api.Role
|
||||
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.contextLogger
|
||||
import net.woggioni.gbcs.base.debug
|
||||
import net.woggioni.gbcs.base.info
|
||||
import net.woggioni.gbcs.configuration.Parser
|
||||
import net.woggioni.gbcs.configuration.Serializer
|
||||
import net.woggioni.gbcs.url.ClasspathUrlStreamHandlerFactoryProvider
|
||||
import net.woggioni.jwo.Application
|
||||
import net.woggioni.jwo.JWO
|
||||
import net.woggioni.jwo.Tuple2
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.net.InetSocketAddress
|
||||
import java.net.URI
|
||||
import java.net.URL
|
||||
import java.net.URLStreamHandlerFactory
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyStore
|
||||
import java.security.MessageDigest
|
||||
import java.security.PrivateKey
|
||||
import java.security.cert.X509Certificate
|
||||
import java.util.Arrays
|
||||
@@ -68,6 +76,7 @@ import java.util.regex.Pattern
|
||||
import javax.naming.ldap.LdapName
|
||||
import javax.net.ssl.SSLEngine
|
||||
import javax.net.ssl.SSLPeerUnverifiedException
|
||||
import kotlin.io.path.absolute
|
||||
|
||||
|
||||
class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
@@ -107,7 +116,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
override fun authenticate(ctx: ChannelHandlerContext, req: HttpRequest): Set<Role>? {
|
||||
return try {
|
||||
sslEngine.session.peerCertificates
|
||||
} catch (es : SSLPeerUnverifiedException) {
|
||||
} catch (es: SSLPeerUnverifiedException) {
|
||||
null
|
||||
}?.takeIf {
|
||||
it.isNotEmpty()
|
||||
@@ -187,13 +196,13 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
.map { it as X509Certificate }
|
||||
.toArray { size -> Array<X509Certificate?>(size) { null } }
|
||||
SslContextBuilder.forServer(serverKey, *serverCert).apply {
|
||||
if (tls.verifyClients) {
|
||||
if (tls.isVerifyClients) {
|
||||
clientAuth(ClientAuth.OPTIONAL)
|
||||
val trustStore = tls.trustStore
|
||||
if (trustStore != null) {
|
||||
val ts = loadKeystore(trustStore.file, trustStore.password)
|
||||
trustManager(
|
||||
ClientCertificateValidator.getTrustManager(ts, trustStore.checkCertificateStatus)
|
||||
ClientCertificateValidator.getTrustManager(ts, trustStore.isCheckCertificateStatus)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -259,7 +268,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
override fun initChannel(ch: Channel) {
|
||||
val pipeline = ch.pipeline()
|
||||
val auth = cfg.authentication
|
||||
var authenticator : AbstractNettyHttpAuthenticator? = null
|
||||
var authenticator: AbstractNettyHttpAuthenticator? = null
|
||||
if (auth is Configuration.BasicAuthentication) {
|
||||
val roleAuthorizer = RoleAuthorizer()
|
||||
authenticator = (NettyHttpBasicAuthenticator(cfg.users, roleAuthorizer))
|
||||
@@ -268,7 +277,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
val sslHandler = sslContext.newHandler(ch.alloc())
|
||||
pipeline.addLast(sslHandler)
|
||||
|
||||
if(auth is Configuration.ClientCertificateAuthentication) {
|
||||
if (auth is Configuration.ClientCertificateAuthentication) {
|
||||
val roleAuthorizer = RoleAuthorizer()
|
||||
authenticator = ClientCertificateAuthenticator(
|
||||
roleAuthorizer,
|
||||
@@ -282,16 +291,12 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
pipeline.addLast(HttpChunkContentCompressor(1024))
|
||||
pipeline.addLast(ChunkedWriteHandler())
|
||||
pipeline.addLast(HttpObjectAggregator(Int.MAX_VALUE))
|
||||
authenticator?.let{
|
||||
authenticator?.let {
|
||||
pipeline.addLast(it)
|
||||
}
|
||||
val cacheImplementation = when(val cache = cfg.cache) {
|
||||
is Configuration.FileSystemCache -> {
|
||||
FileSystemCache(cache.root, cache.maxAge)
|
||||
}
|
||||
else -> throw NotImplementedError()
|
||||
}
|
||||
pipeline.addLast(group, ServerHandler(cacheImplementation, cfg.serverPath))
|
||||
val cacheImplementation = cfg.cache.materialize()
|
||||
val prefix = Path.of("/").resolve(Path.of(cfg.serverPath ?: "/"))
|
||||
pipeline.addLast(group, ServerHandler(cacheImplementation, prefix))
|
||||
pipeline.addLast(ExceptionHandler())
|
||||
}
|
||||
}
|
||||
@@ -305,6 +310,12 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
|
||||
}
|
||||
|
||||
private val TOO_BIG: FullHttpResponse = DefaultFullHttpResponse(
|
||||
HttpVersion.HTTP_1_1, HttpResponseStatus.REQUEST_ENTITY_TOO_LARGE, Unpooled.EMPTY_BUFFER
|
||||
).apply {
|
||||
headers()[HttpHeaderNames.CONTENT_LENGTH] = "0"
|
||||
}
|
||||
|
||||
override fun exceptionCaught(ctx: ChannelHandlerContext, cause: Throwable) {
|
||||
when (cause) {
|
||||
is DecoderException -> {
|
||||
@@ -317,6 +328,11 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
|
||||
}
|
||||
|
||||
is ContentTooLargeException -> {
|
||||
ctx.writeAndFlush(TOO_BIG.retainedDuplicate())
|
||||
.addListener(ChannelFutureListener.CLOSE_ON_FAILURE)
|
||||
}
|
||||
|
||||
else -> {
|
||||
log.error(cause.message, cause)
|
||||
ctx.close()
|
||||
@@ -325,7 +341,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
}
|
||||
}
|
||||
|
||||
private class ServerHandler(private val cache: Cache, private val serverPrefix: String?) :
|
||||
private class ServerHandler(private val cache: Cache, private val serverPrefix: Path) :
|
||||
SimpleChannelInboundHandler<FullHttpRequest>() {
|
||||
|
||||
companion object {
|
||||
@@ -335,7 +351,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
val uri = req.uri()
|
||||
val i = uri.lastIndexOf('/')
|
||||
if (i < 0) throw RuntimeException(String.format("Malformed request URI: '%s'", uri))
|
||||
return uri.substring(0, i).takeIf(String::isNotEmpty) to uri.substring(i + 1)
|
||||
return uri.substring(0, i) to uri.substring(i + 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -343,9 +359,12 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
val keepAlive: Boolean = HttpUtil.isKeepAlive(msg)
|
||||
val method = msg.method()
|
||||
if (method === HttpMethod.GET) {
|
||||
val (prefix, key) = splitPath(msg)
|
||||
// val (prefix, key) = splitPath(msg)
|
||||
val path = Path.of(msg.uri())
|
||||
val prefix = path.parent
|
||||
val key = path.fileName.toString()
|
||||
if (serverPrefix == prefix) {
|
||||
cache.get(digestString(key.toByteArray()))?.let { channel ->
|
||||
cache.get(key)?.let { channel ->
|
||||
log.debug(ctx) {
|
||||
"Cache hit for key '$key'"
|
||||
}
|
||||
@@ -369,6 +388,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
.addListener(ChannelFutureListener.CLOSE)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
ctx.write(ChunkedNioStream(channel))
|
||||
ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT)
|
||||
@@ -391,13 +411,24 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
ctx.writeAndFlush(response)
|
||||
}
|
||||
} else if (method === HttpMethod.PUT) {
|
||||
val (prefix, key) = splitPath(msg)
|
||||
val path = Path.of(msg.uri())
|
||||
val prefix = path.parent
|
||||
val key = path.fileName.toString()
|
||||
|
||||
if (serverPrefix == prefix) {
|
||||
log.debug(ctx) {
|
||||
"Added value for key '$key' to build cache"
|
||||
}
|
||||
val content = msg.content()
|
||||
cache.put(digestString(key.toByteArray()), content)
|
||||
val bodyBytes = msg.content().run {
|
||||
if (isDirect) {
|
||||
ByteArray(readableBytes()).also {
|
||||
readBytes(it)
|
||||
}
|
||||
} else {
|
||||
array()
|
||||
}
|
||||
}
|
||||
cache.put(key, bodyBytes)
|
||||
val response = DefaultFullHttpResponse(
|
||||
msg.protocolVersion(), HttpResponseStatus.CREATED,
|
||||
Unpooled.copiedBuffer(key.toByteArray())
|
||||
@@ -453,7 +484,7 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
// Create the multithreaded event loops for the server
|
||||
val bossGroup = NioEventLoopGroup()
|
||||
val serverSocketChannel = NioServerSocketChannel::class.java
|
||||
val workerGroup = if (cfg.useVirtualThread) {
|
||||
val workerGroup = if (cfg.isUseVirtualThread) {
|
||||
NioEventLoopGroup(0, Executors.newVirtualThreadPerTaskExecutor())
|
||||
} else {
|
||||
NioEventLoopGroup(0, Executors.newWorkStealingPool())
|
||||
@@ -477,8 +508,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
|
||||
companion object {
|
||||
|
||||
private fun String.toUrl() : URL = URL.of(URI(this), null)
|
||||
|
||||
private val log by lazy {
|
||||
contextLogger()
|
||||
}
|
||||
@@ -486,9 +515,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
private const val PROTOCOL_HANDLER = "java.protocol.handler.pkgs"
|
||||
private const val HANDLERS_PACKAGE = "net.woggioni.gbcs.url"
|
||||
|
||||
val CONFIGURATION_SCHEMA_URL by lazy {
|
||||
"classpath:net/woggioni/gbcs/gbcs.xsd".toUrl()
|
||||
}
|
||||
val DEFAULT_CONFIGURATION_URL by lazy { "classpath:net/woggioni/gbcs/gbcs-default.xml".toUrl() }
|
||||
|
||||
/**
|
||||
@@ -515,7 +541,6 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
|
||||
fun loadConfiguration(args: Array<String>): Configuration {
|
||||
// registerUrlProtocolHandler()
|
||||
URL.setURLStreamHandlerFactory(ClasspathUrlStreamHandlerFactoryProvider())
|
||||
// Thread.currentThread().contextClassLoader = GradleBuildCacheServer::class.java.classLoader
|
||||
val app = Application.builder("gbcs")
|
||||
.configurationDirectoryEnvVar("GBCS_CONFIGURATION_DIR")
|
||||
@@ -524,6 +549,9 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
val confDir = app.computeConfigurationDirectory()
|
||||
val configurationFile = confDir.resolve("gbcs.xml")
|
||||
if (!Files.exists(configurationFile)) {
|
||||
log.info {
|
||||
"Creating default configuration file at '$configurationFile'"
|
||||
}
|
||||
Files.createDirectories(confDir)
|
||||
val defaultConfigurationFileResource = DEFAULT_CONFIGURATION_URL
|
||||
Files.newOutputStream(configurationFile).use { outputStream ->
|
||||
@@ -533,38 +561,29 @@ class GradleBuildCacheServer(private val cfg: Configuration) {
|
||||
}
|
||||
}
|
||||
// val schemaUrl = javaClass.getResource("/net/woggioni/gbcs/gbcs.xsd")
|
||||
val schemaUrl = CONFIGURATION_SCHEMA_URL
|
||||
val dbf = Xml.newDocumentBuilderFactory(schemaUrl)
|
||||
// val schemaUrl = GBCS.CONFIGURATION_SCHEMA_URL
|
||||
val dbf = Xml.newDocumentBuilderFactory(null)
|
||||
// dbf.schema = Xml.getSchema(this::class.java.module.getResourceAsStream("/net/woggioni/gbcs/gbcs.xsd"))
|
||||
dbf.schema = Xml.getSchema(schemaUrl)
|
||||
val db = dbf.newDocumentBuilder().apply {
|
||||
setErrorHandler(Xml.ErrorHandler(schemaUrl))
|
||||
}
|
||||
// dbf.schema = Xml.getSchema(schemaUrl)
|
||||
val db = dbf.newDocumentBuilder()
|
||||
val doc = Files.newInputStream(configurationFile).use(db::parse)
|
||||
return Configuration.parse(doc)
|
||||
return Parser.parse(doc)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
ClasspathUrlStreamHandlerFactoryProvider.install()
|
||||
val configuration = loadConfiguration(args)
|
||||
log.debug {
|
||||
ByteArrayOutputStream().also {
|
||||
Xml.write(Serializer.serialize(configuration), it)
|
||||
}.let {
|
||||
"Server configuration:\n${String(it.toByteArray())}"
|
||||
}
|
||||
}
|
||||
GradleBuildCacheServer(configuration).run().use {
|
||||
}
|
||||
}
|
||||
|
||||
fun digest(
|
||||
data: ByteArray,
|
||||
md: MessageDigest = MessageDigest.getInstance("MD5")
|
||||
): ByteArray {
|
||||
md.update(data)
|
||||
return md.digest()
|
||||
}
|
||||
|
||||
fun digestString(
|
||||
data: ByteArray,
|
||||
md: MessageDigest = MessageDigest.getInstance("MD5")
|
||||
): String {
|
||||
return JWO.bytesToHex(digest(data, md))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,87 +2,7 @@ package net.woggioni.gbcs
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.net.InetSocketAddress
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.logging.LogManager
|
||||
|
||||
inline fun <reified T> T.contextLogger() = LoggerFactory.getLogger(T::class.java)
|
||||
|
||||
inline fun Logger.traceParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isTraceEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
trace(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.debugParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isDebugEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
info(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.infoParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isInfoEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
info(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.warnParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isWarnEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
warn(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.errorParam(messageBuilder : () -> Pair<String, Array<Any>>) {
|
||||
if(isErrorEnabled) {
|
||||
val (format, params) = messageBuilder()
|
||||
error(format, params)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
inline fun log(log : Logger,
|
||||
filter : Logger.() -> Boolean,
|
||||
loggerMethod : Logger.(String) -> Unit, messageBuilder : () -> String) {
|
||||
if(log.filter()) {
|
||||
log.loggerMethod(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.trace(messageBuilder : () -> String) {
|
||||
if(isTraceEnabled) {
|
||||
trace(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.debug(messageBuilder : () -> String) {
|
||||
if(isDebugEnabled) {
|
||||
debug(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.info(messageBuilder : () -> String) {
|
||||
if(isInfoEnabled) {
|
||||
info(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.warn(messageBuilder : () -> String) {
|
||||
if(isWarnEnabled) {
|
||||
warn(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.error(messageBuilder : () -> String) {
|
||||
if(isErrorEnabled) {
|
||||
error(messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
inline fun Logger.trace(ctx : ChannelHandlerContext, messageBuilder : () -> String) {
|
||||
log(this, ctx, { isTraceEnabled }, { trace(it) } , messageBuilder)
|
||||
@@ -107,25 +27,4 @@ inline fun log(log : Logger, ctx : ChannelHandlerContext,
|
||||
val clientAddress = (ctx.channel().remoteAddress() as InetSocketAddress).address.hostAddress
|
||||
log.loggerMethod(clientAddress + " - " + messageBuilder())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class LoggingConfig {
|
||||
|
||||
init {
|
||||
val logManager = LogManager.getLogManager()
|
||||
System.getProperty("log.config.source")?.let withSource@ { source ->
|
||||
val urls = LoggingConfig::class.java.classLoader.getResources(source)
|
||||
while(urls.hasMoreElements()) {
|
||||
val url = urls.nextElement()
|
||||
url.openStream().use { inputStream ->
|
||||
logManager.readConfiguration(inputStream)
|
||||
return@withSource
|
||||
}
|
||||
}
|
||||
Path.of(source).takeIf(Files::exists)
|
||||
?.let(Files::newInputStream)
|
||||
?.use(logManager::readConfiguration)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
enum class Role {
|
||||
Reader, Writer
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package net.woggioni.gbcs
|
||||
|
||||
import io.netty.handler.codec.http.HttpMethod
|
||||
import io.netty.handler.codec.http.HttpRequest
|
||||
import net.woggioni.gbcs.api.Role
|
||||
|
||||
class RoleAuthorizer : Authorizer {
|
||||
|
||||
|
||||
@@ -1,262 +0,0 @@
|
||||
package net.woggioni.gbcs
|
||||
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.Node
|
||||
import org.w3c.dom.NodeList
|
||||
import org.xml.sax.SAXNotRecognizedException
|
||||
import org.xml.sax.SAXNotSupportedException
|
||||
import org.xml.sax.SAXParseException
|
||||
import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.net.URL
|
||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD
|
||||
import javax.xml.XMLConstants.ACCESS_EXTERNAL_SCHEMA
|
||||
import javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING
|
||||
import javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI
|
||||
import javax.xml.parsers.DocumentBuilder
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
import javax.xml.transform.OutputKeys
|
||||
import javax.xml.transform.TransformerFactory
|
||||
import javax.xml.transform.dom.DOMSource
|
||||
import javax.xml.transform.stream.StreamResult
|
||||
import javax.xml.transform.stream.StreamSource
|
||||
import javax.xml.validation.Schema
|
||||
import javax.xml.validation.SchemaFactory
|
||||
import org.xml.sax.ErrorHandler as ErrHandler
|
||||
|
||||
|
||||
class NodeListIterator(private val nodeList: NodeList) : Iterator<Node> {
|
||||
private var cursor: Int = 0
|
||||
override fun hasNext(): Boolean {
|
||||
return cursor < nodeList.length
|
||||
}
|
||||
|
||||
override fun next(): Node {
|
||||
return if (hasNext()) nodeList.item(cursor++) else throw NoSuchElementException()
|
||||
}
|
||||
}
|
||||
|
||||
class ElementIterator(parent: Element, name: String? = null) : Iterator<Element> {
|
||||
private val it: NodeListIterator
|
||||
private val name: String?
|
||||
private var next: Element?
|
||||
|
||||
init {
|
||||
it = NodeListIterator(parent.childNodes)
|
||||
this.name = name
|
||||
next = getNext()
|
||||
}
|
||||
|
||||
override fun hasNext(): Boolean {
|
||||
return next != null
|
||||
}
|
||||
|
||||
override fun next(): Element {
|
||||
val result = next ?: throw NoSuchElementException()
|
||||
next = getNext()
|
||||
return result
|
||||
}
|
||||
|
||||
private fun getNext(): Element? {
|
||||
var result: Element? = null
|
||||
while (it.hasNext()) {
|
||||
val node: Node = it.next()
|
||||
if (node is Element && (name == null || name == node.tagName)) {
|
||||
result = node
|
||||
break
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class Xml(private val doc: Document, val element: Element) {
|
||||
|
||||
class ErrorHandler(private val fileURL: URL) : ErrHandler {
|
||||
|
||||
companion object {
|
||||
private val log = LoggerFactory.getLogger(ErrorHandler::class.java)
|
||||
}
|
||||
|
||||
override fun warning(ex: SAXParseException) {
|
||||
log.warn(
|
||||
"Problem at {}:{}:{} parsing deployment configuration: {}",
|
||||
fileURL, ex.lineNumber, ex.columnNumber, ex.message
|
||||
)
|
||||
}
|
||||
|
||||
override fun error(ex: SAXParseException) {
|
||||
log.error(
|
||||
"Problem at {}:{}:{} parsing deployment configuration: {}",
|
||||
fileURL, ex.lineNumber, ex.columnNumber, ex.message
|
||||
)
|
||||
throw ex
|
||||
}
|
||||
|
||||
override fun fatalError(ex: SAXParseException) {
|
||||
log.error(
|
||||
"Problem at {}:{}:{} parsing deployment configuration: {}",
|
||||
fileURL, ex.lineNumber, ex.columnNumber, ex.message
|
||||
)
|
||||
throw ex
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun Element.asIterable() = Iterable { ElementIterator(this, null) }
|
||||
fun NodeList.asIterable() = Iterable { NodeListIterator(this) }
|
||||
|
||||
private fun disableProperty(dbf: DocumentBuilderFactory, propertyName: String) {
|
||||
try {
|
||||
dbf.setAttribute(propertyName, "")
|
||||
} catch (iae: IllegalArgumentException) {
|
||||
// Property not supported.
|
||||
}
|
||||
}
|
||||
|
||||
private fun disableProperty(sf: SchemaFactory, propertyName: String) {
|
||||
try {
|
||||
sf.setProperty(propertyName, "")
|
||||
} catch (ex: SAXNotRecognizedException) {
|
||||
// Property not supported.
|
||||
} catch (ex: SAXNotSupportedException) {
|
||||
}
|
||||
}
|
||||
|
||||
fun getSchema(schema: URL): Schema {
|
||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
||||
sf.errorHandler = ErrorHandler(schema)
|
||||
return sf.newSchema(schema)
|
||||
}
|
||||
|
||||
fun getSchema(inputStream: InputStream): Schema {
|
||||
val sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI)
|
||||
sf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_SCHEMA)
|
||||
// disableProperty(sf, ACCESS_EXTERNAL_DTD)
|
||||
return sf.newSchema(StreamSource(inputStream))
|
||||
}
|
||||
|
||||
fun newDocumentBuilderFactory(schemaResourceURL: URL?): DocumentBuilderFactory {
|
||||
val dbf = DocumentBuilderFactory.newInstance()
|
||||
dbf.setFeature(FEATURE_SECURE_PROCESSING, true)
|
||||
disableProperty(dbf, ACCESS_EXTERNAL_SCHEMA)
|
||||
disableProperty(dbf, ACCESS_EXTERNAL_DTD)
|
||||
dbf.isExpandEntityReferences = false
|
||||
dbf.isIgnoringComments = true
|
||||
dbf.isNamespaceAware = true
|
||||
dbf.isValidating = false
|
||||
schemaResourceURL?.let {
|
||||
dbf.schema = getSchema(it)
|
||||
}
|
||||
return dbf
|
||||
}
|
||||
|
||||
fun newDocumentBuilder(resource: URL, schemaResourceURL: URL?): DocumentBuilder {
|
||||
val db = newDocumentBuilderFactory(schemaResourceURL).newDocumentBuilder()
|
||||
db.setErrorHandler(ErrorHandler(resource))
|
||||
return db
|
||||
}
|
||||
|
||||
fun parseXmlResource(resource: URL, schemaResourceURL: URL?): Document {
|
||||
val db = newDocumentBuilder(resource, schemaResourceURL)
|
||||
return resource.openStream().use(db::parse)
|
||||
}
|
||||
|
||||
fun parseXml(sourceURL : URL, sourceStream: InputStream? = null, schemaResourceURL: URL? = null): Document {
|
||||
val db = newDocumentBuilder(sourceURL, schemaResourceURL)
|
||||
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) {
|
||||
val transformerFactory = TransformerFactory.newInstance()
|
||||
val transformer = transformerFactory.newTransformer()
|
||||
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("{http://xml.apache.org/xslt}indent-amount", "4")
|
||||
transformer.setOutputProperty(OutputKeys.STANDALONE, "yes")
|
||||
transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8")
|
||||
val source = DOMSource(doc)
|
||||
val result = StreamResult(output)
|
||||
transformer.transform(source, result)
|
||||
}
|
||||
|
||||
fun of(namespaceURI: String, qualifiedName: String, schemaResourceURL: URL? = null, cb: Xml.(el: Element) -> Unit): Document {
|
||||
val dbf = newDocumentBuilderFactory(schemaResourceURL)
|
||||
val db = dbf.newDocumentBuilder()
|
||||
val doc = db.newDocument()
|
||||
val root = doc.createElementNS(namespaceURI, qualifiedName)
|
||||
.also(doc::appendChild)
|
||||
Xml(doc, root).cb(root)
|
||||
return doc
|
||||
}
|
||||
|
||||
fun of(doc: Document, el: Element, cb: Xml.(el: Element) -> Unit): Element {
|
||||
Xml(doc, el).cb(el)
|
||||
return el
|
||||
}
|
||||
|
||||
fun Element.removeChildren() {
|
||||
while (true) {
|
||||
removeChild(firstChild ?: break)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun node(
|
||||
name: String,
|
||||
attrs: Map<String, String> = emptyMap(),
|
||||
cb: Xml.(el: Element) -> Unit = {}
|
||||
): Element {
|
||||
val child = doc.createElement(name)
|
||||
for ((key, value) in attrs) {
|
||||
child.setAttribute(key, value)
|
||||
}
|
||||
return child
|
||||
.also {
|
||||
element.appendChild(it)
|
||||
Xml(doc, it).cb(it)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun attrs(vararg attributes: Pair<String, String>) {
|
||||
for (attr in attributes) element.setAttribute(attr.first, attr.second)
|
||||
}
|
||||
|
||||
fun attr(key: String, value: String) {
|
||||
element.setAttribute(key, value)
|
||||
}
|
||||
|
||||
fun text(txt: String) {
|
||||
element.appendChild(doc.createTextNode(txt))
|
||||
}
|
||||
}
|
||||
10
src/main/kotlin/net/woggioni/gbcs/cache/Cache.kt
vendored
10
src/main/kotlin/net/woggioni/gbcs/cache/Cache.kt
vendored
@@ -1,10 +0,0 @@
|
||||
package net.woggioni.gbcs.cache
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import java.nio.channels.ByteChannel
|
||||
|
||||
interface Cache {
|
||||
fun get(key : String) : ByteChannel?
|
||||
|
||||
fun put(key : String, content : ByteBuf) : Unit
|
||||
}
|
||||
@@ -1,20 +1,32 @@
|
||||
package net.woggioni.gbcs.cache
|
||||
|
||||
import io.netty.buffer.ByteBuf
|
||||
import net.woggioni.gbcs.GradleBuildCacheServer.Companion.digestString
|
||||
import net.woggioni.gbcs.api.Cache
|
||||
import net.woggioni.jwo.JWO
|
||||
import net.woggioni.jwo.LockFile
|
||||
import java.nio.channels.Channels
|
||||
import java.nio.channels.FileChannel
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.nio.file.StandardOpenOption
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.security.MessageDigest
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.atomic.AtomicReference
|
||||
import java.util.zip.Deflater
|
||||
import java.util.zip.DeflaterOutputStream
|
||||
import java.util.zip.Inflater
|
||||
import java.util.zip.InflaterInputStream
|
||||
|
||||
|
||||
class FileSystemCache(val root: Path, val maxAge: Duration) : Cache {
|
||||
class FileSystemCache(
|
||||
val root: Path,
|
||||
val maxAge: Duration,
|
||||
val digestAlgorithm: String?,
|
||||
val compressionEnabled: Boolean,
|
||||
val compressionLevel: Int
|
||||
) : Cache {
|
||||
|
||||
private fun lockFilePath(key: String): Path = root.resolve("$key.lock")
|
||||
|
||||
@@ -22,40 +34,52 @@ class FileSystemCache(val root: Path, val maxAge: Duration) : Cache {
|
||||
Files.createDirectories(root)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
return when (other) {
|
||||
is FileSystemCache -> {
|
||||
other.root == root && other.maxAge == maxAge
|
||||
}
|
||||
private var nextGc = AtomicReference(Instant.now().plus(maxAge))
|
||||
|
||||
else -> false
|
||||
override fun get(key: String) = (digestAlgorithm
|
||||
?.let(MessageDigest::getInstance)
|
||||
?.let { md ->
|
||||
digestString(key.toByteArray(), md)
|
||||
} ?: key).let { digest ->
|
||||
LockFile.acquire(lockFilePath(digest), true).use {
|
||||
root.resolve(digest).takeIf(Files::exists)?.let { file ->
|
||||
if (compressionEnabled) {
|
||||
val inflater = Inflater()
|
||||
Channels.newChannel(InflaterInputStream(Files.newInputStream(file), inflater))
|
||||
} else {
|
||||
FileChannel.open(file, StandardOpenOption.READ)
|
||||
}
|
||||
}
|
||||
}.also {
|
||||
gc()
|
||||
}
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
return root.hashCode() xor maxAge.hashCode()
|
||||
}
|
||||
|
||||
private var nextGc = AtomicReference(Instant.now().plus(maxAge))
|
||||
|
||||
override fun get(key: String) = LockFile.acquire(lockFilePath(key), true).use {
|
||||
root.resolve(key).takeIf(Files::exists)?.let { FileChannel.open(it, StandardOpenOption.READ) }
|
||||
}.also {
|
||||
gc()
|
||||
}
|
||||
|
||||
override fun put(key: String, content: ByteBuf) {
|
||||
LockFile.acquire(lockFilePath(key), false).use {
|
||||
val file = root.resolve(key)
|
||||
val tmpFile = Files.createTempFile(root, null, ".tmp")
|
||||
try {
|
||||
Files.newOutputStream(tmpFile).use {
|
||||
content.readBytes(it, content.readableBytes())
|
||||
override fun put(key: String, content: ByteArray) {
|
||||
(digestAlgorithm
|
||||
?.let(MessageDigest::getInstance)
|
||||
?.let { md ->
|
||||
digestString(key.toByteArray(), md)
|
||||
} ?: key).let { digest ->
|
||||
LockFile.acquire(lockFilePath(digest), false).use {
|
||||
val file = root.resolve(digest)
|
||||
val tmpFile = Files.createTempFile(root, null, ".tmp")
|
||||
try {
|
||||
Files.newOutputStream(tmpFile).let {
|
||||
if (compressionEnabled) {
|
||||
val deflater = Deflater(compressionLevel)
|
||||
DeflaterOutputStream(it, deflater)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.use {
|
||||
it.write(content)
|
||||
}
|
||||
Files.move(tmpFile, file, StandardCopyOption.ATOMIC_MOVE)
|
||||
} catch (t: Throwable) {
|
||||
Files.delete(tmpFile)
|
||||
throw t
|
||||
}
|
||||
Files.move(tmpFile, file, StandardCopyOption.ATOMIC_MOVE)
|
||||
} catch (t: Throwable) {
|
||||
Files.delete(tmpFile)
|
||||
throw t
|
||||
}
|
||||
}.also {
|
||||
gc()
|
||||
@@ -87,4 +111,23 @@ class FileSystemCache(val root: Path, val maxAge: Duration) : Cache {
|
||||
Files.delete(lockFile)
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {}
|
||||
|
||||
companion object {
|
||||
fun digest(
|
||||
data: ByteArray,
|
||||
md: MessageDigest = MessageDigest.getInstance("MD5")
|
||||
): ByteArray {
|
||||
md.update(data)
|
||||
return md.digest()
|
||||
}
|
||||
|
||||
fun digestString(
|
||||
data: ByteArray,
|
||||
md: MessageDigest = MessageDigest.getInstance("MD5")
|
||||
): String {
|
||||
return JWO.bytesToHex(digest(data, md))
|
||||
}
|
||||
}
|
||||
}
|
||||
27
src/main/kotlin/net/woggioni/gbcs/cache/FileSystemCacheConfiguration.kt
vendored
Normal file
27
src/main/kotlin/net/woggioni/gbcs/cache/FileSystemCacheConfiguration.kt
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
package net.woggioni.gbcs.cache
|
||||
|
||||
import net.woggioni.gbcs.api.Configuration
|
||||
import net.woggioni.gbcs.base.GBCS
|
||||
import net.woggioni.jwo.Application
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
|
||||
data class FileSystemCacheConfiguration(
|
||||
val root: Path?,
|
||||
val maxAge: Duration,
|
||||
val digestAlgorithm : String?,
|
||||
val compressionEnabled: Boolean,
|
||||
val compressionLevel: Int,
|
||||
) : Configuration.Cache {
|
||||
override fun materialize() = FileSystemCache(
|
||||
root ?: Application.builder("gbcs").build().computeCacheDirectory(),
|
||||
maxAge,
|
||||
digestAlgorithm,
|
||||
compressionEnabled,
|
||||
compressionLevel
|
||||
)
|
||||
|
||||
override fun getNamespaceURI() = GBCS.GBCS_NAMESPACE_URI
|
||||
|
||||
override fun getTypeName() = "fileSystemCacheType"
|
||||
}
|
||||
66
src/main/kotlin/net/woggioni/gbcs/cache/FileSystemCacheProvider.kt
vendored
Normal file
66
src/main/kotlin/net/woggioni/gbcs/cache/FileSystemCacheProvider.kt
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
package net.woggioni.gbcs.cache
|
||||
|
||||
import net.woggioni.gbcs.api.CacheProvider
|
||||
import net.woggioni.gbcs.base.GBCS
|
||||
import net.woggioni.gbcs.base.Xml
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.util.zip.Deflater
|
||||
|
||||
class FileSystemCacheProvider : CacheProvider<FileSystemCacheConfiguration> {
|
||||
|
||||
override fun getXmlSchemaLocation() = "classpath:net/woggioni/gbcs/schema/gbcs.xsd"
|
||||
|
||||
override fun getXmlType() = "fileSystemCacheType"
|
||||
|
||||
override fun getXmlNamespace() = "urn:net.woggioni.gbcs"
|
||||
|
||||
override fun deserialize(el: Element): FileSystemCacheConfiguration {
|
||||
val path = el.getAttribute("path")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?.let(Path::of)
|
||||
val maxAge = el.getAttribute("max-age")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?.let(Duration::parse)
|
||||
?: Duration.ofDays(1)
|
||||
val enableCompression = el.getAttribute("enable-compression")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?.let(String::toBoolean)
|
||||
?: true
|
||||
val compressionLevel = el.getAttribute("compression-level")
|
||||
.takeIf(String::isNotEmpty)
|
||||
?.let(String::toInt)
|
||||
?: Deflater.DEFAULT_COMPRESSION
|
||||
val digestAlgorithm = el.getAttribute("digest").takeIf(String::isNotEmpty) ?: "MD5"
|
||||
|
||||
return FileSystemCacheConfiguration(
|
||||
path,
|
||||
maxAge,
|
||||
digestAlgorithm,
|
||||
enableCompression,
|
||||
compressionLevel
|
||||
)
|
||||
}
|
||||
|
||||
override fun serialize(doc: Document, cache : FileSystemCacheConfiguration) = cache.run {
|
||||
val result = doc.createElement("cache")
|
||||
Xml.of(doc, result) {
|
||||
val prefix = doc.lookupPrefix(GBCS.GBCS_NAMESPACE_URI)
|
||||
attr("xs:type", "${prefix}:fileSystemCacheType", GBCS.XML_SCHEMA_NAMESPACE_URI)
|
||||
attr("path", root.toString())
|
||||
attr("max-age", maxAge.toString())
|
||||
digestAlgorithm?.let { digestAlgorithm ->
|
||||
attr("digest", digestAlgorithm)
|
||||
}
|
||||
attr("enable-compression", compressionEnabled.toString())
|
||||
compressionLevel.takeIf {
|
||||
it != Deflater.DEFAULT_COMPRESSION
|
||||
}?.let {
|
||||
attr("compression-level", it.toString())
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package net.woggioni.gbcs.configuration
|
||||
|
||||
import net.woggioni.gbcs.api.CacheProvider
|
||||
import net.woggioni.gbcs.api.Configuration
|
||||
import java.util.ServiceLoader
|
||||
|
||||
object CacheSerializers {
|
||||
val index = (Configuration::class.java.module.layer?.let { layer ->
|
||||
ServiceLoader.load(layer, CacheProvider::class.java)
|
||||
} ?: ServiceLoader.load(CacheProvider::class.java))
|
||||
.asSequence()
|
||||
.map {
|
||||
(it.xmlNamespace to it.xmlType) to it
|
||||
}.toMap()
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
package net.woggioni.gbcs.configuration
|
||||
|
||||
import net.woggioni.gbcs.Role
|
||||
import net.woggioni.gbcs.Xml.Companion.asIterable
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
import net.woggioni.gbcs.api.Role
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.security.cert.X509Certificate
|
||||
import java.time.Duration
|
||||
|
||||
@ConsistentCopyVisibility
|
||||
data class Configuration private constructor(
|
||||
val host: String,
|
||||
val port: Int,
|
||||
@@ -36,10 +33,6 @@ data class Configuration private constructor(
|
||||
get() = groups.asSequence().flatMap { it.roles }.toSet()
|
||||
}
|
||||
|
||||
data class HostAndPort(val host: String, val port: Int) {
|
||||
override fun toString() = "$host:$port"
|
||||
}
|
||||
|
||||
fun interface UserExtractor {
|
||||
fun extract(cert :X509Certificate) : User
|
||||
}
|
||||
@@ -107,188 +100,210 @@ data class Configuration private constructor(
|
||||
useVirtualThread
|
||||
)
|
||||
|
||||
fun parse(document: Document): Configuration {
|
||||
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" -> {
|
||||
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
|
||||
}
|
||||
// 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
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
231
src/main/kotlin/net/woggioni/gbcs/configuration/Parser.kt
Normal file
231
src/main/kotlin/net/woggioni/gbcs/configuration/Parser.kt
Normal file
@@ -0,0 +1,231 @@
|
||||
package net.woggioni.gbcs.configuration
|
||||
|
||||
import net.woggioni.gbcs.api.Configuration
|
||||
import net.woggioni.gbcs.api.Configuration.Authentication
|
||||
import net.woggioni.gbcs.api.Configuration.BasicAuthentication
|
||||
import net.woggioni.gbcs.api.Configuration.Cache
|
||||
import net.woggioni.gbcs.api.Configuration.ClientCertificateAuthentication
|
||||
import net.woggioni.gbcs.api.Configuration.Group
|
||||
import net.woggioni.gbcs.api.Configuration.KeyStore
|
||||
import net.woggioni.gbcs.api.Configuration.Tls
|
||||
import net.woggioni.gbcs.api.Configuration.TlsCertificateExtractor
|
||||
import net.woggioni.gbcs.api.Configuration.TrustStore
|
||||
import net.woggioni.gbcs.api.Configuration.User
|
||||
import net.woggioni.gbcs.api.Role
|
||||
import net.woggioni.gbcs.base.Xml.Companion.asIterable
|
||||
import org.w3c.dom.Document
|
||||
import org.w3c.dom.Element
|
||||
import org.w3c.dom.TypeInfo
|
||||
import java.nio.file.Paths
|
||||
|
||||
object Parser {
|
||||
|
||||
fun parse(document: Document): Configuration {
|
||||
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.localName) {
|
||||
"authorization" -> {
|
||||
for (gchild in child.asIterable()) {
|
||||
when (child.localName) {
|
||||
"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").split(":")
|
||||
// val namespaceURI = child.lookupNamespaceURI(type[0])
|
||||
// val typeName = type[1]
|
||||
cache = (child as? TypeInfo)?.let { tf ->
|
||||
val typeNamespace = tf.typeNamespace
|
||||
val typeName = tf.typeName
|
||||
CacheSerializers.index[typeNamespace to typeName]
|
||||
}?.deserialize(child) ?: 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.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" -> {
|
||||
for (gchild in child.asIterable()) {
|
||||
when (gchild.localName) {
|
||||
"basic" -> {
|
||||
authentication = BasicAuthentication()
|
||||
}
|
||||
|
||||
"client-certificate" -> {
|
||||
var tlsExtractorUser: TlsCertificateExtractor? = null
|
||||
var tlsExtractorGroup: TlsCertificateExtractor? = null
|
||||
for (gchild in child.asIterable()) {
|
||||
when (gchild.localName) {
|
||||
"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.localName) {
|
||||
"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 Configuration(host, port, serverPath, users, groups, cache!!, authentication, tls, useVirtualThread)
|
||||
}
|
||||
|
||||
private fun parseRoles(root: Element) = root.asIterable().asSequence().map {
|
||||
when (it.localName) {
|
||||
"reader" -> Role.Reader
|
||||
"writer" -> Role.Writer
|
||||
else -> throw UnsupportedOperationException("Illegal node '${it.localName}'")
|
||||
}
|
||||
}.toSet()
|
||||
|
||||
private fun parseUserRefs(root: Element) = root.asIterable().asSequence().filter {
|
||||
it.localName == "user"
|
||||
}.map {
|
||||
it.getAttribute("ref")
|
||||
}.toSet()
|
||||
|
||||
private fun parseUsers(root: Element): Map<String, User> {
|
||||
return root.asIterable().asSequence().filter {
|
||||
it.localName == "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.localName == "group"
|
||||
}.map { el ->
|
||||
val groupName = el.getAttribute("name")
|
||||
var roles = emptySet<Role>()
|
||||
for (child in el.asIterable()) {
|
||||
when (child.localName) {
|
||||
"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
|
||||
}
|
||||
}
|
||||
@@ -1,34 +1,37 @@
|
||||
package net.woggioni.gbcs.configuration
|
||||
|
||||
import net.woggioni.gbcs.Xml
|
||||
import net.woggioni.gbcs.api.CacheProvider
|
||||
import net.woggioni.gbcs.api.Configuration
|
||||
import net.woggioni.gbcs.base.GBCS
|
||||
import net.woggioni.gbcs.base.Xml
|
||||
import org.w3c.dom.Document
|
||||
|
||||
object Serializer {
|
||||
|
||||
private const val GBCS_NAMESPACE: String = "urn:net.woggioni.gbcs"
|
||||
private const val GBCS_PREFIX: String = "gbcs"
|
||||
|
||||
fun serialize(conf : Configuration) : Document {
|
||||
return Xml.of(GBCS_NAMESPACE, GBCS_PREFIX + ":server") {
|
||||
attr("userVirtualThreads", conf.useVirtualThread.toString())
|
||||
conf.serverPath?.let { serverPath ->
|
||||
|
||||
val schemaLocations = CacheSerializers.index.values.asSequence().map {
|
||||
it.xmlNamespace to it.xmlSchemaLocation
|
||||
}.toMap()
|
||||
return Xml.of(GBCS.GBCS_NAMESPACE_URI, GBCS.GBCS_PREFIX + ":server") {
|
||||
attr("useVirtualThreads", conf.isUseVirtualThread.toString())
|
||||
// attr("xmlns:xs", GradleBuildCacheServer.XML_SCHEMA_NAMESPACE_URI)
|
||||
val value = schemaLocations.asSequence().map { (k, v) -> "$k $v" }.joinToString(" ")
|
||||
attr("xs:schemaLocation",value , namespaceURI = GBCS.XML_SCHEMA_NAMESPACE_URI)
|
||||
|
||||
conf.serverPath
|
||||
?.takeIf(String::isNotEmpty)
|
||||
?.let { serverPath ->
|
||||
attr("path", serverPath)
|
||||
}
|
||||
node("bind") {
|
||||
attr("host", conf.host)
|
||||
attr("port", conf.port.toString())
|
||||
}
|
||||
node("cache") {
|
||||
when(val cache = conf.cache) {
|
||||
is Configuration.FileSystemCache -> {
|
||||
node("file-system-cache") {
|
||||
attr("path", cache.root.toString())
|
||||
attr("max-age", cache.maxAge.toString())
|
||||
}
|
||||
}
|
||||
else -> throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
val cache = conf.cache
|
||||
val serializer : CacheProvider<Configuration.Cache> =
|
||||
(CacheSerializers.index[cache.namespaceURI to cache.typeName] as? CacheProvider<Configuration.Cache>) ?: throw NotImplementedError()
|
||||
element.appendChild(serializer.serialize(doc, cache))
|
||||
node("authorization") {
|
||||
node("users") {
|
||||
for(user in conf.users.values) {
|
||||
@@ -112,7 +115,7 @@ object Serializer {
|
||||
trustStore.password?.let { password ->
|
||||
attr("password", password)
|
||||
}
|
||||
attr("check-certificate-status", trustStore.checkCertificateStatus.toString())
|
||||
attr("check-certificate-status", trustStore.isCheckCertificateStatus.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user