Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ class AmazonQToolWindowFactory : ToolWindowFactory, DumbAware {
project.messageBus.connect(toolWindow.disposable).subscribe(
BearerTokenProviderListener.TOPIC,
object : BearerTokenProviderListener {
override fun onChange(providerId: String, newScopes: List<String>?) {
override fun onProviderChange(providerId: String, newScopes: List<String>?) {
if (ToolkitConnectionManager.getInstance(project).connectionStateForFeature(QConnection.getInstance()) == BearerTokenAuthState.AUTHORIZED) {
preparePanelContent(project, qPanel)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class CodeScanChatApp(private val scope: CoroutineScope) : AmazonQApp {
ApplicationManager.getApplication().messageBus.connect(this).subscribe(
BearerTokenProviderListener.TOPIC,
object : BearerTokenProviderListener {
override fun onChange(providerId: String, newScopes: List<String>?) {
override fun onProviderChange(providerId: String, newScopes: List<String>?) {
val qProvider = getQTokenProvider(context.project)
val isQ = qProvider?.id == providerId
val isAuthorized = qProvider?.state() == BearerTokenAuthState.AUTHORIZED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ class CodeTransformChatApp : AmazonQApp {
ApplicationManager.getApplication().messageBus.connect(this).subscribe(
BearerTokenProviderListener.TOPIC,
object : BearerTokenProviderListener {
override fun onChange(providerId: String, newScopes: List<String>?) {
override fun onProviderChange(providerId: String, newScopes: List<String>?) {
val qProvider = getQTokenProvider(context.project)
val isQ = qProvider?.id == providerId
val isAuthorized = qProvider?.state() == BearerTokenAuthState.AUTHORIZED
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class CodeWhispererStatusBarWidget(project: Project) :
ApplicationManager.getApplication().messageBus.connect(this).subscribe(
BearerTokenProviderListener.TOPIC,
object : BearerTokenProviderListener {
override fun onChange(providerId: String, newScopes: List<String>?) {
override fun onProviderChange(providerId: String, newScopes: List<String>?) {
statusBar.updateWidget(ID)
}
}
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManager
import software.aws.toolkits.jetbrains.core.credentials.ToolkitConnectionManagerListener
import software.aws.toolkits.jetbrains.core.credentials.pinning.QConnection
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenAuthState
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProvider
import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.BearerTokenProviderListener
import software.aws.toolkits.jetbrains.services.amazonq.lsp.AmazonQLspService
Expand All @@ -43,15 +42,14 @@
private val project: Project,
private val encryptionManager: JwtEncryptionManager,
private val cs: CoroutineScope,
) : AuthCredentialsService,
BearerTokenProviderListener,
) : BearerTokenProviderListener,
ToolkitConnectionManagerListener,
QRegionProfileSelectedListener,
Disposable {

private val scheduler: ScheduledExecutorService = AppExecutorUtil.getAppScheduledExecutorService()
private var tokenSyncTask: ScheduledFuture<*>? = null
private val tokenSyncIntervalMinutes = 5L
private var tokenRefreshTask: ScheduledFuture<*>? = null
private val tokenRefreshInterval = 5L

Check warning on line 52 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L52

Added line #L52 was not covered by tests

init {
project.messageBus.connect(this).apply {
Expand All @@ -67,49 +65,37 @@
}
}

// Start periodic token sync
startPeriodicTokenSync()
// Start periodic token refresh
startPeriodicTokenRefresh()

Check warning on line 69 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L69

Added line #L69 was not covered by tests
}

private fun startPeriodicTokenSync() {
tokenSyncTask = scheduler.scheduleWithFixedDelay(
// TODO: we really only need a single application-wide instance of this
private fun startPeriodicTokenRefresh() {
tokenRefreshTask = scheduler.scheduleWithFixedDelay(

Check warning on line 74 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L74

Added line #L74 was not covered by tests
{
try {
if (isQConnected(project)) {
if (isQExpired(project)) {
val manager = ToolkitConnectionManager.getInstance(project)
val connection = manager.activeConnectionForFeature(QConnection.getInstance()) ?: return@scheduleWithFixedDelay

// Try to refresh the token if it's in NEEDS_REFRESH state
val tokenProvider = (connection.getConnectionSettings() as? TokenConnectionSettings)
?.tokenProvider
?.delegate
?.let { it as? BearerTokenProvider } ?: return@scheduleWithFixedDelay

if (tokenProvider.state() == BearerTokenAuthState.NEEDS_REFRESH) {
try {
tokenProvider.resolveToken()
// Now that the token is refreshed, update it in Flare
updateTokenFromActiveConnection()
} catch (e: Exception) {
LOG.warn(e) { "Failed to refresh bearer token" }
}
}
} else {
updateTokenFromActiveConnection()
}
val manager = ToolkitConnectionManager.getInstance(project)

Check warning on line 78 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L78

Added line #L78 was not covered by tests
val connection = manager.activeConnectionForFeature(QConnection.getInstance()) ?: return@scheduleWithFixedDelay

// periodically poll token to trigger a background refresh if needed
val tokenProvider = (connection.getConnectionSettings() as? TokenConnectionSettings)
?.tokenProvider
?.delegate
?.let { it as? BearerTokenProvider } ?: return@scheduleWithFixedDelay
tokenProvider.resolveToken()

Check warning on line 86 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L86

Added line #L86 was not covered by tests
Copy link
Contributor

@manodnyab manodnyab Jul 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this attempt to use the cached supplier to refresh? should the refresh called be used here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cached supplier will refresh if needed

}
} catch (e: Exception) {
LOG.warn(e) { "Failed to sync bearer token to Flare" }
LOG.warn(e) { "Failed to refresh bearer token" }

Check warning on line 89 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L89

Added line #L89 was not covered by tests
}
},
tokenSyncIntervalMinutes,
tokenSyncIntervalMinutes,
tokenRefreshInterval,
tokenRefreshInterval,

Check warning on line 93 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L92-L93

Added lines #L92 - L93 were not covered by tests
TimeUnit.MINUTES
)
}

override fun updateTokenCredentials(connection: ToolkitConnection, encrypted: Boolean): CompletableFuture<ResponseMessage> {
fun updateTokenCredentials(connection: ToolkitConnection, encrypted: Boolean): CompletableFuture<ResponseMessage> {
val payload = try {
createUpdateCredentialsPayload(connection, encrypted)
} catch (e: Exception) {
Expand All @@ -129,18 +115,26 @@
}.asCompletableFuture()
}

override fun deleteTokenCredentials() {
fun deleteTokenCredentials() {
cs.launch {
AmazonQLspService.executeAsyncIfRunning(project) { server ->
server.deleteTokenCredentials()
}
}
}

override fun onChange(providerId: String, newScopes: List<String>?) {
override fun onProviderChange(providerId: String, newScopes: List<String>?) {
updateTokenFromActiveConnection()
}

override fun onTokenModified(providerId: String) {
updateTokenFromActiveConnection()
}

Check warning on line 132 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L131-L132

Added lines #L131 - L132 were not covered by tests

override fun invalidate(providerId: String) {
deleteTokenCredentials()
}

Check warning on line 136 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L135-L136

Added lines #L135 - L136 were not covered by tests

override fun activeConnectionChanged(newConnection: ToolkitConnection?) {
val qConnection = ToolkitConnectionManager.getInstance(project)
.activeConnectionForFeature(QConnection.getInstance())
Expand All @@ -161,10 +155,6 @@
private fun updateTokenFromConnection(connection: ToolkitConnection): CompletableFuture<ResponseMessage> =
updateTokenCredentials(connection, true)

override fun invalidate(providerId: String) {
deleteTokenCredentials()
}

private fun createUpdateCredentialsPayload(connection: ToolkitConnection, encrypted: Boolean): UpdateCredentialsPayload {
val token = (connection.getConnectionSettings() as? TokenConnectionSettings)
?.tokenProvider
Expand Down Expand Up @@ -212,8 +202,8 @@
}

override fun dispose() {
tokenSyncTask?.cancel(false)
tokenSyncTask = null
tokenRefreshTask?.cancel(false)
tokenRefreshTask = null

Check warning on line 206 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/lsp/auth/DefaultAuthCredentialsService.kt#L206

Added line #L206 was not covered by tests
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,12 @@
connectionIdToProfileCount[connection.id] = it.size
} ?: error("You don't have access to the resource")
} catch (e: Exception) {
LOG.warn(e) { "Failed to list region profiles: ${e.message}" }
if (e is AccessDeniedException) {
LOG.warn { "Failed to list region profiles: ${e.message}" }

Check warning on line 118 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt#L118

Added line #L118 was not covered by tests
} else {
LOG.warn(e) { "Failed to list region profiles" }

Check warning on line 120 in plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/amazonq/shared/jetbrains-community/src/software/aws/toolkits/jetbrains/services/amazonq/profile/QRegionProfileManager.kt#L120

Added line #L120 was not covered by tests
}

throw e
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ class DefaultAuthCredentialsServiceTest {
sut = DefaultAuthCredentialsService(project, mockEncryptionManager, this)
setupMockConnectionManager("updated-token")

sut.onChange("providerId", listOf("new-scope"))
sut.onProviderChange("providerId", listOf("new-scope"))

advanceUntilIdle()
verify(exactly = 1) { mockLanguageServer.updateTokenCredentials(any()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ open class AwsClientManager : ToolkitClientManager(), Disposable {
busConnection.subscribe(
BearerTokenProviderListener.TOPIC,
object : BearerTokenProviderListener {
override fun onChange(providerId: String, newScopes: List<String>?) {
override fun onProviderChange(providerId: String, newScopes: List<String>?) {
invalidateSdks(providerId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class DefaultAwsResourceCache(
subscribe(
BearerTokenProviderListener.TOPIC,
object : BearerTokenProviderListener {
override fun onChange(providerId: String, newScopes: List<String>?) {
override fun onProviderChange(providerId: String, newScopes: List<String>?) {
clearByCredential(providerId)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class DefaultCredentialManager : CredentialManager(), Disposable {
ApplicationManager.getApplication().messageBus.connect(this).subscribe(
BearerTokenProviderListener.TOPIC,
object : BearerTokenProviderListener {
override fun onChange(providerId: String, newScopes: List<String>?) {
override fun onProviderChange(providerId: String, newScopes: List<String>?) {
modifyDependentProviders(providerId)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@
if (it && connection is Disposable) {
// don't invalidate because we kill the token we just retrieved
ApplicationManager.getApplication().messageBus.syncPublisher(BearerTokenProviderListener.TOPIC)
.onChange(connection.id)
.onProviderChange(connection.id)
Disposer.dispose(connection)
}
}
Expand Down Expand Up @@ -147,7 +147,7 @@
(existOldConn.id == newConnection.id).also { isDuplicate ->
if (isDuplicate && existOldConn is Disposable) {
ApplicationManager.getApplication().messageBus.syncPublisher(BearerTokenProviderListener.TOPIC)
.onChange(existOldConn.id, newConnection.scopes)
.onProviderChange(existOldConn.id, newConnection.scopes)
Disposer.dispose(existOldConn)
}
}
Expand All @@ -157,7 +157,7 @@
(existOldConn.id == newConnection.id).also { isDuplicate ->
if (isDuplicate && existOldConn is Disposable) {
ApplicationManager.getApplication().messageBus.syncPublisher(BearerTokenProviderListener.TOPIC)
.onChange(existOldConn.id, newConnection.scopes)
.onProviderChange(existOldConn.id, newConnection.scopes)

Check warning on line 160 in plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/DefaultToolkitAuthManager.kt

View check run for this annotation

Codecov / codecov/patch

plugins/core/jetbrains-community/src/software/aws/toolkits/jetbrains/core/credentials/DefaultToolkitAuthManager.kt#L160

Added line #L160 was not covered by tests
Disposer.dispose(existOldConn)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ class SsoAccessTokenProvider(

@Deprecated("Device authorization grant flow is deprecated")
private fun registerDAGClient(): ClientRegistration {
loadDagClientRegistration(SourceOfLoadRegistration.REGISTER_CLIENT.toString())?.let {
loadDagClientRegistration(SourceOfLoadRegistration.REGISTER_CLIENT)?.let {
return it
}

Expand Down Expand Up @@ -235,7 +235,7 @@ class SsoAccessTokenProvider(
}

private fun registerPkceClient(): PKCEClientRegistration {
loadPkceClientRegistration(SourceOfLoadRegistration.REGISTER_CLIENT.toString())?.let {
loadPkceClientRegistration(SourceOfLoadRegistration.REGISTER_CLIENT)?.let {
return it
}

Expand Down Expand Up @@ -431,8 +431,8 @@ class SsoAccessTokenProvider(
stageName = RefreshCredentialStage.LOAD_REGISTRATION
val registration = try {
when (currentToken) {
is DeviceAuthorizationGrantToken -> loadDagClientRegistration(SourceOfLoadRegistration.REFRESH_TOKEN.toString())
is PKCEAuthorizationGrantToken -> loadPkceClientRegistration(SourceOfLoadRegistration.REFRESH_TOKEN.toString())
is DeviceAuthorizationGrantToken -> loadDagClientRegistration(SourceOfLoadRegistration.REFRESH_TOKEN)
is PKCEAuthorizationGrantToken -> loadPkceClientRegistration(SourceOfLoadRegistration.REFRESH_TOKEN)
}
} catch (e: Exception) {
val message = e.message ?: "$stageName: ${e::class.java.name}"
Expand Down Expand Up @@ -519,13 +519,13 @@ class SsoAccessTokenProvider(
SAVE_TOKEN,
}

private fun loadDagClientRegistration(source: String): ClientRegistration? =
cache.loadClientRegistration(dagClientRegistrationCacheKey, source)?.let {
private fun loadDagClientRegistration(source: SourceOfLoadRegistration): ClientRegistration? =
cache.loadClientRegistration(dagClientRegistrationCacheKey, source.toString())?.let {
return it
}

private fun loadPkceClientRegistration(source: String): PKCEClientRegistration? =
cache.loadClientRegistration(pkceClientRegistrationCacheKey, source)?.let {
private fun loadPkceClientRegistration(source: SourceOfLoadRegistration): PKCEClientRegistration? =
cache.loadClientRegistration(pkceClientRegistrationCacheKey, source.toString())?.let {
return it as PKCEClientRegistration
}

Expand Down
Loading
Loading