Skip to content

Commit

Permalink
Implemented responses other than POST.
Browse files Browse the repository at this point in the history
  • Loading branch information
moratori committed May 24, 2024
1 parent b22251f commit 81ddcc2
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import okhttp3.Request
import org.bouncycastle.jce.spec.ECNamedCurveSpec
import java.math.BigInteger
import java.net.URI
import java.net.URLEncoder
import java.security.KeyPair
import java.security.PublicKey
import java.security.interfaces.ECPublicKey
Expand Down Expand Up @@ -241,7 +242,11 @@ class OpenIdProvider(val uri: String, val option: ProviderOption = ProviderOptio
val redirectUrl = requireNotNull(authRequest.redirectUri)

println("send id token to $redirectUrl")
val result = sendRequest(redirectUrl, mapOf("id_token" to idToken))

// As a temporary value, give DIRECT_POST a fixed value.
// It needs to be modified when responding to redirect responses.
val result = sendRequest(redirectUrl, mapOf("id_token" to idToken), ResponseMode.DIRECT_POST)

println("Received result: $result")
return Either.Right(result)
} catch (e: Exception) {
Expand All @@ -259,6 +264,14 @@ class OpenIdProvider(val uri: String, val option: ProviderOption = ProviderOptio
)
val presentationDefinition = this.siopRequest.presentationDefinition
?: throw IllegalArgumentException(SIOPErrors.BAD_PARAMS.message)

// https://openid.net/specs/oauth-v2-multiple-response-types-1_0.html#ResponseModes
// the default Response Mode for the OAuth 2.0 code Response Type is the query encoding
// the default Response Mode for the OAuth 2.0 token Response Type is the fragment encoding
// https://openid.net/specs/openid-4-verifiable-presentations-1_0-ID2.html#section-5
// If the parameter is not present, the default value is fragment.
val responseMode = authRequest.responseMode ?: ResponseMode.FRAGMENT

// presentationDefinition.inputDescriptors を使って選択項目でフィルター
val vpTokens = credentials.mapNotNull { it ->
when (it.format) {
Expand Down Expand Up @@ -309,8 +322,17 @@ class OpenIdProvider(val uri: String, val option: ProviderOption = ProviderOptio
}
val jsonString = objectMapper.writeValueAsString(presentationSubmission)

// todo fragmentの場合はSame Deviceにリダイレクト
val redirectUrl = requireNotNull(authRequest.responseUri)
// https://openid.net/specs/openid-4-verifiable-presentations-1_0-ID2.html#name-authorization-request
// response_uri parameter is present, the redirect_uri Authorization Request parameter MUST NOT be present
val destinationUri = if (responseMode == ResponseMode.DIRECT_POST) {
authRequest.responseUri
} else {
authRequest.redirectUri
}
if (destinationUri.isNullOrBlank()) {
return Either.Left("Unknown destination for response")
}


val body = mutableMapOf(
"vp_token" to vpTokenValue,
Expand All @@ -321,8 +343,8 @@ class OpenIdProvider(val uri: String, val option: ProviderOption = ProviderOptio
body["state"] = state
}

println("send vp token to $redirectUrl")
val result = sendRequest(redirectUrl, body)
println("send vp token to $destinationUri")
val result = sendRequest(destinationUri, body, responseMode)
print("status code: ${result.statusCode}")
print("location: ${result.location}")
print("cookies: ${result.cookies}")
Expand Down Expand Up @@ -506,7 +528,7 @@ fun mergeOAuth2AndOpenIdInRequestPayload(
return createRequestObjectPayloadFromMap(mergedMap)
}

fun sendRequest(redirectUrl: String, formData: Map<String, String>): PostResult {
fun sendRequest(destinationUri: String, formData: Map<String, String>, responseMode: ResponseMode): PostResult {
val client = OkHttpClient.Builder()
.followRedirects(false)
.build()
Expand All @@ -516,11 +538,42 @@ fun sendRequest(redirectUrl: String, formData: Map<String, String>): PostResult
formBodyBuilder.add(key, value)
}
val formBody = formBodyBuilder.build()

val request = Request.Builder()
.url(redirectUrl)
.post(formBody)
.build()
val request: Request

when (responseMode) {
ResponseMode.DIRECT_POST -> {
request = Request.Builder()
.url(destinationUri)
.post(formBody)
.build()
}
ResponseMode.QUERY, ResponseMode.FRAGMENT -> {
val uriBuilder = StringBuilder(destinationUri)
formData.forEach { (key, value) ->
if (uriBuilder.contains("?")) {
uriBuilder.append("&")
} else {
uriBuilder.append("?")
}
uriBuilder.append(URLEncoder.encode(key, "UTF-8"))
.append("=")
.append(URLEncoder.encode(value, "UTF-8"))
}
val uriWithParams = uriBuilder.toString()
val destinationUriWithParams = if (responseMode == ResponseMode.FRAGMENT) {
uriWithParams.replace("?", "#")
} else {
uriWithParams
}
request = Request.Builder()
.url(destinationUriWithParams)
.get()
.build()
}
else -> {
throw IllegalArgumentException("Unsupported response mode: $responseMode")
}
}

client.newCall(request).execute().use { response ->
val statusCode = response.code()
Expand All @@ -534,7 +587,7 @@ fun sendRequest(redirectUrl: String, formData: Map<String, String>): PostResult
val uri = URI.create(location)
if (!uri.isAbsolute) {
// 元のURLからホスト情報を抽出して補完
val originalUri = URI.create(redirectUrl)
val originalUri = URI.create(destinationUri)
val portPart = if (originalUri.port != -1) ":${originalUri.port}" else ""
location = "${originalUri.scheme}://${originalUri.host}$portPart$location"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.ownd_project.tw2023_wallet_android.oid

import android.util.Log
import com.auth0.jwt.JWT
import com.auth0.jwt.algorithms.Algorithm
import com.ownd_project.tw2023_wallet_android.encodePublicKeyToJwks
Expand Down Expand Up @@ -111,6 +112,56 @@ class OpenIdProviderTest {
wireMockServer.stop()
}


@Test
fun testSendRequest() = runBlocking {
// MockWebServerに対するレスポンスを設定します。
wireMockServer.stubFor(
WireMock.post(WireMock.urlEqualTo("/"))
.withHeader("Content-Type", WireMock.equalTo("application/x-www-form-urlencoded"))
.willReturn(
WireMock.aResponse()
.withStatus(200)
.withBody("response body")
)
)

// テスト対象のメソッドを呼び出します。
val result = sendRequest("$clientHost:${wireMockServer.port()}/", mapOf("key" to "value"), ResponseMode.DIRECT_POST)

// レスポンスが期待通りであることを確認します。
assertEquals(200, result.statusCode)

// リクエストが期待通りであることを確認します。
val recordedRequest = wireMockServer.findAll(WireMock.postRequestedFor(WireMock.urlEqualTo("/"))).first()
assertEquals("POST", recordedRequest.method.toString())
assertEquals("key=value", recordedRequest.bodyAsString)
}


@Test
fun testSendRequestWithQueryResponseMode() = runBlocking {
// MockWebServerに対するレスポンスを設定します。
wireMockServer.stubFor(
WireMock.get(WireMock.urlMatching("/\\?key=value"))
.willReturn(
WireMock.aResponse()
.withStatus(200)
.withBody("response body")
)
)

// テスト対象のメソッドを呼び出します。
val result = sendRequest("$clientHost:${wireMockServer.port()}/", mapOf("key" to "value"), ResponseMode.QUERY)

// レスポンスが期待通りであることを確認します。
assertEquals(200, result.statusCode)

// リクエストが期待通りであることを確認します。
val recordedRequest = wireMockServer.findAll(WireMock.getRequestedFor(WireMock.urlMatching("/\\?key=value"))).first()
assertEquals("GET", recordedRequest.method.toString())
}

@Test
fun testProcessSIOPRequest() = runBlocking {
val requestJwt = createRequestObjectJwt(
Expand Down

0 comments on commit 81ddcc2

Please sign in to comment.