Wip
This commit is contained in:
@@ -16,7 +16,6 @@
|
|||||||
<description>PairGoth pairing system</description>
|
<description>PairGoth pairing system</description>
|
||||||
<url>TODO</url>
|
<url>TODO</url>
|
||||||
<properties>
|
<properties>
|
||||||
<pac4j.version>5.7.1</pac4j.version>
|
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
<defaultGoal>package</defaultGoal>
|
<defaultGoal>package</defaultGoal>
|
||||||
@@ -166,9 +165,9 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<!-- auth -->
|
<!-- auth -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.pac4j</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>pac4j-oauth</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
<version>${pac4j.version}</version>
|
<version>1.16.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- logging -->
|
<!-- logging -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@@ -1,6 +1,8 @@
|
|||||||
package org.jeudego.pairgoth.api
|
package org.jeudego.pairgoth.api
|
||||||
|
|
||||||
import com.republicate.kson.Json
|
import com.republicate.kson.Json
|
||||||
|
import org.jeudego.pairgoth.util.AESCryptograph
|
||||||
|
import org.jeudego.pairgoth.util.Cryptograph
|
||||||
import javax.servlet.http.HttpServletRequest
|
import javax.servlet.http.HttpServletRequest
|
||||||
import javax.servlet.http.HttpServletResponse
|
import javax.servlet.http.HttpServletResponse
|
||||||
import javax.servlet.http.HttpSession
|
import javax.servlet.http.HttpSession
|
||||||
@@ -9,11 +11,14 @@ class TokenHandler: ApiHandler {
|
|||||||
companion object {
|
companion object {
|
||||||
const val AUTH_KEY = "pairgoth-auth"
|
const val AUTH_KEY = "pairgoth-auth"
|
||||||
const val CHALLENGE_KEY = "pairgoth-challenge"
|
const val CHALLENGE_KEY = "pairgoth-challenge"
|
||||||
|
private val cryptograph: Cryptograph = AESCryptograph().apply {
|
||||||
|
init("78659783ed8ccc0e")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
override fun get(request: HttpServletRequest, response: HttpServletResponse): Json? {
|
||||||
val auth = request.session.getAttribute(AUTH_KEY) as String?
|
val auth = request.session.getAttribute(AUTH_KEY) as String?
|
||||||
if (auth == null) {
|
if (auth == null) {
|
||||||
failed(request.session, response)
|
failed(request, response)
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
return Json.Object(
|
return Json.Object(
|
||||||
@@ -25,19 +30,26 @@ class TokenHandler: ApiHandler {
|
|||||||
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json {
|
override fun post(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||||
val auth = getObjectPayload(request)
|
val auth = getObjectPayload(request)
|
||||||
val answer = auth.getString("answer")
|
val answer = auth.getString("answer")
|
||||||
if (answer == null) {
|
val challenge = request.session.getAttribute(CHALLENGE_KEY) as AuthChallenge?
|
||||||
failed(request.session, response)
|
if (answer == null || challenge == null) {
|
||||||
|
failed(request, response)
|
||||||
} else {
|
} else {
|
||||||
|
val parts = cryptograph.webDecrypt(answer).split(":")
|
||||||
|
if (parts.size != 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun failed(session: HttpSession, response: HttpServletResponse) {
|
override fun delete(request: HttpServletRequest, response: HttpServletResponse): Json {
|
||||||
|
request.session.removeAttribute(AUTH_KEY)
|
||||||
|
return Json.Object("success" to true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun failed(request: HttpServletRequest, response: HttpServletResponse) {
|
||||||
|
val session = request.session
|
||||||
val challenge = AuthChallenge()
|
val challenge = AuthChallenge()
|
||||||
session.setAttribute(CHALLENGE_KEY, challenge)
|
session.setAttribute(CHALLENGE_KEY, challenge)
|
||||||
response.addHeader("WWW-Authenticate", challenge.value)
|
response.addHeader("WWW-Authenticate", challenge.value)
|
||||||
response.status = HttpServletResponse.SC_UNAUTHORIZED
|
response.status = HttpServletResponse.SC_UNAUTHORIZED
|
||||||
response.writer.println(Json.Object("status" to "failed"))
|
response.writer.println(Json.Object("status" to "failed", "message" to "unauthorized"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1,57 +0,0 @@
|
|||||||
package org.jeudego.pairgoth.server
|
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,29 +0,0 @@
|
|||||||
package org.jeudego.pairgoth.server
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
@@ -76,6 +76,11 @@
|
|||||||
<artifactId>kotlinx-datetime-jvm</artifactId>
|
<artifactId>kotlinx-datetime-jvm</artifactId>
|
||||||
<version>0.4.0</version>
|
<version>0.4.0</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>commons-codec</groupId>
|
||||||
|
<artifactId>commons-codec</artifactId>
|
||||||
|
<version>1.16.1</version>
|
||||||
|
</dependency>
|
||||||
<!-- servlets and mail APIs -->
|
<!-- servlets and mail APIs -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>jakarta.servlet</groupId>
|
<groupId>jakarta.servlet</groupId>
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
package org.jeudego.pairgoth.util
|
package org.jeudego.pairgoth.util
|
||||||
|
|
||||||
|
import org.apache.commons.codec.binary.Base64
|
||||||
import java.io.Serializable
|
import java.io.Serializable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -26,4 +27,10 @@ interface Cryptograph : Serializable {
|
|||||||
* @return decrypted string
|
* @return decrypted string
|
||||||
*/
|
*/
|
||||||
fun decrypt(bytes: ByteArray): String
|
fun decrypt(bytes: ByteArray): String
|
||||||
|
|
||||||
|
|
||||||
|
fun webEncrypt(str: String) = Base64.encodeBase64URLSafeString(encrypt(str))
|
||||||
|
|
||||||
|
fun webDecrypt(str: String) = decrypt(Base64.decodeBase64(str))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -16,7 +16,6 @@
|
|||||||
<description>PairGoth pairing system</description>
|
<description>PairGoth pairing system</description>
|
||||||
<url>TODO</url>
|
<url>TODO</url>
|
||||||
<properties>
|
<properties>
|
||||||
<pac4j.version>5.7.1</pac4j.version>
|
|
||||||
<lucene.version>9.9.0</lucene.version>
|
<lucene.version>9.9.0</lucene.version>
|
||||||
</properties>
|
</properties>
|
||||||
<build>
|
<build>
|
||||||
@@ -184,9 +183,9 @@
|
|||||||
</dependency>
|
</dependency>
|
||||||
<!-- auth -->
|
<!-- auth -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.pac4j</groupId>
|
<groupId>commons-codec</groupId>
|
||||||
<artifactId>pac4j-oauth</artifactId>
|
<artifactId>commons-codec</artifactId>
|
||||||
<version>${pac4j.version}</version>
|
<version>1.16.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<!-- logging -->
|
<!-- logging -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@@ -6,7 +6,6 @@ import com.republicate.kson.Json
|
|||||||
import org.jeudego.pairgoth.web.WebappManager
|
import org.jeudego.pairgoth.web.WebappManager
|
||||||
//import com.republicate.modality.util.AESCryptograph
|
//import com.republicate.modality.util.AESCryptograph
|
||||||
//import com.republicate.modality.util.Cryptograph
|
//import com.republicate.modality.util.Cryptograph
|
||||||
import org.apache.commons.codec.binary.Base64
|
|
||||||
import org.apache.http.NameValuePair
|
import org.apache.http.NameValuePair
|
||||||
import org.jeudego.pairgoth.util.AESCryptograph
|
import org.jeudego.pairgoth.util.AESCryptograph
|
||||||
import org.jeudego.pairgoth.util.ApiClient.JsonApiClient
|
import org.jeudego.pairgoth.util.ApiClient.JsonApiClient
|
||||||
@@ -14,8 +13,6 @@ import org.jeudego.pairgoth.util.Cryptograph
|
|||||||
import org.slf4j.Logger
|
import org.slf4j.Logger
|
||||||
import org.slf4j.LoggerFactory
|
import org.slf4j.LoggerFactory
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.UnsupportedEncodingException
|
|
||||||
import java.net.URLEncoder
|
|
||||||
|
|
||||||
abstract class OAuthHelper {
|
abstract class OAuthHelper {
|
||||||
abstract val name: String
|
abstract val name: String
|
||||||
@@ -28,20 +25,22 @@ abstract class OAuthHelper {
|
|||||||
get() = WebappManager.getMandatoryProperty("webapp.external.url").removeSuffix("/") + "/oauth/${name}"
|
get() = WebappManager.getMandatoryProperty("webapp.external.url").removeSuffix("/") + "/oauth/${name}"
|
||||||
|
|
||||||
protected fun getState(sessionId: String): String {
|
protected fun getState(sessionId: String): String {
|
||||||
return name + ":" + encrypt(sessionId)
|
return name + ":" + cryptograph.webEncrypt(sessionId)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun checkState(state: String, expectedSessionId: String): Boolean {
|
fun checkState(state: String, expectedSessionId: String): Boolean {
|
||||||
val foundSessionId = decrypt(state)
|
val foundSessionId = cryptograph.webDecrypt(state)
|
||||||
return expectedSessionId == foundSessionId
|
return expectedSessionId == foundSessionId
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract fun getAccessTokenURL(code: String): Pair<String, List<NameValuePair>>
|
protected abstract fun getAccessTokenURL(code: String): Pair<String, List<NameValuePair>>
|
||||||
|
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun getAccessToken(code: String): String {
|
fun getAccessToken(sessionID: String, code: String): String {
|
||||||
val (url, params) = getAccessTokenURL(code)
|
val (url, params) = getAccessTokenURL(code)
|
||||||
val json = JsonApiClient.post(url, null, *params.toTypedArray()).asObject()
|
val json = JsonApiClient.post(url, null, *params.toTypedArray()).asObject()
|
||||||
|
val state = json.getString("state") ?: throw IOException("could not get state")
|
||||||
|
if (!checkState(state, sessionID)) throw IOException("invalid state")
|
||||||
return json.getString("access_token") ?: throw IOException("could not get access token")
|
return json.getString("access_token") ?: throw IOException("could not get access token")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,17 +54,8 @@ abstract class OAuthHelper {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
protected var logger: Logger = LoggerFactory.getLogger("oauth")
|
protected var logger: Logger = LoggerFactory.getLogger("oauth")
|
||||||
private const val salt = "0efd28fb53cbac42"
|
private val cryptograph: Cryptograph = AESCryptograph().apply {
|
||||||
private val sessionIdCrypto: Cryptograph = AESCryptograph().apply {
|
init("0efd28fb53cbac42")
|
||||||
init(salt)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun encrypt(input: String): String {
|
|
||||||
return Base64.encodeBase64URLSafeString(sessionIdCrypto.encrypt(input))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun decrypt(input: String): String {
|
|
||||||
return sessionIdCrypto.decrypt(Base64.decodeBase64(input))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@@ -34,7 +34,7 @@ class AuthFilter: Filter {
|
|||||||
if (auth == "oauth" && uri.startsWith("/oauth/")) {
|
if (auth == "oauth" && uri.startsWith("/oauth/")) {
|
||||||
val provider = uri.substring("/oauth/".length)
|
val provider = uri.substring("/oauth/".length)
|
||||||
val helper = OauthHelperFactory.getHelper(provider)
|
val helper = OauthHelperFactory.getHelper(provider)
|
||||||
val accessToken = helper.getAccessToken(request.getParameter("code") ?: "")
|
val accessToken = helper.getAccessToken(request.session.id, request.getParameter("code") ?: "")
|
||||||
val user = helper.getUserInfos(accessToken)
|
val user = helper.getUserInfos(accessToken)
|
||||||
request.session.setAttribute("logged", user)
|
request.session.setAttribute("logged", user)
|
||||||
response.sendRedirect("/index")
|
response.sendRedirect("/index")
|
||||||
|
Reference in New Issue
Block a user