Skip to content

Commit 60f2017

Browse files
authored
Merge branch 'master' into add-arm64-architecture
2 parents bc6d2ec + fee61b2 commit 60f2017

File tree

14 files changed

+233
-35
lines changed

14 files changed

+233
-35
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
name: Manually triggered playground
2+
3+
on:
4+
workflow_dispatch:
5+
6+
env:
7+
8+
IMAGE_NAME: ttl.sh/${{ github.repository }}
9+
jobs:
10+
build:
11+
runs-on: ubuntu-latest
12+
steps:
13+
- name: Checkout latest code
14+
uses: actions/checkout@v3
15+
with:
16+
ref: add-arm64-architecture
17+
18+
- name: Set up JDK 11
19+
uses: actions/setup-java@v3
20+
with:
21+
java-version: 11
22+
distribution: 'zulu'
23+
24+
- name: Setup build cache
25+
uses: actions/cache@v3
26+
with:
27+
path: ~/.gradle/caches
28+
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle.kts') }}
29+
restore-keys: |
30+
${{ runner.os }}-gradle-
31+
32+
- name: Setup Gradle wrapper cache
33+
uses: actions/cache@v3
34+
with:
35+
path: ~/.gradle/wrapper
36+
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
37+
restore-keys: |
38+
${{ runner.os }}-gradle-wrapper-
39+
40+
- name: Build JVM stuff
41+
run: ./gradlew build
42+
43+
- name: Build Docker images for amd64 and arm64
44+
# The GITHUB_REF tag comes in the format 'refs/tags/xxx'.
45+
# So if we split on '/' and take the 3rd value, we can get the release name.
46+
run: |
47+
NEW_VERSION=1h
48+
IMAGE=${IMAGE_NAME}:${NEW_VERSION}
49+
echo "Building new version ${NEW_VERSION} of $IMAGE"
50+
./gradlew jib --image="${IMAGE}"

.github/workflows/publish-release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
NEW_VERSION=$(echo "${GITHUB_REF}" | cut -d "/" -f3)
6161
IMAGE=${IMAGE_NAME}:${NEW_VERSION}
6262
echo "Releasing new version ${NEW_VERSION} of $IMAGE"
63-
./gradlew -Pversion=${NEW_VERSION} publish closeAndReleaseRepository -Dorg.gradle.internal.publish.checksums.insecure=true --info
63+
./gradlew -Pversion=${NEW_VERSION} publish publishToSonatype closeAndReleaseStagingRepository -Dorg.gradle.internal.publish.checksums.insecure=true --info
6464
./gradlew jibDockerBuild --image="${IMAGE}"
6565
docker push "${IMAGE}"
6666

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ The motivation behind this library is to provide a setup such that application d
2626
* OAuth2 JWT Bearer Grant (On-Behalf-Of flow)
2727
* OAuth2 Token Exchange Grant
2828
* OAuth2 Refresh Token Grant
29+
* OAuth2 Resource Owner Password Credentials (Password Grant)
30+
* *usage should be avoided if possible as this grant is considered insecure and [removed in its entirety](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-security-topics-13#section-3.4) from OAuth 2.1*
2931
* **Issued JWT tokens are verifiable** through standard mechanisms with OpenID Connect Discovery / OAuth2 Authorization Server Metadata
3032
* **Unit/Integration test support**
3133
* Start and stop server for each test

build.gradle.kts

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,32 @@ import com.github.benmanes.gradle.versions.updates.DependencyUpdatesTask
44
val assertjVersion = "3.22.0"
55
val kotlinLoggingVersion = "2.1.21"
66
val logbackVersion = "1.2.11"
7-
val nimbusSdkVersion = "9.32"
7+
val nimbusSdkVersion = "9.35"
88
val mockWebServerVersion = "4.9.3"
99
val jacksonVersion = "2.13.2"
10-
val nettyVersion = "4.1.75.Final"
10+
val nettyVersion = "4.1.76.Final"
1111
val junitJupiterVersion = "5.8.2"
12-
val kotlinVersion = "1.6.20"
12+
val kotlinVersion = "1.6.21"
1313
val freemarkerVersion = "2.3.31"
14-
val kotestVersion = "5.2.2"
14+
val kotestVersion = "5.2.3"
1515
val bouncyCastleVersion = "1.70"
16-
val springBootVersion = "2.6.6"
17-
val reactorTestVersion = "3.4.16"
16+
val springBootVersion = "2.6.7"
17+
val reactorTestVersion = "3.4.17"
1818
val ktorVersion = "1.6.8"
1919

2020
val mavenRepoBaseUrl = "https://oss.sonatype.org"
2121
val mainClassKt = "no.nav.security.mock.oauth2.StandaloneMockOAuth2ServerKt"
2222

2323
plugins {
2424
application
25-
kotlin("jvm") version "1.6.20"
25+
kotlin("jvm") version "1.6.21"
2626
id("se.patrikerdes.use-latest-versions") version "0.2.18"
2727
id("com.github.ben-manes.versions") version "0.42.0"
28-
id("org.jmailen.kotlinter") version "3.9.0"
28+
id("org.jmailen.kotlinter") version "3.10.0"
2929
id("com.google.cloud.tools.jib") version "3.2.1"
3030
id("com.github.johnrengelman.shadow") version "7.1.2"
3131
id("net.researchgate.release") version "2.8.1"
32-
id("io.codearte.nexus-staging") version "0.30.0"
33-
id("de.marcphilipp.nexus-publish") version "0.4.0"
32+
id("io.github.gradle-nexus.publish-plugin") version "1.1.0"
3433
`java-library`
3534
`maven-publish`
3635
signing
@@ -90,17 +89,19 @@ dependencies {
9089
testImplementation("io.ktor:ktor-server-test-host:$ktorVersion")
9190
}
9291

93-
nexusStaging {
94-
username = System.getenv("SONATYPE_USERNAME")
95-
password = System.getenv("SONATYPE_PASSWORD")
96-
packageGroup = "no.nav"
97-
delayBetweenRetriesInMillis = 5000
98-
}
99-
10092
nexusPublishing {
93+
packageGroup.set("no.nav")
10194
clientTimeout.set(Duration.ofMinutes(2))
10295
repositories {
103-
sonatype()
96+
sonatype {
97+
username.set(System.getenv("SONATYPE_USERNAME"))
98+
password.set(System.getenv("SONATYPE_PASSWORD"))
99+
}
100+
}
101+
102+
transitionCheckOptions {
103+
maxRetries.set(40)
104+
delayBetween.set(Duration.ofMillis(5000))
104105
}
105106
}
106107

@@ -257,14 +258,6 @@ tasks {
257258
dependsOn("shadowJar")
258259
}
259260

260-
"publish" {
261-
dependsOn("initializeSonatypeStagingRepository")
262-
}
263-
264-
"publishToSonatype" {
265-
dependsOn("publish")
266-
}
267-
268261
withType<Sign>().configureEach {
269262
onlyIf {
270263
System.getenv("GPG_KEYS") != null

src/main/kotlin/no/nav/security/mock/oauth2/StandaloneMockOAuth2Server.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ object StandaloneConfig {
2121
fun hostname(): InetAddress = SERVER_HOSTNAME.fromEnv()
2222
?.let { InetAddress.getByName(it) } ?: InetSocketAddress(0).address
2323

24-
fun port(): Int = (SERVER_PORT.fromEnv()?.toInt() ?: PORT.fromEnv()?.toInt())?: 8080
24+
fun port(): Int = (SERVER_PORT.fromEnv()?.toInt() ?: PORT.fromEnv()?.toInt()) ?: 8080
2525

2626
fun oauth2Config(): OAuth2Config = with(jsonFromEnv()) {
2727
if (this != null) {

src/main/kotlin/no/nav/security/mock/oauth2/grant/AuthorizationCodeGrantHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ internal class AuthorizationCodeHandler(
7676
val loginTokenCallbackOrDefault = getLoginTokenCallbackOrDefault(code, oAuth2TokenCallback)
7777
val idToken: SignedJWT = tokenProvider.idToken(tokenRequest, issuerUrl, loginTokenCallbackOrDefault, nonce)
7878
val accessToken: SignedJWT = tokenProvider.accessToken(tokenRequest, issuerUrl, loginTokenCallbackOrDefault, nonce)
79-
val refreshToken: RefreshToken = refreshTokenManager.refreshToken(loginTokenCallbackOrDefault)
79+
val refreshToken: RefreshToken = refreshTokenManager.refreshToken(loginTokenCallbackOrDefault, nonce)
8080

8181
return OAuth2TokenResponse(
8282
tokenType = "Bearer",
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package no.nav.security.mock.oauth2.grant
2+
3+
import com.nimbusds.jwt.SignedJWT
4+
import com.nimbusds.oauth2.sdk.ResourceOwnerPasswordCredentialsGrant
5+
import com.nimbusds.oauth2.sdk.TokenRequest
6+
import no.nav.security.mock.oauth2.extensions.expiresIn
7+
import no.nav.security.mock.oauth2.http.OAuth2HttpRequest
8+
import no.nav.security.mock.oauth2.http.OAuth2TokenResponse
9+
import no.nav.security.mock.oauth2.token.OAuth2TokenCallback
10+
import no.nav.security.mock.oauth2.token.OAuth2TokenProvider
11+
import okhttp3.HttpUrl
12+
13+
internal class PasswordGrantHandler(
14+
private val tokenProvider: OAuth2TokenProvider
15+
) : GrantHandler {
16+
17+
override fun tokenResponse(
18+
request: OAuth2HttpRequest,
19+
issuerUrl: HttpUrl,
20+
oAuth2TokenCallback: OAuth2TokenCallback
21+
): OAuth2TokenResponse {
22+
val tokenRequest = request.asNimbusTokenRequest()
23+
val scope: String? = tokenRequest.scope?.toString()
24+
val passwordGrantTokenCallback = PasswordGrantTokenCallback(oAuth2TokenCallback)
25+
val accessToken: SignedJWT = tokenProvider.accessToken(tokenRequest, issuerUrl, passwordGrantTokenCallback)
26+
27+
return OAuth2TokenResponse(
28+
tokenType = "Bearer",
29+
accessToken = accessToken.serialize(),
30+
expiresIn = accessToken.expiresIn(),
31+
scope = scope
32+
)
33+
}
34+
35+
private class PasswordGrantTokenCallback(
36+
private val tokenCallback: OAuth2TokenCallback
37+
) : OAuth2TokenCallback by tokenCallback {
38+
39+
override fun subject(tokenRequest: TokenRequest) =
40+
tokenRequest.authorizationGrant
41+
?.let { it as? ResourceOwnerPasswordCredentialsGrant }
42+
?.username ?: tokenCallback.subject(tokenRequest)
43+
}
44+
}
Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package no.nav.security.mock.oauth2.grant
22

3-
import java.util.UUID
3+
import com.nimbusds.jwt.JWTClaimsSet
4+
import com.nimbusds.jwt.PlainJWT
45
import no.nav.security.mock.oauth2.token.OAuth2TokenCallback
6+
import java.util.UUID
57

68
typealias RefreshToken = String
79

@@ -10,9 +12,21 @@ internal data class RefreshTokenManager(
1012
) {
1113
operator fun get(refreshToken: RefreshToken) = cache[refreshToken]
1214

13-
fun refreshToken(tokenCallback: OAuth2TokenCallback): RefreshToken {
14-
val refreshToken = UUID.randomUUID().toString()
15+
fun refreshToken(tokenCallback: OAuth2TokenCallback, nonce: String?): RefreshToken {
16+
val jti = UUID.randomUUID().toString()
17+
// added for compatibility with keycloak js client which expects a jwt with nonce
18+
val refreshToken = nonce?.let { plainJWT(jti, nonce) } ?: jti
1519
cache[refreshToken] = tokenCallback
1620
return refreshToken
1721
}
22+
23+
private fun plainJWT(jti: String, nonce: String?): String =
24+
PlainJWT(
25+
JWTClaimsSet.parse(
26+
mapOf(
27+
"jti" to jti,
28+
"nonce" to nonce
29+
)
30+
)
31+
).serialize()
1832
}

src/main/kotlin/no/nav/security/mock/oauth2/http/OAuth2HttpRequestHandler.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import com.nimbusds.oauth2.sdk.GrantType
66
import com.nimbusds.oauth2.sdk.GrantType.AUTHORIZATION_CODE
77
import com.nimbusds.oauth2.sdk.GrantType.CLIENT_CREDENTIALS
88
import com.nimbusds.oauth2.sdk.GrantType.JWT_BEARER
9+
import com.nimbusds.oauth2.sdk.GrantType.PASSWORD
910
import com.nimbusds.oauth2.sdk.GrantType.REFRESH_TOKEN
1011
import com.nimbusds.oauth2.sdk.OAuth2Error
1112
import com.nimbusds.oauth2.sdk.ParseException
@@ -27,6 +28,7 @@ import no.nav.security.mock.oauth2.grant.AuthorizationCodeHandler
2728
import no.nav.security.mock.oauth2.grant.ClientCredentialsGrantHandler
2829
import no.nav.security.mock.oauth2.grant.GrantHandler
2930
import no.nav.security.mock.oauth2.grant.JwtBearerGrantHandler
31+
import no.nav.security.mock.oauth2.grant.PasswordGrantHandler
3032
import no.nav.security.mock.oauth2.grant.RefreshTokenGrantHandler
3133
import no.nav.security.mock.oauth2.grant.RefreshTokenManager
3234
import no.nav.security.mock.oauth2.grant.TOKEN_EXCHANGE
@@ -56,7 +58,8 @@ class OAuth2HttpRequestHandler(private val config: OAuth2Config) {
5658
CLIENT_CREDENTIALS to ClientCredentialsGrantHandler(config.tokenProvider),
5759
JWT_BEARER to JwtBearerGrantHandler(config.tokenProvider),
5860
TOKEN_EXCHANGE to TokenExchangeGrantHandler(config.tokenProvider),
59-
REFRESH_TOKEN to RefreshTokenGrantHandler(config.tokenProvider, refreshTokenManager)
61+
REFRESH_TOKEN to RefreshTokenGrantHandler(config.tokenProvider, refreshTokenManager),
62+
PASSWORD to PasswordGrantHandler(config.tokenProvider)
6063
)
6164

6265
private val exceptionHandler: ExceptionHandler = { request, error ->

src/test/kotlin/no/nav/security/mock/oauth2/StandaloneMockOAuth2ServerKtTest.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,6 @@ internal class StandaloneMockOAuth2ServerKtTest {
5454
}
5555
}
5656

57-
5857
@Test
5958
fun `load oauth2Config from file`() {
6059
withEnvironment(JSON_CONFIG_PATH to configFile) {

0 commit comments

Comments
 (0)