diff --git a/src/main/kotlin/no/nav/security/mock/oauth2/MockOAuth2Server.kt b/src/main/kotlin/no/nav/security/mock/oauth2/MockOAuth2Server.kt index 433b1e4a1..60e6a3b63 100644 --- a/src/main/kotlin/no/nav/security/mock/oauth2/MockOAuth2Server.kt +++ b/src/main/kotlin/no/nav/security/mock/oauth2/MockOAuth2Server.kt @@ -295,7 +295,7 @@ open class MockOAuth2Server( TokenRequest .Builder( URI.create("http://mockgrant"), - ClientID("mockclientid"), + ClientSecretBasic(ClientID("mockclientid"), Secret("secret")), mockGrant, ).build() return this.config.tokenProvider.exchangeAccessToken( diff --git a/src/main/kotlin/no/nav/security/mock/oauth2/extensions/NimbusExtensions.kt b/src/main/kotlin/no/nav/security/mock/oauth2/extensions/NimbusExtensions.kt index 4ff0451a3..594da166f 100644 --- a/src/main/kotlin/no/nav/security/mock/oauth2/extensions/NimbusExtensions.kt +++ b/src/main/kotlin/no/nav/security/mock/oauth2/extensions/NimbusExtensions.kt @@ -30,7 +30,6 @@ import com.nimbusds.openid.connect.sdk.OIDCScopeValue import com.nimbusds.openid.connect.sdk.Prompt import mu.KotlinLogging import no.nav.security.mock.oauth2.OAuth2Exception -import no.nav.security.mock.oauth2.grant.TokenExchangeGrant import no.nav.security.mock.oauth2.invalidRequest import java.time.Duration import java.time.Instant @@ -63,7 +62,7 @@ fun TokenRequest.scopesWithoutOidcScopes() = OIDCScopeValue.values().map { it.toString() }.contains(value) } ?: emptyList() -fun TokenRequest.tokenExchangeGrantOrNull(): TokenExchangeGrant? = authorizationGrant as? TokenExchangeGrant +fun TokenRequest.tokenExchangeGrantOrNull() = authorizationGrant as? com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant fun TokenRequest.authorizationCode(): AuthorizationCode = this.authorizationGrant diff --git a/src/main/kotlin/no/nav/security/mock/oauth2/grant/TokenExchangeGrant.kt b/src/main/kotlin/no/nav/security/mock/oauth2/grant/TokenExchangeGrant.kt deleted file mode 100644 index 7543c40cc..000000000 --- a/src/main/kotlin/no/nav/security/mock/oauth2/grant/TokenExchangeGrant.kt +++ /dev/null @@ -1,36 +0,0 @@ -package no.nav.security.mock.oauth2.grant - -import com.nimbusds.oauth2.sdk.AuthorizationGrant -import com.nimbusds.oauth2.sdk.GrantType -import no.nav.security.mock.oauth2.invalidRequest - -val TOKEN_EXCHANGE = GrantType("urn:ietf:params:oauth:grant-type:token-exchange") - -@Suppress("MemberVisibilityCanBePrivate") -class TokenExchangeGrant( - val subjectTokenType: String, - val subjectToken: String, - val audience: MutableList, -) : AuthorizationGrant(TOKEN_EXCHANGE) { - override fun toParameters(): MutableMap> = - mutableMapOf( - "grant_type" to mutableListOf(TOKEN_EXCHANGE.value), - "subject_token_type" to mutableListOf(subjectTokenType), - "subject_token" to mutableListOf(subjectToken), - "audience" to audience, - ) - - companion object { - fun parse(parameters: Map): TokenExchangeGrant = - TokenExchangeGrant( - parameters.require("subject_token_type"), - parameters.require("subject_token"), - parameters - .require("audience") - .split(" ") - .toMutableList(), - ) - } -} - -private inline fun Map.require(name: String): T = this[name] ?: invalidRequest("missing required parameter $name") diff --git a/src/main/kotlin/no/nav/security/mock/oauth2/grant/TokenExchangeGrantHandler.kt b/src/main/kotlin/no/nav/security/mock/oauth2/grant/TokenExchangeGrantHandler.kt index 5940864e6..a9d8f1e13 100644 --- a/src/main/kotlin/no/nav/security/mock/oauth2/grant/TokenExchangeGrantHandler.kt +++ b/src/main/kotlin/no/nav/security/mock/oauth2/grant/TokenExchangeGrantHandler.kt @@ -2,6 +2,7 @@ package no.nav.security.mock.oauth2.grant import com.nimbusds.jwt.SignedJWT import com.nimbusds.oauth2.sdk.TokenRequest +import com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant import no.nav.security.mock.oauth2.extensions.expiresIn import no.nav.security.mock.oauth2.http.OAuth2HttpRequest import no.nav.security.mock.oauth2.http.OAuth2TokenResponse @@ -36,6 +37,8 @@ internal class TokenExchangeGrantHandler( } } -fun TokenRequest.subjectToken(): SignedJWT = SignedJWT.parse(this.tokenExchangeGrant().subjectToken) +fun TokenRequest.subjectToken(): SignedJWT = SignedJWT.parse(this.tokenExchangeGrant().subjectToken.value) + +fun TokenRequest.audienceOrEmpty(): List = (this.authorizationGrant as? TokenExchangeGrant)?.audience?.map { it.value } ?: emptyList() fun TokenRequest.tokenExchangeGrant() = this.authorizationGrant as? TokenExchangeGrant ?: invalidRequest("missing token exchange grant") 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 f6e66404f..36c50c315 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 @@ -16,7 +16,6 @@ import no.nav.security.mock.oauth2.extensions.toJwksUrl import no.nav.security.mock.oauth2.extensions.toRevocationEndpointUrl import no.nav.security.mock.oauth2.extensions.toTokenEndpointUrl import no.nav.security.mock.oauth2.extensions.toUserInfoUrl -import no.nav.security.mock.oauth2.grant.TokenExchangeGrant import no.nav.security.mock.oauth2.missingParameter import okhttp3.Headers import okhttp3.HttpUrl @@ -33,27 +32,17 @@ data class OAuth2HttpRequest( fun asTokenExchangeRequest(): TokenRequest { val httpRequest: HTTPRequest = this.asNimbusHTTPRequest() - var clientAuthentication = httpRequest.clientAuthentication() + val clientAuthentication = httpRequest.clientAuthentication() if (clientAuthentication.method == ClientAuthenticationMethod.PRIVATE_KEY_JWT) { - clientAuthentication = - clientAuthentication.requirePrivateKeyJwt( - requiredAudience = this.url.toIssuerUrl().toString(), - maxLifetimeSeconds = 120, - additionalAcceptedAudience = this.url.toString(), - ) - } - val tokenExchangeGrant = TokenExchangeGrant.parse(formParameters.map) - - // TODO: add scope if present in request - val builder = - TokenRequest.Builder( - this.url.toUri(), - clientAuthentication, - tokenExchangeGrant, + clientAuthentication.requirePrivateKeyJwt( + requiredAudience = this.url.toIssuerUrl().toString(), + maxLifetimeSeconds = 120, + additionalAcceptedAudience = this.url.toString(), ) - formParameters.map.forEach { (key, value) -> builder.customParameter(key, value) } - - return builder.build() + } + return TokenRequest.parse( + this.asNimbusHTTPRequest(), + ) } @Suppress("MemberVisibilityCanBePrivate") diff --git a/src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequestHandler.kt b/src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequestHandler.kt index 5ce512978..b1c1faa73 100644 --- a/src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequestHandler.kt +++ b/src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequestHandler.kt @@ -8,6 +8,7 @@ import com.nimbusds.oauth2.sdk.GrantType.CLIENT_CREDENTIALS import com.nimbusds.oauth2.sdk.GrantType.JWT_BEARER import com.nimbusds.oauth2.sdk.GrantType.PASSWORD import com.nimbusds.oauth2.sdk.GrantType.REFRESH_TOKEN +import com.nimbusds.oauth2.sdk.GrantType.TOKEN_EXCHANGE import com.nimbusds.oauth2.sdk.OAuth2Error import com.nimbusds.oauth2.sdk.ParseException import com.nimbusds.openid.connect.sdk.AuthenticationRequest @@ -33,7 +34,6 @@ import no.nav.security.mock.oauth2.grant.PasswordGrantHandler import no.nav.security.mock.oauth2.grant.RefreshToken import no.nav.security.mock.oauth2.grant.RefreshTokenGrantHandler import no.nav.security.mock.oauth2.grant.RefreshTokenManager -import no.nav.security.mock.oauth2.grant.TOKEN_EXCHANGE import no.nav.security.mock.oauth2.grant.TokenExchangeGrantHandler import no.nav.security.mock.oauth2.introspect.introspect import no.nav.security.mock.oauth2.invalidGrant diff --git a/src/main/kotlin/no/nav/security/mock/oauth2/token/OAuth2TokenCallback.kt b/src/main/kotlin/no/nav/security/mock/oauth2/token/OAuth2TokenCallback.kt index 110403edf..25fff9f09 100644 --- a/src/main/kotlin/no/nav/security/mock/oauth2/token/OAuth2TokenCallback.kt +++ b/src/main/kotlin/no/nav/security/mock/oauth2/token/OAuth2TokenCallback.kt @@ -7,7 +7,7 @@ import no.nav.security.mock.oauth2.extensions.clientIdAsString import no.nav.security.mock.oauth2.extensions.grantType import no.nav.security.mock.oauth2.extensions.replaceValues import no.nav.security.mock.oauth2.extensions.scopesWithoutOidcScopes -import no.nav.security.mock.oauth2.extensions.tokenExchangeGrantOrNull +import no.nav.security.mock.oauth2.grant.audienceOrEmpty import java.time.Duration import java.util.UUID @@ -48,10 +48,10 @@ open class DefaultOAuth2TokenCallback override fun typeHeader(tokenRequest: TokenRequest): String = typeHeader override fun audience(tokenRequest: TokenRequest): List { - val audienceParam = tokenRequest.tokenExchangeGrantOrNull()?.audience + val audienceParam = tokenRequest.audienceOrEmpty() return when { audience != null -> audience - audienceParam != null -> audienceParam + audienceParam.isNotEmpty() -> audienceParam tokenRequest.scope != null -> tokenRequest.scopesWithoutOidcScopes() else -> listOf("default") } 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 0ee955d63..4035156bb 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 @@ -1,9 +1,9 @@ package no.nav.security.mock.oauth2.e2e +import com.nimbusds.oauth2.sdk.GrantType.TOKEN_EXCHANGE import io.kotest.matchers.collections.shouldContainExactly import io.kotest.matchers.should import io.kotest.matchers.shouldBe -import no.nav.security.mock.oauth2.grant.TOKEN_EXCHANGE import no.nav.security.mock.oauth2.testutils.ClientAssertionType import no.nav.security.mock.oauth2.testutils.ParsedTokenResponse import no.nav.security.mock.oauth2.testutils.SubjectTokenType diff --git a/src/test/kotlin/no/nav/security/mock/oauth2/testutils/Token.kt b/src/test/kotlin/no/nav/security/mock/oauth2/testutils/Token.kt index d4cbdd607..5ff291500 100644 --- a/src/test/kotlin/no/nav/security/mock/oauth2/testutils/Token.kt +++ b/src/test/kotlin/no/nav/security/mock/oauth2/testutils/Token.kt @@ -21,6 +21,7 @@ import com.nimbusds.oauth2.sdk.GrantType.CLIENT_CREDENTIALS import com.nimbusds.oauth2.sdk.GrantType.JWT_BEARER import com.nimbusds.oauth2.sdk.GrantType.PASSWORD import com.nimbusds.oauth2.sdk.GrantType.REFRESH_TOKEN +import com.nimbusds.oauth2.sdk.GrantType.TOKEN_EXCHANGE import com.nimbusds.oauth2.sdk.TokenRequest import io.kotest.assertions.assertSoftly import io.kotest.matchers.Matcher @@ -29,7 +30,6 @@ import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe import no.nav.security.mock.oauth2.MockOAuth2Server -import no.nav.security.mock.oauth2.grant.TOKEN_EXCHANGE import no.nav.security.mock.oauth2.http.OAuth2HttpRequest import no.nav.security.mock.oauth2.http.OAuth2TokenResponse import no.nav.security.mock.oauth2.token.DefaultOAuth2TokenCallback