Skip to content

Commit d489776

Browse files
committed
1
Tool: gitpod/catfood.gitpod.cloud
1 parent 81338c2 commit d489776

16 files changed

+445
-410
lines changed

.idea/misc.xml

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

components/ide/jetbrains/toolbox/build.gradle.kts

+1-3
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ dependencies {
4949
// com.connectrpc https://mvnrepository.com/artifact/com.connectrpc
5050
// connect rpc dependencies
5151
implementation("com.squareup.okhttp3:okhttp:4.12.0")
52+
implementation("com.squareup.okhttp3:logging-interceptor:4.12.0")
5253
implementation("com.connectrpc:connect-kotlin-okhttp:0.6.0")
5354
implementation("com.connectrpc:connect-kotlin:0.6.0")
5455
// Java specific dependencies.
@@ -83,10 +84,7 @@ tasks.shadowJar {
8384
"com.jetbrains.toolbox.gateway",
8485
"com.jetbrains",
8586
"org.jetbrains",
86-
// "com.squareup.okhttp3",
87-
// "org.slf4j",
8887
"org.jetbrains.intellij",
89-
// "com.squareup.okio",
9088
"kotlin."
9189
)
9290

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2025 Gitpod GmbH. All rights reserved.
2+
// Licensed under the GNU Affero General Public License (AGPL).
3+
// See License.AGPL.txt in the project root for license information.
4+
5+
package io.gitpod.toolbox.auth
6+
7+
import com.jetbrains.toolbox.api.core.auth.Account
8+
import kotlinx.serialization.Serializable
9+
import kotlinx.serialization.encodeToString
10+
import kotlinx.serialization.json.Json
11+
12+
@Serializable
13+
class GitpodAccount : Account {
14+
private val userID: String
15+
private val name: String
16+
private val host: String
17+
private val scopes: List<String>
18+
19+
constructor(id: String, name: String, host: String, scopes: List<String>) {
20+
this.userID = id
21+
this.name = name
22+
this.host = host
23+
this.scopes = scopes
24+
}
25+
26+
override fun getId() = userID
27+
override fun getFullName() = name
28+
29+
fun getGitpodHost() = host
30+
31+
fun encode(): String {
32+
return Json.encodeToString(this)
33+
}
34+
35+
companion object {
36+
fun decode(str: String): GitpodAccount {
37+
return Json.decodeFromString<GitpodAccount>(str)
38+
}
39+
}
40+
}
41+
42+
class GitpodLoginConfiguration(val hostUrl: String)
43+
44+
val authScopesJetBrainsToolbox = listOf(
45+
"function:getGitpodTokenScopes",
46+
"function:getLoggedInUser",
47+
"function:getOwnerToken",
48+
"function:getWorkspace",
49+
"function:getWorkspaces",
50+
"function:listenForWorkspaceInstanceUpdates",
51+
"function:startWorkspace",
52+
"function:stopWorkspace",
53+
"function:deleteWorkspace",
54+
"function:getToken",
55+
"resource:default",
56+
)

components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/auth/GitpodAuthManager.kt

+25-131
Original file line numberDiff line numberDiff line change
@@ -4,50 +4,27 @@
44

55
package io.gitpod.toolbox.auth
66

7-
import com.connectrpc.Code
8-
import com.connectrpc.ConnectException
97
import com.jetbrains.toolbox.api.core.auth.*
10-
import io.gitpod.publicapi.experimental.v1.UserServiceClient
11-
import io.gitpod.toolbox.service.GitpodPublicApiManager
8+
import io.gitpod.toolbox.service.GitpodPublicApi
129
import io.gitpod.toolbox.service.Utils
1310
import kotlinx.coroutines.future.future
14-
import kotlinx.serialization.Serializable
15-
import kotlinx.serialization.encodeToString
16-
import kotlinx.serialization.json.Json
17-
import kotlinx.serialization.json.jsonObject
18-
import kotlinx.serialization.json.jsonPrimitive
1911
import java.net.URI
20-
import java.util.*
2112
import java.util.concurrent.Future
2213

23-
// TODO(hw): Validate Scopes
24-
val authScopesJetBrainsToolbox = listOf(
25-
"function:getGitpodTokenScopes",
26-
"function:getLoggedInUser",
27-
"function:getOwnerToken",
28-
"function:getWorkspace",
29-
"function:getWorkspaces",
30-
"function:listenForWorkspaceInstanceUpdates",
31-
"function:startWorkspace",
32-
"function:stopWorkspace",
33-
"function:deleteWorkspace",
34-
"function:getToken",
35-
"resource:default",
36-
)
37-
3814
class GitpodAuthManager {
39-
private val manager: PluginAuthManager<GitpodAccount, GitpodLoginConfiguration>
15+
val manager: PluginAuthManager<GitpodAccount, GitpodLoginConfiguration>
4016
private var loginListeners: MutableList<() -> Unit> = mutableListOf()
4117
private var logoutListeners: MutableList<() -> Unit> = mutableListOf()
4218

19+
4320
init {
4421
manager = Utils.sharedServiceLocator.getAuthManager(
4522
"gitpod",
4623
GitpodAccount::class.java,
4724
{ it.encode() },
4825
{ GitpodAccount.decode(it) },
4926
{ oauthToken, authCfg -> getAuthenticatedUser(URI.create(authCfg.baseUrl).host, oauthToken) },
50-
{ oauthToken, gpAccount -> getAuthenticatedUser(gpAccount.getHost(), oauthToken) },
27+
{ oauthToken, gpAccount -> getAuthenticatedUser(gpAccount.getGitpodHost(), oauthToken) },
5128
{ gpLoginCfg ->
5229
val authParams = mapOf(
5330
"response_type" to "code",
@@ -90,43 +67,32 @@ class GitpodAuthManager {
9067

9168
private fun resetCurrentAccount(accountId: String) {
9269
val account = manager.accountsWithStatus.find { it.account.id == accountId }?.account ?: return
93-
Utils.logger.debug("reset settings for ${account.getHost()}")
94-
Utils.gitpodSettings.resetSettings(account.getHost())
70+
Utils.logger.debug("reset settings for ${account.getGitpodHost()}")
71+
Utils.gitpodSettings.resetSettings(account.getGitpodHost())
72+
}
73+
74+
fun logoutCurrentAccount() {
75+
getCurrentAccount()?.let { manager.logout(it.id) }
9576
}
9677

9778
fun getCurrentAccount(): GitpodAccount? {
98-
return manager.accountsWithStatus.find { it.account.getHost() == Utils.gitpodSettings.gitpodHost }?.account
79+
return manager.accountsWithStatus.find { it.account.getGitpodHost() == Utils.gitpodSettings.gitpodHost }?.account
9980
}
10081

101-
suspend fun loginWithHost(host: String): Boolean {
102-
val currentAccount = getCurrentAccount()
103-
if (currentAccount?.getHost() == host) {
104-
if (currentAccount.isValidate()) {
105-
return true
106-
} else {
107-
manager.logout(currentAccount.id)
108-
Utils.openUrl(this.getOAuthLoginUrl(host))
109-
return false
110-
}
111-
}
112-
val account = manager.accountsWithStatus.find { it.account.getHost() == host }?.account
113-
if (account != null) {
114-
if (account.isValidate()) {
115-
Utils.gitpodSettings.gitpodHost = host
116-
loginListeners.forEach { it() }
117-
return true
118-
} else {
119-
manager.logout(account.id)
120-
Utils.openUrl(this.getOAuthLoginUrl(host))
82+
fun loginWithHost(host: String): Boolean {
83+
val account = manager.accountsWithStatus.find {
84+
if (it.expired) {
85+
manager.logout(it.account.id)
12186
return false
12287
}
123-
}
124-
Utils.openUrl(this.getOAuthLoginUrl(host))
125-
return false
126-
}
88+
return it.account.getGitpodHost() == host
89+
}?.account
12790

128-
fun logout() {
129-
getCurrentAccount()?.let { manager.logout(it.id) }
91+
if (account == null) {
92+
Utils.openUrl(getOAuthLoginUrl(host))
93+
return false
94+
}
95+
return true // getFromCache
13096
}
13197

13298
fun getOAuthLoginUrl(gitpodHost: String): String {
@@ -153,81 +119,9 @@ class GitpodAuthManager {
153119

154120
private fun getAuthenticatedUser(gitpodHost: String, oAuthToken: OAuthToken): Future<GitpodAccount> {
155121
return Utils.coroutineScope.future {
156-
val bearerToken = getBearerToken(oAuthToken)
157-
val client = GitpodPublicApiManager.createClient(gitpodHost, bearerToken)
158-
val user = GitpodPublicApiManager.tryGetAuthenticatedUser(UserServiceClient(client))
159-
GitpodAccount(bearerToken, user.id, user.name, gitpodHost, authScopesJetBrainsToolbox)
160-
}
161-
}
162-
163-
private fun getBearerToken(oAuthToken: OAuthToken): String {
164-
val parts = oAuthToken.authorizationHeader.replace("Bearer ", "").split(".")
165-
// We don't validate jwt token
166-
if (parts.size != 3) {
167-
throw IllegalArgumentException("Invalid JWT")
168-
}
169-
val decoded = String(Base64.getUrlDecoder().decode(parts[1].toByteArray()))
170-
val jsonElement = Json.parseToJsonElement(decoded)
171-
val payloadMap = jsonElement.jsonObject.mapValues {
172-
it.value.jsonPrimitive.content
173-
}
174-
return payloadMap["jti"] ?: throw IllegalArgumentException("Failed to parse JWT token")
175-
}
176-
177-
}
178-
179-
class GitpodLoginConfiguration(val hostUrl: String)
180-
181-
@Serializable
182-
class GitpodAccount : Account {
183-
private val credentials: String
184-
private val id: String
185-
private val name: String
186-
private val host: String
187-
private val scopes: List<String>
188-
189-
constructor(credentials: String, id: String, name: String, host: String, scopes: List<String>) {
190-
this.credentials = credentials
191-
this.id = id
192-
this.name = name
193-
this.host = host
194-
this.scopes = scopes
195-
}
196-
197-
override fun getId() = id
198-
override fun getFullName() = name
199-
fun getCredentials() = credentials
200-
fun getHost() = host
201-
fun getScopes() = scopes
202-
203-
fun encode(): String {
204-
return Json.encodeToString(this)
205-
}
206-
207-
suspend fun isValidate(): Boolean {
208-
val hostUrl = "https://$host"
209-
val client = GitpodPublicApiManager.createClient(URI(hostUrl).host, credentials)
210-
Utils.logger.debug("validating account $hostUrl")
211-
try {
212-
GitpodPublicApiManager.tryGetAuthenticatedUser(UserServiceClient(client))
213-
// TODO: Verify scopes
214-
return true
215-
} catch (e: ConnectException) {
216-
// TODO(hw): Server close jsonrpc so papi server respond internal error
217-
if (e.code == Code.UNAUTHENTICATED || (e.code == Code.INTERNAL_ERROR && e.message != null && e.message!!.contains(
218-
"jsonrpc2: connection is closed"
219-
))
220-
) {
221-
Utils.logger.error("account $hostUrl is not valid")
222-
return false
223-
}
224-
return true
225-
}
226-
}
227-
228-
companion object {
229-
fun decode(str: String): GitpodAccount {
230-
return Json.decodeFromString<GitpodAccount>(str)
122+
val apiClient = GitpodPublicApi(gitpodHost, { Utils.coroutineScope.future { oAuthToken } }) {_, _ -> }
123+
val user = apiClient.getAuthenticatedUser()
124+
GitpodAccount(user.id, user.name, gitpodHost, authScopesJetBrainsToolbox)
231125
}
232126
}
233127
}

components/ide/jetbrains/toolbox/src/main/kotlin/io/gitpod/toolbox/components/Icon.kt

+2-8
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,9 @@
55
package io.gitpod.toolbox.components
66

77
import com.jetbrains.toolbox.api.core.ui.icons.SvgIcon
8-
import io.gitpod.toolbox.gateway.GitpodGatewayExtension
9-
10-
@Suppress("FunctionName")
11-
fun GitpodIconGray(): SvgIcon {
12-
return SvgIcon(GitpodGatewayExtension::class.java.getResourceAsStream("/icon-gray.svg")?.readAllBytes() ?: byteArrayOf())
13-
}
8+
import io.gitpod.toolbox.gateway.GitpodRemoteDevExtension
149

1510
@Suppress("FunctionName")
1611
fun GitpodIcon(): SvgIcon {
17-
return SvgIcon(GitpodGatewayExtension::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf())
12+
return SvgIcon(GitpodRemoteDevExtension::class.java.getResourceAsStream("/icon.svg")?.readAllBytes() ?: byteArrayOf())
1813
}
19-

0 commit comments

Comments
 (0)