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?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
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 {
|
lifecycleScope.launch {
|
||||||
// Retrieve the UI
|
// Retrieve the UI
|
||||||
autofillLauncherViewModel.credentialUiState.collect { uiState ->
|
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?) {
|
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.database.helper.SearchHelper
|
||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.otp.OtpEntryFields
|
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.KeyboardUtil.isKeyboardActivatedInSettings
|
||||||
import com.kunzisoft.keepass.utils.getParcelableCompat
|
import com.kunzisoft.keepass.utils.getParcelableCompat
|
||||||
import com.kunzisoft.keepass.view.toastError
|
import com.kunzisoft.keepass.view.toastError
|
||||||
@@ -105,13 +104,11 @@ class EntrySelectionLauncherActivity : DatabaseModeActivity() {
|
|||||||
sharedWebDomain: String?,
|
sharedWebDomain: String?,
|
||||||
otpString: String?) {
|
otpString: String?) {
|
||||||
// Build domain search param
|
// Build domain search param
|
||||||
getConcreteWebDomain(this, sharedWebDomain) { concreteWebDomain ->
|
val searchInfo = SearchInfo().apply {
|
||||||
val searchInfo = SearchInfo().apply {
|
this.webDomain = sharedWebDomain
|
||||||
this.webDomain = concreteWebDomain
|
this.otpString = otpString
|
||||||
this.otpString = otpString
|
|
||||||
}
|
|
||||||
launch(database, searchInfo)
|
|
||||||
}
|
}
|
||||||
|
launch(database, searchInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launch(database: ContextualDatabase?,
|
private fun launch(database: ContextualDatabase?,
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import com.kunzisoft.keepass.credentialprovider.TypeMode
|
|||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
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.addAppOrigin
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.util.PasskeyHelper.addAuthCode
|
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.credentialprovider.viewmodel.PasskeyLauncherViewModel
|
||||||
import com.kunzisoft.keepass.database.ContextualDatabase
|
import com.kunzisoft.keepass.database.ContextualDatabase
|
||||||
import com.kunzisoft.keepass.model.AppOrigin
|
import com.kunzisoft.keepass.model.AppOrigin
|
||||||
@@ -106,7 +107,17 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
nodeId = uiState.nodeId
|
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(
|
setActivityResult(
|
||||||
typeMode = TypeMode.PASSKEY,
|
typeMode = TypeMode.PASSKEY,
|
||||||
lockDatabase = uiState.lockDatabase,
|
lockDatabase = uiState.lockDatabase,
|
||||||
@@ -114,11 +125,11 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
data = uiState.data
|
data = uiState.data
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is PasskeyLauncherViewModel.UIState.ShowError -> {
|
is CredentialLauncherViewModel.UIState.ShowError -> {
|
||||||
toastError(uiState.error)
|
toastError(uiState.error)
|
||||||
passkeyLauncherViewModel.cancelResult()
|
passkeyLauncherViewModel.cancelResult()
|
||||||
}
|
}
|
||||||
is PasskeyLauncherViewModel.UIState.LaunchGroupActivityForSelection -> {
|
is CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection -> {
|
||||||
GroupActivity.launchForSelection(
|
GroupActivity.launchForSelection(
|
||||||
context = this@PasskeyLauncherActivity,
|
context = this@PasskeyLauncherActivity,
|
||||||
database = uiState.database,
|
database = uiState.database,
|
||||||
@@ -127,7 +138,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
searchInfo = uiState.searchInfo
|
searchInfo = uiState.searchInfo
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is PasskeyLauncherViewModel.UIState.LaunchGroupActivityForRegistration -> {
|
is CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration -> {
|
||||||
GroupActivity.launchForRegistration(
|
GroupActivity.launchForRegistration(
|
||||||
context = this@PasskeyLauncherActivity,
|
context = this@PasskeyLauncherActivity,
|
||||||
activityResultLauncher = mPasskeyRegistrationActivityResultLauncher,
|
activityResultLauncher = mPasskeyRegistrationActivityResultLauncher,
|
||||||
@@ -136,7 +147,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
typeMode = uiState.typeMode
|
typeMode = uiState.typeMode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is PasskeyLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection -> {
|
is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection -> {
|
||||||
FileDatabaseSelectActivity.launchForSelection(
|
FileDatabaseSelectActivity.launchForSelection(
|
||||||
context = this@PasskeyLauncherActivity,
|
context = this@PasskeyLauncherActivity,
|
||||||
typeMode = uiState.typeMode,
|
typeMode = uiState.typeMode,
|
||||||
@@ -144,7 +155,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
searchInfo = uiState.searchInfo,
|
searchInfo = uiState.searchInfo,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is PasskeyLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration -> {
|
is CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration -> {
|
||||||
FileDatabaseSelectActivity.launchForRegistration(
|
FileDatabaseSelectActivity.launchForRegistration(
|
||||||
context = this@PasskeyLauncherActivity,
|
context = this@PasskeyLauncherActivity,
|
||||||
activityResultLauncher = mPasskeyRegistrationActivityResultLauncher,
|
activityResultLauncher = mPasskeyRegistrationActivityResultLauncher,
|
||||||
@@ -152,9 +163,6 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
typeMode = uiState.typeMode
|
typeMode = uiState.typeMode
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
is PasskeyLauncherViewModel.UIState.UpdateEntry -> {
|
|
||||||
updateEntry(uiState.oldEntry, uiState.newEntry)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -162,7 +170,7 @@ class PasskeyLauncherActivity : DatabaseLockActivity() {
|
|||||||
|
|
||||||
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
override fun onDatabaseRetrieved(database: ContextualDatabase?) {
|
||||||
super.onDatabaseRetrieved(database)
|
super.onDatabaseRetrieved(database)
|
||||||
passkeyLauncherViewModel.launchPasskeyActionIfNeeded(intent, mSpecialMode, database)
|
passkeyLauncherViewModel.launchActionIfNeeded(intent, mSpecialMode, database)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDatabaseActionFinished(
|
override fun onDatabaseActionFinished(
|
||||||
|
|||||||
@@ -52,7 +52,6 @@ import com.kunzisoft.keepass.model.EntryInfo
|
|||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
|
|
||||||
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
import com.kunzisoft.keepass.utils.getParcelableExtraCompat
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -380,7 +379,6 @@ object AutofillHelper {
|
|||||||
database: ContextualDatabase,
|
database: ContextualDatabase,
|
||||||
entriesInfo: List<EntryInfo>,
|
entriesInfo: List<EntryInfo>,
|
||||||
parseResult: StructureParser.Result,
|
parseResult: StructureParser.Result,
|
||||||
concreteWebDomain: String?,
|
|
||||||
autofillComponent: AutofillComponent
|
autofillComponent: AutofillComponent
|
||||||
): FillResponse? {
|
): FillResponse? {
|
||||||
val responseBuilder = FillResponse.Builder()
|
val responseBuilder = FillResponse.Builder()
|
||||||
@@ -448,7 +446,7 @@ object AutofillHelper {
|
|||||||
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
|
if (PreferencesUtil.isAutofillManualSelectionEnable(context)) {
|
||||||
val searchInfo = SearchInfo().apply {
|
val searchInfo = SearchInfo().apply {
|
||||||
applicationId = parseResult.applicationId
|
applicationId = parseResult.applicationId
|
||||||
webDomain = concreteWebDomain
|
webDomain = parseResult.webDomain
|
||||||
webScheme = parseResult.webScheme
|
webScheme = parseResult.webScheme
|
||||||
manualSelection = true
|
manualSelection = true
|
||||||
}
|
}
|
||||||
@@ -525,26 +523,23 @@ object AutofillHelper {
|
|||||||
autofillComponent: AutofillComponent,
|
autofillComponent: AutofillComponent,
|
||||||
database: ContextualDatabase,
|
database: ContextualDatabase,
|
||||||
entriesInfo: List<EntryInfo>,
|
entriesInfo: List<EntryInfo>,
|
||||||
onIntentCreated: suspend (Intent) -> Unit
|
onIntentCreated: (Intent) -> Unit
|
||||||
) {
|
) {
|
||||||
if (entriesInfo.isEmpty()) {
|
if (entriesInfo.isEmpty()) {
|
||||||
throw IOException("No entries found")
|
throw IOException("No entries found")
|
||||||
} else {
|
} else {
|
||||||
StructureParser(autofillComponent.assistStructure).parse()?.let { result ->
|
StructureParser(autofillComponent.assistStructure).parse()?.let { result ->
|
||||||
getConcreteWebDomain(context, result.webDomain) { concreteWebDomain ->
|
// New Response
|
||||||
// New Response
|
onIntentCreated(Intent().putExtra(
|
||||||
onIntentCreated(Intent().putExtra(
|
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
|
||||||
AutofillManager.EXTRA_AUTHENTICATION_RESULT,
|
buildResponse(
|
||||||
buildResponse(
|
context = context,
|
||||||
context = context,
|
database = database,
|
||||||
database = database,
|
entriesInfo = entriesInfo,
|
||||||
entriesInfo = entriesInfo,
|
parseResult = result,
|
||||||
parseResult = result,
|
autofillComponent = autofillComponent
|
||||||
concreteWebDomain = concreteWebDomain,
|
)
|
||||||
autofillComponent = autofillComponent
|
))
|
||||||
)
|
|
||||||
))
|
|
||||||
}
|
|
||||||
} ?: throw IOException("Unable to parse the structure")
|
} ?: 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.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
import com.kunzisoft.keepass.settings.AutofillSettingsActivity
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.AppUtil.getConcreteWebDomain
|
|
||||||
import org.joda.time.DateTime
|
import org.joda.time.DateTime
|
||||||
|
|
||||||
|
|
||||||
@@ -116,68 +115,51 @@ class KeeAutofillService : AutofillService() {
|
|||||||
webDomain = parseResult.webDomain,
|
webDomain = parseResult.webDomain,
|
||||||
webDomainBlocklist = webDomainBlocklist)
|
webDomainBlocklist = webDomainBlocklist)
|
||||||
) {
|
) {
|
||||||
getConcreteWebDomain(this, parseResult.webDomain) { webDomainWithoutSubDomain ->
|
val searchInfo = SearchInfo().apply {
|
||||||
val searchInfo = SearchInfo().apply {
|
applicationId = parseResult.applicationId
|
||||||
applicationId = parseResult.applicationId
|
webDomain = parseResult.webDomain
|
||||||
webDomain = webDomainWithoutSubDomain
|
webScheme = parseResult.webScheme
|
||||||
webScheme = parseResult.webScheme
|
|
||||||
}
|
|
||||||
val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
|
||||||
&& autofillInlineSuggestionsEnabled) {
|
|
||||||
CompatInlineSuggestionsRequest(request)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
launchSelection(mDatabase,
|
|
||||||
searchInfo,
|
|
||||||
parseResult,
|
|
||||||
AutofillComponent(
|
|
||||||
latestStructure,
|
|
||||||
inlineSuggestionsRequest
|
|
||||||
),
|
|
||||||
callback
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
val inlineSuggestionsRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
|
||||||
|
&& autofillInlineSuggestionsEnabled) {
|
||||||
|
CompatInlineSuggestionsRequest(request)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
val autofillComponent = AutofillComponent(
|
||||||
|
latestStructure,
|
||||||
|
inlineSuggestionsRequest
|
||||||
|
)
|
||||||
|
SearchHelper.checkAutoSearchInfo(
|
||||||
|
context = this,
|
||||||
|
database = mDatabase,
|
||||||
|
searchInfo = searchInfo,
|
||||||
|
onItemsFound = { openedDatabase, items ->
|
||||||
|
callback.onSuccess(
|
||||||
|
AutofillHelper.buildResponse(
|
||||||
|
context = this,
|
||||||
|
database = openedDatabase,
|
||||||
|
entriesInfo = items,
|
||||||
|
parseResult = parseResult,
|
||||||
|
autofillComponent = autofillComponent
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
onItemNotFound = { openedDatabase ->
|
||||||
|
// Show UI if no search result
|
||||||
|
showUIForEntrySelection(parseResult, openedDatabase,
|
||||||
|
searchInfo, autofillComponent, callback)
|
||||||
|
},
|
||||||
|
onDatabaseClosed = {
|
||||||
|
// Show UI if database not open
|
||||||
|
showUIForEntrySelection(parseResult, null,
|
||||||
|
searchInfo, autofillComponent, callback)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launchSelection(
|
|
||||||
database: ContextualDatabase?,
|
|
||||||
searchInfo: SearchInfo,
|
|
||||||
parseResult: StructureParser.Result,
|
|
||||||
autofillComponent: AutofillComponent,
|
|
||||||
callback: FillCallback
|
|
||||||
) {
|
|
||||||
SearchHelper.checkAutoSearchInfo(
|
|
||||||
context = this,
|
|
||||||
database = database,
|
|
||||||
searchInfo = searchInfo,
|
|
||||||
onItemsFound = { openedDatabase, items ->
|
|
||||||
callback.onSuccess(
|
|
||||||
AutofillHelper.buildResponse(
|
|
||||||
context = this,
|
|
||||||
database = openedDatabase,
|
|
||||||
entriesInfo = items,
|
|
||||||
parseResult = parseResult,
|
|
||||||
concreteWebDomain = searchInfo.webDomain,
|
|
||||||
autofillComponent = autofillComponent
|
|
||||||
)
|
|
||||||
)
|
|
||||||
},
|
|
||||||
onItemNotFound = { openedDatabase ->
|
|
||||||
// Show UI if no search result
|
|
||||||
showUIForEntrySelection(parseResult, openedDatabase,
|
|
||||||
searchInfo, autofillComponent, callback)
|
|
||||||
},
|
|
||||||
onDatabaseClosed = {
|
|
||||||
// Show UI if database not open
|
|
||||||
showUIForEntrySelection(parseResult, null,
|
|
||||||
searchInfo, autofillComponent, callback)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
private fun showUIForEntrySelection(
|
private fun showUIForEntrySelection(
|
||||||
parseResult: StructureParser.Result,
|
parseResult: StructureParser.Result,
|
||||||
@@ -377,7 +359,7 @@ class KeeAutofillService : AutofillService() {
|
|||||||
|
|
||||||
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
|
override fun onSaveRequest(request: SaveRequest, callback: SaveCallback) {
|
||||||
var success = false
|
var success = false
|
||||||
if (askToSaveData) {
|
if (askToSaveData && Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
val latestStructure = request.fillContexts.last().structure
|
val latestStructure = request.fillContexts.last().structure
|
||||||
StructureParser(latestStructure).parse(true)?.let { parseResult ->
|
StructureParser(latestStructure).parse(true)?.let { parseResult ->
|
||||||
|
|
||||||
@@ -403,39 +385,31 @@ class KeeAutofillService : AutofillService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Show UI to save data
|
// Show UI to save data
|
||||||
getConcreteWebDomain(applicationContext, parseResult.webDomain) { concreteWebDomain ->
|
val searchInfo = SearchInfo().apply {
|
||||||
val searchInfo = SearchInfo().apply {
|
applicationId = parseResult.applicationId
|
||||||
applicationId = parseResult.applicationId
|
webDomain = parseResult.webDomain
|
||||||
webDomain = concreteWebDomain
|
webScheme = parseResult.webScheme
|
||||||
webScheme = parseResult.webScheme
|
}
|
||||||
|
val registerInfo = RegisterInfo(
|
||||||
|
searchInfo = searchInfo,
|
||||||
|
username = parseResult.usernameValue?.textValue?.toString(),
|
||||||
|
password = parseResult.passwordValue?.textValue?.toString(),
|
||||||
|
creditCard = parseResult.creditCardNumber?.let { cardNumber ->
|
||||||
|
CreditCard(
|
||||||
|
parseResult.creditCardHolder,
|
||||||
|
cardNumber,
|
||||||
|
expiration,
|
||||||
|
parseResult.cardVerificationValue
|
||||||
|
)
|
||||||
}
|
}
|
||||||
val registerInfo = RegisterInfo(
|
)
|
||||||
searchInfo = searchInfo,
|
|
||||||
username = parseResult.usernameValue?.textValue?.toString(),
|
|
||||||
password = parseResult.passwordValue?.textValue?.toString(),
|
|
||||||
creditCard = parseResult.creditCardNumber?.let { cardNumber ->
|
|
||||||
CreditCard(
|
|
||||||
parseResult.creditCardHolder,
|
|
||||||
cardNumber,
|
|
||||||
expiration,
|
|
||||||
parseResult.cardVerificationValue
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
AutofillLauncherActivity.getPendingIntentForRegistration(
|
||||||
// TODO Test pending intent
|
this,
|
||||||
AutofillLauncherActivity.getPendingIntentForRegistration(
|
registerInfo
|
||||||
this,
|
)?.intentSender?.let { intentSender ->
|
||||||
registerInfo
|
success = true
|
||||||
)?.intentSender?.let { intentSender ->
|
callback.onSuccess(intentSender)
|
||||||
callback.onSuccess(intentSender)
|
|
||||||
} ?: callback.onFailure("Unable to launch registration")
|
|
||||||
} else {
|
|
||||||
AutofillLauncherActivity.launchForRegistration(this, registerInfo)
|
|
||||||
success = true
|
|
||||||
callback.onSuccess()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,9 +120,9 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
for (option in request.beginGetCredentialOptions) {
|
for (option in request.beginGetCredentialOptions) {
|
||||||
when (option) {
|
when (option) {
|
||||||
is BeginGetPublicKeyCredentialOption -> {
|
is BeginGetPublicKeyCredentialOption -> {
|
||||||
credentialEntries.addAll(
|
populatePasskeyData(option) { listCredentials ->
|
||||||
populatePasskeyData(option)
|
credentialEntries.addAll(listCredentials)
|
||||||
)
|
}
|
||||||
return BeginGetCredentialResponse(credentialEntries)
|
return BeginGetCredentialResponse(credentialEntries)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,7 +131,10 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun populatePasskeyData(option: BeginGetPublicKeyCredentialOption): List<CredentialEntry> {
|
private fun populatePasskeyData(
|
||||||
|
option: BeginGetPublicKeyCredentialOption,
|
||||||
|
callback: (List<CredentialEntry>) -> Unit
|
||||||
|
) {
|
||||||
|
|
||||||
val passkeyEntries: MutableList<CredentialEntry> = mutableListOf()
|
val passkeyEntries: MutableList<CredentialEntry> = mutableListOf()
|
||||||
|
|
||||||
@@ -167,6 +170,7 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
callback(passkeyEntries)
|
||||||
},
|
},
|
||||||
onItemNotFound = { _ ->
|
onItemNotFound = { _ ->
|
||||||
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
|
Log.w(TAG, "No passkey found in the database with this relying party : $relyingPartyId")
|
||||||
@@ -189,6 +193,7 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
callback(passkeyEntries)
|
||||||
},
|
},
|
||||||
onDatabaseClosed = {
|
onDatabaseClosed = {
|
||||||
Log.d(TAG, "Add pending intent for passkey selection in closed database")
|
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(
|
override fun onBeginCreateCredentialRequest(
|
||||||
@@ -223,7 +228,9 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
) {
|
) {
|
||||||
Log.d(javaClass.simpleName, "onBeginCreateCredentialRequest called")
|
Log.d(javaClass.simpleName, "onBeginCreateCredentialRequest called")
|
||||||
try {
|
try {
|
||||||
callback.onResult(processCreateCredentialRequest(request))
|
processCreateCredentialRequest(request) {
|
||||||
|
callback.onResult(BeginCreateCredentialResponse(it))
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(javaClass.simpleName, "onBeginCreateCredentialRequest error", e)
|
Log.e(javaClass.simpleName, "onBeginCreateCredentialRequest error", e)
|
||||||
toastError(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) {
|
when (request) {
|
||||||
is BeginCreatePublicKeyCredentialRequest -> {
|
is BeginCreatePublicKeyCredentialRequest -> {
|
||||||
// Request is passkey type
|
// Request is passkey type
|
||||||
return handleCreatePasskeyQuery(request)
|
handleCreatePasskeyQuery(request, callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// request type not supported
|
// 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 databaseName = mDatabase?.name
|
||||||
val accountName =
|
val accountName =
|
||||||
if (databaseName?.isBlank() != false)
|
if (databaseName?.isBlank() != false)
|
||||||
@@ -310,6 +323,7 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
}
|
}
|
||||||
}*/
|
}*/
|
||||||
}
|
}
|
||||||
|
callback(createEntries)
|
||||||
},
|
},
|
||||||
onItemNotFound = { database ->
|
onItemNotFound = { database ->
|
||||||
// To create a new entry
|
// To create a new entry
|
||||||
@@ -318,6 +332,7 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
} else {
|
} else {
|
||||||
createEntries.addPendingIntentCreationNewEntry(accountName, searchInfo)
|
createEntries.addPendingIntentCreationNewEntry(accountName, searchInfo)
|
||||||
}
|
}
|
||||||
|
callback(createEntries)
|
||||||
},
|
},
|
||||||
onDatabaseClosed = {
|
onDatabaseClosed = {
|
||||||
// Launch the passkey launcher activity to open the database
|
// Launch the passkey launcher activity to open the database
|
||||||
@@ -335,10 +350,9 @@ class PasskeyProviderService : CredentialProviderService() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
callback(createEntries)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
return BeginCreateCredentialResponse(createEntries)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClearCredentialStateRequest(
|
override fun onClearCredentialStateRequest(
|
||||||
|
|||||||
@@ -179,13 +179,13 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie
|
|||||||
.getEntryById(NodeIdUUID(nodeId))
|
.getEntryById(NodeIdUUID(nodeId))
|
||||||
?.getEntryInfo(database)
|
?.getEntryInfo(database)
|
||||||
}
|
}
|
||||||
AutofillHelper.buildResponse(
|
withContext(Dispatchers.Main) {
|
||||||
context = getApplication(),
|
AutofillHelper.buildResponse(
|
||||||
autofillComponent = autofillComponent,
|
context = getApplication(),
|
||||||
database = database,
|
autofillComponent = autofillComponent,
|
||||||
entriesInfo = entries
|
database = database,
|
||||||
) { intent ->
|
entriesInfo = entries
|
||||||
withContext(Dispatchers.Main) {
|
) { intent ->
|
||||||
setResult(intent)
|
setResult(intent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,7 +262,6 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie
|
|||||||
fun manageRegistrationResult(
|
fun manageRegistrationResult(
|
||||||
activityResult: ActivityResult
|
activityResult: ActivityResult
|
||||||
) {
|
) {
|
||||||
val intent = activityResult.data
|
|
||||||
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
||||||
Log.e(TAG, "Unable to create registration response for autofill", e)
|
Log.e(TAG, "Unable to create registration response for autofill", e)
|
||||||
showError(e)
|
showError(e)
|
||||||
@@ -270,12 +269,9 @@ class AutofillLauncherViewModel(application: Application): CredentialLauncherVie
|
|||||||
val responseIntent = Intent()
|
val responseIntent = Intent()
|
||||||
when (activityResult.resultCode) {
|
when (activityResult.resultCode) {
|
||||||
RESULT_OK -> {
|
RESULT_OK -> {
|
||||||
withContext(Dispatchers.IO) {
|
Log.d(TAG, "Autofill registration result")
|
||||||
Log.d(TAG, "Autofill registration result")
|
withContext(Dispatchers.Main) {
|
||||||
// TODO Result
|
setResult(responseIntent)
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
setResult(responseIntent)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
RESULT_CANCELED -> {
|
RESULT_CANCELED -> {
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie
|
|||||||
mDatabase = database
|
mDatabase = database
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open fun onExceptionOccurred(e: Throwable) {
|
||||||
|
showError(e)
|
||||||
|
}
|
||||||
|
|
||||||
fun launchActionIfNeeded(
|
fun launchActionIfNeeded(
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
specialMode: SpecialMode,
|
specialMode: SpecialMode,
|
||||||
@@ -67,7 +71,7 @@ abstract class CredentialLauncherViewModel(application: Application): AndroidVie
|
|||||||
if (isResultLauncherRegistered.not()) {
|
if (isResultLauncherRegistered.not()) {
|
||||||
isResultLauncherRegistered = true
|
isResultLauncherRegistered = true
|
||||||
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
viewModelScope.launch(CoroutineExceptionHandler { _, e ->
|
||||||
showError(e)
|
onExceptionOccurred(e)
|
||||||
}) {
|
}) {
|
||||||
launchAction(intent, specialMode, database)
|
launchAction(intent, specialMode, database)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import androidx.annotation.RequiresApi
|
|||||||
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.AndroidViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveNodeId
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveNodeId
|
||||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.retrieveSearchInfo
|
||||||
@@ -56,7 +55,7 @@ import java.io.InvalidObjectException
|
|||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
|
|
||||||
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
|
@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 mUsageParameters: PublicKeyCredentialUsageParameters? = null
|
||||||
private var mCreationParameters: PublicKeyCredentialCreationParameters? = null
|
private var mCreationParameters: PublicKeyCredentialCreationParameters? = null
|
||||||
@@ -64,12 +63,9 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
|||||||
|
|
||||||
private var mBackupEligibility: Boolean = true
|
private var mBackupEligibility: Boolean = true
|
||||||
private var mBackupState: Boolean = false
|
private var mBackupState: Boolean = false
|
||||||
private var mLockDatabase: Boolean = true
|
|
||||||
|
|
||||||
private var isResultLauncherRegistered: Boolean = false
|
private val mUiState = MutableStateFlow<UIState>(UIState.Loading)
|
||||||
|
val uiState: StateFlow<UIState> = mUiState
|
||||||
private val _uiState = MutableStateFlow<UIState>(UIState.Loading)
|
|
||||||
val uiState: StateFlow<UIState> = _uiState
|
|
||||||
|
|
||||||
fun initialize() {
|
fun initialize() {
|
||||||
mBackupEligibility = PreferencesUtil.isPasskeyBackupEligibilityEnable(getApplication())
|
mBackupEligibility = PreferencesUtil.isPasskeyBackupEligibilityEnable(getApplication())
|
||||||
@@ -79,19 +75,14 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
|||||||
fun showAppPrivilegedDialog(
|
fun showAppPrivilegedDialog(
|
||||||
temptingApp: AndroidPrivilegedApp
|
temptingApp: AndroidPrivilegedApp
|
||||||
) {
|
) {
|
||||||
_uiState.value = UIState.ShowAppPrivilegedDialog(temptingApp)
|
mUiState.value = UIState.ShowAppPrivilegedDialog(temptingApp)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun showAppSignatureDialog(
|
fun showAppSignatureDialog(
|
||||||
temptingApp: AppOrigin,
|
temptingApp: AppOrigin,
|
||||||
nodeId: UUID
|
nodeId: UUID
|
||||||
) {
|
) {
|
||||||
_uiState.value = UIState.ShowAppSignatureDialog(temptingApp, nodeId)
|
mUiState.value = UIState.ShowAppSignatureDialog(temptingApp, nodeId)
|
||||||
}
|
|
||||||
|
|
||||||
fun showError(error: Throwable) {
|
|
||||||
Log.e(TAG, "Error on passkey launch", error)
|
|
||||||
_uiState.value = UIState.ShowError(error)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun saveCustomPrivilegedApp(
|
fun saveCustomPrivilegedApp(
|
||||||
@@ -107,7 +98,7 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
|||||||
context = getApplication(),
|
context = getApplication(),
|
||||||
privilegedApps = listOf(temptingApp)
|
privilegedApps = listOf(temptingApp)
|
||||||
)
|
)
|
||||||
launchPasskeyAction(
|
launchAction(
|
||||||
intent = intent,
|
intent = intent,
|
||||||
specialMode = specialMode,
|
specialMode = specialMode,
|
||||||
database = database
|
database = database
|
||||||
@@ -139,54 +130,22 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
|||||||
)
|
)
|
||||||
entryInfo.saveAppOrigin(database, temptingApp)
|
entryInfo.saveAppOrigin(database, temptingApp)
|
||||||
newEntry.setEntryInfo(database, entryInfo)
|
newEntry.setEntryInfo(database, entryInfo)
|
||||||
_uiState.value = UIState.UpdateEntry(
|
mUiState.value = UIState.UpdateEntry(
|
||||||
oldEntry = entry,
|
oldEntry = entry,
|
||||||
newEntry = newEntry
|
newEntry = newEntry
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setResult(intent: Intent) {
|
override fun onExceptionOccurred(e: Throwable) {
|
||||||
// Remove the launcher register
|
if (e is PrivilegedAllowLists.PrivilegedException) {
|
||||||
isResultLauncherRegistered = false
|
showAppPrivilegedDialog(e.temptingApp)
|
||||||
_uiState.value = UIState.SetActivityResult(
|
} else {
|
||||||
lockDatabase = mLockDatabase,
|
super.onExceptionOccurred(e)
|
||||||
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 ->
|
|
||||||
if (e is PrivilegedAllowLists.PrivilegedException) {
|
|
||||||
showAppPrivilegedDialog(e.temptingApp)
|
|
||||||
} else {
|
|
||||||
showError(e)
|
|
||||||
}
|
|
||||||
}) {
|
|
||||||
launchPasskeyAction(intent, specialMode, database)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
override suspend fun launchAction(
|
||||||
* Launch the main action to manage Passkey
|
|
||||||
*/
|
|
||||||
private suspend fun launchPasskeyAction(
|
|
||||||
intent: Intent,
|
intent: Intent,
|
||||||
specialMode: SpecialMode,
|
specialMode: SpecialMode,
|
||||||
database: ContextualDatabase?
|
database: ContextualDatabase?
|
||||||
@@ -260,16 +219,17 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
|||||||
TAG, "No Passkey found for selection," +
|
TAG, "No Passkey found for selection," +
|
||||||
"launch manual selection in opened database"
|
"launch manual selection in opened database"
|
||||||
)
|
)
|
||||||
_uiState.value = UIState.LaunchGroupActivityForSelection(
|
mCredentialUiState.value =
|
||||||
database = openedDatabase,
|
CredentialLauncherViewModel.UIState.LaunchGroupActivityForSelection(
|
||||||
searchInfo = searchInfo,
|
database = openedDatabase,
|
||||||
typeMode = TypeMode.PASSKEY
|
searchInfo = searchInfo,
|
||||||
)
|
typeMode = TypeMode.PASSKEY
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onDatabaseClosed = {
|
onDatabaseClosed = {
|
||||||
Log.d(TAG, "Manual passkey selection in closed database")
|
Log.d(TAG, "Manual passkey selection in closed database")
|
||||||
_uiState.value =
|
mCredentialUiState.value =
|
||||||
UIState.LaunchFileDatabaseSelectActivityForSelection(
|
CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForSelection(
|
||||||
searchInfo = searchInfo,
|
searchInfo = searchInfo,
|
||||||
typeMode = TypeMode.PASSKEY
|
typeMode = TypeMode.PASSKEY
|
||||||
)
|
)
|
||||||
@@ -443,24 +403,26 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
|||||||
TAG, "Passkey found for registration, " +
|
TAG, "Passkey found for registration, " +
|
||||||
"but launch manual registration for a new entry"
|
"but launch manual registration for a new entry"
|
||||||
)
|
)
|
||||||
_uiState.value = UIState.LaunchGroupActivityForRegistration(
|
mCredentialUiState.value =
|
||||||
database = openedDatabase,
|
CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration(
|
||||||
registerInfo = registerInfo,
|
database = openedDatabase,
|
||||||
typeMode = TypeMode.PASSKEY
|
registerInfo = registerInfo,
|
||||||
)
|
typeMode = TypeMode.PASSKEY
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onItemNotFound = { openedDatabase ->
|
onItemNotFound = { openedDatabase ->
|
||||||
Log.d(TAG, "Launch new manual registration in opened database")
|
Log.d(TAG, "Launch new manual registration in opened database")
|
||||||
_uiState.value = UIState.LaunchGroupActivityForRegistration(
|
mCredentialUiState.value =
|
||||||
database = openedDatabase,
|
CredentialLauncherViewModel.UIState.LaunchGroupActivityForRegistration(
|
||||||
registerInfo = registerInfo,
|
database = openedDatabase,
|
||||||
typeMode = TypeMode.PASSKEY
|
registerInfo = registerInfo,
|
||||||
)
|
typeMode = TypeMode.PASSKEY
|
||||||
|
)
|
||||||
},
|
},
|
||||||
onDatabaseClosed = {
|
onDatabaseClosed = {
|
||||||
Log.d(TAG, "Manual passkey registration in closed database")
|
Log.d(TAG, "Manual passkey registration in closed database")
|
||||||
_uiState.value =
|
mCredentialUiState.value =
|
||||||
UIState.LaunchFileDatabaseSelectActivityForRegistration(
|
CredentialLauncherViewModel.UIState.LaunchFileDatabaseSelectActivityForRegistration(
|
||||||
registerInfo = registerInfo,
|
registerInfo = registerInfo,
|
||||||
typeMode = TypeMode.PASSKEY
|
typeMode = TypeMode.PASSKEY
|
||||||
)
|
)
|
||||||
@@ -552,32 +514,6 @@ class PasskeyLauncherViewModel(application: Application): AndroidViewModel(appli
|
|||||||
val temptingApp: AppOrigin,
|
val temptingApp: AppOrigin,
|
||||||
val nodeId: UUID
|
val nodeId: UUID
|
||||||
): UIState()
|
): 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(
|
data class UpdateEntry(
|
||||||
val oldEntry: Entry,
|
val oldEntry: Entry,
|
||||||
val newEntry: Entry
|
val newEntry: Entry
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ import com.kunzisoft.keepass.model.EntryInfo
|
|||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil.searchSubDomains
|
import com.kunzisoft.keepass.settings.PreferencesUtil.searchSubDomains
|
||||||
import com.kunzisoft.keepass.timeout.TimeoutHelper
|
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 {
|
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]
|
* Utility method to perform actions if item is found or not after an auto search in [database]
|
||||||
*/
|
*/
|
||||||
@@ -54,49 +88,57 @@ object SearchHelper {
|
|||||||
onItemNotFound: (openedDatabase: ContextualDatabase) -> Unit,
|
onItemNotFound: (openedDatabase: ContextualDatabase) -> Unit,
|
||||||
onDatabaseClosed: () -> Unit
|
onDatabaseClosed: () -> Unit
|
||||||
) {
|
) {
|
||||||
// TODO suspend
|
// Do not place coroutine at start, bug in Passkey implementation
|
||||||
if (database == null || !database.loaded) {
|
if (database == null || !database.loaded) {
|
||||||
onDatabaseClosed.invoke()
|
onDatabaseClosed.invoke()
|
||||||
} else if (TimeoutHelper.checkTime(context)) {
|
} else if (TimeoutHelper.checkTime(context)) {
|
||||||
var searchWithoutUI = false
|
|
||||||
if (searchInfo != null
|
if (searchInfo != null
|
||||||
&& !searchInfo.manualSelection
|
&& !searchInfo.manualSelection
|
||||||
&& !searchInfo.containsOnlyNullValues()) {
|
&& !searchInfo.containsOnlyNullValues()
|
||||||
// If search provide results
|
) {
|
||||||
database.createVirtualGroupFromSearchInfo(
|
getConcreteWebDomain(
|
||||||
searchParameters = SearchParameters().apply {
|
context,
|
||||||
searchQuery = searchInfo.toString()
|
searchInfo.webDomain
|
||||||
allowEmptyQuery = false
|
) { concreteDomain ->
|
||||||
searchInTitles = false
|
var query = searchInfo.toString()
|
||||||
searchInUsernames = false
|
if (searchInfo.isDomainSearch && concreteDomain != null)
|
||||||
searchInPasswords = false
|
query = concreteDomain
|
||||||
searchInAppIds = searchInfo.isAppIdSearch
|
// If search provide results
|
||||||
searchInUrls = searchInfo.isDomainSearch
|
database.createVirtualGroupFromSearchInfo(
|
||||||
searchByDomain = true
|
searchParameters = SearchParameters().apply {
|
||||||
searchBySubDomain = searchSubDomains(context)
|
searchQuery = query
|
||||||
searchInRelyingParty = searchInfo.isPasskeySearch
|
allowEmptyQuery = false
|
||||||
searchInNotes = false
|
searchInTitles = false
|
||||||
searchInOTP = searchInfo.isOTPSearch
|
searchInUsernames = false
|
||||||
searchInOther = false
|
searchInPasswords = false
|
||||||
searchInUUIDs = false
|
searchInAppIds = searchInfo.isAppIdSearch
|
||||||
searchInTags = searchInfo.isTagSearch
|
searchInUrls = searchInfo.isDomainSearch
|
||||||
searchInCurrentGroup = false
|
searchByDomain = true
|
||||||
searchInSearchableGroup = true
|
searchBySubDomain = searchSubDomains(context)
|
||||||
searchInRecycleBin = false
|
searchInRelyingParty = searchInfo.isPasskeySearch
|
||||||
searchInTemplates = false
|
searchInNotes = false
|
||||||
},
|
searchInOTP = searchInfo.isOTPSearch
|
||||||
max = MAX_SEARCH_ENTRY
|
searchInOther = false
|
||||||
)?.let { searchGroup ->
|
searchInUUIDs = false
|
||||||
if (searchGroup.numberOfChildEntries > 0) {
|
searchInTags = searchInfo.isTagSearch
|
||||||
searchWithoutUI = true
|
searchInCurrentGroup = false
|
||||||
onItemsFound.invoke(database,
|
searchInSearchableGroup = true
|
||||||
searchGroup.getChildEntriesInfo(database))
|
searchInRecycleBin = false
|
||||||
}
|
searchInTemplates = false
|
||||||
|
},
|
||||||
|
max = MAX_SEARCH_ENTRY
|
||||||
|
)?.let { searchGroup ->
|
||||||
|
if (searchGroup.numberOfChildEntries > 0) {
|
||||||
|
onItemsFound.invoke(
|
||||||
|
database,
|
||||||
|
searchGroup.getChildEntriesInfo(database)
|
||||||
|
)
|
||||||
|
} else
|
||||||
|
onItemNotFound.invoke(database)
|
||||||
|
} ?: onItemNotFound.invoke(database)
|
||||||
}
|
}
|
||||||
}
|
} else
|
||||||
if (!searchWithoutUI) {
|
|
||||||
onItemNotFound.invoke(database)
|
onItemNotFound.invoke(database)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,6 +41,11 @@ class AutofillSettingsFragment : PreferenceFragmentCompat() {
|
|||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
|
||||||
autofillInlineSuggestionsPreference?.isVisible = false
|
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) {
|
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||||
|
|||||||
@@ -13,13 +13,6 @@ import com.kunzisoft.keepass.BuildConfig
|
|||||||
import com.kunzisoft.keepass.R
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
import com.kunzisoft.keepass.credentialprovider.passkey.data.AndroidPrivilegedApp
|
||||||
import com.kunzisoft.keepass.education.Education
|
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 {
|
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)
|
@RequiresApi(Build.VERSION_CODES.P)
|
||||||
fun getInstalledBrowsersWithSignatures(context: Context): List<AndroidPrivilegedApp> {
|
fun getInstalledBrowsersWithSignatures(context: Context): List<AndroidPrivilegedApp> {
|
||||||
val packageManager = context.packageManager
|
val packageManager = context.packageManager
|
||||||
|
|||||||
Reference in New Issue
Block a user