Skip to content
Draft
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 @@ -20,9 +20,9 @@ import com.intellij.ui.dsl.builder.panel
import com.jetbrains.gateway.api.ConnectionRequestor
import com.jetbrains.gateway.api.GatewayConnectionHandle
import com.jetbrains.gateway.api.GatewayConnectionProvider
import com.redhat.devtools.gateway.kubeconfig.KubeConfigUtils
import com.redhat.devtools.gateway.devworkspace.DevWorkspaces
import com.redhat.devtools.gateway.openshift.OpenShiftClientFactory
import com.redhat.devtools.gateway.kubeconfig.KubeConfigUtils
import com.redhat.devtools.gateway.openshift.DefaultClientBuilder
import com.redhat.devtools.gateway.openshift.isNotFound
import com.redhat.devtools.gateway.openshift.isUnauthorized
import com.redhat.devtools.gateway.util.ProgressCountdown
Expand All @@ -31,12 +31,7 @@ import com.redhat.devtools.gateway.util.messageWithoutPrefix
import com.redhat.devtools.gateway.view.SelectClusterDialog
import com.redhat.devtools.gateway.view.ui.Dialogs
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 @@ -205,8 +200,7 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
val ctx = DevSpacesContext()

indicator.update(message = "Initializing Kubernetes connection…")
val factory = OpenShiftClientFactory(KubeConfigUtils)
ctx.client = factory.create()
ctx.client = DefaultClientBuilder(KubeConfigUtils).build()

indicator.update(message = "Fetching workspace “$dwName” from namespace “$dwNamespace”…")
ctx.devWorkspace = DevWorkspaces(ctx.client).get(dwNamespace, dwName)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/*
* 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
)

suspend fun HttpClient.sendGetRequest(
url: String,
errorPrefix: String = "Request to $url failed"
): HttpResponse<String> {
val request = HttpRequest.newBuilder()
.uri(URI.create(url))
.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
}

suspend fun HttpClient.sendPostRequest(
url: String,
authHeader: String,
formBody: String,
errorPrefix: String = "Request to $url failed"
): AccessTokenResponseJson {
val request = HttpRequest.newBuilder()
.uri(URI(url))
.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,69 @@
/*
* 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.logger

Check warning on line 14 in src/main/kotlin/com/redhat/devtools/gateway/auth/code/OAuthDiscovery.kt

View workflow job for this annotation

GitHub Actions / Inspect code

Unused import directive

Unused import directive
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