Merge remote-tracking branch 'origin/develar/pwd'

This commit is contained in:
Vladimir Krivosheev
2016-07-29 10:19:00 +02:00
49 changed files with 1062 additions and 1532 deletions
@@ -0,0 +1,5 @@
<root>
<item name='javax.swing.JComboBox java.lang.Object getSelectedItem()'>
<annotation name='org.jetbrains.annotations.Nullable'/>
</item>
</root>
@@ -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 =
@@ -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);
}
@@ -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();
@@ -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();
}
@@ -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;
}
}
@@ -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 {
/**
* <p>Get password stored in a password safe.</p>
*
* <p><b>NB: </b>
* 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.</p>
*
* @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);
}
}
@@ -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)
}
@@ -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<T> : Promise<T>(), Getter<T> {
@Volatile private var done: Consumer<in T>? = null
@Volatile private var rejected: Consumer<in Throwable>? = 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<in T>): Promise<T> {
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<T> : Promise<T>(), Getter<T> {
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 <SUB_RESULT> then(fulfilled: Function<in T, out SUB_RESULT>): Promise<SUB_RESULT> {
@Suppress("UNCHECKED_CAST")
when (state) {
when (state.get()!!) {
Promise.State.PENDING -> {
}
Promise.State.FULFILLED -> return DonePromise<SUB_RESULT>(fulfilled.`fun`(result as T?))
@@ -105,26 +106,23 @@ open class AsyncPromise<T> : Promise<T>(), Getter<T> {
override fun notify(child: AsyncPromise<in T>) {
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 <SUB_RESULT> thenAsync(fulfilled: Function<in T, Promise<SUB_RESULT>>): Promise<SUB_RESULT> {
@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<T> : Promise<T>(), Getter<T> {
}
override fun processed(fulfilled: AsyncPromise<in T>): Promise<T> {
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<T>, rejected: Consumer<Throwable>) {
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<T> : Promise<T>(), Getter<T> {
}
}
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<T> : Promise<T>(), Getter<T> {
override fun processed(processed: Consumer<in T>): Promise<T> {
done(processed)
rejected({ error -> processed.consume(null) })
rejected { processed.consume(null) }
return this
}
private fun <T> setHandler(oldConsumer: Consumer<in T>?, newConsumer: Consumer<in T>, targetState: State): Consumer<in T>? = when (oldConsumer) {
null -> newConsumer
is CompoundConsumer<*> -> {
@Suppress("UNCHECKED_CAST")
val compoundConsumer = oldConsumer as CompoundConsumer<T>
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<T>(c1: Consumer<in T>, c2: Consumer<in T>) : Consumer<T> {
private var consumers: MutableList<Consumer<in T>>? = ArrayList()
var consumers: MutableList<Consumer<in T>>? = ArrayList()
init {
synchronized(this) {
@@ -247,23 +263,16 @@ private class CompoundConsumer<T>(c1: Consumer<in T>, c2: Consumer<in T>) : Cons
fun add(consumer: Consumer<in T>) {
synchronized(this) {
if (consumers != null) {
consumers!!.add(consumer)
consumers.let {
if (it == null) {
// it means that clearHandlers was called
}
consumers?.add(consumer)
}
}
}
}
private fun <T> setHandler(oldConsumer: Consumer<in T>?, newConsumer: Consumer<in T>) = when (oldConsumer) {
null -> newConsumer
is CompoundConsumer<*> -> {
@Suppress("UNCHECKED_CAST")
(oldConsumer as CompoundConsumer<T>).add(newConsumer)
oldConsumer
}
else -> CompoundConsumer(oldConsumer, newConsumer)
}
internal fun isObsolete(consumer: Consumer<*>?) = consumer is Obsolescent && consumer.isObsolete
inline fun <T> AsyncPromise<*>.catchError(runnable: () -> T): T? {
@@ -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<ApplicationLoadListener> 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() {
}
}
@@ -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<String, String>? = null, baseDirectory: Path = Paths.get(PathManager.getConfigPath()), var memoryOnly: Boolean = false) : PasswordSafeProvider() {
private val db = ContainerUtil.newConcurrentMap<String, String>()
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()
}
}
@@ -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
}
@@ -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();
}
/**
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.ide.passwordSafe.config.PasswordSafeOptionsPanel">
<grid id="27dc6" binding="myRoot" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="27dc6" binding="myRoot" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="509" height="269"/>
@@ -13,7 +13,7 @@
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<grid id="62956" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="62956" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -24,18 +24,9 @@
</clientProperties>
<border type="etched" title="Password storage policy"/>
<children>
<component id="496c6" class="javax.swing.JRadioButton" binding="myDoNotRememberPasswordsRadioButton" default-binding="true">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Do &amp;not remember passwords"/>
<toolTipText value="Password asked from user each time they are needed."/>
</properties>
</component>
<component id="286da" class="javax.swing.JRadioButton" binding="myRememberPasswordsUntilClosingRadioButton" default-binding="true">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Remember passwords &amp;until the application is closed"/>
@@ -44,7 +35,7 @@
</component>
<component id="4e7bb" class="javax.swing.JRadioButton" binding="mySaveOnDiskRadioButton" default-binding="true">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
@@ -56,42 +47,9 @@
</grid>
<vspacer id="57a63">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<grid id="432a0" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<clientProperties>
<BorderFactoryClass class="java.lang.String" value="com.intellij.ui.IdeBorderFactory$PlainSmallWithIndent"/>
</clientProperties>
<border type="etched" title="Disk storage protection"/>
<children>
<component id="97bd4" class="javax.swing.JButton" binding="myManagePasswordButton">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="&amp;Master Password"/>
<toolTipText value="Reset or specify a password for the password database"/>
</properties>
</component>
<hspacer id="cb744">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<component id="6fe55" class="com.intellij.ui.components.JBLabel" binding="myMasterPasswordStateLabel">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
</children>
</grid>
</children>
</grid>
<buttonGroups>
@@ -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;
}
}
/**
@@ -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<PasswordSafeSettings.State> {
/**
* The selected provider type
*/
public static final Topic<PasswordSafeSettingsListener> 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
@@ -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)
}
@@ -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<String, String>? {
if (isMasterPasswordValid(password, db)) {
return convertOldDb(password, db)
}
else {
return null
}
}
fun convertOldDb(@Suppress("DEPRECATION") db: PasswordDatabase): Map<String, String>? {
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<String, String>? = 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<String, String> {
val oldKeyB = EncryptionUtil.genPasswordKey(oldKey)
val testKey = ByteArrayWrapper(EncryptionUtil.encryptKey(oldKeyB, rawTestKey(oldKey)))
val newDb = THashMap<String, String>(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>(PasswordDatabase::class.java))
if (newDb != null && newDb.isNotEmpty()) {
FilePasswordSafeProvider(newDb).save()
}
}
}
}
catch (e: Throwable) {
LOG.warn("Cannot check old password safe DB", e)
}
}
}
@@ -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;
}
}
@@ -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
@@ -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.</p>
*
* @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);
}
@@ -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);
@@ -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("<html>Enter the current password and the new password<br>" +
"in order to change the master password.</html>");
}
@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());
}
}
@@ -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<String, Boolean> myPasswordConsumer;
public EnterPasswordComponent(@NotNull Function<String, Boolean> passwordConsumer) {
super("Enter");
myPasswordConsumer = passwordConsumer;
public EnterPasswordComponent(@NotNull MasterKeyPasswordSafe safe, @NotNull Class<?> requestor) {
super(safe, "Enter");
String requestorName = getRequestorTitle(requestor);
myPromptLabel.setText("<html><br>Master password is required to unlock the password database.<br>" +
"The password database will be unlocked during this session<br>" +
"for all subsystems.<br>" +
"<br><b>Requested by</b>: " + requestorName + "</html>");
"for all subsystems.</html>");
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
@@ -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<Ref<Object>> myKey = new PasswordSafeTimed<Ref<Object>>() {
@Override
protected Ref<Object> 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<ByteArrayWrapper, byte[]> oldDb = new HashMap<ByteArrayWrapper, byte[]>();
myDatabase.copyTo(oldDb);
HashMap<ByteArrayWrapper, byte[]> newDb = new HashMap<ByteArrayWrapper, byte[]>();
for (Map.Entry<ByteArrayWrapper, byte[]> 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, E extends Throwable> T invokeAndWait(@NotNull final ThrowableComputable<T, E> computable, @NotNull final Condition<?> expired) throws E {
if (ApplicationManager.getApplication().isDispatchThread()) {
return computable.compute();
}
final AsyncFutureResult<T> 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();
}
}
@@ -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();
}
}
}
@@ -41,7 +41,7 @@
<size top="0" left="10" bottom="10" right="0"/>
</border>
<children>
<grid id="1920e" binding="myNewPasswordPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="1920e" binding="myNewPasswordPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="1" column="0" row-span="2" col-span="2" vsize-policy="0" hsize-policy="0" anchor="0" fill="3" indent="0" use-parent-layout="true"/>
@@ -83,16 +83,6 @@
</constraints>
<properties/>
</component>
<component id="d03d2" class="javax.swing.JCheckBox" binding="myEncryptCheckBox" default-binding="true">
<constraints>
<grid row="2" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="3" anchor="4" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<selected value="true"/>
<text value="&amp;Encrypt with OS user credentials"/>
<toolTipText value="The master password will be stored in password database &#10;encrypted with user credentials using OS-specific mechanisms.&#10;If this checkbox is selected, the master password will not be asked&#10;from user, on new session."/>
</properties>
</component>
</children>
</grid>
<grid id="a4f86" binding="myPasswordPanel" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
@@ -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);
}
}
@@ -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<PasswordDatabase.State> {
/**
* The name of logger
*/
private final static Logger LOG = Logger.getInstance(PasswordDatabase.class.getName());
/**
* The password database
*/
private transient final Map<ByteArrayWrapper, byte[]> myDatabase = new HashMap<ByteArrayWrapper, byte[]>();
/**
* 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<ByteArrayWrapper, byte[]> myDatabase = new HashMap<ByteArrayWrapper, byte[]>();
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<ByteArrayWrapper, byte[]> copy) {
synchronized (myDatabase) {
copy.putAll(myDatabase);
}
}
/**
* Put all entries to the database
*
* @param copy the copy to use
*/
public void putAll(Map<ByteArrayWrapper, byte[]> 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<ByteArrayWrapper, byte[]> sorted;
String pi;
synchronized (myDatabase) {
pi = toHex(myMasterPasswordInfo);
sorted = new TreeMap<ByteArrayWrapper, byte[]>(myDatabase);
}
String[][] db = new String[2][sorted.size()];
int i = 0;
for (Map.Entry<ByteArrayWrapper, byte[]> 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<PasswordDataba
/**
* Information about master password (used in OS specific way)
*/
public String MASTER_PASSWORD_INFO = "";
public String MASTER_PASSWORD_INFO;
/**
* The password database
*/
@@ -1,68 +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;
import com.intellij.ide.passwordSafe.PasswordSafe;
import com.intellij.ide.passwordSafe.impl.PasswordSafeImpl;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.util.ui.DialogUtil;
import com.intellij.util.ui.UIUtil;
/**
* @author gregsh
*/
public class ResetPasswordComponent extends PasswordComponentBase {
private final boolean myFirstTime;
public ResetPasswordComponent(MasterKeyPasswordSafe safe, boolean firstTime) {
super(safe, firstTime ? "Setup" : "Reset");
myFirstTime = firstTime;
UIUtil.setEnabled(myPasswordPanel, false, true);
myPasswordPanel.setVisible(false);
if (firstTime) {
myNewPasswordLabel.setText(myPasswordLabel.getText());
myNewPasswordLabel.setDisplayedMnemonic(myPasswordLabel.getDisplayedMnemonic());
myNewPasswordLabel.setDisplayedMnemonicIndex(myPasswordLabel.getDisplayedMnemonicIndex());
DialogUtil.registerMnemonic(myPasswordLabel, null);
myPromptLabel.setText("<html><br>Specify the new password for the password database.<br>" +
"Leave blank to disable the master password protection.</html>");
}
else {
myPromptLabel.setText("<html><br>The password for the password database will be reset.<br>" +
"<b>All previously stored passwords will be removed!</b></html>");
}
}
@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;
}
}
@@ -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";
@@ -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
}
}
@@ -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();
}
@@ -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<String> 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();
@@ -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");
}
@@ -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
}
@@ -163,6 +163,8 @@
serviceImplementation="com.intellij.ide.passwordSafe.config.PasswordSafeSettings"/>
<applicationService serviceInterface="com.intellij.ide.passwordSafe.PasswordSafe"
serviceImplementation="com.intellij.ide.passwordSafe.impl.PasswordSafeImpl"/>
<ApplicationLoadListener implementation="com.intellij.ide.passwordSafe.masterKey.PasswordDatabaseConvertor"/>
<applicationConfigurable parentId="preferences.general" instance="com.intellij.ide.passwordSafe.config.PasswordSafeConfigurable" id="application.passwordSafe"
displayName="Passwords"/>
<applicationService serviceInterface="com.intellij.execution.process.ColoredOutputTypeRegistry"
@@ -0,0 +1,115 @@
/*
* 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
import com.intellij.ide.passwordSafe.masterKey.FilePasswordSafeProvider
import com.intellij.testFramework.RuleChain
import com.intellij.testFramework.TemporaryDirectory
import org.assertj.core.api.Assertions.assertThat
import org.junit.Rule
import org.junit.Test
import java.math.BigInteger
import java.util.*
class FilePasswordSafeProviderTest {
private val tempDirManager = TemporaryDirectory()
@Rule
@JvmField
val ruleChain = RuleChain(tempDirManager)
@Test
fun many() {
val baseDir = tempDirManager.newPath()
var provider = FilePasswordSafeProvider(baseDirectory = baseDir)
assertThat(baseDir).doesNotExist()
val random = Random()
for (i in 0..9) {
provider.setPassword(BigInteger(8 * 16, random).toString(), BigInteger(8 * 16, random).toString())
}
provider.save()
provider = FilePasswordSafeProvider(baseDirectory = baseDir)
provider.deleteFileStorage()
val pdbFile = baseDir.resolve("pdb")
val pdbPwdFile = baseDir.resolve("pdb.pwd")
val pdbPwdTmpFile = baseDir.resolve("pdb.pwd.tmp")
assertThat(pdbFile).doesNotExist()
assertThat(pdbPwdFile).doesNotExist()
assertThat(pdbPwdTmpFile).doesNotExist()
}
@Test
fun test() {
val baseDir = tempDirManager.newPath()
var provider = FilePasswordSafeProvider(baseDirectory = baseDir)
assertThat(baseDir).doesNotExist()
assertThat(provider.getPassword("foo")).isNull()
provider.setPassword("foo", "pass")
assertThat(baseDir).doesNotExist()
val pdbFile = baseDir.resolve("pdb")
val pdbPwdFile = baseDir.resolve("pdb.pwd")
val pdbPwdTmpFile = baseDir.resolve("pdb.pwd.tmp")
provider.save()
assertThat(pdbFile).isRegularFile()
assertThat(pdbPwdFile).isRegularFile()
assertThat(pdbPwdTmpFile).doesNotExist()
provider.setPassword("am", "pass2")
assertThat(provider.getPassword("foo")).isEqualTo("pass")
assertThat(provider.getPassword("am")).isEqualTo("pass2")
provider.setPassword("foo", null)
assertThat(provider.getPassword("foo")).isNull()
provider.save()
assertThat(pdbFile).isRegularFile()
assertThat(pdbPwdFile).isRegularFile()
assertThat(pdbPwdTmpFile).doesNotExist()
provider = FilePasswordSafeProvider(baseDirectory = baseDir)
assertThat(provider.getPassword("foo")).isNull()
assertThat(provider.getPassword("am")).isEqualTo("pass2")
provider.setPassword("am", null)
assertThat(provider.getPassword("am")).isNull()
provider.save()
assertThat(pdbFile).doesNotExist()
assertThat(pdbPwdFile).isRegularFile()
assertThat(pdbPwdTmpFile).doesNotExist()
provider.deleteFileStorage()
assertThat(pdbFile).doesNotExist()
assertThat(pdbPwdFile).doesNotExist()
assertThat(pdbPwdTmpFile).doesNotExist()
}
}
@@ -0,0 +1,101 @@
/*
* 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
import com.intellij.ide.passwordSafe.impl.providers.masterKey.PasswordDatabase
import com.intellij.ide.passwordSafe.masterKey.FilePasswordSafeProvider
import com.intellij.ide.passwordSafe.masterKey.convertOldDb
import com.intellij.openapi.util.JDOMUtil
import com.intellij.testFramework.ApplicationRule
import com.intellij.testFramework.runInEdtAndWait
import com.intellij.util.xmlb.XmlSerializer
import org.assertj.core.api.Assertions.assertThat
import org.junit.ClassRule
import org.junit.Test
internal class MasterPasswordMigrationTest {
companion object {
@JvmField
@ClassRule
val projectRule = ApplicationRule()
}
@Test
fun emptyPass() {
val passwordSafe = convertOldDb(getDb("""<State>
<option name="MASTER_PASSWORD_INFO" value="" />
<option name="PASSWORDS">
<array>
<array>
<option value="9a9e0048b26176cefb484b17675c09d8d4363acb16a8c2c194ddc160ea7fee1b" />
<option value="e86d036dc89ed252627ba61b6023ec0b21cbd58fbbacdbe2b530c4a553903dd8" />
<option value="05e93059f4487f4cc257133e6c54d81f53b7793965d23ca9e4b0a4960edfca33" />
<option value="41f099697425bce16e5ddd9ab89e03cc630cc4f5fa081af7c4eb934d4718d902" />
</array>
<array>
<option value="c124cdeb9a72bad8d1f3bc8037b45399" />
<option value="8962f7731a0b30862b4da5c9d9830a2cc5f5fc1648242d888b16d1668c2dd1ad" />
<option value="116f1b839d1326d6ecd52b78bb050cd1" />
<option value="6eee9deabcdc913623785094611a0bf0" />
</array>
</array>
</option>
</State>"""))
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<String, String>? = null
runInEdtAndWait {
passwordSafe = convertOldDb(getDb("""<State>
<option name="MASTER_PASSWORD_INFO" value="" />
<option name="PASSWORDS">
<array>
<array>
<option value="a3e35d4153b3ddff12629573cd3ef001bd852f33bbf7ada6988362f6f72ccab2" />
<option value="a35725ba0caaf9ea8dc6a6a001bb03ce0307f5ace094264f3e10019076e5bd3b" />
<option value="a6f02f741d8be093f6f95db718cf4d779a93ff8917e1693eb29072b4f75aade4" />
<option value="4acfac8ee7e1f0ef8865150707a911d6d1ac6164ff31d09c1ade16c8a4191568" />
</array>
<array>
<option value="b81ff8a25505f9e6db9d398dcab08774" />
<option value="d8e6e0d1ea8e5b239c4c87583fc263f8" />
<option value="05396cc1090959a5cde51670c228bb8fbc6eebb9fae479d8360e11e40642f074" />
<option value="d8e6e0d1ea8e5b239c4c87583fc263f8" />
</array>
</array>
</option>
</State>"""))
}
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
}
}
@@ -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"));
}
}
@@ -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"));
}
}
@@ -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
@@ -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.
@@ -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);
}
}
}
@@ -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<GithubSettings.State> {
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<GithubSettings.S
}
public boolean isSavePasswordMakesSense() {
final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance();
return passwordSafe.getSettings().getProviderType() == PasswordSafeSettings.ProviderType.MASTER_PASSWORD;
return !PasswordSafe.getInstance().isMemoryOnly();
}
public boolean isCloneGitUsingSsh() {
@@ -172,33 +166,11 @@ public class GithubSettings implements PersistentStateComponent<GithubSettings.S
@NotNull
private String getPassword() {
String password;
try {
password = PasswordSafe.getInstance().getPassword(null, GithubSettings.class, GITHUB_SETTINGS_PASSWORD_KEY);
}
catch (PasswordSafeException e) {
LOG.info("Couldn't get password for key [" + GITHUB_SETTINGS_PASSWORD_KEY + "]", e);
password = "";
}
return StringUtil.notNullize(password);
return StringUtil.notNullize(PasswordSafe.getInstance().getPassword(GithubSettings.class, GITHUB_SETTINGS_PASSWORD_KEY));
}
private void setPassword(@NotNull String password, boolean rememberPassword) {
try {
if (rememberPassword) {
PasswordSafe.getInstance().storePassword(null, GithubSettings.class, GITHUB_SETTINGS_PASSWORD_KEY, password);
}
else {
final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance();
if (passwordSafe.getSettings().getProviderType() != PasswordSafeSettings.ProviderType.DO_NOT_STORE) {
passwordSafe.getMemoryProvider().storePassword(null, GithubSettings.class, GITHUB_SETTINGS_PASSWORD_KEY, password);
}
}
}
catch (PasswordSafeException e) {
LOG.info("Couldn't set password for key [" + GITHUB_SETTINGS_PASSWORD_KEY + "]", e);
}
PasswordSafe.getInstance().setPassword(GithubSettings.class, GITHUB_SETTINGS_PASSWORD_KEY, password, !rememberPassword);
}
private static boolean isValidGitAuth(@NotNull GithubAuthData auth) {
@@ -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.
@@ -160,7 +160,7 @@ public class AppEngineCloudConfigurable extends RemoteServerConfigurable impleme
myConfiguration.setOAuth2(isOAuth2());
String password = getPassword();
if (myRememberPasswordCheckBox.isSelected() && !StringUtil.isEmpty(email) && !password.isEmpty()) {
AppEngineAccountDialog.storePassword(email, password, myProject);
AppEngineAccountDialog.storePassword(email, password);
myConfiguration.setPasswordStored(true);
}
else {
@@ -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.
@@ -19,8 +19,6 @@ import com.intellij.appengine.cloud.AppEngineAuthData;
import com.intellij.appengine.cloud.AppEngineCloudConfigurable;
import com.intellij.appengine.cloud.AppEngineServerConfiguration;
import com.intellij.ide.passwordSafe.PasswordSafe;
import com.intellij.ide.passwordSafe.PasswordSafeException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
@@ -31,7 +29,6 @@ import org.jetbrains.annotations.Nullable;
* @author nik
*/
public class AppEngineAccountDialog {
private static final Logger LOG = Logger.getInstance(AppEngineAccountDialog.class);
private static final String PASSWORD_KEY = "GOOGLE_APP_ENGINE_PASSWORD";
@Nullable
@@ -42,7 +39,7 @@ public class AppEngineAccountDialog {
String email = configuration.getEmail();
if (!StringUtil.isEmpty(email) && configuration.isPasswordStored()) {
String password = getStoredPassword(project, email);
String password = getStoredPassword(email);
if (!StringUtil.isEmpty(password)) {
return AppEngineAuthData.login(email, password);
}
@@ -59,13 +56,8 @@ public class AppEngineAccountDialog {
return AppEngineAuthData.login(configurable.getEmail(), configurable.getPassword());
}
public static void storePassword(@NotNull String email, @NotNull String password, @Nullable Project project) {
try {
PasswordSafe.getInstance().storePassword(project, AppEngineAccountDialog.class, getPasswordKey(email), password);
}
catch (PasswordSafeException e) {
LOG.info(e);
}
public static void storePassword(@NotNull String email, @NotNull String password) {
PasswordSafe.getInstance().setPassword(AppEngineAccountDialog.class, getPasswordKey(email), password);
}
private static String getPasswordKey(String email) {
@@ -73,17 +65,10 @@ public class AppEngineAccountDialog {
}
@Nullable
private static String getStoredPassword(Project project, String email) {
private static String getStoredPassword(String email) {
if (StringUtil.isEmpty(email)) {
return null;
}
try {
return PasswordSafe.getInstance().getPassword(project, AppEngineAccountDialog.class, getPasswordKey(email));
}
catch (PasswordSafeException e) {
LOG.info(e);
return null;
}
return PasswordSafe.getInstance().getPassword(AppEngineAccountDialog.class, getPasswordKey(email));
}
}
@@ -13,12 +13,8 @@
package org.zmlx.hg4idea.execution;
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.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFileManager;
@@ -33,9 +29,6 @@ import org.zmlx.hg4idea.HgVcsMessages;
* Base class for any command interacting with a remote repository and which needs authentication.
*/
class HgCommandAuthenticator {
private static final Logger LOG = Logger.getInstance(HgCommandAuthenticator.class.getName());
private GetPasswordRunnable myGetPassword;
private final Project myProject;
private boolean myForceAuthorization;
@@ -51,25 +44,12 @@ class HgCommandAuthenticator {
public void saveCredentials() {
if (myGetPassword == null) return;
// if checkbox is selected, save on disk. Otherwise in memory. Don't read password safe settings.
final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance();
final String url = VirtualFileManager.extractPath(myGetPassword.getURL());
final String key = keyForUrlAndLogin(url, myGetPassword.getUserName());
try {
if (myGetPassword.isRememberPassword()) {
PasswordSafe.getInstance().storePassword(myProject, HgCommandAuthenticator.class, key, myGetPassword.getPassword());
}
else if (passwordSafe.getSettings().getProviderType() != PasswordSafeSettings.ProviderType.DO_NOT_STORE) {
passwordSafe.getMemoryProvider().storePassword(myProject, HgCommandAuthenticator.class, key, myGetPassword.getPassword());
}
final HgVcs vcs = HgVcs.getInstance(myProject);
if (vcs != null) {
vcs.getGlobalSettings().addRememberedUrl(url, myGetPassword.getUserName());
}
}
catch (PasswordSafeException e) {
LOG.info("Couldn't store the password for key [" + key + "]", e);
PasswordSafe.getInstance().setPassword(HgCommandAuthenticator.class, key, myGetPassword.getPassword(), !myGetPassword.isRememberPassword());
final HgVcs vcs = HgVcs.getInstance(myProject);
if (vcs != null) {
vcs.getGlobalSettings().addRememberedUrl(url, myGetPassword.getUserName());
}
}
@@ -89,8 +69,6 @@ class HgCommandAuthenticator {
}
private static class GetPasswordRunnable implements Runnable {
private static final Logger LOG = Logger.getInstance(GetPasswordRunnable.class.getName());
private String myUserName;
private String myPassword;
private Project myProject;
@@ -136,19 +114,7 @@ class HgCommandAuthenticator {
String password = null;
if (!StringUtil.isEmptyOrSpaces(login) && myURL != null) {
// if we've logged in with this login, search for password
final String key = keyForUrlAndLogin(myURL, login);
try {
final PasswordSafeImpl passwordSafe = (PasswordSafeImpl)PasswordSafe.getInstance();
if (mySilent) {
password = passwordSafe.getMemoryProvider().getPassword(myProject, HgCommandAuthenticator.class, key);
}
else {
password = passwordSafe.getPassword(myProject, HgCommandAuthenticator.class, key);
}
}
catch (PasswordSafeException e) {
LOG.info("Couldn't get password for key [" + key + "]", e);
}
password = PasswordSafe.getInstance().getPassword(HgCommandAuthenticator.class, keyForUrlAndLogin(myURL, login));
}
// don't show dialog if we don't have to (both fields are known) except force authorization required
@@ -1,8 +1,6 @@
package com.jetbrains.edu.learning.stepic;
import com.intellij.ide.passwordSafe.PasswordSafe;
import com.intellij.ide.passwordSafe.PasswordSafeException;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.xmlb.annotations.Transient;
import com.jetbrains.edu.learning.StudyTaskManager;
@@ -10,7 +8,6 @@ import org.jetbrains.annotations.NotNull;
public class StepicUser {
private static final String STEPIC_SETTINGS_PASSWORD_KEY = "STEPIC_SETTINGS_PASSWORD_KEY";
private static final Logger LOG = Logger.getInstance(StepicUser.class);
private int id = -1;
private String myFirstName = "";
private String myLastName = "";
@@ -64,28 +61,13 @@ public class StepicUser {
public String getPassword() {
final String login = getEmail();
if (StringUtil.isEmptyOrSpaces(login)) return "";
String password;
try {
password = PasswordSafe.getInstance().getPassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + login);
}
catch (PasswordSafeException e) {
LOG.info("Couldn't get password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + "]", e);
password = "";
}
return StringUtil.notNullize(password);
return StringUtil.notNullize(PasswordSafe.getInstance().getPassword(StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + login));
}
@Transient
public void setPassword(@NotNull final String password) {
if (password.isEmpty()) return;
try {
PasswordSafe.getInstance().storePassword(null, StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + getEmail(), password);
}
catch (PasswordSafeException e) {
LOG.info("Couldn't set password for key [" + STEPIC_SETTINGS_PASSWORD_KEY + getEmail() + "]", e);
}
PasswordSafe.getInstance().setPassword(StudyTaskManager.class, STEPIC_SETTINGS_PASSWORD_KEY + getEmail(), password);
}
@NotNull