Skip to content

Commit 3eca227

Browse files
authored
Merge pull request #689 from openziti/fix-load-null-ztAPIs
fix identity loading issues - move away from kotlinx.serialization due to compatibility issues on older runtimes (springboot) - add more tests
2 parents 42552ea + 85e3362 commit 3eca227

File tree

12 files changed

+108
-158
lines changed

12 files changed

+108
-158
lines changed

gradle/libs.versions.toml

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ ziti-cli = "1.5.4"
1616
# third party
1717
lazysodium-java = "5.1.4"
1818
coroutines = "1.10.2"
19-
serialization = "1.8.1"
2019
slf4j = "2.0.17"
2120
jupiter = "5.12.2"
2221
gson = "2.13.1"
@@ -42,8 +41,6 @@ kotlin-reflect = { group = "org.jetbrains.kotlin", name = "kotlin-reflect", vers
4241
kotlin-test = { group = "org.jetbrains.kotlin", name = "kotlin-test-junit", version.ref = "kotlin" }
4342
kotlin-coroutines-lib = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "coroutines"}
4443
kotlin-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "coroutines"}
45-
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "serialization" }
46-
4744

4845
tls-channel = { group = "com.github.marianobarrios", name = "tls-channel", version.ref = "tls-channel" }
4946
sodium = { group = "com.goterl", name = "lazysodium-java", version.ref = "lazysodium-java" }

ziti/build.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import kotlinx.coroutines.runBlocking
2121
plugins {
2222
id("java-library")
2323
alias(libs.plugins.kotlin)
24-
alias(libs.plugins.kotlin.serialization)
2524
alias(libs.plugins.dokka)
2625
id("maven-publish")
2726
alias(libs.plugins.shadow)
@@ -38,7 +37,6 @@ dependencies {
3837
implementation(libs.kotlin.lib)
3938
implementation(libs.kotlin.coroutines.lib)
4039
implementation(libs.kotlin.reflect)
41-
implementation(libs.kotlinx.serialization.json)
4240
implementation(libs.slf4j.api)
4341
implementation(libs.gson)
4442
implementation(libs.jackson.bind)
@@ -160,7 +158,9 @@ tasks.register<Exec>("buildZiti") {
160158
group = LifecycleBasePlugin.BUILD_GROUP
161159
description = "Builds the Ziti CLI"
162160
environment("GOBIN", binDir.asFile.absolutePath)
163-
commandLine("go", "install", "github.com/openziti/ziti/ziti@v${zitiVersion}")
161+
commandLine("env",
162+
"go", "install", "github.com/openziti/ziti/ziti@v${zitiVersion}"
163+
)
164164
outputs.file(zitiCLI)
165165
}
166166

ziti/src/integrationTest/kotlin/org/openziti/api/ControllerTests.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class ControllerTests: BaseTest() {
4444

4545
@Test
4646
fun testOttEnrollment() = runTest {
47-
val ctrl = Controller(cfg.controller, cfg.sslContext())
47+
val ctrl = Controller.getActiveController(cfg.controllers(), cfg.sslContext())
4848
val session = ctrl.login()
4949
assertEquals(idName, session.identity.name)
5050
ctrl.logout()

ziti/src/integrationTest/kotlin/org/openziti/impl/LoadTests.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@
1616

1717
package org.openziti.impl
1818

19-
import kotlinx.serialization.json.Json
2019
import org.junit.jupiter.api.AfterEach
2120
import org.junit.jupiter.api.Assertions.assertEquals
2221
import org.junit.jupiter.api.BeforeEach
@@ -43,7 +42,11 @@ class LoadTests: BaseTest() {
4342

4443
@Test
4544
fun testLoadConfigByteArray() {
46-
val cfgBytes = Json.encodeToString(cfg).toByteArray(Charsets.UTF_8)
45+
46+
val cfgBytes = ByteArrayOutputStream().use {
47+
cfg.store(it)
48+
it.toByteArray()
49+
}
4750

4851
Ziti.init(cfgBytes, false)
4952

ziti/src/integrationTest/kotlin/org/openziti/net/ConnectionTests.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,4 +216,10 @@ class ConnectionTests: BaseTest() {
216216
}
217217
}
218218

219+
@Test
220+
fun closeUnconnectedSocket() = runTest(timeout = 1.seconds) {
221+
val sock = Ziti.getSocketFactory().createSocket()
222+
sock.close()
223+
assertTrue(sock.isClosed)
224+
}
219225
}

ziti/src/main/kotlin/org/openziti/IdentityConfig.kt

Lines changed: 23 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,10 @@
1616

1717
package org.openziti
1818

19-
import kotlinx.serialization.ExperimentalSerializationApi
20-
import kotlinx.serialization.SerialName
21-
import kotlinx.serialization.Serializable
22-
import kotlinx.serialization.json.Json
23-
import kotlinx.serialization.json.decodeFromStream
19+
import com.fasterxml.jackson.annotation.JsonIgnoreProperties
20+
import com.fasterxml.jackson.annotation.JsonProperty
21+
import com.fasterxml.jackson.databind.ObjectMapper
22+
import com.fasterxml.jackson.module.kotlin.kotlinModule
2423
import org.openziti.identity.makeSSLContext
2524
import org.openziti.util.readCerts
2625
import org.openziti.util.readKey
@@ -34,23 +33,26 @@ import javax.net.ssl.SSLContext
3433
/**
3534
* Identity loaded from identity configuration JSON.
3635
*/
37-
@Serializable data class IdentityConfig (
36+
@JsonIgnoreProperties(ignoreUnknown = true)
37+
data class IdentityConfig (
3838
/**
3939
* Ziti controller address.
4040
*/
41-
@SerialName("ztAPI") val controller: String,
41+
@JsonProperty("ztAPI")
42+
val controller: String,
4243

4344
/**
4445
* List of ziti controller addresses.
4546
*/
46-
@SerialName("ztAPIs") val controllers: Collection<String> = listOf(controller),
47+
@JsonProperty("ztAPIs")
48+
val apiEndpoints: Collection<String>? = listOf(controller),
4749

4850
/**
4951
* Identity credentials.
5052
*/
5153
val id: Id): Identity {
5254

53-
@Serializable data class Id(
55+
data class Id(
5456
/** Identity private key in PEM format */
5557
val key: String? = null,
5658
/** Identity X.509 certificate in PEM format */
@@ -63,44 +65,36 @@ import javax.net.ssl.SSLContext
6365
* Store identity configuration to the output stream.
6466
*/
6567
fun store(output: OutputStream) {
66-
output.write(json.encodeToString(this).encodeToByteArray())
68+
jsonMapper.writeValue(output, this)
6769
}
6870

6971
/**
7072
* @inheritDoc
7173
*/
72-
override fun controllers(): Collection<String> = controllers
74+
override fun controllers(): Collection<String> = apiEndpoints ?: listOf(controller)
7375

7476
/**
7577
* @inheritDoc
7678
*/
77-
override fun sslContext(): SSLContext = makeSSLContext(key, cert, caCerts)
79+
override fun sslContext(): SSLContext = makeSSLContext(key(), cert(), caCerts())
7880

79-
internal val key: PrivateKey? by lazy {
80-
id.key?.let {
81-
readKey(it)
82-
}
83-
}
84-
85-
internal val cert: List<X509Certificate> by lazy {
86-
id.cert?.let {
87-
readCerts(it)
88-
} ?: emptyList()
89-
}
81+
internal fun key(): PrivateKey? = id.key?.let { readKey(it) }
9082

91-
internal val caCerts by lazy {
92-
readCerts(id.ca)
93-
}
83+
internal fun cert(): List<X509Certificate> = id.cert?.let {
84+
readCerts(it)
85+
} ?: emptyList()
9486

87+
internal fun caCerts() = readCerts(id.ca)
9588

9689
companion object {
97-
90+
val jsonMapper: ObjectMapper =
91+
ObjectMapper().registerModule(kotlinModule())
9892
/**
9993
* Load identity configuration from the input stream.
10094
*/
101-
@OptIn(ExperimentalSerializationApi::class)
10295
@JvmStatic
103-
fun load(input: InputStream): IdentityConfig = Json.decodeFromStream(serializer(), input)
96+
fun load(input: InputStream): IdentityConfig =
97+
jsonMapper.readValue(input, IdentityConfig::class.java)
10498

10599
/**
106100
* Load identity configuration from the byte array.
@@ -126,9 +120,5 @@ import javax.net.ssl.SSLContext
126120

127121
return load(cfg.toByteArray())
128122
}
129-
130-
private val json = Json {
131-
prettyPrint = true
132-
}
133123
}
134124
}

ziti/src/main/kotlin/org/openziti/api/Authentication.kt

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,12 @@
1616

1717
package org.openziti.api
1818

19+
import com.fasterxml.jackson.databind.JsonNode
20+
import com.fasterxml.jackson.databind.ObjectMapper
21+
import com.fasterxml.jackson.module.kotlin.kotlinModule
1922
import kotlinx.coroutines.Dispatchers
2023
import kotlinx.coroutines.asExecutor
2124
import kotlinx.coroutines.future.await
22-
import kotlinx.serialization.json.Json
23-
import kotlinx.serialization.json.JsonObject
24-
import kotlinx.serialization.json.jsonObject
25-
import kotlinx.serialization.json.jsonPrimitive
2625
import org.openziti.edge.ApiClient
2726
import org.openziti.edge.api.AuthenticationApi
2827
import org.openziti.edge.api.CurrentApiSessionApi
@@ -128,6 +127,7 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
128127
val Encoder: Base64.Encoder = Base64.getUrlEncoder().withoutPadding()
129128
const val DISCOVERY = "/oidc/.well-known/openid-configuration"
130129
const val TOKEN_EXCHANGE_GRANT = "urn:ietf:params:oauth:grant-type:token-exchange"
130+
val json: ObjectMapper = ObjectMapper().registerModule(kotlinModule())
131131
}
132132

133133

@@ -136,7 +136,7 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
136136
.followRedirects(HttpClient.Redirect.NEVER)
137137
.executor(Dispatchers.IO.asExecutor())
138138
.build()
139-
lateinit var tokens: JsonObject
139+
lateinit var tokens: JsonNode
140140

141141
private val config by lazy {
142142
loadConfig()
@@ -166,7 +166,7 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
166166
.POST(HttpRequest.BodyPublishers.ofString(body))
167167
.build()
168168

169-
i{"sending auth request $req"}
169+
d{"sending auth request $req"}
170170
val resp = http.sendAsync(req, HttpResponse.BodyHandlers.ofString()).await()
171171

172172
if (resp.statusCode() / 100 != 3 && resp.headers().firstValue("Location").isEmpty) {
@@ -203,7 +203,7 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
203203
return query["code"]!! to query["state"]!!
204204
}
205205

206-
private suspend fun getTokens(ep: URI, code: String, codeVerifier: String): JsonObject {
206+
private suspend fun getTokens(ep: URI, code: String, codeVerifier: String): JsonNode {
207207
val body = formatForm(
208208
mapOf(
209209
"grant_type" to "authorization_code",
@@ -219,7 +219,7 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
219219
.POST(HttpRequest.BodyPublishers.ofString(body)).build()
220220

221221
val tokenResp = http.sendAsync(req, HttpResponse.BodyHandlers.ofString()).await()
222-
return Json.parseToJsonElement(tokenResp.body()).jsonObject
222+
return json.readTree(tokenResp.body())
223223
}
224224

225225
override suspend fun login(): ZitiAuthenticator.ZitiAccessToken {
@@ -230,9 +230,9 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
230230
val state = Encoder.encodeToString(Random.Default.nextBytes(30))
231231

232232

233-
val authEndpoint = config["authorization_endpoint"]?.jsonPrimitive?.content
233+
val authEndpoint = config["authorization_endpoint"]?.textValue()
234234
?: throw Exception("Missing authorization endpoint in OIDC config")
235-
val tokenEndpoint = config["token_endpoint"]?.jsonPrimitive?.content
235+
val tokenEndpoint = config["token_endpoint"]?.textValue()
236236
?: throw Exception("Missing token endpoint in OIDC config")
237237

238238
val loginURI = startAuth(authEndpoint, challenge, state)
@@ -244,14 +244,14 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
244244
tokens = getTokens(URI.create(tokenEndpoint), code, codeVerifier)
245245
d{ "OIDC tokens: $tokens" }
246246

247-
val accessToken = tokens["access_token"]?.jsonPrimitive?.content
247+
val accessToken = tokens["access_token"]?.textValue()
248248
?: throw Exception("Missing access token in OIDC response")
249-
val exp = OffsetDateTime.now().plusSeconds(tokens["expires_in"]?.jsonPrimitive?.content?.toLong() ?: 600)
249+
val exp = OffsetDateTime.now().plusSeconds(tokens["expires_in"]?.longValue() ?: 600)
250250
return ZitiAuthenticator.ZitiAccessToken(ZitiAuthenticator.TokenType.BEARER, accessToken, exp)
251251
}
252252

253253
override suspend fun refresh(): ZitiAuthenticator.ZitiAccessToken {
254-
val refreshToken = tokens.get("refresh_token")?.jsonPrimitive?.content
254+
val refreshToken = tokens.get("refresh_token")?.textValue()
255255

256256
if (refreshToken == null) return login()
257257

@@ -263,7 +263,7 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
263263
)
264264

265265
val req = HttpRequest.newBuilder()
266-
.uri(config["token_endpoint"]?.jsonPrimitive?.content?.let { URI.create(it) })
266+
.uri(config["token_endpoint"]?.textValue()?.let { URI.create(it) })
267267
.header("Accept", "application/x-www-form-urlencoded")
268268
.POST(HttpRequest.BodyPublishers.ofString(formatForm(form)))
269269
.build()
@@ -274,14 +274,14 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
274274
return login()
275275
}
276276

277-
tokens = Json.parseToJsonElement(resp.body()).jsonObject
278-
val accessToken = tokens["access_token"]?.jsonPrimitive?.content
277+
tokens = json.readTree(resp.body())
278+
val accessToken = tokens["access_token"]?.textValue()
279279
?: throw Exception("Missing access token in OIDC response")
280-
val exp = OffsetDateTime.now().plusSeconds(tokens["expires_in"]?.jsonPrimitive?.content?.toLong() ?: 600)
280+
val exp = OffsetDateTime.now().plusSeconds(tokens["expires_in"]?.longValue() ?: 600)
281281
return ZitiAuthenticator.ZitiAccessToken(ZitiAuthenticator.TokenType.BEARER, accessToken, exp)
282282
}
283283

284-
private fun loadConfig(): JsonObject {
284+
private fun loadConfig(): JsonNode {
285285
val url = URI.create(ep).resolve(DISCOVERY)
286286

287287
val request = HttpRequest.newBuilder(url)
@@ -293,7 +293,7 @@ class InternalOIDC(val ep: String, ssl: SSLContext): ZitiAuthenticator, Logged b
293293
throw Exception("Failed to get OIDC config: ${response.statusCode()}")
294294
}
295295

296-
i("OIDC config response: ${response.body()}")
297-
return Json.parseToJsonElement(response.body()).jsonObject
296+
v {"OIDC config response: ${response.body()}"}
297+
return ObjectMapper().readTree(response.body())
298298
}
299299
}

ziti/src/main/kotlin/org/openziti/identity/Enroller.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ internal class Enroller(
144144

145145
return IdentityConfig(
146146
controller = api.baseUri,
147-
controllers = controllers,
147+
apiEndpoints = controllers,
148148
id = IdentityConfig.Id(
149149
key = kp.private.toPEM(),
150150
cert = cert,
@@ -182,7 +182,7 @@ internal class Enroller(
182182

183183
return IdentityConfig(
184184
controller = api.baseUri,
185-
controllers = controllers,
185+
apiEndpoints = controllers,
186186
id = IdentityConfig.Id(
187187
key = key,
188188
cert = cert,

ziti/src/main/kotlin/org/openziti/net/nio/AsychChannelSocket.kt

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,11 @@ class AsychChannelSocket(internal val impl: AsyncSocketImpl = AsyncSocketImpl())
4141
connect(InetSocketAddress(address, port))
4242
}
4343

44-
override fun isConnected(): Boolean {
45-
return impl.channel.remoteAddress != null
46-
}
47-
48-
override fun isClosed(): Boolean = !impl.channel.isOpen
44+
override fun isConnected(): Boolean = impl.isConnected()
45+
override fun isClosed(): Boolean = impl.isClosed()
4946

5047
override fun close() {
51-
impl.channel.close()
48+
impl.closeInternal()
5249
}
5350

5451
override fun getInputStream(): InputStream {

ziti/src/main/kotlin/org/openziti/net/nio/AsyncSocketImpl.kt

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,14 @@ internal class AsyncSocketImpl(private val connector: Connector = DefaultConnect
177177
}
178178
}
179179

180-
override fun close() {
181-
if (::channel.isInitialized)
182-
channel.close()
180+
internal fun isConnected(): Boolean = ::channel.isInitialized && channel.remoteAddress != null
181+
fun isClosed(): Boolean = ::channel.isInitialized && !channel.isOpen
182+
183+
override fun close() = closeInternal()
184+
internal fun closeInternal() {
185+
if (!::channel.isInitialized)
186+
channel = AsynchronousSocketChannel.open()
187+
channel.close()
183188
}
184189

185190
override fun getOption(optID: Int): Any? {

0 commit comments

Comments
 (0)