fix: Add webOrigin, fix title and add verification state

This commit is contained in:
J-Jamet
2025-08-29 18:41:40 +02:00
parent 4f10d13691
commit f8787ba03d
9 changed files with 258 additions and 181 deletions

View File

@@ -9,6 +9,7 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.isIntentSenderMode
import com.kunzisoft.keepass.credentialprovider.SpecialMode
import com.kunzisoft.keepass.credentialprovider.TypeMode
import com.kunzisoft.keepass.model.RegisterInfo
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.view.ToolbarSpecial
@@ -113,7 +114,8 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
mSpecialMode = EntrySelectionHelper.retrieveSpecialModeFromIntent(intent)
mTypeMode = EntrySelectionHelper.retrieveTypeModeFromIntent(intent)
val searchInfo: SearchInfo? = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)?.searchInfo
val registerInfo: RegisterInfo? = EntrySelectionHelper.retrieveRegisterInfoFromIntent(intent)
val searchInfo: SearchInfo? = registerInfo?.searchInfo
?: EntrySelectionHelper.retrieveSearchInfoFromIntent(intent)
// To show the selection mode
@@ -137,7 +139,7 @@ abstract class DatabaseModeActivity : DatabaseActivity() {
if (mTypeMode != TypeMode.DEFAULT)
title = "$title (${getString(typeModeStringId)})"
// Populate subtitle
subtitle = searchInfo?.getName(resources)
subtitle = registerInfo?.getName(resources) ?: searchInfo?.getName(resources)
// Show the toolbar or not
visible = when (mSpecialMode) {

View File

@@ -48,7 +48,7 @@ import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addSe
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildCreatePublicKeyCredentialResponse
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyPublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.checkSecurity
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getVerifiedClientDataResponse
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getVerifiedGETClientDataResponse
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeAppOrigin
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveAppOrigin
@@ -85,38 +85,32 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
val responseIntent = Intent()
try {
Log.d(TAG, "Passkey selection result")
val passkey = intent?.retrievePasskey()
val appOrigin = intent?.retrieveAppOrigin()
intent?.removePasskey()
intent?.removeAppOrigin()
passkey?.let {
if (intent == null)
throw IOException("Intent is null")
val passkey = intent.retrievePasskey()
?: throw IOException("Passkey is null")
val appOrigin = intent.retrieveAppOrigin()
?: throw IOException("App origin is null")
intent.removePasskey()
intent.removeAppOrigin()
mUsageParameters?.let { usageParameters ->
// Check verified origin
getVerifiedClientDataResponse(
usageParameters = usageParameters,
appOrigin = appOrigin,
onOriginChecked = { clientDataResponse ->
PendingIntentHandler.setGetCredentialResponse(
responseIntent,
GetCredentialResponse(
buildPasskeyPublicKeyCredential(
requestOptions = usageParameters.publicKeyCredentialRequestOptions,
clientDataResponse = clientDataResponse,
clientDataResponse = getVerifiedGETClientDataResponse(
usageParameters = usageParameters,
appOrigin = appOrigin
),
passkey = passkey
)
)
)
},
onOriginNotChecked = {
throw SecurityException("Wrong signature for ${usageParameters.androidApp.id}")
}
)
} ?: run {
throw IOException("Usage parameters is null")
}
} ?: run {
throw IOException("Passkey is null")
}
} catch (e: Exception) {
Log.e(TAG, "Unable to create selection response for passkey", e)
showError(e)
@@ -197,7 +191,8 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
private fun autoSelectPasskeyAndSetResult(
database: ContextualDatabase?,
nodeId: UUID
nodeId: UUID,
appOrigin: AppOrigin
) {
mUsageParameters?.let { usageParameters ->
// To get the passkey from the database
@@ -213,7 +208,10 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
GetCredentialResponse(
buildPasskeyPublicKeyCredential(
requestOptions = usageParameters.publicKeyCredentialRequestOptions,
clientDataResponse = usageParameters.clientDataResponse,
clientDataResponse = getVerifiedGETClientDataResponse(
usageParameters = usageParameters,
appOrigin = appOrigin
),
passkey = passkey
)
)
@@ -230,23 +228,20 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
private suspend fun launchSelection(
database: ContextualDatabase?,
nodeId: UUID?,
searchInfo: SearchInfo?,
appOrigin: AppOrigin?
searchInfo: SearchInfo,
appOrigin: AppOrigin
) {
Log.d(TAG, "Launch passkey selection")
retrievePasskeyUsageRequestParameters(
intent = intent,
assetManager = assets,
appOrigin = appOrigin
appOrigin = appOrigin,
) { usageParameters ->
// Save the requested parameters
mUsageParameters = usageParameters
// Manage the passkey to use
nodeId?.let { nodeId ->
if (usageParameters.androidAppVerified.not()) {
throw SecurityException("Wrong signature for ${usageParameters.androidApp.id}")
}
autoSelectPasskeyAndSetResult(database, nodeId)
autoSelectPasskeyAndSetResult(database, nodeId, appOrigin)
} ?: run {
SearchHelper.checkAutoSearchInfo(
context = this,

View File

@@ -19,11 +19,12 @@
*/
package com.kunzisoft.keepass.credentialprovider.passkey.data
import com.kunzisoft.keepass.model.AppIdentifier
import com.kunzisoft.keepass.model.AndroidOrigin
import com.kunzisoft.keepass.model.WebOrigin
data class PublicKeyCredentialUsageParameters(
val publicKeyCredentialRequestOptions: PublicKeyCredentialRequestOptions,
val clientDataResponse: ClientDataResponse,
val androidApp: AppIdentifier,
val androidAppVerified: Boolean
val androidOrigin: AndroidOrigin,
val webOrigin: WebOrigin,
)

View File

@@ -25,8 +25,10 @@ import android.util.Log
import androidx.annotation.RequiresApi
import androidx.credentials.provider.CallingAppInfo
import com.kunzisoft.encrypt.HashManager.getApplicationSignatures
import com.kunzisoft.keepass.model.AppIdentifier
import com.kunzisoft.keepass.model.AndroidOrigin
import com.kunzisoft.keepass.model.AppOrigin
import com.kunzisoft.keepass.model.Verification
import com.kunzisoft.keepass.model.WebOrigin
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
@@ -37,7 +39,8 @@ import kotlinx.coroutines.withContext
class OriginManager(
private val providedClientDataHash: ByteArray?,
private val callingAppInfo: CallingAppInfo?,
private val assets: AssetManager
private val assets: AssetManager,
private val relyingParty: String,
) {
/**
@@ -50,20 +53,23 @@ class OriginManager(
onOriginCreated: (appInfoToStore: AppOrigin, origin: String) -> Unit
) {
getOrigin(
onOriginRetrieved = { appIdentifier, callOrigin, clientDataHash ->
onOriginRetrieved = { androidOrigin, webOrigin, callOrigin, clientDataHash ->
onOriginRetrieved(
AppOrigin().apply {
// Do not store Web Browser AppId -> addIdentifier(appIdentifier)
addWebDomain(callOrigin)
addAndroidOrigin(androidOrigin)
addWebOrigin(webOrigin)
},
clientDataHash
)
},
onOriginNotRetrieved = { appIdentifier ->
onOriginNotRetrieved = { appIdentifier, webOrigin ->
// Create a new Android Origin and prepare the signature app storage
onOriginCreated(
AppOrigin().apply { addIdentifier(appIdentifier) },
appIdentifier.buildAndroidOrigin()
AppOrigin().apply {
addAndroidOrigin(appIdentifier)
addWebOrigin(webOrigin)
},
appIdentifier.toAndroidOrigin()
)
}
)
@@ -75,25 +81,27 @@ class OriginManager(
* calls [onOriginCreated] if the origin was created manually, origin is verified if present in the KeePass database
*/
suspend fun getOriginAtUsage(
appOrigin: AppOrigin?,
onOriginRetrieved: (appIdentifier: AppIdentifier, clientDataHash: ByteArray) -> Unit,
onOriginCreated: (appIdentifier: AppIdentifier, origin: String, originVerified: Boolean) -> Unit
appOrigin: AppOrigin,
onOriginRetrieved: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin, clientDataHash: ByteArray) -> Unit,
onOriginCreated: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin) -> Unit
) {
getOrigin(
onOriginRetrieved = { appIdentifier, origin, clientDataHash ->
onOriginRetrieved(appIdentifier, clientDataHash)
onOriginRetrieved = { androidOrigin, webOrigin, origin, clientDataHash ->
onOriginRetrieved(androidOrigin, webOrigin, clientDataHash)
},
onOriginNotRetrieved = { appIdentifierToCheck ->
// Verify the app signature to retrieve the origin
val androidOrigin = appIdentifierToCheck.buildAndroidOrigin()
appIdentifierToCheck.checkInAppOrigin(
appOrigin = appOrigin,
onOriginChecked = {
onOriginCreated(appIdentifierToCheck, androidOrigin, true)
},
onOriginNotChecked = {
onOriginCreated(appIdentifierToCheck, androidOrigin, false)
}
onOriginNotRetrieved = { appIdentifierToCheck, webOrigin ->
// Check the app signature in the appOrigin, webOrigin cannot be checked now
onOriginCreated(
AndroidOrigin(
packageName = appIdentifierToCheck.packageName,
signature = appIdentifierToCheck.signature,
verification =
if (appOrigin.containsVerifiedAndroidOrigin(appIdentifierToCheck))
Verification.MANUALLY_VERIFIED
else
Verification.NOT_VERIFIED
),
webOrigin
)
}
)
@@ -106,8 +114,8 @@ class OriginManager(
* call [onOriginNotRetrieved] if the origin is not retrieved from the system
*/
private suspend fun getOrigin(
onOriginRetrieved: (appInfoRetrieved: AppIdentifier, origin: String, clientDataHash: ByteArray) -> Unit,
onOriginNotRetrieved: (appInfoRetrieved: AppIdentifier) -> Unit
onOriginRetrieved: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin, origin: String, clientDataHash: ByteArray) -> Unit,
onOriginNotRetrieved: (androidOrigin: AndroidOrigin, webOrigin: WebOrigin) -> Unit
) {
if (callingAppInfo == null) {
throw SecurityException("Calling app info cannot be retrieved")
@@ -119,17 +127,37 @@ class OriginManager(
}
// for trusted browsers like Chrome and Firefox
callOrigin = callingAppInfo.getOrigin(privilegedAllowlist)?.removeSuffix("/")
val appIdentifier = AppIdentifier(
id = callingAppInfo.packageName,
val androidOrigin = AndroidOrigin(
packageName = callingAppInfo.packageName,
signature = callingAppInfo.signingInfo
.getApplicationSignatures()
.getApplicationSignatures(),
verification = Verification.NOT_VERIFIED
)
// Check if the webDomain is validated for the
withContext(Dispatchers.Main) {
if (callOrigin != null && providedClientDataHash != null) {
Log.d(TAG, "Origin $callOrigin retrieved from callingAppInfo")
onOriginRetrieved(appIdentifier, callOrigin, providedClientDataHash)
onOriginRetrieved(
AndroidOrigin(
packageName = androidOrigin.packageName,
signature = androidOrigin.signature,
verification = Verification.AUTOMATICALLY_VERIFIED
),
WebOrigin.fromRelyingParty(
relyingParty = relyingParty,
verification = Verification.AUTOMATICALLY_VERIFIED
),
callOrigin,
providedClientDataHash
)
} else {
onOriginNotRetrieved(appIdentifier)
onOriginNotRetrieved(
androidOrigin,
WebOrigin.fromRelyingParty(
relyingParty = relyingParty,
verification = Verification.NOT_VERIFIED
)
)
}
}
}
@@ -137,42 +165,5 @@ class OriginManager(
companion object {
private val TAG = OriginManager::class.simpleName
/**
* Verify that the application signature is contained in the [appOrigin]
*/
fun AppIdentifier.checkInAppOrigin(
appOrigin: AppOrigin?,
onOriginChecked: (origin: String) -> Unit,
onOriginNotChecked: () -> Unit
) {
// Verify the app signature to retrieve the origin
val appIdentifierStored = appOrigin?.appIdentifiers?.filter {
it.id == this.id
}
if (appIdentifierStored?.any { it.signature == this.signature } == true) {
onOriginChecked(this.buildAndroidOrigin())
} else {
onOriginNotChecked()
}
}
/**
* Builds an Android Origin from a AppIdentifier
*/
fun AppIdentifier.buildAndroidOrigin(): String {
return buildAndroidOrigin(this.id)
}
/**
* Builds an Android Origin from a package name.
*/
private fun buildAndroidOrigin(packageName: String?): String {
if (packageName.isNullOrEmpty())
throw SecurityException("Package name cannot be empty")
val packageOrigin = "androidapp://${packageName}"
Log.d(TAG, "Origin $packageOrigin retrieved from package name")
return packageOrigin
}
}
}

View File

@@ -51,11 +51,11 @@ import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredential
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
import com.kunzisoft.keepass.credentialprovider.passkey.util.Base64Helper.Companion.b64Encode
import com.kunzisoft.keepass.credentialprovider.passkey.util.OriginManager.Companion.checkInAppOrigin
import com.kunzisoft.keepass.model.AppOrigin
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Passkey
import com.kunzisoft.keepass.model.SearchInfo
import com.kunzisoft.keepass.model.Verification
import com.kunzisoft.keepass.utils.StringUtil.toHexString
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
import com.kunzisoft.random.KeePassDXRandom
@@ -364,7 +364,8 @@ object PasskeyHelper {
OriginManager(
providedClientDataHash = clientDataHash,
callingAppInfo = callingAppInfo,
assets = assetManager
assets = assetManager,
relyingParty = relyingParty
).getOriginAtCreation(
onOriginRetrieved = { appInfoToStore, clientDataHash ->
passkeyCreated.invoke(
@@ -441,7 +442,7 @@ object PasskeyHelper {
suspend fun retrievePasskeyUsageRequestParameters(
intent: Intent,
assetManager: AssetManager,
appOrigin: AppOrigin?,
appOrigin: AppOrigin,
result: (PublicKeyCredentialUsageParameters) -> Unit
) {
val getCredentialRequest = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
@@ -456,30 +457,32 @@ object PasskeyHelper {
OriginManager(
providedClientDataHash = clientDataHash,
callingAppInfo = callingAppInfo,
assets = assetManager
assets = assetManager,
relyingParty = requestOptions.rpId
).getOriginAtUsage(
appOrigin = appOrigin,
onOriginRetrieved = { appIdentifier, clientDataHash ->
onOriginRetrieved = { androidOrigin, webOrigin, clientDataHash ->
result.invoke(
PublicKeyCredentialUsageParameters(
publicKeyCredentialRequestOptions = requestOptions,
clientDataResponse = ClientDataDefinedResponse(clientDataHash),
androidApp = appIdentifier,
androidAppVerified = true
androidOrigin = androidOrigin,
webOrigin = webOrigin
)
)
},
onOriginCreated = { appIdentifier, origin, verified ->
onOriginCreated = { androidOrigin, webOrigin ->
// By default we crate an usage parameter with Android origin
result.invoke(
PublicKeyCredentialUsageParameters(
publicKeyCredentialRequestOptions = requestOptions,
clientDataResponse = ClientDataBuildResponse(
type = ClientDataBuildResponse.Type.GET,
challenge = requestOptions.challenge,
origin = origin
origin = androidOrigin.toAndroidOrigin()
),
androidApp = appIdentifier,
androidAppVerified = verified
androidOrigin = androidOrigin,
webOrigin = webOrigin
)
)
}
@@ -518,30 +521,35 @@ object PasskeyHelper {
* Verify that the application signature is contained in the [appOrigin]
* or that the webDomain contains the origin
*/
fun getVerifiedClientDataResponse(
fun getVerifiedGETClientDataResponse(
usageParameters: PublicKeyCredentialUsageParameters,
appOrigin: AppOrigin?,
onOriginChecked: (clientDataResponse: ClientDataResponse) -> Unit,
onOriginNotChecked: () -> Unit
) {
if (usageParameters.androidAppVerified) {
onOriginChecked(usageParameters.clientDataResponse)
appOrigin: AppOrigin
): ClientDataResponse {
val appToCheck = usageParameters.androidOrigin
val webToCheck = usageParameters.webOrigin
if (appToCheck.verification == Verification.AUTOMATICALLY_VERIFIED) {
return usageParameters.clientDataResponse
} else {
usageParameters.androidApp.checkInAppOrigin(
appOrigin = appOrigin,
onOriginChecked = { origin ->
// Origin checked by Android app signature
onOriginChecked(
ClientDataBuildResponse(
if (appOrigin.containsVerifiedAndroidOrigin(appToCheck)) {
if (webToCheck.verification.verified
|| appOrigin.containsVerifiedWebOrigin(webToCheck)) {
// Origin checked by URL
return ClientDataBuildResponse(
type = ClientDataBuildResponse.Type.GET,
challenge = usageParameters.publicKeyCredentialRequestOptions.challenge,
origin = origin
)
)
},
onOriginNotChecked
origin = webToCheck.toWebOrigin()
)
}
// Origin checked by Android app signature
return ClientDataBuildResponse(
type = ClientDataBuildResponse.Type.GET,
challenge = usageParameters.publicKeyCredentialRequestOptions.challenge,
origin = appOrigin.firstVerifiedWebOrigin()?.toWebOrigin()
?: appToCheck.toAndroidOrigin()
)
} else {
throw SecurityException("Wrong signature for ${appToCheck.packageName}")
}
}
}
}

View File

@@ -24,38 +24,96 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class AppOrigin(
val appIdentifiers: MutableList<AppIdentifier> = mutableListOf(),
val webDomains: MutableList<String> = mutableListOf()
val androidOrigins: MutableList<AndroidOrigin> = mutableListOf(),
val webOrigins: MutableList<WebOrigin> = mutableListOf()
) : Parcelable {
fun addIdentifier(appIdentifier: AppIdentifier) {
appIdentifiers.add(appIdentifier)
fun addAndroidOrigin(androidOrigin: AndroidOrigin) {
androidOrigins.add(androidOrigin)
}
fun addWebDomain(webDomain: String) {
this.webDomains.add(webDomain)
fun addWebOrigin(webOrigin: WebOrigin) {
this.webOrigins.add(webOrigin)
}
fun removeAppElement(appIdentifier: AppIdentifier) {
appIdentifiers.remove(appIdentifier)
fun containsVerifiedAndroidOrigin(androidOrigin: AndroidOrigin): Boolean {
return androidOrigins.any {
it.packageName == androidOrigin.packageName
&& it.signature == androidOrigin.signature
&& it.verification.verified
}
}
fun removeWebDomain(webDomain: String) {
this.webDomains.remove(webDomain)
fun containsVerifiedWebOrigin(webOrigin: WebOrigin): Boolean {
return this.webOrigins.any {
it.origin == webOrigin.origin
&& it.verification.verified
}
}
fun firstVerifiedWebOrigin(): WebOrigin? {
return webOrigins.first {
it.verification.verified
}
}
fun clear() {
appIdentifiers.clear()
webDomains.clear()
androidOrigins.clear()
webOrigins.clear()
}
fun isEmpty(): Boolean {
return appIdentifiers.isEmpty() && webDomains.isEmpty()
return androidOrigins.isEmpty() && webOrigins.isEmpty()
}
fun toName(): String? {
return if (androidOrigins.isNotEmpty()) {
androidOrigins.first().packageName
} else if (webOrigins.isNotEmpty()){
webOrigins.first().origin
} else null
}
}
enum class Verification {
MANUALLY_VERIFIED, AUTOMATICALLY_VERIFIED, NOT_VERIFIED;
val verified: Boolean
get() = this == MANUALLY_VERIFIED || this == AUTOMATICALLY_VERIFIED
}
@Parcelize
data class AndroidOrigin(
val packageName: String,
val signature: String? = null,
val verification: Verification = Verification.AUTOMATICALLY_VERIFIED,
) : Parcelable {
fun toAndroidOrigin(): String {
return "androidapp://${packageName}"
}
}
@Parcelize
data class AppIdentifier(
val id: String,
val signature: String? = null,
) : Parcelable
data class WebOrigin(
val origin: String,
val assetLinks: String? = null,
val verification: Verification = Verification.AUTOMATICALLY_VERIFIED,
) : Parcelable {
fun toWebOrigin(): String {
return origin
}
fun defaultAssetLinks(): String {
return "${origin}/.well-known/assetlinks.json"
}
companion object {
const val RELYING_PARTY_DEFAULT_PROTOCOL = "https"
fun fromRelyingParty(relyingParty: String, verification: Verification): WebOrigin = WebOrigin(
origin ="$RELYING_PARTY_DEFAULT_PROTOCOL://$relyingParty",
verification = verification
)
}
}

View File

@@ -48,8 +48,8 @@ object AppOriginEntryField {
}
}.takeWhile { it != null }
.forEach { pair ->
appOrigin.addIdentifier(
AppIdentifier(pair!!.first, pair.second)
appOrigin.addAndroidOrigin(
AndroidOrigin(pair!!.first, pair.second)
)
}
// Get Domains
@@ -58,7 +58,7 @@ object AppOriginEntryField {
val domainKey = WEB_DOMAIN_FIELD_NAME + suffixFieldNamePosition(domainFieldPosition)
val domainValue = getField(domainKey)
if (domainValue != null) {
appOrigin.addWebDomain(domainValue)
appOrigin.addWebOrigin(WebOrigin(origin = domainValue))
domainFieldPosition++
} else {
break // No more domain found
@@ -137,11 +137,12 @@ object AppOriginEntryField {
* Only if [customFieldsAllowed] is true
*/
fun EntryInfo.setAppOrigin(appOrigin: AppOrigin?, customFieldsAllowed: Boolean) {
appOrigin?.appIdentifiers?.forEach { appIdentifier ->
setApplicationId(appIdentifier.id, appIdentifier.signature)
appOrigin?.androidOrigins?.forEach { appIdentifier ->
setApplicationId(appIdentifier.packageName, appIdentifier.signature)
}
appOrigin?.webDomains?.forEach { webDomain ->
setWebDomain(webDomain, null, customFieldsAllowed)
appOrigin?.webOrigins?.forEach { webOrigin ->
if (webOrigin.verification.verified)
setWebDomain(webOrigin.origin, null, customFieldsAllowed)
}
}
}

View File

@@ -176,7 +176,16 @@ class EntryInfo : NodeInfo {
}
/**
* Add searchInfo to current EntryInfo, return true if new data, false if no modification
* Capitalize and remove suffix of a title
*/
fun String.toTitle(): String {
return this.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}
}
/**
* Add searchInfo to current EntryInfo
*/
fun saveSearchInfo(database: Database?, searchInfo: SearchInfo) {
searchInfo.otpString?.let { otpString ->
@@ -191,20 +200,13 @@ class EntryInfo : NodeInfo {
setApplicationId(applicationId)
}
if (title.isEmpty()) {
title = searchInfo.toTitle()
title = searchInfo.toString().toTitle()
}
}
/**
* Capitalize and remove suffix of web domain to create a title
* Add registerInfo to current EntryInfo
*/
fun SearchInfo.toTitle(): String {
val webDomain = this.webDomain
return webDomain?.substring(0, webDomain.lastIndexOf('.'))?.replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
} ?: this.toString()
}
fun saveRegisterInfo(database: Database?, registerInfo: RegisterInfo) {
saveSearchInfo(database, registerInfo.searchInfo)
registerInfo.username?.let { username = it }
@@ -215,6 +217,9 @@ class EntryInfo : NodeInfo {
registerInfo.appOrigin,
database?.allowEntryCustomFields() == true
)
if (title.isEmpty()) {
title = registerInfo.toString().toTitle()
}
}
fun getVisualTitle(): String {

View File

@@ -1,7 +1,9 @@
package com.kunzisoft.keepass.model
import android.content.res.Resources
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.utils.ObjectNameResource
import com.kunzisoft.keepass.utils.readParcelableCompat
data class RegisterInfo(
@@ -11,12 +13,12 @@ data class RegisterInfo(
val creditCard: CreditCard? = null,
val passkey: Passkey? = null,
val appOrigin: AppOrigin? = null
): Parcelable {
) : ObjectNameResource, Parcelable {
constructor(parcel: Parcel) : this(
searchInfo = parcel.readParcelableCompat() ?: SearchInfo(),
username = parcel.readString() ?: "",
password = parcel.readString() ?: "",
username = parcel.readString(),
password = parcel.readString(),
creditCard = parcel.readParcelableCompat(),
passkey = parcel.readParcelableCompat(),
appOrigin = parcel.readParcelableCompat()
@@ -35,6 +37,20 @@ data class RegisterInfo(
return 0
}
override fun getName(resources: Resources): String {
return username
?: passkey?.relyingParty
?: appOrigin?.toName()
?: searchInfo.getName(resources)
}
override fun toString(): String {
return username
?: passkey?.relyingParty
?: appOrigin?.toName()
?: searchInfo.toString()
}
companion object CREATOR : Parcelable.Creator<RegisterInfo> {
override fun createFromParcel(parcel: Parcel): RegisterInfo {
return RegisterInfo(parcel)