mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Change main credential validation
This commit is contained in:
@@ -36,6 +36,7 @@ import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.activities.helpers.ExternalFileHelper
|
||||
import com.kunzisoft.keepass.activities.helpers.setOpenDocumentClickListener
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
|
||||
import com.kunzisoft.keepass.model.MainCredential
|
||||
import com.kunzisoft.keepass.password.PasswordEntropy
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
@@ -53,7 +54,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
private lateinit var rootView: View
|
||||
|
||||
private lateinit var passwordCheckBox: CompoundButton
|
||||
private lateinit var passKeyView: PassKeyView
|
||||
private lateinit var passwordView: PassKeyView
|
||||
private lateinit var passwordRepeatTextInputLayout: TextInputLayout
|
||||
private lateinit var passwordRepeatView: TextView
|
||||
|
||||
@@ -139,7 +140,7 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
}
|
||||
|
||||
passwordCheckBox = rootView.findViewById(R.id.password_checkbox)
|
||||
passKeyView = rootView.findViewById(R.id.password_view)
|
||||
passwordView = rootView.findViewById(R.id.password_view)
|
||||
passwordRepeatTextInputLayout = rootView.findViewById(R.id.password_repeat_input_layout)
|
||||
passwordRepeatView = rootView.findViewById(R.id.password_confirmation)
|
||||
passwordRepeatView.applyFontVisibility()
|
||||
@@ -165,8 +166,15 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
}
|
||||
keyFileSelectionView.setOpenDocumentClickListener(mExternalFileHelper)
|
||||
|
||||
hardwareKeySelectionView.selectionListener = { _ ->
|
||||
hardwareKeySelectionView.selectionListener = { hardwareKey ->
|
||||
hardwareKeyCheckBox.isChecked = true
|
||||
hardwareKeySelectionView.error =
|
||||
if (!HardwareKeyResponseHelper.isHardwareKeyAvailable(requireActivity(), hardwareKey)) {
|
||||
// show hardware driver dialog if required
|
||||
getString(R.string.warning_hardware_key_required)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
val dialog = builder.create()
|
||||
@@ -194,18 +202,41 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
}
|
||||
|
||||
private fun approveMainCredential() {
|
||||
var error = verifyPassword() || verifyKeyFile() || verifyHardwareKey()
|
||||
if (!passwordCheckBox.isChecked
|
||||
&& !keyFileCheckBox.isChecked
|
||||
&& !hardwareKeyCheckBox.isChecked
|
||||
val errorPassword = verifyPassword()
|
||||
val errorKeyFile = verifyKeyFile()
|
||||
val errorHardwareKey = verifyHardwareKey()
|
||||
// Check all to fill error
|
||||
var error = errorPassword || errorKeyFile || errorHardwareKey
|
||||
val hardwareKey = hardwareKeySelectionView.hardwareKey
|
||||
if (!error
|
||||
&& (!passwordCheckBox.isChecked)
|
||||
&& (!keyFileCheckBox.isChecked)
|
||||
&& (!hardwareKeyCheckBox.isChecked)
|
||||
) {
|
||||
error = true
|
||||
if (mAllowNoMasterKey)
|
||||
if (mAllowNoMasterKey) {
|
||||
// show no key dialog if required
|
||||
showNoKeyConfirmationDialog()
|
||||
else {
|
||||
} else {
|
||||
passwordRepeatTextInputLayout.error =
|
||||
getString(R.string.error_disallow_no_credentials)
|
||||
}
|
||||
} else if (!error
|
||||
&& mMasterPassword.isNullOrEmpty()
|
||||
&& !keyFileCheckBox.isChecked
|
||||
&& !hardwareKeyCheckBox.isChecked
|
||||
) {
|
||||
// show empty password dialog if required
|
||||
error = true
|
||||
showEmptyPasswordConfirmationDialog()
|
||||
} else if (!error
|
||||
&& hardwareKey != null
|
||||
&& !HardwareKeyResponseHelper.isHardwareKeyAvailable(
|
||||
requireActivity(), hardwareKey, false)
|
||||
) {
|
||||
// show hardware driver dialog if required
|
||||
error = true
|
||||
hardwareKeySelectionView.error = getString(R.string.warning_hardware_key_required)
|
||||
}
|
||||
if (!error) {
|
||||
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
|
||||
@@ -213,30 +244,11 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun retrieveMainCredential(): MainCredential {
|
||||
val masterPassword = if (passwordCheckBox.isChecked) mMasterPassword else null
|
||||
val keyFileUri = if (keyFileCheckBox.isChecked) mKeyFileUri else null
|
||||
val hardwareKey = if (hardwareKeyCheckBox.isChecked) mHardwareKey else null
|
||||
return MainCredential(masterPassword, keyFileUri, hardwareKey)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// To check checkboxes if a text is present
|
||||
passKeyView.addTextChangedListener(passwordTextWatcher)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
passKeyView.removeTextChangedListener(passwordTextWatcher)
|
||||
}
|
||||
|
||||
private fun verifyPassword(): Boolean {
|
||||
var error = false
|
||||
passwordRepeatTextInputLayout.error = null
|
||||
if (passwordCheckBox.isChecked) {
|
||||
mMasterPassword = passKeyView.passwordString
|
||||
mMasterPassword = passwordView.passwordString
|
||||
val confPassword = passwordRepeatView.text.toString()
|
||||
|
||||
// Verify that passwords match
|
||||
@@ -245,21 +257,13 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
// Passwords do not match
|
||||
passwordRepeatTextInputLayout.error = getString(R.string.error_pass_match)
|
||||
}
|
||||
|
||||
if ((mMasterPassword.isNullOrEmpty())
|
||||
&& (!keyFileCheckBox.isChecked
|
||||
|| keyFileSelectionView.uri == null)
|
||||
&& (!hardwareKeyCheckBox.isChecked
|
||||
|| hardwareKeySelectionView.hardwareKey == null)) {
|
||||
error = true
|
||||
showEmptyPasswordConfirmationDialog()
|
||||
}
|
||||
}
|
||||
return error
|
||||
}
|
||||
|
||||
private fun verifyKeyFile(): Boolean {
|
||||
var error = false
|
||||
keyFileSelectionView.error = null
|
||||
if (keyFileCheckBox.isChecked) {
|
||||
keyFileSelectionView.uri?.let { uri ->
|
||||
mKeyFileUri = uri
|
||||
@@ -273,25 +277,45 @@ class SetMainCredentialDialogFragment : DatabaseDialogFragment() {
|
||||
|
||||
private fun verifyHardwareKey(): Boolean {
|
||||
var error = false
|
||||
hardwareKeySelectionView.error = null
|
||||
if (hardwareKeyCheckBox.isChecked) {
|
||||
hardwareKeySelectionView.hardwareKey.let { hardwareKey ->
|
||||
hardwareKeySelectionView.hardwareKey?.let { hardwareKey ->
|
||||
mHardwareKey = hardwareKey
|
||||
} ?: run {
|
||||
error = true
|
||||
hardwareKeySelectionView.error = getString(R.string.error_no_hardware_key)
|
||||
}
|
||||
// TODO verify drivers
|
||||
// error = true
|
||||
}
|
||||
return error
|
||||
}
|
||||
|
||||
private fun retrieveMainCredential(): MainCredential {
|
||||
val masterPassword = if (passwordCheckBox.isChecked) mMasterPassword else null
|
||||
val keyFileUri = if (keyFileCheckBox.isChecked) mKeyFileUri else null
|
||||
val hardwareKey = if (hardwareKeyCheckBox.isChecked) mHardwareKey else null
|
||||
return MainCredential(masterPassword, keyFileUri, hardwareKey)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
|
||||
// To check checkboxes if a text is present
|
||||
passwordView.addTextChangedListener(passwordTextWatcher)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
passwordView.removeTextChangedListener(passwordTextWatcher)
|
||||
}
|
||||
|
||||
private fun showEmptyPasswordConfirmationDialog() {
|
||||
activity?.let {
|
||||
val builder = AlertDialog.Builder(it)
|
||||
builder.setMessage(R.string.warning_empty_password)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
if (!verifyKeyFile()) {
|
||||
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
|
||||
this@SetMainCredentialDialogFragment.dismiss()
|
||||
}
|
||||
mListener?.onAssignKeyDialogPositiveClick(retrieveMainCredential())
|
||||
this@SetMainCredentialDialogFragment.dismiss()
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
mEmptyPasswordConfirmationDialog = builder.create()
|
||||
|
||||
@@ -42,6 +42,7 @@ import com.kunzisoft.keepass.database.element.database.CompressionAlgorithm
|
||||
import com.kunzisoft.keepass.database.element.node.Node
|
||||
import com.kunzisoft.keepass.database.element.node.NodeId
|
||||
import com.kunzisoft.keepass.database.element.node.Type
|
||||
import com.kunzisoft.keepass.database.exception.InvalidCredentialsDatabaseException
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.hardware.HardwareKeyResponseHelper
|
||||
import com.kunzisoft.keepass.model.CipherEncryptDatabase
|
||||
@@ -84,7 +85,7 @@ import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment
|
||||
import com.kunzisoft.keepass.tasks.ProgressTaskDialogFragment.Companion.PROGRESS_TASK_DIALOG_TAG
|
||||
import com.kunzisoft.keepass.utils.DATABASE_START_TASK_ACTION
|
||||
import com.kunzisoft.keepass.utils.DATABASE_STOP_TASK_ACTION
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.launch
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
@@ -134,8 +135,12 @@ class DatabaseTaskProvider {
|
||||
respondToChallengeIfAllowed()
|
||||
}
|
||||
this.requestChallengeListener = object: DatabaseTaskNotificationService.RequestChallengeListener {
|
||||
override fun onChallengeResponseRequested(hardwareKey: HardwareKey?, seed: ByteArray?) {
|
||||
mHardwareKeyResponseHelper?.launchChallengeForResponse(hardwareKey, seed)
|
||||
override fun onChallengeResponseRequested(hardwareKey: HardwareKey, seed: ByteArray?) {
|
||||
if (HardwareKeyResponseHelper.isHardwareKeyAvailable(activity, hardwareKey)) {
|
||||
mHardwareKeyResponseHelper?.launchChallengeForResponse(hardwareKey, seed)
|
||||
} else {
|
||||
throw InvalidCredentialsDatabaseException("Driver for $hardwareKey is required.")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -4,13 +4,17 @@ import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.activity.result.ActivityResult
|
||||
import androidx.activity.result.ActivityResultCallback
|
||||
import androidx.activity.result.ActivityResultLauncher
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.utils.UriUtil
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class HardwareKeyResponseHelper {
|
||||
|
||||
@@ -32,14 +36,13 @@ class HardwareKeyResponseHelper {
|
||||
fun buildHardwareKeyResponse(onChallengeResponded: (challengeResponse: ByteArray?,
|
||||
extra: Bundle?) -> Unit) {
|
||||
val resultCallback = ActivityResultCallback<ActivityResult> { result ->
|
||||
Log.d(TAG, "resultCode from ykdroid: " + result.resultCode)
|
||||
if (result.resultCode == Activity.RESULT_OK) {
|
||||
val challengeResponse: ByteArray? = result.data?.getByteArrayExtra("response")
|
||||
Log.d(TAG, "Response: " + challengeResponse.contentToString())
|
||||
Log.d(TAG, "Response form challenge : " + challengeResponse.contentToString())
|
||||
onChallengeResponded.invoke(challengeResponse,
|
||||
result.data?.getBundleExtra(EXTRA_BUNDLE_KEY))
|
||||
} else {
|
||||
Log.e(TAG, "Response error")
|
||||
Log.e(TAG, "Response from challenge error")
|
||||
onChallengeResponded.invoke(null,
|
||||
result.data?.getBundleExtra(EXTRA_BUNDLE_KEY))
|
||||
}
|
||||
@@ -58,39 +61,25 @@ class HardwareKeyResponseHelper {
|
||||
}
|
||||
}
|
||||
|
||||
fun launchChallengeForResponse(hardwareKey: HardwareKey?, seed: ByteArray?) {
|
||||
try {
|
||||
when (hardwareKey) {
|
||||
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||
// Transform the seed before sending
|
||||
var challenge: ByteArray? = null
|
||||
if (seed != null) {
|
||||
challenge = ByteArray(64)
|
||||
seed.copyInto(challenge, 0, 0, 32)
|
||||
challenge.fill(32, 32, 64)
|
||||
}
|
||||
// Send to the driver
|
||||
getChallengeResponseResultLauncher?.launch(Intent(YKDROID_CHALLENGE_RESPONSE_INTENT).apply {
|
||||
putExtra(YKDROID_SEED_KEY, challenge)
|
||||
})
|
||||
Log.d(TAG, "Challenge sent : " + challenge.contentToString())
|
||||
}
|
||||
else -> {
|
||||
// TODO other algorithm
|
||||
}
|
||||
fun launchChallengeForResponse(hardwareKey: HardwareKey, seed: ByteArray?) {
|
||||
when (hardwareKey) {
|
||||
HardwareKey.FIDO2_SECRET -> {
|
||||
// TODO FIDO2
|
||||
throw Exception("FIDO2 not implemented")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"Unable to retrieve the challenge response",
|
||||
e
|
||||
)
|
||||
e.message?.let { message ->
|
||||
Toast.makeText(
|
||||
activity,
|
||||
message,
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||
// Transform the seed before sending
|
||||
var challenge: ByteArray? = null
|
||||
if (seed != null) {
|
||||
challenge = ByteArray(64)
|
||||
seed.copyInto(challenge, 0, 0, 32)
|
||||
challenge.fill(32, 32, 64)
|
||||
}
|
||||
// Send to the driver
|
||||
getChallengeResponseResultLauncher!!.launch(Intent(YKDROID_CHALLENGE_RESPONSE_INTENT).apply {
|
||||
putExtra(YKDROID_SEED_KEY, challenge)
|
||||
})
|
||||
Log.d(TAG, "Challenge sent : " + challenge.contentToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,9 +87,39 @@ class HardwareKeyResponseHelper {
|
||||
companion object {
|
||||
private val TAG = HardwareKeyResponseHelper::class.java.simpleName
|
||||
|
||||
private const val YKDROID_CHALLENGE_RESPONSE_INTENT = "net.pp3345.ykdroid.intent.action.CHALLENGE_RESPONSE"
|
||||
private const val YKDROID_PACKAGE = "net.pp3345.ykdroid"
|
||||
private const val YKDROID_CHALLENGE_RESPONSE_INTENT =
|
||||
"$YKDROID_PACKAGE.intent.action.CHALLENGE_RESPONSE"
|
||||
private const val YKDROID_SEED_KEY = "challenge"
|
||||
private const val EXTRA_BUNDLE_KEY = "EXTRA_BUNDLE_KEY"
|
||||
|
||||
fun isHardwareKeyAvailable(activity: FragmentActivity,
|
||||
hardwareKey: HardwareKey,
|
||||
showDialog: Boolean = true): Boolean {
|
||||
return when (hardwareKey) {
|
||||
HardwareKey.FIDO2_SECRET -> {
|
||||
// TODO FIDO2
|
||||
if (showDialog)
|
||||
showHardwareKeyDriverNeeded(activity)
|
||||
false
|
||||
}
|
||||
HardwareKey.CHALLENGE_RESPONSE_YUBIKEY -> {
|
||||
// TODO (UriUtil.isExternalAppInstalled(activity, KEEPASSDX_PRO_PACKAGE)
|
||||
UriUtil.isExternalAppInstalled(activity, YKDROID_PACKAGE)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun showHardwareKeyDriverNeeded(activity: FragmentActivity) {
|
||||
activity.lifecycleScope.launch {
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
builder.setMessage(R.string.warning_hardware_key_required)
|
||||
.setPositiveButton(android.R.string.ok) { _, _ ->
|
||||
UriUtil.openExternalApp(activity, UriUtil.KEEPASSDX_PRO_PACKAGE)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { _, _ -> }
|
||||
builder.create().show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +70,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
private var mActionTaskBinder = ActionTaskBinder()
|
||||
private var mActionTaskListeners = mutableListOf<ActionTaskListener>()
|
||||
// Channel to connect asynchronously a listener or a response
|
||||
private var mRequestChallengeListenerChannel = Channel<RequestChallengeListener?>(0)
|
||||
private var mRequestChallengeListenerChannel = Channel<RequestChallengeListener>(0)
|
||||
private var mResponseChallengeChannel = Channel<ByteArray?>(0)
|
||||
|
||||
private var mActionRunning = false
|
||||
@@ -156,7 +156,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
}
|
||||
|
||||
interface RequestChallengeListener {
|
||||
fun onChallengeResponseRequested(hardwareKey: HardwareKey?, seed: ByteArray?)
|
||||
fun onChallengeResponseRequested(hardwareKey: HardwareKey, seed: ByteArray?)
|
||||
}
|
||||
|
||||
fun checkDatabase() {
|
||||
@@ -598,7 +598,7 @@ open class DatabaseTaskNotificationService : LockNotificationService(), Progress
|
||||
runBlocking {
|
||||
// Send the request
|
||||
val challengeResponseRequestListener = mRequestChallengeListenerChannel.receive()
|
||||
challengeResponseRequestListener?.onChallengeResponseRequested(hardwareKey, seed)
|
||||
challengeResponseRequestListener.onChallengeResponseRequested(hardwareKey, seed)
|
||||
// Wait the response
|
||||
response = mResponseChallengeChannel.receive() ?: byteArrayOf()
|
||||
}
|
||||
|
||||
@@ -269,11 +269,11 @@ object UriUtil {
|
||||
|
||||
fun contributingUser(context: Context): Boolean {
|
||||
return (Education.isEducationScreenReclickedPerformed(context)
|
||||
|| isExternalAppInstalled(context, "com.kunzisoft.keepass.pro", false)
|
||||
|| isExternalAppInstalled(context, KEEPASSDX_PRO_PACKAGE, false)
|
||||
)
|
||||
}
|
||||
|
||||
private fun isExternalAppInstalled(context: Context, packageName: String, showError: Boolean = true): Boolean {
|
||||
fun isExternalAppInstalled(context: Context, packageName: String, showError: Boolean = true): Boolean {
|
||||
try {
|
||||
context.applicationContext.packageManager.getPackageInfo(packageName, PackageManager.GET_ACTIVITIES)
|
||||
Education.setEducationScreenReclickedPerformed(context)
|
||||
@@ -317,4 +317,6 @@ object UriUtil {
|
||||
}
|
||||
|
||||
private const val TAG = "UriUtil"
|
||||
|
||||
const val KEEPASSDX_PRO_PACKAGE = "com.kunzisoft.keepass.pro"
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import android.widget.ArrayAdapter
|
||||
import android.widget.Filter
|
||||
import androidx.appcompat.widget.AppCompatAutoCompleteTextView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import com.google.android.material.textfield.TextInputLayout
|
||||
import com.kunzisoft.keepass.R
|
||||
import com.kunzisoft.keepass.hardware.HardwareKey
|
||||
import com.kunzisoft.keepass.utils.readEnum
|
||||
@@ -25,8 +26,9 @@ class HardwareKeySelectionView @JvmOverloads constructor(context: Context,
|
||||
|
||||
private var mHardwareKey: HardwareKey? = null
|
||||
|
||||
private val hardwareKeyLayout: TextInputLayout
|
||||
private val hardwareKeyCompletion: AppCompatAutoCompleteTextView
|
||||
var selectionListener: ((HardwareKey?)-> Unit)? = null
|
||||
var selectionListener: ((HardwareKey)-> Unit)? = null
|
||||
|
||||
private val mHardwareKeyAdapter = ArrayAdapterNoFilter(context)
|
||||
|
||||
@@ -66,6 +68,7 @@ class HardwareKeySelectionView @JvmOverloads constructor(context: Context,
|
||||
val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater?
|
||||
inflater?.inflate(R.layout.view_hardware_key_selection, this)
|
||||
|
||||
hardwareKeyLayout = findViewById(R.id.input_entry_hardware_key_layout)
|
||||
hardwareKeyCompletion = findViewById(R.id.input_entry_hardware_key_completion)
|
||||
|
||||
hardwareKeyCompletion.inputType = InputType.TYPE_NULL
|
||||
@@ -74,7 +77,9 @@ class HardwareKeySelectionView @JvmOverloads constructor(context: Context,
|
||||
hardwareKeyCompletion.onItemClickListener =
|
||||
AdapterView.OnItemClickListener { _, _, position, _ ->
|
||||
mHardwareKey = HardwareKey.fromPosition(position)
|
||||
selectionListener?.invoke(mHardwareKey)
|
||||
mHardwareKey?.let { hardwareKey ->
|
||||
selectionListener?.invoke(hardwareKey)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,6 +93,12 @@ class HardwareKeySelectionView @JvmOverloads constructor(context: Context,
|
||||
hardwareKeyCompletion.setSelection(value.ordinal)
|
||||
}
|
||||
|
||||
var error: CharSequence?
|
||||
get() = hardwareKeyLayout.error
|
||||
set(value) {
|
||||
hardwareKeyLayout.error = value
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable {
|
||||
val superState = super.onSaveInstanceState()
|
||||
val saveState = SavedState(superState)
|
||||
|
||||
@@ -107,9 +107,19 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
onPasswordChecked?.onCheckedChanged(view, checked)
|
||||
}
|
||||
checkboxKeyFileView.setOnCheckedChangeListener { view, checked ->
|
||||
if (checked) {
|
||||
if (keyFileSelectionView.uri == null) {
|
||||
checkboxKeyFileView.isChecked = false
|
||||
}
|
||||
}
|
||||
onKeyFileChecked?.onCheckedChanged(view, checked)
|
||||
}
|
||||
checkboxHardwareView.setOnCheckedChangeListener { view, checked ->
|
||||
if (checked) {
|
||||
if (hardwareKeySelectionView.hardwareKey == null) {
|
||||
checkboxHardwareView.isChecked = false
|
||||
}
|
||||
}
|
||||
onHardwareKeyChecked?.onCheckedChanged(view, checked)
|
||||
}
|
||||
|
||||
@@ -152,8 +162,8 @@ class MainCredentialView @JvmOverloads constructor(context: Context,
|
||||
|
||||
fun isFill(): Boolean {
|
||||
return checkboxPasswordView.isChecked
|
||||
|| checkboxKeyFileView.isChecked // TODO better recognition
|
||||
|| checkboxHardwareView.isChecked
|
||||
|| (checkboxKeyFileView.isChecked && keyFileSelectionView.uri != null)
|
||||
|| (checkboxHardwareView.isChecked && hardwareKeySelectionView.hardwareKey != null)
|
||||
}
|
||||
|
||||
fun getMainCredential(): MainCredential {
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
style="@style/KeepassDXStyle.TextInputLayout.ExposedMenu"
|
||||
android:id="@+id/input_entry_hardware_key"
|
||||
android:id="@+id/input_entry_hardware_key_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/hardware_key">
|
||||
|
||||
@@ -161,6 +161,7 @@
|
||||
<string name="error_no_name">Enter a name.</string>
|
||||
<string name="error_word_reserved">This word is reserved and cannot be used.</string>
|
||||
<string name="error_nokeyfile">Select a keyfile.</string>
|
||||
<string name="error_no_hardware_key">Select a hardware key.</string>
|
||||
<string name="error_out_of_memory">No memory to load your entire database.</string>
|
||||
<string name="error_load_database">Could not load your database.</string>
|
||||
<string name="error_load_database_KDF_memory">Could not load the key. Try to lower the KDF \"Memory Usage\".</string>
|
||||
@@ -355,6 +356,7 @@
|
||||
<string name="warning_database_revoked">Access to the file revoked by the file manager, close the database and reopen it from its location.</string>
|
||||
<string name="warning_exact_alarm">You have not allowed the app to use an exact alarm. As a result, the features requiring a timer will not be done with an exact time.</string>
|
||||
<string name="warning_keyfile_integrity">The hash of the file is not guaranteed because Android can change its data on the fly. Change the file extension to .bin for correct integrity.</string>
|
||||
<string name="warning_hardware_key_required">Driver for this hardware is required.</string>
|
||||
<string name="permission">Permission</string>
|
||||
<string name="version_label">Version %1$s</string>
|
||||
<string name="build_label">Build %1$s</string>
|
||||
|
||||
Reference in New Issue
Block a user