diff --git a/src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequest.kt b/src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequest.kt index 54109a16..aeb62941 100644 --- a/src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequest.kt +++ b/src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequest.kt @@ -2,6 +2,7 @@ package no.nav.security.mock.oauth2.http import com.nimbusds.oauth2.sdk.GrantType import com.nimbusds.oauth2.sdk.TokenRequest +import com.nimbusds.oauth2.sdk.auth.ClientAuthenticationMethod import com.nimbusds.oauth2.sdk.http.HTTPRequest import com.nimbusds.openid.connect.sdk.AuthenticationRequest import no.nav.security.mock.oauth2.extensions.clientAuthentication @@ -32,7 +33,10 @@ data class OAuth2HttpRequest( fun asTokenExchangeRequest(): TokenRequest { val httpRequest: HTTPRequest = this.asNimbusHTTPRequest() - val clientAuthentication = httpRequest.clientAuthentication().requirePrivateKeyJwt(this.url.toString(), 120) + var clientAuthentication = httpRequest.clientAuthentication() + if (clientAuthentication.method == ClientAuthenticationMethod.PRIVATE_KEY_JWT) { + clientAuthentication = clientAuthentication.requirePrivateKeyJwt(this.url.toString(), 120) + } val tokenExchangeGrant = TokenExchangeGrant.parse(formParameters.map) // TODO: add scope if present in request diff --git a/src/test/kotlin/no/nav/security/mock/oauth2/e2e/TokenExchangeGrantIntegrationTest.kt b/src/test/kotlin/no/nav/security/mock/oauth2/e2e/TokenExchangeGrantIntegrationTest.kt index 39a43abc..7016ddc5 100644 --- a/src/test/kotlin/no/nav/security/mock/oauth2/e2e/TokenExchangeGrantIntegrationTest.kt +++ b/src/test/kotlin/no/nav/security/mock/oauth2/e2e/TokenExchangeGrantIntegrationTest.kt @@ -87,22 +87,66 @@ class TokenExchangeGrantIntegrationTest { } } + @Test + fun `token request with token exchange grant and client basic auth should exchange subject_token with a new token containing many of the same claims`() { + withMockOAuth2Server { + val initialSubject = "yolo" + val initialToken = this.issueToken( + issuerId = "idprovider", + clientId = "initialClient", + tokenCallback = DefaultOAuth2TokenCallback( + issuerId = "idprovider", + subject = initialSubject, + claims = mapOf( + "claim1" to "value1", + "claim2" to "value2", + ), + ), + ) + + val issuerId = "tokenx" + val tokenEndpointUrl = this.tokenEndpointUrl(issuerId) + val targetAudienceForToken = "targetAudience" + + val response: ParsedTokenResponse = client.tokenRequest( + url = tokenEndpointUrl, + basicAuth = Pair("client", "secret"), + parameters = mapOf( + "grant_type" to TOKEN_EXCHANGE.value, + "subject_token_type" to SubjectTokenType.TOKEN_TYPE_JWT, + "subject_token" to initialToken.serialize(), + "audience" to targetAudienceForToken, + ), + ).toTokenResponse() + + response shouldBeValidFor TOKEN_EXCHANGE + response.scope shouldBe null + response.tokenType shouldBe "Bearer" + response.issuedTokenType shouldBe "urn:ietf:params:oauth:token-type:access_token" + + response.accessToken!! should verifyWith(issuerId, this) + + response.accessToken.subject shouldBe initialSubject + response.accessToken.audience shouldContainExactly listOf(targetAudienceForToken) + response.accessToken.claims["claim1"] shouldBe "value1" + response.accessToken.claims["claim2"] shouldBe "value2" + } + } + @Test fun `token request without client_assertion should fail`() { withMockOAuth2Server { - val response: Response = + val response: Response = client.tokenRequest( - url = this.tokenEndpointUrl("tokenx"), - parameters = - mapOf( - "grant_type" to TOKEN_EXCHANGE.value, - "client_id" to "myid", - "client_secret" to "somesecret", - "subject_token_type" to SubjectTokenType.TOKEN_TYPE_JWT, - "subject_token" to "yolo", - "audience" to "targetAudienceForToken", - ), - ) + url = this.tokenEndpointUrl("tokenx"), + parameters = + mapOf( + "grant_type" to TOKEN_EXCHANGE.value, + "subject_token_type" to SubjectTokenType.TOKEN_TYPE_JWT, + "subject_token" to "yolo", + "audience" to "targetAudienceForToken", + ), + ) response.code shouldBe 400 } }