Re-add keepass2android plugin
This commit is contained in:
@@ -1,20 +1,45 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED" />
|
||||
|
||||
<!-- Keepass2Android plugin mode: make the KP2A activities resolvable from this app -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="keepass2android.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="keepass2android.ACTION_START_WITH_TASK" />
|
||||
</intent>
|
||||
<package android:name="keepass2android.keepass2android" />
|
||||
<package android:name="keepass2android.keepass2android_nonet" />
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:icon="@mipmap/ic_launcher_round"
|
||||
android:label="${APP_NAME}"
|
||||
android:supportsRtl="true">
|
||||
<activity
|
||||
android:name=".activity.MainActivity"
|
||||
android:exported="true">
|
||||
android:exported="true"
|
||||
android:launchMode="singleTask">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver
|
||||
android:name=".pluginSDK.PluginAccessBroadcastReceiver"
|
||||
android:exported="true"
|
||||
tools:ignore="ExportedReceiver">
|
||||
<intent-filter>
|
||||
<action android:name="keepass2android.ACTION_TRIGGER_REQUEST_ACCESS" />
|
||||
<action android:name="keepass2android.ACTION_RECEIVE_ACCESS" />
|
||||
<action android:name="keepass2android.ACTION_REVOKE_ACCESS" />
|
||||
</intent-filter>
|
||||
</receiver>
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
package net.helcel.fidelity.pluginSDK
|
||||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.core.content.edit
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
|
||||
|
||||
object AccessManager {
|
||||
private const val PREF_KEY_SCOPE = "scope"
|
||||
private const val PREF_KEY_TOKEN = "token"
|
||||
private const val PREF_HOSTS = "KP2A.PluginAccess.hosts"
|
||||
|
||||
private fun stringArrayToString(values: ArrayList<String?>): String? {
|
||||
if (values.isEmpty()) return null
|
||||
val a = JSONArray()
|
||||
values.forEach { a.put(it) }
|
||||
return a.toString()
|
||||
}
|
||||
|
||||
private fun stringToStringArray(s: String?): ArrayList<String> {
|
||||
val strings = ArrayList<String>()
|
||||
if (s.isNullOrEmpty()) return strings
|
||||
|
||||
try {
|
||||
val a = JSONArray(s)
|
||||
for (i in 0 until a.length())
|
||||
strings.add(a.optString(i))
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return strings
|
||||
}
|
||||
|
||||
fun storeAccessToken(
|
||||
ctx: Context,
|
||||
hostPackage: String?,
|
||||
accessToken: String?,
|
||||
scopes: ArrayList<String?>
|
||||
) {
|
||||
getPrefsForHost(ctx, hostPackage).edit {
|
||||
putString(PREF_KEY_TOKEN, accessToken)
|
||||
putString(PREF_KEY_SCOPE, stringArrayToString(scopes))
|
||||
}
|
||||
|
||||
val hostPrefs = ctx.getSharedPreferences(PREF_HOSTS, Context.MODE_PRIVATE)
|
||||
if (!hostPrefs.contains(hostPackage))
|
||||
hostPrefs.edit { putString(hostPackage, "") }
|
||||
}
|
||||
|
||||
private fun getPrefsForHost(
|
||||
ctx: Context,
|
||||
hostPackage: String?
|
||||
): SharedPreferences {
|
||||
return ctx.getSharedPreferences("KP2A.PluginAccess.$hostPackage", Context.MODE_PRIVATE)
|
||||
}
|
||||
|
||||
fun tryGetAccessToken(ctx: Context, hostPackage: String?, scopes: ArrayList<String?>): String? {
|
||||
if (hostPackage.isNullOrEmpty()) return null
|
||||
|
||||
val prefs = getPrefsForHost(ctx, hostPackage)
|
||||
val scopesString = prefs.getString(PREF_KEY_SCOPE, "")
|
||||
val currentScope = stringToStringArray(scopesString)
|
||||
if (!isSubset(scopes, currentScope))
|
||||
return null
|
||||
return prefs.getString(PREF_KEY_TOKEN, null)
|
||||
}
|
||||
|
||||
private fun isSubset(
|
||||
requiredScopes: ArrayList<String?>,
|
||||
availableScopes: ArrayList<String>
|
||||
): Boolean {
|
||||
return availableScopes.containsAll(requiredScopes)
|
||||
}
|
||||
|
||||
fun removeAccessToken(
|
||||
ctx: Context, hostPackage: String?,
|
||||
accessToken: String?
|
||||
) {
|
||||
val prefs = getPrefsForHost(ctx, hostPackage)
|
||||
if (prefs.getString(PREF_KEY_TOKEN, "") == accessToken)
|
||||
prefs.edit { clear() }
|
||||
|
||||
val hostPrefs = ctx.getSharedPreferences(PREF_HOSTS, Context.MODE_PRIVATE)
|
||||
if (hostPrefs.contains(hostPackage))
|
||||
hostPrefs.edit { remove(hostPackage) }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package net.helcel.fidelity.pluginSDK
|
||||
|
||||
@Suppress("unused")
|
||||
object KeepassDef {
|
||||
var TitleField: String = "Title"
|
||||
var UserNameField: String = "UserName"
|
||||
var PasswordField: String = "Password"
|
||||
var UrlField: String = "URL"
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package net.helcel.fidelity.pluginSDK
|
||||
|
||||
import android.content.Intent
|
||||
import org.json.JSONException
|
||||
import org.json.JSONObject
|
||||
|
||||
object Kp2aControl {
|
||||
|
||||
fun getAddEntryIntent(
|
||||
fields: HashMap<String, String>,
|
||||
protectedFields: ArrayList<String>?
|
||||
): Intent {
|
||||
val outputData = JSONObject((fields as Map<*, *>)).toString()
|
||||
val startKp2aIntent = Intent(Strings.ACTION_START_WITH_TASK)
|
||||
startKp2aIntent.addCategory(Intent.CATEGORY_DEFAULT)
|
||||
startKp2aIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
|
||||
startKp2aIntent.putExtra("KP2A_APPTASK", "CreateEntryThenCloseTask")
|
||||
startKp2aIntent.putExtra("ShowUserNotifications", "true")
|
||||
startKp2aIntent.putExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA, outputData)
|
||||
if (protectedFields != null)
|
||||
startKp2aIntent.putStringArrayListExtra(
|
||||
Strings.EXTRA_PROTECTED_FIELDS_LIST,
|
||||
protectedFields
|
||||
)
|
||||
return startKp2aIntent
|
||||
}
|
||||
|
||||
fun getQueryEntryForOwnPackageIntent(): Intent {
|
||||
return Intent(Strings.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE)
|
||||
}
|
||||
|
||||
fun getEntryFieldsFromIntent(intent: Intent?): HashMap<String, String> {
|
||||
val res = HashMap<String, String>()
|
||||
try {
|
||||
val json = JSONObject(intent?.getStringExtra(Strings.EXTRA_ENTRY_OUTPUT_DATA) ?: "")
|
||||
val itr = json.keys()
|
||||
while (itr.hasNext()) {
|
||||
val key = itr.next()
|
||||
val value = json[key].toString()
|
||||
res[key] = value
|
||||
}
|
||||
} catch (e: JSONException) {
|
||||
e.printStackTrace()
|
||||
} catch (e: NullPointerException) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return res
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package net.helcel.fidelity.pluginSDK
|
||||
|
||||
import android.content.BroadcastReceiver
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
class PluginAccessBroadcastReceiver : BroadcastReceiver() {
|
||||
override fun onReceive(ctx: Context, intent: Intent) {
|
||||
val action = intent.action ?: return
|
||||
when (action) {
|
||||
Strings.ACTION_TRIGGER_REQUEST_ACCESS -> requestAccess(ctx, intent)
|
||||
Strings.ACTION_RECEIVE_ACCESS -> receiveAccess(ctx, intent)
|
||||
Strings.ACTION_REVOKE_ACCESS -> revokeAccess(ctx, intent)
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun revokeAccess(ctx: Context, intent: Intent) {
|
||||
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
|
||||
val accessToken = intent.getStringExtra(Strings.EXTRA_ACCESS_TOKEN)
|
||||
AccessManager.removeAccessToken(ctx, senderPackage, accessToken)
|
||||
}
|
||||
|
||||
|
||||
private fun receiveAccess(ctx: Context, intent: Intent) {
|
||||
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
|
||||
val accessToken = intent.getStringExtra(Strings.EXTRA_ACCESS_TOKEN)
|
||||
AccessManager.storeAccessToken(ctx, senderPackage, accessToken, scopes)
|
||||
}
|
||||
|
||||
private fun requestAccess(ctx: Context, intent: Intent) {
|
||||
val senderPackage = intent.getStringExtra(Strings.EXTRA_SENDER)
|
||||
val requestToken = intent.getStringExtra(Strings.EXTRA_REQUEST_TOKEN)
|
||||
val rpi = Intent(Strings.ACTION_REQUEST_ACCESS)
|
||||
rpi.setPackage(senderPackage)
|
||||
rpi.putExtra(Strings.EXTRA_SENDER, ctx.packageName)
|
||||
rpi.putExtra(Strings.EXTRA_REQUEST_TOKEN, requestToken)
|
||||
|
||||
val token: String? = AccessManager.tryGetAccessToken(ctx, senderPackage, scopes)
|
||||
rpi.putExtra(Strings.EXTRA_ACCESS_TOKEN, token)
|
||||
|
||||
rpi.putStringArrayListExtra(Strings.EXTRA_SCOPES, scopes)
|
||||
ctx.sendBroadcast(rpi)
|
||||
}
|
||||
|
||||
private val scopes: ArrayList<String?> = ArrayList(
|
||||
listOf(
|
||||
Strings.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE,
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package net.helcel.fidelity.pluginSDK
|
||||
|
||||
@Suppress("unused")
|
||||
object Strings {
|
||||
|
||||
const val SCOPE_DATABASE_ACTIONS = "keepass2android.SCOPE_DATABASE_ACTIONS"
|
||||
const val SCOPE_CURRENT_ENTRY = "keepass2android.SCOPE_CURRENT_ENTRY"
|
||||
const val SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
|
||||
"keepass2android.SCOPE_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
|
||||
const val SCOPE_QUERY_CREDENTIALS = "keepass2android.SCOPE_QUERY_CREDENTIALS"
|
||||
|
||||
const val EXTRA_SCOPES = "keepass2android.EXTRA_SCOPES"
|
||||
const val EXTRA_PLUGIN_PACKAGE = "keepass2android.EXTRA_PLUGIN_PACKAGE"
|
||||
|
||||
const val EXTRA_SENDER = "keepass2android.EXTRA_SENDER"
|
||||
const val EXTRA_REQUEST_TOKEN = "keepass2android.EXTRA_REQUEST_TOKEN"
|
||||
const val ACTION_START_WITH_TASK = "keepass2android.ACTION_START_WITH_TASK"
|
||||
|
||||
const val ACTION_TRIGGER_REQUEST_ACCESS = "keepass2android.ACTION_TRIGGER_REQUEST_ACCESS"
|
||||
const val ACTION_REQUEST_ACCESS = "keepass2android.ACTION_REQUEST_ACCESS"
|
||||
const val ACTION_RECEIVE_ACCESS = "keepass2android.ACTION_RECEIVE_ACCESS"
|
||||
const val ACTION_REVOKE_ACCESS = "keepass2android.ACTION_REVOKE_ACCESS"
|
||||
|
||||
|
||||
const val EXTRA_ENTRY_OUTPUT_DATA = "keepass2android.EXTRA_ENTRY_OUTPUT_DATA"
|
||||
const val EXTRA_PROTECTED_FIELDS_LIST = "keepass2android.EXTRA_PROTECTED_FIELDS_LIST"
|
||||
const val EXTRA_ACCESS_TOKEN = "keepass2android.EXTRA_ACCESS_TOKEN"
|
||||
const val EXTRA_ENTRY_ID = "keepass2android.EXTRA_ENTRY_ID"
|
||||
|
||||
const val ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE =
|
||||
"keepass2android.ACTION_QUERY_CREDENTIALS_FOR_OWN_PACKAGE"
|
||||
}
|
||||
Reference in New Issue
Block a user