mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Merge branch 'develop' into feature/Passkeys
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
KeePassDX(4.1.3)
|
||||
* Fix Autofill Registration #2089
|
||||
* Fix Biometric errors #2081
|
||||
* Fixed timestamp in copy file #1981 #1983
|
||||
* Fix Template Email #1986
|
||||
|
||||
KeePassDX(4.1.2)
|
||||
* Fix URL search #1940 #1946 #2003 #2040 #2044
|
||||
* Fix Autofill popup #2054
|
||||
|
||||
@@ -54,7 +54,7 @@ Optional visual styles are accessible after a contribution (and a congratulatory
|
||||
|--------|--------|---------|
|
||||
| [Google Play](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.free) |  | Free + [Pro](https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro) |
|
||||
| [F-Droid](https://f-droid.org/en/packages/com.kunzisoft.keepass.libre/) |  | Libre |
|
||||
| [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.free) |  | Free |
|
||||
| [IzzyOnDroid](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.free) |  | Free & [Libre](https://apt.izzysoft.de/fdroid/index/apk/com.kunzisoft.keepass.libre) |
|
||||
| [GitHub](https://github.com/Kunzisoft/KeePassDX/releases) / [Obtainium](https://github.com/ImranR98/Obtainium) |  | Free & Libre |
|
||||
|
||||
## Package authenticity from GitHub
|
||||
|
||||
@@ -11,8 +11,8 @@ android {
|
||||
applicationId "com.kunzisoft.keepass"
|
||||
minSdkVersion 19
|
||||
targetSdkVersion 34
|
||||
versionCode = 134
|
||||
versionName = "4.1.2"
|
||||
versionCode = 135
|
||||
versionName = "4.1.3"
|
||||
multiDexEnabled true
|
||||
|
||||
testApplicationId = "com.kunzisoft.keepass.tests"
|
||||
|
||||
@@ -119,7 +119,7 @@ import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.view.updateLockPaddingStart
|
||||
import com.kunzisoft.keepass.viewmodels.GroupEditViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.GroupViewModel
|
||||
import org.joda.time.Instant
|
||||
import org.joda.time.LocalDateTime
|
||||
|
||||
|
||||
class GroupActivity : DatabaseLockActivity(),
|
||||
@@ -344,7 +344,7 @@ class GroupActivity : DatabaseLockActivity(),
|
||||
mExternalFileHelper?.createDocument(
|
||||
getString(R.string.database_file_name_default) +
|
||||
"_" +
|
||||
Instant.now().toString() +
|
||||
LocalDateTime.now().toString() +
|
||||
mDatabase?.defaultFileExtension)
|
||||
}
|
||||
R.id.menu_lock_all -> {
|
||||
|
||||
@@ -32,7 +32,6 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
@@ -43,6 +42,9 @@ import androidx.appcompat.widget.Toolbar
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
import androidx.fragment.app.commit
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.dialogs.DuplicateUuidDialog
|
||||
@@ -82,10 +84,11 @@ import com.kunzisoft.keepass.view.asError
|
||||
import com.kunzisoft.keepass.view.showActionErrorIfNeeded
|
||||
import com.kunzisoft.keepass.viewmodels.AdvancedUnlockViewModel
|
||||
import com.kunzisoft.keepass.viewmodels.DatabaseFileViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
|
||||
class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.BuilderListener {
|
||||
class MainCredentialActivity : DatabaseModeActivity() {
|
||||
|
||||
// Views
|
||||
private var toolbar: Toolbar? = null
|
||||
@@ -165,21 +168,13 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
}
|
||||
|
||||
// Listen password checkbox to init advanced unlock and confirmation button
|
||||
mainCredentialView?.onPasswordChecked =
|
||||
CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
mAdvancedUnlockViewModel.checkUnlockAvailability()
|
||||
enableConfirmationButton()
|
||||
}
|
||||
mainCredentialView?.onKeyFileChecked =
|
||||
CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
|
||||
enableConfirmationButton()
|
||||
}
|
||||
mainCredentialView?.onHardwareKeyChecked =
|
||||
CompoundButton.OnCheckedChangeListener { _, _ ->
|
||||
// TODO mAdvancedUnlockViewModel.checkUnlockAvailability()
|
||||
enableConfirmationButton()
|
||||
}
|
||||
mainCredentialView?.onConditionToStoreCredentialChanged = { credentialStorage, verified ->
|
||||
mAdvancedUnlockViewModel.checkUnlockAvailability(
|
||||
conditionToStoreCredentialVerified = verified
|
||||
)
|
||||
// TODO Async by ViewModel
|
||||
enableConfirmationButton()
|
||||
}
|
||||
|
||||
// Observe if default database
|
||||
mDatabaseFileViewModel.isDefaultDatabase.observe(this) { isDefaultDatabase ->
|
||||
@@ -227,6 +222,27 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
|
||||
onDatabaseFileLoaded(databaseFile?.databaseUri, keyFileUri, hardwareKey)
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
mAdvancedUnlockViewModel.uiState.collect { uiState ->
|
||||
// New value received
|
||||
if (uiState.isCredentialRequired) {
|
||||
mAdvancedUnlockViewModel.provideCredentialForEncryption(
|
||||
getCredentialForEncryption()
|
||||
)
|
||||
}
|
||||
uiState.cipherEncryptDatabase?.let { cipherEncryptDatabase ->
|
||||
onCredentialEncrypted(cipherEncryptDatabase)
|
||||
mAdvancedUnlockViewModel.consumeCredentialEncrypted()
|
||||
}
|
||||
uiState.cipherDecryptDatabase?.let { cipherDecryptDatabase ->
|
||||
onCredentialDecrypted(cipherDecryptDatabase)
|
||||
mAdvancedUnlockViewModel.consumeCredentialDecrypted()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -295,9 +311,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
super.onDatabaseActionFinished(database, actionTask, result)
|
||||
when (actionTask) {
|
||||
ACTION_DATABASE_LOAD_TASK -> {
|
||||
// Recheck advanced unlock if error
|
||||
mAdvancedUnlockViewModel.initAdvancedUnlockMode()
|
||||
|
||||
if (result.isSuccess) {
|
||||
launchGroupActivityIfLoaded(database)
|
||||
} else {
|
||||
@@ -399,23 +412,6 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
}
|
||||
}
|
||||
|
||||
override fun retrieveCredentialForEncryption(): ByteArray {
|
||||
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
|
||||
?: byteArrayOf()
|
||||
}
|
||||
|
||||
override fun conditionToStoreCredential(): Boolean {
|
||||
return mainCredentialView?.conditionToStoreCredential() == true
|
||||
}
|
||||
|
||||
override fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
|
||||
// Load the database if password is registered with biometric
|
||||
loadDatabase(mDatabaseFileUri,
|
||||
mainCredentialView?.getMainCredential(),
|
||||
cipherEncryptDatabase
|
||||
)
|
||||
}
|
||||
|
||||
private val credentialStorageListener = object: MainCredentialView.CredentialStorageListener {
|
||||
override fun passwordToStore(password: String?): ByteArray? {
|
||||
return password?.toByteArray()
|
||||
@@ -432,7 +428,20 @@ class MainCredentialActivity : DatabaseModeActivity(), AdvancedUnlockFragment.Bu
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
|
||||
private fun getCredentialForEncryption(): ByteArray {
|
||||
return mainCredentialView?.retrieveCredentialForStorage(credentialStorageListener)
|
||||
?: byteArrayOf()
|
||||
}
|
||||
|
||||
private fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
|
||||
// Load the database if password is registered with biometric
|
||||
loadDatabase(mDatabaseFileUri,
|
||||
mainCredentialView?.getMainCredential(),
|
||||
cipherEncryptDatabase
|
||||
)
|
||||
}
|
||||
|
||||
private fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
|
||||
// Load the database if password is retrieve from biometric
|
||||
// Retrieve from biometric
|
||||
val mainCredential = mainCredentialView?.getMainCredential() ?: MainCredential()
|
||||
|
||||
@@ -20,14 +20,18 @@
|
||||
package com.kunzisoft.keepass.biometric
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
import android.view.*
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuInflater
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.biometric.BiometricManager
|
||||
@@ -35,7 +39,9 @@ import androidx.biometric.BiometricPrompt
|
||||
import androidx.core.view.MenuProvider
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.app.database.CipherDatabaseAction
|
||||
import com.kunzisoft.keepass.database.exception.UnknownDatabaseLocationException
|
||||
@@ -52,8 +58,6 @@ import kotlinx.coroutines.launch
|
||||
|
||||
class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCallback {
|
||||
|
||||
private var mBuilderListener: BuilderListener? = null
|
||||
|
||||
private var mAdvancedUnlockEnabled = false
|
||||
private var mAutoOpenPromptEnabled = false
|
||||
|
||||
@@ -84,6 +88,8 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
// Only keep connection when we request a device credential activity
|
||||
private var keepConnection = false
|
||||
|
||||
private var isConditionToStoreCredentialVerified = false
|
||||
|
||||
private var mDeviceCredentialResultLauncher = registerForActivityResult(
|
||||
ActivityResultContracts.StartActivityForResult()
|
||||
) { result ->
|
||||
@@ -115,37 +121,10 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttach(context: Context) {
|
||||
super.onAttach(context)
|
||||
|
||||
mAdvancedUnlockEnabled = PreferencesUtil.isAdvancedUnlockEnable(context)
|
||||
mAutoOpenPromptEnabled = PreferencesUtil.isAdvancedUnlockPromptAutoOpenEnable(context)
|
||||
try {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
mBuilderListener = context as BuilderListener
|
||||
}
|
||||
} catch (e: ClassCastException) {
|
||||
throw ClassCastException(context.toString()
|
||||
+ " must implement " + BuilderListener::class.java.name)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
cipherDatabaseAction = CipherDatabaseAction.getInstance(requireContext().applicationContext)
|
||||
|
||||
mAdvancedUnlockViewModel.onInitAdvancedUnlockModeRequested.observe(this) {
|
||||
initAdvancedUnlockMode()
|
||||
}
|
||||
|
||||
mAdvancedUnlockViewModel.onUnlockAvailabilityCheckRequested.observe(this) {
|
||||
checkUnlockAvailability()
|
||||
}
|
||||
|
||||
mAdvancedUnlockViewModel.onDatabaseFileLoaded.observe(this) {
|
||||
onDatabaseLoaded(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
@@ -162,6 +141,32 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
activity?.addMenuProvider(menuProvider, viewLifecycleOwner)
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
mAdvancedUnlockViewModel.uiState.collect { uiState ->
|
||||
// Database loaded
|
||||
uiState.databaseFileLoaded?.let { databaseLoaded ->
|
||||
onDatabaseLoaded(databaseLoaded)
|
||||
mAdvancedUnlockViewModel.consumeDatabaseFileLoaded()
|
||||
}
|
||||
// New credential value received
|
||||
uiState.credential?.let {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
advancedUnlockManager?.encryptData(uiState.credential)
|
||||
}
|
||||
mAdvancedUnlockViewModel.consumeCredentialForEncryption()
|
||||
}
|
||||
// Condition to store credential verified
|
||||
isConditionToStoreCredentialVerified = uiState.isConditionToStoreCredentialVerified
|
||||
// Check unlock availability
|
||||
if (uiState.onUnlockAvailabilityCheckRequested) {
|
||||
checkUnlockAvailability()
|
||||
mAdvancedUnlockViewModel.consumeCheckUnlockAvailability()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
@@ -178,10 +183,9 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
// To get device credential unlock result, only if same database uri
|
||||
if (databaseUri != null
|
||||
&& mAdvancedUnlockEnabled) {
|
||||
val deviceCredentialAuthSucceeded = mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded
|
||||
deviceCredentialAuthSucceeded?.let {
|
||||
mAdvancedUnlockViewModel.deviceCredentialAuthSucceeded?.let { authSucceeded ->
|
||||
if (databaseUri == databaseFileUri) {
|
||||
if (deviceCredentialAuthSucceeded == true) {
|
||||
if (authSucceeded) {
|
||||
advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationSucceeded()
|
||||
} else {
|
||||
advancedUnlockManager?.advancedUnlockCallback?.onAuthenticationFailed()
|
||||
@@ -250,7 +254,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
if (advancedUnlockManager?.isKeyManagerInitialized != true) {
|
||||
toggleMode(Mode.KEY_MANAGER_UNAVAILABLE)
|
||||
} else {
|
||||
if (mBuilderListener?.conditionToStoreCredential() == true) {
|
||||
if (isConditionToStoreCredentialVerified) {
|
||||
// listen for encryption
|
||||
toggleMode(Mode.STORE_CREDENTIAL)
|
||||
} else {
|
||||
@@ -261,8 +265,13 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
// listen for decryption
|
||||
Mode.EXTRACT_CREDENTIAL
|
||||
} else {
|
||||
// wait for typing
|
||||
Mode.WAIT_CREDENTIAL
|
||||
if (isConditionToStoreCredentialVerified) {
|
||||
// if condition OK, key manager in error
|
||||
Mode.KEY_MANAGER_UNAVAILABLE
|
||||
} else {
|
||||
// wait for typing
|
||||
Mode.WAIT_CREDENTIAL
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -523,9 +532,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
}
|
||||
Mode.STORE_CREDENTIAL -> {
|
||||
// newly store the entered password in encrypted way
|
||||
mBuilderListener?.retrieveCredentialForEncryption()?.let { credential ->
|
||||
advancedUnlockManager?.encryptData(credential)
|
||||
}
|
||||
mAdvancedUnlockViewModel.retrieveCredentialForEncryption()
|
||||
}
|
||||
Mode.EXTRACT_CREDENTIAL -> {
|
||||
// retrieve the encrypted value from preferences
|
||||
@@ -545,7 +552,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
|
||||
override fun handleEncryptedResult(encryptedValue: ByteArray, ivSpec: ByteArray) {
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
mBuilderListener?.onCredentialEncrypted(
|
||||
mAdvancedUnlockViewModel.onCredentialEncrypted(
|
||||
CipherEncryptDatabase().apply {
|
||||
this.databaseUri = databaseUri
|
||||
this.credentialStorage = credentialDatabaseStorage
|
||||
@@ -559,7 +566,7 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
override fun handleDecryptedResult(decryptedValue: ByteArray) {
|
||||
// Load database directly with password retrieve
|
||||
databaseFileUri?.let { databaseUri ->
|
||||
mBuilderListener?.onCredentialDecrypted(
|
||||
mAdvancedUnlockViewModel.onCredentialDecrypted(
|
||||
CipherDecryptDatabase().apply {
|
||||
this.databaseUri = databaseUri
|
||||
this.credentialStorage = credentialDatabaseStorage
|
||||
@@ -630,13 +637,6 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
EXTRACT_CREDENTIAL
|
||||
}
|
||||
|
||||
interface BuilderListener {
|
||||
fun retrieveCredentialForEncryption(): ByteArray
|
||||
fun conditionToStoreCredential(): Boolean
|
||||
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase)
|
||||
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
if (!keepConnection) {
|
||||
@@ -645,13 +645,11 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
advancedUnlockManager = null
|
||||
}
|
||||
}
|
||||
|
||||
super.onPause()
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
mAdvancedUnlockInfoView = null
|
||||
|
||||
super.onDestroyView()
|
||||
}
|
||||
|
||||
@@ -659,20 +657,11 @@ class AdvancedUnlockFragment: Fragment(), AdvancedUnlockManager.AdvancedUnlockCa
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
disconnect()
|
||||
advancedUnlockManager = null
|
||||
mBuilderListener = null
|
||||
}
|
||||
|
||||
super.onDestroy()
|
||||
}
|
||||
|
||||
override fun onDetach() {
|
||||
mBuilderListener = null
|
||||
|
||||
super.onDetach()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val TAG = AdvancedUnlockFragment::class.java.name
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,6 @@
|
||||
*/
|
||||
package com.kunzisoft.keepass.credentialprovider.activity
|
||||
|
||||
import android.app.Activity
|
||||
import android.app.PendingIntent
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -32,10 +31,10 @@ import androidx.annotation.RequiresApi
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.FileDatabaseSelectActivity
|
||||
import com.kunzisoft.keepass.activities.GroupActivity
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.activities.legacy.DatabaseModeActivity
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper
|
||||
import com.kunzisoft.keepass.credentialprovider.EntrySelectionHelper.buildActivityResultLauncher
|
||||
import com.kunzisoft.keepass.credentialprovider.SpecialMode
|
||||
import com.kunzisoft.keepass.credentialprovider.TypeMode
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillComponent
|
||||
import com.kunzisoft.keepass.credentialprovider.autofill.AutofillHelper
|
||||
@@ -117,7 +116,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
}
|
||||
else -> {
|
||||
// Not an autofill call
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
@@ -128,17 +127,13 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
autofillComponent: AutofillComponent?,
|
||||
searchInfo: SearchInfo) {
|
||||
if (autofillComponent == null) {
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
} else if (!KeeAutofillService.autofillAllowedFor(
|
||||
} else if (KeeAutofillService.autofillAllowedFor(
|
||||
applicationId = searchInfo.applicationId,
|
||||
webDomain = searchInfo.webDomain,
|
||||
context = this
|
||||
)) {
|
||||
showBlockRestartMessage()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
finish()
|
||||
} else {
|
||||
// If database is open
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = this,
|
||||
@@ -170,6 +165,10 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
showBlockRestartMessage()
|
||||
setResult(RESULT_CANCELED)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,10 +179,7 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
applicationId = searchInfo.applicationId,
|
||||
webDomain = searchInfo.webDomain,
|
||||
context = this
|
||||
)) {
|
||||
showBlockRestartMessage()
|
||||
setResult(Activity.RESULT_CANCELED)
|
||||
} else {
|
||||
)) {
|
||||
val readOnly = database?.isReadOnly != false
|
||||
SearchHelper.checkAutoSearchInfo(
|
||||
context = this,
|
||||
@@ -227,6 +223,9 @@ class AutofillLauncherActivity : DatabaseModeActivity() {
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
showBlockRestartMessage()
|
||||
setResult(RESULT_CANCELED)
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
@@ -53,9 +53,7 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
private var checkboxHardwareView: CompoundButton
|
||||
private var hardwareKeySelectionView: HardwareKeySelectionView
|
||||
|
||||
var onPasswordChecked: (CompoundButton.OnCheckedChangeListener)? = null
|
||||
var onKeyFileChecked: (CompoundButton.OnCheckedChangeListener)? = null
|
||||
var onHardwareKeyChecked: (CompoundButton.OnCheckedChangeListener)? = null
|
||||
var onConditionToStoreCredentialChanged: ((CredentialStorage, verified: Boolean) -> Unit)? = null
|
||||
var onValidateListener: (() -> Unit)? = null
|
||||
|
||||
private var mCredentialStorage: CredentialStorage = CredentialStorage.PASSWORD
|
||||
@@ -104,7 +102,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
}
|
||||
|
||||
checkboxPasswordView.setOnCheckedChangeListener { view, checked ->
|
||||
onPasswordChecked?.onCheckedChanged(view, checked)
|
||||
onConditionToStoreCredentialChanged?.invoke(
|
||||
mCredentialStorage,
|
||||
conditionToStoreCredential()
|
||||
)
|
||||
}
|
||||
checkboxKeyFileView.setOnCheckedChangeListener { view, checked ->
|
||||
if (checked) {
|
||||
@@ -112,7 +113,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
checkboxKeyFileView.isChecked = false
|
||||
}
|
||||
}
|
||||
onKeyFileChecked?.onCheckedChanged(view, checked)
|
||||
onConditionToStoreCredentialChanged?.invoke(
|
||||
mCredentialStorage,
|
||||
conditionToStoreCredential()
|
||||
)
|
||||
}
|
||||
checkboxHardwareView.setOnCheckedChangeListener { view, checked ->
|
||||
if (checked) {
|
||||
@@ -120,7 +124,10 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
checkboxHardwareView.isChecked = false
|
||||
}
|
||||
}
|
||||
onHardwareKeyChecked?.onCheckedChanged(view, checked)
|
||||
onConditionToStoreCredentialChanged?.invoke(
|
||||
mCredentialStorage,
|
||||
conditionToStoreCredential()
|
||||
)
|
||||
}
|
||||
|
||||
hardwareKeySelectionView.selectionListener = { _ ->
|
||||
|
||||
@@ -1,32 +1,152 @@
|
||||
package com.kunzisoft.keepass.viewmodels
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.kunzisoft.keepass.model.CipherDecryptDatabase
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
||||
class AdvancedUnlockViewModel : ViewModel() {
|
||||
|
||||
var allowAutoOpenBiometricPrompt : Boolean = true
|
||||
var deviceCredentialAuthSucceeded: Boolean? = null
|
||||
|
||||
val onInitAdvancedUnlockModeRequested : LiveData<Void?> get() = _onInitAdvancedUnlockModeRequested
|
||||
private val _onInitAdvancedUnlockModeRequested = SingleLiveEvent<Void?>()
|
||||
private val _uiState = MutableStateFlow(DeviceUnlockState())
|
||||
val uiState: StateFlow<DeviceUnlockState> = _uiState
|
||||
|
||||
val onUnlockAvailabilityCheckRequested : LiveData<Void?> get() = _onUnlockAvailabilityCheckRequested
|
||||
private val _onUnlockAvailabilityCheckRequested = SingleLiveEvent<Void?>()
|
||||
|
||||
val onDatabaseFileLoaded : LiveData<Uri?> get() = _onDatabaseFileLoaded
|
||||
private val _onDatabaseFileLoaded = SingleLiveEvent<Uri?>()
|
||||
|
||||
fun initAdvancedUnlockMode() {
|
||||
_onInitAdvancedUnlockModeRequested.call()
|
||||
fun checkUnlockAvailability(conditionToStoreCredentialVerified: Boolean? = null) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
onUnlockAvailabilityCheckRequested = true,
|
||||
isConditionToStoreCredentialVerified = conditionToStoreCredentialVerified
|
||||
?: _uiState.value.isConditionToStoreCredentialVerified
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkUnlockAvailability() {
|
||||
_onUnlockAvailabilityCheckRequested.call()
|
||||
fun consumeCheckUnlockAvailability() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
onUnlockAvailabilityCheckRequested = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun databaseFileLoaded(databaseUri: Uri?) {
|
||||
_onDatabaseFileLoaded.value = databaseUri
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
databaseFileLoaded = databaseUri
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeDatabaseFileLoaded() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
databaseFileLoaded = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun retrieveCredentialForEncryption() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
isCredentialRequired = true,
|
||||
credential = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun provideCredentialForEncryption(credential: ByteArray) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
isCredentialRequired = false,
|
||||
credential = credential
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeCredentialForEncryption() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
isCredentialRequired = false,
|
||||
credential = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCredentialEncrypted(cipherEncryptDatabase: CipherEncryptDatabase) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherEncryptDatabase = cipherEncryptDatabase
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeCredentialEncrypted() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherEncryptDatabase = null
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun onCredentialDecrypted(cipherDecryptDatabase: CipherDecryptDatabase) {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherDecryptDatabase = cipherDecryptDatabase
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun consumeCredentialDecrypted() {
|
||||
_uiState.update { currentState ->
|
||||
currentState.copy(
|
||||
cipherDecryptDatabase = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class DeviceUnlockState(
|
||||
val initAdvancedUnlockMode: Boolean = false,
|
||||
val databaseFileLoaded: Uri? = null,
|
||||
val isCredentialRequired: Boolean = false,
|
||||
val credential: ByteArray? = null,
|
||||
val isConditionToStoreCredentialVerified: Boolean = false,
|
||||
val onUnlockAvailabilityCheckRequested: Boolean = false,
|
||||
val cipherEncryptDatabase: CipherEncryptDatabase? = null,
|
||||
val cipherDecryptDatabase: CipherDecryptDatabase? = null
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as DeviceUnlockState
|
||||
|
||||
if (initAdvancedUnlockMode != other.initAdvancedUnlockMode) return false
|
||||
if (isCredentialRequired != other.isCredentialRequired) return false
|
||||
if (isConditionToStoreCredentialVerified != other.isConditionToStoreCredentialVerified) return false
|
||||
if (onUnlockAvailabilityCheckRequested != other.onUnlockAvailabilityCheckRequested) return false
|
||||
if (databaseFileLoaded != other.databaseFileLoaded) return false
|
||||
if (!credential.contentEquals(other.credential)) return false
|
||||
if (cipherEncryptDatabase != other.cipherEncryptDatabase) return false
|
||||
if (cipherDecryptDatabase != other.cipherDecryptDatabase) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = initAdvancedUnlockMode.hashCode()
|
||||
result = 31 * result + isCredentialRequired.hashCode()
|
||||
result = 31 * result + isConditionToStoreCredentialVerified.hashCode()
|
||||
result = 31 * result + onUnlockAvailabilityCheckRequested.hashCode()
|
||||
result = 31 * result + (databaseFileLoaded?.hashCode() ?: 0)
|
||||
result = 31 * result + (credential?.contentHashCode() ?: 0)
|
||||
result = 31 * result + (cipherEncryptDatabase?.hashCode() ?: 0)
|
||||
result = 31 * result + (cipherDecryptDatabase?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
@@ -33,10 +33,10 @@
|
||||
<string name="encryption">Encryption</string>
|
||||
<string name="contact">Contact</string>
|
||||
<string name="contribution">Contribution</string>
|
||||
<string name="about_description">password</string>
|
||||
<string name="encryption_algorithm">Encryption</string>
|
||||
<string name="about_description">Android implementation of the KeePass password manager</string>
|
||||
<string name="encryption_algorithm">Encryption algorithm</string>
|
||||
<string name="app_timeout">Timeout</string>
|
||||
<string name="app_timeout_summary">database</string>
|
||||
<string name="app_timeout_summary">Idle time before locking the database</string>
|
||||
<string name="application">App</string>
|
||||
<string name="brackets">Brackets</string>
|
||||
<string name="extended_ASCII">Extended ASCII</string>
|
||||
@@ -236,4 +236,4 @@
|
||||
<string name="list_groups_show_number_entries_title">Show number of entries</string>
|
||||
<string name="show_otp_token_summary">Displays OTP tokens in the list of entries</string>
|
||||
<string name="show_otp_token_title">Show OTP Token</string>
|
||||
</resources>
|
||||
</resources>
|
||||
|
||||
@@ -24,52 +24,115 @@ import java.util.*
|
||||
|
||||
class TemplateBuilder {
|
||||
|
||||
private val urlAttribute = TemplateAttribute(TemplateField.LABEL_URL, TemplateAttributeType.TEXT)
|
||||
private val usernameAttribute = TemplateAttribute(TemplateField.LABEL_USERNAME, TemplateAttributeType.TEXT)
|
||||
private val urlAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_URL,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val usernameAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_USERNAME,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val notesAttribute = TemplateAttribute(
|
||||
TemplateField.LABEL_NOTES,
|
||||
TemplateAttributeType.TEXT,
|
||||
false,
|
||||
TemplateAttributeOption().apply {
|
||||
label = TemplateField.LABEL_NOTES,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = false,
|
||||
options = TemplateAttributeOption().apply {
|
||||
setNumberLinesToMany()
|
||||
})
|
||||
private val holderAttribute = TemplateAttribute(TemplateField.LABEL_HOLDER, TemplateAttributeType.TEXT)
|
||||
private val numberAttribute = TemplateAttribute(TemplateField.LABEL_NUMBER, TemplateAttributeType.TEXT)
|
||||
private val cvvAttribute = TemplateAttribute(TemplateField.LABEL_CVV, TemplateAttributeType.TEXT, true)
|
||||
private val pinAttribute = TemplateAttribute(TemplateField.LABEL_PIN, TemplateAttributeType.TEXT, true)
|
||||
private val nameAttribute = TemplateAttribute(TemplateField.LABEL_NAME, TemplateAttributeType.TEXT)
|
||||
private val placeOfIssueAttribute = TemplateAttribute(TemplateField.LABEL_PLACE_OF_ISSUE, TemplateAttributeType.TEXT)
|
||||
}
|
||||
)
|
||||
private val holderAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_HOLDER,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val numberAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_NUMBER,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val cvvAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_CVV,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val pinAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PIN,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val nameAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_NAME,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val placeOfIssueAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PLACE_OF_ISSUE,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val dateOfIssueAttribute = TemplateAttribute(
|
||||
TemplateField.LABEL_DATE_OF_ISSUE,
|
||||
TemplateAttributeType.DATETIME,
|
||||
false,
|
||||
TemplateAttributeOption().apply {
|
||||
label = TemplateField.LABEL_DATE_OF_ISSUE,
|
||||
type = TemplateAttributeType.DATETIME,
|
||||
protected = false,
|
||||
options = TemplateAttributeOption().apply {
|
||||
setDateFormatToDate()
|
||||
})
|
||||
}
|
||||
)
|
||||
private val expirationDateAttribute = TemplateAttribute(
|
||||
TemplateField.LABEL_EXPIRATION,
|
||||
TemplateAttributeType.DATETIME,
|
||||
false,
|
||||
TemplateAttributeOption().apply {
|
||||
label = TemplateField.LABEL_EXPIRATION,
|
||||
type = TemplateAttributeType.DATETIME,
|
||||
protected = false,
|
||||
options = TemplateAttributeOption().apply {
|
||||
setDateFormatToDate()
|
||||
})
|
||||
private val emailAddressAttribute = TemplateAttribute(TemplateField.LABEL_EMAIL_ADDRESS, TemplateAttributeType.TEXT)
|
||||
private val passwordAttribute = TemplateAttribute(TemplateField.LABEL_PASSWORD, TemplateAttributeType.TEXT, true)
|
||||
private val ssidAttribute = TemplateAttribute(TemplateField.LABEL_SSID, TemplateAttributeType.TEXT)
|
||||
}
|
||||
)
|
||||
private val emailAddressAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_USERNAME,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
options = TemplateAttributeOption().apply {
|
||||
alias = TemplateField.LABEL_EMAIL_ADDRESS
|
||||
}
|
||||
)
|
||||
private val passwordAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PASSWORD,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val ssidAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_SSID,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val wirelessTypeAttribute = TemplateAttribute(
|
||||
TemplateField.LABEL_TYPE,
|
||||
TemplateAttributeType.LIST,
|
||||
false,
|
||||
TemplateAttributeOption().apply{
|
||||
label = TemplateField.LABEL_TYPE,
|
||||
type = TemplateAttributeType.LIST,
|
||||
protected = false,
|
||||
options = TemplateAttributeOption().apply{
|
||||
setListItems("WPA3", "WPA2", "WPA", "WEP")
|
||||
default = "WPA2"
|
||||
})
|
||||
private val tokenAttribute = TemplateAttribute(TemplateField.LABEL_TOKEN, TemplateAttributeType.TEXT)
|
||||
private val publicKeyAttribute = TemplateAttribute(TemplateField.LABEL_PUBLIC_KEY, TemplateAttributeType.TEXT)
|
||||
private val privateKeyAttribute = TemplateAttribute(TemplateField.LABEL_PRIVATE_KEY, TemplateAttributeType.TEXT, true)
|
||||
private val seedAttribute = TemplateAttribute(TemplateField.LABEL_SEED, TemplateAttributeType.TEXT, true)
|
||||
private val bicAttribute = TemplateAttribute(TemplateField.LABEL_BIC, TemplateAttributeType.TEXT)
|
||||
private val ibanAttribute = TemplateAttribute(TemplateField.LABEL_IBAN, TemplateAttributeType.TEXT)
|
||||
}
|
||||
)
|
||||
private val tokenAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_TOKEN,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val publicKeyAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PUBLIC_KEY,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val privateKeyAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_PRIVATE_KEY,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val seedAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_SEED,
|
||||
type = TemplateAttributeType.TEXT,
|
||||
protected = true
|
||||
)
|
||||
private val bicAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_BIC,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
private val ibanAttribute = TemplateAttribute(
|
||||
label = TemplateField.LABEL_IBAN,
|
||||
type = TemplateAttributeType.TEXT
|
||||
)
|
||||
|
||||
val email: Template
|
||||
get() {
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
KeePassDX ist ein <b>Passwort-Safe und -Manager</b>, der es ermöglicht, verschlüsselte <b>Daten in einer einzigen Datei</b> im offenen KeePass-Format zu bearbeiten und <b>die Formulare auf sichere Weise auszufüllen</b>, <b>erfordert keine Internetverbindung</b> und integriert Android-Designstandards. Die App ist <b>Open Source, ohne Werbung</b>.
|
||||
|
||||
<b>Funktionalitäten</b>
|
||||
- Erstellung von Datenbanken / Einträgen und Gruppen.
|
||||
- Unterstützung von .kdb- und .kdbx-Dateien (Version 1 bis 4) mit AES - Twofish - ChaCha20 - Argon2 Algorithmus.
|
||||
- Kompatibel mit den meisten alternativen Programmen (KeePass, KeePassXC, KeeWeb, ...).
|
||||
- Ermöglicht das schnelle Kopieren von Feldern und das Öffnen von URIs /URLs.
|
||||
- Biometrische Erkennung für eine schnelle Entsperrung (Fingerabdrücke / Entsperrung durch Gesicht / ...).
|
||||
- Verwaltung von Einmalpasswörtern (One-Time Password HOTP / TOTP) für die Zwei-Faktor-Authentifizierung (2FA).
|
||||
- Material Design mit Themen.
|
||||
- Automatisches Ausfüllen von Feldern.
|
||||
- Tastatur zum Ausfüllen von Feldern.
|
||||
- Dynamische Schablonen.
|
||||
- Historie jedes Eintrags.
|
||||
- Präzise Verwaltung der Parameter.
|
||||
- In nativen Sprachen geschriebener Code (Kotlin / Java / JNI / C).
|
||||
|
||||
- Erstellung von Datenbanken / Einträgen und Gruppen.
|
||||
- Unterstützung von .kdb- und .kdbx-Dateien (Version 1 bis 4) mit AES - Twofish - ChaCha20 - Argon2 Algorithmus.
|
||||
- Kompatibel mit den meisten alternativen Programmen (KeePass, KeePassXC, KeeWeb, ...).
|
||||
- Ermöglicht das schnelle Kopieren von Feldern und das Öffnen von URIs /URLs.
|
||||
- Biometrische Erkennung für eine schnelle Entsperrung (Fingerabdrücke / Entsperrung durch Gesicht / ...).
|
||||
- Verwaltung von Einmalpasswörtern (One-Time Password HOTP / TOTP) für die Zwei-Faktor-Authentifizierung (2FA).
|
||||
- Material Design mit Themen.
|
||||
- Automatisches Ausfüllen von Feldern.
|
||||
- Tastatur zum Ausfüllen von Feldern.
|
||||
- Dynamische Schablonen.
|
||||
- Historie jedes Eintrags.
|
||||
- Präzise Verwaltung der Parameter.
|
||||
- In nativen Sprachen geschriebener Code (Kotlin / Java / JNI / C).
|
||||
|
||||
Sie können spenden oder die Pro-Version kaufen, um einen besseren Service und eine schnelle Entwicklung der von Ihnen gewünschten Funktionen zu erhalten: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
* Fix Group notes #2053
|
||||
* Fix Dialog background #2005 #2004 (Thx @codokie)
|
||||
* Fix OTP configuration #2042 #2065 (Thx @Dev-ClayP)
|
||||
* Fix small UI elements #1987 #2007 (Thx @ymcx)
|
||||
* Fix small UI elements #1987 #2007 (Thx @ymcx)
|
||||
* RTL layout support #2021 (Thx @codokie)
|
||||
* App Metadata to translation #1823
|
||||
4
fastlane/metadata/android/en-US/changelogs/135.txt
Normal file
4
fastlane/metadata/android/en-US/changelogs/135.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
* Fix Autofill Registration #2089
|
||||
* Fix Biometric errors #2081
|
||||
* Fixed timestamp in copy file #1981 #1983
|
||||
* Fix Template Email #1986
|
||||
@@ -1,19 +1,20 @@
|
||||
KeePassDX is a <b>password safe and manager</b> allows editing <b>encrypted data in a single file</b> in the open KeePass format and <b>fill in the forms in a secure way</b>, requires <b>no Internet connection</b> and integrates Android design standards. The app is <b>open source, with no advertising</b>.
|
||||
|
||||
<b>Features</b>
|
||||
- Create database files / entries and groups.
|
||||
- Support for .kdb and .kdbx files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
|
||||
- Compatible with the majority of alternative programs (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Allows opening and copying URI / URL fields quickly.
|
||||
- Biometric recognition for fast unlocking (fingerprint / face unlock / …).
|
||||
- One-time password management (HOTP / TOTP) for two-factor authentication (2FA).
|
||||
- Material design with themes.
|
||||
- Auto-Fill and integration.
|
||||
- Field filling keyboard.
|
||||
- Dynamic templates.
|
||||
- History of each entry.
|
||||
- Precise management of settings.
|
||||
- Code written in native languages (Kotlin / Java / JNI / C).
|
||||
|
||||
- Create database files / entries and groups.
|
||||
- Support for .kdb and .kdbx files (version 1 to 4) with AES - Twofish - ChaCha20 - Argon2 algorithm.
|
||||
- Compatible with the majority of alternative programs (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Allows opening and copying URI / URL fields quickly.
|
||||
- Biometric recognition for fast unlocking (fingerprint / face unlock / …).
|
||||
- One-time password management (HOTP / TOTP) for two-factor authentication (2FA).
|
||||
- Material design with themes.
|
||||
- Auto-Fill and integration.
|
||||
- Field filling keyboard.
|
||||
- Dynamic templates.
|
||||
- History of each entry.
|
||||
- Precise management of settings.
|
||||
- Code written in native languages (Kotlin / Java / JNI / C).
|
||||
|
||||
You can donate or buy the pro version for better service and a quick development of features you want: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
|
||||
4
fastlane/metadata/android/fr-FR/changelogs/135.txt
Normal file
4
fastlane/metadata/android/fr-FR/changelogs/135.txt
Normal file
@@ -0,0 +1,4 @@
|
||||
* Correction de l'enregistrement pour le remplissage automatique #2089
|
||||
* Correction des erreurs biométriques #2081
|
||||
* Correction du timestamp dans le fichier de copie #1981 #1983
|
||||
* Correction des gabaris Email #1986
|
||||
@@ -2,19 +2,19 @@ KeePassDX est un <b>coffre-fort et un gestionnaire de mots de passe</b> qui perm
|
||||
|
||||
<b>Fonctionnalités</b>
|
||||
|
||||
- Création de bases de données / entrées et groupes.
|
||||
- Support des fichiers .kdb et .kdbx (version 1 à 4) avec algorithme AES - Twofish - ChaCha20 - Argon2.
|
||||
- Compatible avec la majorité des programmes alternatifs (KeePass, KeePassXC, KeeWeb, …)
|
||||
- Permet la copie rapide de champs et l'ouverture d'URI /URL.
|
||||
- Reconnaissance biométrique pour un déblocage rapide (Empreintes digitales / Déverouillage par visage / …).
|
||||
- Gestion des mots de passe à usage unique (One-Time Password HOTP / TOTP) pour l'authentification à deux facteurs (2FA).
|
||||
- Material design avec thèmes.
|
||||
- Remplissage automatique de champs.
|
||||
- Clavier de remplissage de champs.
|
||||
- Gabarits dynamiques.
|
||||
- Historique de chaque entrée.
|
||||
- Gestion précise des paramètres.
|
||||
- Code écrit en langages natifs (Kotlin / Java / JNI / C).
|
||||
- Création de bases de données / entrées et groupes.
|
||||
- Support des fichiers .kdb et .kdbx (version 1 à 4) avec algorithme AES - Twofish - ChaCha20 - Argon2.
|
||||
- Compatible avec la majorité des programmes alternatifs (KeePass, KeePassXC, KeeWeb, …)
|
||||
- Permet la copie rapide de champs et l'ouverture d'URI /URL.
|
||||
- Reconnaissance biométrique pour un déblocage rapide (Empreintes digitales / Déverouillage par visage / …).
|
||||
- Gestion des mots de passe à usage unique (One-Time Password HOTP / TOTP) pour l'authentification à deux facteurs (2FA).
|
||||
- Material design avec thèmes.
|
||||
- Remplissage automatique de champs.
|
||||
- Clavier de remplissage de champs.
|
||||
- Gabarits dynamiques.
|
||||
- Historique de chaque entrée.
|
||||
- Gestion précise des paramètres.
|
||||
- Code écrit en langages natifs (Kotlin / Java / JNI / C).
|
||||
|
||||
Vous pouvez faire un don ou acheter la version pro pour un meilleur service et un développement rapide des fonctionnalités que vous souhaitez : <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
KeePassDX は <b>オープンソース</b>かつ<b>広告なし</b>です。<b>複数の形式に対応する KeePass パスワード マネージャー</b>。Android の設計基準が組み込まれており、パスワード、鍵、デジタル ID を安全な方法で保存して使用できます。<b>インターネットに接続する必要はありません。</b>
|
||||
|
||||
<b>機能</b>
|
||||
- データベースファイル / エントリー・グループの作成
|
||||
- .kdb、.kdbx ファイル(バージョン1から4)に対応。AES、Twofish、ChaCha20、Argon2 アルゴリズムが使用可能
|
||||
- 主流の代替ソフトウェア(KeePass、KeePassXC, KeeWeb など)との互換性あり
|
||||
- URI / URL フィールドは開くのもコピーするのもすばやく行えます
|
||||
- 生体認証を使った高速ロック解除 (指紋認証 / 顔認証 / …)
|
||||
- 2 要素認証(2FA)のためのワンタイムパスワード管理(HOTP / TOTP)
|
||||
- マテリアルデザインに準拠した複数のテーマ
|
||||
- 自動入力機能の統合
|
||||
- フィールド入力用のキーボード
|
||||
- ダイナミックテンプレート
|
||||
- エントリーごとの履歴
|
||||
- 設定の細かな管理
|
||||
- コードはネイティブ言語(Kotlin / Java / JNI / C)で書かれています
|
||||
|
||||
- データベースファイル / エントリー・グループの作成
|
||||
- .kdb、.kdbx ファイル(バージョン1から4)に対応。AES、Twofish、ChaCha20、Argon2 アルゴリズムが使用可能
|
||||
- 主流の代替ソフトウェア(KeePass、KeePassXC, KeeWeb など)との互換性あり
|
||||
- URI / URL フィールドは開くのもコピーするのもすばやく行えます
|
||||
- 生体認証を使った高速ロック解除 (指紋認証 / 顔認証 / …)
|
||||
- 2 要素認証(2FA)のためのワンタイムパスワード管理(HOTP / TOTP)
|
||||
- マテリアルデザインに準拠した複数のテーマ
|
||||
- 自動入力機能の統合
|
||||
- フィールド入力用のキーボード
|
||||
- ダイナミックテンプレート
|
||||
- エントリーごとの履歴
|
||||
- 設定の細かな管理
|
||||
- コードはネイティブ言語(Kotlin / Java / JNI / C)で書かれています
|
||||
|
||||
寄付または pro バージョンの購入はサービスの改善と必要な機能の迅速な開発につながります: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro</a>
|
||||
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
KeePassDX имеет <b>открытый исходный код</b> и <b>без рекламы</b>. <b>Многоформатный менеджер паролей KeePass</b> это приложение которое позволяет безопасно сохранять и использовать пароли, ключи и цифровые идентификаторы за счет интеграции стандартов дизайна Android и <b>не требует подключения к Интернету</b>.
|
||||
|
||||
<b>Возможности</b>
|
||||
- Создание файлов/записей и групп.
|
||||
— Поддержка файлов .kdb и .kdbx (версии от 1 до 4) с алгоритмом AES — Twofish — ChaCha20 — Argon2.
|
||||
- Совместим с большинством альтернативных программ (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Позволяет быстро открывать и копировать поля URI / URL.
|
||||
- Биометрическое распознавание для быстрой разблокировки (отпечаток пальца /разблокировка по лицу / …).
|
||||
- Управление одноразовыми паролями (HOTP / TOTP) для двухфакторной аутентификации (2FA).
|
||||
- Материальный дизайн с темами.
|
||||
- Автозаполнение и интеграция.
|
||||
- Клавиатура для заполнения полей.
|
||||
- Динамические шаблоны.
|
||||
- История каждой записи.
|
||||
- Точное управление настройками.
|
||||
— Код написан на родных языках для платформы (Kotlin / Java / JNI / C).
|
||||
|
||||
- Создание файлов/записей и групп.
|
||||
- Поддержка файлов .kdb и .kdbx (версии от 1 до 4) с алгоритмом AES — Twofish — ChaCha20 — Argon2.
|
||||
- Совместим с большинством альтернативных программ (KeePass, KeePassXC, KeeWeb, …).
|
||||
- Позволяет быстро открывать и копировать поля URI / URL.
|
||||
- Биометрическое распознавание для быстрой разблокировки (отпечаток пальца /разблокировка по лицу / …).
|
||||
- Управление одноразовыми паролями (HOTP / TOTP) для двухфакторной аутентификации (2FA).
|
||||
- Материальный дизайн с темами.
|
||||
- Автозаполнение и интеграция.
|
||||
- Клавиатура для заполнения полей.
|
||||
- Динамические шаблоны.
|
||||
- История каждой записи.
|
||||
- Точное управление настройками.
|
||||
- Код написан на родных языках для платформы (Kotlin / Java / JNI / C).
|
||||
|
||||
Вы можете пожертвовать или купить профессиональную версию чтобы улучшить обслуживание и ускорить разработку нужных вам возможностей: <a href="https://play.google.com/store/apps/details?id=com.kunzisoft.keepass.pro">KeePass Pro</a>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user