This commit is contained in:
Claude Brisson
2024-02-26 15:09:48 +01:00
parent 71549f185e
commit 924b31d24b
13 changed files with 191 additions and 68 deletions

View File

@@ -40,8 +40,9 @@ abstract class OAuthHelper {
fun getAccessToken(sessionID: String, code: String): String {
val (url, params) = getAccessTokenURL(code)
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")
// CB TODO - do not check state for now
// 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")
}

View File

@@ -10,6 +10,7 @@ import org.jeudego.pairgoth.web.AuthFilter
import org.jeudego.pairgoth.web.WebappManager
import org.slf4j.LoggerFactory
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse
class ApiTool {
companion object {
@@ -36,7 +37,22 @@ class ApiTool {
private fun Json.toRequestBody() = toString().toRequestBody(JSON.toMediaType())
private fun Request.Builder.process(): Json {
try {
return client.newCall(build()).execute().use { response ->
val apiReq = build()
if (logger.isTraceEnabled) {
logger.trace(">> ${apiReq.method} ${apiReq.url}")
apiReq.headers.forEach { header ->
logger.trace(" ${header.first} ${header.second}")
}
}
logger.trace(" ")
return client.newCall(apiReq).execute().use { response ->
if (logger.isTraceEnabled) {
logger.trace("<< ${response.code} ${response.message}")
response.headers.forEach { header ->
logger.trace(" ${header.first} ${header.second}")
}
}
if (response.isSuccessful) {
when (response.body?.contentType()?.subtype) {
null -> throw Error("null body or content type")
@@ -44,6 +60,10 @@ class ApiTool {
else -> throw Error("unhandled content type: ${response.body!!.contentType()}")
}
} else {
if (response.code == HttpServletResponse.SC_UNAUTHORIZED) {
request.session.removeAttribute(AuthFilter.SESSION_KEY_API_TOKEN)
request.session.removeAttribute(AuthFilter.SESSION_KEY_USER)
}
when (response.body?.contentType()?.subtype) {
"json" -> Json.parse(response.body!!.string()) ?: throw Error("could not parse error json")
else -> throw Error("${response.code} ${response.message}")

View File

@@ -9,6 +9,7 @@ import org.jeudego.pairgoth.oauth.OauthHelperFactory
import org.jeudego.pairgoth.util.AESCryptograph
import org.jeudego.pairgoth.view.ApiTool
import org.slf4j.LoggerFactory
import java.io.IOException
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
import javax.servlet.Filter
@@ -95,39 +96,55 @@ class AuthFilter: Filter {
}
fun fetchApiToken(req: HttpServletRequest, user: Json.Object): String? {
val challengeReq = Request.Builder().url("${ApiTool.apiRoot}tour/token")
.header("Authorization", "Bearer ${getBearer(req)}")
.build()
val challengeResp = client.newCall(challengeReq).execute()
if (challengeResp.code == HttpServletResponse.SC_UNAUTHORIZED) {
val email = user.getString("email") ?: "-"
val challenge = challengeResp.headers["WWW-Authenticate"]
if (challenge != null) {
val signature = hasher.digest(
"${
req.session.id
}:${
challenge
}:${
email
}".toByteArray(StandardCharsets.UTF_8))
val answer = Json.Object(
"session" to req.session.id,
"email" to email,
"signature" to signature
)
val answerReq = Request.Builder().url("${ApiTool.apiRoot}tour/token").post(
answer.toString().toRequestBody(ApiTool.JSON.toMediaType())
).build()
val answerResp = client.newCall(answerReq).execute()
if (answerResp.isSuccessful && "json" == answerResp.body?.contentType()?.subtype) {
val payload = Json.parse(answerResp.body!!.string())
if (payload != null && payload.isObject) {
val token = payload.asObject().getString("token")
if (token != null) return token
try {
logger.trace("getting challenge...")
val challengeReq = Request.Builder().url("${ApiTool.apiRoot}tour/token")
.header("Accept", "application/json")
.header("Authorization", "Bearer ${getBearer(req)}")
.build()
val challengeResp = client.newCall(challengeReq).execute()
challengeResp.use {
if (challengeResp.code == HttpServletResponse.SC_UNAUTHORIZED) {
logger.trace("building answer...")
val email = user.getString("email") ?: "-"
val challenge = challengeResp.headers["WWW-Authenticate"]
if (challenge != null) {
val signature = hasher.digest(
"${
req.session.id
}:${
challenge
}:${
email
}".toByteArray(StandardCharsets.UTF_8)
).toHex()
val answer = Json.Object(
"session" to req.session.id,
"email" to email,
"signature" to signature
)
val answerReq = Request.Builder().url("${ApiTool.apiRoot}tour/token")
.header("Accept", "application/json")
.post(answer.toString().toRequestBody(ApiTool.JSON.toMediaType()))
.build()
val answerResp = client.newCall(answerReq).execute()
answerResp.use {
if (answerResp.isSuccessful && "json" == answerResp.body?.contentType()?.subtype) {
val payload = Json.parse(answerResp.body!!.string())
if (payload != null && payload.isObject) {
val token = payload.asObject().getString("token")
if (token != null) {
logger.trace("got token $token")
return token
}
}
}
}
}
}
}
} catch (e: IOException) {
logger.warn("could not fetch access token", e)
}
return null
}

View File

@@ -1 +0,0 @@
level = info