diff --git a/lib/annotations/jdk/javax/swing/annotations.xml b/lib/annotations/jdk/javax/swing/annotations.xml new file mode 100644 index 000000000000..741f81f5f0ce --- /dev/null +++ b/lib/annotations/jdk/javax/swing/annotations.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/platform/configuration-store-impl/testSrc/ProjectStoreTest.kt b/platform/configuration-store-impl/testSrc/ProjectStoreTest.kt index cdbbf5eac8d9..806bfb2621cf 100644 --- a/platform/configuration-store-impl/testSrc/ProjectStoreTest.kt +++ b/platform/configuration-store-impl/testSrc/ProjectStoreTest.kt @@ -71,10 +71,11 @@ internal class ProjectStoreTest { val projectRule = ProjectRule() } - val tempDirManager = TemporaryDirectory() + private val tempDirManager = TemporaryDirectory() - private val ruleChain = RuleChain(tempDirManager) - @Rule fun getChain() = ruleChain + @Rule + @JvmField + val ruleChain = RuleChain(tempDirManager) @Language("XML") private val iprFileContent = diff --git a/platform/core-impl/src/com/intellij/mock/MockApplicationEx.java b/platform/core-impl/src/com/intellij/mock/MockApplicationEx.java index 9d53126fbc8e..361371a2eeba 100644 --- a/platform/core-impl/src/com/intellij/mock/MockApplicationEx.java +++ b/platform/core-impl/src/com/intellij/mock/MockApplicationEx.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -24,7 +24,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.io.IOException; public class MockApplicationEx extends MockApplication implements ApplicationEx { public MockApplicationEx(@NotNull Disposable parentDisposable) { @@ -43,11 +42,11 @@ public class MockApplicationEx extends MockApplication implements ApplicationEx } @Override - public void load(String path) { + public void load(@Nullable String path) { } @Override - public void load() throws IOException { + public void load() { load(null); } diff --git a/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java b/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java index 8c4c57946bc6..337dd04eb83a 100644 --- a/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java +++ b/platform/core-impl/src/com/intellij/openapi/application/ex/ApplicationEx.java @@ -22,7 +22,6 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; -import java.io.IOException; /** * @author max @@ -37,7 +36,7 @@ public interface ApplicationEx extends Application { */ void load(@Nullable String configPath); - void load() throws IOException; + void load(); boolean isLoaded(); diff --git a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafe.java b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafe.java index 5e64427a7829..5d7fedae0157 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafe.java +++ b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafe.java @@ -17,10 +17,15 @@ package com.intellij.ide.passwordSafe; import com.intellij.openapi.components.ServiceManager; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; public abstract class PasswordSafe implements PasswordStorage { @NotNull public static PasswordSafe getInstance() { return ServiceManager.getService(PasswordSafe.class); } + + public abstract void setPassword(@Nullable Class requestor, @NotNull String key, @Nullable String value, boolean memoryOnly); + + public abstract boolean isMemoryOnly(); } diff --git a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java index 74bdd1902c56..22f4f1035ef6 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java +++ b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordSafeException.java @@ -15,14 +15,8 @@ */ package com.intellij.ide.passwordSafe; -/** - * The exception that is thrown when password safe is not available (unable to ask for master password) - */ +@Deprecated public class PasswordSafeException extends RuntimeException { - private static final long MIN_INTERVAL = 1000L; - - private long myTimeMillis = System.currentTimeMillis(); - public PasswordSafeException(String message, Throwable cause) { super(message, cause); } @@ -30,13 +24,4 @@ public class PasswordSafeException extends RuntimeException { public PasswordSafeException(String message) { super(message); } - - public boolean justHappened() { - long timeMillis = System.currentTimeMillis(); - if (timeMillis - myTimeMillis < MIN_INTERVAL) { - myTimeMillis = timeMillis; - return true; - } - return false; - } } diff --git a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java index 7ee62a58cb7f..90e7a411a56a 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java +++ b/platform/platform-api/src/com/intellij/ide/passwordSafe/PasswordStorage.java @@ -15,54 +15,46 @@ */ package com.intellij.ide.passwordSafe; -import com.intellij.openapi.application.Application; -import com.intellij.openapi.application.ModalityState; import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -/** - * The interface defines basic password management operations - */ public interface PasswordStorage { - /** - *

Get password stored in a password safe.

- * - *

NB: - * This method may be called from the background, - * and it may need to ask user to enter the master password to access the database by calling - * {@link Application#invokeAndWait(Runnable, ModalityState) invokeAndWait()} to show a modal dialog. - * So make sure not to call it from the read action. - * Calling this method from the dispatch thread is allowed.

- * - * @param project the project, that is used to ask for the master password if this is the first access to password safe - * @param requestor the requestor class - * @param key the key for the password - * @return the stored password or null if the password record was not found or was removed - * @throws PasswordSafeException if password safe cannot be accessed - * @throws IllegalStateException if the method is called from the read action. - */ @Nullable - String getPassword(@Nullable Project project, @NotNull Class requestor, String key); + default String getPassword(@NotNull String key) { + //noinspection deprecation + return getPassword(null, null, key); + } + + @Nullable + String getPassword(@Nullable Class requestor, @NotNull String key); + + default void setPassword(@NotNull String key, @Nullable String value) { + setPassword(null, key, value); + } + + void setPassword(@Nullable Class requestor, @NotNull String key, @Nullable String value); /** - * Remove password stored in a password safe - * - * @param project the project, that is used to ask for the master password if this is the first access to password safe - * @param requestor the requestor class - * @param key the key for the password - * @throws PasswordSafeException if password safe cannot be accessed + * @deprecated Please use {@link #setPassword} and pass value as null */ - void removePassword(@Nullable Project project, @NotNull Class requestor, String key); + @SuppressWarnings("unused") + @Deprecated + default void removePassword(@SuppressWarnings("UnusedParameters") @Nullable Project project, @Nullable Class requestor, String key) { + setPassword(requestor, key, null); + } /** - * Store password in password safe - * - * @param project the project, that is used to ask for the master password if this is the first access to password safe - * @param requestor the requestor class - * @param key the key for the password - * @param value the value to store - * @throws PasswordSafeException if password safe cannot be accessed + * @deprecated Please use {@link #setPassword} */ - void storePassword(@Nullable Project project, @NotNull Class requestor, String key, String value); + @Deprecated + default void storePassword(@SuppressWarnings("UnusedParameters") @Nullable Project project, @Nullable Class requestor, @NotNull String key, @Nullable String value) { + setPassword(requestor, key, value); + } + + @Deprecated + @Nullable + default String getPassword(@SuppressWarnings("UnusedParameters") @Nullable Project project, @Nullable Class requestor, @NotNull String key) { + return getPassword(requestor, key); + } } diff --git a/platform/platform-api/src/com/intellij/util/EncryptionSupport.kt b/platform/platform-api/src/com/intellij/util/EncryptionSupport.kt new file mode 100644 index 000000000000..7e4b3a2d32e5 --- /dev/null +++ b/platform/platform-api/src/com/intellij/util/EncryptionSupport.kt @@ -0,0 +1,60 @@ +/* + * 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. + */ +package com.intellij.util + +import java.nio.ByteBuffer +import java.security.Key +import java.security.SecureRandom +import javax.crypto.Cipher +import javax.crypto.spec.IvParameterSpec +import javax.crypto.spec.SecretKeySpec + +// opposite to PropertiesEncryptionSupport, we store iv length, but not body length +open class EncryptionSupport(private val key: Key = SecretKeySpec(generateAesKey(), "AES")) { + open fun encrypt(data: ByteArray, size: Int = data.size) = encrypt(data, size, key) + + open fun decrypt(data: ByteArray) = decrypt(data, key) +} + +fun generateAesKey(): ByteArray { + // http://security.stackexchange.com/questions/14068/why-most-people-use-256-bit-encryption-instead-of-128-bit + val bytes = ByteArray(16) + SecureRandom().nextBytes(bytes) + return bytes +} + +private fun encrypt(message: ByteArray, size: Int, key: Key): ByteArray { + val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") + cipher.init(Cipher.ENCRYPT_MODE, key) + val body = cipher.doFinal(message, 0, size) + val iv = cipher.iv + + val byteBuffer = ByteBuffer.wrap(ByteArray(4 + iv.size + body.size)) + byteBuffer.putInt(iv.size) + byteBuffer.put(iv) + byteBuffer.put(body) + return byteBuffer.array() +} + +private fun decrypt(data: ByteArray, key: Key): ByteArray { + val byteBuffer = ByteBuffer.wrap(data) + @Suppress("UsePropertyAccessSyntax") + val ivLength = byteBuffer.getInt() + val ciph = Cipher.getInstance("AES/CBC/PKCS5Padding") + ciph.init(Cipher.DECRYPT_MODE, key, IvParameterSpec(data, byteBuffer.position(), ivLength)) + val dataOffset = byteBuffer.position() + ivLength + return ciph.doFinal(data, dataOffset, data.size - dataOffset) +} \ No newline at end of file diff --git a/platform/platform-api/src/org/jetbrains/concurrency/AsyncPromise.kt b/platform/platform-api/src/org/jetbrains/concurrency/AsyncPromise.kt index 91f13db02c96..03bb4a69e895 100644 --- a/platform/platform-api/src/org/jetbrains/concurrency/AsyncPromise.kt +++ b/platform/platform-api/src/org/jetbrains/concurrency/AsyncPromise.kt @@ -20,6 +20,7 @@ import com.intellij.openapi.util.Getter import com.intellij.util.Consumer import com.intellij.util.Function import java.util.* +import java.util.concurrent.atomic.AtomicReference private val LOG = Logger.getInstance(AsyncPromise::class.java) @@ -30,30 +31,30 @@ open class AsyncPromise : Promise(), Getter { @Volatile private var done: Consumer? = null @Volatile private var rejected: Consumer? = null - @Volatile private var state: Promise.State = Promise.State.PENDING + private val state = AtomicReference(Promise.State.PENDING) // result object or error message @Volatile private var result: Any? = null - override fun getState() = state + override fun getState() = state.get()!! override fun done(done: Consumer): Promise { if (isObsolete(done)) { return this } - when (state) { + when (state.get()!!) { Promise.State.PENDING -> { + this.done = setHandler(this.done, done, State.FULFILLED) } Promise.State.FULFILLED -> { @Suppress("UNCHECKED_CAST") done.consume(result as T?) - return this } - Promise.State.REJECTED -> return this + Promise.State.REJECTED -> { + } } - this.done = setHandler(this.done, done) return this } @@ -62,26 +63,26 @@ open class AsyncPromise : Promise(), Getter { return this } - when (state) { + when (state.get()!!) { Promise.State.PENDING -> { + this.rejected = setHandler(this.rejected, rejected, State.REJECTED) + } + Promise.State.FULFILLED -> { } - Promise.State.FULFILLED -> return this Promise.State.REJECTED -> { rejected.consume(result as Throwable?) - return this } } - this.rejected = setHandler(this.rejected, rejected) return this } @Suppress("UNCHECKED_CAST") - override fun get() = if (state == Promise.State.FULFILLED) result as T? else null + override fun get() = if (state.get() == Promise.State.FULFILLED) result as T? else null override fun then(fulfilled: Function): Promise { @Suppress("UNCHECKED_CAST") - when (state) { + when (state.get()!!) { Promise.State.PENDING -> { } Promise.State.FULFILLED -> return DonePromise(fulfilled.`fun`(result as T?)) @@ -105,26 +106,23 @@ open class AsyncPromise : Promise(), Getter { override fun notify(child: AsyncPromise) { LOG.assertTrue(child !== this) - when (state) { + when (state.get()!!) { Promise.State.PENDING -> { + addHandlers(Consumer({ child.catchError { child.setResult(it) } }), Consumer({ child.setError(it) })) } Promise.State.FULFILLED -> { @Suppress("UNCHECKED_CAST") child.setResult(result as T) - return } Promise.State.REJECTED -> { child.setError((result as Throwable?)!!) - return } } - - addHandlers(Consumer({ child.catchError { child.setResult(it) } }), Consumer({ child.setError(it) })) } override fun thenAsync(fulfilled: Function>): Promise { @Suppress("UNCHECKED_CAST") - when (state) { + when (state.get()!!) { Promise.State.PENDING -> { } Promise.State.FULFILLED -> return fulfilled.`fun`(result as T?) @@ -144,36 +142,32 @@ open class AsyncPromise : Promise(), Getter { } override fun processed(fulfilled: AsyncPromise): Promise { - when (state) { + when (state.get()!!) { Promise.State.PENDING -> { + addHandlers(Consumer({ result -> fulfilled.catchError { fulfilled.setResult(result) } }), Consumer({ fulfilled.setError(it) })) } Promise.State.FULFILLED -> { @Suppress("UNCHECKED_CAST") fulfilled.setResult(result as T) - return this } Promise.State.REJECTED -> { fulfilled.setError((result as Throwable?)!!) - return this } } - - addHandlers(Consumer({ result -> fulfilled.catchError { fulfilled.setResult(result) } }), Consumer({ fulfilled.setError(it) })) return this } private fun addHandlers(done: Consumer, rejected: Consumer) { - this.done = setHandler(this.done, done) - this.rejected = setHandler(this.rejected, rejected) + this.done = setHandler(this.done, done, State.FULFILLED) + this.rejected = setHandler(this.rejected, rejected, State.REJECTED) } fun setResult(result: T?) { - if (state != Promise.State.PENDING) { + if (!state.compareAndSet(Promise.State.PENDING, Promise.State.FULFILLED)) { return } this.result = result - state = Promise.State.FULFILLED val done = this.done clearHandlers() @@ -182,21 +176,18 @@ open class AsyncPromise : Promise(), Getter { } } - fun setError(error: String): Boolean { - return setError(Promise.createError(error)) - } + fun setError(error: String) = setError(Promise.createError(error)) fun cancel() { setError(OBSOLETE_ERROR) } open fun setError(error: Throwable): Boolean { - if (state != Promise.State.PENDING) { + if (!state.compareAndSet(Promise.State.PENDING, Promise.State.REJECTED)) { return false } result = error - state = Promise.State.REJECTED val rejected = this.rejected clearHandlers() @@ -216,13 +207,38 @@ open class AsyncPromise : Promise(), Getter { override fun processed(processed: Consumer): Promise { done(processed) - rejected({ error -> processed.consume(null) }) + rejected { processed.consume(null) } return this } + + private fun setHandler(oldConsumer: Consumer?, newConsumer: Consumer, targetState: State): Consumer? = when (oldConsumer) { + null -> newConsumer + is CompoundConsumer<*> -> { + @Suppress("UNCHECKED_CAST") + val compoundConsumer = oldConsumer as CompoundConsumer + synchronized(compoundConsumer) { + compoundConsumer.consumers.let { + if (it == null) { + // clearHandlers was called - just execute newConsumer + if (state.get() == targetState) { + @Suppress("UNCHECKED_CAST") + newConsumer.consume(result as T?) + } + return null + } + else { + it.add(newConsumer) + return compoundConsumer + } + } + } + } + else -> CompoundConsumer(oldConsumer, newConsumer) + } } private class CompoundConsumer(c1: Consumer, c2: Consumer) : Consumer { - private var consumers: MutableList>? = ArrayList() + var consumers: MutableList>? = ArrayList() init { synchronized(this) { @@ -247,23 +263,16 @@ private class CompoundConsumer(c1: Consumer, c2: Consumer) : Cons fun add(consumer: Consumer) { synchronized(this) { - if (consumers != null) { - consumers!!.add(consumer) + consumers.let { + if (it == null) { + // it means that clearHandlers was called + } + consumers?.add(consumer) } } } } -private fun setHandler(oldConsumer: Consumer?, newConsumer: Consumer) = when (oldConsumer) { - null -> newConsumer - is CompoundConsumer<*> -> { - @Suppress("UNCHECKED_CAST") - (oldConsumer as CompoundConsumer).add(newConsumer) - oldConsumer - } - else -> CompoundConsumer(oldConsumer, newConsumer) -} - internal fun isObsolete(consumer: Consumer<*>?) = consumer is Obsolescent && consumer.isObsolete inline fun AsyncPromise<*>.catchError(runnable: () -> T): T? { diff --git a/platform/platform-impl/src/com/intellij/ide/ApplicationLoadListener.java b/platform/platform-impl/src/com/intellij/ide/ApplicationLoadListener.java index 47b11480857f..fe81280cb51b 100644 --- a/platform/platform-impl/src/com/intellij/ide/ApplicationLoadListener.java +++ b/platform/platform-impl/src/com/intellij/ide/ApplicationLoadListener.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * 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. @@ -22,5 +22,9 @@ import org.jetbrains.annotations.NotNull; public interface ApplicationLoadListener { ExtensionPointName EP_NAME = ExtensionPointName.create("com.intellij.ApplicationLoadListener"); - void beforeApplicationLoaded(@NotNull Application application, @NotNull String configPath); + default void beforeApplicationLoaded(@NotNull Application application, @NotNull String configPath) { + } + + default void beforeComponentsCreated() { + } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/FilePasswordSafeProvider.kt b/platform/platform-impl/src/com/intellij/ide/passwordSafe/FilePasswordSafeProvider.kt new file mode 100644 index 000000000000..42277ecd3028 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/FilePasswordSafeProvider.kt @@ -0,0 +1,217 @@ +/* + * 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. + */ +package com.intellij.ide.passwordSafe.masterKey + +import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider +import com.intellij.ide.passwordSafe.impl.providers.masterKey.windows.WindowsCryptUtils +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.util.SystemInfo +import com.intellij.openapi.util.io.BufferExposingByteArrayOutputStream +import com.intellij.openapi.util.io.setOwnerPermissions +import com.intellij.util.* +import com.intellij.util.containers.ContainerUtil +import java.io.DataInputStream +import java.io.DataOutputStream +import java.nio.file.NoSuchFileException +import java.nio.file.Path +import java.nio.file.Paths +import java.security.Key +import java.security.MessageDigest +import java.security.SecureRandom +import java.util.Base64 +import java.util.concurrent.atomic.AtomicBoolean +import javax.crypto.spec.SecretKeySpec + +internal val LOG = Logger.getInstance(FilePasswordSafeProvider::class.java) + +class FilePasswordSafeProvider @JvmOverloads constructor(keyToValue: Map? = null, baseDirectory: Path = Paths.get(PathManager.getConfigPath()), var memoryOnly: Boolean = false) : PasswordSafeProvider() { + private val db = ContainerUtil.newConcurrentMap() + + private val dbFile = baseDirectory.resolve("pdb") + private val masterKeyStorage = MasterKeyFileStorage(baseDirectory) + + private var encryptionSupport: EncryptionSupport? = null + + private val needToSave: AtomicBoolean + + init { + if (keyToValue == null) { + needToSave = AtomicBoolean(false) + run { + encryptionSupport = EncryptionSupport(SecretKeySpec(masterKeyStorage.get() ?: return@run, "AES")) + + val data: ByteArray + try { + data = encryptionSupport!!.decrypt(dbFile.readBytes()) + } + catch (e: NoSuchFileException) { + LOG.warn("key file exists, but db file not") + return@run + } + + val input = DataInputStream(data.inputStream()) + while (input.available() > 0) { + db.put(input.readUTF(), input.readUTF()) + } + } + } + else { + needToSave = AtomicBoolean(!memoryOnly) + db.putAll(keyToValue) + } + } + + @Synchronized + fun save() { + if (memoryOnly || !needToSave.compareAndSet(true, false)) { + return + } + + try { + var encryptionSupport = encryptionSupport + if (encryptionSupport == null) { + val masterKey = generateAesKey() + masterKeyStorage.set(masterKey) + // set only if key stored successfully + encryptionSupport = EncryptionSupport(SecretKeySpec(masterKey, "AES")) + this.encryptionSupport = encryptionSupport + } + + if (db.isEmpty()) { + dbFile.delete() + return + } + + val byteOut = BufferExposingByteArrayOutputStream() + DataOutputStream(byteOut).use { out -> + for ((key, value) in db) { + out.writeUTF(key) + out.writeUTF(value) + } + } + + dbFile.writeSafe(encryptionSupport.encrypt(byteOut.internalBuffer, byteOut.size())) + dbFile.setOwnerPermissions() + } + catch (e: Throwable) { + // schedule save again + needToSave.set(true) + LOG.error("Cannot save password database", e) + } + } + + @Synchronized + fun deleteFileStorage() { + try { + dbFile.delete() + } + finally { + masterKeyStorage.set(null) + encryptionSupport = null + } + } + + override fun getPassword(requestor: Class<*>?, key: String): String? { + val rawKey = getRawKey(key, requestor) + // try old key - as hash + var value = db.get(rawKey) + if (value == null) { + value = db.remove(toOldKey(MessageDigest.getInstance("SHA-256").digest(rawKey.toByteArray()))) + if (value != null) { + db.put(rawKey, value) + needToSave.set(true) + } + } + return value + } + + override fun setPassword(requestor: Class<*>?, key: String, value: String?) { + val rawKey = getRawKey(key, requestor) + if (value == null) { + if (db.remove(rawKey) != null) { + needToSave.set(true) + } + } + else if (db.put(rawKey, value) != value) { + needToSave.set(true) + } + } + + override fun getName() = "File PasswordSafe" +} + +private fun getRawKey(key: String?, requestor: Class<*>?) = "${if (requestor == null) "" else "${requestor.name}/"}$key" + +internal fun generate(): ByteArray { + val bytes = ByteArray(16) + SecureRandom().nextBytes(bytes) + return bytes +} + +interface MasterKeyStorage { + fun get(): ByteArray? + + fun set(key: ByteArray?) +} + +class WindowsEncryptionSupport(key: Key): EncryptionSupport(key) { + override fun encrypt(data: ByteArray, size: Int) = WindowsCryptUtils.protect(super.encrypt(data, size)) + + override fun decrypt(data: ByteArray) = super.decrypt(WindowsCryptUtils.unprotect(data)) +} + +class MasterKeyFileStorage(baseDirectory: Path) : MasterKeyStorage { + private val encryptionSupport: EncryptionSupport + private val passwordFile = baseDirectory.resolve("pdb.pwd") + + init { + val key = SecretKeySpec(byteArrayOf( + 0x50, 0x72, 0x6f.toByte(), 0x78.toByte(), 0x79.toByte(), 0x20.toByte(), + 0x43.toByte(), 0x6f.toByte(), 0x6e.toByte(), 0x66.toByte(), 0x69.toByte(), 0x67.toByte(), + 0x20.toByte(), 0x53.toByte(), 0x65.toByte(), 0x63.toByte()), "AES") + + encryptionSupport = if (SystemInfo.isWindows) WindowsEncryptionSupport(key) else EncryptionSupport(key) + } + + override fun get(): ByteArray? { + val data: ByteArray + try { + data = passwordFile.readBytes() + } + catch (e: NoSuchFileException) { + return null + } + + try { + return encryptionSupport.decrypt(data) + } + catch (e: Exception) { + LOG.warn("Cannot decrypt master key, file content: ${Base64.getEncoder().encodeToString(data)}", e) + return null + } + } + + override fun set(key: ByteArray?) { + if (key == null) { + passwordFile.delete() + return + } + + passwordFile.writeSafe(encryptionSupport.encrypt(key)) + passwordFile.setOwnerPermissions() + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/PasswordSafeImpl.kt b/platform/platform-impl/src/com/intellij/ide/passwordSafe/PasswordSafeImpl.kt new file mode 100644 index 000000000000..51a84e92e98d --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/PasswordSafeImpl.kt @@ -0,0 +1,93 @@ +/* + * 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. + */ +package com.intellij.ide.passwordSafe.impl + +import com.intellij.ide.passwordSafe.PasswordSafe +import com.intellij.ide.passwordSafe.PasswordSafeSettingsListener +import com.intellij.ide.passwordSafe.config.PasswordSafeSettings +import com.intellij.ide.passwordSafe.config.PasswordSafeSettings.ProviderType +import com.intellij.ide.passwordSafe.masterKey.FilePasswordSafeProvider +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.SettingsSavingComponent + +class PasswordSafeImpl(/* public - backward compatibility */val settings: PasswordSafeSettings) : PasswordSafe(), SettingsSavingComponent { + private val currentProvider: FilePasswordSafeProvider + + // it is helper storage to support set password as memory-only (see setPassword memoryOnly flag) + private val memoryHelperProvider = lazy { FilePasswordSafeProvider(emptyMap(), memoryOnly = true) } + + override fun isMemoryOnly() = settings.providerType == ProviderType.MEMORY_ONLY + + init { + currentProvider = FilePasswordSafeProvider(memoryOnly = settings.providerType == ProviderType.MEMORY_ONLY) + ApplicationManager.getApplication().messageBus.connect().subscribe(PasswordSafeSettings.TOPIC, object: PasswordSafeSettingsListener { + override fun typeChanged(oldValue: ProviderType, newValue: ProviderType) { + val memoryOnly = newValue == ProviderType.MEMORY_ONLY + currentProvider.memoryOnly = memoryOnly + if (memoryOnly) { + currentProvider.deleteFileStorage() + } + } + }) + } + + override fun getPassword(requestor: Class<*>?, key: String): String? { + val value = currentProvider.getPassword(requestor, key) + if (value == null && memoryHelperProvider.isInitialized()) { + // if password was set as `memoryOnly` + return memoryHelperProvider.value.getPassword(requestor, key) + } + return value + } + + override fun setPassword(requestor: Class<*>?, key: String, value: String?) { + currentProvider.setPassword(requestor, key, value) + if (memoryHelperProvider.isInitialized()) { + val memoryHelper = memoryHelperProvider.value + // update password in the memory helper, but only if it was previously set + if (value == null || memoryHelper.getPassword(requestor, key) != null) { + memoryHelper.setPassword(requestor, key, value) + } + } + } + + override fun setPassword(requestor: Class<*>?, key: String, value: String?, memoryOnly: Boolean) { + if (memoryOnly) { + memoryHelperProvider.value.setPassword(requestor, key, value) + // remove to ensure that on getPassword we will not return some value from default provider + currentProvider.setPassword(requestor, key, null) + } + else { + setPassword(requestor, key, value) + } + } + + override fun save() { + currentProvider.save() + } + + // public - backward compatibility + @Suppress("unused", "DeprecatedCallableAddReplaceWith") + @Deprecated("Do not use it") + val masterKeyProvider: PasswordSafeProvider + get() = currentProvider + + @Suppress("unused") + @Deprecated("Do not use it") + // public - backward compatibility + val memoryProvider: PasswordSafeProvider + get() = memoryHelperProvider.value +} diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeConfigurable.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeConfigurable.java index b4b9b1a78106..6091b8c5bc3f 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeConfigurable.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeConfigurable.java @@ -1,6 +1,20 @@ +/* + * 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. + */ package com.intellij.ide.passwordSafe.config; -import com.intellij.ide.passwordSafe.PasswordSafe; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.SearchableConfigurable; @@ -17,10 +31,7 @@ public class PasswordSafeConfigurable implements SearchableConfigurable, Configu * The settings for the password safe */ final PasswordSafeSettings mySettings; - /** - * The password safe service - */ - private final PasswordSafe myPasswordSafe; + /** * The option panel to use */ @@ -31,9 +42,8 @@ public class PasswordSafeConfigurable implements SearchableConfigurable, Configu * * @param settings the password safe settings */ - public PasswordSafeConfigurable(@NotNull PasswordSafeSettings settings, @NotNull PasswordSafe passwordSafe) { + public PasswordSafeConfigurable(@NotNull PasswordSafeSettings settings) { mySettings = settings; - myPasswordSafe = passwordSafe; } /** @@ -55,9 +65,9 @@ public class PasswordSafeConfigurable implements SearchableConfigurable, Configu * {@inheritDoc} */ public JComponent createComponent() { - myPanel = new PasswordSafeOptionsPanel(myPasswordSafe); + myPanel = new PasswordSafeOptionsPanel(); myPanel.reset(mySettings); - return myPanel.getRoot(); //To change body of implemented methods use File | Settings | File Templates. + return myPanel.getRoot(); } /** diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.form b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.form index b2db444be071..4e7fe6a2bf72 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.form +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.form @@ -1,6 +1,6 @@
- + @@ -13,7 +13,7 @@ - + @@ -24,18 +24,9 @@ - - - - - - - - - - + @@ -44,7 +35,7 @@ - + @@ -56,42 +47,9 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.java index d8b52bdead19..29d256077098 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeOptionsPanel.java @@ -15,25 +15,12 @@ */ package com.intellij.ide.passwordSafe.config; -import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; -import com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterPasswordDialog; -import com.intellij.ui.JBColor; -import com.intellij.ui.components.JBLabel; - import javax.swing.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; /** * The option panel for password safe */ public class PasswordSafeOptionsPanel { - private final PasswordSafeImpl myPasswordSafe; - /** - * The password storage policy option - */ - private JRadioButton myDoNotRememberPasswordsRadioButton; /** * The password storage policy option */ @@ -42,50 +29,18 @@ public class PasswordSafeOptionsPanel { * The password storage policy option */ private JRadioButton mySaveOnDiskRadioButton; - /** - * The change password button - */ - private JButton myManagePasswordButton; + /** * The root panel */ private JPanel myRoot; - private JBLabel myMasterPasswordStateLabel; - /** - * The constructor - * - * @param passwordSafe the password safe service instance - */ - public PasswordSafeOptionsPanel(PasswordSafe passwordSafe) { - myPasswordSafe = (PasswordSafeImpl)passwordSafe; - myMasterPasswordStateLabel.setForeground(JBColor.BLUE); - updateMasterPasswordState(); - myManagePasswordButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - if (myPasswordSafe.getMasterKeyProvider().isEmpty()) { - MasterPasswordDialog.resetMasterPasswordDialog(null, myPasswordSafe.getMasterKeyProvider()).show(); - } - else { - MasterPasswordDialog.changeMasterPasswordDialog(null, myPasswordSafe.getMasterKeyProvider()).show(); - } - updateMasterPasswordState(); - } - }); - } - - private void updateMasterPasswordState() { - boolean empty = myPasswordSafe.getMasterKeyProvider().isMasterPasswordEnabled(); - myMasterPasswordStateLabel.setText(empty ? "Disabled" : "Enabled"); + public PasswordSafeOptionsPanel() { } public void reset(PasswordSafeSettings settings) { PasswordSafeSettings.ProviderType t = settings.getProviderType(); - updateMasterPasswordState(); switch (t) { - case DO_NOT_STORE: - myDoNotRememberPasswordsRadioButton.setSelected(true); - break; case MEMORY_ONLY: myRememberPasswordsUntilClosingRadioButton.setSelected(true); break; @@ -98,13 +53,12 @@ public class PasswordSafeOptionsPanel { } private PasswordSafeSettings.ProviderType getProviderType() { - if (myDoNotRememberPasswordsRadioButton.isSelected()) { - return PasswordSafeSettings.ProviderType.DO_NOT_STORE; - } if (myRememberPasswordsUntilClosingRadioButton.isSelected()) { return PasswordSafeSettings.ProviderType.MEMORY_ONLY; } - return PasswordSafeSettings.ProviderType.MASTER_PASSWORD; + else { + return PasswordSafeSettings.ProviderType.MASTER_PASSWORD; + } } /** diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeSettings.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeSettings.java index f1edc303a264..2b5ef29f7ae1 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeSettings.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeSettings.java @@ -15,71 +15,52 @@ */ package com.intellij.ide.passwordSafe.config; +import com.intellij.ide.passwordSafe.PasswordSafeSettingsListener; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.RoamingType; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; +import com.intellij.util.ObjectUtils; +import com.intellij.util.messages.Topic; +import org.jetbrains.annotations.NotNull; -/** - * The password safe settings - */ -@State( - name = "PasswordSafe", - storages = @Storage(value = "security.xml", roamingType = RoamingType.DISABLED) -) +@State(name = "PasswordSafe", storages = @Storage(value = "security.xml", roamingType = RoamingType.DISABLED)) public class PasswordSafeSettings implements PersistentStateComponent { - /** - * The selected provider type - */ + public static final Topic TOPIC = Topic.create("PasswordSafeSettingsListener", PasswordSafeSettingsListener.class); + private ProviderType myProviderType = ProviderType.MASTER_PASSWORD; - /** - * @return get provider type - */ + @NotNull public ProviderType getProviderType() { return myProviderType; } - /** - * Set provider type - * - * @param providerType a new value - */ - public void setProviderType(ProviderType providerType) { - myProviderType = providerType; + public void setProviderType(@NotNull ProviderType value) { + ProviderType oldValue = myProviderType; + if (value != oldValue) { + myProviderType = value; + ApplicationManager.getApplication().getMessageBus().syncPublisher(TOPIC).typeChanged(oldValue, value); + } } - /** - * {@inheritDoc} - */ + @NotNull public State getState() { State s = new State(); s.PROVIDER = myProviderType; return s; } - /** - * {@inheritDoc} - */ - public void loadState(State state) { - myProviderType = state.PROVIDER; + public void loadState(@NotNull State state) { + setProviderType(ObjectUtils.chooseNotNull(state.PROVIDER, ProviderType.MASTER_PASSWORD)); } - - /** - * The settings state - */ public static class State { public ProviderType PROVIDER = ProviderType.MASTER_PASSWORD; } - /** - * The provider type for the password safe - */ public enum ProviderType { - /** - * The passwords are not stored - */ + @Deprecated DO_NOT_STORE, /** * The passwords are stored only in the memory diff --git a/platform/platform-api/src/com/intellij/ide/passwordSafe/MasterPasswordUnavailableException.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeSettingsListener.kt similarity index 62% rename from platform/platform-api/src/com/intellij/ide/passwordSafe/MasterPasswordUnavailableException.java rename to platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeSettingsListener.kt index 5617c9c11072..37f63cc21b47 100644 --- a/platform/platform-api/src/com/intellij/ide/passwordSafe/MasterPasswordUnavailableException.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/config/PasswordSafeSettingsListener.kt @@ -13,13 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.intellij.ide.passwordSafe; +package com.intellij.ide.passwordSafe -/** - * This exception is thrown when master password is not available (process of entering password is cancelled, or IDEA is running headless mode) - */ -public class MasterPasswordUnavailableException extends RuntimeException { - public MasterPasswordUnavailableException(String message) { - super(message); - } -} +import com.intellij.ide.passwordSafe.config.PasswordSafeSettings + +interface PasswordSafeSettingsListener { + fun typeChanged(oldValue: PasswordSafeSettings.ProviderType, newValue: PasswordSafeSettings.ProviderType) +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/dbV1Convertor.kt b/platform/platform-impl/src/com/intellij/ide/passwordSafe/dbV1Convertor.kt new file mode 100644 index 000000000000..806478f71a50 --- /dev/null +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/dbV1Convertor.kt @@ -0,0 +1,144 @@ +/* + * 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. + */ +package com.intellij.ide.passwordSafe.masterKey + +import com.intellij.ide.ApplicationLoadListener +import com.intellij.ide.passwordSafe.config.PasswordSafeSettings +import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper +import com.intellij.ide.passwordSafe.impl.providers.EncryptionUtil +import com.intellij.ide.passwordSafe.impl.providers.masterKey.EnterPasswordComponent +import com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterPasswordDialog +import com.intellij.ide.passwordSafe.impl.providers.masterKey.PasswordDatabase +import com.intellij.ide.passwordSafe.impl.providers.masterKey.windows.WindowsCryptUtils +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.application.PathManager +import com.intellij.openapi.components.ServiceManager +import com.intellij.openapi.ui.DialogWrapper +import com.intellij.openapi.util.SystemInfo +import gnu.trove.THashMap +import java.nio.file.Files +import java.nio.file.Paths +import java.util.* +import java.util.function.Function + +private val TEST_PASSWORD_VALUE = "test password" + +internal fun isMasterPasswordValid(password: String, @Suppress("DEPRECATION") db: PasswordDatabase): Boolean { + val key = EncryptionUtil.genPasswordKey(password) + val value = db.myDatabase.get(ByteArrayWrapper(EncryptionUtil.encryptKey(key, rawTestKey(password)))) + if (value != null) { + return EncryptionUtil.decryptText(key, value) == TEST_PASSWORD_VALUE + } + return false +} + +internal fun checkPassAndConvertOldDb(password: String, @Suppress("DEPRECATION") db: PasswordDatabase): Map? { + if (isMasterPasswordValid(password, db)) { + return convertOldDb(password, db) + } + else { + return null + } +} + +fun convertOldDb(@Suppress("DEPRECATION") db: PasswordDatabase): Map? { + if (db.myDatabase.size <= 1) { + return null + } + + // trying empty password: people who have set up empty password, don't want to get disturbed by the prompt + checkPassAndConvertOldDb("", db)?.let { return it } + + if (SystemInfo.isWindows) { + db.myMasterPassword?.let { + try { + WindowsCryptUtils.unprotect(it).toString(Charsets.UTF_8) + } + catch (e: Exception) { + LOG.warn(e) + null + } + }?.let { + checkPassAndConvertOldDb(it, db)?.let { return it } + } + } + + // if db contains only one entry (+ test entry) - do not ask master pass, just skip + if (db.myDatabase.size <= 2) { + return null + } + + var result: Map? = null + val dialog = MasterPasswordDialog(EnterPasswordComponent(Function { + result = checkPassAndConvertOldDb(it, db) + result != null + })) + + if (ApplicationManager.getApplication().isUnitTestMode) { + dialog.doOKAction() + dialog.close(DialogWrapper.OK_EXIT_CODE) + } + else if (!dialog.showAndGet()) { + LOG.warn("User cancelled master password dialog, will be recreated") + } + return result +} + +internal fun convertOldDb(oldKey: String, @Suppress("DEPRECATION") db: PasswordDatabase): Map { + val oldKeyB = EncryptionUtil.genPasswordKey(oldKey) + val testKey = ByteArrayWrapper(EncryptionUtil.encryptKey(oldKeyB, rawTestKey(oldKey))) + val newDb = THashMap(db.myDatabase.size) + for ((key, value) in db.myDatabase) { + if (testKey == key) { + continue + } + + // in old db we cannot get key value - it is hashed, so, we store it as a base64 encoded in the new DB + newDb.put(toOldKey(EncryptionUtil.decryptKey(oldKeyB, key.unwrap())), EncryptionUtil.decryptText(oldKeyB, value)) + } + return newDb +} + +fun toOldKey(hash: ByteArray) = "old-hashed-key|" + Base64.getEncoder().encodeToString(hash) + +private fun rawTestKey(oldKey: String) = EncryptionUtil.hash("com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafe/TEST_PASSWORD:${oldKey}".toByteArray()) + +internal class PasswordDatabaseConvertor : ApplicationLoadListener { + override fun beforeComponentsCreated() { + try { + val oldDbFile = Paths.get(PathManager.getConfigPath(), "options", "security.xml") + if (Files.exists(oldDbFile)) { + val settings = ServiceManager.getService(PasswordSafeSettings::class.java) + if (settings.providerType != PasswordSafeSettings.ProviderType.MASTER_PASSWORD) { + return + } + + @Suppress("DEPRECATION") + val oldDb = ServiceManager.getService(PasswordDatabase::class.java) + // old db contains at least one test key - skip it + if (oldDb.myDatabase.size > 1) { + val newDb = convertOldDb(ServiceManager.getService(PasswordDatabase::class.java)) + if (newDb != null && newDb.isNotEmpty()) { + FilePasswordSafeProvider(newDb).save() + } + } + } + } + catch (e: Throwable) { + LOG.warn("Cannot check old password safe DB", e) + } + } +} \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java deleted file mode 100644 index 39efd68b0113..000000000000 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeImpl.java +++ /dev/null @@ -1,113 +0,0 @@ -/* - * 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. - */ -package com.intellij.ide.passwordSafe.impl; - -import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; -import com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafe; -import com.intellij.ide.passwordSafe.impl.providers.masterKey.PasswordDatabase; -import com.intellij.ide.passwordSafe.impl.providers.memory.MemoryPasswordSafe; -import com.intellij.ide.passwordSafe.impl.providers.nil.NilProvider; -import com.intellij.openapi.diagnostic.Logger; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -public class PasswordSafeImpl extends PasswordSafe { - private static final Logger LOG = Logger.getInstance(PasswordSafeImpl.class); - - private final PasswordSafeSettings mySettings; - private final MasterKeyPasswordSafe myMasterKeyProvider; - private final NilProvider myNilProvider; - private final MemoryPasswordSafe myMemoryProvider; - - public PasswordSafeImpl(PasswordSafeSettings settings, PasswordDatabase database) { - mySettings = settings; - myMasterKeyProvider = new MasterKeyPasswordSafe(database); - myNilProvider = new NilProvider(); - myMemoryProvider = new MemoryPasswordSafe(); - } - - /** - * @return get currently selected provider - */ - private PasswordSafeProvider provider() { - PasswordSafeProvider p = null; - switch (mySettings.getProviderType()) { - case DO_NOT_STORE: - p = myNilProvider; - break; - case MEMORY_ONLY: - p = myMemoryProvider; - break; - case MASTER_PASSWORD: - p = myMasterKeyProvider; - break; - default: - LOG.error("Unknown provider type: " + mySettings.getProviderType()); - } - if (!p.isSupported()) { - p = myMemoryProvider; - } - return p; - } - - public PasswordSafeSettings getSettings() { - return mySettings; - } - - @Nullable - public String getPassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { - String password = getMemoryProvider().getPassword(project, requester, key); - if (password == null) { - password = provider().getPassword(project, requester, key); - if (password != null) { - // cache the password in memory as well for easier access during the session - getMemoryProvider().storePassword(project, requester, key, password); - } - } - return password; - } - return provider().getPassword(project, requester, key); - } - - public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { - getMemoryProvider().removePassword(project, requester, key); - } - provider().removePassword(project, requester, key); - } - - public void storePassword(@Nullable Project project, @NotNull Class requester, String key, String value) throws PasswordSafeException { - if (mySettings.getProviderType().equals(PasswordSafeSettings.ProviderType.MASTER_PASSWORD)) { - getMemoryProvider().storePassword(project, requester, key, value); - } - provider().storePassword(project, requester, key, value); - } - - /** - * @return get master key provider instance (used for configuration specific to this provider) - */ - public MasterKeyPasswordSafe getMasterKeyProvider() { - return myMasterKeyProvider; - } - - public MemoryPasswordSafe getMemoryProvider() { - return myMemoryProvider; - } -} diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeProvider.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeProvider.java index 26ef98ec1ed3..f0f076764c81 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/PasswordSafeProvider.java @@ -27,10 +27,6 @@ public abstract class PasswordSafeProvider implements PasswordStorage { public boolean isSupported() { return true; } - /** - * @return the description of the provider - */ - public abstract String getDescription(); /** * @return the name of provider diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java index cde1dd1288d8..b34c9751590f 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/BasePasswordSafeProvider.java @@ -15,11 +15,9 @@ */ package com.intellij.ide.passwordSafe.impl.providers; -import com.intellij.ide.passwordSafe.PasswordSafeException; import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.project.Project; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -37,19 +35,16 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { * So make sure not to call it from the read action. * Calling this method from the dispatch thread is allowed.

* - * @param project the project to use - * @param requestor * @return the secret key to use - * @throws PasswordSafeException in case of problems with access to the password database. - * @throws IllegalStateException if the method is called from the read action. */ @NotNull - protected abstract byte[] key(@Nullable Project project, @NotNull Class requestor); + protected abstract byte[] key(); @Nullable - public String getPassword(@Nullable Project project, @NotNull Class requestor, String key) { - byte[] ct = getEncryptedPassword(dbKey(project, requestor, key)); - return ct == null ? null : EncryptionUtil.decryptText(key(project, requestor), ct); + public String getPassword(@Nullable Class requestor, @NotNull String key) { + byte[] masterKey = key(); + byte[] encryptedPassword = getEncryptedPassword(EncryptionUtil.dbKey(masterKey, requestor, key)); + return encryptedPassword == null ? null : EncryptionUtil.decryptText(masterKey, encryptedPassword); } /** @@ -63,19 +58,13 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { /** * Get database key * - * @param project * @param requestor the requestor class * @param key the key to use * @return the key to use for map */ @NotNull - private byte[] dbKey(@Nullable Project project, Class requestor, String key) { - return EncryptionUtil.dbKey(key(project, requestor), requestor, key); - } - - public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - byte[] k = dbKey(project, requester, key); - removeEncryptedPassword(k); + private byte[] dbKey(@Nullable Class requestor, String key) { + return EncryptionUtil.dbKey(key(), requestor, key); } /** @@ -85,9 +74,14 @@ public abstract class BasePasswordSafeProvider extends PasswordSafeProvider { */ protected abstract void removeEncryptedPassword(byte[] key); - public void storePassword(@Nullable Project project, @NotNull Class requestor, String key, String value) throws PasswordSafeException { - byte[] k = dbKey(project, requestor, key); - byte[] ct = EncryptionUtil.encryptText(key(project, requestor), value); + public void setPassword(@Nullable Class requestor, @NotNull String key, @Nullable String value) { + if (value == null) { + removeEncryptedPassword(dbKey(requestor, key)); + return; + } + + byte[] k = dbKey(requestor, key); + byte[] ct = EncryptionUtil.encryptText(key(), value); storeEncryptedPassword(k, ct); } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java index 35c1ee376eea..986857db1c59 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/EncryptionUtil.java @@ -17,6 +17,7 @@ package com.intellij.ide.passwordSafe.impl.providers; import com.intellij.openapi.vfs.CharsetToolkit; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec; @@ -81,8 +82,8 @@ public class EncryptionUtil { * @param key the key * @return the raw key bytes */ - static byte[] rawKey(Class requester, String key) { - return hash(getUTF8Bytes(requester.getName() + "/" + key)); + static byte[] rawKey(@Nullable Class requester, String key) { + return hash(getUTF8Bytes((requester == null ? "" : (requester.getName() + "/")) + key)); } /** @@ -105,7 +106,7 @@ public class EncryptionUtil { * @param password the password to use * @return the generated key */ - public static byte[] genPasswordKey(String password) { + public static byte[] genPasswordKey(@NotNull String password) { return genKey(hash(getUTF8Bytes(password))); } @@ -138,7 +139,7 @@ public class EncryptionUtil { * @return the key to use in the database */ @NotNull - public static byte[] dbKey(@NotNull byte[] password, Class requestor, String key) { + public static byte[] dbKey(@NotNull byte[] password, @Nullable Class requestor, String key) { return encryptKey(password, rawKey(requestor, key)); } @@ -211,14 +212,6 @@ public class EncryptionUtil { } } - - /** - * Encrypt text - * - * @param password the secret key to use - * @param data the bytes to decrypt - * @return encrypted text - */ @NotNull public static String decryptText(byte[] password, byte[] data) { byte[] plain = decryptData(password, data); diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/ChangePasswordComponent.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/ChangePasswordComponent.java deleted file mode 100644 index 6e4ef44c232b..000000000000 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/ChangePasswordComponent.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2000-2014 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. - */ -package com.intellij.ide.passwordSafe.impl.providers.masterKey; - -import com.intellij.ide.passwordSafe.HelpID; - -/** - * @author gregsh - */ -public class ChangePasswordComponent extends PasswordComponentBase { - - - public ChangePasswordComponent(MasterKeyPasswordSafe safe) { - super(safe, "Change"); - myPromptLabel.setText("Enter the current password and the new password
" + - "in order to change the master password."); - } - - @Override - public String getHelpId() { - return HelpID.CHANGE_PASSWORD; - } - - @Override - public boolean apply() { - String o = new String(myPasswordField.getPassword()); - String n = new String(myNewPasswordField.getPassword()); - return mySafe.changeMasterPassword(o, n, myEncryptCheckBox.isSelected()); - } -} diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/EnterPasswordComponent.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/EnterPasswordComponent.java index d5f164c5108a..dc0b91381664 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/EnterPasswordComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/EnterPasswordComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -16,24 +16,34 @@ package com.intellij.ide.passwordSafe.impl.providers.masterKey; import com.intellij.ide.passwordSafe.HelpID; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; +import java.util.function.Function; + /** * @author gregsh */ public class EnterPasswordComponent extends PasswordComponentBase { + @NotNull + private final Function myPasswordConsumer; + + public EnterPasswordComponent(@NotNull Function passwordConsumer) { + super("Enter"); + + myPasswordConsumer = passwordConsumer; - public EnterPasswordComponent(@NotNull MasterKeyPasswordSafe safe, @NotNull Class requestor) { - super(safe, "Enter"); - String requestorName = getRequestorTitle(requestor); myPromptLabel.setText("
Master password is required to unlock the password database.
" + "The password database will be unlocked during this session
" + - "for all subsystems.
" + - "
Requested by: " + requestorName + ""); + "for all subsystems."); UIUtil.setEnabled(myNewPasswordPanel, false, true); myNewPasswordPanel.setVisible(false); + + if (ApplicationManager.getApplication().isUnitTestMode()) { + myPasswordField.setText("pass"); + } } @Override @@ -42,9 +52,13 @@ public class EnterPasswordComponent extends PasswordComponentBase { } @Override - public boolean apply() { + public ValidationInfo apply() { + // enter password — only and only old key, so, we use EncryptionUtil.genPasswordKey String password = new String(myPasswordField.getPassword()); - return mySafe.changeMasterPassword(password, password, myEncryptCheckBox.isSelected()); + if (!myPasswordConsumer.apply(password)) { + return new ValidationInfo("Password is incorrect", myPasswordField); + } + return null; } @Override diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java deleted file mode 100644 index 42fc03bfd22a..000000000000 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafe.java +++ /dev/null @@ -1,335 +0,0 @@ -/* - * 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. - */ -package com.intellij.ide.passwordSafe.impl.providers.masterKey; - -import com.intellij.concurrency.AsyncFutureFactory; -import com.intellij.concurrency.AsyncFutureResult; -import com.intellij.ide.passwordSafe.MasterPasswordUnavailableException; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.impl.PasswordSafeTimed; -import com.intellij.ide.passwordSafe.impl.providers.BasePasswordSafeProvider; -import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper; -import com.intellij.ide.passwordSafe.impl.providers.EncryptionUtil; -import com.intellij.ide.passwordSafe.impl.providers.masterKey.windows.WindowsCryptUtils; -import com.intellij.openapi.application.ApplicationManager; -import com.intellij.openapi.progress.ProcessCanceledException; -import com.intellij.openapi.progress.ProgressIndicator; -import com.intellij.openapi.progress.ProgressIndicatorProvider; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.*; -import com.intellij.openapi.util.registry.Registry; -import com.intellij.openapi.vfs.CharsetToolkit; -import com.intellij.openapi.wm.IdeFocusManager; -import com.intellij.util.ArrayUtil; -import com.intellij.util.ui.UIUtil; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ExecutionException; - -/** - * The password safe that stores information in configuration file encrypted by master password - */ -public class MasterKeyPasswordSafe extends BasePasswordSafeProvider { - private static final String TEST_PASSWORD_KEY = "TEST_PASSWORD:"; - private static final String TEST_PASSWORD_VALUE = "test password"; - - private final PasswordDatabase myDatabase; - private final transient PasswordSafeTimed> myKey = new PasswordSafeTimed>() { - @Override - protected Ref compute() { - return Ref.create(); - } - - @Override - protected int getMinutesToLive() { - return Registry.intValue("passwordSafe.masterPassword.ttl"); - } - }; - - public MasterKeyPasswordSafe(PasswordDatabase database) { - myDatabase = database; - } - - /** - * Reset password for the password safe (clears password database). The method is used from plugin's UI. - * - * @param password the password to set - * @param encrypt if the password should be encrypted an stored is master database - */ - void resetMasterPassword(String password, boolean encrypt) { - myKey.get().set(EncryptionUtil.genPasswordKey(password)); - myDatabase.clear(); - try { - storePassword(null, MasterKeyPasswordSafe.class, testKey(password), TEST_PASSWORD_VALUE); - if (encrypt) { - myDatabase.setPasswordInfo(encryptPassword(password)); - } - else { - myDatabase.setPasswordInfo(ArrayUtil.EMPTY_BYTE_ARRAY); - } - } - catch (PasswordSafeException e) { - throw new IllegalStateException("There should be no problem with password at this point", e); - } - } - - /** - * Set password to use (used from plugin's UI) - * - * @param password the password - * @return true, if password is a correct one - */ - boolean setMasterPassword(String password) { - Object savedKey = myKey.get().get(); - myKey.get().set(EncryptionUtil.genPasswordKey(password)); - String rc; - try { - rc = getPassword(null, MasterKeyPasswordSafe.class, testKey(password)); - } - catch (PasswordSafeException e) { - throw new IllegalStateException("There should be no problem with password at this point", e); - } - if (!TEST_PASSWORD_VALUE.equals(rc)) { - myKey.get().set(savedKey); - return false; - } - else { - return true; - } - } - - /** - * Encrypt database with new password - * - * @param oldPassword the old password - * @param newPassword the new password - * @return re-encrypted database - */ - boolean changeMasterPassword(String oldPassword, String newPassword, boolean encrypt) { - if (!setMasterPassword(oldPassword)) { - return false; - } - byte[] oldKey = (byte[])myKey.get().get(); // set right in the previous call - byte[] newKey = EncryptionUtil.genPasswordKey(newPassword); - ByteArrayWrapper testKey = new ByteArrayWrapper(EncryptionUtil.dbKey(oldKey, MasterKeyPasswordSafe.class, testKey(oldPassword))); - HashMap oldDb = new HashMap(); - myDatabase.copyTo(oldDb); - HashMap newDb = new HashMap(); - for (Map.Entry e : oldDb.entrySet()) { - if (testKey.equals(e.getKey())) { - continue; - } - byte[] decryptedKey = EncryptionUtil.decryptKey(oldKey, e.getKey().unwrap()); - String decryptedText = EncryptionUtil.decryptText(oldKey, e.getValue()); - newDb.put(new ByteArrayWrapper(EncryptionUtil.encryptKey(newKey, decryptedKey)), EncryptionUtil.encryptText(newKey, decryptedText)); - } - synchronized (myDatabase.getDbLock()) { - resetMasterPassword(newPassword, encrypt); - myDatabase.putAll(newDb); - } - return true; - } - - - private static String testKey(String password) { - return TEST_PASSWORD_KEY + password; - } - - @NotNull - @Override - protected byte[] key(@Nullable final Project project, @NotNull final Class requestor) { - Object key = myKey.get().get(); - if (key instanceof byte[]) { - return (byte[])key; - } - - if (key instanceof PasswordSafeException && ((PasswordSafeException)key).justHappened()) { - throw (PasswordSafeException)key; - } - - if (SystemInfo.isWindows) { - try { - setMasterPassword(new String(WindowsCryptUtils.unprotect(myDatabase.getPasswordInfo()), CharsetToolkit.UTF8_CHARSET)); - key = myKey.get().get(); - if (key instanceof byte[]) { - return (byte[])key; - } - } - catch (Exception ignored) { - // ignore exception and ask password - } - } - - key = invokeAndWait(() -> { - Object key1 = myKey.get().get(); - if (key1 instanceof byte[] || key1 instanceof PasswordSafeException && ((PasswordSafeException)key1).justHappened()) { - return key1; - } - try { - if (!myDatabase.isEmpty()) { - MasterPasswordDialog.askPassword(project, this, requestor); - } - else if (!MasterPasswordDialog.resetMasterPasswordDialog(project, this).showAndGet()) { - throw new MasterPasswordUnavailableException("Master password is required to store passwords in the database."); - } - } - catch (PasswordSafeException e) { - myKey.get().set(e); - throw e; - } - return myKey.get().get(); - }, project == null ? Conditions.alwaysFalse() : project.getDisposed()); - if (key instanceof byte[]) { - return (byte[])key; - } - if (key instanceof PasswordSafeException) { - throw (PasswordSafeException)key; - } - - throw new AssertionError(); - } - - private static final Object ourEDTLock = new Object(); - public T invokeAndWait(@NotNull final ThrowableComputable computable, @NotNull final Condition expired) throws E { - if (ApplicationManager.getApplication().isDispatchThread()) { - return computable.compute(); - } - - final AsyncFutureResult future = AsyncFutureFactory.getInstance().createAsyncFutureResult(); - final ExpirableRunnable runnable = new ExpirableRunnable() { - @Override - public boolean isExpired() { - boolean b = expired.value(null); - if (b) future.setException(new ProcessCanceledException()); - return b; - } - - @Override - public void run() { - try { - future.set(computable.compute()); - } - catch (Throwable e) { - future.setException(e); - } - } - }; - ProgressIndicator indicator = ProgressIndicatorProvider.getGlobalProgressIndicator(); - synchronized (ourEDTLock) { - if (indicator != null && indicator.isModal()) { - UIUtil.invokeLaterIfNeeded(() -> { - if (!runnable.isExpired()) { - runnable.run(); - } - }); - } - else { - IdeFocusManager.getGlobalInstance().doWhenFocusSettlesDown(runnable); - } - try { - return future.get(); - } - catch (InterruptedException e) { - throw new ProcessCanceledException(e); - } - catch (ExecutionException e) { - throw (E) e.getCause(); - } - } - } - - @Override - public String getPassword(@Nullable Project project, @NotNull Class requestor, String key) throws PasswordSafeException { - return myDatabase.isEmpty() ? null : super.getPassword(project, requestor, key); - } - - @Override - public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - if (myDatabase.isEmpty()) { - return; - } - super.removePassword(project, requester, key); - } - - @Override - protected byte[] getEncryptedPassword(@NotNull byte[] key) { - return myDatabase.get(key); - } - - @Override - protected void removeEncryptedPassword(byte[] key) { - myDatabase.remove(key); - } - - @Override - protected void storeEncryptedPassword(byte[] key, byte[] encryptedPassword) { - myDatabase.put(key, encryptedPassword); - } - - @Override - public boolean isSupported() { - return !ApplicationManager.getApplication().isHeadlessEnvironment(); - } - - @Override - public String getDescription() { - return "This provider stores passwords in IDEA config and uses master password to encrypt other passwords. " + - "The passwords for the same resources are shared between different projects."; - } - - @Override - public String getName() { - return "Master Key PasswordSafe"; - } - - - public boolean isMasterPasswordEnabled() { - return setMasterPassword(""); - } - - public boolean isOsProtectedPasswordSupported() { - return SystemInfo.isWindows; - } - - /** - * Encrypt master password - * - * @param pw the password to encrypt - * @return the encrypted password - * @throws MasterPasswordUnavailableException - * if encryption fails - */ - private static byte[] encryptPassword(String pw) throws MasterPasswordUnavailableException { - assert SystemInfo.isWindows; - return WindowsCryptUtils.protect(EncryptionUtil.getUTF8Bytes(pw)); - } - - public boolean isPasswordEncrypted() { - if (!isOsProtectedPasswordSupported()) { - return false; - } - - byte[] info = myDatabase.getPasswordInfo(); - return info != null && info.length > 0; - } - - public boolean isEmpty() { - return myDatabase.isEmpty(); - } -} diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordDialog.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordDialog.java index 8478ab7701d7..3f7b52470185 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterPasswordDialog.java @@ -15,9 +15,6 @@ */ package com.intellij.ide.passwordSafe.impl.providers.masterKey; -import com.intellij.ide.passwordSafe.MasterPasswordUnavailableException; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.openapi.util.text.StringUtil; @@ -29,7 +26,6 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.ActionEvent; -import java.util.Arrays; import java.util.List; /** @@ -43,51 +39,12 @@ public class MasterPasswordDialog extends DialogWrapper { private final DialogWrapperAction myCardAction; private int myRetriesCount; - /** - * Ask password from user and set it to password safe instance - * - * @param project the current project - * @param safe the password safe - * @param requestor - * @throws PasswordSafeException if the master password is not provided. - */ - public static void askPassword(@Nullable Project project, @NotNull MasterKeyPasswordSafe safe, @NotNull Class requestor) - throws PasswordSafeException { - // trying empty password: people who have set up empty password, don't want to get disturbed by the prompt. - if (safe.setMasterPassword("")) { - return; - } - - if (!enterMasterPasswordDialog(project, safe, requestor).showAndGet()) { - throw new MasterPasswordUnavailableException(PasswordComponentBase.getRequestorTitle(requestor) + ": Cancelled by user"); - } - } - - public static MasterPasswordDialog resetMasterPasswordDialog(@Nullable Project project, - @NotNull MasterKeyPasswordSafe safe) { - return new MasterPasswordDialog(project, new ResetPasswordComponent(safe, true)); - } - - public static MasterPasswordDialog changeMasterPasswordDialog(@Nullable Project project, - @NotNull MasterKeyPasswordSafe safe) { - return new MasterPasswordDialog(project, new ChangePasswordComponent(safe), new ResetPasswordComponent(safe, false)); - } - - public static MasterPasswordDialog enterMasterPasswordDialog(@Nullable Project project, - @NotNull MasterKeyPasswordSafe safe, - @NotNull Class requestor) { - return new MasterPasswordDialog(project, new EnterPasswordComponent(safe, requestor), new ResetPasswordComponent(safe, false)); - } - - protected MasterPasswordDialog(@Nullable Project project, PasswordComponentBase... components) { - super(project, false); + public MasterPasswordDialog(@NotNull PasswordComponentBase component) { + super(false); setResizable(false); - assert components.length > 0; - myComponents.addAll(Arrays.asList(components)); - for (PasswordComponentBase component : myComponents) { - myRootPanel.add(component.getComponent(), component.getTitle()); - } + myComponents.add(component); + myRootPanel.add(component.getComponent(), component.getTitle()); myCardAction = new DialogWrapperAction("") { @Override protected void doAction(ActionEvent e) { @@ -160,21 +117,19 @@ public class MasterPasswordDialog extends DialogWrapper { } @Override - protected void doOKAction() { + public void doOKAction() { PasswordComponentBase component = getSelectedComponent(); - if (component.apply()) { + ValidationInfo info = component.apply(); + if (info == null) { super.doOKAction(); } else { - ValidationInfo info = component.validatePassword(); - if (info != null) { - setErrorText(info.message + " " + StringUtil.repeat(".", myRetriesCount)); - if (info.component != null) { - info.component.requestFocus(); - } - if (++myRetriesCount > NUMBER_OF_RETRIES) { - super.doCancelAction(); - } + setErrorText(info.message + " " + StringUtil.repeat(".", myRetriesCount)); + if (info.component != null) { + info.component.requestFocus(); + } + if (++myRetriesCount > NUMBER_OF_RETRIES) { + super.doCancelAction(); } } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.form b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.form index d1a28d249433..f088f62aa219 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.form +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.form @@ -41,7 +41,7 @@ - + @@ -83,16 +83,6 @@ - - - - - - - - - - diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.java index 453fd0f133ac..ca3a8ceb8759 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordComponentBase.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -16,7 +16,6 @@ package com.intellij.ide.passwordSafe.impl.providers.masterKey; import com.intellij.icons.AllIcons; -import com.intellij.ide.TypePresentationService; import com.intellij.openapi.ui.ValidationInfo; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -29,7 +28,6 @@ import java.util.Arrays; * @author gregsh */ public abstract class PasswordComponentBase { - protected final MasterKeyPasswordSafe mySafe; private final String myTitle; private JPanel myRootPanel; @@ -41,27 +39,16 @@ public abstract class PasswordComponentBase { protected JPasswordField myPasswordField; protected JPasswordField myNewPasswordField; protected JPasswordField myConfirmPasswordField; - protected JCheckBox myEncryptCheckBox; protected JLabel myPasswordLabel; protected JLabel myNewPasswordLabel; - - public PasswordComponentBase(@NotNull MasterKeyPasswordSafe safe, @NotNull String title) { - mySafe = safe; + public PasswordComponentBase(@NotNull String title) { myTitle = title; myIconLabel.setText(""); myIconLabel.setIcon(AllIcons.General.PasswordLock); myIconLabel.setDisabledIcon(AllIcons.General.PasswordLock); //myPromptLabel.setUI(new MultiLineLabelUI()); myPromptLabel.setFont(UIUtil.getLabelFont(UIUtil.FontSize.SMALL)); - - if (!safe.isOsProtectedPasswordSupported()) { - myEncryptCheckBox.setSelected(false); - myEncryptCheckBox.setVisible(false); - } - else { - myEncryptCheckBox.setSelected(safe.isPasswordEncrypted()); - } } public JComponent getComponent() { @@ -85,25 +72,10 @@ public abstract class PasswordComponentBase { return null; } - public abstract boolean apply(); + @Nullable + public abstract ValidationInfo apply(); public String getHelpId() { return null; } - - @Nullable - protected ValidationInfo validatePassword() { - if (myPasswordField.isEnabled()) { - String oldPassword = new String(myPasswordField.getPassword()); - if (!mySafe.setMasterPassword(oldPassword)) { - return new ValidationInfo("Password is incorrect", myPasswordField); - } - } - return null; - } - - @NotNull - public static String getRequestorTitle(@NotNull Class requestor) { - return TypePresentationService.getDefaultTypeName(requestor); - } } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordDatabase.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordDatabase.java index 1f2260168c5d..68c632bd2f0e 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordDatabase.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/masterKey/PasswordDatabase.java @@ -13,7 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.intellij.ide.passwordSafe.impl.providers.masterKey; import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper; @@ -22,219 +21,55 @@ import com.intellij.openapi.components.RoamingType; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; import com.intellij.openapi.diagnostic.Logger; -import org.apache.commons.codec.DecoderException; -import org.apache.commons.codec.binary.Hex; import org.jetbrains.annotations.Nullable; +import javax.xml.bind.DatatypeConverter; import java.util.HashMap; import java.util.Map; -import java.util.TreeMap; -/** - * The password database. The internal component for {@link MasterKeyPasswordSafe}. - */ -@State( - name = "PasswordDatabase", - storages = {@Storage(value = "security.xml", roamingType = RoamingType.DISABLED)} -) +@Deprecated +@State(name = "PasswordDatabase", storages = @Storage(value = "security.xml", roamingType = RoamingType.DISABLED, deprecated = true)) public class PasswordDatabase implements PersistentStateComponent { - /** - * The name of logger - */ private final static Logger LOG = Logger.getInstance(PasswordDatabase.class.getName()); - /** - * The password database - */ - private transient final Map myDatabase = new HashMap(); - /** - * OS-specific information about master password - */ - private transient byte[] myMasterPasswordInfo; - /** - * Clear the password database - */ - public void clear() { - synchronized (myDatabase) { - myDatabase.clear(); - } - } + public transient final Map myDatabase = new HashMap(); + public transient byte[] myMasterPassword; - /** - * @return true if the database is empty - */ - public boolean isEmpty() { - synchronized (myDatabase) { - return myDatabase.isEmpty(); - } - } - - /** - * Put password in the database - * - * @param key the encrypted key - * @param value the encrypted value - */ - public void put(byte[] key, byte[] value) { - synchronized (myDatabase) { - myDatabase.put(new ByteArrayWrapper(key), value); - } - } - - /** - * Get all entries in the database - * - * @param copy the copy to use - */ - public void copyTo(Map copy) { - synchronized (myDatabase) { - copy.putAll(myDatabase); - } - } - - /** - * Put all entries to the database - * - * @param copy the copy to use - */ - public void putAll(Map copy) { - synchronized (myDatabase) { - myDatabase.putAll(copy); - } - } - - - /** - * Get password from the database - * - * @param key the encrypted key - * @return the encrypted value or null - */ - public byte[] get(byte[] key) { - synchronized (myDatabase) { - return myDatabase.get(new ByteArrayWrapper(key)); - } - - } - - /** - * Remove password from the database - * - * @param key the encrypted key - */ - public void remove(byte[] key) { - synchronized (myDatabase) { - myDatabase.remove(new ByteArrayWrapper(key)); - } - } - - /** - * {@inheritDoc} - */ + @Override public State getState() { - TreeMap sorted; - String pi; - synchronized (myDatabase) { - pi = toHex(myMasterPasswordInfo); - sorted = new TreeMap(myDatabase); - } - String[][] db = new String[2][sorted.size()]; - int i = 0; - for (Map.Entry e : sorted.entrySet()) { - db[0][i] = toHex(e.getKey().unwrap()); - db[1][i] = toHex(e.getValue()); - i++; - } - State s = new State(); - s.PASSWORDS = db; - s.MASTER_PASSWORD_INFO = pi; - return s; + return new State(); } - - /** - * Covert bytes to hex - * - * @param bytes bytes to convert - * @return hex representation - */ @Nullable - private static String toHex(byte[] bytes) { - return bytes == null ? null : new String(Hex.encodeHex(bytes)); + private static byte[] fromHex(@Nullable String hex) { + return hex == null ? null : DatatypeConverter.parseHexBinary(hex); } - /** - * Covert hex to bytes - * - * @param hex string to convert - * @return bytes representation - * @throws DecoderException if invalid data encountered - */ - @Nullable - private static byte[] fromHex(String hex) throws DecoderException { - return hex == null ? null : Hex.decodeHex(hex.toCharArray()); - } - - /** - * {@inheritDoc} - */ + @Override public void loadState(State state) { String[][] db = state.PASSWORDS; String pi = state.MASTER_PASSWORD_INFO; - synchronized (myDatabase) { + try { + myMasterPassword = fromHex(pi); + } + catch (Exception e) { + myMasterPassword = null; + } + myDatabase.clear(); + if (db[0].length != db[1].length) { + LOG.warn("The password database is in inconsistent state, ignoring it: " + db[0].length + " != " + db[1].length); + } + int n = db[0].length; + for (int i = 0; i < n; i++) { try { - myMasterPasswordInfo = fromHex(pi); - if (myMasterPasswordInfo == null) { - myMasterPasswordInfo = new byte[0]; + byte[] key = fromHex(db[0][i]); + byte[] value = fromHex(db[1][i]); + if (key != null && value != null) { + myDatabase.put(new ByteArrayWrapper(key), value); } } - catch (DecoderException e) { - myMasterPasswordInfo = new byte[0]; + catch (Exception ignored) { } - myDatabase.clear(); - if (db[0].length != db[1].length) { - LOG.warn("The password database is in inconsistent state, ignoring it: " + db[0].length + " != " + db[1].length); - } - int n = db[0].length; - for (int i = 0; i < n; i++) { - try { - byte[] key = fromHex(db[0][i]); - byte[] value = fromHex(db[1][i]); - if (key != null && value != null) { - myDatabase.put(new ByteArrayWrapper(key), value); - } - } - catch (DecoderException e) { - // skip the entry - } - } - } - } - - /** - * @return the object over which database is synchronized - */ - Object getDbLock() { - return myDatabase; - } - - /** - * @return master password information - */ - byte[] getPasswordInfo() { - synchronized (myDatabase) { - return myMasterPasswordInfo; - } - } - - /** - * Set master password information - * - * @param bytes the bytes for the master password - */ - public void setPasswordInfo(byte[] bytes) { - synchronized (myDatabase) { - myMasterPasswordInfo = bytes; } } @@ -245,7 +80,7 @@ public class PasswordDatabase implements PersistentStateComponent
Specify the new password for the password database.
" + - "Leave blank to disable the master password protection."); - } - else { - myPromptLabel.setText("
The password for the password database will be reset.
" + - "All previously stored passwords will be removed!"); - } - } - - @Override - public String getHelpId() { - return myFirstTime ? HelpID.INIT_PASSWORD : HelpID.RESET_PASSWORD; - } - - @Override - public boolean apply() { - if (myFirstTime || - Messages.showYesNoDialog((Project)null, "All stored passwords will be removed! Are you sure you want to proceed?", - "Confirm Master Password Reset", Messages.getWarningIcon()) == Messages.YES) { - mySafe.resetMasterPassword(new String(myNewPasswordField.getPassword()), myEncryptCheckBox.isSelected()); - ((PasswordSafeImpl)PasswordSafe.getInstance()).getMemoryProvider().clear(); - return true; - } - return false; - } -} 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 fc0a57441688..e50877515cb9 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 @@ -19,7 +19,6 @@ import com.intellij.ide.passwordSafe.impl.PasswordSafeTimed; import com.intellij.ide.passwordSafe.impl.providers.BasePasswordSafeProvider; import com.intellij.ide.passwordSafe.impl.providers.ByteArrayWrapper; import com.intellij.ide.passwordSafe.impl.providers.EncryptionUtil; -import com.intellij.openapi.project.Project; import com.intellij.openapi.util.registry.Registry; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -60,7 +59,7 @@ public class MemoryPasswordSafe extends BasePasswordSafeProvider { @NotNull @Override - protected byte[] key(Project project, @NotNull Class requestor) { + protected byte[] key() { if (key.get() == null) { byte[] rnd = new byte[EncryptionUtil.SECRET_KEY_SIZE_BYTES * 16]; new SecureRandom().nextBytes(rnd); @@ -84,11 +83,6 @@ public class MemoryPasswordSafe extends BasePasswordSafeProvider { database.get().put(new ByteArrayWrapper(key), encryptedPassword); } - @Override - public String getDescription() { - return "Memory-based password safe provider. The passwords are stored only for the duration of IDEA process."; - } - @Override public String getName() { return "Memory PasswordSafe"; diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java deleted file mode 100644 index ed63e3691cbb..000000000000 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/impl/providers/nil/NilProvider.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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. - */ -package com.intellij.ide.passwordSafe.impl.providers.nil; - -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.impl.PasswordSafeProvider; -import com.intellij.openapi.project.Project; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -/** - * The most secure provider that does not store anything, so it cannot be cracked - */ -public final class NilProvider extends PasswordSafeProvider { - @Override - public String getDescription() { - return "The provider that does not remembers password."; - } - - @Override - public String getName() { - return "Do not Store"; - } - - public String getPassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - // nothing is stored - return null; - } - - public void removePassword(@Nullable Project project, @NotNull Class requester, String key) throws PasswordSafeException { - // do nothing - } - - public void storePassword(@Nullable Project project, @NotNull Class requester, String key, String value) throws PasswordSafeException { - // just forget about password - } -} diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordPromptComponent.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordPromptComponent.java index 0393b983ca60..2372157fe667 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordPromptComponent.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordPromptComponent.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -15,7 +15,6 @@ */ package com.intellij.ide.passwordSafe.ui; -import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ui.DialogUtil; @@ -39,7 +38,7 @@ public class PasswordPromptComponent { private JTextField myUserTextField; private JLabel myIconLabel; - public PasswordPromptComponent(PasswordSafeSettings.ProviderType type, + public PasswordPromptComponent(boolean memoryOnly, String message, boolean showUserName, String passwordFieldLabel, @@ -48,7 +47,17 @@ public class PasswordPromptComponent { myIconLabel.setIcon(Messages.getWarningIcon()); JTextPane messageField = Messages.configureMessagePaneUi(new JTextPane(), message, UIUtil.FontSize.SMALL); myMessagePanel.add(Messages.wrapToScrollPaneIfNeeded(messageField, 0, 4), BorderLayout.CENTER); - setTargetProviderType(type); + + if (memoryOnly) { + myRememberCheckBox.setVisible(false); + myRememberCheckBox.setEnabled(false); + myRememberCheckBox.setSelected(false); + } + else { + myRememberCheckBox.setSelected(true); + myRememberCheckBox.setToolTipText("The password will be stored between application sessions."); + } + setUserInputVisible(showUserName); if (passwordFieldLabel != null) myPasswordLabel.setText(passwordFieldLabel); if (rememberCheckBoxLabel != null) { @@ -70,29 +79,6 @@ public class PasswordPromptComponent { myUserPanel.setVisible(visible); } - private void setTargetProviderType(PasswordSafeSettings.ProviderType type) { - switch (type) { - case MASTER_PASSWORD: - myRememberCheckBox.setEnabled(true); - myRememberCheckBox.setSelected(true); - myRememberCheckBox.setToolTipText("The password will be stored between application sessions."); - break; - case MEMORY_ONLY: - myRememberCheckBox.setEnabled(true); - myRememberCheckBox.setSelected(true); - myRememberCheckBox.setToolTipText("The password will be stored only during this application session."); - break; - case DO_NOT_STORE: - myRememberCheckBox.setVisible(false); - myRememberCheckBox.setEnabled(false); - myRememberCheckBox.setSelected(false); - myRememberCheckBox.setToolTipText("The password storing is disabled."); - break; - default: - throw new AssertionError("Unknown policy type: " + type); - } - } - public String getUserName() { return myUserTextField.getText(); } diff --git a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java index 16b53889ca9c..df4ad64b202a 100644 --- a/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java +++ b/platform/platform-impl/src/com/intellij/ide/passwordSafe/ui/PasswordSafePromptDialog.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2013 JetBrains s.r.o. + * 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. @@ -16,15 +16,10 @@ package com.intellij.ide.passwordSafe.ui; import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; -import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; -import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Ref; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; @@ -36,17 +31,10 @@ import javax.swing.*; * The generic password dialog. Use it to ask a password from user with option to remember it. */ public class PasswordSafePromptDialog extends DialogWrapper { - private static final Logger LOG = Logger.getInstance(PasswordSafePromptDialog.class.getName()); - private final PasswordPromptComponent myComponent; /** * The private constructor. Note that it does not do init on dialog. - * - * @param project the project - * @param title the dialog title - * @param message the message on the dialog - * @param type */ private PasswordSafePromptDialog(@Nullable Project project, @NotNull String title, @NotNull PasswordPromptComponent component) { super(project, true); @@ -159,47 +147,26 @@ public class PasswordSafePromptDialog extends DialogWrapper { final String error, final String promptLabel, final String checkboxLabel) { - final PasswordSafeImpl ps = (PasswordSafeImpl)PasswordSafe.getInstance(); - try { - if (resetPassword) { - ps.removePassword(project, requestor, key); - } - else { - String pw = ps.getPassword(project, requestor, key); - if (pw != null) { - return pw; - } - } + PasswordSafe ps = PasswordSafe.getInstance(); + if (resetPassword) { + ps.setPassword(requestor, key, null); } - catch (PasswordSafeException ex) { - // ignore exception on get/reset phase - if (LOG.isDebugEnabled()) { - LOG.debug("Failed to retrieve or reset password", ex); + else { + String pw = ps.getPassword(requestor, key); + if (pw != null) { + return pw; } } + final Ref ref = Ref.create(); ApplicationManager.getApplication().invokeAndWait(() -> { - PasswordSafeSettings.ProviderType type = ps.getSettings().getProviderType(); - final PasswordPromptComponent component = new PasswordPromptComponent(type, message, false, promptLabel, checkboxLabel); + final PasswordPromptComponent component = new PasswordPromptComponent(ps.isMemoryOnly(), message, false, promptLabel, checkboxLabel); PasswordSafePromptDialog d = new PasswordSafePromptDialog(project, title, component); d.setErrorText(error); if (d.showAndGet()) { ref.set(new String(component.getPassword())); - try { - if (component.isRememberSelected()) { - ps.storePassword(project, requestor, key, ref.get()); - } - else if (!type.equals(PasswordSafeSettings.ProviderType.DO_NOT_STORE)) { - ps.getMemoryProvider().storePassword(project, requestor, key, ref.get()); - } - } - catch (PasswordSafeException e) { - Messages.showErrorDialog(project, e.getMessage(), "Failed to Store Password"); - if (LOG.isDebugEnabled()) { - LOG.debug("Failed to store password", e); - } - } + ps.setPassword(requestor, key, ref.get(), !component.isRememberSelected()); } }, ModalityState.any()); return ref.get(); diff --git a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java index 128711d081e1..1e13f87558be 100644 --- a/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/application/impl/ApplicationImpl.java @@ -381,7 +381,7 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App } @Override - public void load() throws IOException { + public void load() { load(null); } @@ -401,7 +401,8 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App getPicoContainer().getComponentInstance(ServiceManagerImpl.class); String effectiveConfigPath = FileUtilRt.toSystemIndependentName(configPath == null ? PathManager.getConfigPath() : configPath); - for (ApplicationLoadListener listener : ApplicationLoadListener.EP_NAME.getExtensions()) { + ApplicationLoadListener[] applicationLoadListeners = ApplicationLoadListener.EP_NAME.getExtensions(); + for (ApplicationLoadListener listener : applicationLoadListeners) { try { listener.beforeApplicationLoaded(this, effectiveConfigPath); } @@ -412,6 +413,15 @@ public class ApplicationImpl extends PlatformComponentManagerImpl implements App // we set it after beforeApplicationLoaded call, because app store can depends on stream provider state ServiceKt.getStateStore(this).setPath(effectiveConfigPath); + + for (ApplicationLoadListener listener : applicationLoadListeners) { + try { + listener.beforeComponentsCreated(); + } + catch (Throwable e) { + LOG.error(e); + } + } }); LOG.info(getComponentConfigCount() + " application components initialized in " + (System.currentTimeMillis() - start) + "ms"); } diff --git a/platform/platform-impl/src/com/intellij/util/path.kt b/platform/platform-impl/src/com/intellij/util/path.kt index 6e383abca49f..e0a39d305471 100644 --- a/platform/platform-impl/src/com/intellij/util/path.kt +++ b/platform/platform-impl/src/com/intellij/util/path.kt @@ -15,6 +15,7 @@ */ package com.intellij.util +import com.intellij.ide.passwordSafe.masterKey.LOG import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.vfs.LocalFileSystem import com.intellij.openapi.vfs.VfsUtil @@ -26,6 +27,7 @@ import java.io.OutputStream import java.nio.file.* import java.nio.file.attribute.BasicFileAttributes import java.nio.file.attribute.FileTime +import java.util.* fun Path.exists() = Files.exists(this) @@ -91,8 +93,21 @@ fun Path.writeChild(relativePath: String, data: ByteArray) = resolve(relativePat fun Path.writeChild(relativePath: String, data: String) = writeChild(relativePath, data.toByteArray()) -fun Path.write(data: ByteArray, offset: Int = 0, length: Int = data.size): Path { - outputStream().use { it.write(data, offset, length) } +fun Path.write(data: ByteArray, offset: Int = 0, size: Int = data.size): Path { + outputStream().use { it.write(data, offset, size) } + return this +} + +fun Path.writeSafe(data: ByteArray, offset: Int = 0, size: Int = data.size): Path { + val tempFile = parent.resolve("${fileName}.${UUID.randomUUID()}.tmp") + tempFile.write(data, offset, size) + try { + Files.move(tempFile, this, StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING) + } + catch (e: IOException) { + LOG.warn(e) + FileUtil.rename(tempFile.toFile(), this.toFile()) + } return this } diff --git a/platform/platform-resources/src/META-INF/PlatformExtensions.xml b/platform/platform-resources/src/META-INF/PlatformExtensions.xml index de606a9e0669..3e0339a6d8eb 100644 --- a/platform/platform-resources/src/META-INF/PlatformExtensions.xml +++ b/platform/platform-resources/src/META-INF/PlatformExtensions.xml @@ -163,6 +163,8 @@ serviceImplementation="com.intellij.ide.passwordSafe.config.PasswordSafeSettings"/> + + + + """)) + assertThat(passwordSafe).isNotEmpty + + val provider = FilePasswordSafeProvider(passwordSafe) + assertThat(provider.getPassword("com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafeTest/TEST")).isEqualTo("test") + } + + @Test + fun nonEmptyPass() { + var passwordSafe: Map? = null + runInEdtAndWait { + passwordSafe = convertOldDb(getDb(""" + + """)) + } + assertThat(passwordSafe).isNotEmpty + val provider = FilePasswordSafeProvider(passwordSafe) + assertThat(provider.getPassword("com.intellij.ide.passwordSafe.impl.providers.masterKey.MasterKeyPasswordSafeTest/TEST")).isEqualTo("test") + } + + @Suppress("DEPRECATION") + private fun getDb(data: String): PasswordDatabase { + val passwordDatabase = PasswordDatabase() + val state = PasswordDatabase.State() + XmlSerializer.deserializeInto(state, JDOMUtil.load(data.reader())) + passwordDatabase.loadState(state) + return passwordDatabase + } +} \ No newline at end of file diff --git a/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafeTest.java b/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafeTest.java deleted file mode 100644 index a84497a8f083..000000000000 --- a/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/masterKey/MasterKeyPasswordSafeTest.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright 2000-2010 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. - */ -package com.intellij.ide.passwordSafe.impl.providers.masterKey; - -import com.intellij.ide.passwordSafe.PasswordSafeException; -import org.junit.Test; - -import static org.junit.Assert.*; - -/** - * The test for master key password safe - */ -public class MasterKeyPasswordSafeTest { - - @Test - public void testMasterKey() throws PasswordSafeException { - PasswordDatabase db = new PasswordDatabase(); - MasterKeyPasswordSafe s1 = new MasterKeyPasswordSafe(db); - s1.resetMasterPassword("pass1", false); - s1.storePassword(null, MasterKeyPasswordSafeTest.class, "TEST", "test"); - assertEquals("test", s1.getPassword(null, MasterKeyPasswordSafeTest.class, "TEST")); - assertTrue(s1.changeMasterPassword("pass1", "pass2", false)); - assertEquals("test", s1.getPassword(null, MasterKeyPasswordSafeTest.class, "TEST")); - MasterKeyPasswordSafe s2 = new MasterKeyPasswordSafe(db); - assertFalse(s2.setMasterPassword("pass1")); - assertTrue(s2.setMasterPassword("pass2")); - assertEquals("test", s2.getPassword(null, MasterKeyPasswordSafeTest.class, "TEST")); - assertTrue(s2.changeMasterPassword("pass2", "pass3", false)); - assertTrue(s2.changeMasterPassword("pass3", "pass4", false)); - assertTrue(s2.setMasterPassword("pass4")); - assertEquals("test", s2.getPassword(null, MasterKeyPasswordSafeTest.class, "TEST")); - s2.resetMasterPassword("fail", false); - assertNull(s2.getPassword(null, MasterKeyPasswordSafeTest.class, "TEST")); - } -} diff --git a/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafeTest.java b/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafeTest.java deleted file mode 100644 index 925e7f1fe6bd..000000000000 --- a/platform/platform-tests/testSrc/com/intellij/ide/passwordSafe/impl/providers/memory/MemoryPasswordSafeTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2000-2010 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. - */ -package com.intellij.ide.passwordSafe.impl.providers.memory; - -import org.junit.Assert; -import org.junit.Test; - -/** - * The test for memory implementation of password safe - */ -public class MemoryPasswordSafeTest { - - /** - * Test basic operation of memory-based provider - * - * @throws Exception in case of problem - */ - @Test - public void testMemory() throws Exception { - MemoryPasswordSafe p = new MemoryPasswordSafe(); - Assert.assertNull(p.getPassword(null, MemoryPasswordSafeTest.class, "test")); - p.storePassword(null, MemoryPasswordSafeTest.class, "test", "TEST PASSWORD"); - Assert.assertEquals("TEST PASSWORD", p.getPassword(null, MemoryPasswordSafeTest.class, "test")); - p.removePassword(null, MemoryPasswordSafeTest.class, "test"); - Assert.assertNull(p.getPassword(null, MemoryPasswordSafeTest.class, "test")); - } -} diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties index 04ad69a6c180..113686f3034f 100644 --- a/platform/util/resources/misc/registry.properties +++ b/platform/util/resources/misc/registry.properties @@ -92,7 +92,6 @@ ide.windowSystem.showListItemsPopup=true ide.windowSystem.asyncSplitters=true ide.windowSystem.showTooWindowButtonsSwitcher=true -passwordSafe.masterPassword.ttl=360 passwordSafe.memorySafe.ttl=-1 ide.tree.yieldingUiUpdate=true diff --git a/platform/vcs-impl/src/com/intellij/vcsUtil/AuthDialog.java b/platform/vcs-impl/src/com/intellij/vcsUtil/AuthDialog.java index cd05cfd7e062..31c34f8ea1f4 100644 --- a/platform/vcs-impl/src/com/intellij/vcsUtil/AuthDialog.java +++ b/platform/vcs-impl/src/com/intellij/vcsUtil/AuthDialog.java @@ -13,8 +13,6 @@ package com.intellij.vcsUtil; import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; -import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.DialogWrapper; import com.intellij.openapi.util.text.StringUtil; @@ -42,9 +40,8 @@ public class AuthDialog extends DialogWrapper { @Nullable private static Boolean decideOnShowRememberPasswordOption(@Nullable String password, boolean rememberByDefault) { - final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); // if password saving is disabled, don't show the checkbox. - if (passwordSafe.getSettings().getProviderType().equals(PasswordSafeSettings.ProviderType.DO_NOT_STORE)) { + if (PasswordSafe.getInstance().isMemoryOnly()) { return null; } // if password is prefilled, it is expected to continue remembering it. diff --git a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java index 397c4782ad16..562c68a2f7e2 100644 --- a/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java +++ b/plugins/git4idea/src/git4idea/commands/GitHttpGuiAuthenticator.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * 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. @@ -15,10 +15,7 @@ */ package git4idea.commands; -import com.intellij.ide.passwordSafe.MasterPasswordUnavailableException; import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; import com.intellij.ide.passwordSafe.ui.PasswordSafePromptDialog; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ModalityState; @@ -172,18 +169,7 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { // save password if (myPasswordKey != null && myPassword != null) { - PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance(); - try { - passwordSafe.getMemoryProvider().storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); - if (mySaveOnDisk) { - passwordSafe.getMasterKeyProvider().storePassword(myProject, PASS_REQUESTER, myPasswordKey, myPassword); - } - } - catch (MasterPasswordUnavailableException ignored) { - } - catch (PasswordSafeException e) { - LOG.error("Couldn't remember password for " + myPasswordKey, e); - } + PasswordSafe.getInstance().setPassword(PASS_REQUESTER, myPasswordKey, myPassword, !mySaveOnDisk); } } @@ -295,14 +281,8 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { String userName = getUsername(url); String key = makeKey(url, userName); final PasswordSafe passwordSafe = PasswordSafe.getInstance(); - try { - String password = passwordSafe.getPassword(myProject, PASS_REQUESTER, key); - return new AuthData(StringUtil.notNullize(userName), password); - } - catch (PasswordSafeException e) { - LOG.info("Couldn't get the password for key [" + key + "]", e); - return null; - } + String password = passwordSafe.getPassword(PASS_REQUESTER, key); + return new AuthData(StringUtil.notNullize(userName), password); } @Nullable @@ -314,13 +294,7 @@ class GitHttpGuiAuthenticator implements GitHttpAuthenticator { public void forgetPassword(@NotNull String url) { String key = myPasswordKey != null ? myPasswordKey : makeKey(url, getUsername(url)); LOG.debug("forgetPassword. key=" + key); - try { - PasswordSafe.getInstance().removePassword(myProject, PASS_REQUESTER, key); - } - catch (PasswordSafeException e) { - LOG.info("Couldn't forget the password for " + myPasswordKey); - } + PasswordSafe.getInstance().setPassword(PASS_REQUESTER, key, null); } } - } diff --git a/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java b/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java index bf9bb2299262..41f4ced21158 100644 --- a/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java +++ b/plugins/github/src/org/jetbrains/plugins/github/util/GithubSettings.java @@ -16,14 +16,10 @@ package org.jetbrains.plugins.github.util; import com.intellij.ide.passwordSafe.PasswordSafe; -import com.intellij.ide.passwordSafe.PasswordSafeException; -import com.intellij.ide.passwordSafe.config.PasswordSafeSettings; -import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl; import com.intellij.openapi.components.PersistentStateComponent; import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.components.State; import com.intellij.openapi.components.Storage; -import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.ThreeState; import org.jetbrains.annotations.NotNull; @@ -38,7 +34,6 @@ import static org.jetbrains.plugins.github.util.GithubAuthData.AuthType; @SuppressWarnings("MethodMayBeStatic") @State(name = "GithubSettings", storages = @Storage("github_settings.xml")) public class GithubSettings implements PersistentStateComponent { - private static final Logger LOG = GithubUtil.LOG; private static final String GITHUB_SETTINGS_PASSWORD_KEY = "GITHUB_SETTINGS_PASSWORD_KEY"; private State myState = new State(); @@ -129,8 +124,7 @@ public class GithubSettings implements PersistentStateComponent