make pwa token validation more stable
This commit is contained in:
+1
@@ -14,6 +14,7 @@ object PwaConstants {
|
|||||||
const val EXTRA_PWA_PROFILE_UUID = "pwa_profile_uuid"
|
const val EXTRA_PWA_PROFILE_UUID = "pwa_profile_uuid"
|
||||||
const val EXTRA_PWA_CONTEXT_ID = "pwa_context_id"
|
const val EXTRA_PWA_CONTEXT_ID = "pwa_context_id"
|
||||||
const val EXTRA_PWA_TOKEN = "pwa_token"
|
const val EXTRA_PWA_TOKEN = "pwa_token"
|
||||||
|
const val EXTRA_PWA_INSTALL_START_URL = "pwa_install_start_url"
|
||||||
|
|
||||||
// Profile and file paths
|
// Profile and file paths
|
||||||
const val CURRENT_PROFILE_FILE = "weblibre_profiles/current_profile"
|
const val CURRENT_PROFILE_FILE = "weblibre_profiles/current_profile"
|
||||||
|
|||||||
+48
-6
@@ -10,6 +10,8 @@ import android.app.Activity
|
|||||||
import android.app.AlertDialog
|
import android.app.AlertDialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.content.pm.ShortcutManager
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import eu.weblibre.flutter_mozilla_components.Components
|
import eu.weblibre.flutter_mozilla_components.Components
|
||||||
@@ -147,7 +149,8 @@ class IntentReceiverActivity : Activity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isTrustedPwaLaunch(intent: Intent, profileUuid: String, token: String?): Boolean {
|
private fun isTrustedPwaLaunch(intent: Intent, profileUuid: String, token: String?): Boolean {
|
||||||
val url = intent.dataString ?: return false
|
val intentUrl = intent.dataString ?: return false
|
||||||
|
val installStartUrl = intent.getStringExtra(PwaConstants.EXTRA_PWA_INSTALL_START_URL)
|
||||||
val action = intent.action
|
val action = intent.action
|
||||||
val hasTrustedAction = action == Intent.ACTION_VIEW || action == "mozilla.components.feature.pwa.VIEW_PWA"
|
val hasTrustedAction = action == Intent.ACTION_VIEW || action == "mozilla.components.feature.pwa.VIEW_PWA"
|
||||||
if (!hasTrustedAction || token.isNullOrEmpty()) {
|
if (!hasTrustedAction || token.isNullOrEmpty()) {
|
||||||
@@ -158,14 +161,53 @@ class IntentReceiverActivity : Activity() {
|
|||||||
PwaConstants.PROFILE_MAPPING_PREFS,
|
PwaConstants.PROFILE_MAPPING_PREFS,
|
||||||
Context.MODE_PRIVATE,
|
Context.MODE_PRIVATE,
|
||||||
)
|
)
|
||||||
val tokenKey = "${PwaConstants.PROFILE_MAPPING_TOKEN_PREFIX}${url}::${profileUuid}"
|
val tokenKey = "${PwaConstants.PROFILE_MAPPING_TOKEN_PREFIX}${intentUrl}::${profileUuid}"
|
||||||
val storedToken = prefs.getString(tokenKey, null)
|
if (prefs.getString(tokenKey, null) == token) {
|
||||||
if (storedToken == null || storedToken != token) {
|
return true
|
||||||
Log.w(TAG, "PWA token mismatch for $url")
|
}
|
||||||
|
|
||||||
|
if (!installStartUrl.isNullOrEmpty() && installStartUrl != intentUrl) {
|
||||||
|
val installTokenKey = "${PwaConstants.PROFILE_MAPPING_TOKEN_PREFIX}${installStartUrl}::${profileUuid}"
|
||||||
|
if (prefs.getString(installTokenKey, null) == token) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isPinnedShortcutTokenMatch(intentUrl, profileUuid, token, installStartUrl)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
Log.w(TAG, "PWA token mismatch for $intentUrl")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPinnedShortcutTokenMatch(
|
||||||
|
intentUrl: String,
|
||||||
|
profileUuid: String,
|
||||||
|
token: String,
|
||||||
|
installStartUrl: String?,
|
||||||
|
): Boolean {
|
||||||
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
val shortcutManager = getSystemService(ShortcutManager::class.java) ?: return false
|
||||||
|
return shortcutManager.pinnedShortcuts.any { shortcut ->
|
||||||
|
val shortcutIntent = shortcut.intent ?: return@any false
|
||||||
|
if (shortcutIntent.getStringExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID) != profileUuid) {
|
||||||
|
return@any false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (shortcutIntent.getStringExtra(PwaConstants.EXTRA_PWA_TOKEN) != token) {
|
||||||
|
return@any false
|
||||||
|
}
|
||||||
|
|
||||||
|
val shortcutUrl = shortcutIntent.dataString
|
||||||
|
val shortcutInstallUrl = shortcutIntent.getStringExtra(PwaConstants.EXTRA_PWA_INSTALL_START_URL)
|
||||||
|
shortcutUrl == intentUrl ||
|
||||||
|
(shortcutInstallUrl != null && shortcutInstallUrl == intentUrl) ||
|
||||||
|
(!installStartUrl.isNullOrEmpty() && shortcutInstallUrl == installStartUrl && (shortcutUrl == intentUrl || shortcutUrl == installStartUrl))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resolveSessionIdFromStore(
|
private fun resolveSessionIdFromStore(
|
||||||
|
|||||||
+53
-8
@@ -145,7 +145,12 @@ class GeckoPwaApiImpl(
|
|||||||
val (iconBitmap, isMaskable) = loadPwaIcon(manifest)
|
val (iconBitmap, isMaskable) = loadPwaIcon(manifest)
|
||||||
|
|
||||||
val shortcutId = generateShortcutId(manifest.startUrl)
|
val shortcutId = generateShortcutId(manifest.startUrl)
|
||||||
val launchToken = generateAndStoreLaunchToken(manifest.startUrl, profileUuid)
|
val launchToken = resolveLaunchToken(
|
||||||
|
shortcutManager = shortcutManager,
|
||||||
|
shortcutId = shortcutId,
|
||||||
|
startUrl = manifest.startUrl,
|
||||||
|
profileUuid = profileUuid,
|
||||||
|
)
|
||||||
|
|
||||||
val appName = manifest.shortName ?: manifest.name ?: "Web App"
|
val appName = manifest.shortName ?: manifest.name ?: "Web App"
|
||||||
|
|
||||||
@@ -155,6 +160,7 @@ class GeckoPwaApiImpl(
|
|||||||
putExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID, profileUuid)
|
putExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID, profileUuid)
|
||||||
putExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID, contextId)
|
putExtra(PwaConstants.EXTRA_PWA_CONTEXT_ID, contextId)
|
||||||
putExtra(PwaConstants.EXTRA_PWA_TOKEN, launchToken)
|
putExtra(PwaConstants.EXTRA_PWA_TOKEN, launchToken)
|
||||||
|
putExtra(PwaConstants.EXTRA_PWA_INSTALL_START_URL, manifest.startUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
val shortcut = ShortcutInfo.Builder(context, shortcutId).apply {
|
val shortcut = ShortcutInfo.Builder(context, shortcutId).apply {
|
||||||
@@ -261,16 +267,55 @@ class GeckoPwaApiImpl(
|
|||||||
.apply()
|
.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun generateAndStoreLaunchToken(startUrl: String, profileUuid: String): String {
|
private fun resolveLaunchToken(
|
||||||
val token = UUID.randomUUID().toString()
|
shortcutManager: ShortcutManager,
|
||||||
val tokenKey = "${PwaConstants.PROFILE_MAPPING_TOKEN_PREFIX}${startUrl}::${profileUuid}"
|
shortcutId: String,
|
||||||
val committed = appPrefs.edit()
|
startUrl: String,
|
||||||
.putString(tokenKey, token)
|
profileUuid: String,
|
||||||
.commit()
|
): String {
|
||||||
|
val storedToken = getStoredLaunchToken(startUrl, profileUuid)
|
||||||
|
val existingShortcutToken = shortcutManager.pinnedShortcuts
|
||||||
|
.firstOrNull { shortcut -> shortcut.id == shortcutId }
|
||||||
|
?.intent
|
||||||
|
?.takeIf { shortcutIntent ->
|
||||||
|
shortcutIntent.getStringExtra(PwaConstants.EXTRA_PWA_PROFILE_UUID) == profileUuid
|
||||||
|
}
|
||||||
|
?.getStringExtra(PwaConstants.EXTRA_PWA_TOKEN)
|
||||||
|
|
||||||
|
if (!existingShortcutToken.isNullOrEmpty()) {
|
||||||
|
val committed = storeLaunchToken(startUrl, profileUuid, existingShortcutToken)
|
||||||
|
if (!committed) {
|
||||||
|
logger.warn("Failed to persist pinned shortcut PWA token for $startUrl")
|
||||||
|
}
|
||||||
|
return existingShortcutToken
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!storedToken.isNullOrEmpty()) {
|
||||||
|
val committed = storeLaunchToken(startUrl, profileUuid, storedToken)
|
||||||
|
if (!committed) {
|
||||||
|
logger.warn("Failed to refresh stored PWA launch token index for $startUrl")
|
||||||
|
}
|
||||||
|
return storedToken
|
||||||
|
}
|
||||||
|
|
||||||
|
val generatedToken = UUID.randomUUID().toString()
|
||||||
|
val committed = storeLaunchToken(startUrl, profileUuid, generatedToken)
|
||||||
if (!committed) {
|
if (!committed) {
|
||||||
logger.warn("Failed to persist PWA launch token for $startUrl")
|
logger.warn("Failed to persist PWA launch token for $startUrl")
|
||||||
}
|
}
|
||||||
return token
|
return generatedToken
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStoredLaunchToken(startUrl: String, profileUuid: String): String? {
|
||||||
|
val tokenKey = "${PwaConstants.PROFILE_MAPPING_TOKEN_PREFIX}${startUrl}::${profileUuid}"
|
||||||
|
return appPrefs.getString(tokenKey, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun storeLaunchToken(startUrl: String, profileUuid: String, token: String): Boolean {
|
||||||
|
val tokenKey = "${PwaConstants.PROFILE_MAPPING_TOKEN_PREFIX}${startUrl}::${profileUuid}"
|
||||||
|
return appPrefs.edit()
|
||||||
|
.putString(tokenKey, token)
|
||||||
|
.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getProfileMapping(startUrl: String): String? {
|
private fun getProfileMapping(startUrl: String): String? {
|
||||||
|
|||||||
Reference in New Issue
Block a user