mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Merge remote-tracking branch 'origin/develar/pwd'
This commit is contained in:
@@ -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
|
||||
}
|
||||
+19
-9
@@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+5
-47
@@ -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 &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 &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="&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>
|
||||
|
||||
+5
-51
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+19
-38
@@ -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
|
||||
|
||||
+6
-9
@@ -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;
|
||||
}
|
||||
}
|
||||
-4
@@ -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
-21
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
+5
-12
@@ -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);
|
||||
|
||||
-43
@@ -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());
|
||||
}
|
||||
}
|
||||
+22
-8
@@ -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
|
||||
|
||||
-335
@@ -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();
|
||||
}
|
||||
}
|
||||
+13
-58
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-11
@@ -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="&Encrypt with OS user credentials"/>
|
||||
<toolTipText value="The master password will be stored in password database encrypted with user credentials using OS-specific mechanisms. If this checkbox is selected, the master password will not be asked 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">
|
||||
|
||||
+4
-32
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+28
-193
@@ -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
|
||||
*/
|
||||
|
||||
-68
@@ -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;
|
||||
}
|
||||
}
|
||||
+1
-7
@@ -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";
|
||||
|
||||
-50
@@ -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
|
||||
}
|
||||
}
|
||||
+13
-27
@@ -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();
|
||||
}
|
||||
|
||||
+11
-44
@@ -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();
|
||||
|
||||
+12
-2
@@ -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"
|
||||
|
||||
+115
@@ -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()
|
||||
}
|
||||
}
|
||||
+101
@@ -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
|
||||
}
|
||||
}
|
||||
-48
@@ -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"));
|
||||
}
|
||||
}
|
||||
-40
@@ -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) {
|
||||
|
||||
+2
-2
@@ -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 {
|
||||
|
||||
+6
-21
@@ -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
|
||||
|
||||
+2
-20
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user