diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a5cb2a89f39..d2a32f799b5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -28,7 +28,7 @@ mockitoKotlin = "5.4.1-SNAPSHOT" mockk = "1.13.17" nimbus-jose-jwt = "9.40" node-gradle = "7.0.2" -telemetryGenerator = "1.0.329" +telemetryGenerator = "1.0.338" testLogger = "4.0.0" testRetry = "1.5.10" # test-only; platform provides slf4j transitively at runtime 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 6b3c9280201..1e9f6015b59 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 @@ -11,6 +11,7 @@ import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.util.registry.Registry import software.amazon.awssdk.auth.token.credentials.SdkTokenProvider import software.amazon.awssdk.awscore.exception.AwsServiceException +import software.amazon.awssdk.core.exception.SdkServiceException import software.amazon.awssdk.services.ssooidc.SsoOidcClient import software.amazon.awssdk.services.ssooidc.model.AuthorizationPendingException import software.amazon.awssdk.services.ssooidc.model.CreateTokenResponse @@ -338,11 +339,34 @@ class SsoAccessTokenProvider( throw ProcessCanceledException(IllegalStateException("Login canceled by user")) } - val tokenResponse = client.createToken { - it.clientId(registration.clientId) - it.clientSecret(registration.clientSecret) - it.grantType(DEVICE_GRANT_TYPE) - it.deviceCode(authorization.deviceCode) + val startTime = clock.instant() + val tokenResponse = try { + client.createToken { + it.clientId(registration.clientId) + it.clientSecret(registration.clientSecret) + it.grantType(DEVICE_GRANT_TYPE) + it.deviceCode(authorization.deviceCode) + }.also { + val duration = Duration.between(startTime, clock.instant()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Succeeded, + grantType = DEVICE_GRANT_TYPE, + duration = duration + ) + LOG.info { "SSO token operation succeeded: grantType=$DEVICE_GRANT_TYPE, duration=${duration}ms" } + } + } catch (e: Exception) { + val duration = Duration.between(startTime, clock.instant()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Failed, + grantType = DEVICE_GRANT_TYPE, + duration = duration, + reason = e::class.simpleName, + reasonDesc = e.message?.let { scrubNames(it) }, + httpStatusCode = (e as? SdkServiceException)?.statusCode()?.toString() + ) + LOG.warn { "SSO token operation failed: grantType=$DEVICE_GRANT_TYPE, duration=${duration}ms, error=${e::class.simpleName}" } + throw e } onPendingToken.tokenRetrieved() @@ -459,11 +483,34 @@ class SsoAccessTokenProvider( stageName = RefreshCredentialStage.CREATE_TOKEN try { - val newToken = client.createToken { - it.clientId(registration.clientId) - it.clientSecret(registration.clientSecret) - it.grantType(REFRESH_GRANT_TYPE) - it.refreshToken(currentToken.refreshToken) + val startTime = clock.instant() + val newToken = try { + client.createToken { + it.clientId(registration.clientId) + it.clientSecret(registration.clientSecret) + it.grantType(REFRESH_GRANT_TYPE) + it.refreshToken(currentToken.refreshToken) + }.also { + val duration = Duration.between(startTime, clock.instant()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Succeeded, + grantType = REFRESH_GRANT_TYPE, + duration = duration + ) + LOG.info { "SSO token operation succeeded: grantType=$REFRESH_GRANT_TYPE, duration=${duration}ms" } + } + } catch (e: Exception) { + val duration = Duration.between(startTime, clock.instant()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Failed, + grantType = REFRESH_GRANT_TYPE, + duration = duration, + reason = e::class.simpleName, + reasonDesc = e.message?.let { scrubNames(it) }, + httpStatusCode = (e as? SdkServiceException)?.statusCode()?.toString() + ) + LOG.warn { "SSO token operation failed: grantType=$REFRESH_GRANT_TYPE, duration=${duration}ms, error=${e::class.simpleName}" } + throw e } stageName = RefreshCredentialStage.GET_TOKEN_DETAILS 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 17569a16414..f0ef07f7444 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 @@ -24,6 +24,7 @@ import io.netty.handler.codec.http.QueryStringDecoder import org.jetbrains.ide.BuiltInServerManager import org.jetbrains.ide.RestService import org.jetbrains.io.response +import software.amazon.awssdk.core.exception.SdkServiceException import software.amazon.awssdk.regions.Region import software.amazon.awssdk.services.ssooidc.endpoints.SsoOidcEndpointParams import software.amazon.awssdk.services.ssooidc.endpoints.internal.DefaultSsoOidcEndpointProvider @@ -32,10 +33,13 @@ import software.aws.toolkits.jetbrains.core.credentials.sso.PKCEAuthorizationGra import software.aws.toolkits.jetbrains.core.credentials.sso.PKCEClientRegistration import software.aws.toolkits.jetbrains.core.credentials.sso.bearer.buildUnmanagedSsoOidcClient import software.aws.toolkits.jetbrains.core.gettingstarted.editor.SourceOfEntry +import software.aws.toolkits.jetbrains.services.telemetry.scrubNames import software.aws.toolkits.resources.AwsCoreBundle +import software.aws.toolkits.telemetry.AuthTelemetry import software.aws.toolkits.telemetry.AuthType import software.aws.toolkits.telemetry.AwsTelemetry import software.aws.toolkits.telemetry.MetricResult +import software.aws.toolkits.telemetry.Result import java.math.BigInteger import java.time.Instant import java.util.Base64 @@ -149,15 +153,37 @@ internal class ToolkitOauthCredentialsAcquirer( private val redirectUri: String, ) : OAuthCredentialsAcquirer { override fun acquireCredentials(code: String): OAuthCredentialsAcquirer.AcquireCredentialsResult { - val token = buildUnmanagedSsoOidcClient(registration.region).use { client -> - client.createToken { - it.clientId(registration.clientId) - it.clientSecret(registration.clientSecret) - it.grantType("authorization_code") - it.redirectUri(redirectUri) - it.codeVerifier(codeVerifier) - it.code(code) + val grantType = "authorization_code" + val startTime = Instant.now() + val token = try { + buildUnmanagedSsoOidcClient(registration.region).use { client -> + client.createToken { + it.clientId(registration.clientId) + it.clientSecret(registration.clientSecret) + it.grantType(grantType) + it.redirectUri(redirectUri) + it.codeVerifier(codeVerifier) + it.code(code) + }.also { + val duration = java.time.Duration.between(startTime, Instant.now()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Succeeded, + grantType = grantType, + duration = duration + ) + } } + } catch (e: Exception) { + val duration = java.time.Duration.between(startTime, Instant.now()).toMillis().toDouble() + AuthTelemetry.ssoTokenOperation( + result = Result.Failed, + grantType = grantType, + duration = duration, + reason = e::class.simpleName, + reasonDesc = e.message?.let { scrubNames(it) }, + httpStatusCode = (e as? SdkServiceException)?.statusCode()?.toString() + ) + throw e } return OAuthCredentialsAcquirer.AcquireCredentialsResult.Success(