Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ build
.DS_Store
.intellijPlatform/
.kotlin/
.vscode/
.opencode/
.serena/
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,7 @@
import com.redhat.devtools.gateway.util.ProgressCountdown
import com.redhat.devtools.gateway.view.SelectClusterDialog
import io.kubernetes.client.openapi.ApiException
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.withContext
import kotlinx.coroutines.*
import java.util.concurrent.CancellationException
import javax.swing.JComponent
import javax.swing.Timer
Expand Down Expand Up @@ -74,6 +69,14 @@
val thinClient = handle.clientHandle
?: throw RuntimeException("Failed to obtain ThinClientHandle")

if (thinClient.clientPresent) {
indicator.text = "Workspace IDE has started successfully"
indicator.text2 = "Opening project window…"
runDelayed(1000) { if (indicator.isRunning) indicator.stop() }
cont.resume(handle)
return@runProcessWithProgressSynchronously
}

indicator.text = "Waiting for workspace IDE to start..."

val ready = CompletableDeferred<GatewayConnectionHandle?>()
Expand All @@ -94,6 +97,17 @@
cont.resumeWith(Result.failure(error))
}
}

runBlocking {
withTimeoutOrNull(60_000L) { ready.await() } ?: run {

Check warning on line 102 in src/main/kotlin/com/redhat/devtools/gateway/DevSpacesConnectionProvider.kt

View workflow job for this annotation

GitHub Actions / Inspect code

Long overload to Duration conversion

Legacy Long overload can be converted to Duration
if (ready.isActive) {
indicator.text = "Workspace IDE did not report readiness in time."
ready.completeExceptionally(
RuntimeException("Workspace IDE did not report readiness in time.")
)
}
}
}
} catch (e: Exception) {
DevSpacesConnectionProviderErrors.showDialog(e, ctx, indicator)
runDelayed(2000) { if (indicator.isRunning) indicator.stop() }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2026 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package com.redhat.devtools.gateway.auth.code

import kotlinx.coroutines.future.await
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.net.URI
import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse

private val json = Json { ignoreUnknownKeys = true }

@Serializable
data class AccessTokenResponseJson(
@SerialName("access_token") val accessToken: String,
@SerialName("expires_in") val expiresIn: Long = 0L,
@SerialName("id_token") val idToken: String = ""
)

suspend fun HttpClient.sendGetRequest(
url: String,
errorPrefix: String = "Request to $url failed"
): HttpResponse<String> {
val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.timeout(java.time.Duration.ofSeconds(30))
.GET()
.build()
val response = sendAsync(request, HttpResponse.BodyHandlers.ofString()).await()
if (response.statusCode() !in 200..299) {
error("$errorPrefix: ${response.statusCode()}\n${response.body()}")
}
return response
}
Comment thread
adietish marked this conversation as resolved.

suspend fun HttpClient.sendPostRequest(
url: String,
authHeader: String,
formBody: String,
errorPrefix: String = "Request to $url failed"
): AccessTokenResponseJson {
val request = HttpRequest.newBuilder()
.uri(URI(url))
.timeout(java.time.Duration.ofSeconds(30))
.header("Authorization", authHeader)
.header("Content-Type", "application/x-www-form-urlencoded")
.header("Accept", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(formBody))
.build()
val response = sendAsync(request, HttpResponse.BodyHandlers.ofString()).await()
if (response.statusCode() !in 200..299) {
error("$errorPrefix: ${response.statusCode()}\n${response.body()}")
}
return json.decodeFromString(AccessTokenResponseJson.serializer(), response.body())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2026 Red Hat, Inc.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
*/
package com.redhat.devtools.gateway.auth.code

import com.intellij.openapi.diagnostic.thisLogger
import com.redhat.devtools.gateway.util.toServerBaseUrl
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import java.net.URI
import java.net.http.HttpClient
import javax.net.ssl.SSLContext

private val json = Json { ignoreUnknownKeys = true }

@Serializable
data class OAuthMetadata(
val issuer: String,
@SerialName("authorization_endpoint")
val authorizationEndpoint: String,
@SerialName("token_endpoint")
val tokenEndpoint: String
)

class OAuthDiscovery(
apiServerUrl: String,
sslContext: SSLContext,
private val client: HttpClient = HttpClient.newBuilder()
.sslContext(sslContext)
.version(HttpClient.Version.HTTP_1_1)
.followRedirects(HttpClient.Redirect.NORMAL)
.build()
) {

private val discoveryUrl = "$apiServerUrl/.well-known/oauth-authorization-server"

suspend fun discoverOAuthMetadata(): OAuthMetadata {
val response = client.sendGetRequest(discoveryUrl)
return json.decodeFromString(OAuthMetadata.serializer(), response.body())
}

suspend fun endpointBaseUrls(): List<String> {
thisLogger().info("TLS trust: discovering OAuth endpoints from $discoveryUrl")
val md = try {
discoverOAuthMetadata()
} catch (e: Exception) {
thisLogger().error("TLS trust: OAuth discovery request to $discoveryUrl failed", e)
throw e
}
val urls = listOf(md.tokenEndpoint, md.authorizationEndpoint)
.map { URI(it).toServerBaseUrl() }
.distinct()
thisLogger().info(
"TLS trust: OAuth discovery succeeded (issuer=${md.issuer}, " +
"endpoints=${urls.joinToString()})"
)
return urls
}
}
Loading
Loading