mirror of
https://github.com/Kunzisoft/KeePassDX.git
synced 2025-12-04 15:49:33 +01:00
Manage OTP links #556
This commit is contained in:
@@ -150,6 +150,13 @@
|
|||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
<data android:mimeType="text/plain" />
|
<data android:mimeType="text/plain" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.VIEW" />
|
||||||
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
|
<category android:name="android.intent.category.BROWSABLE" />
|
||||||
|
<data android:scheme="otpauth" android:host="totp" />
|
||||||
|
<data android:scheme="otpauth" android:host="hotp" />
|
||||||
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity
|
<activity
|
||||||
android:name="com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity"
|
android:name="com.kunzisoft.keepass.activities.MagikeyboardLauncherActivity"
|
||||||
|
|||||||
@@ -23,15 +23,17 @@ import android.app.Activity
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.kunzisoft.keepass.R
|
||||||
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
import com.kunzisoft.keepass.activities.helpers.EntrySelectionHelper
|
||||||
import com.kunzisoft.keepass.database.element.Database
|
import com.kunzisoft.keepass.database.element.Database
|
||||||
import com.kunzisoft.keepass.database.search.SearchHelper
|
import com.kunzisoft.keepass.database.search.SearchHelper
|
||||||
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
import com.kunzisoft.keepass.magikeyboard.MagikIME
|
||||||
import com.kunzisoft.keepass.model.EntryInfo
|
import com.kunzisoft.keepass.model.EntryInfo
|
||||||
import com.kunzisoft.keepass.model.SearchInfo
|
import com.kunzisoft.keepass.model.SearchInfo
|
||||||
|
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.UriUtil
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Activity to search or select entry in database,
|
* Activity to search or select entry in database,
|
||||||
@@ -42,22 +44,32 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
|
|||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
||||||
var sharedWebDomain: String? = null
|
var sharedWebDomain: String? = null
|
||||||
|
var otpString: String? = null
|
||||||
|
|
||||||
when (intent?.action) {
|
when (intent?.action) {
|
||||||
Intent.ACTION_SEND -> {
|
Intent.ACTION_SEND -> {
|
||||||
if ("text/plain" == intent.type) {
|
if ("text/plain" == intent.type) {
|
||||||
// Retrieve web domain
|
// Retrieve web domain or OTP
|
||||||
intent.getStringExtra(Intent.EXTRA_TEXT)?.let {
|
intent.getStringExtra(Intent.EXTRA_TEXT)?.let { extra ->
|
||||||
sharedWebDomain = Uri.parse(it).host
|
sharedWebDomain = Uri.parse(extra).host
|
||||||
|
otpString = checkOtpString(extra)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Intent.ACTION_VIEW -> {
|
||||||
|
// Retrieve OTP
|
||||||
|
intent.dataString?.let { extra ->
|
||||||
|
otpString = checkOtpString(extra)
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build search param
|
|
||||||
|
// Build domain search param
|
||||||
val searchInfo = SearchInfo().apply {
|
val searchInfo = SearchInfo().apply {
|
||||||
webDomain = sharedWebDomain
|
this.webDomain = sharedWebDomain
|
||||||
|
this.otpString = otpString
|
||||||
}
|
}
|
||||||
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
|
SearchInfo.getConcreteWebDomain(this, searchInfo.webDomain) { concreteWebDomain ->
|
||||||
searchInfo.webDomain = concreteWebDomain
|
searchInfo.webDomain = concreteWebDomain
|
||||||
@@ -67,63 +79,102 @@ class EntrySelectionLauncherActivity : AppCompatActivity() {
|
|||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun launch(searchInfo: SearchInfo) {
|
private fun checkOtpString(otpString: String?): String? {
|
||||||
// Setting to integrate Magikeyboard
|
return if (OtpEntryFields.parseFields { otpString } == null) null else otpString
|
||||||
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
|
}
|
||||||
|
|
||||||
// If database is open
|
private fun launch(searchInfo: SearchInfo) {
|
||||||
val database = Database.getInstance()
|
|
||||||
val readOnly = database.isReadOnly
|
if (!searchInfo.containsOnlyNullValues()) {
|
||||||
SearchHelper.checkAutoSearchInfo(this,
|
// Setting to integrate Magikeyboard
|
||||||
database,
|
val searchShareForMagikeyboard = PreferencesUtil.isKeyboardSearchShareEnable(this)
|
||||||
searchInfo,
|
|
||||||
{ items ->
|
// If database is open
|
||||||
// Items found
|
val database = Database.getInstance()
|
||||||
if (searchShareForMagikeyboard) {
|
val readOnly = database.isReadOnly
|
||||||
if (items.size == 1) {
|
SearchHelper.checkAutoSearchInfo(this,
|
||||||
// Automatically populate keyboard
|
database,
|
||||||
val entryPopulate = items[0]
|
searchInfo,
|
||||||
populateKeyboardAndMoveAppToBackground(this,
|
{ items ->
|
||||||
entryPopulate,
|
// Items found
|
||||||
intent)
|
if (searchInfo.otpString != null) {
|
||||||
|
if (!readOnly) {
|
||||||
|
GroupActivity.launchForSaveResult(this,
|
||||||
|
searchInfo,
|
||||||
|
false)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(applicationContext,
|
||||||
|
R.string.autofill_read_only_save,
|
||||||
|
Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else if (searchShareForMagikeyboard) {
|
||||||
|
if (items.size == 1) {
|
||||||
|
// Automatically populate keyboard
|
||||||
|
val entryPopulate = items[0]
|
||||||
|
populateKeyboardAndMoveAppToBackground(this,
|
||||||
|
entryPopulate,
|
||||||
|
intent)
|
||||||
|
} else {
|
||||||
|
// Select the one we want
|
||||||
|
GroupActivity.launchForKeyboardSelectionResult(this,
|
||||||
|
readOnly,
|
||||||
|
searchInfo,
|
||||||
|
true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
// Select the one we want
|
GroupActivity.launchForSearchResult(this,
|
||||||
GroupActivity.launchForKeyboardSelectionResult(this,
|
|
||||||
readOnly,
|
readOnly,
|
||||||
searchInfo,
|
searchInfo,
|
||||||
true)
|
true)
|
||||||
}
|
}
|
||||||
} else {
|
},
|
||||||
GroupActivity.launchForSearchResult(this,
|
{
|
||||||
readOnly,
|
// Show the database UI to select the entry
|
||||||
searchInfo,
|
if (searchInfo.otpString != null) {
|
||||||
true)
|
if (!readOnly) {
|
||||||
|
GroupActivity.launchForSaveResult(this,
|
||||||
|
searchInfo,
|
||||||
|
false)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(applicationContext,
|
||||||
|
R.string.autofill_read_only_save,
|
||||||
|
Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else if (readOnly || searchShareForMagikeyboard) {
|
||||||
|
GroupActivity.launchForKeyboardSelectionResult(this,
|
||||||
|
readOnly,
|
||||||
|
searchInfo,
|
||||||
|
false)
|
||||||
|
} else {
|
||||||
|
GroupActivity.launchForSaveResult(this,
|
||||||
|
searchInfo,
|
||||||
|
false)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// If database not open
|
||||||
|
if (searchInfo.otpString != null) {
|
||||||
|
if (!readOnly) {
|
||||||
|
FileDatabaseSelectActivity.launchForSaveResult(this,
|
||||||
|
searchInfo)
|
||||||
|
} else {
|
||||||
|
Toast.makeText(applicationContext,
|
||||||
|
R.string.autofill_read_only_save,
|
||||||
|
Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
} else if (searchShareForMagikeyboard) {
|
||||||
|
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this,
|
||||||
|
searchInfo)
|
||||||
|
} else {
|
||||||
|
FileDatabaseSelectActivity.launchForSearchResult(this,
|
||||||
|
searchInfo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
{
|
}
|
||||||
// Show the database UI to select the entry
|
|
||||||
if (readOnly || searchShareForMagikeyboard) {
|
|
||||||
GroupActivity.launchForKeyboardSelectionResult(this,
|
|
||||||
readOnly,
|
|
||||||
searchInfo,
|
|
||||||
false)
|
|
||||||
} else {
|
|
||||||
GroupActivity.launchForSaveResult(this,
|
|
||||||
searchInfo,
|
|
||||||
false)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// If database not open
|
|
||||||
if (searchShareForMagikeyboard) {
|
|
||||||
FileDatabaseSelectActivity.launchForKeyboardSelectionResult(this,
|
|
||||||
searchInfo)
|
|
||||||
} else {
|
|
||||||
FileDatabaseSelectActivity.launchForSearchResult(this,
|
|
||||||
searchInfo)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -468,6 +468,19 @@ class FileDatabaseSelectActivity : SpecialModeActivity(),
|
|||||||
searchInfo)
|
searchInfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* -------------------------
|
||||||
|
* Save Launch
|
||||||
|
* -------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
fun launchForSaveResult(context: Context,
|
||||||
|
searchInfo: SearchInfo) {
|
||||||
|
EntrySelectionHelper.startActivityForSaveModeResult(context,
|
||||||
|
Intent(context, FileDatabaseSelectActivity::class.java),
|
||||||
|
searchInfo)
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------
|
* -------------------------
|
||||||
* Keyboard Launch
|
* Keyboard Launch
|
||||||
|
|||||||
@@ -1370,8 +1370,20 @@ class GroupActivity : LockingActivity(),
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
{
|
{ searchInfo ->
|
||||||
// Nothing with Save Info, only pass by search first
|
// Save info used with OTP
|
||||||
|
if (!readOnly) {
|
||||||
|
GroupActivity.launchForSaveResult(activity,
|
||||||
|
searchInfo,
|
||||||
|
false)
|
||||||
|
onLaunchActivitySpecialMode()
|
||||||
|
} else {
|
||||||
|
Toast.makeText(activity.applicationContext,
|
||||||
|
R.string.autofill_read_only_save,
|
||||||
|
Toast.LENGTH_LONG)
|
||||||
|
.show()
|
||||||
|
onCancelSpecialMode()
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ searchInfo ->
|
{ searchInfo ->
|
||||||
SearchHelper.checkAutoSearchInfo(activity,
|
SearchHelper.checkAutoSearchInfo(activity,
|
||||||
|
|||||||
@@ -795,6 +795,25 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* -------------------------
|
||||||
|
* Save Launch
|
||||||
|
* -------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Throws(FileNotFoundException::class)
|
||||||
|
fun launchForSaveResult(activity: Activity,
|
||||||
|
databaseFile: Uri,
|
||||||
|
keyFile: Uri?,
|
||||||
|
searchInfo: SearchInfo) {
|
||||||
|
buildAndLaunchIntent(activity, databaseFile, keyFile) { intent ->
|
||||||
|
EntrySelectionHelper.startActivityForSaveModeResult(
|
||||||
|
activity,
|
||||||
|
intent,
|
||||||
|
searchInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* -------------------------
|
* -------------------------
|
||||||
* Keyboard Launch
|
* Keyboard Launch
|
||||||
@@ -877,8 +896,11 @@ open class PasswordActivity : SpecialModeActivity() {
|
|||||||
searchInfo)
|
searchInfo)
|
||||||
onLaunchActivitySpecialMode()
|
onLaunchActivitySpecialMode()
|
||||||
},
|
},
|
||||||
{ // Save Action
|
{ searchInfo -> // Save Action
|
||||||
// Not directly used, a search is performed before
|
PasswordActivity.launchForSaveResult(activity,
|
||||||
|
databaseUri, keyFile,
|
||||||
|
searchInfo)
|
||||||
|
onLaunchActivitySpecialMode()
|
||||||
},
|
},
|
||||||
{ searchInfo -> // Keyboard Selection Action
|
{ searchInfo -> // Keyboard Selection Action
|
||||||
PasswordActivity.launchForKeyboardResult(activity,
|
PasswordActivity.launchForKeyboardResult(activity,
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ import com.kunzisoft.keepass.database.element.icon.IconImage
|
|||||||
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
import com.kunzisoft.keepass.database.element.icon.IconImageStandard
|
||||||
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
import com.kunzisoft.keepass.database.element.security.ProtectedString
|
||||||
import com.kunzisoft.keepass.otp.OtpElement
|
import com.kunzisoft.keepass.otp.OtpElement
|
||||||
|
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||||
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
import com.kunzisoft.keepass.otp.OtpEntryFields.OTP_TOKEN_FIELD
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
@@ -125,7 +126,14 @@ class EntryInfo : Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun saveSearchInfo(database: Database?, searchInfo: SearchInfo) {
|
fun saveSearchInfo(database: Database?, searchInfo: SearchInfo) {
|
||||||
searchInfo.webDomain?.let { webDomain ->
|
searchInfo.otpString?.let { otpString ->
|
||||||
|
// Replace the OTP field
|
||||||
|
OtpEntryFields.parseFields { otpString }?.let { otpElement ->
|
||||||
|
(customFields as ArrayList<Field>).add(
|
||||||
|
OtpEntryFields.buildOtpField(otpElement, null, null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} ?: searchInfo.webDomain?.let { webDomain ->
|
||||||
// If unable to save web domain in custom field or URL not populated, save in URL
|
// If unable to save web domain in custom field or URL not populated, save in URL
|
||||||
val scheme = searchInfo.webScheme
|
val scheme = searchInfo.webScheme
|
||||||
val webScheme = if (scheme.isNullOrEmpty()) "http" else scheme
|
val webScheme = if (scheme.isNullOrEmpty()) "http" else scheme
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import android.content.Context
|
|||||||
import android.content.res.Resources
|
import android.content.res.Resources
|
||||||
import android.os.Parcel
|
import android.os.Parcel
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
import com.kunzisoft.keepass.otp.OtpEntryFields
|
||||||
import com.kunzisoft.keepass.settings.PreferencesUtil
|
import com.kunzisoft.keepass.settings.PreferencesUtil
|
||||||
import com.kunzisoft.keepass.utils.ObjectNameResource
|
import com.kunzisoft.keepass.utils.ObjectNameResource
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
@@ -35,6 +36,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
get() {
|
get() {
|
||||||
return if (webDomain == null) null else field
|
return if (webDomain == null) null else field
|
||||||
}
|
}
|
||||||
|
var otpString: String? = null
|
||||||
|
|
||||||
constructor()
|
constructor()
|
||||||
|
|
||||||
@@ -42,6 +44,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
applicationId = toCopy?.applicationId
|
applicationId = toCopy?.applicationId
|
||||||
webDomain = toCopy?.webDomain
|
webDomain = toCopy?.webDomain
|
||||||
webScheme = toCopy?.webScheme
|
webScheme = toCopy?.webScheme
|
||||||
|
otpString = toCopy?.otpString
|
||||||
}
|
}
|
||||||
|
|
||||||
private constructor(parcel: Parcel) {
|
private constructor(parcel: Parcel) {
|
||||||
@@ -51,6 +54,8 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
webDomain = if (readDomain.isNullOrEmpty()) null else readDomain
|
webDomain = if (readDomain.isNullOrEmpty()) null else readDomain
|
||||||
val readScheme = parcel.readString()
|
val readScheme = parcel.readString()
|
||||||
webScheme = if (readScheme.isNullOrEmpty()) null else readScheme
|
webScheme = if (readScheme.isNullOrEmpty()) null else readScheme
|
||||||
|
val readOtp = parcel.readString()
|
||||||
|
otpString = if (readOtp.isNullOrEmpty()) null else readOtp
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun describeContents(): Int {
|
override fun describeContents(): Int {
|
||||||
@@ -61,14 +66,23 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
parcel.writeString(applicationId ?: "")
|
parcel.writeString(applicationId ?: "")
|
||||||
parcel.writeString(webDomain ?: "")
|
parcel.writeString(webDomain ?: "")
|
||||||
parcel.writeString(webScheme ?: "")
|
parcel.writeString(webScheme ?: "")
|
||||||
|
parcel.writeString(otpString ?: "")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getName(resources: Resources): String {
|
override fun getName(resources: Resources): String {
|
||||||
|
if (otpString != null) {
|
||||||
|
OtpEntryFields.parseFields { otpString }?.let { otpElement ->
|
||||||
|
return "${otpElement.type} (${otpElement.name})"
|
||||||
|
}
|
||||||
|
}
|
||||||
return toString()
|
return toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun containsOnlyNullValues(): Boolean {
|
fun containsOnlyNullValues(): Boolean {
|
||||||
return applicationId == null && webDomain == null && webScheme == null
|
return applicationId == null
|
||||||
|
&& webDomain == null
|
||||||
|
&& webScheme == null
|
||||||
|
&& otpString == null
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun equals(other: Any?): Boolean {
|
override fun equals(other: Any?): Boolean {
|
||||||
@@ -80,6 +94,7 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
if (applicationId != other.applicationId) return false
|
if (applicationId != other.applicationId) return false
|
||||||
if (webDomain != other.webDomain) return false
|
if (webDomain != other.webDomain) return false
|
||||||
if (webScheme != other.webScheme) return false
|
if (webScheme != other.webScheme) return false
|
||||||
|
if (otpString != other.otpString) return false
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
@@ -88,11 +103,12 @@ class SearchInfo : ObjectNameResource, Parcelable {
|
|||||||
var result = applicationId?.hashCode() ?: 0
|
var result = applicationId?.hashCode() ?: 0
|
||||||
result = 31 * result + (webDomain?.hashCode() ?: 0)
|
result = 31 * result + (webDomain?.hashCode() ?: 0)
|
||||||
result = 31 * result + (webScheme?.hashCode() ?: 0)
|
result = 31 * result + (webScheme?.hashCode() ?: 0)
|
||||||
|
result = 31 * result + (otpString?.hashCode() ?: 0)
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return webDomain ?: applicationId ?: ""
|
return otpString ?: webDomain ?: applicationId ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|||||||
Reference in New Issue
Block a user