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 @@