mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
feat: Add dialog
This commit is contained in:
@@ -52,6 +52,28 @@ object EntrySelectionHelper {
|
|||||||
private const val KEY_SEARCH_INFO = "com.kunzisoft.keepass.extra.SEARCH_INFO"
|
private const val KEY_SEARCH_INFO = "com.kunzisoft.keepass.extra.SEARCH_INFO"
|
||||||
private const val KEY_REGISTER_INFO = "com.kunzisoft.keepass.extra.REGISTER_INFO"
|
private const val KEY_REGISTER_INFO = "com.kunzisoft.keepass.extra.REGISTER_INFO"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finish the activity by passing the result code and by locking the database if necessary
|
||||||
|
*/
|
||||||
|
fun Activity.setActivityResult(
|
||||||
|
lockDatabase: Boolean = false,
|
||||||
|
resultCode: Int,
|
||||||
|
data: Intent? = null,
|
||||||
|
) {
|
||||||
|
when (resultCode) {
|
||||||
|
Activity.RESULT_OK ->
|
||||||
|
this.setResult(resultCode, data)
|
||||||
|
Activity.RESULT_CANCELED ->
|
||||||
|
this.setResult(resultCode)
|
||||||
|
}
|
||||||
|
this.finish()
|
||||||
|
|
||||||
|
if (lockDatabase && PreferencesUtil.isAutofillCloseDatabaseEnable(this)) {
|
||||||
|
// Close the database
|
||||||
|
this.sendBroadcast(Intent(LOCK_ACTION))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility method to build a registerForActivityResult,
|
* Utility method to build a registerForActivityResult,
|
||||||
* Used recursively, close each activity with return data
|
* Used recursively, close each activity with return data
|
||||||
@@ -63,19 +85,11 @@ object EntrySelectionHelper {
|
|||||||
return this.registerForActivityResult(
|
return this.registerForActivityResult(
|
||||||
ActivityResultContracts.StartActivityForResult()
|
ActivityResultContracts.StartActivityForResult()
|
||||||
) {
|
) {
|
||||||
val resultCode = it.resultCode
|
setActivityResult(
|
||||||
if (resultCode == Activity.RESULT_OK) {
|
lockDatabase,
|
||||||
this.setResult(resultCode, dataTransformation(it.data))
|
it.resultCode,
|
||||||
}
|
dataTransformation(it.data)
|
||||||
if (resultCode == Activity.RESULT_CANCELED) {
|
)
|
||||||
this.setResult(Activity.RESULT_CANCELED)
|
|
||||||
}
|
|
||||||
this.finish()
|
|
||||||
|
|
||||||
if (lockDatabase && PreferencesUtil.isAutofillCloseDatabaseEnable(this)) {
|
|
||||||
// Close the database
|
|
||||||
this.sendBroadcast(Intent(LOCK_ACTION))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -27,12 +27,16 @@ import android.os.Bundle
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.ActivityResultLauncher
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.credentials.GetCredentialResponse
|
import androidx.credentials.GetCredentialResponse
|
||||||
import androidx.credentials.exceptions.GetCredentialUnknownException
|
import androidx.credentials.exceptions.GetCredentialUnknownException
|
||||||
import androidx.credentials.provider.PendingIntentHandler
|
import androidx.credentials.provider.PendingIntentHandler
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||||
import com.kunzisoft.keepass.activities.GroupActivity
|
import com.kunzisoft.keepass.activities.GroupActivity
|
||||||
@@ -40,8 +44,10 @@ import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
|||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addSpecialMode
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addSpecialMode
|
||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addTypeMode
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addTypeMode
|
||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildActivityResultLauncher
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildActivityResultLauncher
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
|
||||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
|
||||||
@@ -62,6 +68,7 @@ import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retri
|
|||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveSearchInfo
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.retrieveSearchInfo
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists.saveCustomPrivilegedApps
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists.saveCustomPrivilegedApps
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewModel
|
||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
import com.kunzisoft.keepass.database.element.node.NodeIdUUID
|
||||||
import com.kunzisoft.keepass.database.helper.SearchHelper
|
import com.kunzisoft.keepass.database.helper.SearchHelper
|
||||||
@@ -69,6 +76,7 @@ import com.kunzisoft.keepass.model.AppOrigin
|
|||||||
import com.kunzisoft.keepass.model.Passkey
|
import com.kunzisoft.keepass.model.Passkey
|
||||||
import com.kunzisoft.keepass.model.RegisterInfo
|
import com.kunzisoft.keepass.model.RegisterInfo
|
||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
import com.kunzisoft.keepass.model.SignatureNotFoundException
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -79,6 +87,7 @@ import java.util.UUID
|
|||||||
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||||
class PasskeyLauncherActivity : DatabaseModeActivity() {
|
class PasskeyLauncherActivity : DatabaseModeActivity() {
|
||||||
|
|
||||||
|
private val passkeyLauncherViewModel: PasskeyLauncherViewModel by viewModels()
|
||||||
private var mUsageParameters: PublicKeyCredentialUsageParameters? = null
|
private var mUsageParameters: PublicKeyCredentialUsageParameters? = null
|
||||||
private var mCreationParameters: PublicKeyCredentialCreationParameters? = null
|
private var mCreationParameters: PublicKeyCredentialCreationParameters? = null
|
||||||
private var mPasskey: Passkey? = null
|
private var mPasskey: Passkey? = null
|
||||||
@@ -87,49 +96,69 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
|||||||
private var mBackupState: Boolean = false
|
private var mBackupState: Boolean = false
|
||||||
|
|
||||||
private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
||||||
this.buildActivityResultLauncher(
|
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
lockDatabase = true,
|
val intent = it.data
|
||||||
dataTransformation = { intent ->
|
val resultCode = it.resultCode
|
||||||
// Build a new formatted response from the selection response
|
// Build a new formatted response from the selection response
|
||||||
val responseIntent = Intent()
|
val responseIntent = Intent()
|
||||||
try {
|
when (resultCode) {
|
||||||
Log.d(TAG, "Passkey selection result")
|
RESULT_OK -> {
|
||||||
if (intent == null)
|
try {
|
||||||
throw IOException("Intent is null")
|
Log.d(TAG, "Passkey selection result")
|
||||||
val passkey = intent.retrievePasskey()
|
if (intent == null)
|
||||||
?: throw IOException("Passkey is null")
|
throw IOException("Intent is null")
|
||||||
val appOrigin = intent.retrieveAppOrigin()
|
val passkey = intent.retrievePasskey()
|
||||||
?: throw IOException("App origin is null")
|
?: throw IOException("Passkey is null")
|
||||||
intent.removePasskey()
|
val appOrigin = intent.retrieveAppOrigin()
|
||||||
intent.removeAppOrigin()
|
?: throw IOException("App origin is null")
|
||||||
mUsageParameters?.let { usageParameters ->
|
intent.removePasskey()
|
||||||
// Check verified origin
|
intent.removeAppOrigin()
|
||||||
PendingIntentHandler.setGetCredentialResponse(
|
mUsageParameters?.let { usageParameters ->
|
||||||
responseIntent,
|
// Check verified origin
|
||||||
GetCredentialResponse(
|
PendingIntentHandler.setGetCredentialResponse(
|
||||||
buildPasskeyPublicKeyCredential(
|
responseIntent,
|
||||||
requestOptions = usageParameters.publicKeyCredentialRequestOptions,
|
GetCredentialResponse(
|
||||||
clientDataResponse = getVerifiedGETClientDataResponse(
|
buildPasskeyPublicKeyCredential(
|
||||||
usageParameters = usageParameters,
|
requestOptions = usageParameters.publicKeyCredentialRequestOptions,
|
||||||
appOrigin = appOrigin
|
clientDataResponse = getVerifiedGETClientDataResponse(
|
||||||
),
|
usageParameters = usageParameters,
|
||||||
passkey = passkey,
|
appOrigin = appOrigin
|
||||||
backupEligibility = mBackupEligibility,
|
),
|
||||||
backupState = mBackupState
|
passkey = passkey,
|
||||||
|
backupEligibility = mBackupEligibility,
|
||||||
|
backupState = mBackupState
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
} ?: run {
|
||||||
|
throw IOException("Usage parameters is null")
|
||||||
|
}
|
||||||
|
setActivityResult(
|
||||||
|
lockDatabase = passkeyLauncherViewModel.lockDatabase,
|
||||||
|
resultCode = RESULT_OK,
|
||||||
|
data = responseIntent
|
||||||
|
)
|
||||||
|
} catch (e: SignatureNotFoundException) {
|
||||||
|
passkeyLauncherViewModel.showAppSignatureDialog(e.temptingApp)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e(TAG, "Unable to create selection response for passkey", e)
|
||||||
|
showError(e)
|
||||||
|
setActivityResult(
|
||||||
|
lockDatabase = passkeyLauncherViewModel.lockDatabase,
|
||||||
|
resultCode = RESULT_CANCELED
|
||||||
)
|
)
|
||||||
} ?: run {
|
|
||||||
throw IOException("Usage parameters is null")
|
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e(TAG, "Unable to create selection response for passkey", e)
|
|
||||||
showError(e)
|
|
||||||
}
|
}
|
||||||
// Return the response
|
RESULT_CANCELED -> {
|
||||||
responseIntent
|
setActivityResult(
|
||||||
|
lockDatabase = passkeyLauncherViewModel.lockDatabase,
|
||||||
|
resultCode = RESULT_CANCELED
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
)
|
// Remove the launcher register
|
||||||
|
passkeyLauncherViewModel.isResultLauncherRegistered = false
|
||||||
|
}
|
||||||
|
|
||||||
private var mPasskeyRegistrationActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
private var mPasskeyRegistrationActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
||||||
this.buildActivityResultLauncher(
|
this.buildActivityResultLauncher(
|
||||||
@@ -177,6 +206,24 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
mBackupEligibility = PreferencesUtil.isPasskeyBackupEligibilityEnable(applicationContext)
|
mBackupEligibility = PreferencesUtil.isPasskeyBackupEligibilityEnable(applicationContext)
|
||||||
mBackupState = PreferencesUtil.isPasskeyBackupStateEnable(applicationContext)
|
mBackupState = PreferencesUtil.isPasskeyBackupStateEnable(applicationContext)
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
passkeyLauncherViewModel.uiState.collect { uiState ->
|
||||||
|
when (uiState) {
|
||||||
|
is PasskeyLauncherViewModel.UIState.Loading -> {
|
||||||
|
// Nothing to do
|
||||||
|
}
|
||||||
|
is PasskeyLauncherViewModel.UIState.ShowAppPrivilegedDialog -> {
|
||||||
|
showAppPrivilegedDialog(uiState.temptingApp)
|
||||||
|
}
|
||||||
|
is PasskeyLauncherViewModel.UIState.ShowAppSignatureDialog -> {
|
||||||
|
showAppSignatureDialog( uiState.temptingApp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun cancelRequest() {
|
private fun cancelRequest() {
|
||||||
@@ -194,6 +241,7 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
|||||||
* Launch the main action to manage Passkey
|
* Launch the main action to manage Passkey
|
||||||
*/
|
*/
|
||||||
private suspend fun launchPasskeyAction(database: ContextualDatabase?) {
|
private suspend fun launchPasskeyAction(database: ContextualDatabase?) {
|
||||||
|
passkeyLauncherViewModel.isResultLauncherRegistered = true
|
||||||
val searchInfo = intent.retrieveSearchInfo() ?: SearchInfo()
|
val searchInfo = intent.retrieveSearchInfo() ?: SearchInfo()
|
||||||
val appOrigin = intent.retrieveAppOrigin() ?: AppOrigin(verified = false)
|
val appOrigin = intent.retrieveAppOrigin() ?: AppOrigin(verified = false)
|
||||||
val nodeId = intent.retrieveNodeId()
|
val nodeId = intent.retrieveNodeId()
|
||||||
@@ -216,7 +264,7 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
|||||||
/**
|
/**
|
||||||
* Display a dialog that asks the user to add an app to the list of privileged apps.
|
* Display a dialog that asks the user to add an app to the list of privileged apps.
|
||||||
*/
|
*/
|
||||||
private fun showAppPrivilegedDialog(e: PrivilegedAllowLists.PrivilegedException) {
|
private fun showAppPrivilegedDialog(temptingApp: AndroidPrivilegedApp) {
|
||||||
Log.w(javaClass.simpleName, "No privileged apps file found")
|
Log.w(javaClass.simpleName, "No privileged apps file found")
|
||||||
AlertDialog.Builder(this@PasskeyLauncherActivity).apply {
|
AlertDialog.Builder(this@PasskeyLauncherActivity).apply {
|
||||||
setTitle(getString(R.string.passkeys_privileged_apps_ask_title))
|
setTitle(getString(R.string.passkeys_privileged_apps_ask_title))
|
||||||
@@ -224,7 +272,7 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
|||||||
.append(
|
.append(
|
||||||
getString(
|
getString(
|
||||||
R.string.passkeys_privileged_apps_ask_message,
|
R.string.passkeys_privileged_apps_ask_message,
|
||||||
e.temptingApp.toString()
|
temptingApp.toString()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.append("\n\n")
|
.append("\n\n")
|
||||||
@@ -237,7 +285,7 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
|||||||
}) {
|
}) {
|
||||||
saveCustomPrivilegedApps(
|
saveCustomPrivilegedApps(
|
||||||
context = application,
|
context = application,
|
||||||
privilegedApps = listOf(e.temptingApp)
|
privilegedApps = listOf(temptingApp)
|
||||||
)
|
)
|
||||||
launchPasskeyAction(mDatabase)
|
launchPasskeyAction(mDatabase)
|
||||||
}
|
}
|
||||||
@@ -251,16 +299,63 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
|||||||
}.create().show()
|
}.create().show()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display a dialog that asks the user to add an app signature in an existing passkey
|
||||||
|
*/
|
||||||
|
private fun showAppSignatureDialog(appOrigin: AppOrigin) {
|
||||||
|
AlertDialog.Builder(this@PasskeyLauncherActivity).apply {
|
||||||
|
setTitle(getString(R.string.passkeys_missing_signature_app_ask_title))
|
||||||
|
setMessage(StringBuilder()
|
||||||
|
.append(
|
||||||
|
getString(
|
||||||
|
R.string.passkeys_missing_signature_app_ask_message,
|
||||||
|
appOrigin.toName()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.append("\n\n")
|
||||||
|
.append(getString(R.string.passkeys_missing_signature_app_ask_explanation))
|
||||||
|
.toString()
|
||||||
|
)
|
||||||
|
setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
|
lifecycleScope.launch(CoroutineExceptionHandler { _, e ->
|
||||||
|
cancelRequest(e)
|
||||||
|
}) {
|
||||||
|
// TODO
|
||||||
|
setActivityResult(
|
||||||
|
lockDatabase = passkeyLauncherViewModel.lockDatabase,
|
||||||
|
resultCode = RESULT_OK
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
|
setActivityResult(
|
||||||
|
lockDatabase = passkeyLauncherViewModel.lockDatabase,
|
||||||
|
resultCode = RESULT_CANCELED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
setOnCancelListener {
|
||||||
|
setActivityResult(
|
||||||
|
lockDatabase = passkeyLauncherViewModel.lockDatabase,
|
||||||
|
resultCode = RESULT_CANCELED
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}.create().show()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
||||||
super.onDatabaseRetrieved(database)
|
super.onDatabaseRetrieved(database)
|
||||||
|
|
||||||
lifecycleScope.launch(CoroutineExceptionHandler { _, e ->
|
if (passkeyLauncherViewModel.isResultLauncherRegistered.not()) {
|
||||||
when (e) {
|
lifecycleScope.launch(CoroutineExceptionHandler { _, e ->
|
||||||
is PrivilegedAllowLists.PrivilegedException -> showAppPrivilegedDialog(e)
|
when (e) {
|
||||||
else -> cancelRequest(e)
|
is PrivilegedAllowLists.PrivilegedException -> {
|
||||||
|
passkeyLauncherViewModel.showAppPrivilegedDialog(e.temptingApp)
|
||||||
|
}
|
||||||
|
else -> cancelRequest(e)
|
||||||
|
}
|
||||||
|
}) {
|
||||||
|
launchPasskeyAction(database)
|
||||||
}
|
}
|
||||||
}) {
|
|
||||||
launchPasskeyAction(database)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,27 +373,36 @@ class PasskeyLauncherActivity : DatabaseModeActivity() {
|
|||||||
?: throw GetCredentialUnknownException("No passkey with nodeId $nodeId found")
|
?: throw GetCredentialUnknownException("No passkey with nodeId $nodeId found")
|
||||||
|
|
||||||
val result = Intent()
|
val result = Intent()
|
||||||
PendingIntentHandler.setGetCredentialResponse(
|
try {
|
||||||
result,
|
PendingIntentHandler.setGetCredentialResponse(
|
||||||
GetCredentialResponse(
|
result,
|
||||||
buildPasskeyPublicKeyCredential(
|
GetCredentialResponse(
|
||||||
requestOptions = usageParameters.publicKeyCredentialRequestOptions,
|
buildPasskeyPublicKeyCredential(
|
||||||
clientDataResponse = getVerifiedGETClientDataResponse(
|
requestOptions = usageParameters.publicKeyCredentialRequestOptions,
|
||||||
usageParameters = usageParameters,
|
clientDataResponse = getVerifiedGETClientDataResponse(
|
||||||
appOrigin = appOrigin
|
usageParameters = usageParameters,
|
||||||
),
|
appOrigin = appOrigin
|
||||||
passkey = passkey,
|
),
|
||||||
backupEligibility = mBackupEligibility,
|
passkey = passkey,
|
||||||
backupState = mBackupState
|
backupEligibility = mBackupEligibility,
|
||||||
|
backupState = mBackupState
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
setActivityResult(
|
||||||
setResult(RESULT_OK, result)
|
lockDatabase = passkeyLauncherViewModel.lockDatabase,
|
||||||
finish()
|
resultCode = RESULT_OK,
|
||||||
|
data = result
|
||||||
|
)
|
||||||
|
} catch (e: SignatureNotFoundException) {
|
||||||
|
passkeyLauncherViewModel.showAppSignatureDialog(e.temptingApp)
|
||||||
|
}
|
||||||
} ?: run {
|
} ?: run {
|
||||||
Log.e(TAG, "Unable to auto select passkey, usage parameters are empty")
|
Log.e(TAG, "Unable to auto select passkey, usage parameters are empty")
|
||||||
setResult(RESULT_CANCELED)
|
setActivityResult(
|
||||||
finish()
|
lockDatabase = passkeyLauncherViewModel.lockDatabase,
|
||||||
|
resultCode = RESULT_CANCELED
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.kunzisoft.keepass.credentialprovider.viewmodel
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||||
|
import com.kunzisoft.keepass.model.AppOrigin
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
class PasskeyLauncherViewModel: ViewModel() {
|
||||||
|
|
||||||
|
var isResultLauncherRegistered: Boolean = false
|
||||||
|
val lockDatabase = true
|
||||||
|
|
||||||
|
private val _uiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||||
|
val uiState: StateFlow<UIState> = _uiState
|
||||||
|
|
||||||
|
fun showAppPrivilegedDialog(temptingApp: AndroidPrivilegedApp) {
|
||||||
|
_uiState.value = UIState.ShowAppPrivilegedDialog(temptingApp)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun showAppSignatureDialog(temptingApp: AppOrigin) {
|
||||||
|
_uiState.value = UIState.ShowAppSignatureDialog(temptingApp)
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class UIState {
|
||||||
|
object Loading : UIState()
|
||||||
|
data class ShowAppPrivilegedDialog(
|
||||||
|
val temptingApp: AndroidPrivilegedApp
|
||||||
|
): UIState()
|
||||||
|
data class ShowAppSignatureDialog(
|
||||||
|
val temptingApp: AppOrigin
|
||||||
|
): UIState()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -424,9 +424,12 @@
|
|||||||
<string name="passkeys_preference_title">Passkeys settings</string>
|
<string name="passkeys_preference_title">Passkeys settings</string>
|
||||||
<string name="passkeys_privileged_apps_title">Privileged apps</string>
|
<string name="passkeys_privileged_apps_title">Privileged apps</string>
|
||||||
<string name="passkeys_privileged_apps_summary">Manage browsers in the custom list of privileged apps</string>
|
<string name="passkeys_privileged_apps_summary">Manage browsers in the custom list of privileged apps</string>
|
||||||
<string name="passkeys_privileged_apps_explanation">WARNING : A privileged app acts as a gateway to retrieve the origin of an authentication. Ensure its legitimacy to avoid security issues.</string>
|
<string name="passkeys_privileged_apps_explanation">WARNING: A privileged app acts as a gateway to retrieve the origin of an authentication. Ensure its legitimacy to avoid security issues.</string>
|
||||||
<string name="passkeys_privileged_apps_ask_title">App not recognized</string>
|
<string name="passkeys_privileged_apps_ask_title">App not recognized</string>
|
||||||
<string name="passkeys_privileged_apps_ask_message">%1$s attempts to perform a Passkey action.\n\nWould you like to add it to the list of privileged apps?</string>
|
<string name="passkeys_privileged_apps_ask_message">%1$s attempts to perform a Passkey action.\n\nAdd it to the list of privileged apps?</string>
|
||||||
|
<string name="passkeys_missing_signature_app_ask_title">Signature missing</string>
|
||||||
|
<string name="passkeys_missing_signature_app_ask_explanation">WARNING: The passkey was created from another client or the signature has been deleted. Ensure the app you want to authenticate is part of the same service and is legitimate to avoid security issues.</string>
|
||||||
|
<string name="passkeys_missing_signature_app_ask_message">%1$s with an unknown signature attempts to authenticate with an existing passkey.\n\nAdd app signature to passkey entry?</string>
|
||||||
<string name="passkeys_backup_eligibility_title">Backup Eligibility</string>
|
<string name="passkeys_backup_eligibility_title">Backup Eligibility</string>
|
||||||
<string name="passkeys_backup_eligibility_summary">Determine at creation time whether the public key credential source is allowed to be backed up</string>
|
<string name="passkeys_backup_eligibility_summary">Determine at creation time whether the public key credential source is allowed to be backed up</string>
|
||||||
<string name="passkeys_backup_state_title">Backup State</string>
|
<string name="passkeys_backup_state_title">Backup State</string>
|
||||||
|
|||||||
@@ -49,6 +49,9 @@ data class AppOrigin(
|
|||||||
* return the first verified origin or throw an exception if none is found
|
* return the first verified origin or throw an exception if none is found
|
||||||
*/
|
*/
|
||||||
fun checkAppOrigin(compare: AppOrigin): String {
|
fun checkAppOrigin(compare: AppOrigin): String {
|
||||||
|
if (compare.androidOrigins.isNotEmpty()) {
|
||||||
|
throw SignatureNotFoundException(this, "Android origin not found")
|
||||||
|
}
|
||||||
return androidOrigins.firstOrNull { androidOrigin ->
|
return androidOrigins.firstOrNull { androidOrigin ->
|
||||||
compare.androidOrigins.any {
|
compare.androidOrigins.any {
|
||||||
it.packageName == androidOrigin.packageName
|
it.packageName == androidOrigin.packageName
|
||||||
@@ -100,6 +103,14 @@ data class AppOrigin(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception indicating that no signature is present for the Android origin
|
||||||
|
*/
|
||||||
|
class SignatureNotFoundException(
|
||||||
|
val temptingApp: AppOrigin,
|
||||||
|
message: String
|
||||||
|
) : Exception(message)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an Android app origin, the [packageName] is the applicationId of the app
|
* Represents an Android app origin, the [packageName] is the applicationId of the app
|
||||||
* and the [fingerprint] is the
|
* and the [fingerprint] is the
|
||||||
|
|||||||
Reference in New Issue
Block a user