From e8af292a54d181f0560bf62302ca4ed399383559 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Wed, 24 Jul 2024 10:24:44 -0700 Subject: [PATCH 1/4] try fix 404 with hacky approach to manually close netty channel --- .../toolwindow/AmazonQToolWindowFactory.kt | 1 - .../credentials/sso/SsoAccessTokenProvider.kt | 1 + .../sso/pkce/ToolkitOAuthService.kt | 30 +++++++++++++++---- 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt index 7a8137c1948..836f22dc195 100644 --- a/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt +++ b/plugins/amazonq/chat/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/toolwindow/AmazonQToolWindowFactory.kt @@ -69,7 +69,6 @@ class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware { object : BearerTokenProviderListener { override fun onChange(providerId: String, newScopes: List?) { if (ToolkitConnectionManager.getInstance(project).connectionStateForFeature(QConnection.getInstance()) == BearerTokenAuthState.AUTHORIZED) { - contentManager.removeAllContents(true) val content = contentManager.factory.createContent(AmazonQToolWindow.getInstance(project).component, null, false).also { it.isCloseable = true it.isPinnable = true diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt index ca2b66216a3..07379304e9b 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt @@ -330,6 +330,7 @@ class SsoAccessTokenProvider( sleepWithCancellation(Duration.ofMillis(100), progressIndicator) } catch (e: ProcessCanceledException) { future.cancel(true) +// future.completeExceptionally(e) _authorization.set(null) throw ProcessCanceledException(IllegalStateException(message("credentials.pending.user_cancel.message"))) } diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt index 5223abb4e88..280220fbc29 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt @@ -17,6 +17,7 @@ import com.intellij.util.Url import com.intellij.util.Urls.newFromEncoded import com.intellij.util.io.DigestUtil import io.netty.buffer.Unpooled +import io.netty.channel.Channel import io.netty.channel.ChannelHandlerContext import io.netty.handler.codec.http.FullHttpRequest import io.netty.handler.codec.http.QueryStringDecoder @@ -38,6 +39,22 @@ import java.util.concurrent.CompletableFuture const val PKCE_CLIENT_NAME = "AWS IDE Plugins for JetBrains" +// TODO: stupid short term way to mitigate 404 not found (tcp connection not closed after success/failure/cancellation) +private var channel: Channel? = null + +private fun closeIfChannelExist() { + try { + channel?.let { + it.close().apply { + sync() + println("closing channel ${it}") + } + } + } catch (e: Exception) { + println("error while attempting to close netty channel, ${e.message}") + } +} + @Service class ToolkitOAuthService : OAuthServiceBase() { override val name: String = "aws/toolkit" @@ -45,6 +62,8 @@ class ToolkitOAuthService : OAuthServiceBase() { fun hasPendingRequest() = currentRequest.get() != null fun authorize(registration: PKCEClientRegistration): CompletableFuture { + closeIfChannelExist() + currentRequest.set(null) val currentRequest = currentRequest.get() val toolkitRequest = currentRequest?.request as? ToolkitOAuthRequest @@ -183,14 +202,15 @@ internal class ToolkitOAuthCallbackHandler : OAuthCallbackHandlerBase() { } override fun isSupported(request: FullHttpRequest): Boolean { - // only handle if we're actively waiting on a redirect - if (!oauthService().hasPendingRequest()) { - return false - } - // only handle the /oauth/callback endpoint return request.uri().trim('/').startsWith("oauth/callback") } + + override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? { + channel = context.channel() + println("set channel = ${context.channel()}") + return super.execute(urlDecoder, request, context) + } } internal class ToolkitOAuthCallbackResultService : RestService() { From d35ec922cc5bdb953604263abfe73635800e583c Mon Sep 17 00:00:00 2001 From: Will Lo Date: Fri, 26 Jul 2024 12:16:27 -0700 Subject: [PATCH 2/4] revert --- .../jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt index 07379304e9b..ca2b66216a3 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/SsoAccessTokenProvider.kt @@ -330,7 +330,6 @@ class SsoAccessTokenProvider( sleepWithCancellation(Duration.ofMillis(100), progressIndicator) } catch (e: ProcessCanceledException) { future.cancel(true) -// future.completeExceptionally(e) _authorization.set(null) throw ProcessCanceledException(IllegalStateException(message("credentials.pending.user_cancel.message"))) } From 29e0574bef133fbfaa1d1b11661d0ffee7925c20 Mon Sep 17 00:00:00 2001 From: Will Lo Date: Fri, 26 Jul 2024 18:36:31 -0700 Subject: [PATCH 3/4] fix 404 --- .../sso/pkce/ToolkitOAuthService.kt | 33 ++++++------------- 1 file changed, 10 insertions(+), 23 deletions(-) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt index 280220fbc29..121bbf43024 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt @@ -16,6 +16,7 @@ import com.intellij.openapi.wm.IdeFocusManager import com.intellij.util.Url import com.intellij.util.Urls.newFromEncoded import com.intellij.util.io.DigestUtil +import com.jetbrains.rd.util.AtomicReference import io.netty.buffer.Unpooled import io.netty.channel.Channel import io.netty.channel.ChannelHandlerContext @@ -39,21 +40,8 @@ import java.util.concurrent.CompletableFuture const val PKCE_CLIENT_NAME = "AWS IDE Plugins for JetBrains" -// TODO: stupid short term way to mitigate 404 not found (tcp connection not closed after success/failure/cancellation) -private var channel: Channel? = null - -private fun closeIfChannelExist() { - try { - channel?.let { - it.close().apply { - sync() - println("closing channel ${it}") - } - } - } catch (e: Exception) { - println("error while attempting to close netty channel, ${e.message}") - } -} +// TODO: naive short term way to mitigate 404 not found (tcp connection is kept alive after success/failure/cancellation) +private var channel: AtomicReference = AtomicReference(null) @Service class ToolkitOAuthService : OAuthServiceBase() { @@ -62,8 +50,12 @@ class ToolkitOAuthService : OAuthServiceBase() { fun hasPendingRequest() = currentRequest.get() != null fun authorize(registration: PKCEClientRegistration): CompletableFuture { - closeIfChannelExist() - currentRequest.set(null) + channel.get()?.let { + if (it.isOpen) { + it.close().sync() + } + } + val currentRequest = currentRequest.get() val toolkitRequest = currentRequest?.request as? ToolkitOAuthRequest @@ -205,16 +197,11 @@ internal class ToolkitOAuthCallbackHandler : OAuthCallbackHandlerBase() { // only handle the /oauth/callback endpoint return request.uri().trim('/').startsWith("oauth/callback") } - - override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? { - channel = context.channel() - println("set channel = ${context.channel()}") - return super.execute(urlDecoder, request, context) - } } internal class ToolkitOAuthCallbackResultService : RestService() { override fun execute(urlDecoder: QueryStringDecoder, request: FullHttpRequest, context: ChannelHandlerContext): String? { + channel.getAndSet(context.channel()) val path = urlDecoder.path().substringAfter(getServiceName()).trim('/') val type = when { path.endsWith(".css") -> "text/css" From 679cafbbd43672f097daf7d776d3139c668eed8a Mon Sep 17 00:00:00 2001 From: Will Lo Date: Mon, 29 Jul 2024 03:08:21 -0700 Subject: [PATCH 4/4] add comment --- .../core/credentials/sso/pkce/ToolkitOAuthService.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt index 121bbf43024..d9933e20d34 100644 --- a/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt +++ b/plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/sso/pkce/ToolkitOAuthService.kt @@ -194,6 +194,16 @@ internal class ToolkitOAuthCallbackHandler : OAuthCallbackHandlerBase() { } override fun isSupported(request: FullHttpRequest): Boolean { + /** + * comment this out because if we return false early here the TCP connection will be still kept alive by the IDE. In this state, users + * following login attempts will keep seeing 404 not found page, which could be only resolved by (1) re-start the IDE (2) wait until the TCP connection + * between browsers and IDE is closed + */ + // only handle if we're actively waiting on a redirect +// if (!oauthService().hasPendingRequest()) { +// return false +// } + // only handle the /oauth/callback endpoint return request.uri().trim('/').startsWith("oauth/callback") }