mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 04:51:24 +07:00
IJPL-199758 Asynchronous credential store API
IJ-CR-177617 IJ-MR-178816 (cherry picked from commit e000745417425417326e229462f66d208a39db9a) GitOrigin-RevId: 9659a0132ca707c9b955f46abf51b9dbeb044323
This commit is contained in:
committed by
intellij-monorepo-bot
parent
7c5ca137c9
commit
cc613702a4
@@ -9,24 +9,19 @@ import com.intellij.credentialStore.keePass.getDefaultDbFile
|
||||
import com.intellij.ide.passwordSafe.PasswordSafe
|
||||
import com.intellij.notification.NotificationAction
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.ComponentManagerEx
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.serviceContainer.NonInjectable
|
||||
import com.intellij.util.Ephemeral
|
||||
import com.intellij.util.SlowOperations
|
||||
import com.intellij.util.concurrency.SynchronizedClearableLazy
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.future.asCompletableFuture
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.ApiStatus.Internal
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import org.jetbrains.concurrency.Promise
|
||||
import org.jetbrains.concurrency.asPromise
|
||||
import java.io.Closeable
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
@@ -35,7 +30,7 @@ private val LOG: Logger
|
||||
get() = logger<CredentialStore>()
|
||||
|
||||
@Internal
|
||||
abstract class BasePasswordSafe(private val coroutineScope: CoroutineScope) : PasswordSafe() {
|
||||
abstract class BasePasswordSafe : PasswordSafe() {
|
||||
protected abstract val settings: PasswordSafeSettings
|
||||
|
||||
override var isRememberPasswordByDefault: Boolean
|
||||
@@ -128,12 +123,8 @@ abstract class BasePasswordSafe(private val coroutineScope: CoroutineScope) : Pa
|
||||
}
|
||||
}
|
||||
|
||||
// maybe in the future we will use native async; this method added here instead "if needed, just use runAsync in your code"
|
||||
override fun getAsync(attributes: CredentialAttributes): Promise<Credentials?> {
|
||||
return coroutineScope.async(Dispatchers.IO) {
|
||||
get(attributes)
|
||||
}.asCompletableFuture().asPromise()
|
||||
}
|
||||
override suspend fun getAsync(attributes: CredentialAttributes): Ephemeral<Credentials> =
|
||||
currentProvider.getAsync(attributes)
|
||||
|
||||
suspend fun save() {
|
||||
val keePassCredentialStore = currentProviderIfComputed as? KeePassCredentialStore ?: return
|
||||
@@ -161,7 +152,7 @@ abstract class BasePasswordSafe(private val coroutineScope: CoroutineScope) : Pa
|
||||
@Internal
|
||||
class TestPasswordSafeImpl @NonInjectable constructor(
|
||||
override val settings: PasswordSafeSettings
|
||||
) : BasePasswordSafe(coroutineScope = (ApplicationManager.getApplication() as ComponentManagerEx).getCoroutineScope()) {
|
||||
) : BasePasswordSafe() {
|
||||
@TestOnly
|
||||
constructor() : this(service<PasswordSafeSettings>())
|
||||
|
||||
@@ -173,7 +164,7 @@ class TestPasswordSafeImpl @NonInjectable constructor(
|
||||
}
|
||||
|
||||
@Internal
|
||||
class PasswordSafeImpl(coroutineScope: CoroutineScope) : BasePasswordSafe(coroutineScope), SettingsSavingComponent {
|
||||
class PasswordSafeImpl : BasePasswordSafe(), SettingsSavingComponent {
|
||||
override val settings: PasswordSafeSettings
|
||||
get() = service<PasswordSafeSettings>()
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ jvm_library(
|
||||
"//platform/util",
|
||||
"@lib//:kotlin-stdlib",
|
||||
"//platform/core-api:core",
|
||||
"//platform/util/concurrency",
|
||||
]
|
||||
)
|
||||
### auto-generated section `build intellij.platform.credentialStore` end
|
||||
7
platform/credential-store/api-dump-experimental.txt
Normal file
7
platform/credential-store/api-dump-experimental.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
com.intellij.credentialStore.CredentialStore
|
||||
- *:ephemeral(java.lang.Object,kotlin.coroutines.Continuation):java.lang.Object
|
||||
- *:getAsync(com.intellij.credentialStore.CredentialAttributes,kotlin.coroutines.Continuation):java.lang.Object
|
||||
*:com.intellij.util.Ephemeral
|
||||
- a:asFlow():kotlinx.coroutines.flow.Flow
|
||||
- a:derive(kotlin.jvm.functions.Function1):com.intellij.util.Ephemeral
|
||||
- a:unwrap(kotlin.coroutines.Continuation):java.lang.Object
|
||||
@@ -76,7 +76,6 @@ a:com.intellij.ide.passwordSafe.PasswordSafe
|
||||
- com.intellij.ide.passwordSafe.PasswordStorage
|
||||
- sf:Companion:com.intellij.ide.passwordSafe.PasswordSafe$Companion
|
||||
- <init>():V
|
||||
- a:getAsync(com.intellij.credentialStore.CredentialAttributes):org.jetbrains.concurrency.Promise
|
||||
- sf:getInstance():com.intellij.ide.passwordSafe.PasswordSafe
|
||||
- a:isMemoryOnly():Z
|
||||
- a:isPasswordStoredOnlyInMemory(com.intellij.credentialStore.CredentialAttributes,com.intellij.credentialStore.Credentials):Z
|
||||
|
||||
@@ -12,5 +12,6 @@
|
||||
<orderEntry type="module" module-name="intellij.platform.util" />
|
||||
<orderEntry type="library" name="kotlin-stdlib" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.core" />
|
||||
<orderEntry type="module" module-name="intellij.platform.concurrency" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,23 +1,34 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.credentialStore;
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.credentialStore
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import com.intellij.util.Ephemeral
|
||||
import com.intellij.util.StaticEphemeral
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.concurrency.await
|
||||
import org.jetbrains.concurrency.runAsync
|
||||
|
||||
/**
|
||||
* Please see <a href="https://plugins.jetbrains.com/docs/intellij/persisting-sensitive-data.html">Storing Sensitive Data</a>.
|
||||
* Please see [Storing Sensitive Data](https://plugins.jetbrains.com/docs/intellij/persisting-sensitive-data.html).
|
||||
*/
|
||||
public interface CredentialStore {
|
||||
@Nullable Credentials get(@NotNull CredentialAttributes attributes);
|
||||
interface CredentialStore {
|
||||
operator fun get(attributes: CredentialAttributes): Credentials?
|
||||
|
||||
default @Nullable String getPassword(@NotNull CredentialAttributes attributes) {
|
||||
var credentials = get(attributes);
|
||||
return credentials == null ? null : credentials.getPasswordAsString();
|
||||
fun getPassword(attributes: CredentialAttributes): String? {
|
||||
val credentials = get(attributes)
|
||||
return credentials?.getPasswordAsString()
|
||||
}
|
||||
|
||||
void set(@NotNull CredentialAttributes attributes, @Nullable Credentials credentials);
|
||||
@ApiStatus.Experimental
|
||||
suspend fun getAsync(attributes: CredentialAttributes): Ephemeral<Credentials> =
|
||||
ephemeral(runAsync { get(attributes) }.await() )
|
||||
|
||||
default void setPassword(@NotNull CredentialAttributes attributes, @Nullable String password) {
|
||||
set(attributes, password == null ? null : new Credentials(attributes.getUserName(), password));
|
||||
@ApiStatus.Experimental
|
||||
suspend fun <T : Any> ephemeral(value: T?): Ephemeral<T> =
|
||||
StaticEphemeral(value)
|
||||
|
||||
operator fun set(attributes: CredentialAttributes, credentials: Credentials?)
|
||||
|
||||
fun setPassword(attributes: CredentialAttributes, password: String?) {
|
||||
set(attributes, password?.let { Credentials(attributes.userName, it) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import com.intellij.credentialStore.CredentialAttributes
|
||||
import com.intellij.credentialStore.CredentialStore
|
||||
import com.intellij.credentialStore.Credentials
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import org.jetbrains.concurrency.Promise
|
||||
|
||||
/**
|
||||
* [See the documentation](https://plugins.jetbrains.com/docs/intellij/persisting-sensitive-data.html).
|
||||
@@ -27,7 +26,5 @@ abstract class PasswordSafe : CredentialStore, PasswordStorage {
|
||||
|
||||
abstract operator fun set(attributes: CredentialAttributes, credentials: Credentials?, memoryOnly: Boolean)
|
||||
|
||||
abstract fun getAsync(attributes: CredentialAttributes): Promise<Credentials?>
|
||||
|
||||
abstract fun isPasswordStoredOnlyInMemory(attributes: CredentialAttributes, credentials: Credentials): Boolean
|
||||
}
|
||||
|
||||
49
platform/credential-store/src/util/Ephemeral.kt
Normal file
49
platform/credential-store/src/util/Ephemeral.kt
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.util
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* Represents a wrapper for an ephemeral value with a limited lifetime.
|
||||
*/
|
||||
@ApiStatus.Experimental
|
||||
interface Ephemeral<out T : Any> {
|
||||
/**
|
||||
* Suspends until the ephemeral value held by this instance is available and returns it, or `null` if the value has expired.
|
||||
*/
|
||||
suspend fun unwrap(): T?
|
||||
|
||||
/**
|
||||
* Returns a long-living `Flow` that tracks the ephemeral value lifecycle.
|
||||
*
|
||||
* Flow behavior:
|
||||
* - Starts with `null`
|
||||
* - Emits the value when available
|
||||
* - Emits `null` when expired
|
||||
* - Can re-emit the value if it becomes available again
|
||||
*
|
||||
* The flow can outlive the original `Ephemeral` instance. Exceptions are logged and treated as `null`.
|
||||
*
|
||||
* @return A `Flow<T?>` where `null` indicates unavailable value
|
||||
*/
|
||||
fun asFlow(): Flow<T?>
|
||||
|
||||
/**
|
||||
* Creates a new `Ephemeral` instance by transforming the value held by the current instance
|
||||
* using the provided mapping function. The resulting value inherits the original lifetime.
|
||||
*/
|
||||
fun <P : Any> derive(map: (T) -> P?): Ephemeral<P>
|
||||
}
|
||||
|
||||
internal class StaticEphemeral<T : Any>(private val data: T?) : Ephemeral<T> {
|
||||
override suspend fun unwrap(): T? =
|
||||
data
|
||||
|
||||
override fun asFlow(): Flow<T?> =
|
||||
flowOf(data)
|
||||
|
||||
override fun <P : Any> derive(map: (T) -> P?): Ephemeral<P> =
|
||||
StaticEphemeral(data?.let(map))
|
||||
}
|
||||
Reference in New Issue
Block a user