From eb60d8172b3d4097dfa5d55faee9a23c5be66e65 Mon Sep 17 00:00:00 2001 From: Vladimir Krivosheev Date: Tue, 2 Oct 2018 16:46:13 +0200 Subject: [PATCH] extract *KeePassDatabaseAction, move SynchronizedClearableLazy to platform --- .../src/PasswordSafeConfigurable.kt | 90 ++++++++++--------- .../credential-store/src/PasswordSafeImpl.kt | 13 ++- .../com/intellij/ui/layout/LayoutBuilder.kt | 8 +- .../concurrency}/SynchronizedClearableLazy.kt | 10 ++- .../src/ConfigurationFileManager.kt | 1 + .../runConfigurationTemplateProvider.kt | 2 +- .../src/providers/updateSettingsProvider.kt | 2 +- 7 files changed, 72 insertions(+), 54 deletions(-) rename {plugins/configuration-script/src => platform/platform-impl/src/com/intellij/util/concurrency}/SynchronizedClearableLazy.kt (71%) diff --git a/platform/credential-store/src/PasswordSafeConfigurable.kt b/platform/credential-store/src/PasswordSafeConfigurable.kt index 07e63f0f6eb6..f0f94797d37f 100644 --- a/platform/credential-store/src/PasswordSafeConfigurable.kt +++ b/platform/credential-store/src/PasswordSafeConfigurable.kt @@ -168,49 +168,14 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi } keePassDbFile = textFieldWithBrowseButton("KeePass Database File", fileChooserDescriptor = fileChooserDescriptor, - fileChosen = ::normalizeSelectedFile, + fileChosen = { + normalizeSelectedFile(it) + }, comment = if (SystemInfo.isWindows) null else "Stored using weak encryption. It is recommended to store on encrypted volume for additional security.") gearButton( - object : DumbAwareAction("Clear") { - override fun actionPerformed(event: AnActionEvent) { - if (!MessageDialogBuilder.yesNo("Clear Passwords", "Are you sure want to remove all passwords?").yesText("Remove Passwords").isYes) { - return - } - - LOG.info("Passwords cleared", Error()) - createKeePassFileManager()?.clear() - } - - override fun update(e: AnActionEvent) { - e.presentation.isEnabled = getNewDbFile()?.exists() ?: false - } - }, - object : DumbAwareAction("Import") { - override fun actionPerformed(event: AnActionEvent) { - chooseFile(fileChooserDescriptor, event) { - createKeePassFileManager()?.import(Paths.get(normalizeSelectedFile(it)), event) - // force reload KeePass Store - passwordSafe.closeCurrentProvider() - } - } - }, - object : DumbAwareAction("${if (MasterKeyFileStorage(getDefaultMasterPasswordFile()).isAutoGenerated()) "Set" else "Change"} Master Password") { - override fun actionPerformed(event: AnActionEvent) { - // even if current provider is not KEEPASS, all actions for db file must be applied immediately (show error if new master password not applicable for existing db file) - if (createKeePassFileManager()?.askAndSetMasterKey(event) == true) { - if (passwordSafe.settings.providerType == ProviderType.KEEPASS) { - // force reload KeePass Store - passwordSafe.closeCurrentProvider() - } - - templatePresentation.text = "Change Master Password" - } - } - - override fun update(e: AnActionEvent) { - e.presentation.isEnabled = getNewDbFileAsString() != null - } - } + ClearKeePassDatabaseAction(), + ImportKeePassDatabaseAction(passwordSafe), + ChangeKeePassDatabaseMasterPasswordAction() ) } } @@ -237,6 +202,49 @@ internal class PasswordSafeConfigurableUi : ConfigurableUi else -> ProviderType.KEYCHAIN } } + + private inner class ClearKeePassDatabaseAction : DumbAwareAction("Clear") { + override fun actionPerformed(event: AnActionEvent) { + if (!MessageDialogBuilder.yesNo("Clear Passwords", "Are you sure want to remove all passwords?").yesText("Remove Passwords").isYes) { + return + } + + LOG.info("Passwords cleared", Error()) + createKeePassFileManager()?.clear() ?: return + (PasswordSafe.instance as PasswordSafeImpl).closeCurrentStoreIfKeePass() + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = getNewDbFile()?.exists() ?: false + } + } + + private inner class ImportKeePassDatabaseAction(private val passwordSafe: PasswordSafeImpl) : DumbAwareAction("Import") { + override fun actionPerformed(event: AnActionEvent) { + FileChooserDescriptorFactory.createSingleLocalFileDescriptor() + .withFileFilter { + it.nameSequence.endsWith(".kdbx") + } + .chooseFile(event) { + createKeePassFileManager()?.import(Paths.get(normalizeSelectedFile(it)), event) + passwordSafe.closeCurrentStoreIfKeePass() + } + } + } + + private inner class ChangeKeePassDatabaseMasterPasswordAction : DumbAwareAction("${if (MasterKeyFileStorage(getDefaultMasterPasswordFile()).isAutoGenerated()) "Set" else "Change"} Master Password") { + override fun actionPerformed(event: AnActionEvent) { + // even if current provider is not KEEPASS, all actions for db file must be applied immediately (show error if new master password not applicable for existing db file) + if (createKeePassFileManager()?.askAndSetMasterKey(event) == true) { + (PasswordSafe.instance as PasswordSafeImpl).closeCurrentStoreIfKeePass() + templatePresentation.text = "Change Master Password" + } + } + + override fun update(e: AnActionEvent) { + e.presentation.isEnabled = getNewDbFileAsString() != null + } + } } private fun normalizeSelectedFile(file: VirtualFile): String { diff --git a/platform/credential-store/src/PasswordSafeImpl.kt b/platform/credential-store/src/PasswordSafeImpl.kt index 1c1e8137b252..5dc1c36e25d2 100644 --- a/platform/credential-store/src/PasswordSafeImpl.kt +++ b/platform/credential-store/src/PasswordSafeImpl.kt @@ -17,6 +17,7 @@ import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.util.ShutDownTracker import com.intellij.util.Alarm import com.intellij.util.SingleAlarm +import com.intellij.util.concurrency.SynchronizedClearableLazy import org.jetbrains.annotations.TestOnly import org.jetbrains.concurrency.Promise import org.jetbrains.concurrency.runAsync @@ -62,14 +63,14 @@ private fun computeProvider(settings: PasswordSafeSettings): CredentialStore { } class PasswordSafeImpl @JvmOverloads constructor(val settings: PasswordSafeSettings /* public - backward compatibility */, - provider: CredentialStore? = null) : PasswordSafe(), SettingsSavingComponent { + provider: CredentialStore? = null /* TestOnly */) : PasswordSafe(), SettingsSavingComponent { override var isRememberPasswordByDefault: Boolean get() = settings.state.isRememberPasswordByDefault set(value) { settings.state.isRememberPasswordByDefault = value } - private var _currentProvider: Lazy = if (provider == null) lazy { computeProvider(settings) } else lazyOf(provider) + private var _currentProvider: Lazy = if (provider == null) SynchronizedClearableLazy { computeProvider(settings) } else lazyOf(provider) internal val currentProviderIfComputed: CredentialStore? get() = if (_currentProvider.isInitialized()) _currentProvider.value else null @@ -80,8 +81,12 @@ class PasswordSafeImpl @JvmOverloads constructor(val settings: PasswordSafeSetti _currentProvider = lazyOf(value) } - internal fun closeCurrentProvider() { - _currentProvider = lazy { computeProvider(settings) } + // force reload KeePass Store if settings changed + internal fun closeCurrentStoreIfKeePass() { + val store = currentProviderIfComputed + if (store is KeePassCredentialStore && !store.isMemoryOnly) { + (_currentProvider as SynchronizedClearableLazy).drop() + } } // it is helper storage to support set password as memory-only (see setPassword memoryOnly flag) diff --git a/platform/platform-impl/src/com/intellij/ui/layout/LayoutBuilder.kt b/platform/platform-impl/src/com/intellij/ui/layout/LayoutBuilder.kt index 6ac04abd866b..9fe2b7002539 100644 --- a/platform/platform-impl/src/com/intellij/ui/layout/LayoutBuilder.kt +++ b/platform/platform-impl/src/com/intellij/ui/layout/LayoutBuilder.kt @@ -48,13 +48,13 @@ class LayoutBuilder @PublishedApi internal constructor(@PublishedApi internal va return group } - fun chooseFile(descriptor: FileChooserDescriptor, event: AnActionEvent, fileChosen: (chosenFile: VirtualFile) -> Unit) { - FileChooser.chooseFile(descriptor, event.getData(PlatformDataKeys.PROJECT), event.getData(PlatformDataKeys.CONTEXT_COMPONENT), null, fileChosen) - } - @Suppress("PropertyName") @PublishedApi @Deprecated("", replaceWith = ReplaceWith("builder"), level = DeprecationLevel.ERROR) internal val `$`: LayoutBuilderImpl get() = builder +} + +fun FileChooserDescriptor.chooseFile(event: AnActionEvent, fileChosen: (chosenFile: VirtualFile) -> Unit) { + FileChooser.chooseFile(this, event.getData(PlatformDataKeys.PROJECT), event.getData(PlatformDataKeys.CONTEXT_COMPONENT), null, fileChosen) } \ No newline at end of file diff --git a/plugins/configuration-script/src/SynchronizedClearableLazy.kt b/platform/platform-impl/src/com/intellij/util/concurrency/SynchronizedClearableLazy.kt similarity index 71% rename from plugins/configuration-script/src/SynchronizedClearableLazy.kt rename to platform/platform-impl/src/com/intellij/util/concurrency/SynchronizedClearableLazy.kt index 04402388d8b4..5cf469830938 100644 --- a/plugins/configuration-script/src/SynchronizedClearableLazy.kt +++ b/platform/platform-impl/src/com/intellij/util/concurrency/SynchronizedClearableLazy.kt @@ -1,11 +1,15 @@ -@file:Suppress("ClassName") +// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. -package com.intellij.configurationScript +package com.intellij.util.concurrency +@Suppress("ClassName") private object UNINITIALIZED_VALUE +/** + * Kotlin-friendly version of ClearableLazyValue + */ @Suppress("LocalVariableName") -internal class SynchronizedClearableLazy(private val initializer: () -> T) : Lazy { +class SynchronizedClearableLazy(private val initializer: () -> T) : Lazy { @Volatile private var _value: Any? = UNINITIALIZED_VALUE diff --git a/plugins/configuration-script/src/ConfigurationFileManager.kt b/plugins/configuration-script/src/ConfigurationFileManager.kt index 75014f16ddeb..8381e9b30eb9 100644 --- a/plugins/configuration-script/src/ConfigurationFileManager.kt +++ b/plugins/configuration-script/src/ConfigurationFileManager.kt @@ -9,6 +9,7 @@ import com.intellij.openapi.vfs.newvfs.BulkFileListener import com.intellij.openapi.vfs.newvfs.events.VFileCopyEvent import com.intellij.openapi.vfs.newvfs.events.VFileCreateEvent import com.intellij.openapi.vfs.newvfs.events.VFileEvent +import com.intellij.util.concurrency.SynchronizedClearableLazy import com.intellij.util.containers.ContainerUtil import com.intellij.util.io.exists import com.intellij.util.io.inputStreamIfExists diff --git a/plugins/configuration-script/src/providers/runConfigurationTemplateProvider.kt b/plugins/configuration-script/src/providers/runConfigurationTemplateProvider.kt index b8f2f39791c4..84203e0ed71b 100644 --- a/plugins/configuration-script/src/providers/runConfigurationTemplateProvider.kt +++ b/plugins/configuration-script/src/providers/runConfigurationTemplateProvider.kt @@ -3,7 +3,6 @@ package com.intellij.configurationScript.providers import com.intellij.configurationScript.ConfigurationFileManager import com.intellij.configurationScript.Keys import com.intellij.configurationScript.RunConfigurationListReader -import com.intellij.configurationScript.SynchronizedClearableLazy import com.intellij.execution.configurations.ConfigurationFactory import com.intellij.execution.configurations.RunConfigurationBase import com.intellij.execution.impl.RunConfigurationTemplateProvider @@ -13,6 +12,7 @@ import com.intellij.openapi.components.BaseState import com.intellij.openapi.components.PersistentStateComponent import com.intellij.openapi.components.service import com.intellij.openapi.project.Project +import com.intellij.util.concurrency.SynchronizedClearableLazy import gnu.trove.THashMap import org.yaml.snakeyaml.nodes.MappingNode import org.yaml.snakeyaml.nodes.ScalarNode diff --git a/plugins/configuration-script/src/providers/updateSettingsProvider.kt b/plugins/configuration-script/src/providers/updateSettingsProvider.kt index 562f593c989f..13ef479ec97b 100644 --- a/plugins/configuration-script/src/providers/updateSettingsProvider.kt +++ b/plugins/configuration-script/src/providers/updateSettingsProvider.kt @@ -2,12 +2,12 @@ package com.intellij.configurationScript.providers import com.intellij.configurationScript.ConfigurationFileManager import com.intellij.configurationScript.Keys -import com.intellij.configurationScript.SynchronizedClearableLazy import com.intellij.configurationScript.readObject import com.intellij.openapi.components.BaseState import com.intellij.openapi.components.service import com.intellij.openapi.project.Project import com.intellij.openapi.updateSettings.impl.UpdateSettingsProvider +import com.intellij.util.concurrency.SynchronizedClearableLazy import org.yaml.snakeyaml.nodes.MappingNode import org.yaml.snakeyaml.nodes.ScalarNode