Skip to content

Commit

Permalink
refactor: replace custom TokenExchangeGrant with Nimbus SDK grant
Browse files Browse the repository at this point in the history
* breaking: TokenExchangeGrant.kt has been removed in favor of com.nimbusds.oauth2.sdk.tokenexchange.TokenExchangeGrant, this affects the extension function fun TokenRequest.tokenExchangeGrantOrNull()
* use nimbus sdk to construct tokenRequest for token_exchange
* remove use of deprecated constructors for nimbus TokenRequest
* formatting
  • Loading branch information
tommytroen committed Aug 21, 2024
1 parent 5fe5d8e commit 781776c
Show file tree
Hide file tree
Showing 10 changed files with 23 additions and 65 deletions.
13 changes: 7 additions & 6 deletions src/main/kotlin/no/nav/security/mock/oauth2/MockOAuth2Server.kt
Original file line number Diff line number Diff line change
Expand Up @@ -241,11 +241,12 @@ open class MockOAuth2Server(
val uri = tokenEndpointUrl(issuerId)
val issuerUrl = issuerUrl(issuerId)
val tokenRequest =
TokenRequest(
uri.toUri(),
ClientSecretBasic(ClientID(clientId), Secret("secret")),
AuthorizationCodeGrant(AuthorizationCode("123"), URI.create("http://localhost")),
)
TokenRequest
.Builder(
uri.toUri(),
ClientSecretBasic(ClientID(clientId), Secret("secret")),
AuthorizationCodeGrant(AuthorizationCode("123"), URI.create("http://localhost")),
).build()
return config.tokenProvider.accessToken(tokenRequest, issuerUrl, tokenCallback, null)
}

Expand Down Expand Up @@ -291,7 +292,7 @@ open class MockOAuth2Server(
override fun toParameters(): MutableMap<String, MutableList<String>> = mutableMapOf()
}
return this.config.tokenProvider.exchangeAccessToken(
TokenRequest(URI.create("http://mockgrant"), ClientID("mockclientid"), mockGrant),
TokenRequest.Builder(URI.create("http://mockgrant"), ClientSecretBasic(ClientID("mockclientid"), Secret("secret")), mockGrant).build(),
issuerUrl,
jwtClaimsSet,
DefaultOAuth2TokenCallback(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<String> = (this.authorizationGrant as? TokenExchangeGrant)?.audience?.map { it.value } ?: emptyList()

fun TokenRequest.tokenExchangeGrant() = this.authorizationGrant as? TokenExchangeGrant ?: invalidRequest("missing token exchange grant")
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,20 +32,13 @@ 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(this.url.toString(), 120)
clientAuthentication.requirePrivateKeyJwt(this.url.toString(), 120)
}
val tokenExchangeGrant = TokenExchangeGrant.parse(formParameters.map)

// TODO: add scope if present in request
return TokenRequest(
this.url.toUri(),
clientAuthentication,
tokenExchangeGrant,
null,
emptyList(),
formParameters.map.mapValues { mutableListOf(it.value) },

return TokenRequest.parse(
this.asNimbusHTTPRequest(),
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,4 @@ open class KeyProvider
jwkSelector: JWKSelector?,
context: SecurityContext?,
): MutableList<JWK> = jwkSelector?.select(JWKSet(signingKeys.values.toList()).toPublicJWKSet()) ?: mutableListOf()

}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -48,10 +48,10 @@ open class DefaultOAuth2TokenCallback
override fun typeHeader(tokenRequest: TokenRequest): String = typeHeader

override fun audience(tokenRequest: TokenRequest): List<String> {
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")
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package no.nav.security.mock.oauth2.e2e

import com.nimbusds.jwt.JWTClaimsSet
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 okhttp3.Headers
Expand Down

0 comments on commit 781776c

Please sign in to comment.