diff --git a/oauth.sh b/oauth.sh
new file mode 100755
index 0000000..6d2bf1f
--- /dev/null
+++ b/oauth.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+
+trap 'kill $CSSWATCH; exit' INT
+( cd view-webapp; ./csswatch.sh ) &
+CSSWATCH=$!
+
+export MAVEN_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=*:5006"
+#mvn --projects view-webapp -Dpairgoth.api.url=http://localhost:8085/api/ package jetty:run
+mvn -DskipTests=true \
+ -Dpairgoth.auth=oauth \
+ -Dpairgoth.oauth.providers=ffg \
+ -Dpairgoth.oauth.ffg.secret=43f3a67bffcb5054d2f1b0e2a2374bdc \
+ -Dwebapp.external.url=http://localhost:8080
+ --projects view-webapp package jetty:run
+kill $CSSWATCH
diff --git a/pom.xml b/pom.xml
index 3e50889..5118b4a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,7 +80,8 @@
file
tournamentfiles
none
-
+ this_should_be_overriden_with_a_command_line_option
+ pairtogh
587
diff --git a/view-webapp/pom.xml b/view-webapp/pom.xml
index 37f1fd8..7aefcb6 100644
--- a/view-webapp/pom.xml
+++ b/view-webapp/pom.xml
@@ -232,9 +232,20 @@
-->
- com.squareup.okhttp3
- okhttp
- 4.8.1
+ org.apache.httpcomponents
+ httpclient
+ 4.5.14
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+ org.apache.httpcomponents
+ httpmime
+ 4.5.14
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/FFGHelper.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/FFGHelper.kt
new file mode 100644
index 0000000..71310a7
--- /dev/null
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/FFGHelper.kt
@@ -0,0 +1,29 @@
+package org.jeudego.pairgoth.oauth
+
+class FFGHelper : OAuthHelper() {
+ override val name: String
+ get() = "facebook"
+
+ private val FFG_HOST = "https://testffg"
+
+ override fun getLoginURL(sessionId: String?): String {
+ return "$FFG_HOST/oauth2/entry_point.php/authorize?" +
+ "client_id=" + clientId +
+ "&redirect_uri=" + redirectURI +
+ "&scope=email"
+ }
+
+ override fun getAccessTokenURL(code: String): String? {
+ return "$FFG_HOST/oauth2/entry_point.php/access_token?" +
+ "client_id=" + clientId +
+ "&redirect_uri=" + redirectURI +
+ "&client_secret=" + secret +
+ "&code=" + code
+ }
+
+ override fun getUserInfosURL(accessToken: String): String? {
+ return "$FFG_HOST/oauth2/entry_point.php/user_info?" +
+ "field=email" +
+ "&access_token=" + accessToken
+ }
+}
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt
index 2c964c2..4c54b31 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OAuthHelper.kt
@@ -7,6 +7,10 @@ import org.jeudego.pairgoth.web.WebappManager
//import com.republicate.modality.util.AESCryptograph
//import com.republicate.modality.util.Cryptograph
import org.apache.commons.codec.binary.Base64
+import org.jeudego.pairgoth.util.AESCryptograph
+import org.jeudego.pairgoth.util.ApiClient.JsonApiClient
+import org.jeudego.pairgoth.util.Cryptograph
+import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.IOException
import java.io.UnsupportedEncodingException
@@ -21,7 +25,7 @@ abstract class OAuthHelper {
protected get() = WebappManager.getMandatoryProperty("oauth." + name + ".secret")
protected val redirectURI: String?
protected get() = try {
- val uri: String = WebappManager.Companion.getProperty("webapp.external.url") + "/oauth.html"
+ val uri: String = WebappManager.getMandatoryProperty("webapp.external.url") + "/oauth"
URLEncoder.encode(uri, "UTF-8")
} catch (uee: UnsupportedEncodingException) {
logger.error("could not encode redirect URI", uee)
@@ -48,30 +52,23 @@ abstract class OAuthHelper {
@Throws(IOException::class)
fun getUserEmail(accessToken: String): String {
- val json: Json.Object = Json.Object()
- // TODO
- // apiClient.get(getUserInfosURL(accessToken))
- return json.getString("email") ?: throw IOException("could not fetch email")
+ val json = getUserInfosURL(accessToken)?.let { JsonApiClient.get(it).asObject() }
+ return json?.getString("email") ?: throw IOException("could not fetch email")
}
companion object {
- protected var logger = LoggerFactory.getLogger("oauth")
+ protected var logger: Logger = LoggerFactory.getLogger("oauth")
private const val salt = "0efd28fb53cbac42"
-// private val sessionIdCrypto: Cryptograph = AESCryptograph().apply {
-// init(salt)
-// }
+ private val sessionIdCrypto: Cryptograph = AESCryptograph().apply {
+ init(salt)
+ }
private fun encrypt(input: String): String {
- return "TODO"
-// return Base64.encodeBase64URLSafeString(sessionIdCrypto.encrypt(input))
+ return Base64.encodeBase64URLSafeString(sessionIdCrypto.encrypt(input))
}
private fun decrypt(input: String): String {
- return "TODO"
-// return sessionIdCrypto.decrypt(Base64.decodeBase64(input))
+ return sessionIdCrypto.decrypt(Base64.decodeBase64(input))
}
-
- // TODO
- // private val apiClient: ApiClient = ApiClient()
}
}
\ No newline at end of file
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OauthHelperFactory.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OauthHelperFactory.kt
index 45568da..6415a5b 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OauthHelperFactory.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/oauth/OauthHelperFactory.kt
@@ -1,6 +1,7 @@
package org.jeudego.pairgoth.oauth
object OauthHelperFactory {
+ private val ffg: OAuthHelper = FFGHelper()
private val facebook: OAuthHelper = FacebookHelper()
private val google: OAuthHelper = GoogleHelper()
private val instagram: OAuthHelper = InstagramHelper()
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/AESCryptograph.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/AESCryptograph.kt
new file mode 100644
index 0000000..691fde9
--- /dev/null
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/AESCryptograph.kt
@@ -0,0 +1,57 @@
+package org.jeudego.pairgoth.util
+
+import java.nio.charset.Charset
+import javax.crypto.Cipher
+import javax.crypto.Cipher.DECRYPT_MODE
+import javax.crypto.Cipher.ENCRYPT_MODE
+import javax.crypto.SecretKey
+import javax.crypto.spec.SecretKeySpec
+
+
+/**
+ * Basic AES encryption. Please note that it uses the ECB block mode, which has the advantage
+ * to not require random bytes, thus providing some *persistence* for the encrypted data, but
+ * at the expense of some security weaknesses. The purpose here is just to encrypt temporary
+ * session ids in URLs, not to protect state secrets.
+ */
+class AESCryptograph : Cryptograph {
+
+ override fun init(key: String) {
+ val bytes = key.toByteArray(Charset.defaultCharset())
+ if (bytes.size < 16) {
+ throw Error("not enough secret bytes")
+ }
+ val secret: SecretKey = SecretKeySpec(bytes, 0, 16, ALGORITHM)
+ try {
+ encrypt.init(ENCRYPT_MODE, secret)
+ decrypt.init(DECRYPT_MODE, secret)
+ } catch (e: Exception) {
+ throw RuntimeException("cyptograph initialization failed", e)
+ }
+ }
+
+ override fun encrypt(str: String): ByteArray {
+ return try {
+ encrypt.doFinal(str.toByteArray(Charset.defaultCharset()))
+ } catch (e: Exception) {
+ throw RuntimeException("encryption failed failed", e)
+ }
+ }
+
+ override fun decrypt(bytes: ByteArray): String {
+ return try {
+ String(decrypt.doFinal(bytes), Charset.defaultCharset())
+ } catch (e: Exception) {
+ throw RuntimeException("encryption failed failed", e)
+ }
+ }
+
+ private var encrypt = Cipher.getInstance(CIPHER)
+ private var decrypt = Cipher.getInstance(CIPHER)
+
+ companion object {
+ private val CIPHER = "AES/ECB/PKCS5Padding"
+ private val ALGORITHM = "AES"
+
+ }
+}
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/ApiClient.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/ApiClient.kt
new file mode 100644
index 0000000..b966219
--- /dev/null
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/ApiClient.kt
@@ -0,0 +1,341 @@
+package org.jeudego.pairgoth.util.ApiClient
+
+import org.jeudego.pairgoth.util.parse
+
+import com.republicate.kson.Json
+import org.apache.http.*
+import org.apache.http.client.ClientProtocolException
+import org.apache.http.client.config.CookieSpecs
+import org.apache.http.client.config.RequestConfig
+import org.apache.http.client.entity.UrlEncodedFormEntity
+import org.apache.http.client.methods.*
+import org.apache.http.config.SocketConfig
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory
+import org.apache.http.entity.ContentType
+import org.apache.http.entity.EntityTemplate
+import org.apache.http.entity.StringEntity
+import org.apache.http.entity.mime.HttpMultipartMode
+import org.apache.http.entity.mime.MultipartEntityBuilder
+import org.apache.http.entity.mime.content.StringBody
+import org.apache.http.impl.client.HttpClients
+import org.apache.http.message.BasicHeader
+import org.apache.http.message.BasicNameValuePair
+import org.apache.http.ssl.SSLContexts
+import org.apache.http.util.EntityUtils
+import org.slf4j.LoggerFactory
+import org.w3c.dom.Element
+import org.xml.sax.InputSource
+import java.io.BufferedReader
+import java.io.ByteArrayInputStream
+import java.io.IOException
+import java.io.InputStreamReader
+import java.io.OutputStream
+import java.io.StringReader
+import java.net.ProtocolException
+import java.net.URLDecoder
+import java.nio.charset.Charset
+import java.nio.charset.StandardCharsets
+import java.util.concurrent.TimeUnit
+import javax.xml.parsers.DocumentBuilderFactory
+
+
+/**
+ * This class implements a basic API client around Apache HTTP client.
+ */
+
+val API_CLIENT_TIMEOUT = 60000
+
+// TODO cookieStore ? credentialsProvider ?
+// CookieStore cookieStore = new BasicCookieStore();
+// CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+
+private val client = HttpClients.custom()
+ .setSSLSocketFactory(
+ SSLConnectionSocketFactory(
+ SSLContexts.createSystemDefault(), arrayOf("TLSv1.2"),
+ null,
+ SSLConnectionSocketFactory.getDefaultHostnameVerifier()
+ )
+ )
+ .setConnectionTimeToLive(1, TimeUnit.MINUTES)
+ .setDefaultSocketConfig(
+ SocketConfig.custom()
+ .setSoTimeout(API_CLIENT_TIMEOUT)
+ .build()
+ )
+ .setDefaultRequestConfig(
+ RequestConfig.custom()
+ .setConnectTimeout(API_CLIENT_TIMEOUT)
+ .setSocketTimeout(API_CLIENT_TIMEOUT)
+ .setCookieSpec(CookieSpecs.STANDARD)
+ .build()
+ )
+ .build()
+
+// CB TODO - this should go elsewhere
+fun ByteArray.reader(charset: Charset) =
+ BufferedReader(InputStreamReader(ByteArrayInputStream(this), charset))
+
+fun header(name: String, value: String) = BasicHeader(name, value)
+
+fun param(name: String, value: String) = BasicNameValuePair(name, value)
+
+// Incomplete list of binary content types, that we should avoid to log
+fun ContentType.isBinary() = mimeType.startsWith("image/") || mimeType.endsWith("pdf")
+fun ContentType.isText() = !isBinary()
+
+data class BinaryData(val contentType: ContentType, val data: ByteArray, val name: String, val filename: String? = null)
+
+fun buildMultiPartBody(payload: Json.Object, binaryData: BinaryData? = null): HttpEntity {
+ val builder = MultipartEntityBuilder.create()
+ builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE)
+ for (entry in payload.entries) {
+ builder.addPart(entry.key, StringBody(entry.value.toString(), ContentType.MULTIPART_FORM_DATA))
+ }
+ binaryData?.let {
+ builder.addBinaryBody(it.name, it.data, it.contentType, it.filename ?: "file")
+ }
+ return builder.build()
+}
+
+abstract class BaseApiClient {
+
+ companion object {
+ internal var logger = LoggerFactory.getLogger("api")
+ }
+
+ private fun build(method: String, url: String, body: B? = null, vararg keyValues: NameValuePair): HttpRequestBase {
+
+ val headers = mutableListOf()
+ val params = mutableListOf()
+ for (pair in keyValues) {
+ when (pair) {
+ is Header -> headers.add(pair)
+ else -> params.add(pair)
+ }
+ }
+
+ val req: HttpRequestBase = when (method) {
+ "GET" -> {
+ val paramsString = params.map { "${it.name}=${it.value}" }.joinToString("&")
+ val finalURL = url + (if (url.contains('?')) '&' else '?' ) + paramsString
+ HttpGet(finalURL)
+ }
+ "POST" -> {
+ HttpPost(url).also { req ->
+ if (params.isNotEmpty() && body != null) {
+ throw ClientProtocolException("specify POST body or POST parameters but not both")
+ }
+ if (params.isNotEmpty()) {
+ val entity = UrlEncodedFormEntity(params, "UTF-8")
+ req.entity = entity
+ } else if (body != null) {
+ val entity = if (body is HttpEntity) body
+ else StringEntity(body.toString(),
+ if (body is Json) ContentType.APPLICATION_JSON
+ else ContentType.TEXT_PLAIN.withCharset(StandardCharsets.UTF_8))
+ req.entity = entity
+ }
+ }
+ }
+ "PATCH" -> {
+ HttpPatch(url).also { req ->
+ if (params.isNotEmpty() && body != null) {
+ throw ClientProtocolException("specify PATCH body or PATCH parameters but not both")
+ }
+ if (params.isNotEmpty()) {
+ val entity = UrlEncodedFormEntity(params, "UTF-8")
+ req.entity = entity
+ } else if (body != null) {
+ val entity = if (body is HttpEntity) body
+ else EntityTemplate { outputstream: OutputStream ->
+ outputstream.write(body.toString().toByteArray())
+ outputstream.flush()
+ }.also {
+ it.setContentType(ContentType.APPLICATION_JSON.toString())
+ }
+ req.entity = entity
+ }
+ }
+ }
+ "DELETE" -> {
+ HttpDelete(url).also { req ->
+ if (params.isNotEmpty() || body != null) {
+ throw ClientProtocolException("DELETE body or DELETE parameters not supported")
+ }
+ }
+ }
+ else -> throw ClientProtocolException("unhandled method: $method")
+ }
+ headers.addAll(acceptHeaders())
+ headers.forEach {
+ req.addHeader(it)
+ }
+ return req
+ }
+
+ private fun submit(req: HttpRequestBase): Pair {
+ try {
+ val resp: HttpResponse = client.execute(req)
+ val statusLine = resp.statusLine
+ val status = statusLine.statusCode
+ when {
+ status in 200..299 -> {
+ if (status != 204 && resp.entity == null) throw ClientProtocolException("Response is empty")
+ val body = resp.entity?.let { EntityUtils.toByteArray(it) }
+ val contentType = resp.entity?.let { ContentType.get(it) }
+ if (logger.isTraceEnabled) traceResponse(resp, body?.toString(contentType?.charset ?: StandardCharsets.UTF_8), contentType)
+ return Pair(body ?: "{}".toByteArray(StandardCharsets.UTF_8), contentType ?: ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8))
+ }
+ else -> {
+ val body = resp.entity?.let { EntityUtils.toString(it) }
+ val contentType = resp.entity?.let { ContentType.get(it) }
+ if (logger.isTraceEnabled) traceResponse(resp, body, contentType)
+ var message = statusLine.toString()
+ if (body != null) message = "$message - $body"
+ throw IOException(message)
+ }
+ }
+ } finally {
+ req.releaseConnection()
+ }
+ }
+
+ private fun traceRequest(req: HttpRequestBase, body: String? = null) {
+ logger.trace(">> ${req.method} ${req.uri}")
+ for (header in req.allHeaders) {
+ logger.trace(">> $header")
+ }
+ if (body != null) {
+ val contentType = req.allHeaders.firstOrNull { it.name == HttpHeaders.CONTENT_TYPE }?.let {
+ ContentType.parse(it.value)
+ }
+ if (contentType?.isText() ?: true) {
+ for (line in body.split(Regex("[\r\n]"))) {
+ logger.trace(">> $line")
+ }
+ } else {
+ logger.trace(">> ${contentType?.toString() ?: "unknown mime type"}, ${body.length} bytes")
+ }
+ }
+ }
+
+ private fun traceResponse(resp: HttpResponse, body: String?, contentType: ContentType?) {
+ val statusLine = resp.statusLine
+ logger.trace("<< ${statusLine.statusCode} ${statusLine.reasonPhrase}")
+ for (header in resp.allHeaders) logger.trace("<< $header")
+ if (body != null) {
+ val knownContentType = contentType ?: resp.allHeaders.firstOrNull { it.name == HttpHeaders.CONTENT_TYPE }?.let {
+ ContentType.parse(it.value)
+ }
+ if (knownContentType?.isText() ?: true) {
+ for (line in body.split(Regex("[\r\n]"))) {
+ logger.trace("<< $line")
+ }
+ } else {
+ logger.trace("<< ${contentType?.toString() ?: "unknown mime type"}, ${body.length} bytes")
+ }
+ }
+ }
+
+ protected abstract fun acceptHeaders(): List
+
+ protected abstract fun parseResult(result: Pair): T
+
+ fun get(url: String, vararg with: NameValuePair): T {
+ val req = build("GET", url, null, *with)
+ if (logger.isTraceEnabled) traceRequest(req)
+ val result = submit(req)
+ return parseResult(result)
+ }
+
+ fun post(url: String, body: B?, vararg with: NameValuePair): T {
+ val req = build("POST", url, body, *with)
+ if (logger.isTraceEnabled) traceRequest(req, body?.toString())
+ val result = submit(req)
+ return parseResult(result)
+ }
+
+ fun patch(url: String, body: B?, vararg with: NameValuePair): T {
+ val req = build("PATCH", url, body, *with)
+ if (logger.isTraceEnabled) traceRequest(req, body?.toString())
+ val result = submit(req)
+ return parseResult(result)
+ }
+
+ fun delete(url: String, body: B?, vararg with: NameValuePair): T {
+ val req = build("DELETE", url, body, *with)
+ if (logger.isTraceEnabled) traceRequest(req, body?.toString())
+ val result = submit(req)
+ return parseResult(result)
+ }
+}
+
+object AgnosticApiClient: BaseApiClient>() {
+ override fun acceptHeaders() = listOf(BasicHeader(HttpHeaders.ACCEPT, "*/*"))
+ override fun parseResult(result: Pair) = result
+}
+
+object JsonApiClient: BaseApiClient() {
+
+ override fun acceptHeaders(): List {
+ return listOf(BasicHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_JSON.mimeType))
+ }
+
+ override fun parseResult(result: Pair): Json {
+ when (result.second.mimeType) {
+ ContentType.APPLICATION_JSON.mimeType, "application/vnd.api+json" -> {
+ return Json.parse(result.first.reader(result.second.charset ?: StandardCharsets.UTF_8))!!
+ }
+ ContentType.APPLICATION_FORM_URLENCODED.mimeType -> {
+ val json = Json.MutableObject()
+ val charset = result.second.charset ?: StandardCharsets.UTF_8
+ val decoded = URLDecoder.decode(result.first.toString(charset), charset.name())
+ decoded.split("&").forEach {
+ val keyValue = it.split(Regex("="), 2)
+ if (keyValue.size != 2) throw ProtocolException("expecting a key-value pair: $it")
+ val prev = json.put(keyValue[0], keyValue[1])
+ if (prev != null) throw ClientProtocolException("Unsupported redundant values in response for key: " + keyValue[0]);
+ }
+ return json
+ }
+ else -> throw ClientProtocolException("invalid content type: ${result.second.mimeType}")
+ }
+ }
+}
+
+// CB TODO - needs to factorize XmlUtils with server => needs a common module
+//
+//object XmlApiClient: BaseApiClient() {
+//
+// override fun acceptHeaders(): List {
+// return listOf(
+// BasicHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_XML.mimeType),
+// BasicHeader(HttpHeaders.ACCEPT, ContentType.APPLICATION_SOAP_XML.mimeType)
+// )
+// }
+//
+// override fun parseResult(result: Pair): Element {
+// when (result.second.mimeType) {
+// ContentType.APPLICATION_XML.mimeType, ContentType.APPLICATION_SOAP_XML.mimeType -> {
+// return XmlUtils.parse(result.first.reader(result.second.charset ?: StandardCharsets.UTF_8)) ?: throw ClientProtocolException("empty XML body")
+// }
+// else -> throw ClientProtocolException("invalid content type: ${result.second.mimeType}")
+// }
+// }
+//}
+//
+//object SoapTextApiClient: BaseApiClient() {
+//
+// override fun acceptHeaders() = listOf(BasicHeader(HttpHeaders.ACCEPT, "*/*"))
+//
+// override fun parseResult(result: Pair): Element {
+// when (result.second.mimeType) {
+// ContentType.TEXT_XML.mimeType -> {
+// return XmlUtils.parse(result.first.reader(result.second.charset ?: StandardCharsets.UTF_8)) ?: throw ClientProtocolException("empty XML body")
+// }
+// else -> throw ClientProtocolException("invalid content type: ${result.second.mimeType}")
+// }
+// }
+//}
+//
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Cryptograph.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Cryptograph.kt
new file mode 100644
index 0000000..2a221d0
--- /dev/null
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/util/Cryptograph.kt
@@ -0,0 +1,29 @@
+package org.jeudego.pairgoth.util
+
+import java.io.Serializable
+
+/**
+ * Cryptograph - used to encrypt and decrypt strings.
+ *
+ */
+interface Cryptograph : Serializable {
+ /**
+ * init.
+ * @param random random string
+ */
+ fun init(random: String)
+
+ /**
+ * encrypt.
+ * @param str string to encrypt
+ * @return encrypted string
+ */
+ fun encrypt(str: String): ByteArray
+
+ /**
+ * decrypt.
+ * @param bytes to decrypt
+ * @return decrypted string
+ */
+ fun decrypt(bytes: ByteArray): String
+}
diff --git a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt
index 57c5ff0..657d41a 100644
--- a/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt
+++ b/view-webapp/src/main/kotlin/org/jeudego/pairgoth/web/WebappManager.kt
@@ -67,8 +67,14 @@ class WebappManager : ServletContextListener, ServletContextAttributeListener, H
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["env"])
- context.setAttribute("version", properties["version"] ?: "?")
+ context.setAttribute("env", properties.getProperty("env") ?: "dev")
+ context.setAttribute("version", properties.getProperty("version") ?: "?")
+ properties.get("auth")?.let {
+
+ }
+ properties.getProperty("oauth.providers")?.let {
+ context.setAttribute("oauth.providers", it.split(Regex("\\s*,\\s*")))
+ }
// set system user agent string to empty string
System.setProperty("http.agent", "")