Add pairgoth-common module to share sources between api and view webapps
This commit is contained in:
@@ -114,6 +114,11 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeudego.pairgoth</groupId>
|
||||||
|
<artifactId>pairgoth-common</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- main dependencies -->
|
<!-- main dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
@@ -65,7 +65,7 @@ class ApiServlet: HttpServlet() {
|
|||||||
|
|
||||||
// validate request
|
// validate request
|
||||||
|
|
||||||
if ("dev" == WebappManager.getProperty("env")) {
|
if ("dev" == WebappManager.properties["env"]) {
|
||||||
response.addHeader("Access-Control-Allow-Origin", "*")
|
response.addHeader("Access-Control-Allow-Origin", "*")
|
||||||
}
|
}
|
||||||
validateAccept(request);
|
validateAccept(request);
|
||||||
|
@@ -1,171 +1,30 @@
|
|||||||
package org.jeudego.pairgoth.server
|
package org.jeudego.pairgoth.server
|
||||||
|
|
||||||
import com.republicate.mailer.SmtpLoop
|
import com.republicate.mailer.SmtpLoop
|
||||||
import org.apache.commons.lang3.tuple.Pair
|
import org.jeudego.pairgoth.web.BaseWebappManager
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.io.IOException
|
|
||||||
import java.lang.IllegalAccessError
|
|
||||||
import java.security.SecureRandom
|
|
||||||
import java.security.cert.X509Certificate
|
|
||||||
import java.util.*
|
|
||||||
import java.util.IllegalFormatCodePointException
|
|
||||||
import javax.net.ssl.*
|
|
||||||
import javax.servlet.*
|
import javax.servlet.*
|
||||||
import javax.servlet.annotation.WebListener
|
import javax.servlet.annotation.WebListener
|
||||||
import javax.servlet.http.HttpSessionEvent
|
|
||||||
import javax.servlet.http.HttpSessionListener
|
|
||||||
|
|
||||||
@WebListener
|
@WebListener
|
||||||
class WebappManager : ServletContextListener, ServletContextAttributeListener, HttpSessionListener {
|
class WebappManager : BaseWebappManager("API Server","api") {
|
||||||
private fun disableSSLCertificateChecks() {
|
|
||||||
// see http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/
|
|
||||||
try {
|
|
||||||
// Create a trust manager that does not validate certificate chains
|
|
||||||
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate>? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("TrustAllX509TrustManager")
|
|
||||||
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
|
|
||||||
@Suppress("TrustAllX509TrustManager")
|
|
||||||
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Install the all-trusting trust manager
|
|
||||||
val sc = SSLContext.getInstance("SSL")
|
|
||||||
sc.init(null, trustAllCerts, SecureRandom())
|
|
||||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
|
|
||||||
|
|
||||||
// Create all-trusting host name verifier
|
|
||||||
val allHostsValid = HostnameVerifier { hostname, session -> true }
|
|
||||||
|
|
||||||
// Install the all-trusting host verifier
|
|
||||||
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.error("could not disable SSL certificate checks", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ServletContextListener interface */
|
/* ServletContextListener interface */
|
||||||
override fun contextInitialized(sce: ServletContextEvent) {
|
override fun contextInitialized(sce: ServletContextEvent) {
|
||||||
context = sce.servletContext
|
super.contextInitialized(sce)
|
||||||
logger.info("---------- Starting $WEBAPP_NAME ----------")
|
|
||||||
webappRoot = context.getRealPath("/")
|
|
||||||
try {
|
|
||||||
// load default properties
|
|
||||||
properties.load(context.getResourceAsStream("/WEB-INF/pairgoth.default.properties"))
|
|
||||||
// override with system properties after stripping off the 'pairgoth.' prefix
|
|
||||||
System.getProperties().filter { (key, value) -> key is String && key.startsWith(PAIRGOTH_PROPERTIES_PREFIX)
|
|
||||||
}.forEach { (key, value) ->
|
|
||||||
properties[(key as String).removePrefix(PAIRGOTH_PROPERTIES_PREFIX)] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("pairgoth server ${properties["version"]} with profile ${properties["env"]}")
|
logger.info("pairgoth server ${properties["version"]} with profile ${properties["env"]}")
|
||||||
|
|
||||||
// set system user agent string to empty string
|
// start smtp loop
|
||||||
System.setProperty("http.agent", "")
|
if (properties.containsKey("smtp.host")) {
|
||||||
|
logger.info("Launching SMTP loop")
|
||||||
// disable (for now ?) the SSL certificate checks, because many sites
|
registerService("smtp", SmtpLoop(properties))
|
||||||
// fail to correctly implement SSL...
|
startService("smtp")
|
||||||
disableSSLCertificateChecks()
|
|
||||||
|
|
||||||
// start smtp loop
|
|
||||||
if (properties.containsKey("smtp.host")) {
|
|
||||||
registerService("smtp", SmtpLoop(properties))
|
|
||||||
startService("smtp")
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (ioe: IOException) {
|
|
||||||
logger.error("webapp initialization error", ioe)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contextDestroyed(sce: ServletContextEvent) {
|
|
||||||
logger.info("---------- Stopping $WEBAPP_NAME ----------")
|
|
||||||
|
|
||||||
stopService("smtp");
|
|
||||||
|
|
||||||
val context = sce.servletContext
|
|
||||||
for (service in webServices.keys) stopService(service, true)
|
|
||||||
// ??? DriverManager.deregisterDriver(com.mysql.cj.jdbc.Driver ...);
|
|
||||||
|
|
||||||
logger.info("---------- Stopped $WEBAPP_NAME ----------")
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ServletContextAttributeListener interface */
|
|
||||||
override fun attributeAdded(event: ServletContextAttributeEvent) {}
|
|
||||||
override fun attributeRemoved(event: ServletContextAttributeEvent) {}
|
|
||||||
override fun attributeReplaced(event: ServletContextAttributeEvent) {}
|
|
||||||
|
|
||||||
/* HttpSessionListener interface */
|
|
||||||
override fun sessionCreated(se: HttpSessionEvent) {}
|
|
||||||
override fun sessionDestroyed(se: HttpSessionEvent) {}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val WEBAPP_NAME = "Pairgoth API Server"
|
val properties get() = BaseWebappManager.properties
|
||||||
const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth."
|
fun getMandatoryProperty(prop: String) = properties.getProperty(prop) ?: throw Error("missing property: $prop")
|
||||||
lateinit var webappRoot: String
|
val context get() = BaseWebappManager.context
|
||||||
lateinit var context: ServletContext
|
|
||||||
private val webServices: MutableMap<String?, Pair<Runnable, Thread?>> = TreeMap()
|
|
||||||
var logger = LoggerFactory.getLogger(WebappManager::class.java)
|
|
||||||
val properties = Properties()
|
|
||||||
fun getProperty(prop: String): String? {
|
|
||||||
return properties.getProperty(prop)
|
|
||||||
}
|
|
||||||
fun getMandatoryProperty(prop: String): String {
|
|
||||||
return getProperty(prop) ?: throw Error("missing property: ${prop}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val webappURL by lazy { getProperty("webapp.external.url") }
|
|
||||||
|
|
||||||
private val services = mutableMapOf<String, Pair<Runnable, Thread>>()
|
|
||||||
|
|
||||||
@JvmOverloads
|
|
||||||
fun registerService(name: String?, task: Runnable, initialStatus: Boolean? = null) {
|
|
||||||
if (webServices.containsKey(name)) {
|
|
||||||
logger.warn("service {} already registered")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.debug("registered service {}", name)
|
|
||||||
webServices[name] =
|
|
||||||
Pair.of(task, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startService(name: String?) {
|
|
||||||
val service = webServices[name]!!
|
|
||||||
if (service.right != null && service.right!!.isAlive) {
|
|
||||||
logger.warn("service {} is already running", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.debug("starting service {}", name)
|
|
||||||
val thread = Thread(service.left, name)
|
|
||||||
thread.start()
|
|
||||||
webServices[name] =
|
|
||||||
Pair.of(
|
|
||||||
service.left,
|
|
||||||
thread
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmOverloads
|
|
||||||
fun stopService(name: String?, webappClosing: Boolean = false) {
|
|
||||||
val service = webServices[name]!!
|
|
||||||
val thread = service.right
|
|
||||||
if (thread == null || !thread.isAlive) {
|
|
||||||
logger.warn("service {} is already stopped", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.debug("stopping service {}", name)
|
|
||||||
thread.interrupt()
|
|
||||||
try {
|
|
||||||
thread.join()
|
|
||||||
} catch (ie: InterruptedException) {
|
|
||||||
}
|
|
||||||
if (!webappClosing) {
|
|
||||||
webServices[name] = Pair.of(service.left, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -3,10 +3,10 @@ package org.jeudego.pairgoth.store
|
|||||||
import org.jeudego.pairgoth.server.WebappManager
|
import org.jeudego.pairgoth.server.WebappManager
|
||||||
|
|
||||||
private fun createStoreImplementation(): IStore {
|
private fun createStoreImplementation(): IStore {
|
||||||
return when (val storeProperty = WebappManager.getProperty("store") ?: "memory") {
|
return when (val storeProperty = WebappManager.properties.getProperty("store") ?: "memory") {
|
||||||
"memory" -> MemoryStore()
|
"memory" -> MemoryStore()
|
||||||
"file" -> {
|
"file" -> {
|
||||||
val filePath = WebappManager.getProperty("store.file.path") ?: "."
|
val filePath = WebappManager.properties.getProperty("store.file.path") ?: "."
|
||||||
FileStore(filePath)
|
FileStore(filePath)
|
||||||
}
|
}
|
||||||
else -> throw Error("unknown store: $storeProperty")
|
else -> throw Error("unknown store: $storeProperty")
|
||||||
|
150
pairgoth-common/pom.xml
Normal file
150
pairgoth-common/pom.xml
Normal file
@@ -0,0 +1,150 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.jeudego.pairgoth</groupId>
|
||||||
|
<artifactId>engine-parent</artifactId>
|
||||||
|
<version>0.3</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>pairgoth-common</artifactId>
|
||||||
|
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
<name>${project.groupId}:${project.artifactId}</name>
|
||||||
|
<description>Pairgoth common utilities</description>
|
||||||
|
<url>TODO</url>
|
||||||
|
<build>
|
||||||
|
<defaultGoal>package</defaultGoal>
|
||||||
|
<sourceDirectory>src/main/kotlin</sourceDirectory>
|
||||||
|
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>compile</id>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>test-compile</id>
|
||||||
|
<phase>test-compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit</groupId>
|
||||||
|
<artifactId>junit-bom</artifactId>
|
||||||
|
<version>5.9.3</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<!-- main dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-test-junit5</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-reflect</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
|
<artifactId>kotlinx-datetime-jvm</artifactId>
|
||||||
|
<version>0.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- servlets and mail APIs -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
<version>${servlet.api.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>jakarta.mail</artifactId>
|
||||||
|
<version>1.6.7</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- utils -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.13.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.microutils</groupId>
|
||||||
|
<artifactId>kotlin-logging-jvm</artifactId>
|
||||||
|
<version>3.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.diogonunes</groupId>
|
||||||
|
<artifactId>JColor</artifactId>
|
||||||
|
<version>5.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- mailer -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate</groupId>
|
||||||
|
<artifactId>simple-mailer</artifactId>
|
||||||
|
<version>1.6</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- json -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate.kson</groupId>
|
||||||
|
<artifactId>essential-kson-jvm</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- tests -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>${junit.jupiter.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito.kotlin</groupId>
|
||||||
|
<artifactId>mockito-kotlin</artifactId>
|
||||||
|
<version>4.1.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
307
pairgoth-common/pom.xml.versionsBackup
Normal file
307
pairgoth-common/pom.xml.versionsBackup
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.jeudego.pairgoth</groupId>
|
||||||
|
<artifactId>engine-parent</artifactId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<artifactId>api-webapp</artifactId>
|
||||||
|
|
||||||
|
<packaging>war</packaging>
|
||||||
|
<name>${project.groupId}:${project.artifactId}</name>
|
||||||
|
<description>PairGoth pairing system</description>
|
||||||
|
<url>TODO</url>
|
||||||
|
<properties>
|
||||||
|
<pac4j.version>5.7.1</pac4j.version>
|
||||||
|
</properties>
|
||||||
|
<build>
|
||||||
|
<defaultGoal>package</defaultGoal>
|
||||||
|
<sourceDirectory>src/main/kotlin</sourceDirectory>
|
||||||
|
<testSourceDirectory>src/test/kotlin</testSourceDirectory>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-maven-plugin</artifactId>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>compile</id>
|
||||||
|
<phase>compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
<execution>
|
||||||
|
<id>test-compile</id>
|
||||||
|
<phase>test-compile</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>test-compile</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<classpathDependencyExcludes>
|
||||||
|
<classpathDependencyExclude>com.republicate:webapp-slf4j-logger</classpathDependencyExclude>
|
||||||
|
</classpathDependencyExcludes>
|
||||||
|
<systemPropertyVariables>
|
||||||
|
<test.build.dir>${project.build.testOutputDirectory}</test.build.dir>
|
||||||
|
</systemPropertyVariables>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-maven-plugin</artifactId>
|
||||||
|
<version>${jetty.version}</version>
|
||||||
|
<configuration>
|
||||||
|
<scan>0</scan>
|
||||||
|
<httpConnector>
|
||||||
|
<host>${pairgoth.api.host}</host>
|
||||||
|
<port>${pairgoth.api.port}</port>
|
||||||
|
</httpConnector>
|
||||||
|
<systemProperties>
|
||||||
|
<pairgoth.env>${pairgoth.env}</pairgoth.env>
|
||||||
|
<pairgoth.version>${pairgoth.version}</pairgoth.version>
|
||||||
|
<pairgoth.api.external.url>${pairgoth.api.external.url}</pairgoth.api.external.url>
|
||||||
|
<pairgoth.webapp.external.url>${pairgoth.webapp.external.url}</pairgoth.webapp.external.url>
|
||||||
|
<pairgoth.store>${pairgoth.store}</pairgoth.store>
|
||||||
|
<pairgoth.store.file.path>${pairgoth.store.file.path}</pairgoth.store.file.path>
|
||||||
|
<pairgoth.logger.level>${pairgoth.logger.level}</pairgoth.logger.level>
|
||||||
|
<pairgoth.logger.format>${pairgoth.logger.format}</pairgoth.logger.format>
|
||||||
|
</systemProperties>
|
||||||
|
<webApp>
|
||||||
|
<contextPath>${pairgoth.api.context}/</contextPath>
|
||||||
|
</webApp>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>jaxb2-maven-plugin</artifactId>
|
||||||
|
<version>3.1.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>gen-schema</id>
|
||||||
|
<goals>
|
||||||
|
<goal>xjc</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<packageName>org.jeudego.pairgoth.opengotha</packageName>
|
||||||
|
<sourceType>XmlSchema</sourceType>
|
||||||
|
<sources>
|
||||||
|
<source>src/main/resources/xsd</source>
|
||||||
|
</sources>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
<dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit</groupId>
|
||||||
|
<artifactId>junit-bom</artifactId>
|
||||||
|
<version>5.9.3</version>
|
||||||
|
<type>pom</type>
|
||||||
|
<scope>import</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</dependencyManagement>
|
||||||
|
<dependencies>
|
||||||
|
<!-- main dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-test-junit5</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-stdlib-jdk8</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
<artifactId>kotlin-reflect</artifactId>
|
||||||
|
<version>${kotlin.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jetbrains.kotlinx</groupId>
|
||||||
|
<artifactId>kotlinx-datetime-jvm</artifactId>
|
||||||
|
<version>0.4.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- servlets and mail APIs -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
<artifactId>jakarta.servlet-api</artifactId>
|
||||||
|
<version>${servlet.api.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>jakarta.mail</artifactId>
|
||||||
|
<version>1.6.7</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- utils -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-lang3</artifactId>
|
||||||
|
<version>3.12.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.13.0</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- auth -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.pac4j</groupId>
|
||||||
|
<artifactId>pac4j-oauth</artifactId>
|
||||||
|
<version>${pac4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- logging -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.github.microutils</groupId>
|
||||||
|
<artifactId>kotlin-logging-jvm</artifactId>
|
||||||
|
<version>3.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate</groupId>
|
||||||
|
<artifactId>webapp-slf4j-logger</artifactId>
|
||||||
|
<version>3.0</version>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.diogonunes</groupId>
|
||||||
|
<artifactId>JColor</artifactId>
|
||||||
|
<version>5.0.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- mailer -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate</groupId>
|
||||||
|
<artifactId>simple-mailer</artifactId>
|
||||||
|
<version>1.6</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- json -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate.kson</groupId>
|
||||||
|
<artifactId>essential-kson-jvm</artifactId>
|
||||||
|
<version>2.4</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- charset detection
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.ibm.icu</groupId>
|
||||||
|
<artifactId>icu4j</artifactId>
|
||||||
|
<version>70.1</version>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
|
<!-- net clients
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpclient</artifactId>
|
||||||
|
<version>4.5.13</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<artifactId>commons-logging</artifactId>
|
||||||
|
<groupId>commons-logging</groupId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.httpcomponents</groupId>
|
||||||
|
<artifactId>httpmime</artifactId>
|
||||||
|
<version>4.5.13</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-io</groupId>
|
||||||
|
<artifactId>commons-io</artifactId>
|
||||||
|
<version>2.11.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-net</groupId>
|
||||||
|
<artifactId>commons-net</artifactId>
|
||||||
|
<version>3.8.0</version>
|
||||||
|
</dependency>
|
||||||
|
-->
|
||||||
|
<!-- pdf -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.pdfbox</groupId>
|
||||||
|
<artifactId>pdfbox</artifactId>
|
||||||
|
<version>2.0.28</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- server-side events -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.republicate</groupId>
|
||||||
|
<artifactId>jeasse-servlet3</artifactId>
|
||||||
|
<version>1.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- graph solver -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jgrapht</groupId>
|
||||||
|
<artifactId>jgrapht-core</artifactId>
|
||||||
|
<version>1.5.2</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- xml class generation -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.xml.bind</groupId>
|
||||||
|
<artifactId>jakarta.xml.bind-api</artifactId>
|
||||||
|
<version>4.0.0</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.glassfish.jaxb</groupId>
|
||||||
|
<artifactId>jaxb-runtime</artifactId>
|
||||||
|
<version>4.0.3</version>
|
||||||
|
</dependency>
|
||||||
|
<!-- tests -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter-engine</artifactId>
|
||||||
|
<version>${junit.jupiter.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mockito.kotlin</groupId>
|
||||||
|
<artifactId>mockito-kotlin</artifactId>
|
||||||
|
<version>4.1.0</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<!-- test emails -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.icegreen</groupId>
|
||||||
|
<artifactId>greenmail</artifactId>
|
||||||
|
<version>1.6.12</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>junit</groupId>
|
||||||
|
<artifactId>junit</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>javax.activation</groupId>
|
||||||
|
<artifactId>activation</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>javax.mail</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
@@ -0,0 +1,147 @@
|
|||||||
|
package org.jeudego.pairgoth.web
|
||||||
|
|
||||||
|
import com.republicate.mailer.SmtpLoop
|
||||||
|
import org.apache.commons.lang3.tuple.Pair
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import java.io.IOException
|
||||||
|
import java.lang.IllegalAccessError
|
||||||
|
import java.security.SecureRandom
|
||||||
|
import java.security.cert.X509Certificate
|
||||||
|
import java.util.*
|
||||||
|
import java.util.IllegalFormatCodePointException
|
||||||
|
import javax.net.ssl.*
|
||||||
|
import javax.servlet.*
|
||||||
|
import javax.servlet.annotation.WebListener
|
||||||
|
import javax.servlet.http.HttpSessionEvent
|
||||||
|
import javax.servlet.http.HttpSessionListener
|
||||||
|
|
||||||
|
abstract class BaseWebappManager(val webappName: String, loggerName: String) : ServletContextListener, ServletContextAttributeListener, HttpSessionListener {
|
||||||
|
|
||||||
|
val logger = LoggerFactory.getLogger(loggerName)
|
||||||
|
|
||||||
|
protected fun disableSSLCertificateChecks() {
|
||||||
|
// see http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/
|
||||||
|
try {
|
||||||
|
// Create a trust manager that does not validate certificate chains
|
||||||
|
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
||||||
|
override fun getAcceptedIssuers(): Array<X509Certificate>? {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("TrustAllX509TrustManager")
|
||||||
|
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
|
||||||
|
@Suppress("TrustAllX509TrustManager")
|
||||||
|
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Install the all-trusting trust manager
|
||||||
|
val sc = SSLContext.getInstance("SSL")
|
||||||
|
sc.init(null, trustAllCerts, SecureRandom())
|
||||||
|
HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
|
||||||
|
|
||||||
|
// Create all-trusting host name verifier
|
||||||
|
val allHostsValid = HostnameVerifier { hostname, session -> true }
|
||||||
|
|
||||||
|
// Install the all-trusting host verifier
|
||||||
|
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("could not disable SSL certificate checks", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val webServices: MutableMap<String?, Pair<Runnable, Thread?>> = TreeMap()
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun registerService(name: String?, task: Runnable, initialStatus: Boolean? = null) {
|
||||||
|
if (webServices.containsKey(name)) {
|
||||||
|
logger.warn("service {} already registered")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.debug("registered service {}", name)
|
||||||
|
webServices[name] =
|
||||||
|
Pair.of(task, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun startService(name: String?) {
|
||||||
|
val service = webServices[name]!!
|
||||||
|
if (service.right != null && service.right!!.isAlive) {
|
||||||
|
logger.warn("service {} is already running", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.debug("starting service {}", name)
|
||||||
|
val thread = Thread(service.left, name)
|
||||||
|
thread.start()
|
||||||
|
webServices[name] =
|
||||||
|
Pair.of(
|
||||||
|
service.left,
|
||||||
|
thread
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmOverloads
|
||||||
|
fun stopService(name: String?, webappClosing: Boolean = false) {
|
||||||
|
val service = webServices[name]!!
|
||||||
|
val thread = service.right
|
||||||
|
if (thread == null || !thread.isAlive) {
|
||||||
|
logger.warn("service {} is already stopped", name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
logger.debug("stopping service {}", name)
|
||||||
|
thread.interrupt()
|
||||||
|
try {
|
||||||
|
thread.join()
|
||||||
|
} catch (ie: InterruptedException) {
|
||||||
|
}
|
||||||
|
if (!webappClosing) {
|
||||||
|
webServices[name] = Pair.of(service.left, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ServletContextListener interface */
|
||||||
|
override fun contextInitialized(sce: ServletContextEvent) {
|
||||||
|
context = sce.servletContext
|
||||||
|
logger.info("---------- Starting $webappName ----------")
|
||||||
|
webappRoot = context.getRealPath("/")
|
||||||
|
try {
|
||||||
|
// load default properties
|
||||||
|
properties.load(context.getResourceAsStream("/WEB-INF/pairgoth.default.properties"))
|
||||||
|
// override with system properties after stripping off the 'pairgoth.' prefix
|
||||||
|
System.getProperties().filter { (key, value) -> key is String && key.startsWith(PAIRGOTH_PROPERTIES_PREFIX)
|
||||||
|
}.forEach { (key, value) ->
|
||||||
|
properties[(key as String).removePrefix(PAIRGOTH_PROPERTIES_PREFIX)] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
// set system user agent string to empty string
|
||||||
|
System.setProperty("http.agent", "")
|
||||||
|
|
||||||
|
// disable (for now ?) the SSL certificate checks, because many sites
|
||||||
|
// fail to correctly implement SSL...
|
||||||
|
disableSSLCertificateChecks()
|
||||||
|
|
||||||
|
} catch (ioe: IOException) {
|
||||||
|
logger.error("webapp initialization error", ioe)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun contextDestroyed(sce: ServletContextEvent) {
|
||||||
|
logger.info("---------- Stopping $webappName ----------")
|
||||||
|
for (service in webServices.keys) stopService(service, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ServletContextAttributeListener interface */
|
||||||
|
override fun attributeAdded(event: ServletContextAttributeEvent) {}
|
||||||
|
override fun attributeRemoved(event: ServletContextAttributeEvent) {}
|
||||||
|
override fun attributeReplaced(event: ServletContextAttributeEvent) {}
|
||||||
|
|
||||||
|
/* HttpSessionListener interface */
|
||||||
|
override fun sessionCreated(se: HttpSessionEvent) {}
|
||||||
|
override fun sessionDestroyed(se: HttpSessionEvent) {}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth."
|
||||||
|
lateinit var webappRoot: String
|
||||||
|
lateinit var context: ServletContext
|
||||||
|
val properties = Properties()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,73 @@
|
|||||||
|
package org.jeudego.pairgoth.web
|
||||||
|
|
||||||
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.util.Colorizer.blue
|
||||||
|
import org.jeudego.pairgoth.util.Colorizer.green
|
||||||
|
import org.jeudego.pairgoth.util.toString
|
||||||
|
import org.slf4j.Logger
|
||||||
|
import java.io.StringWriter
|
||||||
|
import javax.servlet.http.HttpServletRequest
|
||||||
|
|
||||||
|
fun Logger.logRequest(req: HttpServletRequest, logHeaders: Boolean = false) {
|
||||||
|
val builder = StringBuilder()
|
||||||
|
builder.append(req.method).append(' ')
|
||||||
|
.append(req.scheme).append("://")
|
||||||
|
.append(req.localName)
|
||||||
|
val port = req.localPort
|
||||||
|
if (port != 80) builder.append(':').append(port)
|
||||||
|
/*
|
||||||
|
if (!req.contextPath.isEmpty()) {
|
||||||
|
builder.append(req.contextPath)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
builder.append(req.requestURI)
|
||||||
|
if (req.method == "GET") {
|
||||||
|
val qs = req.queryString
|
||||||
|
if (qs != null) builder.append('?').append(qs)
|
||||||
|
}
|
||||||
|
// builder.append(' ').append(req.getProtocol());
|
||||||
|
info(blue("<< {}"), builder.toString())
|
||||||
|
if (isTraceEnabled && logHeaders) {
|
||||||
|
// CB TODO - should be bufferized and asynchronously written in synchronous chunks
|
||||||
|
// so that header lines from parallel requests are not mixed up in the logs ;
|
||||||
|
// synchronizing the whole request log is not desirable
|
||||||
|
val headerNames = req.headerNames
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
val name = headerNames.nextElement()
|
||||||
|
val value = req.getHeader(name)
|
||||||
|
trace(blue("<< {}: {}"), name, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Logger.logPayload(prefix: String?, payload: Json, upstream: Boolean) {
|
||||||
|
val writer = StringWriter()
|
||||||
|
//payload.toPrettyString(writer, "");
|
||||||
|
payload.toString(writer)
|
||||||
|
if (isTraceEnabled) {
|
||||||
|
for (line in writer.toString().split("\n".toRegex()).dropLastWhile { it.isEmpty() }
|
||||||
|
.toTypedArray()) {
|
||||||
|
trace(if (upstream) blue("{}{}") else green("{}{}"), prefix, line)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var line = writer.toString()
|
||||||
|
val pos = line.indexOf('\n')
|
||||||
|
if (pos != -1) line = line.substring(0, pos)
|
||||||
|
if (line.length > 50) line = line.substring(0, 50) + "..."
|
||||||
|
debug(if (upstream) blue("{}{}") else green("{}{}"), prefix, line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun HttpServletRequest.getRemoteAddress(): String? {
|
||||||
|
var ip = getHeader("X-Forwarded-For")
|
||||||
|
if (ip == null) {
|
||||||
|
ip = remoteAddr
|
||||||
|
} else {
|
||||||
|
val comma = ip.indexOf(',')
|
||||||
|
if (comma != -1) {
|
||||||
|
ip = ip.substring(0, comma).trim { it <= ' ' } // keep the left-most IP address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ip
|
||||||
|
}
|
1
pom.xml
1
pom.xml
@@ -93,6 +93,7 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
|
<module>pairgoth-common</module>
|
||||||
<module>api-webapp</module>
|
<module>api-webapp</module>
|
||||||
<module>view-webapp</module>
|
<module>view-webapp</module>
|
||||||
<module>webserver</module>
|
<module>webserver</module>
|
||||||
|
@@ -137,6 +137,11 @@
|
|||||||
</dependencies>
|
</dependencies>
|
||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jeudego.pairgoth</groupId>
|
||||||
|
<artifactId>pairgoth-common</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<!-- main dependencies -->
|
<!-- main dependencies -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jetbrains.kotlin</groupId>
|
<groupId>org.jetbrains.kotlin</groupId>
|
||||||
|
@@ -22,7 +22,7 @@ abstract class RatingsHandler(val origin: RatingsManager.Ratings) {
|
|||||||
private var updated = false
|
private var updated = false
|
||||||
|
|
||||||
val url: URL by lazy {
|
val url: URL by lazy {
|
||||||
WebappManager.getProperty("ratings.${origin.name.lowercase(Locale.ROOT)}")?.let { URL(it) } ?: defaultURL
|
WebappManager.properties.getProperty("ratings.${origin.name.lowercase(Locale.ROOT)}")?.let { URL(it) } ?: defaultURL
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateIfNeeded(): Boolean {
|
fun updateIfNeeded(): Boolean {
|
||||||
|
@@ -91,7 +91,7 @@ object RatingsManager: Runnable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val logger = LoggerFactory.getLogger("ratings")
|
val logger = LoggerFactory.getLogger("ratings")
|
||||||
val path = Path.of(WebappManager.getProperty("ratings.path") ?: "ratings").also {
|
val path = Path.of(WebappManager.properties.getProperty("ratings.path") ?: "ratings").also {
|
||||||
val file = it.toFile()
|
val file = it.toFile()
|
||||||
if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.ratings.path must be a directory")
|
if (!file.mkdirs() && !file.isDirectory) throw Error("Property pairgoth.ratings.path must be a directory")
|
||||||
}
|
}
|
||||||
|
@@ -1,18 +0,0 @@
|
|||||||
package org.jeudego.pairgoth.util
|
|
||||||
|
|
||||||
import com.diogonunes.jcolor.Ansi
|
|
||||||
import com.diogonunes.jcolor.AnsiFormat
|
|
||||||
import com.diogonunes.jcolor.Attribute
|
|
||||||
|
|
||||||
private val blue = AnsiFormat(Attribute.BRIGHT_BLUE_TEXT())
|
|
||||||
private val green = AnsiFormat(Attribute.BRIGHT_GREEN_TEXT())
|
|
||||||
private val red = AnsiFormat(Attribute.BRIGHT_RED_TEXT())
|
|
||||||
private val bold = AnsiFormat(Attribute.BOLD())
|
|
||||||
|
|
||||||
object Colorizer {
|
|
||||||
|
|
||||||
fun blue(str: String) = Ansi.colorize(str, blue)
|
|
||||||
fun green(str: String) = Ansi.colorize(str, green)
|
|
||||||
fun red(str: String) = Ansi.colorize(str, red)
|
|
||||||
fun bold(str: String) = Ansi.colorize(str, bold)
|
|
||||||
}
|
|
@@ -1,25 +0,0 @@
|
|||||||
package org.jeudego.pairgoth.util
|
|
||||||
|
|
||||||
import com.republicate.kson.Json
|
|
||||||
import java.io.Reader
|
|
||||||
import java.io.Writer
|
|
||||||
|
|
||||||
|
|
||||||
fun Json.Companion.parse(reader: Reader) = Json.Companion.parse(object: Json.Input {
|
|
||||||
override fun read() = reader.read().toChar()
|
|
||||||
})
|
|
||||||
|
|
||||||
fun Json.toString(writer: Writer) = toString(object: Json.Output {
|
|
||||||
override fun writeChar(c: Char): Json.Output {
|
|
||||||
writer.write(c.code)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
override fun writeString(s: String): Json.Output {
|
|
||||||
writer.write(s)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
override fun writeString(s: String, from: Int, to: Int): Json.Output {
|
|
||||||
writer.write(s, from, to)
|
|
||||||
return this
|
|
||||||
}
|
|
||||||
})
|
|
@@ -14,7 +14,7 @@ class ApiTool {
|
|||||||
const val JSON = "application/json"
|
const val JSON = "application/json"
|
||||||
const val XML = "application/xml"
|
const val XML = "application/xml"
|
||||||
val apiRoot by lazy {
|
val apiRoot by lazy {
|
||||||
WebappManager.getProperty("api.external.url")?.let { "${it.removeSuffix("/")}/" }
|
WebappManager.properties.getProperty("api.external.url")?.let { "${it.removeSuffix("/")}/" }
|
||||||
?: throw Error("no configured API url")
|
?: throw Error("no configured API url")
|
||||||
}
|
}
|
||||||
val logger = LoggerFactory.getLogger("api")
|
val logger = LoggerFactory.getLogger("api")
|
||||||
|
@@ -20,7 +20,7 @@ class ApiServlet : AsyncProxyServlet() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val apiRoot by lazy {
|
private val apiRoot by lazy {
|
||||||
WebappManager.getProperty("api.external.url")?.let { "${it.removeSuffix("/")}" }
|
WebappManager.properties.getProperty("api.external.url")?.let { "${it.removeSuffix("/")}" }
|
||||||
?: throw Error("no configured API url")
|
?: throw Error("no configured API url")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -28,7 +28,7 @@ class AuthFilter: Filter {
|
|||||||
val response = resp as HttpServletResponse
|
val response = resp as HttpServletResponse
|
||||||
val uri = request.requestURI
|
val uri = request.requestURI
|
||||||
val session: HttpSession? = request.getSession(false)
|
val session: HttpSession? = request.getSession(false)
|
||||||
val auth = WebappManager.getProperty("auth") ?: throw Error("authentication not configured")
|
val auth = WebappManager.properties.getProperty("auth") ?: throw Error("authentication not configured")
|
||||||
val forwarded = request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) != null
|
val forwarded = request.getAttribute(RequestDispatcher.FORWARD_REQUEST_URI) != null
|
||||||
|
|
||||||
if (auth == "oauth" && uri.startsWith("/oauth/")) {
|
if (auth == "oauth" && uri.startsWith("/oauth/")) {
|
||||||
@@ -66,6 +66,5 @@ class AuthFilter: Filter {
|
|||||||
val nolangUri = uri.replace(Regex("^/../"), "/")
|
val nolangUri = uri.replace(Regex("^/../"), "/")
|
||||||
return whitelist.contains(nolangUri)
|
return whitelist.contains(nolangUri)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,7 @@ class LoginServlet: HttpServlet() {
|
|||||||
val mimeType = if (sep == -1) contentType else contentType.substring(0, sep).trim { it <= ' ' }
|
val mimeType = if (sep == -1) contentType else contentType.substring(0, sep).trim { it <= ' ' }
|
||||||
if (!isJson(mimeType)) throw Error("expecting json")
|
if (!isJson(mimeType)) throw Error("expecting json")
|
||||||
val payload = Json.Companion.parse(req.reader.readText())?.asObject() ?: throw Error("null json")
|
val payload = Json.Companion.parse(req.reader.readText())?.asObject() ?: throw Error("null json")
|
||||||
val user = when (WebappManager.getProperty("auth")) {
|
val user = when (WebappManager.properties.getProperty("auth")) {
|
||||||
"sesame" -> checkSesame(payload)
|
"sesame" -> checkSesame(payload)
|
||||||
else -> checkLoginPass(payload)
|
else -> checkLoginPass(payload)
|
||||||
} ?: throw Error("authentication failed")
|
} ?: throw Error("authentication failed")
|
||||||
@@ -33,7 +33,7 @@ class LoginServlet: HttpServlet() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun checkSesame(payload: Json.Object): Json.Object? {
|
fun checkSesame(payload: Json.Object): Json.Object? {
|
||||||
val expected = WebappManager.getProperty("auth.sesame") ?: throw Error("sesame wrongly configured")
|
val expected = WebappManager.properties.getProperty("auth.sesame") ?: throw Error("sesame wrongly configured")
|
||||||
return if (payload.getString("sesame")?.equals(expected) == true) Json.Object("logged" to true) else null
|
return if (payload.getString("sesame")?.equals(expected) == true) Json.Object("logged" to true) else null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -16,175 +16,43 @@ import javax.servlet.http.HttpSessionEvent
|
|||||||
import javax.servlet.http.HttpSessionListener
|
import javax.servlet.http.HttpSessionListener
|
||||||
|
|
||||||
@WebListener
|
@WebListener
|
||||||
class WebappManager : ServletContextListener, ServletContextAttributeListener, HttpSessionListener {
|
class WebappManager : BaseWebappManager("View Webapp", "view") {
|
||||||
private fun disableSSLCertificateChecks() {
|
|
||||||
// see http://www.nakov.com/blog/2009/07/16/disable-certificate-validation-in-java-ssl-connections/
|
|
||||||
try {
|
|
||||||
// Create a trust manager that does not validate certificate chains
|
|
||||||
val trustAllCerts = arrayOf<TrustManager>(object : X509TrustManager {
|
|
||||||
override fun getAcceptedIssuers(): Array<X509Certificate>? {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("TrustAllX509TrustManager")
|
|
||||||
override fun checkClientTrusted(certs: Array<X509Certificate>, authType: String) {}
|
|
||||||
@Suppress("TrustAllX509TrustManager")
|
|
||||||
override fun checkServerTrusted(certs: Array<X509Certificate>, authType: String) {}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// Install the all-trusting trust manager
|
|
||||||
val sc = SSLContext.getInstance("SSL")
|
|
||||||
sc.init(null, trustAllCerts, SecureRandom())
|
|
||||||
HttpsURLConnection.setDefaultSSLSocketFactory(sc.socketFactory)
|
|
||||||
|
|
||||||
// Create all-trusting host name verifier
|
|
||||||
val allHostsValid = HostnameVerifier { hostname, session -> true }
|
|
||||||
|
|
||||||
// Install the all-trusting host verifier
|
|
||||||
HttpsURLConnection.setDefaultHostnameVerifier(allHostsValid)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
logger.error("could not disable SSL certificate checks", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ServletContextListener interface */
|
/* ServletContextListener interface */
|
||||||
override fun contextInitialized(sce: ServletContextEvent) {
|
override fun contextInitialized(sce: ServletContextEvent) {
|
||||||
context = sce.servletContext
|
super.contextInitialized(sce)
|
||||||
logger.info("---------- Starting $WEBAPP_NAME ----------")
|
|
||||||
logger.info("info level is active")
|
|
||||||
logger.debug("debug level is active")
|
|
||||||
logger.trace("trace level is active")
|
|
||||||
webappRoot = context.getRealPath("/")
|
|
||||||
try {
|
|
||||||
// load default properties
|
|
||||||
properties.load(context.getResourceAsStream("/WEB-INF/pairgoth.default.properties"))
|
|
||||||
// override with system properties after stripping off the 'pairgoth.' prefix
|
|
||||||
System.getProperties().filter { (key, value) -> key is String && key.startsWith(PAIRGOTH_PROPERTIES_PREFIX)
|
|
||||||
}.forEach { (key, value) ->
|
|
||||||
properties[(key as String).removePrefix(PAIRGOTH_PROPERTIES_PREFIX)] = value
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.info("pairgoth server ${properties["version"]} with profile ${properties["env"]}")
|
// publish some properties to the webapp context; for easy access from the template
|
||||||
|
context.setAttribute("env", properties.getProperty("env") ?: "dev")
|
||||||
// publish some properties to the webapp context; for easy access from the template
|
context.setAttribute("version", properties.getProperty("version") ?: "?")
|
||||||
context.setAttribute("env", properties.getProperty("env") ?: "dev")
|
val auth = properties.getProperty("auth") ?: "none"
|
||||||
context.setAttribute("version", properties.getProperty("version") ?: "?")
|
context.setAttribute("auth", auth)
|
||||||
val auth = properties.getProperty("auth") ?: "none"
|
when (auth) {
|
||||||
context.setAttribute("auth", auth)
|
"none", "sesame" -> {}
|
||||||
when (auth) {
|
"oauth" -> {
|
||||||
"none", "sesame" -> {}
|
properties.getProperty("oauth.providers")?.let {
|
||||||
"oauth" -> {
|
val providers = it.split(Regex("\\s*,\\s*"))
|
||||||
properties.getProperty("oauth.providers")?.let {
|
context.setAttribute("oauthProviders", providers)
|
||||||
val providers = it.split(Regex("\\s*,\\s*"))
|
providers.forEach { provider ->
|
||||||
context.setAttribute("oauthProviders", providers)
|
context.setAttribute("${provider}Provider", OauthHelperFactory.getHelper(provider))
|
||||||
providers.forEach { provider ->
|
|
||||||
context.setAttribute("${provider}Provider", OauthHelperFactory.getHelper(provider))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> throw Error("Unhandled auth: $auth")
|
|
||||||
}
|
}
|
||||||
|
else -> throw Error("Unhandled auth: $auth")
|
||||||
// set system user agent string to empty string
|
|
||||||
System.setProperty("http.agent", "")
|
|
||||||
|
|
||||||
// disable (for now ?) the SSL certificate checks, because many sites
|
|
||||||
// fail to correctly implement SSL...
|
|
||||||
disableSSLCertificateChecks()
|
|
||||||
|
|
||||||
registerService("ratings", RatingsManager)
|
|
||||||
startService("ratings")
|
|
||||||
|
|
||||||
} catch (ioe: IOException) {
|
|
||||||
logger.error("webapp initialization error", ioe)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerService("ratings", RatingsManager)
|
||||||
|
startService("ratings")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun contextDestroyed(sce: ServletContextEvent) {
|
override fun contextDestroyed(sce: ServletContextEvent) {
|
||||||
logger.info("---------- Stopping $WEBAPP_NAME ----------")
|
super.contextDestroyed(sce)
|
||||||
|
|
||||||
Translator.notifyExiting()
|
Translator.notifyExiting()
|
||||||
|
|
||||||
val context = sce.servletContext
|
|
||||||
for (service in webServices.keys) stopService(service, true)
|
|
||||||
// ??? DriverManager.deregisterDriver(com.mysql.cj.jdbc.Driver ...);
|
|
||||||
|
|
||||||
logger.info("---------- Stopped $WEBAPP_NAME ----------")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ServletContextAttributeListener interface */
|
|
||||||
override fun attributeAdded(event: ServletContextAttributeEvent) {}
|
|
||||||
override fun attributeRemoved(event: ServletContextAttributeEvent) {}
|
|
||||||
override fun attributeReplaced(event: ServletContextAttributeEvent) {}
|
|
||||||
|
|
||||||
/* HttpSessionListener interface */
|
|
||||||
override fun sessionCreated(se: HttpSessionEvent) {}
|
|
||||||
override fun sessionDestroyed(se: HttpSessionEvent) {}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val WEBAPP_NAME = "Pairgoth Web Client"
|
val properties get() = BaseWebappManager.properties
|
||||||
const val PAIRGOTH_PROPERTIES_PREFIX = "pairgoth."
|
val context get() = BaseWebappManager.context
|
||||||
lateinit var webappRoot: String
|
fun getMandatoryProperty(prop: String) = properties.getProperty(prop) ?: throw Error("missing property: $prop")
|
||||||
lateinit var context: ServletContext
|
|
||||||
private val webServices: MutableMap<String?, Pair<Runnable, Thread?>> = TreeMap()
|
|
||||||
var logger = LoggerFactory.getLogger(WebappManager::class.java)
|
|
||||||
val properties = Properties()
|
|
||||||
fun getProperty(prop: String): String? {
|
|
||||||
return properties.getProperty(prop)
|
|
||||||
}
|
|
||||||
fun getMandatoryProperty(prop: String): String {
|
|
||||||
return getProperty(prop) ?: throw Error("missing property: ${prop}")
|
|
||||||
}
|
|
||||||
|
|
||||||
val webappURL by lazy { getProperty("webapp.external.url") }
|
|
||||||
|
|
||||||
private val services = mutableMapOf<String, Pair<Runnable, Thread>>()
|
|
||||||
|
|
||||||
@JvmOverloads
|
|
||||||
fun registerService(name: String?, task: Runnable, initialStatus: Boolean? = null) {
|
|
||||||
if (webServices.containsKey(name)) {
|
|
||||||
logger.warn("service {} already registered")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.debug("registered service {}", name)
|
|
||||||
webServices[name] =
|
|
||||||
Pair.of(task, null)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun startService(name: String?) {
|
|
||||||
val service = webServices[name]!!
|
|
||||||
if (service.right != null && service.right!!.isAlive) {
|
|
||||||
logger.warn("service {} is already running", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.debug("starting service {}", name)
|
|
||||||
val thread = Thread(service.left, name)
|
|
||||||
thread.start()
|
|
||||||
webServices[name] =
|
|
||||||
Pair.of(
|
|
||||||
service.left,
|
|
||||||
thread
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmOverloads
|
|
||||||
fun stopService(name: String?, webappClosing: Boolean = false) {
|
|
||||||
val service = webServices[name]!!
|
|
||||||
val thread = service.right
|
|
||||||
if (thread == null || !thread.isAlive) {
|
|
||||||
logger.warn("service {} is already stopped", name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
logger.debug("stopping service {}", name)
|
|
||||||
thread.interrupt()
|
|
||||||
try {
|
|
||||||
thread.join()
|
|
||||||
} catch (ie: InterruptedException) {
|
|
||||||
}
|
|
||||||
if (!webappClosing) {
|
|
||||||
webServices[name] = Pair.of(service.left, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user