Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e3e1b37
feat: Gateway: CRW-8927 - Simplify login in to the OCP cluster from t…
vrubezhny Jan 20, 2026
2c1fd4f
feat: Gateway: CRW-8927 - Simplify login in to the OCP cluster from t…
vrubezhny Jan 28, 2026
345059b
feat: Gateway: CRW-8927 - Simplify login in to the OCP cluster from t…
vrubezhny Mar 6, 2026
3169baf
feat: Gateway: CRW-8927 - Simplify login in to the OCP cluster from t…
vrubezhny Mar 6, 2026
cbbdd79
fixed leaking httpclient
adietish Mar 27, 2026
b29f744
fixed blocking calls:
adietish Mar 27, 2026
582a5dd
avoid blocking thread requesting secret
adietish Mar 27, 2026
33aff8a
ensure digest is OpenSSL compliant lowercase
adietish Mar 27, 2026
c37d708
added logging to OpenShiftAuthSessionManager, RedHatAuthSessionManager
adietish Mar 27, 2026
f6e0d16
created AbstractAuthSessionManager as base class for OpenShiftAuthSes…
adietish Mar 27, 2026
74b99f0
dont freeze UI when DevSpacesServerStepView.onNext()
adietish Mar 27, 2026
debf651
simplified if statement, removed '== true'
adietish Mar 27, 2026
e290155
extracted class ClipboardTokenMonitor
adietish Mar 27, 2026
97eedd7
have 'Authentication:' label/cell y-aligned to the top
adietish Mar 27, 2026
317b6b4
created classes for the different strategies
adietish Mar 30, 2026
ee512e1
'Show token' checkbox is not to the right of the token textfield
adietish Apr 13, 2026
1ede5ac
replaced 'show token'-checkbox by icon within password field
adietish Apr 27, 2026
c60eb23
cleanup
adietish Apr 28, 2026
616522d
fix: make Red Hat SSO work with KUBECONFIG env var
adietish Apr 28, 2026
c456608
Fix the Pipeline Token Secret creation (RH SSO)
vrubezhny Apr 28, 2026
df617a6
feature: replaced overly verbose error dialog with human readable mes…
adietish Apr 28, 2026
5a079c7
all auth stragies now throw AuthenticationException
adietish Apr 28, 2026
84e5d51
nested 'Certificate Authority' txt field in collapsible panel
adietish Apr 28, 2026
b0a1dd0
replaced collapsible panel with default implementatin
adietish Apr 28, 2026
0cbdee4
cleanup
adietish Apr 28, 2026
2d208bb
put authenticating via OpenShift OAuth 2nd
adietish Apr 29, 2026
dbe7b14
provide only ClientCertificateAuthenticationStrategy with means to sa…
adietish Apr 29, 2026
ef7fd15
fixed 'Check Connection' button enablement when switching auth method
adietish Apr 29, 2026
d0fcc46
fix: corrected background color for auth tabs
adietish May 4, 2026
61430af
feat: support file paths for certificates in kubeconfig
adietish Apr 30, 2026
3c2ffa7
fix: corrected use of internal API, fixing plugin verification
adietish May 4, 2026
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
Binary file added .serena/cache/kotlin/document_symbols.pkl
Binary file not shown.
Binary file added .serena/cache/kotlin/raw_document_symbols.pkl
Binary file not shown.
5 changes: 5 additions & 0 deletions .serena/project.local.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# This file allows you to locally override settings in project.yml for development purposes.
#
# Use the same keys as in project.yml here. Any setting you specify will override the corresponding
# setting in project.yml, allowing you to customise the configuration for your local development environment
# without affecting the project configuration in project.yml (which is intended to be versioned).
12 changes: 12 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ plugins {
alias(libs.plugins.changelog) // Gradle Changelog Plugin
alias(libs.plugins.qodana) // Gradle Qodana Plugin
alias(libs.plugins.kover) // Gradle Kover Plugin
kotlin("plugin.serialization") version "1.9.22" // Serialization needed for RedHat Auth
}

group = providers.gradleProperty("pluginGroup").get()
Expand Down Expand Up @@ -71,6 +72,17 @@ dependencies {
implementation("io.kubernetes:client-java:26.0.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.21.3")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.21.3")

// RedHat Auth dependencies
implementation("io.ktor:ktor-server-core-jvm:2.3.7")
implementation("io.ktor:ktor-server-netty-jvm:2.3.7")
implementation("io.ktor:ktor-server-content-negotiation-jvm:2.3.7")

implementation("com.nimbusds:oauth2-oidc-sdk:11.15") // Core OIDC/OAuth2
implementation("com.nimbusds:nimbus-jose-jwt:9.37") // JWT processing

// JSON serialization
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3")
}

// Configure IntelliJ Platform Gradle Plugin - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-extension.html
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2024-2025 Red Hat, Inc.
* Copyright (c) 2024-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/
Expand Down Expand Up @@ -28,11 +28,10 @@ import com.redhat.devtools.gateway.openshift.isUnauthorized
import com.redhat.devtools.gateway.util.ProgressCountdown
import com.redhat.devtools.gateway.util.isCancellationException
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.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.*
import java.util.concurrent.CancellationException
import javax.swing.JComponent
import javax.swing.Timer
Expand All @@ -48,14 +47,22 @@ private const val DW_NAME = "dwName"
*/
class DevSpacesConnectionProvider : GatewayConnectionProvider {

private var clientFactory: OpenShiftClientFactory? = null

@OptIn(ExperimentalCoroutinesApi::class)
@Suppress("UnstableApiUsage")
override suspend fun connect(
parameters: Map<String, String>,
requestor: ConnectionRequestor
): GatewayConnectionHandle? {
val ctx = DevSpacesContext()

val confirmed = withContext(Dispatchers.Main) {
SelectClusterDialog(ctx).showAndConnect()
}

if (!confirmed) {
return null
}

return suspendCancellableCoroutine { cont ->
ProgressManager.getInstance().runProcessWithProgressSynchronously(
{
Expand Down Expand Up @@ -194,7 +201,6 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {

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

indicator.update(message = "Fetching workspace “$dwName” from namespace “$dwNamespace”…")
Expand Down Expand Up @@ -244,12 +250,10 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
private fun handleUnauthorizedError(err: ApiException): Boolean {
if (!err.isUnauthorized()) return false

val tokenNote = if (clientFactory?.isTokenAuth() == true)
"\n\nYou are using token-based authentication.\nUpdate your token in the kubeconfig file."
else ""

Dialogs.error(
"Your session has expired.\nPlease log in again to continue.$tokenNote",
"Your session has expired.\n" +
"Please authenticate again to continue.\n\n" +
"If you are using token-based authentication, update your token in the kubeconfig file.",
"Authentication Required"
)
return true
Expand All @@ -274,5 +278,4 @@ class DevSpacesConnectionProvider : GatewayConnectionProvider {
runnable.invoke()
}.start()
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* Copyright (c) 2025-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 java.net.URI
import com.nimbusds.oauth2.sdk.pkce.CodeVerifier
import com.nimbusds.openid.connect.sdk.Nonce
import kotlinx.serialization.Serializable

/**
* Represents the data needed to start the PKCE + Auth Code flow.
*/
data class AuthCodeRequest(
val authorizationUri: URI, // URL to open in browser
val codeVerifier: CodeVerifier, // Used for token exchange
val nonce: Nonce // Anti-replay / OIDC nonce
)

/**
* Represents the SSO Token
*/
data class SSOToken(
val accessToken: String,
val idToken: String,
val accountLabel: String,
val expiresAt: Long? = null
) {
fun isExpired(now: Long = System.currentTimeMillis()): Boolean =
expiresAt?.let { now >= it } ?: false
}

/**
* Represents the final result after exchanging code for tokens.
*/
enum class AuthTokenKind {
SSO,
TOKEN,
PIPELINE
}

@Serializable
data class TokenModel(
val accessToken: String,
val expiresAt: Long?, // null = non-expiring (pipeline)
val accountLabel: String,
val kind: AuthTokenKind,
val clusterApiUrl: String,
val namespace: String? = null,
val serviceAccount: String? = null
)

typealias Parameters = Map<String, String>

interface AuthCodeFlow {
/** Starts the 2-step auth flow and returns the info to open the browser */
suspend fun startAuthFlow(): AuthCodeRequest

/** Handles the redirect/callback and returns the final tokens for the 2-step auth flow */
suspend fun handleCallback(parameters: Parameters): SSOToken

/** Single-step auth flow - exchanges username/password to the final token */
suspend fun login(parameters: Parameters): SSOToken
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright (c) 2025-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
*/
@file:OptIn(kotlinx.serialization.ExperimentalSerializationApi::class)

package com.redhat.devtools.gateway.auth.code

import com.intellij.credentialStore.CredentialAttributes
import com.intellij.credentialStore.Credentials
import com.intellij.ide.passwordSafe.PasswordSafe
import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

class IdeaSecureTokenStorage : SecureTokenStorage {

private val json = Json {
ignoreUnknownKeys = true
explicitNulls = false
}

private val attributes = CredentialAttributes(
"com.redhat.devtools.gateway.auth.sso"
)

override suspend fun saveToken(token: TokenModel) {
val serialized = json.encodeToString(token)

withContext(Dispatchers.IO) {
PasswordSafe.instance.set(
attributes,
Credentials("sso", serialized)
)
}
}

override suspend fun loadToken(): TokenModel? {
val credentials = withContext(Dispatchers.IO) {
PasswordSafe.instance.get(attributes)
} ?: return null

val raw = credentials.password?.toString()
?: return null

return runCatching {
json.decodeFromString<TokenModel>(raw)
}.getOrNull()
}

override suspend fun clearToken() {
withContext(Dispatchers.IO) {
PasswordSafe.instance.set(attributes, null)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025-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.credentialStore.CredentialAttributes
import com.intellij.credentialStore.Credentials
import com.intellij.credentialStore.generateServiceName
import com.intellij.ide.passwordSafe.PasswordSafe
import kotlinx.coroutines.*
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json

class JBPasswordSafeTokenStorage : SecureTokenStorage {

private val attributes = CredentialAttributes(
generateServiceName(
"RedHatGatewayPlugin",
"RedHatAuthToken"
)
)

override suspend fun saveToken(token: TokenModel) {
val json = Json.encodeToString(token)

val credentials = Credentials(
"redhat",
json
)

withContext(Dispatchers.IO) {
PasswordSafe.instance.set(attributes, credentials)
}
}

override suspend fun loadToken(): TokenModel? {
val credentials = withContext(Dispatchers.IO) {
PasswordSafe.instance.get(attributes)
} ?: return null
val json = credentials.getPasswordAsString() ?: return null
return Json.decodeFromString(json)
}

override suspend fun clearToken() {
withContext(Dispatchers.IO) {
PasswordSafe.instance.set(attributes, null)
}
}
}

Loading
Loading