mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
thread safe KdbxGroup — group
This commit is contained in:
@@ -131,7 +131,7 @@ internal class FileCredentialStore(keyToValue: Map<CredentialAttributes, Credent
|
||||
db.rootGroup.getGroup(GROUP_NAME)?.removeEntry(attributes.serviceName, attributes.accountName)
|
||||
}
|
||||
else {
|
||||
db.rootGroup.getOrCreateGroup(GROUP_NAME).getOrCreateEntry(attributes.serviceName, attributes.accountName).password = credentials.password
|
||||
db.rootGroup.getOrCreateGroup(GROUP_NAME).getOrCreateEntry(attributes.serviceName, attributes.accountName ?: credentials.user).password = credentials.password
|
||||
}
|
||||
|
||||
if (db.isDirty) {
|
||||
|
||||
@@ -18,7 +18,6 @@ package com.intellij.credentialStore.kdbx
|
||||
import com.intellij.util.element
|
||||
import com.intellij.util.getOrCreate
|
||||
import org.jdom.Element
|
||||
import java.time.format.DateTimeParseException
|
||||
import java.util.*
|
||||
import kotlin.reflect.KProperty
|
||||
|
||||
@@ -48,22 +47,14 @@ class KdbxEntry(val element: Element, internal val database: KeePassDatabase) {
|
||||
}
|
||||
}
|
||||
|
||||
val parent: KdbxGroup?
|
||||
get() = element.parentElement?.let { KdbxGroup(it, database) }
|
||||
|
||||
val uuid: UUID
|
||||
get() = uuidFromBase64(element.getChildText(UUID_ELEMENT_NAME)!!)
|
||||
|
||||
var userName: String? by PropertyDelegate("UserName")
|
||||
|
||||
var password: String? by PropertyDelegate("Password")
|
||||
|
||||
var url: String? by PropertyDelegate("URL")
|
||||
|
||||
var title: String? by PropertyDelegate("Title")
|
||||
|
||||
var notes: String? by PropertyDelegate("Notes")
|
||||
|
||||
var icon: Icon?
|
||||
get() = DomIconWrapper(element.getChild(ICON_ELEMENT_NAME)!!)
|
||||
set(value) {
|
||||
@@ -72,30 +63,6 @@ class KdbxEntry(val element: Element, internal val database: KeePassDatabase) {
|
||||
database.isDirty = true
|
||||
}
|
||||
|
||||
val lastAccessTime: Date
|
||||
get() = getTime(LAST_ACCESS_TIME_ELEMENT_NAME)
|
||||
|
||||
val creationTime: Date
|
||||
get() = getTime(CREATION_TIME_ELEMENT_NAME)
|
||||
|
||||
val expiryTime: Date
|
||||
get() = getTime(EXPIRY_TIME_ELEMENT_NAME)
|
||||
|
||||
val lastModificationTime: Date
|
||||
get() = getTime(LAST_MODIFICATION_TIME_ELEMENT_NAME)
|
||||
|
||||
private fun getTime(name: String): Date {
|
||||
element.getChildText(name)?.let {
|
||||
try {
|
||||
dateFormatter.parse(it)
|
||||
}
|
||||
catch (e: DateTimeParseException) {
|
||||
return Date(0)
|
||||
}
|
||||
}
|
||||
return Date(0)
|
||||
}
|
||||
|
||||
private fun getPropertyContainer(name: String): Element? {
|
||||
for (element in element.getChildren("String")) {
|
||||
if (element.getChildText("Key") == name) {
|
||||
@@ -122,18 +89,6 @@ class KdbxEntry(val element: Element, internal val database: KeePassDatabase) {
|
||||
result = 31 * result + database.hashCode()
|
||||
return result
|
||||
}
|
||||
|
||||
val path: String
|
||||
get() {
|
||||
val parent = this.parent
|
||||
var result = ""
|
||||
if (parent != null) {
|
||||
result = parent.path
|
||||
}
|
||||
return result + title
|
||||
}
|
||||
|
||||
override fun toString() = this.path
|
||||
}
|
||||
|
||||
private class PropertyDelegate(private val name: String) {
|
||||
|
||||
@@ -1,55 +1,79 @@
|
||||
package com.intellij.credentialStore.kdbx
|
||||
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.containers.ContainerUtil
|
||||
import com.intellij.util.containers.Stack
|
||||
import com.intellij.util.get
|
||||
import com.intellij.util.getOrCreate
|
||||
import org.jdom.Element
|
||||
import java.util.*
|
||||
import org.jdom.filter.ElementFilter
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
|
||||
class KdbxGroup(private val element: Element, private val database: KeePassDatabase) {
|
||||
val isRootGroup: Boolean
|
||||
get() = parent != null && element.parentElement?.name == "Root"
|
||||
|
||||
var name: String
|
||||
get() = element.getChildText(NAME_ELEMENT_NAME)
|
||||
class KdbxGroup(private val element: Element, private val database: KeePassDatabase, private var parent: KdbxGroup?) {
|
||||
@Volatile var name = element.getChildText(NAME_ELEMENT_NAME) ?: "Unnamed"
|
||||
set(value) {
|
||||
element.getOrCreate(NAME_ELEMENT_NAME).text = value
|
||||
database.isDirty = true
|
||||
if (field != value) {
|
||||
field = value
|
||||
database.isDirty = true
|
||||
}
|
||||
}
|
||||
|
||||
val uuid: UUID
|
||||
get() = uuidFromBase64(element.getChildText(UUID_ELEMENT_NAME))
|
||||
private val subGroups: MutableList<KdbxGroup>
|
||||
|
||||
@Suppress("ConvertLambdaToReference")
|
||||
var icon: Icon?
|
||||
get() = element.getChild(ICON_ELEMENT_NAME)?.let { DomIconWrapper(it) }
|
||||
set(value) {
|
||||
element.getOrCreate(ICON_ELEMENT_NAME).text = value!!.index.toString()
|
||||
database.isDirty = true
|
||||
private @Volatile var locationChanged = element.get("Times")?.get("LocationChanged")?.text?.let(::parseTime) ?: 0
|
||||
|
||||
init {
|
||||
locationChanged = element.get("Times")?.get("LocationChanged")?.text?.let(::parseTime) ?: 0
|
||||
|
||||
val groups = SmartList<KdbxGroup>()
|
||||
val groupIterator = element.getContent(ElementFilter(GROUP_ELEMENT_NAME)).iterator()
|
||||
while (groupIterator.hasNext()) {
|
||||
val child = groupIterator.next()
|
||||
groups.add(KdbxGroup(child, database, this))
|
||||
groupIterator.remove()
|
||||
}
|
||||
subGroups = ContainerUtil.createLockFreeCopyOnWriteList<KdbxGroup>(groups)
|
||||
}
|
||||
|
||||
fun toXml(): Element {
|
||||
val element = element.clone()
|
||||
element.getOrCreate(NAME_ELEMENT_NAME).text = name
|
||||
|
||||
val locationChangedElement = element.getOrCreate("Times").getOrCreate("LocationChanged")
|
||||
if (locationChanged == 0L) {
|
||||
element.get("Times")?.get("CreationTime")?.text?.let {
|
||||
locationChangedElement.text = it
|
||||
}
|
||||
}
|
||||
else {
|
||||
locationChangedElement.text = Instant.ofEpochMilli(locationChanged).atZone(ZoneOffset.UTC).format(dateFormatter)
|
||||
}
|
||||
|
||||
val parent: KdbxGroup?
|
||||
get() {
|
||||
val parent = element.parentElement ?: return null
|
||||
return if (isRootGroup) null else KdbxGroup(parent, database)
|
||||
for (group in subGroups) {
|
||||
element.addContent(group.toXml())
|
||||
}
|
||||
return element
|
||||
}
|
||||
|
||||
fun addGroup(group: KdbxGroup): KdbxGroup {
|
||||
if (group.isRootGroup) {
|
||||
if (group == database.rootGroup) {
|
||||
throw IllegalStateException("Cannot set root group as child of another group")
|
||||
}
|
||||
|
||||
// skip if this is a new group with no parent
|
||||
group.parent?.removeGroup(group)
|
||||
element.addContent(group.element)
|
||||
subGroups.add(group)
|
||||
group.locationChanged = LocalDateTime.now(ZoneOffset.UTC).toEpochSecond(ZoneOffset.UTC)
|
||||
|
||||
group.element.getOrCreate("Times").getOrCreate("LocationChanged").text = formattedNow()
|
||||
database.isDirty = true
|
||||
return group
|
||||
}
|
||||
|
||||
fun removeGroup(group: KdbxGroup): KdbxGroup {
|
||||
element.removeContent(group.element)
|
||||
database.isDirty = true
|
||||
if (subGroups.remove(group)) {
|
||||
database.isDirty = true
|
||||
}
|
||||
return group
|
||||
}
|
||||
|
||||
@@ -57,25 +81,20 @@ class KdbxGroup(private val element: Element, private val database: KeePassDatab
|
||||
getGroup(name)?.let { removeGroup(it) }
|
||||
}
|
||||
|
||||
fun getGroup(name: String) = getGroup { it.name == name }
|
||||
fun getGroup(name: String) = subGroups.firstOrNull { it.name == name }
|
||||
|
||||
fun getOrCreateGroup(name: String) = getGroup(name) ?: createGroup(name)
|
||||
|
||||
fun createGroup(name: String) = addGroup(database.createGroup(name))
|
||||
fun createGroup(name: String): KdbxGroup {
|
||||
val result = createGroup(database, this)
|
||||
result.name = name
|
||||
addGroup(result)
|
||||
return result
|
||||
}
|
||||
|
||||
val entries: List<KdbxEntry>
|
||||
get() = element.getChildren(ENTRY_ELEMENT_NAME).map { KdbxEntry(it, database) }
|
||||
|
||||
private inline fun getGroup(matcher: (KdbxGroup) -> Boolean): KdbxGroup? {
|
||||
for (groupElement in element.getChildren(GROUP_ELEMENT_NAME)) {
|
||||
val item = KdbxGroup(groupElement, database)
|
||||
if (matcher(item)) {
|
||||
return item
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun getEntry(matcher: (entry: KdbxEntry) -> Boolean): KdbxEntry? {
|
||||
for (entryElement in element.getChildren(ENTRY_ELEMENT_NAME)) {
|
||||
val entry = KdbxEntry(entryElement, database)
|
||||
@@ -147,4 +166,26 @@ class KdbxGroup(private val element: Element, private val database: KeePassDatab
|
||||
result = 31 * result + database.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun createGroup(db: KeePassDatabase, parent: KdbxGroup?): KdbxGroup {
|
||||
val element = Element(GROUP_ELEMENT_NAME)
|
||||
ensureElements(element, mandatoryGroupElements)
|
||||
val result = KdbxGroup(element, db, parent)
|
||||
return result
|
||||
}
|
||||
|
||||
private const val NOTES_ELEMENT_NAME = "Notes"
|
||||
|
||||
private val mandatoryGroupElements: Map<String, ValueCreator> = linkedMapOf (
|
||||
UUID_ELEMENT_NAME to UuidValueCreator(),
|
||||
NOTES_ELEMENT_NAME to ConstantValueCreator(""),
|
||||
ICON_ELEMENT_NAME to ConstantValueCreator("0"),
|
||||
CREATION_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
LAST_MODIFICATION_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
LAST_ACCESS_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
EXPIRY_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
EXPIRES_ELEMENT_NAME to ConstantValueCreator("False"),
|
||||
USAGE_COUNT_ELEMENT_NAME to ConstantValueCreator("0"),
|
||||
LOCATION_CHANGED to DateValueCreator()
|
||||
)
|
||||
@@ -1,29 +1,32 @@
|
||||
package com.intellij.credentialStore.kdbx
|
||||
|
||||
import com.intellij.util.get
|
||||
import com.intellij.util.getOrCreate
|
||||
import org.jdom.Element
|
||||
import org.jdom.xpath.XPath
|
||||
import org.linguafranca.pwdb.kdbx.KdbxSerializer
|
||||
import java.io.OutputStream
|
||||
import java.nio.ByteBuffer
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneOffset
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.*
|
||||
|
||||
private const val LOCATION_CHANGED = "Times/LocationChanged"
|
||||
private const val USAGE_COUNT_ELEMENT_NAME = "Times/UsageCount"
|
||||
private const val EXPIRES_ELEMENT_NAME = "Times/Expires"
|
||||
private const val TIMES_ELEMENT_NAME = "Times"
|
||||
internal const val LOCATION_CHANGED = "Times/LocationChanged"
|
||||
internal const val USAGE_COUNT_ELEMENT_NAME = "Times/UsageCount"
|
||||
internal const val EXPIRES_ELEMENT_NAME = "Times/Expires"
|
||||
internal const val GROUP_ELEMENT_NAME = "Group"
|
||||
internal const val ENTRY_ELEMENT_NAME = "Entry"
|
||||
internal const val ICON_ELEMENT_NAME = "IconID"
|
||||
internal const val UUID_ELEMENT_NAME = "UUID"
|
||||
internal const val NAME_ELEMENT_NAME = "Name"
|
||||
private const val NOTES_ELEMENT_NAME = "Notes"
|
||||
internal const val LAST_MODIFICATION_TIME_ELEMENT_NAME = "Times/LastModificationTime"
|
||||
internal const val CREATION_TIME_ELEMENT_NAME = "Times/CreationTime"
|
||||
internal const val LAST_ACCESS_TIME_ELEMENT_NAME = "Times/LastAccessTime"
|
||||
internal const val EXPIRY_TIME_ELEMENT_NAME = "Times/ExpiryTime"
|
||||
|
||||
private const val ROOT_ELEMENT_NAME = "Root"
|
||||
|
||||
internal var dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'")
|
||||
|
||||
class KeePassDatabase(private val rootElement: Element = createEmptyDatabase()) {
|
||||
@@ -33,40 +36,32 @@ class KeePassDatabase(private val rootElement: Element = createEmptyDatabase())
|
||||
@Volatile var isDirty: Boolean = false
|
||||
internal set
|
||||
|
||||
var name: String?
|
||||
get() = dbMeta.getChildText("DatabaseName")
|
||||
set(name) {
|
||||
dbMeta.getOrCreate("DatabaseName").text = name
|
||||
dbMeta.getOrCreate("DatabaseNameChanged").text = formattedNow()
|
||||
isDirty = true
|
||||
val rootGroup: KdbxGroup
|
||||
|
||||
init {
|
||||
val rootElement = rootElement.get(ROOT_ELEMENT_NAME)
|
||||
val groupElement = rootElement?.get("Group")
|
||||
if (groupElement == null) {
|
||||
rootGroup = createGroup(this, null)
|
||||
rootGroup.name = ROOT_ELEMENT_NAME
|
||||
}
|
||||
else {
|
||||
rootElement!!.removeChild("Group")
|
||||
rootGroup = KdbxGroup(groupElement, this, null)
|
||||
}
|
||||
}
|
||||
|
||||
fun save(credentials: KeePassCredentials, outputStream: OutputStream) {
|
||||
KdbxStreamFormat().save(rootElement, credentials, outputStream)
|
||||
val element = rootElement.clone()
|
||||
element.getOrCreate(ROOT_ELEMENT_NAME).addContent(rootGroup.toXml())
|
||||
val kdbxHeader = KdbxHeader()
|
||||
KdbxSerializer.createEncryptedOutputStream(credentials, kdbxHeader, outputStream).use {
|
||||
element.getOrCreate("HeaderHash").text = Base64.getEncoder().encodeToString(kdbxHeader.headerHash)
|
||||
save(element, it, Salsa20Encryption(kdbxHeader.protectedStreamKey))
|
||||
}
|
||||
isDirty = false
|
||||
}
|
||||
|
||||
fun save(streamFormat: KdbxStreamFormat, credentials: KeePassCredentials, outputStream: OutputStream) {
|
||||
streamFormat.save(rootElement, credentials, outputStream)
|
||||
isDirty = false
|
||||
}
|
||||
|
||||
// fun shouldProtect(name: String): Boolean {
|
||||
// val protectionElement = DomHelper.getElement("MemoryProtection/Protect$name", dbMeta, false) ?: return false
|
||||
// return protectionElement.textContent.toBoolean()
|
||||
// }
|
||||
|
||||
val rootGroup: KdbxGroup
|
||||
get() = KdbxGroup(rootElement.getChild("Root").getChild("Group"), this)
|
||||
|
||||
fun createGroup(name: String): KdbxGroup {
|
||||
val element = Element(GROUP_ELEMENT_NAME)
|
||||
ensureElements(element, mandatoryGroupElements)
|
||||
val result = KdbxGroup(element, this)
|
||||
result.name = name
|
||||
return result
|
||||
}
|
||||
|
||||
fun createEntry(title: String): KdbxEntry {
|
||||
val element = Element(ENTRY_ELEMENT_NAME)
|
||||
ensureElements(element, mandatoryEntryElements)
|
||||
@@ -110,12 +105,11 @@ class DomIconWrapper(private val element: Element) : Icon {
|
||||
override fun hashCode() = index
|
||||
}
|
||||
|
||||
private val mandatoryEntryElements: Map<String, ValueCreator> = mapOf (
|
||||
private val mandatoryEntryElements: Map<String, ValueCreator> = linkedMapOf (
|
||||
UUID_ELEMENT_NAME to UuidValueCreator(),
|
||||
ICON_ELEMENT_NAME to ConstantValueCreator("0"),
|
||||
TIMES_ELEMENT_NAME to ConstantValueCreator(""),
|
||||
LAST_MODIFICATION_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
CREATION_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
LAST_MODIFICATION_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
LAST_ACCESS_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
EXPIRY_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
EXPIRES_ELEMENT_NAME to ConstantValueCreator("False"),
|
||||
@@ -123,22 +117,7 @@ private val mandatoryEntryElements: Map<String, ValueCreator> = mapOf (
|
||||
LOCATION_CHANGED to DateValueCreator()
|
||||
)
|
||||
|
||||
private val mandatoryGroupElements: Map<String, ValueCreator> = mapOf (
|
||||
UUID_ELEMENT_NAME to UuidValueCreator(),
|
||||
NAME_ELEMENT_NAME to ConstantValueCreator(""),
|
||||
NOTES_ELEMENT_NAME to ConstantValueCreator(""),
|
||||
ICON_ELEMENT_NAME to ConstantValueCreator("0"),
|
||||
TIMES_ELEMENT_NAME to ConstantValueCreator(""),
|
||||
LAST_MODIFICATION_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
CREATION_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
LAST_ACCESS_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
EXPIRY_TIME_ELEMENT_NAME to DateValueCreator(),
|
||||
EXPIRES_ELEMENT_NAME to ConstantValueCreator("False"),
|
||||
USAGE_COUNT_ELEMENT_NAME to ConstantValueCreator("0"),
|
||||
LOCATION_CHANGED to DateValueCreator()
|
||||
)
|
||||
|
||||
private fun ensureElements(element: Element, childElements: Map<String, ValueCreator>) {
|
||||
internal fun ensureElements(element: Element, childElements: Map<String, ValueCreator>) {
|
||||
for ((elementPath, value) in childElements) {
|
||||
var result = XPath.newInstance(elementPath).selectSingleNode(element)
|
||||
if (result == null) {
|
||||
@@ -156,7 +135,7 @@ private fun createHierarchically(elementPath: String, startElement: Element): El
|
||||
return currentElement
|
||||
}
|
||||
|
||||
internal fun formattedNow() = LocalDateTime.now().format(dateFormatter)
|
||||
internal fun formattedNow() = LocalDateTime.now(ZoneOffset.UTC).format(dateFormatter)
|
||||
|
||||
interface ValueCreator {
|
||||
val value: String
|
||||
|
||||
@@ -17,7 +17,6 @@ package com.intellij.credentialStore.kdbx
|
||||
|
||||
import com.intellij.openapi.util.JDOMUtil
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.getOrCreate
|
||||
import com.intellij.util.inputStream
|
||||
import com.intellij.util.loadElement
|
||||
import org.bouncycastle.crypto.engines.Salsa20Engine
|
||||
@@ -29,6 +28,8 @@ import java.io.InputStream
|
||||
import java.io.OutputStream
|
||||
import java.nio.file.Path
|
||||
import java.security.MessageDigest
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeParseException
|
||||
import java.util.*
|
||||
import javax.xml.bind.DatatypeConverter
|
||||
|
||||
@@ -57,18 +58,9 @@ class KdbxStreamFormat {
|
||||
return load(it, encryption)
|
||||
}
|
||||
}
|
||||
|
||||
fun save(element: Element, credentials: KeePassCredentials, outputStream: OutputStream) {
|
||||
val kdbxHeader = KdbxHeader()
|
||||
KdbxSerializer.createEncryptedOutputStream(credentials, kdbxHeader, outputStream).use {
|
||||
val rootElement = element.clone()
|
||||
rootElement.getOrCreate("HeaderHash").text = Base64.getEncoder().encodeToString(kdbxHeader.headerHash)
|
||||
save(rootElement, it, Salsa20Encryption(kdbxHeader.protectedStreamKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun save(rootElement: Element, outputStream: OutputStream, encryption: KdbxEncryption) {
|
||||
internal fun save(rootElement: Element, outputStream: OutputStream, encryption: KdbxEncryption) {
|
||||
val meta = rootElement.getChild("Meta")?.getChild("MemoryProtection")
|
||||
if (meta != null) {
|
||||
val propertiesToProtect = SmartList<String>()
|
||||
@@ -125,67 +117,44 @@ private fun processEntries(groupElement: Element, processor: (container: Element
|
||||
internal fun createEmptyDatabase(): Element {
|
||||
val creationDate = formattedNow()
|
||||
return loadElement("""<KeePassFile>
|
||||
<Meta>
|
||||
<Generator>IJ</Generator>
|
||||
<HeaderHash></HeaderHash>
|
||||
<DatabaseName>New Database</DatabaseName>
|
||||
<DatabaseNameChanged>${creationDate}</DatabaseNameChanged>
|
||||
<DatabaseDescription>Empty Database</DatabaseDescription>
|
||||
<DatabaseDescriptionChanged>${creationDate}</DatabaseDescriptionChanged>
|
||||
<DefaultUserName/>
|
||||
<DefaultUserNameChanged>${creationDate}</DefaultUserNameChanged>
|
||||
<MaintenanceHistoryDays>365</MaintenanceHistoryDays>
|
||||
<Color/>
|
||||
<MasterKeyChanged>${creationDate}</MasterKeyChanged>
|
||||
<MasterKeyChangeRec>-1</MasterKeyChangeRec>
|
||||
<MasterKeyChangeForce>-1</MasterKeyChangeForce>
|
||||
<MemoryProtection>
|
||||
<ProtectTitle>False</ProtectTitle>
|
||||
<ProtectUserName>False</ProtectUserName>
|
||||
<ProtectPassword>True</ProtectPassword>
|
||||
<ProtectURL>False</ProtectURL>
|
||||
<ProtectNotes>False</ProtectNotes>
|
||||
</MemoryProtection>
|
||||
<CustomIcons/>
|
||||
<RecycleBinEnabled>True</RecycleBinEnabled>
|
||||
<RecycleBinUUID>AAAAAAAAAAAAAAAAAAAAAA==</RecycleBinUUID>
|
||||
<RecycleBinChanged>${creationDate}</RecycleBinChanged>
|
||||
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup>
|
||||
<EntryTemplatesGroupChanged>${creationDate}</EntryTemplatesGroupChanged>
|
||||
<LastSelectedGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastSelectedGroup>
|
||||
<LastTopVisibleGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleGroup>
|
||||
<HistoryMaxItems>10</HistoryMaxItems>
|
||||
<HistoryMaxSize>6291456</HistoryMaxSize>
|
||||
<Binaries/>
|
||||
<CustomData/>
|
||||
</Meta>
|
||||
<Root>
|
||||
<Group>
|
||||
<UUID>${base64RandomUuid()}</UUID>
|
||||
<Name>Root</Name>
|
||||
<Notes/>
|
||||
<IconID>48</IconID>
|
||||
<Times>
|
||||
<LastModificationTime>${creationDate}</LastModificationTime>
|
||||
<CreationTime>${creationDate}</CreationTime>
|
||||
<LastAccessTime>${creationDate}</LastAccessTime>
|
||||
<ExpiryTime>${creationDate}</ExpiryTime>
|
||||
<Expires>False</Expires>
|
||||
<UsageCount>0</UsageCount>
|
||||
<LocationChanged>${creationDate}</LocationChanged>
|
||||
</Times>
|
||||
<IsExpanded>True</IsExpanded>
|
||||
<DefaultAutoTypeSequence/>
|
||||
<EnableAutoType>True</EnableAutoType>
|
||||
<EnableSearching>True</EnableSearching>
|
||||
<LastTopVisibleEntry>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleEntry>
|
||||
</Group>
|
||||
<DeletedObjects/>
|
||||
</Root>
|
||||
<Meta>
|
||||
<Generator>IJ</Generator>
|
||||
<HeaderHash></HeaderHash>
|
||||
<DatabaseName>New Database</DatabaseName>
|
||||
<DatabaseNameChanged>${creationDate}</DatabaseNameChanged>
|
||||
<DatabaseDescription>Empty Database</DatabaseDescription>
|
||||
<DatabaseDescriptionChanged>${creationDate}</DatabaseDescriptionChanged>
|
||||
<DefaultUserName/>
|
||||
<DefaultUserNameChanged>${creationDate}</DefaultUserNameChanged>
|
||||
<MaintenanceHistoryDays>365</MaintenanceHistoryDays>
|
||||
<Color/>
|
||||
<MasterKeyChanged>${creationDate}</MasterKeyChanged>
|
||||
<MasterKeyChangeRec>-1</MasterKeyChangeRec>
|
||||
<MasterKeyChangeForce>-1</MasterKeyChangeForce>
|
||||
<MemoryProtection>
|
||||
<ProtectTitle>False</ProtectTitle>
|
||||
<ProtectUserName>False</ProtectUserName>
|
||||
<ProtectPassword>True</ProtectPassword>
|
||||
<ProtectURL>False</ProtectURL>
|
||||
<ProtectNotes>False</ProtectNotes>
|
||||
</MemoryProtection>
|
||||
<CustomIcons/>
|
||||
<RecycleBinEnabled>True</RecycleBinEnabled>
|
||||
<RecycleBinUUID>AAAAAAAAAAAAAAAAAAAAAA==</RecycleBinUUID>
|
||||
<RecycleBinChanged>${creationDate}</RecycleBinChanged>
|
||||
<EntryTemplatesGroup>AAAAAAAAAAAAAAAAAAAAAA==</EntryTemplatesGroup>
|
||||
<EntryTemplatesGroupChanged>${creationDate}</EntryTemplatesGroupChanged>
|
||||
<LastSelectedGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastSelectedGroup>
|
||||
<LastTopVisibleGroup>AAAAAAAAAAAAAAAAAAAAAA==</LastTopVisibleGroup>
|
||||
<HistoryMaxItems>10</HistoryMaxItems>
|
||||
<HistoryMaxSize>6291456</HistoryMaxSize>
|
||||
<Binaries/>
|
||||
<CustomData/>
|
||||
</Meta>
|
||||
</KeePassFile>""")
|
||||
}
|
||||
|
||||
private interface KdbxEncryption {
|
||||
internal interface KdbxEncryption {
|
||||
val key: ByteArray
|
||||
|
||||
fun decrypt(encryptedText: ByteArray): ByteArray
|
||||
@@ -205,7 +174,7 @@ private fun createSalsa20(key: ByteArray): Salsa20Engine {
|
||||
/**
|
||||
* Salsa20 doesn't quite fit the KeePass memory model - all encrypted items have to be en/decrypted in order of encryption, i.e. in document order and at the same time.
|
||||
*/
|
||||
private class Salsa20Encryption(override val key: ByteArray) : KdbxEncryption {
|
||||
internal class Salsa20Encryption(override val key: ByteArray) : KdbxEncryption {
|
||||
private val salsa20 = createSalsa20(key)
|
||||
|
||||
override fun decrypt(encryptedText: ByteArray): ByteArray {
|
||||
@@ -220,3 +189,12 @@ private class Salsa20Encryption(override val key: ByteArray) : KdbxEncryption {
|
||||
return output
|
||||
}
|
||||
}
|
||||
|
||||
internal fun parseTime(value: String): Long {
|
||||
try {
|
||||
return ZonedDateTime.parse(value).toEpochSecond()
|
||||
}
|
||||
catch (e: DateTimeParseException) {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package com.intellij.credentialStore.linux
|
||||
import com.intellij.credentialStore.CredentialAttributes
|
||||
import com.intellij.credentialStore.CredentialStore
|
||||
import com.intellij.credentialStore.Credentials
|
||||
import com.intellij.credentialStore.FileCredentialStore
|
||||
import com.intellij.credentialStore.macOs.KeyChainCredentialStore
|
||||
import com.intellij.openapi.util.SystemInfo
|
||||
import com.intellij.testFramework.UsefulTestCase
|
||||
@@ -50,6 +51,11 @@ class NativeKeychainTest {
|
||||
testEmptyAccountName(SecretCredentialStore("com.intellij.test"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `keepass - testEmptyAccountName`() {
|
||||
testEmptyAccountName(FileCredentialStore())
|
||||
}
|
||||
|
||||
private fun doTest(store: CredentialStore) {
|
||||
val pass = BigInteger(128, Random()).toString(32)
|
||||
store.setPassword(CredentialAttributes(TEST_SERVICE_NAME, "test"), pass)
|
||||
|
||||
@@ -72,6 +72,8 @@ fun Element.getOrCreate(name: String): Element {
|
||||
return element
|
||||
}
|
||||
|
||||
fun Element.get(name: String): Element? = getChild(name)
|
||||
|
||||
fun Element.element(name: String): Element {
|
||||
val element = Element(name)
|
||||
addContent(element)
|
||||
|
||||
Reference in New Issue
Block a user