mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
fix: Registration and webDomain coroutine
This commit is contained in:
@@ -78,6 +78,25 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
// Initialize the parameters
|
||||
autofillLauncherViewModel.uiState.collect { uiState ->
|
||||
when (uiState) {
|
||||
AutofillLauncherViewModel.UIState.Loading -> {}
|
||||
is AutofillLauncherViewModel.UIState.ShowBlockRestartMessage -> {
|
||||
showBlockRestartMessage()
|
||||
autofillLauncherViewModel.cancelResult()
|
||||
}
|
||||
is AutofillLauncherViewModel.UIState.ShowReadOnlyMessage -> {
|
||||
showReadOnlySaveMessage()
|
||||
autofillLauncherViewModel.cancelResult()
|
||||
}
|
||||
is AutofillLauncherViewModel.UIState.ShowAutofillSuggestionMessage -> {
|
||||
showAutofillSuggestionMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
// Retrieve the UI
|
||||
autofillLauncherViewModel.credentialUiState.collect { uiState ->
|
||||
@@ -132,25 +151,6 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
// Initialize the parameters
|
||||
autofillLauncherViewModel.uiState.collect { uiState ->
|
||||
when (uiState) {
|
||||
AutofillLauncherViewModel.UIState.Loading -> {}
|
||||
is AutofillLauncherViewModel.UIState.ShowBlockRestartMessage -> {
|
||||
showBlockRestartMessage()
|
||||
autofillLauncherViewModel.cancelResult()
|
||||
}
|
||||
is AutofillLauncherViewModel.UIState.ShowReadOnlyMessage -> {
|
||||
showReadOnlySaveMessage()
|
||||
autofillLauncherViewModel.cancelResult()
|
||||
}
|
||||
is AutofillLauncherViewModel.UIState.ShowAutofillSuggestionMessage -> {
|
||||
showAutofillSuggestionMessage()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
||||
|
||||
@@ -33,7 +33,6 @@ import com.kunzisoft.keepass.database.exception.RegisterInReadOnlyDatabaseExcept
|
||||
import com.kunzisoft.keepass.database.helper.SearchHelper
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
|
||||
import com.kunzisoft.keepass.utils.KeyboardUtil.isKeyboardActivatedInSettings
|
||||
import com.kunzisoft.keepass.utils.getParcelableCompat
|
||||
import com.kunzisoft.keepass.view.toastError
|
||||
@@ -105,14 +104,12 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
|
||||
sharedWebDomain: String?,
|
||||
otpString: String?) {
|
||||
// Build domain search param
|
||||
getConcreteWebDomain(this, sharedWebDomain) { concreteWebDomain ->
|
||||
val searchInfo = SearchInfo().apply {
|
||||
this.webDomain = concreteWebDomain
|
||||
this.webDomain = sharedWebDomain
|
||||
this.otpString = otpString
|
||||
}
|
||||
launch(database, searchInfo)
|
||||
}
|
||||
}
|
||||
|
||||
private fun launch(database: ContextualDatabase?,
|
||||
searchInfo: SearchInfo) {
|
||||
|
||||
@@ -46,6 +46,7 @@ import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAppOrigin
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode
|
||||
import com.kunzisoft.keepass.credentialprovider.viewmodel.CredentialLauncherViewModel
|
||||
import com.kunzisoft.keepass.credentialprovider.viewmodel.PasskeyLauncherViewModel
|
||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||
import com.kunzisoft.keepass.model.AppOrigin
|
||||
@@ -106,7 +107,17 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
nodeId = uiState.nodeId
|
||||
)
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.SetActivityResult -> {
|
||||
is PasskeyLauncherViewModel.UIState.UpdateEntry -> {
|
||||
updateEntry(uiState.oldEntry, uiState.newEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
lifecycleScope.launch {
|
||||
passkeyLauncherViewModel.credentialUiState.collect { uiState ->
|
||||
when (uiState) {
|
||||
is CredentialLauncherViewModel.UIState.Loading -> {}
|
||||
is CredentialLauncherViewModel.UIState.SetActivityResult -> {
|
||||
setActivityResult(
|
||||
typeMode = TypeMode.PASSKEY,
|
||||
lockDatabase = uiState.lockDatabase,
|
||||
@@ -114,11 +125,11 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
data = uiState.data
|
||||
)
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.ShowError -> {
|
||||
is CredentialLauncherViewModel.UIState.ShowError -> {
|
||||
toastError(uiState.error)
|
||||
passkeyLauncherViewModel.cancelResult()
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.LaunchGroupActivityForSelection -> {
|
||||
is CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection -> {
|
||||
GroupActivity.launchForSelection(
|
||||
context = this@PasskeyLauncherActivity,
|
||||
database = uiState.database,
|
||||
@@ -127,7 +138,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
searchInfo = uiState.searchInfo
|
||||
)
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.LaunchGroupActivityForRegistration -> {
|
||||
is CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration -> {
|
||||
GroupActivity.launchForRegistration(
|
||||
context = this@PasskeyLauncherActivity,
|
||||
activityResultLauncher = mPasskeyRegistrationActivityResultLauncher,
|
||||
@@ -136,7 +147,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
typeMode = uiState.typeMode
|
||||
)
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection -> {
|
||||
is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection -> {
|
||||
FileDatabaseSelectActivity.launchForSelection(
|
||||
context = this@PasskeyLauncherActivity,
|
||||
typeMode = uiState.typeMode,
|
||||
@@ -144,7 +155,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
searchInfo = uiState.searchInfo,
|
||||
)
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration -> {
|
||||
is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration -> {
|
||||
FileDatabaseSelectActivity.launchForRegistration(
|
||||
context = this@PasskeyLauncherActivity,
|
||||
activityResultLauncher = mPasskeyRegistrationActivityResultLauncher,
|
||||
@@ -152,9 +163,6 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
typeMode = uiState.typeMode
|
||||
)
|
||||
}
|
||||
is PasskeyLauncherViewModel.UIState.UpdateEntry -> {
|
||||
updateEntry(uiState.oldEntry, uiState.newEntry)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +170,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
||||
|
||||
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
||||
super.onDatabaseRetrieved(database)
|
||||
passkeyLauncherViewModel.launchPasskeyActionIfNeeded(intent, mSpecialMode, database)
|
||||
passkeyLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database)
|
||||
}
|
||||
|
||||
override fun onDatabaseActionFinished(
|
||||
|
||||
@@ -52,7 +52,6 @@ import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
|
||||
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
||||
import java.io.IOException
|
||||
import kotlin.math.min
|
||||
@@ -380,7 +379,6 @@ object AutofillHelper {
|
||||
database: ContextualDatabase,
|
||||
entriesInfo: List<EntryInfo>,
|
||||
parseResult: StructureParser.Result,
|
||||
concreteWebDomain: String?,
|
||||
autofillComponent: AutofillComponent
|
||||
): FillResponse? {
|
||||
val responseBuilder = FillResponse.Builder()
|
||||
@@ -448,7 +446,7 @@ object AutofillHelper {
|
||||
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = concreteWebDomain
|
||||
webDomain = parseResult.webDomain
|
||||
webScheme = parseResult.webScheme
|
||||
manualSelection = true
|
||||
}
|
||||
@@ -525,13 +523,12 @@ object AutofillHelper {
|
||||
autofillComponent: AutofillComponent,
|
||||
database: ContextualDatabase,
|
||||
entriesInfo: List<EntryInfo>,
|
||||
onIntentCreated: suspend (Intent) -> Unit
|
||||
onIntentCreated: (Intent) -> Unit
|
||||
) {
|
||||
if (entriesInfo.isEmpty()) {
|
||||
throw IOException("No entries found")
|
||||
} else {
|
||||
StructureParser(autofillComponent.assistStructure).parse()?.let { result ->
|
||||
getConcreteWebDomain(context, result.webDomain) { concreteWebDomain ->
|
||||
// New Response
|
||||
onIntentCreated(Intent().putExtra(
|
||||
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
|
||||
@@ -540,11 +537,9 @@ object AutofillHelper {
|
||||
database = database,
|
||||
entriesInfo = entriesInfo,
|
||||
parseResult = result,
|
||||
concreteWebDomain = concreteWebDomain,
|
||||
autofillComponent = autofillComponent
|
||||
)
|
||||
))
|
||||
}
|
||||
} ?: throw IOException("Unable to parse the structure")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,6 @@ import com.kunzisoft.keepass.model.RegisterInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
|
||||
import org.joda.time.DateTime
|
||||
|
||||
|
||||
@@ -116,10 +115,9 @@ class KeeAutofillService : AutofillService() {
|
||||
webDomain = parseResult.webDomain,
|
||||
webDomainBlocklist = webDomainBlocklist)
|
||||
) {
|
||||
getConcreteWebDomain(this, parseResult.webDomain) { webDomainWithoutSubDomain ->
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = webDomainWithoutSubDomain
|
||||
webDomain = parseResult.webDomain
|
||||
webScheme = parseResult.webScheme
|
||||
}
|
||||
val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||
@@ -128,30 +126,13 @@ class KeeAutofillService : AutofillService() {
|
||||
} else {
|
||||
null
|
||||
}
|
||||
launchSelection(mDatabase,
|
||||
searchInfo,
|
||||
parseResult,
|
||||
AutofillComponent(
|
||||
val autofillComponent = AutofillComponent(
|
||||
latestStructure,
|
||||
inlineSuggestionsRequest
|
||||
),
|
||||
callback
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun launchSelection(
|
||||
database: ContextualDatabase?,
|
||||
searchInfo: SearchInfo,
|
||||
parseResult: StructureParser.Result,
|
||||
autofillComponent: AutofillComponent,
|
||||
callback: FillCallback
|
||||
) {
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = this,
|
||||
database = database,
|
||||
database = mDatabase,
|
||||
searchInfo = searchInfo,
|
||||
onItemsFound = { openedDatabase, items ->
|
||||
callback.onSuccess(
|
||||
@@ -160,7 +141,6 @@ class KeeAutofillService : AutofillService() {
|
||||
database = openedDatabase,
|
||||
entriesInfo = items,
|
||||
parseResult = parseResult,
|
||||
concreteWebDomain = searchInfo.webDomain,
|
||||
autofillComponent = autofillComponent
|
||||
)
|
||||
)
|
||||
@@ -177,6 +157,8 @@ class KeeAutofillService : AutofillService() {
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("RestrictedApi")
|
||||
private fun showUIForEntrySelection(
|
||||
@@ -377,7 +359,7 @@ class KeeAutofillService : AutofillService() {
|
||||
|
||||
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
|
||||
var success = false
|
||||
if (askToSaveData) {
|
||||
if (askToSaveData && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
val latestStructure = request.fillContexts.last().structure
|
||||
StructureParser(latestStructure).parse(true)?.let { parseResult ->
|
||||
|
||||
@@ -403,10 +385,9 @@ class KeeAutofillService : AutofillService() {
|
||||
}
|
||||
|
||||
// Show UI to save data
|
||||
getConcreteWebDomain(applicationContext, parseResult.webDomain) { concreteWebDomain ->
|
||||
val searchInfo = SearchInfo().apply {
|
||||
applicationId = parseResult.applicationId
|
||||
webDomain = concreteWebDomain
|
||||
webDomain = parseResult.webDomain
|
||||
webScheme = parseResult.webScheme
|
||||
}
|
||||
val registerInfo = RegisterInfo(
|
||||
@@ -423,19 +404,12 @@ class KeeAutofillService : AutofillService() {
|
||||
}
|
||||
)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||
// TODO Test pending intent
|
||||
AutofillLauncherActivity.getPendingIntentForRegistration(
|
||||
this,
|
||||
registerInfo
|
||||
)?.intentSender?.let { intentSender ->
|
||||
callback.onSuccess(intentSender)
|
||||
} ?: callback.onFailure("Unable to launch registration")
|
||||
} else {
|
||||
AutofillLauncherActivity.launchForRegistration(this, registerInfo)
|
||||
success = true
|
||||
callback.onSuccess()
|
||||
}
|
||||
callback.onSuccess(intentSender)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,9 +120,9 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
for (option in request.beginGetCredentialOptions) {
|
||||
when (option) {
|
||||
is BeginGetPublicKeyCredentialOption -> {
|
||||
credentialEntries.addAll(
|
||||
populatePasskeyData(option)
|
||||
)
|
||||
populatePasskeyData(option) { listCredentials ->
|
||||
credentialEntries.addAll(listCredentials)
|
||||
}
|
||||
return BeginGetCredentialResponse(credentialEntries)
|
||||
}
|
||||
}
|
||||
@@ -131,7 +131,10 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun populatePasskeyData(option: BeginGetPublicKeyCredentialOption): List<CredentialEntry> {
|
||||
private fun populatePasskeyData(
|
||||
option: BeginGetPublicKeyCredentialOption,
|
||||
callback: (List<CredentialEntry>) -> Unit
|
||||
) {
|
||||
|
||||
val passkeyEntries: MutableList<CredentialEntry> = mutableListOf()
|
||||
|
||||
@@ -167,6 +170,7 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
)
|
||||
}
|
||||
}
|
||||
callback(passkeyEntries)
|
||||
},
|
||||
onItemNotFound = { _ ->
|
||||
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
|
||||
@@ -189,6 +193,7 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
)
|
||||
)
|
||||
}
|
||||
callback(passkeyEntries)
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
Log.d(TAG, "Add pending intent for passkey selection in closed database")
|
||||
@@ -211,9 +216,9 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
)
|
||||
)
|
||||
}
|
||||
callback(passkeyEntries)
|
||||
}
|
||||
)
|
||||
return passkeyEntries
|
||||
}
|
||||
|
||||
override fun onBeginCreateCredentialRequest(
|
||||
@@ -223,7 +228,9 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
) {
|
||||
Log.d(javaClass.simpleName, "onBeginCreateCredentialRequest called")
|
||||
try {
|
||||
callback.onResult(processCreateCredentialRequest(request))
|
||||
processCreateCredentialRequest(request) {
|
||||
callback.onResult(BeginCreateCredentialResponse(it))
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(javaClass.simpleName, "onBeginCreateCredentialRequest error", e)
|
||||
toastError(e)
|
||||
@@ -231,11 +238,14 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun processCreateCredentialRequest(request: BeginCreateCredentialRequest): BeginCreateCredentialResponse {
|
||||
private fun processCreateCredentialRequest(
|
||||
request: BeginCreateCredentialRequest,
|
||||
callback: (List<CreateEntry>) -> Unit
|
||||
) {
|
||||
when (request) {
|
||||
is BeginCreatePublicKeyCredentialRequest -> {
|
||||
// Request is passkey type
|
||||
return handleCreatePasskeyQuery(request)
|
||||
handleCreatePasskeyQuery(request, callback)
|
||||
}
|
||||
}
|
||||
// request type not supported
|
||||
@@ -264,7 +274,10 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCreatePasskeyQuery(request: BeginCreatePublicKeyCredentialRequest): BeginCreateCredentialResponse {
|
||||
private fun handleCreatePasskeyQuery(
|
||||
request: BeginCreatePublicKeyCredentialRequest,
|
||||
callback: (List<CreateEntry>) -> Unit
|
||||
) {
|
||||
val databaseName = mDatabase?.name
|
||||
val accountName =
|
||||
if (databaseName?.isBlank() != false)
|
||||
@@ -310,6 +323,7 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
}
|
||||
}*/
|
||||
}
|
||||
callback(createEntries)
|
||||
},
|
||||
onItemNotFound = { database ->
|
||||
// To create a new entry
|
||||
@@ -318,6 +332,7 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
} else {
|
||||
createEntries.addPendingIntentCreationNewEntry(accountName, searchInfo)
|
||||
}
|
||||
callback(createEntries)
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
// Launch the passkey launcher activity to open the database
|
||||
@@ -335,10 +350,9 @@ class PasskeyProviderService : CredentialProviderService() {
|
||||
)
|
||||
)
|
||||
}
|
||||
callback(createEntries)
|
||||
}
|
||||
)
|
||||
|
||||
return BeginCreateCredentialResponse(createEntries)
|
||||
}
|
||||
|
||||
override fun onClearCredentialStateRequest(
|
||||
|
||||
@@ -179,13 +179,13 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie
|
||||
.getEntryById(NodeIdUUID(nodeId))
|
||||
?.getEntryInfo(database)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
AutofillHelper.buildResponse(
|
||||
context = getApplication(),
|
||||
autofillComponent = autofillComponent,
|
||||
database = database,
|
||||
entriesInfo = entries
|
||||
) { intent ->
|
||||
withContext(Dispatchers.Main) {
|
||||
setResult(intent)
|
||||
}
|
||||
}
|
||||
@@ -262,7 +262,6 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie
|
||||
fun manageRegistrationResult(
|
||||
activityResult: ActivityResult
|
||||
) {
|
||||
val intent = activityResult.data
|
||||
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
||||
Log.e(TAG, "Unable to create registration response for autofill", e)
|
||||
showError(e)
|
||||
@@ -270,14 +269,11 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie
|
||||
val responseIntent = Intent()
|
||||
when (activityResult.resultCode) {
|
||||
RESULT_OK -> {
|
||||
withContext(Dispatchers.IO) {
|
||||
Log.d(TAG, "Autofill registration result")
|
||||
// TODO Result
|
||||
withContext(Dispatchers.Main) {
|
||||
setResult(responseIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
RESULT_CANCELED -> {
|
||||
withContext(Dispatchers.Main) {
|
||||
cancelResult()
|
||||
|
||||
@@ -58,6 +58,10 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie
|
||||
mDatabase = database
|
||||
}
|
||||
|
||||
open fun onExceptionOccurred(e: Throwable) {
|
||||
showError(e)
|
||||
}
|
||||
|
||||
fun launchActionIfNeeded(
|
||||
intent: Intent,
|
||||
specialMode: SpecialMode,
|
||||
@@ -67,7 +71,7 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie
|
||||
if (isResultLauncherRegistered.not()) {
|
||||
isResultLauncherRegistered = true
|
||||
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
||||
showError(e)
|
||||
onExceptionOccurred(e)
|
||||
}) {
|
||||
launchAction(intent, specialMode, database)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ import androidx.annotation.RequiresApi
|
||||
import androidx.credentials.GetCredentialResponse
|
||||
import androidx.credentials.exceptions.GetCredentialUnknownException
|
||||
import androidx.credentials.provider.PendingIntentHandler
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveNodeId
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||
@@ -56,7 +55,7 @@ import java.io.InvalidObjectException
|
||||
import java.util.UUID
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
||||
class PasskeyLauncherViewModel(application: Application): AndroidViewModel(application) {
|
||||
class PasskeyLauncherViewModel(application: Application): CredentialLauncherViewModel(application) {
|
||||
|
||||
private var mUsageParameters: PublicKeyCredentialUsageParameters? = null
|
||||
private var mCreationParameters: PublicKeyCredentialCreationParameters? = null
|
||||
@@ -64,12 +63,9 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
|
||||
private var mBackupEligibility: Boolean = true
|
||||
private var mBackupState: Boolean = false
|
||||
private var mLockDatabase: Boolean = true
|
||||
|
||||
private var isResultLauncherRegistered: Boolean = false
|
||||
|
||||
private val _uiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||
val uiState: StateFlow<UIState> = _uiState
|
||||
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||
val uiState: StateFlow<UIState> = mUiState
|
||||
|
||||
fun initialize() {
|
||||
mBackupEligibility = PreferencesUtil.isPasskeyBackupEligibilityEnable(getApplication())
|
||||
@@ -79,19 +75,14 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
fun showAppPrivilegedDialog(
|
||||
temptingApp: AndroidPrivilegedApp
|
||||
) {
|
||||
_uiState.value = UIState.ShowAppPrivilegedDialog(temptingApp)
|
||||
mUiState.value = UIState.ShowAppPrivilegedDialog(temptingApp)
|
||||
}
|
||||
|
||||
fun showAppSignatureDialog(
|
||||
temptingApp: AppOrigin,
|
||||
nodeId: UUID
|
||||
) {
|
||||
_uiState.value = UIState.ShowAppSignatureDialog(temptingApp, nodeId)
|
||||
}
|
||||
|
||||
fun showError(error: Throwable) {
|
||||
Log.e(TAG, "Error on passkey launch", error)
|
||||
_uiState.value = UIState.ShowError(error)
|
||||
mUiState.value = UIState.ShowAppSignatureDialog(temptingApp, nodeId)
|
||||
}
|
||||
|
||||
fun saveCustomPrivilegedApp(
|
||||
@@ -107,7 +98,7 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
context = getApplication(),
|
||||
privilegedApps = listOf(temptingApp)
|
||||
)
|
||||
launchPasskeyAction(
|
||||
launchAction(
|
||||
intent = intent,
|
||||
specialMode = specialMode,
|
||||
database = database
|
||||
@@ -139,54 +130,22 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
)
|
||||
entryInfo.saveAppOrigin(database, temptingApp)
|
||||
newEntry.setEntryInfo(database, entryInfo)
|
||||
_uiState.value = UIState.UpdateEntry(
|
||||
mUiState.value = UIState.UpdateEntry(
|
||||
oldEntry = entry,
|
||||
newEntry = newEntry
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun setResult(intent: Intent) {
|
||||
// Remove the launcher register
|
||||
isResultLauncherRegistered = false
|
||||
_uiState.value = UIState.SetActivityResult(
|
||||
lockDatabase = mLockDatabase,
|
||||
resultCode = RESULT_OK,
|
||||
data = intent
|
||||
)
|
||||
}
|
||||
|
||||
fun cancelResult() {
|
||||
isResultLauncherRegistered = false
|
||||
_uiState.value = UIState.SetActivityResult(
|
||||
lockDatabase = mLockDatabase,
|
||||
resultCode = RESULT_CANCELED
|
||||
)
|
||||
}
|
||||
|
||||
fun launchPasskeyActionIfNeeded(
|
||||
intent: Intent,
|
||||
specialMode: SpecialMode,
|
||||
database: ContextualDatabase?
|
||||
) {
|
||||
if (isResultLauncherRegistered.not()) {
|
||||
isResultLauncherRegistered = true
|
||||
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
||||
override fun onExceptionOccurred(e: Throwable) {
|
||||
if (e is PrivilegedAllowLists.PrivilegedException) {
|
||||
showAppPrivilegedDialog(e.temptingApp)
|
||||
} else {
|
||||
showError(e)
|
||||
}
|
||||
}) {
|
||||
launchPasskeyAction(intent, specialMode, database)
|
||||
}
|
||||
super.onExceptionOccurred(e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch the main action to manage Passkey
|
||||
*/
|
||||
private suspend fun launchPasskeyAction(
|
||||
override suspend fun launchAction(
|
||||
intent: Intent,
|
||||
specialMode: SpecialMode,
|
||||
database: ContextualDatabase?
|
||||
@@ -260,7 +219,8 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
TAG, "No Passkey found for selection," +
|
||||
"launch manual selection in opened database"
|
||||
)
|
||||
_uiState.value = UIState.LaunchGroupActivityForSelection(
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection(
|
||||
database = openedDatabase,
|
||||
searchInfo = searchInfo,
|
||||
typeMode = TypeMode.PASSKEY
|
||||
@@ -268,8 +228,8 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
Log.d(TAG, "Manual passkey selection in closed database")
|
||||
_uiState.value =
|
||||
UIState.LaunchFileDatabaseSelectActivityForSelection(
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection(
|
||||
searchInfo = searchInfo,
|
||||
typeMode = TypeMode.PASSKEY
|
||||
)
|
||||
@@ -443,7 +403,8 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
TAG, "Passkey found for registration, " +
|
||||
"but launch manual registration for a new entry"
|
||||
)
|
||||
_uiState.value = UIState.LaunchGroupActivityForRegistration(
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration(
|
||||
database = openedDatabase,
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.PASSKEY
|
||||
@@ -451,7 +412,8 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
},
|
||||
onItemNotFound = { openedDatabase ->
|
||||
Log.d(TAG, "Launch new manual registration in opened database")
|
||||
_uiState.value = UIState.LaunchGroupActivityForRegistration(
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration(
|
||||
database = openedDatabase,
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.PASSKEY
|
||||
@@ -459,8 +421,8 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
},
|
||||
onDatabaseClosed = {
|
||||
Log.d(TAG, "Manual passkey registration in closed database")
|
||||
_uiState.value =
|
||||
UIState.LaunchFileDatabaseSelectActivityForRegistration(
|
||||
mCredentialUiState.value =
|
||||
CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration(
|
||||
registerInfo = registerInfo,
|
||||
typeMode = TypeMode.PASSKEY
|
||||
)
|
||||
@@ -552,32 +514,6 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
||||
val temptingApp: AppOrigin,
|
||||
val nodeId: UUID
|
||||
): UIState()
|
||||
data class LaunchGroupActivityForSelection(
|
||||
val database: ContextualDatabase,
|
||||
val searchInfo: SearchInfo?,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchGroupActivityForRegistration(
|
||||
val database: ContextualDatabase,
|
||||
val registerInfo: RegisterInfo,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchFileDatabaseSelectActivityForSelection(
|
||||
val searchInfo: SearchInfo,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class LaunchFileDatabaseSelectActivityForRegistration(
|
||||
val registerInfo: RegisterInfo,
|
||||
val typeMode: TypeMode
|
||||
): UIState()
|
||||
data class SetActivityResult(
|
||||
val lockDatabase: Boolean,
|
||||
val resultCode: Int,
|
||||
val data: Intent? = null
|
||||
): UIState()
|
||||
data class ShowError(
|
||||
val error: Throwable
|
||||
): UIState()
|
||||
data class UpdateEntry(
|
||||
val oldEntry: Entry,
|
||||
val newEntry: Entry
|
||||
|
||||
@@ -26,6 +26,11 @@ import com.kunzisoft.keepass.model.EntryInfo
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil.searchSubDomains
|
||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
|
||||
object SearchHelper {
|
||||
|
||||
@@ -42,6 +47,35 @@ object SearchHelper {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the concrete web domain AKA without sub domain if needed
|
||||
*/
|
||||
private fun getConcreteWebDomain(
|
||||
context: Context,
|
||||
webDomain: String?,
|
||||
concreteWebDomain: (String?) -> Unit
|
||||
) {
|
||||
val domain = webDomain
|
||||
if (domain != null) {
|
||||
// Warning, web domain can contains IP, don't crop in this case
|
||||
if (searchSubDomains(context)
|
||||
|| Regex(SearchInfo.WEB_IP_REGEX).matches(domain)) {
|
||||
concreteWebDomain.invoke(webDomain)
|
||||
} else {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val publicSuffixList = PublicSuffixList(context)
|
||||
val publicSuffix = publicSuffixList
|
||||
.getPublicSuffixPlusOne(domain).await()
|
||||
withContext(Dispatchers.Main) {
|
||||
concreteWebDomain.invoke(publicSuffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
concreteWebDomain.invoke(null)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility method to perform actions if item is found or not after an auto search in [database]
|
||||
*/
|
||||
@@ -54,18 +88,25 @@ object SearchHelper {
|
||||
onItemNotFound: (openedDatabase: ContextualDatabase) -> Unit,
|
||||
onDatabaseClosed: () -> Unit
|
||||
) {
|
||||
// TODO suspend
|
||||
// Do not place coroutine at start, bug in Passkey implementation
|
||||
if (database == null || !database.loaded) {
|
||||
onDatabaseClosed.invoke()
|
||||
} else if (TimeoutHelper.checkTime(context)) {
|
||||
var searchWithoutUI = false
|
||||
if (searchInfo != null
|
||||
&& !searchInfo.manualSelection
|
||||
&& !searchInfo.containsOnlyNullValues()) {
|
||||
&& !searchInfo.containsOnlyNullValues()
|
||||
) {
|
||||
getConcreteWebDomain(
|
||||
context,
|
||||
searchInfo.webDomain
|
||||
) { concreteDomain ->
|
||||
var query = searchInfo.toString()
|
||||
if (searchInfo.isDomainSearch && concreteDomain != null)
|
||||
query = concreteDomain
|
||||
// If search provide results
|
||||
database.createVirtualGroupFromSearchInfo(
|
||||
searchParameters = SearchParameters().apply {
|
||||
searchQuery = searchInfo.toString()
|
||||
searchQuery = query
|
||||
allowEmptyQuery = false
|
||||
searchInTitles = false
|
||||
searchInUsernames = false
|
||||
@@ -88,15 +129,16 @@ object SearchHelper {
|
||||
max = MAX_SEARCH_ENTRY
|
||||
)?.let { searchGroup ->
|
||||
if (searchGroup.numberOfChildEntries > 0) {
|
||||
searchWithoutUI = true
|
||||
onItemsFound.invoke(database,
|
||||
searchGroup.getChildEntriesInfo(database))
|
||||
onItemsFound.invoke(
|
||||
database,
|
||||
searchGroup.getChildEntriesInfo(database)
|
||||
)
|
||||
} else
|
||||
onItemNotFound.invoke(database)
|
||||
} ?: onItemNotFound.invoke(database)
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!searchWithoutUI) {
|
||||
} else
|
||||
onItemNotFound.invoke(database)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,6 +41,11 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||
autofillInlineSuggestionsPreference?.isVisible = false
|
||||
}
|
||||
|
||||
val autofillAskSaveDataPreference: TwoStatePreference? = findPreference(getString(R.string.autofill_ask_to_save_data_key))
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) {
|
||||
autofillAskSaveDataPreference?.isVisible = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||
|
||||
@@ -13,13 +13,6 @@ import com.kunzisoft.keepass.BuildConfig
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||
import com.kunzisoft.keepass.education.Education
|
||||
import com.kunzisoft.keepass.model.SearchInfo
|
||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import mozilla.components.lib.publicsuffixlist.PublicSuffixList
|
||||
|
||||
object AppUtil {
|
||||
|
||||
@@ -84,39 +77,6 @@ object AppUtil {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the concrete web domain AKA without sub domain if needed
|
||||
*/
|
||||
fun getConcreteWebDomain(
|
||||
context: Context,
|
||||
webDomain: String?,
|
||||
concreteWebDomain: suspend (String?) -> Unit
|
||||
) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val domain = webDomain
|
||||
if (domain != null) {
|
||||
// Warning, web domain can contains IP, don't crop in this case
|
||||
if (PreferencesUtil.searchSubDomains(context)
|
||||
|| Regex(SearchInfo.WEB_IP_REGEX).matches(domain)) {
|
||||
withContext(Dispatchers.Main) {
|
||||
concreteWebDomain.invoke(webDomain)
|
||||
}
|
||||
} else {
|
||||
val publicSuffixList = PublicSuffixList(context)
|
||||
val publicSuffix = publicSuffixList
|
||||
.getPublicSuffixPlusOne(domain).await()
|
||||
withContext(Dispatchers.Main) {
|
||||
concreteWebDomain.invoke(publicSuffix)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
concreteWebDomain.invoke(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.P)
|
||||
fun getInstalledBrowsersWithSignatures(context: Context): List<AndroidPrivilegedApp> {
|
||||
val packageManager = context.packageManager
|
||||
|
||||
Reference in New Issue
Block a user