mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Add MainCredentialViewModel
This commit is contained in:
@@ -52,6 +52,7 @@ import androidx.core.view.WindowInsetsCompat
|
|||||||
import androidx.core.view.WindowInsetsControllerCompat
|
import androidx.core.view.WindowInsetsControllerCompat
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import com.google.android.material.datepicker.MaterialDatePicker
|
import com.google.android.material.datepicker.MaterialDatePicker
|
||||||
import com.google.android.material.timepicker.MaterialTimePicker
|
import com.google.android.material.timepicker.MaterialTimePicker
|
||||||
@@ -75,7 +76,6 @@ import com.kunzisoft.keepass.credentialprovider.TypeMode
|
|||||||
import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService
|
import com.kunzisoft.keepass.credentialprovider.magikeyboard.MagikeyboardService
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.buildPasskeyResponseAndSetResult
|
||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
import com.kunzisoft.keepass.database.MainCredential
|
|
||||||
import com.kunzisoft.keepass.database.element.DateInstant
|
import com.kunzisoft.keepass.database.element.DateInstant
|
||||||
import com.kunzisoft.keepass.database.element.Entry
|
import com.kunzisoft.keepass.database.element.Entry
|
||||||
import com.kunzisoft.keepass.database.element.Group
|
import com.kunzisoft.keepass.database.element.Group
|
||||||
@@ -121,6 +121,8 @@ import com.kunzisoft.keepass.view.toastError
|
|||||||
import com.kunzisoft.keepass.view.updateLockPaddingStart
|
import com.kunzisoft.keepass.view.updateLockPaddingStart
|
||||||
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
|
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
|
||||||
import com.kunzisoft.keepass.viewmodels.GroupViewModel
|
import com.kunzisoft.keepass.viewmodels.GroupViewModel
|
||||||
|
import com.kunzisoft.keepass.viewmodels.MainCredentialViewModel
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.joda.time.LocalDateTime
|
import org.joda.time.LocalDateTime
|
||||||
import java.util.EnumSet
|
import java.util.EnumSet
|
||||||
|
|
||||||
@@ -130,8 +132,7 @@ class GroupActivity : DatabaseLockActivity(),
|
|||||||
GroupFragment.NodesActionMenuListener,
|
GroupFragment.NodesActionMenuListener,
|
||||||
GroupFragment.OnScrollListener,
|
GroupFragment.OnScrollListener,
|
||||||
GroupFragment.GroupRefreshedListener,
|
GroupFragment.GroupRefreshedListener,
|
||||||
SortDialogFragment.SortSelectionListener,
|
SortDialogFragment.SortSelectionListener {
|
||||||
MainCredentialDialogFragment.AskMainCredentialDialogListener {
|
|
||||||
|
|
||||||
// Views
|
// Views
|
||||||
private var header: ViewGroup? = null
|
private var header: ViewGroup? = null
|
||||||
@@ -157,6 +158,8 @@ class GroupActivity : DatabaseLockActivity(),
|
|||||||
private val mGroupViewModel: GroupViewModel by viewModels()
|
private val mGroupViewModel: GroupViewModel by viewModels()
|
||||||
private val mGroupEditViewModel: GroupEditViewModel by viewModels()
|
private val mGroupEditViewModel: GroupEditViewModel by viewModels()
|
||||||
|
|
||||||
|
private val mMainCredentialViewModel: MainCredentialViewModel by viewModels()
|
||||||
|
|
||||||
private val mGroupActivityEducation = GroupActivityEducation(this)
|
private val mGroupActivityEducation = GroupActivityEducation(this)
|
||||||
|
|
||||||
private var mBreadcrumbAdapter: BreadcrumbAdapter? = null
|
private var mBreadcrumbAdapter: BreadcrumbAdapter? = null
|
||||||
@@ -548,6 +551,21 @@ class GroupActivity : DatabaseLockActivity(),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
// Initialize the parameters
|
||||||
|
mMainCredentialViewModel.uiState.collect { uiState ->
|
||||||
|
when (uiState) {
|
||||||
|
is MainCredentialViewModel.UIState.Loading -> {}
|
||||||
|
is MainCredentialViewModel.UIState.OnMainCredentialValidated -> {
|
||||||
|
mergeDatabaseFrom(uiState.databaseUri, uiState.mainCredential)
|
||||||
|
}
|
||||||
|
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
|
||||||
|
// Noting here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun viewToInvalidateTimeout(): View? {
|
override fun viewToInvalidateTimeout(): View? {
|
||||||
@@ -1133,20 +1151,6 @@ class GroupActivity : DatabaseLockActivity(),
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAskMainCredentialDialogPositiveClick(
|
|
||||||
databaseUri: Uri?,
|
|
||||||
mainCredential: MainCredential
|
|
||||||
) {
|
|
||||||
databaseUri?.let {
|
|
||||||
mergeDatabaseFrom(it, mainCredential)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAskMainCredentialDialogNegativeClick(
|
|
||||||
databaseUri: Uri?,
|
|
||||||
mainCredential: MainCredential
|
|
||||||
) { }
|
|
||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.onResume()
|
super.onResume()
|
||||||
|
|
||||||
|
|||||||
@@ -20,45 +20,26 @@
|
|||||||
package com.kunzisoft.keepass.activities.dialogs
|
package com.kunzisoft.keepass.activities.dialogs
|
||||||
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||||
import com.kunzisoft.keepass.database.MainCredential
|
import com.kunzisoft.keepass.database.MainCredential
|
||||||
import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
|
import com.kunzisoft.keepass.utils.UriUtil.getDocumentFile
|
||||||
import com.kunzisoft.keepass.utils.getParcelableCompat
|
import com.kunzisoft.keepass.utils.getParcelableCompat
|
||||||
import com.kunzisoft.keepass.view.MainCredentialView
|
import com.kunzisoft.keepass.view.MainCredentialView
|
||||||
|
import com.kunzisoft.keepass.viewmodels.MainCredentialViewModel
|
||||||
|
|
||||||
class MainCredentialDialogFragment : DatabaseDialogFragment() {
|
class MainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||||
|
|
||||||
private var mainCredentialView: MainCredentialView? = null
|
private var mainCredentialView: MainCredentialView? = null
|
||||||
|
|
||||||
private var mListener: AskMainCredentialDialogListener? = null
|
|
||||||
|
|
||||||
private var mExternalFileHelper: ExternalFileHelper? = null
|
private var mExternalFileHelper: ExternalFileHelper? = null
|
||||||
|
|
||||||
interface AskMainCredentialDialogListener {
|
private val mMainCredentialViewModel: MainCredentialViewModel by activityViewModels()
|
||||||
fun onAskMainCredentialDialogPositiveClick(databaseUri: Uri?, mainCredential: MainCredential)
|
|
||||||
fun onAskMainCredentialDialogNegativeClick(databaseUri: Uri?, mainCredential: MainCredential)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onAttach(activity: Context) {
|
|
||||||
super.onAttach(activity)
|
|
||||||
try {
|
|
||||||
mListener = activity as AskMainCredentialDialogListener
|
|
||||||
} catch (e: ClassCastException) {
|
|
||||||
throw ClassCastException(activity.toString()
|
|
||||||
+ " must implement " + AskMainCredentialDialogListener::class.java.name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetach() {
|
|
||||||
mListener = null
|
|
||||||
super.onDetach()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
@@ -76,22 +57,21 @@ class MainCredentialDialogFragment : DatabaseDialogFragment() {
|
|||||||
databaseUri?.let {
|
databaseUri?.let {
|
||||||
root.findViewById<TextView>(R.id.title_database)?.text =
|
root.findViewById<TextView>(R.id.title_database)?.text =
|
||||||
it.getDocumentFile(requireContext())?.name
|
it.getDocumentFile(requireContext())?.name
|
||||||
}
|
|
||||||
builder.setView(root)
|
builder.setView(root)
|
||||||
// Add action buttons
|
// Add action buttons
|
||||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||||
mListener?.onAskMainCredentialDialogPositiveClick(
|
mMainCredentialViewModel.validateMainCredential(
|
||||||
databaseUri,
|
databaseUri = databaseUri,
|
||||||
retrieveMainCredential()
|
mainCredential = retrieveMainCredential()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
.setNegativeButton(android.R.string.cancel) { _, _ ->
|
||||||
mListener?.onAskMainCredentialDialogNegativeClick(
|
mMainCredentialViewModel.cancelMainCredential(
|
||||||
databaseUri,
|
databaseUri = databaseUri
|
||||||
retrieveMainCredential()
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
mExternalFileHelper = ExternalFileHelper(this)
|
mExternalFileHelper = ExternalFileHelper(this)
|
||||||
mExternalFileHelper?.buildOpenDocument { uri ->
|
mExternalFileHelper?.buildOpenDocument { uri ->
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
package com.kunzisoft.keepass.credentialprovider
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.biometric.BiometricManager
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
||||||
|
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
||||||
|
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
|
||||||
|
import androidx.biometric.BiometricPrompt
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
|
import com.kunzisoft.keepass.activities.dialogs.MainCredentialDialogFragment
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
||||||
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
|
import com.kunzisoft.keepass.utils.getEnumExtra
|
||||||
|
import com.kunzisoft.keepass.utils.putEnumExtra
|
||||||
|
import com.kunzisoft.keepass.view.toastError
|
||||||
|
|
||||||
|
class UserVerification {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val EXTRA_USER_VERIFICATION = "com.kunzisoft.keepass.extra.userVerification"
|
||||||
|
private const val EXTRA_USER_VERIFIED_WITH_AUTH = "com.kunzisoft.keepass.extra.userVerifiedWithAuth"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allowed authenticators for the User Verification
|
||||||
|
*/
|
||||||
|
const val ALLOWED_AUTHENTICATORS = BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the device supports the biometric prompt for User Verification
|
||||||
|
*/
|
||||||
|
fun Context.isAuthenticatorsAllowed(): Boolean {
|
||||||
|
return BiometricManager.from(this)
|
||||||
|
.canAuthenticate(ALLOWED_AUTHENTICATORS) == BIOMETRIC_SUCCESS
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the User Verification to the intent
|
||||||
|
*/
|
||||||
|
fun Intent.addUserVerification(
|
||||||
|
userVerification: UserVerificationRequirement,
|
||||||
|
userVerifiedWithAuth: Boolean
|
||||||
|
) {
|
||||||
|
putEnumExtra(EXTRA_USER_VERIFICATION, userVerification)
|
||||||
|
putExtra(EXTRA_USER_VERIFIED_WITH_AUTH, userVerifiedWithAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define if the User is verified with authentification from the intent
|
||||||
|
*/
|
||||||
|
fun Intent.getUserVerifiedWithAuth(): Boolean {
|
||||||
|
return getBooleanExtra(EXTRA_USER_VERIFIED_WITH_AUTH, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the User Verification from the intent
|
||||||
|
*/
|
||||||
|
fun Intent.removeUserVerification() {
|
||||||
|
removeExtra(EXTRA_USER_VERIFICATION)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the User verified with auth from the intent
|
||||||
|
*/
|
||||||
|
fun Intent.removeUserVerifiedWithAuth() {
|
||||||
|
removeExtra(EXTRA_USER_VERIFIED_WITH_AUTH)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the User Verification from the intent
|
||||||
|
*/
|
||||||
|
fun Intent.getUserVerificationCondition(): Boolean {
|
||||||
|
return (getEnumExtra<UserVerificationRequirement>(EXTRA_USER_VERIFICATION)
|
||||||
|
?: UserVerificationRequirement.PREFERRED) == UserVerificationRequirement.REQUIRED
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ask the user for verification
|
||||||
|
* Ask for the biometric if defined on the device
|
||||||
|
* Ask for the database credential otherwise
|
||||||
|
*/
|
||||||
|
fun FragmentActivity.askUserVerification(
|
||||||
|
database: ContextualDatabase,
|
||||||
|
onVerificationSucceeded: () -> Unit,
|
||||||
|
onVerificationFailed: () -> Unit
|
||||||
|
) {
|
||||||
|
if (this.intent.getUserVerificationCondition()) {
|
||||||
|
if (isAuthenticatorsAllowed()) {
|
||||||
|
BiometricPrompt(
|
||||||
|
this, ContextCompat.getMainExecutor(this),
|
||||||
|
object : BiometricPrompt.AuthenticationCallback() {
|
||||||
|
override fun onAuthenticationError(
|
||||||
|
errorCode: Int,
|
||||||
|
errString: CharSequence
|
||||||
|
) {
|
||||||
|
super.onAuthenticationError(errorCode, errString)
|
||||||
|
when (errorCode) {
|
||||||
|
BiometricPrompt.ERROR_CANCELED,
|
||||||
|
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
|
||||||
|
BiometricPrompt.ERROR_USER_CANCELED -> {
|
||||||
|
// No operation
|
||||||
|
Log.i("UserVerification", "$errString")
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
toastError(SecurityException("Authentication error: $errString"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
onVerificationFailed()
|
||||||
|
}
|
||||||
|
override fun onAuthenticationSucceeded(
|
||||||
|
result: BiometricPrompt.AuthenticationResult
|
||||||
|
) {
|
||||||
|
super.onAuthenticationSucceeded(result)
|
||||||
|
onVerificationSucceeded()
|
||||||
|
}
|
||||||
|
override fun onAuthenticationFailed() {
|
||||||
|
super.onAuthenticationFailed()
|
||||||
|
toastError(SecurityException(getString(R.string.device_unlock_not_recognized)))
|
||||||
|
onVerificationFailed()
|
||||||
|
}
|
||||||
|
}).authenticate(
|
||||||
|
BiometricPrompt.PromptInfo.Builder()
|
||||||
|
.setTitle(getString(R.string.user_verification_required))
|
||||||
|
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
|
||||||
|
.setConfirmationRequired(false)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
MainCredentialDialogFragment.getInstance(database.fileUri)
|
||||||
|
.show(
|
||||||
|
supportFragmentManager,
|
||||||
|
MainCredentialDialogFragment.TAG_ASK_MAIN_CREDENTIAL
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -31,8 +31,6 @@ import androidx.activity.result.contract.ActivityResultContracts
|
|||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.appcompat.app.AlertDialog
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.biometric.BiometricPrompt
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||||
@@ -45,16 +43,13 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.addTypeMode
|
|||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.setActivityResult
|
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.UserVerification.Companion.addUserVerification
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.UserVerification.Companion.askUserVerification
|
||||||
|
import com.kunzisoft.keepass.credentialprovider.UserVerification.Companion.getUserVerifiedWithAuth
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.ALLOWED_AUTHENTICATORS
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addUserVerification
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerificationCondition
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerifiedWithAuth
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.isAuthenticatorsAllowed
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removeUserVerification
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.viewmodel.CredentialLauncherViewModel
|
import com.kunzisoft.keepass.credentialprovider.viewmodel.CredentialLauncherViewModel
|
||||||
import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewModel
|
import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewModel
|
||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
@@ -63,8 +58,8 @@ import com.kunzisoft.keepass.model.SearchInfo
|
|||||||
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
import com.kunzisoft.keepass.services.DatabaseTaskNotificationService.Companion.ACTION_DATABASE_UPDATE_ENTRY_TASK
|
||||||
import com.kunzisoft.keepass.tasks.ActionRunnable
|
import com.kunzisoft.keepass.tasks.ActionRunnable
|
||||||
import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode
|
import com.kunzisoft.keepass.utils.AppUtil.randomRequestCode
|
||||||
import com.kunzisoft.keepass.utils.LOCK_ACTION
|
|
||||||
import com.kunzisoft.keepass.view.toastError
|
import com.kunzisoft.keepass.view.toastError
|
||||||
|
import com.kunzisoft.keepass.viewmodels.MainCredentialViewModel
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@@ -72,6 +67,7 @@ import java.util.UUID
|
|||||||
class PasskeyLauncherActivity : DatabaseLockActivity() {
|
class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||||
|
|
||||||
private val passkeyLauncherViewModel: PasskeyLauncherViewModel by viewModels()
|
private val passkeyLauncherViewModel: PasskeyLauncherViewModel by viewModels()
|
||||||
|
private val mainCredentialViewModel: MainCredentialViewModel by viewModels()
|
||||||
|
|
||||||
private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
private var mPasskeySelectionActivityResultLauncher: ActivityResultLauncher<Intent>? =
|
||||||
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
this.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
@@ -92,60 +88,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
// To manage https://github.com/Kunzisoft/KeePassDX/issues/2283
|
|
||||||
val userVerificationCondition = intent.getUserVerificationCondition()
|
|
||||||
if (userVerificationCondition) {
|
|
||||||
if (isAuthenticatorsAllowed().not()) {
|
|
||||||
intent.removeUserVerification()
|
|
||||||
sendBroadcast(Intent(LOCK_ACTION))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// super.onCreate must be after UserVerification to allow database lock
|
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
// Biometric must be after super.onCreate
|
|
||||||
if (userVerificationCondition) {
|
|
||||||
if (isAuthenticatorsAllowed()) {
|
|
||||||
BiometricPrompt(
|
|
||||||
this, ContextCompat.getMainExecutor(this),
|
|
||||||
object : BiometricPrompt.AuthenticationCallback() {
|
|
||||||
override fun onAuthenticationError(
|
|
||||||
errorCode: Int,
|
|
||||||
errString: CharSequence
|
|
||||||
) {
|
|
||||||
super.onAuthenticationError(errorCode, errString)
|
|
||||||
when (errorCode) {
|
|
||||||
BiometricPrompt.ERROR_CANCELED,
|
|
||||||
BiometricPrompt.ERROR_NEGATIVE_BUTTON,
|
|
||||||
BiometricPrompt.ERROR_USER_CANCELED -> {
|
|
||||||
// No operation
|
|
||||||
Log.i(TAG, "$errString")
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
toastError(SecurityException("Authentication error: $errString"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
passkeyLauncherViewModel.cancelResult()
|
|
||||||
}
|
|
||||||
override fun onAuthenticationSucceeded(
|
|
||||||
result: BiometricPrompt.AuthenticationResult
|
|
||||||
) {
|
|
||||||
super.onAuthenticationSucceeded(result)
|
|
||||||
passkeyLauncherViewModel.launchAction(userVerified = true, intent, mSpecialMode)
|
|
||||||
}
|
|
||||||
override fun onAuthenticationFailed() {
|
|
||||||
super.onAuthenticationFailed()
|
|
||||||
toastError(SecurityException(getString(R.string.device_unlock_not_recognized)))
|
|
||||||
passkeyLauncherViewModel.cancelResult()
|
|
||||||
}
|
|
||||||
}).authenticate(
|
|
||||||
BiometricPrompt.PromptInfo.Builder()
|
|
||||||
.setTitle(getString(R.string.user_verification_required))
|
|
||||||
.setAllowedAuthenticators(ALLOWED_AUTHENTICATORS)
|
|
||||||
.setConfirmationRequired(false)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
// Initialize the parameters
|
// Initialize the parameters
|
||||||
@@ -225,10 +168,38 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lifecycleScope.launch {
|
||||||
|
mainCredentialViewModel.uiState.collect { uiState ->
|
||||||
|
when (uiState) {
|
||||||
|
is MainCredentialViewModel.UIState.Loading -> {}
|
||||||
|
is MainCredentialViewModel.UIState.OnMainCredentialValidated -> {
|
||||||
|
// TODO Pass through UserVerification View Model
|
||||||
|
passkeyLauncherViewModel.launchAction(userVerified = true, intent, mSpecialMode)
|
||||||
|
}
|
||||||
|
is MainCredentialViewModel.UIState.OnMainCredentialCanceled -> {
|
||||||
|
passkeyLauncherViewModel.cancelResult()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onUnknownDatabaseRetrieved(database: ContextualDatabase?) {
|
override fun onUnknownDatabaseRetrieved(database: ContextualDatabase?) {
|
||||||
super.onUnknownDatabaseRetrieved(database)
|
super.onUnknownDatabaseRetrieved(database)
|
||||||
|
|
||||||
|
// To manage https://github.com/Kunzisoft/KeePassDX/issues/2283
|
||||||
|
database?.let {
|
||||||
|
askUserVerification(
|
||||||
|
database = it,
|
||||||
|
onVerificationSucceeded = {
|
||||||
|
passkeyLauncherViewModel.launchAction(userVerified = true, intent, mSpecialMode)
|
||||||
|
},
|
||||||
|
onVerificationFailed = {
|
||||||
|
passkeyLauncherViewModel.cancelResult()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
passkeyLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database)
|
passkeyLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -50,7 +50,6 @@ import com.kunzisoft.keepass.credentialprovider.activity.PasskeyLauncherActivity
|
|||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationOptions
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationOptions
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.isAuthenticatorsAllowed
|
|
||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
import com.kunzisoft.keepass.database.DatabaseTaskProvider
|
import com.kunzisoft.keepass.database.DatabaseTaskProvider
|
||||||
import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException
|
import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseException
|
||||||
@@ -67,7 +66,6 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
|
private var mDatabaseTaskProvider: DatabaseTaskProvider? = null
|
||||||
private var mDatabase: ContextualDatabase? = null
|
private var mDatabase: ContextualDatabase? = null
|
||||||
private lateinit var defaultIcon: Icon
|
private lateinit var defaultIcon: Icon
|
||||||
private lateinit var relaunchIcon: Icon
|
|
||||||
private var isAutoSelectAllowed: Boolean = false
|
private var isAutoSelectAllowed: Boolean = false
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@@ -86,11 +84,6 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
setTintBlendMode(BlendMode.DST)
|
setTintBlendMode(BlendMode.DST)
|
||||||
}
|
}
|
||||||
|
|
||||||
relaunchIcon = Icon.createWithResource(
|
|
||||||
this@PasskeyProviderService,
|
|
||||||
R.drawable.ic_clock_loader_red_24dp
|
|
||||||
)
|
|
||||||
|
|
||||||
isAutoSelectAllowed = isPasskeyAutoSelectEnable(this)
|
isAutoSelectAllowed = isPasskeyAutoSelectEnable(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -157,7 +150,8 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
val credentialIdList = publicKeyCredentialRequestOptions.allowCredentials
|
val credentialIdList = publicKeyCredentialRequestOptions.allowCredentials
|
||||||
.map { b64Encode(it.id) }
|
.map { b64Encode(it.id) }
|
||||||
val searchInfo = buildPasskeySearchInfo(relyingPartyId, credentialIdList)
|
val searchInfo = buildPasskeySearchInfo(relyingPartyId, credentialIdList)
|
||||||
val userVerification = publicKeyCredentialRequestOptions.userVerification
|
// TODO remove
|
||||||
|
val userVerification = UserVerificationRequirement.REQUIRED//publicKeyCredentialRequestOptions.userVerification
|
||||||
Log.d(TAG, "Build passkey search for UV $userVerification, " +
|
Log.d(TAG, "Build passkey search for UV $userVerification, " +
|
||||||
"RP $relyingPartyId and Credential IDs $credentialIdList")
|
"RP $relyingPartyId and Credential IDs $credentialIdList")
|
||||||
SearchHelper.checkAutoSearchInfo(
|
SearchHelper.checkAutoSearchInfo(
|
||||||
@@ -165,84 +159,70 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
database = mDatabase,
|
database = mDatabase,
|
||||||
searchInfo = searchInfo,
|
searchInfo = searchInfo,
|
||||||
onItemsFound = { database, items ->
|
onItemsFound = { database, items ->
|
||||||
manageUserVerification(
|
Log.d(TAG, "Add pending intent for passkey selection with found items")
|
||||||
passkeyEntries = passkeyEntries,
|
for (passkeyEntry in items) {
|
||||||
searchInfo = searchInfo,
|
PasskeyLauncherActivity.getPendingIntent(
|
||||||
option = option,
|
context = applicationContext,
|
||||||
userVerification = userVerification
|
specialMode = SpecialMode.SELECTION,
|
||||||
) {
|
nodeId = passkeyEntry.id,
|
||||||
Log.d(TAG, "Add pending intent for passkey selection with found items")
|
appOrigin = passkeyEntry.appOrigin,
|
||||||
for (passkeyEntry in items) {
|
userVerification = userVerification,
|
||||||
PasskeyLauncherActivity.getPendingIntent(
|
userVerifiedWithAuth = false
|
||||||
context = applicationContext,
|
)?.let { usagePendingIntent ->
|
||||||
specialMode = SpecialMode.SELECTION,
|
val passkey = passkeyEntry.passkey
|
||||||
nodeId = passkeyEntry.id,
|
passkeyEntries.add(
|
||||||
appOrigin = passkeyEntry.appOrigin,
|
PublicKeyCredentialEntry(
|
||||||
userVerification = userVerification,
|
context = applicationContext,
|
||||||
userVerifiedWithAuth = false
|
username = passkey?.username ?: "Unknown",
|
||||||
)?.let { usagePendingIntent ->
|
icon = passkeyEntry.buildIcon(
|
||||||
val passkey = passkeyEntry.passkey
|
this@PasskeyProviderService,
|
||||||
passkeyEntries.add(
|
database
|
||||||
PublicKeyCredentialEntry(
|
)?.apply {
|
||||||
context = applicationContext,
|
setTintBlendMode(BlendMode.DST)
|
||||||
username = passkey?.username ?: "Unknown",
|
} ?: defaultIcon,
|
||||||
icon = passkeyEntry.buildIcon(
|
pendingIntent = usagePendingIntent,
|
||||||
this@PasskeyProviderService,
|
beginGetPublicKeyCredentialOption = option,
|
||||||
database
|
displayName = passkeyEntry.getVisualTitle(),
|
||||||
)?.apply {
|
isAutoSelectAllowed = isAutoSelectAllowed
|
||||||
setTintBlendMode(BlendMode.DST)
|
|
||||||
} ?: defaultIcon,
|
|
||||||
pendingIntent = usagePendingIntent,
|
|
||||||
beginGetPublicKeyCredentialOption = option,
|
|
||||||
displayName = passkeyEntry.getVisualTitle(),
|
|
||||||
isAutoSelectAllowed = isAutoSelectAllowed
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
callback(passkeyEntries)
|
callback(passkeyEntries)
|
||||||
},
|
},
|
||||||
onItemNotFound = { _ ->
|
onItemNotFound = { _ ->
|
||||||
manageUserVerification(
|
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
|
||||||
passkeyEntries = passkeyEntries,
|
if (credentialIdList.isEmpty()) {
|
||||||
searchInfo = searchInfo,
|
Log.d(TAG, "Add pending intent for passkey selection in opened database")
|
||||||
option = option,
|
PasskeyLauncherActivity.getPendingIntent(
|
||||||
userVerification = userVerification,
|
context = applicationContext,
|
||||||
) {
|
specialMode = SpecialMode.SELECTION,
|
||||||
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
|
searchInfo = searchInfo,
|
||||||
if (credentialIdList.isEmpty()) {
|
userVerification = userVerification,
|
||||||
Log.d(TAG, "Add pending intent for passkey selection in opened database")
|
userVerifiedWithAuth = false
|
||||||
PasskeyLauncherActivity.getPendingIntent(
|
)?.let { pendingIntent ->
|
||||||
context = applicationContext,
|
passkeyEntries.add(
|
||||||
specialMode = SpecialMode.SELECTION,
|
PublicKeyCredentialEntry(
|
||||||
searchInfo = searchInfo,
|
context = applicationContext,
|
||||||
userVerification = userVerification,
|
username = getString(R.string.passkey_database_username),
|
||||||
userVerifiedWithAuth = false
|
displayName = getString(R.string.passkey_selection_description),
|
||||||
)?.let { pendingIntent ->
|
icon = defaultIcon,
|
||||||
passkeyEntries.add(
|
pendingIntent = pendingIntent,
|
||||||
PublicKeyCredentialEntry(
|
beginGetPublicKeyCredentialOption = option,
|
||||||
context = applicationContext,
|
lastUsedTime = Instant.now(),
|
||||||
username = getString(R.string.passkey_database_username),
|
isAutoSelectAllowed = isAutoSelectAllowed
|
||||||
displayName = getString(R.string.passkey_selection_description),
|
|
||||||
icon = defaultIcon,
|
|
||||||
pendingIntent = pendingIntent,
|
|
||||||
beginGetPublicKeyCredentialOption = option,
|
|
||||||
lastUsedTime = Instant.now(),
|
|
||||||
isAutoSelectAllowed = isAutoSelectAllowed
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
callback(passkeyEntries)
|
|
||||||
} else {
|
|
||||||
throw IOException(
|
|
||||||
getString(
|
|
||||||
R.string.error_passkey_credential_id,
|
|
||||||
relyingPartyId,
|
|
||||||
credentialIdList
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
callback(passkeyEntries)
|
||||||
|
} else {
|
||||||
|
throw IOException(
|
||||||
|
getString(
|
||||||
|
R.string.error_passkey_credential_id,
|
||||||
|
relyingPartyId,
|
||||||
|
credentialIdList
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onDatabaseClosed = {
|
onDatabaseClosed = {
|
||||||
@@ -272,42 +252,6 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* To easily manage user verification condition
|
|
||||||
*/
|
|
||||||
private fun manageUserVerification(
|
|
||||||
passkeyEntries: MutableList<CredentialEntry>,
|
|
||||||
searchInfo: SearchInfo,
|
|
||||||
option: BeginGetPublicKeyCredentialOption,
|
|
||||||
userVerification: UserVerificationRequirement,
|
|
||||||
standardAction: () -> Unit
|
|
||||||
) {
|
|
||||||
if (userVerification == UserVerificationRequirement.REQUIRED && isAuthenticatorsAllowed().not()) {
|
|
||||||
PasskeyLauncherActivity.getPendingIntent(
|
|
||||||
context = applicationContext,
|
|
||||||
specialMode = SpecialMode.SELECTION,
|
|
||||||
searchInfo = searchInfo,
|
|
||||||
userVerification = userVerification,
|
|
||||||
userVerifiedWithAuth = true
|
|
||||||
)?.let { pendingIntent ->
|
|
||||||
passkeyEntries.add(
|
|
||||||
PublicKeyCredentialEntry(
|
|
||||||
context = applicationContext,
|
|
||||||
username = getString(R.string.passkey_database_username),
|
|
||||||
displayName = getString(R.string.passkey_relaunch_database_description),
|
|
||||||
icon = relaunchIcon,
|
|
||||||
pendingIntent = pendingIntent,
|
|
||||||
beginGetPublicKeyCredentialOption = option,
|
|
||||||
lastUsedTime = Instant.now(),
|
|
||||||
isAutoSelectAllowed = isAutoSelectAllowed
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
standardAction()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBeginCreateCredentialRequest(
|
override fun onBeginCreateCredentialRequest(
|
||||||
request: BeginCreateCredentialRequest,
|
request: BeginCreateCredentialRequest,
|
||||||
cancellationSignal: CancellationSignal,
|
cancellationSignal: CancellationSignal,
|
||||||
|
|||||||
@@ -30,10 +30,6 @@ import android.security.keystore.KeyProperties
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
import androidx.biometric.BiometricManager
|
|
||||||
import androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_WEAK
|
|
||||||
import androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL
|
|
||||||
import androidx.biometric.BiometricManager.BIOMETRIC_SUCCESS
|
|
||||||
import androidx.credentials.CreatePublicKeyCredentialRequest
|
import androidx.credentials.CreatePublicKeyCredentialRequest
|
||||||
import androidx.credentials.CreatePublicKeyCredentialResponse
|
import androidx.credentials.CreatePublicKeyCredentialResponse
|
||||||
import androidx.credentials.GetPublicKeyCredentialOption
|
import androidx.credentials.GetPublicKeyCredentialOption
|
||||||
@@ -60,7 +56,6 @@ import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredential
|
|||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialCreationParameters
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialRequestOptions
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.PublicKeyCredentialUsageParameters
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.UserVerificationRequirement
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists.getOriginFromPrivilegedAllowLists
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PrivilegedAllowLists.getOriginFromPrivilegedAllowLists
|
||||||
import com.kunzisoft.keepass.model.AndroidOrigin
|
import com.kunzisoft.keepass.model.AndroidOrigin
|
||||||
import com.kunzisoft.keepass.model.AppOrigin
|
import com.kunzisoft.keepass.model.AppOrigin
|
||||||
@@ -68,9 +63,7 @@ import com.kunzisoft.keepass.model.EntryInfo
|
|||||||
import com.kunzisoft.keepass.model.Passkey
|
import com.kunzisoft.keepass.model.Passkey
|
||||||
import com.kunzisoft.keepass.utils.AppUtil
|
import com.kunzisoft.keepass.utils.AppUtil
|
||||||
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
import com.kunzisoft.keepass.utils.StringUtil.toHexString
|
||||||
import com.kunzisoft.keepass.utils.getEnumExtra
|
|
||||||
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
||||||
import com.kunzisoft.keepass.utils.putEnumExtra
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
@@ -98,8 +91,6 @@ object PasskeyHelper {
|
|||||||
private const val EXTRA_APP_ORIGIN = "com.kunzisoft.keepass.extra.appOrigin"
|
private const val EXTRA_APP_ORIGIN = "com.kunzisoft.keepass.extra.appOrigin"
|
||||||
private const val EXTRA_TIMESTAMP = "com.kunzisoft.keepass.extra.timestamp"
|
private const val EXTRA_TIMESTAMP = "com.kunzisoft.keepass.extra.timestamp"
|
||||||
private const val EXTRA_AUTHENTICATION_CODE = "com.kunzisoft.keepass.extra.authenticationCode"
|
private const val EXTRA_AUTHENTICATION_CODE = "com.kunzisoft.keepass.extra.authenticationCode"
|
||||||
private const val EXTRA_USER_VERIFICATION = "com.kunzisoft.keepass.extra.userVerification"
|
|
||||||
private const val EXTRA_USER_VERIFIED_WITH_AUTH = "com.kunzisoft.keepass.extra.userVerifiedWithAuth"
|
|
||||||
|
|
||||||
private const val SEPARATOR = "_"
|
private const val SEPARATOR = "_"
|
||||||
|
|
||||||
@@ -116,60 +107,6 @@ object PasskeyHelper {
|
|||||||
|
|
||||||
private val internalSecureRandom: SecureRandom = SecureRandom()
|
private val internalSecureRandom: SecureRandom = SecureRandom()
|
||||||
|
|
||||||
/**
|
|
||||||
* Add the User Verification to the intent
|
|
||||||
*/
|
|
||||||
fun Intent.addUserVerification(
|
|
||||||
userVerification: UserVerificationRequirement,
|
|
||||||
userVerifiedWithAuth: Boolean
|
|
||||||
) {
|
|
||||||
putEnumExtra(EXTRA_USER_VERIFICATION, userVerification)
|
|
||||||
putExtra(EXTRA_USER_VERIFIED_WITH_AUTH, userVerifiedWithAuth)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the User Verification from the intent
|
|
||||||
*/
|
|
||||||
fun Intent.getUserVerificationCondition(): Boolean {
|
|
||||||
return (getEnumExtra<UserVerificationRequirement>(EXTRA_USER_VERIFICATION)
|
|
||||||
?: UserVerificationRequirement.PREFERRED) == UserVerificationRequirement.REQUIRED
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Define if the User is verified with authentification from the intent
|
|
||||||
*/
|
|
||||||
fun Intent.getUserVerifiedWithAuth(): Boolean {
|
|
||||||
return getBooleanExtra(EXTRA_USER_VERIFIED_WITH_AUTH, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the User Verification from the intent
|
|
||||||
*/
|
|
||||||
fun Intent.removeUserVerification() {
|
|
||||||
removeExtra(EXTRA_USER_VERIFICATION)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove the User verified with auth from the intent
|
|
||||||
*/
|
|
||||||
fun Intent.removeUserVerifiedWithAuth() {
|
|
||||||
removeExtra(EXTRA_USER_VERIFIED_WITH_AUTH)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Allowed authenticators for the User Verification
|
|
||||||
*/
|
|
||||||
const val ALLOWED_AUTHENTICATORS = BIOMETRIC_WEAK or DEVICE_CREDENTIAL
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if the device supports the biometric prompt for User Verification
|
|
||||||
*/
|
|
||||||
fun Context.isAuthenticatorsAllowed(): Boolean {
|
|
||||||
return BiometricManager.from(this)
|
|
||||||
.canAuthenticate(ALLOWED_AUTHENTICATORS) == BIOMETRIC_SUCCESS
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add an authentication code generated by an entry to the intent
|
* Add an authentication code generated by an entry to the intent
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -18,13 +18,13 @@ import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveNod
|
|||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||||
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.UserVerification.Companion.getUserVerificationCondition
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
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.buildCreatePublicKeyCredentialResponse
|
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.buildPasskeyPublicKeyCredential
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.checkSecurity
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.checkSecurity
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getUserVerificationCondition
|
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.getVerifiedGETClientDataResponse
|
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.removeAppOrigin
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey
|
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.removePasskey
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
package com.kunzisoft.keepass.viewmodels
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.kunzisoft.keepass.database.MainCredential
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel for the Main Credential Dialog
|
||||||
|
* Easily retrieves main credential from the database identified by its URI
|
||||||
|
*/
|
||||||
|
class MainCredentialViewModel: ViewModel() {
|
||||||
|
|
||||||
|
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||||
|
val uiState: StateFlow<UIState> = mUiState
|
||||||
|
|
||||||
|
fun validateMainCredential(
|
||||||
|
databaseUri: Uri,
|
||||||
|
mainCredential: MainCredential
|
||||||
|
) {
|
||||||
|
mUiState.value = UIState.OnMainCredentialValidated(databaseUri, mainCredential)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun cancelMainCredential(
|
||||||
|
databaseUri: Uri
|
||||||
|
) {
|
||||||
|
mUiState.value = UIState.OnMainCredentialCanceled(databaseUri, MainCredential())
|
||||||
|
}
|
||||||
|
|
||||||
|
sealed class UIState {
|
||||||
|
object Loading: UIState()
|
||||||
|
data class OnMainCredentialValidated(
|
||||||
|
val databaseUri: Uri,
|
||||||
|
val mainCredential: MainCredential
|
||||||
|
): UIState()
|
||||||
|
data class OnMainCredentialCanceled(
|
||||||
|
val databaseUri: Uri,
|
||||||
|
val mainCredential: MainCredential
|
||||||
|
): UIState()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:height="24dp"
|
|
||||||
android:width="24dp"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:viewportWidth="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="#E50808"
|
|
||||||
android:pathData="M480,880Q397,880 324,848.5Q251,817 197,763Q143,709 111.5,636Q80,563 80,480Q80,397 111.5,324Q143,251 197,197Q251,143 324,111.5Q397,80 480,80Q563,80 636,111.5Q709,143 763,197Q817,251 848.5,324Q880,397 880,480Q880,563 848.5,636Q817,709 763,763Q709,817 636,848.5Q563,880 480,880ZM253,707L480,480L480,160Q346,160 253,253Q160,346 160,480Q160,544 184,603Q208,662 253,707Z"/>
|
|
||||||
</vector>
|
|
||||||
@@ -767,7 +767,6 @@
|
|||||||
<string name="passkey_selection_description">Select an existing passkey</string>
|
<string name="passkey_selection_description">Select an existing passkey</string>
|
||||||
<string name="passkey_database_username">KeePassDX Database</string>
|
<string name="passkey_database_username">KeePassDX Database</string>
|
||||||
<string name="passkey_locked_database_description">Select to unlock</string>
|
<string name="passkey_locked_database_description">Select to unlock</string>
|
||||||
<string name="passkey_relaunch_database_description">Reauthenticate (No Device Auth)</string>
|
|
||||||
<string name="passkey_username">Passkey Username</string>
|
<string name="passkey_username">Passkey Username</string>
|
||||||
<string name="passkey_private_key">Passkey Private Key</string>
|
<string name="passkey_private_key">Passkey Private Key</string>
|
||||||
<string name="passkey_credential_id">Passkey Credential Id</string>
|
<string name="passkey_credential_id">Passkey Credential Id</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user