Skip to content
Open
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
12 changes: 12 additions & 0 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,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 @@ -68,6 +69,17 @@ dependencies {
implementation("io.kubernetes:client-java:25.0.0")
implementation("com.fasterxml.jackson.core:jackson-databind:2.20.1")
implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml:2.20.1")

// 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
@@ -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,57 @@
/*
* 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.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)

PasswordSafe.instance.set(
attributes,
Credentials("sso", serialized)
)
}

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

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

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

override suspend fun clearToken() {
PasswordSafe.instance.set(attributes, null)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* 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.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
)

PasswordSafe.instance.set(attributes, credentials)
}

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

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

Loading
Loading