diff --git a/platform/credential-store/src/CredentialStoreWrapper.kt b/platform/credential-store/src/CredentialStoreWrapper.kt index 41e8d66b6464..52b8a8f51818 100644 --- a/platform/credential-store/src/CredentialStoreWrapper.kt +++ b/platform/credential-store/src/CredentialStoreWrapper.kt @@ -1,6 +1,7 @@ // 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.credentialStore +import com.google.common.cache.CacheBuilder import com.intellij.ide.passwordSafe.PasswordStorage import com.intellij.notification.NotificationGroup import com.intellij.notification.NotificationType @@ -11,6 +12,7 @@ import com.intellij.openapi.diagnostic.runAndLogException import com.intellij.openapi.util.SystemInfo import com.intellij.util.SystemProperties import com.intellij.util.concurrency.QueueProcessor +import java.util.concurrent.TimeUnit private val nullCredentials = Credentials("\u0000", OneTimeString("\u0000")) @@ -22,20 +24,30 @@ internal val NOTIFICATION_MANAGER by lazy { private class CredentialStoreWrapper(private val store: CredentialStore) : PasswordStorage { private val fallbackStore = lazy { KeePassCredentialStore(memoryOnly = true) } - private val queueProcessor = QueueProcessor<() -> Unit>({ it() }) + private val queueProcessor = QueueProcessor<() -> Unit> { it() } private val postponedCredentials = KeePassCredentialStore(memoryOnly = true) + private val deniedItems = CacheBuilder.newBuilder().expireAfterAccess(1, TimeUnit.MINUTES).build() override fun get(attributes: CredentialAttributes): Credentials? { postponedCredentials.get(attributes)?.let { return if (it == nullCredentials) null else it } + if (deniedItems.getIfPresent(attributes) != null) { + LOG.warn("User denied access to $attributes") + return null + } + var store = if (fallbackStore.isInitialized()) fallbackStore.value else store val requestor = attributes.requestor val userName = attributes.userName try { val value = store.get(attributes) + if (value === ACCESS_TO_KEY_CHAIN_DENIED) { + deniedItems.put(attributes, true) + return null + } if (value != null || requestor == null || userName == null) { return value } diff --git a/platform/credential-store/src/credentialStore.kt b/platform/credential-store/src/credentialStore.kt index 4eee639f392a..2d2b19d8bf12 100644 --- a/platform/credential-store/src/credentialStore.kt +++ b/platform/credential-store/src/credentialStore.kt @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// 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.credentialStore import com.intellij.openapi.diagnostic.Logger @@ -100,9 +86,10 @@ private fun parseString(data: String, delimiter: Char): List { @JvmOverloads fun Credentials.serialize(storePassword: Boolean = true): ByteArray = joinData(userName, if (storePassword) password else null)!! -fun SecureString(value: CharSequence): SecureString = SecureString(Charsets.UTF_8.encode(CharBuffer.wrap(value)).toByteArray()) +@Suppress("FunctionName") +internal fun SecureString(value: CharSequence): SecureString = SecureString(Charsets.UTF_8.encode(CharBuffer.wrap(value)).toByteArray()) -class SecureString(value: ByteArray) { +internal class SecureString(value: ByteArray) { companion object { private val encryptionSupport = EncryptionSupport(SecretKeySpec(generateAesKey(), "AES")) } @@ -110,4 +97,6 @@ class SecureString(value: ByteArray) { private val data = encryptionSupport.encrypt(value) fun get(clearable: Boolean = true): OneTimeString = OneTimeString(encryptionSupport.decrypt(data), clearable = clearable) -} \ No newline at end of file +} + +internal val ACCESS_TO_KEY_CHAIN_DENIED = Credentials(null, null as OneTimeString?) diff --git a/platform/credential-store/src/kdbx/KdbxEntry.kt b/platform/credential-store/src/kdbx/KdbxEntry.kt index a5bae43ebe14..4a39e6a4aa68 100644 --- a/platform/credential-store/src/kdbx/KdbxEntry.kt +++ b/platform/credential-store/src/kdbx/KdbxEntry.kt @@ -22,7 +22,7 @@ import org.jdom.Element private const val VALUE_ELEMENT_NAME = "Value" -class KdbxEntry(private val element: Element, private val database: KeePassDatabase, internal @Volatile var group: KdbxGroup?) { +internal class KdbxEntry(private val element: Element, private val database: KeePassDatabase, internal @Volatile var group: KdbxGroup?) { @Volatile var title: String? = element.removeProperty("Title") set(value) { if (field != value) { diff --git a/platform/credential-store/src/kdbx/KdbxGroup.kt b/platform/credential-store/src/kdbx/KdbxGroup.kt index c88dd12517a2..847d95f14221 100644 --- a/platform/credential-store/src/kdbx/KdbxGroup.kt +++ b/platform/credential-store/src/kdbx/KdbxGroup.kt @@ -1,3 +1,4 @@ +// 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.credentialStore.kdbx import com.intellij.util.containers.ContainerUtil @@ -10,7 +11,7 @@ import java.time.Instant import java.time.LocalDateTime import java.time.ZoneOffset -class KdbxGroup(private val element: Element, private val database: KeePassDatabase, private @Volatile var parent: KdbxGroup?) { +internal class KdbxGroup(private val element: Element, private val database: KeePassDatabase, private @Volatile var parent: KdbxGroup?) { @Volatile var name: String = element.getChildText(NAME_ELEMENT_NAME) ?: "Unnamed" set(value) { if (field != value) { diff --git a/platform/credential-store/src/kdbx/KeePassDatabase.kt b/platform/credential-store/src/kdbx/KeePassDatabase.kt index 76a581f66a51..5adac6ce07cd 100644 --- a/platform/credential-store/src/kdbx/KeePassDatabase.kt +++ b/platform/credential-store/src/kdbx/KeePassDatabase.kt @@ -29,7 +29,7 @@ private const val ROOT_ELEMENT_NAME = "Root" internal var dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'") -class KeePassDatabase(private val rootElement: Element = createEmptyDatabase()) { +internal class KeePassDatabase(private val rootElement: Element = createEmptyDatabase()) { private val dbMeta: Element get() = rootElement.getChild("Meta") ?: throw IllegalStateException("no meta") diff --git a/platform/credential-store/src/macOsKeychainLibrary.kt b/platform/credential-store/src/macOsKeychainLibrary.kt index a70b833a2fcb..778a55696134 100644 --- a/platform/credential-store/src/macOsKeychainLibrary.kt +++ b/platform/credential-store/src/macOsKeychainLibrary.kt @@ -79,12 +79,15 @@ internal class KeyChainCredentialStore : CredentialStore { } } -fun findGenericPassword(serviceName: ByteArray, accountName: String?): Credentials? { +private fun findGenericPassword(serviceName: ByteArray, accountName: String?): Credentials? { val accountNameBytes = accountName?.toByteArray() val passwordSize = IntArray(1) val passwordRef = PointerByReference() val itemRef = PointerByReference() - checkForError("find", LIBRARY.SecKeychainFindGenericPassword(null, serviceName.size, serviceName, accountNameBytes?.size ?: 0, accountNameBytes, passwordSize, passwordRef, itemRef)) + val errorCode = checkForError("find", LIBRARY.SecKeychainFindGenericPassword(null, serviceName.size, serviceName, accountNameBytes?.size ?: 0, accountNameBytes, passwordSize, passwordRef, itemRef)) + if (errorCode == errSecUserCanceled) { + return ACCESS_TO_KEY_CHAIN_DENIED + } val pointer = passwordRef.value ?: return null val password = OneTimeString(pointer.getByteArray(0, passwordSize.get(0))) @@ -158,9 +161,9 @@ internal class SecKeychainAttributeInfo : Structure() { override fun getFieldOrder() = listOf("count", "tag", "format") } -private fun checkForError(message: String, code: Int) { +private fun checkForError(message: String, code: Int): Int { if (code == errSecSuccess || code == errSecItemNotFound) { - return + return code } val translated = LIBRARY.SecCopyErrorMessageString(code, null) @@ -183,6 +186,8 @@ private fun checkForError(message: String, code: Int) { else { LOG.error(builder.toString()) } + + return code } @Suppress("FunctionName") diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafe.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafe.java index 0e87298d2085..18d960240893 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafe.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafe.java @@ -1,18 +1,4 @@ -/* - * Copyright 2000-2016 JetBrains s.r.o. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ +// 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.ide.passwordSafe.impl.providers.memory; import com.intellij.ide.passwordSafe.impl.PasswordSafeTimed; @@ -46,7 +32,7 @@ public class MemoryPasswordSafe extends BasePasswordSafeProvider { private final transient PasswordSafeTimed> database = new PasswordSafeTimed>() { @Override protected Map compute() { - return Collections.synchronizedMap(ContainerUtil.newHashMap()); + return Collections.synchronizedMap(ContainerUtil.newHashMap()); } @Override