mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[space plugin] authorization: get rid of Ktor Server and implement simple socket auth.
Better solution required GitOrigin-RevId: 086ac8ccf3c4d6f41a75a6b9c643bfa5369e6aa9
This commit is contained in:
committed by
intellij-monorepo-bot
parent
bbe32c654d
commit
c0555b53ed
@@ -1,65 +0,0 @@
|
||||
package circlet.auth
|
||||
|
||||
import circlet.common.oauth.*
|
||||
import circlet.platform.api.oauth.*
|
||||
import circlet.platform.workspaces.*
|
||||
import io.ktor.application.*
|
||||
import io.ktor.request.*
|
||||
import io.ktor.response.*
|
||||
import io.ktor.routing.*
|
||||
import io.ktor.server.engine.*
|
||||
import io.ktor.server.jetty.*
|
||||
import libraries.coroutines.extra.*
|
||||
import libraries.klogging.*
|
||||
import runtime.utils.*
|
||||
import java.awt.*
|
||||
import java.net.*
|
||||
import java.util.concurrent.*
|
||||
import kotlin.coroutines.*
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
val log = KLoggers.logger()
|
||||
|
||||
private val ports = lazy {
|
||||
val regex = "[^\\d]*(?<port>\\d+)[^\\d]*".toRegex()
|
||||
val ports = IdeaOAuthConfig.redirectURIs.mapNotNull { it ->
|
||||
regex.find(it)?.groups?.get("port")?.value?.toIntOrNull()
|
||||
}
|
||||
Pair(ports.min() ?: 1000, ports.max() ?: 65535)
|
||||
}
|
||||
|
||||
suspend fun accessTokenInteractive(lifetime: Lifetime, config: WorkspaceConfiguration): OAuthTokenResponse {
|
||||
val port = selectFreePort(ports.value.first, ports.value.second)
|
||||
val codeFlow = CodeFlowConfig(config, "http://localhost:$port/auth")
|
||||
|
||||
return suspendCancellableCoroutine { cnt ->
|
||||
|
||||
val server = try {
|
||||
embeddedServer(Jetty, port, "localhost") {
|
||||
routing {
|
||||
get("auth") {
|
||||
val token = codeFlow.handleCodeFlowRedirect(call.request.uri)
|
||||
call.respondText("<script>close()</script>", io.ktor.http.ContentType("text", "html"))
|
||||
cnt.resume(token)
|
||||
}
|
||||
}
|
||||
}.start(wait = false)
|
||||
} catch (th: Throwable) {
|
||||
log.error(th, "Can't start server at: ${codeFlow.redirectUri}")
|
||||
throw th
|
||||
}
|
||||
|
||||
lifetime.add {
|
||||
server.stop(100, 5000, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
try {
|
||||
val uri = URI(codeFlow.codeFlowURL())
|
||||
Desktop.getDesktop().browse(uri)
|
||||
} catch (th: Throwable) {
|
||||
val message = "Can't open '${config.server}' in system browser"
|
||||
log.warn(th, message)
|
||||
cnt.resume(OAuthTokenResponse.Error(config.server, "", "Can't open '${config.server}' in system browser."))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package circlet.components
|
||||
|
||||
import circlet.arenas.initCircletArenas
|
||||
import circlet.auth.accessTokenInteractive
|
||||
import circlet.client.api.impl.ApiClassesDeserializer
|
||||
import circlet.common.oauth.IdeaOAuthConfig
|
||||
import circlet.permission.FeatureFlagsVmPersistenceKey
|
||||
@@ -9,6 +8,7 @@ import circlet.platform.api.oauth.OAuthTokenResponse
|
||||
import circlet.platform.api.oauth.toTokenInfo
|
||||
import circlet.platform.api.serialization.ExtendableSerializationRegistry
|
||||
import circlet.platform.client.ConnectionStatus
|
||||
import circlet.platform.workspaces.CodeFlowConfig
|
||||
import circlet.platform.workspaces.WorkspaceConfiguration
|
||||
import circlet.platform.workspaces.WorkspaceManagerHost
|
||||
import circlet.runtime.ApplicationDispatcher
|
||||
@@ -23,8 +23,12 @@ import circlet.workspaces.Workspace
|
||||
import circlet.workspaces.WorkspaceManager
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ServiceManager
|
||||
import com.intellij.util.concurrency.AppExecutorUtil
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||
import libraries.coroutines.extra.Lifetime
|
||||
import libraries.coroutines.extra.launch
|
||||
import libraries.klogging.KLogger
|
||||
import libraries.klogging.assert
|
||||
import libraries.klogging.logger
|
||||
import runtime.Ui
|
||||
@@ -33,11 +37,19 @@ import runtime.persistence.InMemoryPersistence
|
||||
import runtime.persistence.PersistenceConfiguration
|
||||
import runtime.persistence.PersistenceKey
|
||||
import runtime.reactive.*
|
||||
|
||||
private val log = logger<CircletWorkspaceComponent>()
|
||||
import runtime.utils.selectFreePort
|
||||
import java.awt.Desktop
|
||||
import java.io.BufferedReader
|
||||
import java.io.InputStreamReader
|
||||
import java.io.PrintWriter
|
||||
import java.net.ServerSocket
|
||||
import java.net.Socket
|
||||
import java.net.URI
|
||||
import kotlin.coroutines.resume
|
||||
|
||||
// monitors CircletConfigurable state, creates and exposed instance of Workspace, provides various state properties and callbacks.
|
||||
class CircletWorkspaceComponent : WorkspaceManagerHost(), LifetimedDisposable by LifetimedDisposableImpl() {
|
||||
private val log: KLogger = logger<CircletWorkspaceComponent>()
|
||||
|
||||
private val ideaClientPersistenceConfiguration = PersistenceConfiguration(
|
||||
FeatureFlagsVmPersistenceKey,
|
||||
@@ -45,6 +57,7 @@ class CircletWorkspaceComponent : WorkspaceManagerHost(), LifetimedDisposable by
|
||||
)
|
||||
|
||||
private val workspacesLifetimes = SequentialLifetimes(lifetime)
|
||||
|
||||
private val manager = mutableProperty<WorkspaceManager?>(null)
|
||||
|
||||
val workspace = flatMapInit<WorkspaceManager?, Workspace?>(manager, null) {
|
||||
@@ -75,9 +88,6 @@ class CircletWorkspaceComponent : WorkspaceManagerHost(), LifetimedDisposable by
|
||||
|
||||
private fun initApp() {
|
||||
val application = ApplicationManager.getApplication()
|
||||
//if (!application.isUnitTestMode && !application.isHeadlessEnvironment) {
|
||||
// KLoggerStaticFactory.customFactory = KLoggerFactoryIdea
|
||||
//}
|
||||
|
||||
mutableUiDispatch = ApplicationDispatcher(application)
|
||||
|
||||
@@ -92,13 +102,63 @@ class CircletWorkspaceComponent : WorkspaceManagerHost(), LifetimedDisposable by
|
||||
manager.value?.signOut(false)
|
||||
}
|
||||
|
||||
private val ports = lazy {
|
||||
val regex = "[^\\d]*(?<port>\\d+)[^\\d]*".toRegex()
|
||||
val ports = IdeaOAuthConfig.redirectURIs.mapNotNull { it ->
|
||||
regex.find(it)?.groups?.get("port")?.value?.toIntOrNull()
|
||||
}
|
||||
Pair(ports.min() ?: 1000, ports.max() ?: 65535)
|
||||
}
|
||||
|
||||
suspend fun signIn(lifetime: Lifetime, server: String): OAuthTokenResponse {
|
||||
log.assert(manager.value == null, "manager.value == null")
|
||||
|
||||
val lt = workspacesLifetimes.next()
|
||||
val wsConfig = ideaConfig(server)
|
||||
val wss = WorkspaceManager(lt, null, this, InMemoryPersistence(), IdeaPasswordSafePersistence, ideaClientPersistenceConfiguration, wsConfig)
|
||||
val response = accessTokenInteractive(lifetime, wsConfig)
|
||||
val wss = WorkspaceManager(lt, null, this, InMemoryPersistence(), IdeaPasswordSafePersistence, ideaClientPersistenceConfiguration,
|
||||
wsConfig)
|
||||
|
||||
val port = selectFreePort(ports.value.first, ports.value.second)
|
||||
val authUrl = "http://localhost:$port/auth"
|
||||
val codeFlow = CodeFlowConfig(wsConfig, authUrl)
|
||||
|
||||
val response: OAuthTokenResponse = suspendCancellableCoroutine { cnt ->
|
||||
launch(lifetime, AppExecutorUtil.getAppExecutorService().asCoroutineDispatcher()) {
|
||||
ServerSocket(port).use { serverSocket ->
|
||||
val socket: Socket = serverSocket.accept()
|
||||
socket.getInputStream().use { inputStream ->
|
||||
BufferedReader(InputStreamReader(inputStream)).use { reader ->
|
||||
var line = reader.readLine()
|
||||
|
||||
line = line.substringAfter("/").substringBefore(" ")
|
||||
val token = codeFlow.handleCodeFlowRedirect("/$line")
|
||||
cnt.resume(token)
|
||||
|
||||
PrintWriter(socket.getOutputStream()).use { out ->
|
||||
val response = "<script>close()</script>"
|
||||
out.println("HTTP/1.1 200 OK")
|
||||
out.println("Content-Type: text/html")
|
||||
out.println("Content-Length: " + response.length)
|
||||
out.println()
|
||||
out.println(response)
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
val uri = URI(codeFlow.codeFlowURL())
|
||||
Desktop.getDesktop().browse(uri)
|
||||
}
|
||||
catch (th: Throwable) {
|
||||
val message = "Can't open '${wsConfig.server}' in system browser"
|
||||
log.warn(th, message)
|
||||
cnt.resume(OAuthTokenResponse.Error(wsConfig.server, "", "Can't open '${wsConfig.server}' in system browser."))
|
||||
}
|
||||
}
|
||||
|
||||
if (response is OAuthTokenResponse.Success) {
|
||||
log.info { "response = ${response.accessToken} ${response.expiresIn} ${response.refreshToken} ${response.scope}" }
|
||||
wss.signInWithToken(response.toTokenInfo())
|
||||
|
||||
Reference in New Issue
Block a user