Add OTP auto generate field for Magikeyboard

This commit is contained in:
J-Jamet
2019-11-06 13:39:19 +01:00
parent 477a8f2e38
commit e6d9df2b98
9 changed files with 344 additions and 133 deletions

View File

@@ -44,6 +44,7 @@ import com.kunzisoft.keepass.education.EntryActivityEducation
import com.kunzisoft.keepass.icons.assignDatabaseIcon
import com.kunzisoft.keepass.magikeyboard.MagikIME
import com.kunzisoft.keepass.notifications.ClipboardEntryNotificationService
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.settings.PreferencesUtil
import com.kunzisoft.keepass.settings.SettingsAutofillActivity
import com.kunzisoft.keepass.timeout.ClipboardHelper
@@ -69,7 +70,7 @@ class EntryActivity : LockingHideActivity() {
private var mIsHistory: Boolean = false
private var mShowPassword: Boolean = false
private var mOtpEntryFields: OtpEntryFields? = null
private var mOtpElement: OtpElement? = null
private var clipboardHelper: ClipboardHelper? = null
private var firstLaunchOfActivity: Boolean = false
@@ -140,7 +141,9 @@ class EntryActivity : LockingHideActivity() {
mEntry?.let { entry ->
// Init OTP
mOtpEntryFields = OtpEntryFields(entry)
mOtpElement = OtpEntryFields{ id ->
entry.customFields[id]?.toString()
}.otpElement
// Fill data in resume to update from EntryEditActivity
fillEntryDataInContentsView(entry)
@@ -228,11 +231,11 @@ class EntryActivity : LockingHideActivity() {
}
}
mOtpEntryFields?.let { otpEntryFields ->
entryContentsView?.assignOtp(otpEntryFields, entryProgress,
mOtpElement?.let { otpElement ->
entryContentsView?.assignOtp(otpElement, entryProgress,
View.OnClickListener {
clipboardHelper?.timeoutCopyToClipboard(
otpEntryFields.token,
otpElement.token,
getString(R.string.copy_field, getString(R.string.entry_otp))
)
})

View File

@@ -18,7 +18,6 @@ class FieldsAdapter(context: Context) : RecyclerView.Adapter<FieldsAdapter.Field
var fields: MutableList<Field> = ArrayList()
var onItemClickListener: OnItemClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FieldViewHolder {
val view = inflater.inflate(R.layout.keyboard_popup_fields_item, parent, false)
return FieldViewHolder(view)

View File

@@ -5,6 +5,7 @@ import android.os.Parcelable
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.model.EntryInfo
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.OtpEntryFields
import java.util.*
import kotlin.collections.ArrayList
@@ -302,6 +303,10 @@ class EntryVersioned : NodeVersioned, PwEntryInterface<GroupVersioned> {
------------
*/
/**
* Retrieve generated entry info,
* Remove parameter fields and add auto generated elements in auto custom fields
*/
fun getEntryInfo(database: Database?, raw: Boolean = false): EntryInfo {
val entryInfo = EntryInfo()
if (raw)
@@ -318,6 +323,12 @@ class EntryVersioned : NodeVersioned, PwEntryInterface<GroupVersioned> {
entryInfo.customFields.add(
Field(entry.key, entry.value))
}
// Add otpElement to generate token
entryInfo.otpElement = OtpEntryFields { key ->
customFields[key]?.toString()
}.otpElement
// Replace parameter fields by generated OTP fields
entryInfo.customFields = OtpEntryFields.generateAutoFields(entryInfo.customFields)
if (!raw)
database?.stopManageEntry(this)
return entryInfo

View File

@@ -100,20 +100,25 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
val popupFieldsView = LayoutInflater.from(context)
.inflate(R.layout.keyboard_popup_fields, FrameLayout(context))
popupCustomKeys?.dismiss()
popupCustomKeys = PopupWindow(context)
popupCustomKeys?.width = WindowManager.LayoutParams.WRAP_CONTENT
popupCustomKeys?.height = WindowManager.LayoutParams.WRAP_CONTENT
popupCustomKeys?.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
popupCustomKeys?.inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
popupCustomKeys?.contentView = popupFieldsView
dismissCustomKeys()
popupCustomKeys = PopupWindow(context).apply {
width = WindowManager.LayoutParams.WRAP_CONTENT
height = WindowManager.LayoutParams.WRAP_CONTENT
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
inputMethodMode = PopupWindow.INPUT_METHOD_NEEDED
contentView = popupFieldsView
}
val recyclerView = popupFieldsView.findViewById<androidx.recyclerview.widget.RecyclerView>(R.id.keyboard_popup_fields_list)
fieldsAdapter = FieldsAdapter(this)
fieldsAdapter?.onItemClickListener = object : FieldsAdapter.OnItemClickListener {
override fun onItemClick(item: Field) {
currentInputConnection.commitText(item.protectedValue.toString(), 1)
if (entryInfoKey?.isAutoGeneratedField(item) == true) {
entryInfoKey?.doForAutoGeneratedField(item) { valueGenerated ->
currentInputConnection.commitText(valueGenerated, 1)
}
} else
currentInputConnection.commitText(item.protectedValue.toString(), 1)
}
}
recyclerView.layoutManager = androidx.recyclerview.widget.LinearLayoutManager(this, androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL, true)
@@ -129,6 +134,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
}
private fun assignKeyboardView() {
dismissCustomKeys()
if (keyboardView != null) {
if (entryInfoKey != null) {
if (keyboardEntry != null) {
@@ -138,7 +144,6 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
} else {
if (keyboard != null) {
hideEntryInfo()
dismissCustomKeys()
keyboardView?.keyboard = keyboard
}
}
@@ -251,6 +256,7 @@ class MagikIME : InputMethodService(), KeyboardView.OnKeyboardActionListener {
KEY_FIELDS -> {
if (entryInfoKey != null) {
fieldsAdapter?.fields = entryInfoKey!!.customFields
fieldsAdapter?.notifyDataSetChanged()
}
popupCustomKeys?.showAtLocation(keyboardView, Gravity.END or Gravity.TOP, 0, 0)
}

View File

@@ -2,8 +2,9 @@ package com.kunzisoft.keepass.model
import android.os.Parcel
import android.os.Parcelable
import java.util.ArrayList
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpEntryFields.Companion.OTP_TOKEN_FIELD
import java.util.*
class EntryInfo : Parcelable {
@@ -14,6 +15,7 @@ class EntryInfo : Parcelable {
var url: String = ""
var notes: String = ""
var customFields: MutableList<Field> = ArrayList()
var otpElement: OtpElement = OtpElement()
constructor()
@@ -25,6 +27,7 @@ class EntryInfo : Parcelable {
url = parcel.readString() ?: url
notes = parcel.readString() ?: notes
parcel.readList(customFields, Field::class.java.classLoader)
otpElement = parcel.readParcelable(OtpElement::class.java.classLoader) ?: otpElement
}
override fun describeContents(): Int {
@@ -39,6 +42,7 @@ class EntryInfo : Parcelable {
parcel.writeString(url)
parcel.writeString(notes)
parcel.writeArray(customFields.toTypedArray())
parcel.writeParcelable(otpElement, flags)
}
fun containsCustomFieldsProtected(): Boolean {
@@ -49,6 +53,15 @@ class EntryInfo : Parcelable {
return customFields.any { !it.protectedValue.isProtected }
}
fun isAutoGeneratedField(field: Field): Boolean {
return field.name == OTP_TOKEN_FIELD
}
fun doForAutoGeneratedField(field: Field, action: (valueGenerated: String) -> Unit) {
if (field.name == OTP_TOKEN_FIELD)
action.invoke(otpElement.token)
}
companion object {
@JvmField

View File

@@ -9,7 +9,7 @@ class Field : Parcelable {
var name: String = ""
var protectedValue: ProtectedString = ProtectedString()
constructor(name: String, value: ProtectedString) {
constructor(name: String, value: ProtectedString = ProtectedString()) {
this.name = name
this.protectedValue = value
}
@@ -28,6 +28,21 @@ class Field : Parcelable {
dest.writeParcelable(protectedValue, flags)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Field
if (name != other.name) return false
return true
}
override fun hashCode(): Int {
return name.hashCode()
}
companion object {
@JvmField

View File

@@ -0,0 +1,133 @@
package com.kunzisoft.keepass.otp
import android.os.Parcel
import android.os.Parcelable
import com.kunzisoft.keepass.otp.TokenCalculator.DEFAULT_ALGORITHM
data class OtpElement(var type: OtpType = OtpType.UNDEFINED, // ie : HOTP or TOTP
var tokenType: TokenType = TokenType.Default,
var name: String = "", // ie : user@email.com
var issuer: String = "", // ie : Gitlab
var secret: ByteArray = ByteArray(0), // Seed
var counter: Int = TokenCalculator.HOTP_INITIAL_COUNTER, // ie : 5 - only for HOTP
var step: Int = TokenCalculator.TOTP_DEFAULT_PERIOD, // ie : 30 seconds - only for TOTP
var digits: Int = TokenType.Default.tokenDigits,
var algorithm: TokenCalculator.HashAlgorithm = DEFAULT_ALGORITHM
) : Parcelable {
constructor(parcel: Parcel) : this(
OtpType.values()[parcel.readInt()],
TokenType.values()[parcel.readInt()],
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.createByteArray() ?: ByteArray(0),
parcel.readInt(),
parcel.readInt(),
parcel.readInt(),
TokenCalculator.HashAlgorithm.values()[parcel.readInt()])
fun setSettings(seed: String, digits: Int, step: Int) {
// TODO: Implement a way to set TOTP from device
}
val token: String
get() {
return when (type) {
OtpType.HOTP -> TokenCalculator.HOTP(secret, counter.toLong(), digits, algorithm)
OtpType.TOTP -> when (tokenType) {
TokenType.Steam -> TokenCalculator.TOTP_Steam(secret, this.step, digits, algorithm)
TokenType.Default -> TokenCalculator.TOTP_RFC6238(secret, this.step, digits, algorithm)
}
OtpType.UNDEFINED -> ""
}
}
val secondsRemaining: Int
get() = step - (System.currentTimeMillis() / 1000 % step).toInt()
fun shouldRefreshToken(): Boolean {
return secondsRemaining == this.step
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as OtpElement
if (type != other.type) return false
// Other values only for defined element
if (type != OtpType.UNDEFINED) {
// Token type is important only if it's a TOTP
if (type == OtpType.TOTP && tokenType != other.tokenType) return false
if (!secret.contentEquals(other.secret)) return false
// Counter only for HOTP
if (type == OtpType.HOTP && counter != other.counter) return false
// Step only for TOTP
if (type == OtpType.TOTP && step != other.step) return false
if (digits != other.digits) return false
if (algorithm != other.algorithm) return false
}
return true
}
override fun describeContents(): Int {
return 0
}
override fun hashCode(): Int {
var result = type.hashCode()
result = 31 * result + tokenType.hashCode()
result = 31 * result + secret.contentHashCode()
result = 31 * result + counter
result = 31 * result + step
result = 31 * result + digits
result = 31 * result + algorithm.hashCode()
return result
}
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeInt(type.ordinal)
parcel.writeInt(tokenType.ordinal)
parcel.writeString(name)
parcel.writeString(issuer)
parcel.writeByteArray(secret)
parcel.writeInt(counter)
parcel.writeInt(step)
parcel.writeInt(digits)
parcel.writeInt(algorithm.ordinal)
}
companion object CREATOR : Parcelable.Creator<OtpElement> {
override fun createFromParcel(parcel: Parcel): OtpElement {
return OtpElement(parcel)
}
override fun newArray(size: Int): Array<OtpElement?> {
return arrayOfNulls(size)
}
}
}
enum class OtpType {
UNDEFINED,
HOTP, // counter based
TOTP // time based
}
enum class TokenType (var tokenDigits: Int) {
Default(TokenCalculator.TOTP_DEFAULT_DIGITS),
Steam(TokenCalculator.STEAM_DEFAULT_DIGITS);
companion object {
fun getFromString(tokenType: String?): TokenType {
if (tokenType == null)
return Default
return when (tokenType) {
"S", "steam" -> Steam
else -> Default
}
}
}
}

View File

@@ -22,7 +22,7 @@ package com.kunzisoft.keepass.otp
import android.net.Uri
import android.util.Log
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.model.Field
import com.kunzisoft.keepass.otp.TokenCalculator.*
import org.apache.commons.codec.DecoderException
import org.apache.commons.codec.binary.Base32
@@ -32,88 +32,51 @@ import java.nio.charset.Charset
import java.util.*
import java.util.regex.Pattern
class OtpEntryFields(private val entry: EntryVersioned) {
class OtpEntryFields(private val getField: (id: String) -> String?) {
var type = OtpType.UNDEFINED
private set // ie : HOTP or TOTP
var name = "" // ie : user@email.com
var issuer = "" // ie : Gitlab
var secret: ByteArray? = null
var otpElement: OtpElement = OtpElement()
private set
var counter = HOTP_INITIAL_COUNTER // ie : 5 - only for HOTP
private var type
get() = otpElement.type
set(value) {
field = if (value < 0) HOTP_INITIAL_COUNTER else value
otpElement.type = value
}
var step = TOTP_DEFAULT_PERIOD
private set(step) = if (step <= 0 || step > 60) {
field = TOTP_DEFAULT_PERIOD
} else {
field = step
} // ie : 30 seconds - only for TOTP
var digits = TokenType.Default.tokenDigits // ie : 6 - number of digits generated
private var tokenType
get() = otpElement.tokenType
set(value) {
field = if (value <= 0) TokenType.Default.tokenDigits else value
}
var otpAlgorithm: HashAlgorithm = DEFAULT_ALGORITHM
val token: String
get() {
return when (type) {
OtpType.HOTP -> HOTP(secret, counter.toLong(), digits, otpAlgorithm)
OtpType.TOTP -> when (tokenType) {
TokenType.Steam -> TOTP_Steam(secret, this.step, digits, otpAlgorithm)
TokenType.Default -> TOTP_RFC6238(secret, this.step, digits, otpAlgorithm)
}
OtpType.UNDEFINED -> ""
}
otpElement.tokenType = value
}
val secondsRemaining: Int
get() = this.step - (System.currentTimeMillis() / 1000 % this.step).toInt()
enum class OtpType {
UNDEFINED,
HOTP, // counter based
TOTP // time based
}
private var tokenType = TokenType.Default // ie : default or Steam
private enum class TokenType constructor(var tokenDigits: Int) {
Default(TOTP_DEFAULT_DIGITS),
Steam(STEAM_DEFAULT_DIGITS);
companion object {
fun getFromString(tokenType: String?): TokenType {
if (tokenType == null)
return Default
return when (tokenType) {
"S", "steam" -> Steam
else -> Default
}
}
private var name
get() = otpElement.name
set(value) {
otpElement.name = value
}
private var issuer
get() = otpElement.issuer
set(value) {
otpElement.issuer = value
}
private var secret
get() = otpElement.secret
set(value) {
otpElement.secret = value
}
private fun setUTF8Secret(secret: String) {
this.secret = secret.toByteArray(Charset.forName("UTF-8"))
}
init {
// OTP (HOTP/TOTP) from URL and field from KeePassXC
var parse = parseOtpUri()
// TOTP from key values (maybe plugin or old KeePassXC)
if (!parse)
parse = parseTotpKeyValues()
// TOTP from custom field
if (!parse)
parse = parseTOTPFromField()
// HOTP fields from KeePass 2
if (!parse)
parseHOTPFromField()
}
fun shouldRefreshToken(): Boolean {
return secondsRemaining == this.step
}
fun setSettings(seed: String, digits: Int, step: Int) {
// TODO: Implement a way to set TOTP from device
private fun setHexSecret(secret: String) {
try {
this.secret = Hex.decodeHex(secret)
} catch (e: DecoderException) {
e.printStackTrace()
}
}
private fun setBase32Secret(secret: String) {
@@ -124,6 +87,44 @@ class OtpEntryFields(private val entry: EntryVersioned) {
this.secret = Base64().decode(secret.toByteArray())
}
private var counter
get() = otpElement.counter
set(value) {
otpElement.counter = if (value < 0) HOTP_INITIAL_COUNTER else value
}
private var step
get() = otpElement.step
set(value) {
otpElement.step = if (value <= 0 || value > 60) TOTP_DEFAULT_PERIOD else value
}
private var digits
get() = otpElement.digits
set(value) {
otpElement.digits = if (value <= 0) TokenType.Default.tokenDigits else value
}
private var algorithm
get() = otpElement.algorithm
set(value) {
otpElement.algorithm = value
}
init {
// OTP (HOTP/TOTP) from URL and field from KeePassXC
var parse = parseOTPUri()
// TOTP from key values (maybe plugin or old KeePassXC)
if (!parse)
parse = parseTOTPKeyValues()
// TOTP from custom field
if (!parse)
parse = parseTOTPFromField()
// HOTP fields from KeePass 2
if (!parse)
parseHOTPFromField()
}
/**
* Parses a secret value from a URI. The format will be:
*
@@ -133,12 +134,12 @@ class OtpEntryFields(private val entry: EntryVersioned) {
*
* otpauth://hotp/user@example.com?secret=FFF...&counter=123
*/
private fun parseOtpUri(): Boolean {
private fun parseOTPUri(): Boolean {
val otpPlainText = getField(OTP_FIELD)
if (otpPlainText != null && !otpPlainText.isEmpty()) {
if (otpPlainText != null && otpPlainText.isNotEmpty()) {
val uri = Uri.parse(otpPlainText)
if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase()) {
if (uri.scheme == null || OTP_SCHEME != uri.scheme!!.toLowerCase(Locale.ENGLISH)) {
Log.e(TAG, "Invalid or missing scheme in uri")
return false
}
@@ -172,7 +173,7 @@ class OtpEntryFields(private val entry: EntryVersioned) {
val algorithmParam = uri.getQueryParameter(ALGORITHM_URL_PARAM)
if (algorithmParam != null && algorithmParam.isNotEmpty())
otpAlgorithm = HashAlgorithm.valueOf(algorithmParam.toUpperCase(Locale.ENGLISH))
algorithm = HashAlgorithm.valueOf(algorithmParam.toUpperCase(Locale.ENGLISH))
val issuerParam = uri.getQueryParameter(ISSUER_URL_PARAM)
if (issuerParam != null && issuerParam.isNotEmpty())
@@ -205,7 +206,7 @@ class OtpEntryFields(private val entry: EntryVersioned) {
return false
}
private fun parseTotpKeyValues(): Boolean {
private fun parseTOTPKeyValues(): Boolean {
val plainText = getField(OTP_FIELD)
if (plainText != null && plainText.isNotEmpty()) {
if (Pattern.matches(validKeyValueRegex, plainText)) {
@@ -251,24 +252,19 @@ class OtpEntryFields(private val entry: EntryVersioned) {
}
private fun parseHOTPFromField(): Boolean {
val secretField = getField(HMACOTP_SECRET_KEY)
val secretHexField = getField(HMACOTP_SECRET_HEX_KEY)
val secretBase32Field = getField(HMACOTP_SECRET_BASE32_KEY)
val secretBase64Field = getField(HMACOTP_SECRET_BASE64_KEY)
val secretField = getField(HMACOTP_SECRET_FIELD)
val secretHexField = getField(HMACOTP_SECRET_HEX_FIELD)
val secretBase32Field = getField(HMACOTP_SECRET_BASE32_FIELD)
val secretBase64Field = getField(HMACOTP_SECRET_BASE64_FIELD)
when {
secretField != null -> secret = secretField.toByteArray(Charset.forName("UTF-8"))
secretHexField != null -> try {
secret = Hex.decodeHex(secretHexField)
} catch (e: DecoderException) {
e.printStackTrace()
return false
}
secretField != null -> setUTF8Secret(secretField)
secretHexField != null -> setHexSecret(secretHexField)
secretBase32Field != null -> setBase32Secret(secretBase32Field)
secretBase64Field != null -> setBase64Secret(secretBase64Field)
else -> return false
}
val secretCounterField = getField(HMACOTP_SECRET_COUNTER_KEY)
val secretCounterField = getField(HMACOTP_SECRET_COUNTER_FIELD)
if (secretCounterField != null) {
counter = secretCounterField.toInt()
}
@@ -277,11 +273,6 @@ class OtpEntryFields(private val entry: EntryVersioned) {
return true
}
private fun getField(id: String): String? {
val field = entry.customFields[id]
return field?.toString()
}
companion object {
private val TAG = OtpEntryFields::class.java.name
@@ -307,16 +298,19 @@ class OtpEntryFields(private val entry: EntryVersioned) {
private const val STEP_KEY = "step"
// HmacOtp KeePass2 values (https://keepass.info/help/base/placeholders.html#hmacotp)
private const val HMACOTP_SECRET_KEY = "HmacOtp-Secret"
private const val HMACOTP_SECRET_HEX_KEY = "HmacOtp-Secret-Hex"
private const val HMACOTP_SECRET_BASE32_KEY = "HmacOtp-Secret-Base32"
private const val HMACOTP_SECRET_BASE64_KEY = "HmacOtp-Secret-Base64"
private const val HMACOTP_SECRET_COUNTER_KEY = "HmacOtp-Counter"
private const val HMACOTP_SECRET_FIELD = "HmacOtp-Secret"
private const val HMACOTP_SECRET_HEX_FIELD = "HmacOtp-Secret-Hex"
private const val HMACOTP_SECRET_BASE32_FIELD = "HmacOtp-Secret-Base32"
private const val HMACOTP_SECRET_BASE64_FIELD = "HmacOtp-Secret-Base64"
private const val HMACOTP_SECRET_COUNTER_FIELD = "HmacOtp-Counter"
// Custom fields (maybe from plugin)
private const val TOTP_SEED_FIELD = "TOTP Seed"
private const val TOTP_SETTING_FIELD = "TOTP Settings"
// Token field, use dynamically to generate OTP token
const val OTP_TOKEN_FIELD = "OTP Token"
// Logical breakdown of key=value regex. the final string is as follows:
// [^&=\s]+=[^&=\s]+(&[^&=\s]+=[^&=\s]+)*
private const val validKeyValue = "[^&=\\s]+"
@@ -343,5 +337,40 @@ class OtpEntryFields(private val entry: EntryVersioned) {
}
return output
}
/**
Build new generated fields in a new list from [fieldsToParse] in parameter,
Remove parameters fields use to generate auto fields
*/
fun generateAutoFields(fieldsToParse: MutableList<Field>): MutableList<Field> {
val newCustomFields: MutableList<Field> = ArrayList(fieldsToParse)
// Remove parameter fields
val otpField = Field(OTP_FIELD)
val totpSeedField = Field(TOTP_SEED_FIELD)
val totpSettingField = Field(TOTP_SETTING_FIELD)
val hmacOtpSecretField = Field(HMACOTP_SECRET_FIELD)
val hmacOtpSecretHewField = Field(HMACOTP_SECRET_HEX_FIELD)
val hmacOtpSecretBase32Field = Field(HMACOTP_SECRET_BASE32_FIELD)
val hmacOtpSecretBase64Field = Field(HMACOTP_SECRET_BASE64_FIELD)
val hmacOtpSecretCounterField = Field(HMACOTP_SECRET_COUNTER_FIELD)
newCustomFields.remove(otpField)
newCustomFields.remove(totpSeedField)
newCustomFields.remove(totpSettingField)
newCustomFields.remove(hmacOtpSecretField)
newCustomFields.remove(hmacOtpSecretHewField)
newCustomFields.remove(hmacOtpSecretBase32Field)
newCustomFields.remove(hmacOtpSecretBase64Field)
newCustomFields.remove(hmacOtpSecretCounterField)
// Empty auto generated OTP Token field
if (fieldsToParse.contains(otpField)
|| fieldsToParse.contains(totpSeedField)
|| fieldsToParse.contains(hmacOtpSecretField)
|| fieldsToParse.contains(hmacOtpSecretHewField)
|| fieldsToParse.contains(hmacOtpSecretBase32Field)
|| fieldsToParse.contains(hmacOtpSecretBase64Field)
)
newCustomFields.add(Field(OTP_TOKEN_FIELD))
return newCustomFields
}
}
}

View File

@@ -37,7 +37,8 @@ import com.kunzisoft.keepass.adapters.EntryHistoryAdapter
import com.kunzisoft.keepass.database.element.EntryVersioned
import com.kunzisoft.keepass.database.element.PwDate
import com.kunzisoft.keepass.database.element.security.ProtectedString
import com.kunzisoft.keepass.otp.OtpEntryFields
import com.kunzisoft.keepass.otp.OtpElement
import com.kunzisoft.keepass.otp.OtpType
import java.util.*
class EntryContentsView @JvmOverloads constructor(context: Context,
@@ -211,38 +212,39 @@ class EntryContentsView @JvmOverloads constructor(context: Context,
}
}
fun assignOtp(otpEntryFields: OtpEntryFields,
fun assignOtp(otpElement: OtpElement,
otpProgressView: ProgressBar?,
onClickListener: OnClickListener) {
if (otpEntryFields.type != OtpEntryFields.OtpType.UNDEFINED) {
if (otpElement.type != OtpType.UNDEFINED) {
otpContainerView.visibility = View.VISIBLE
if (otpEntryFields.token.isEmpty()) {
if (otpElement.token.isEmpty()) {
otpView.text = context.getString(R.string.error_invalid_OTP)
otpActionView.setColorFilter(ContextCompat.getColor(context, R.color.grey_dark))
assignOtpCopyListener(null)
} else {
assignOtpCopyListener(onClickListener)
otpView.text = otpEntryFields.token
otpLabelView.text = otpEntryFields.type.name
otpView.text = otpElement.token
otpLabelView.text = otpElement.type.name
when (otpEntryFields.type) {
when (otpElement.type) {
// Only add token if HOTP
OtpEntryFields.OtpType.HOTP -> {
OtpType.HOTP -> {
}
// Refresh view if TOTP
OtpEntryFields.OtpType.TOTP -> {
OtpType.TOTP -> {
otpProgressView?.apply {
max = otpEntryFields.step
progress = otpEntryFields.secondsRemaining
max = otpElement.step
progress = otpElement.secondsRemaining
visibility = View.VISIBLE
}
otpContainerView.post(object : Runnable {
override fun run() {
if (otpEntryFields.shouldRefreshToken()) {
otpView.text = otpEntryFields.token
if (otpElement.shouldRefreshToken()) {
otpView.text = otpElement.token
}
otpProgressView?.progress = otpEntryFields.secondsRemaining
otpProgressView?.progress = otpElement.secondsRemaining
otpContainerView.postDelayed(this, 1000)
}
})