Skip to content

Commit b57b11c

Browse files
authored
Add sign out for Idc (#3945)
* Add sign out for Idc * detekt * detekt * feedback changes * detekt * tests * detekt * added edge cases for deletion of profile * fixed tests * modified with edge cases * updated tests * provide sign out option from menu * feedback changes * new test
1 parent 6eea59a commit b57b11c

File tree

7 files changed

+470
-11
lines changed

7 files changed

+470
-11
lines changed

jetbrains-core/src/software/aws/toolkits/jetbrains/core/credentials/ConfigFilesFacade.kt

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
package software.aws.toolkits.jetbrains.core.credentials
55

6+
import com.intellij.openapi.application.ApplicationManager
7+
import com.intellij.openapi.fileEditor.FileDocumentManager
68
import software.amazon.awssdk.profiles.Profile
79
import software.amazon.awssdk.profiles.ProfileFile
810
import software.amazon.awssdk.profiles.ProfileFileLocation
@@ -15,6 +17,8 @@ import software.aws.toolkits.core.utils.touch
1517
import software.aws.toolkits.core.utils.tryDirOp
1618
import software.aws.toolkits.core.utils.tryFileOp
1719
import software.aws.toolkits.core.utils.writeText
20+
import software.aws.toolkits.jetbrains.core.credentials.profiles.ProfileWatcher
21+
import software.aws.toolkits.jetbrains.core.credentials.profiles.SsoSessionConstants
1822
import software.aws.toolkits.jetbrains.core.credentials.profiles.ssoSessions
1923
import java.nio.file.Path
2024

@@ -35,6 +39,8 @@ interface ConfigFilesFacade {
3539
fun appendProfileToCredentials(profile: Profile)
3640
fun appendSectionToConfig(sectionName: String, profile: Profile)
3741
fun updateSectionInConfig(sectionName: String, profile: Profile)
42+
43+
fun deleteSsoConnectionFromConfig(sessionName: String)
3844
}
3945

4046
class DefaultConfigFilesFacade(
@@ -173,6 +179,86 @@ class DefaultConfigFilesFacade(
173179
}
174180
}
175181

182+
override fun deleteSsoConnectionFromConfig(sessionName: String) {
183+
val filePath = configPath
184+
val lines = filePath.inputStreamIfExists()?.reader()?.readLines().orEmpty()
185+
val ssoHeaderLine = lines.indexOfFirst { it.startsWith("[${SsoSessionConstants.SSO_SESSION_SECTION_NAME} $sessionName]") }
186+
if (ssoHeaderLine == -1) return
187+
val nextHeaderLine = lines.subList(ssoHeaderLine + 1, lines.size).indexOfFirst { it.startsWith("[") }
188+
val endIndex = if (nextHeaderLine == -1) lines.size else ssoHeaderLine + nextHeaderLine + 1
189+
val updatedArray = lines.subList(0, ssoHeaderLine) + lines.subList(endIndex, lines.size)
190+
val profileHeaderLine = getCorrespondingSsoSessionProfilePosition(updatedArray, sessionName)
191+
filePath.writeText(profileHeaderLine.joinToString("\n"))
192+
193+
val applicationManager = ApplicationManager.getApplication()
194+
if (applicationManager != null && !applicationManager.isUnitTestMode) {
195+
FileDocumentManager.getInstance().saveAllDocuments()
196+
ProfileWatcher.getInstance().forceRefresh()
197+
}
198+
}
199+
200+
private fun getCorrespondingSsoSessionProfilePosition(updatedArray: List<String>, sessionName: String): List<String> {
201+
var content = updatedArray
202+
val finalContent = mutableListOf<String>()
203+
while (content.size > 0) {
204+
val sessionProfile = checkIfProfileIsPartOfSession(content, sessionName)
205+
if (sessionProfile != null) { // There is atleast one profile with the prefix matching the session name
206+
if (sessionProfile.shouldBeWrittenToConfig) {
207+
finalContent.addAll(content.subList(0, sessionProfile.endIndex))
208+
} else {
209+
finalContent.addAll(content.subList(0, sessionProfile.startIndex))
210+
}
211+
content = content.subList(sessionProfile.endIndex, content.size)
212+
} else {
213+
finalContent.addAll(content)
214+
break
215+
}
216+
}
217+
return finalContent
218+
}
219+
220+
private fun checkIfProfileIsPartOfSession(content: List<String>, sessionName: String): ProfileLimitsInConfig? {
221+
val pos = content.indexOfFirst { it.startsWith("[profile") }
222+
// if no matching profile section found
223+
if (pos == -1) return null
224+
225+
// if matching profile section found which is an sso-profile
226+
val contentAfterProfileHeader = content.subList(pos + 1, content.size)
227+
val checkIfProfileIsValid = isProfileSso(contentAfterProfileHeader, sessionName)
228+
229+
return ProfileLimitsInConfig(pos, pos + checkIfProfileIsValid.endIndex + 1, shouldBeWrittenToConfig = !checkIfProfileIsValid.isProfileSso)
230+
}
231+
232+
private fun isProfileSso(configContent: List<String>, sessionName: String): CurrentProfileLimitsInConfig {
233+
val nextSectionHeaderIndex = configContent.indexOfFirst { it.startsWith("[") }
234+
val endIndex = if (nextSectionHeaderIndex == -1) configContent.size else nextSectionHeaderIndex
235+
val currentProfile = configContent.subList(0, endIndex)
236+
currentProfile.forEach {
237+
if (it.startsWith("sso_session")) {
238+
return if (it.substringAfter("=").trim() == sessionName) {
239+
CurrentProfileLimitsInConfig(isProfileSso = true, endIndex)
240+
} else {
241+
CurrentProfileLimitsInConfig(
242+
isProfileSso = false,
243+
endIndex
244+
)
245+
}
246+
}
247+
}
248+
return CurrentProfileLimitsInConfig(isProfileSso = false, endIndex)
249+
}
250+
251+
data class ProfileLimitsInConfig(
252+
val startIndex: Int,
253+
val endIndex: Int,
254+
val shouldBeWrittenToConfig: Boolean = true
255+
)
256+
257+
data class CurrentProfileLimitsInConfig(
258+
val isProfileSso: Boolean,
259+
val endIndex: Int = 0
260+
)
261+
176262
private fun appendSection(path: Path, sectionName: String, profile: Profile) {
177263
val isConfigFile = path.fileName.toString() != "credentials"
178264
if (sectionName == "sso-session" && !isConfigFile) {

jetbrains-core/src/software/aws/toolkits/jetbrains/core/credentials/ConnectionSettingsMenuBuilder.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@ import com.intellij.openapi.actionSystem.ToggleAction
1313
import com.intellij.openapi.project.DumbAware
1414
import com.intellij.openapi.project.DumbAwareAction
1515
import com.intellij.openapi.project.Project
16+
import com.intellij.openapi.ui.MessageDialogBuilder
1617
import software.aws.toolkits.core.credentials.CredentialIdentifier
1718
import software.aws.toolkits.core.region.AwsRegion
19+
import software.aws.toolkits.jetbrains.core.credentials.sono.SONO_URL
20+
import software.aws.toolkits.jetbrains.core.gettingstarted.deleteSsoConnectionCW
1821
import software.aws.toolkits.jetbrains.core.region.AwsRegionProvider
1922
import software.aws.toolkits.jetbrains.core.utils.buildList
2023
import software.aws.toolkits.resources.message
@@ -248,6 +251,15 @@ class ConnectionSettingsMenuBuilder private constructor() {
248251
object : DumbAwareAction(message("credentials.individual_identity.signout")) {
249252
override fun actionPerformed(e: AnActionEvent) {
250253
val settings = identitySelectionSettings as? ActionsIdentitySelectionSettings
254+
if (value.startUrl != SONO_URL) {
255+
val confirmDeletion = MessageDialogBuilder.okCancel(
256+
message("gettingstarted.auth.idc.sign.out.confirmation.title"),
257+
message("gettingstarted.auth.idc.sign.out.confirmation")
258+
).yesText(message("general.confirm")).ask(settings?.project)
259+
if (confirmDeletion) {
260+
deleteSsoConnectionCW(value)
261+
}
262+
}
251263
logoutFromSsoConnection(settings?.project, value)
252264
}
253265
}

jetbrains-core/src/software/aws/toolkits/jetbrains/core/credentials/profiles/ProfileCredentialProviderFactory.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ private class ProfileCredentialsIdentifierLegacySso(
8686

8787
class ProfileCredentialsIdentifierSso internal constructor(
8888
profileName: String,
89-
private val ssoSessionName: String,
89+
val ssoSessionName: String,
9090
defaultRegionId: String?,
9191
credentialType: CredentialType?
9292
) : ProfileCredentialsIdentifier(profileName, defaultRegionId, credentialType), PostValidateInteractiveCredential, SsoSessionBackedCredentialIdentifier {

jetbrains-core/src/software/aws/toolkits/jetbrains/core/gettingstarted/GettingStartedAuthUtils.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,15 @@ import software.amazon.awssdk.profiles.Profile
1212
import software.amazon.awssdk.services.ssooidc.model.InvalidGrantException
1313
import software.amazon.awssdk.services.ssooidc.model.InvalidRequestException
1414
import software.amazon.awssdk.services.ssooidc.model.SsoOidcException
15+
import software.aws.toolkits.core.credentials.CredentialIdentifier
1516
import software.aws.toolkits.core.utils.error
1617
import software.aws.toolkits.jetbrains.core.credentials.AwsBearerTokenConnection
1718
import software.aws.toolkits.jetbrains.core.credentials.BearerSsoConnection
1819
import software.aws.toolkits.jetbrains.core.credentials.ConfigFilesFacade
1920
import software.aws.toolkits.jetbrains.core.credentials.DefaultConfigFilesFacade
2021
import software.aws.toolkits.jetbrains.core.credentials.ToolkitAuthManager
2122
import software.aws.toolkits.jetbrains.core.credentials.UserConfigSsoSessionProfile
23+
import software.aws.toolkits.jetbrains.core.credentials.profiles.ProfileCredentialsIdentifierSso
2224
import software.aws.toolkits.jetbrains.core.credentials.profiles.SsoSessionConstants
2325
import software.aws.toolkits.jetbrains.core.credentials.reauthProviderIfNeeded
2426
import software.aws.toolkits.jetbrains.core.credentials.sono.CODEWHISPERER_SCOPES
@@ -266,3 +268,19 @@ internal fun authAndUpdateConfig(
266268

267269
return connection
268270
}
271+
272+
fun deleteSsoConnectionCW(connection: AwsBearerTokenConnection) =
273+
deleteSsoConnection(getSsoSessionProfileNameFromBearer(connection))
274+
275+
fun deleteSsoConnectionExplorer(connection: CredentialIdentifier) =
276+
deleteSsoConnection(getSsoSessionProfileNameFromCredentials(connection))
277+
278+
fun deleteSsoConnection(sessionName: String) = DefaultConfigFilesFacade().deleteSsoConnectionFromConfig(sessionName)
279+
280+
fun getSsoSessionProfileNameFromBearer(connection: AwsBearerTokenConnection): String =
281+
connection.id.substringAfter("${SsoSessionConstants.SSO_SESSION_SECTION_NAME}:")
282+
283+
fun getSsoSessionProfileNameFromCredentials(connection: CredentialIdentifier): String {
284+
connection as ProfileCredentialsIdentifierSso
285+
return connection.ssoSessionName
286+
}

0 commit comments

Comments
 (0)