commit c9114e7b6177118ca0902466e62d62a16867766d parent 2429ecd45a81753661bc3a2cd663876c9bf7e248 Author: rhunk <101876869+rhunk@users.noreply.github.com> Date: Sat, 1 Jun 2024 18:13:46 +0200 refactor: config - add config to logs - sensitive data filter Diffstat:
14 files changed, 100 insertions(+), 44 deletions(-)
diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/LogManager.kt b/app/src/main/kotlin/me/rhunk/snapenhance/LogManager.kt @@ -191,7 +191,21 @@ class LogManager( } fun exportLogsToZip(outputStream: OutputStream) { - val zipOutputStream = ZipOutputStream(outputStream) + val zipOutputStream = ZipOutputStream(outputStream).apply { + setMethod(ZipOutputStream.DEFLATED) + } + + // add device info to zip + zipOutputStream.putNextEntry(ZipEntry("device_info.json")) + val gson = GsonBuilder().setPrettyPrinting().create() + zipOutputStream.write(gson.toJson(remoteSideContext.installationSummary).toByteArray()) + zipOutputStream.closeEntry() + + // add config + zipOutputStream.putNextEntry(ZipEntry("config.json")) + zipOutputStream.write(remoteSideContext.config.exportToString(exportSensitiveData = false).toByteArray()) + zipOutputStream.closeEntry() + //add logFolder to zip logFolder.walk().forEach { if (it.isFile) { @@ -201,12 +215,6 @@ class LogManager( } } - //add device info to zip - zipOutputStream.putNextEntry(ZipEntry("device_info.json")) - val gson = GsonBuilder().setPrettyPrinting().create() - zipOutputStream.write(gson.toJson(remoteSideContext.installationSummary).toByteArray()) - zipOutputStream.closeEntry() - zipOutputStream.close() } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/Routes.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/Routes.kt @@ -94,6 +94,12 @@ class Routes( routes.navController.navigate(replaceArguments(routeInfo.id, HashMap<String, String>().apply { args() })) } + fun navigateReload() { + routes.navController.navigate(routeInfo.id) { + popUpTo(routeInfo.id) { inclusive = true } + } + } + fun navigateReset(args: MutableMap<String, String>.() -> Unit = {}) { routes.navController.navigate(replaceArguments(routeInfo.id, HashMap<String, String>().apply { args() })) { popUpTo(routes.navController.graph.findStartDestination().id) { diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/features/FeaturesRoot.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/features/FeaturesRoot.kt @@ -529,6 +529,7 @@ class FeaturesRoot : Routes.Route() { var showExportDropdownMenu by remember { mutableStateOf(false) } var showResetConfirmationDialog by remember { mutableStateOf(false) } + var showExportDialog by remember { mutableStateOf(false) } if (showResetConfirmationDialog) { AlertDialog( @@ -558,19 +559,50 @@ class FeaturesRoot : Routes.Route() { ) } - val actions = remember { - mapOf( - translation["export_option"] to { - activityLauncher { - saveFile("config.json", "application/json") { uri -> + if (showExportDialog) { + fun exportConfig( + exportSensitiveData: Boolean + ) { + showExportDialog = false + activityLauncher { + saveFile("config.json", "application/json") { uri -> + runCatching { context.androidContext.contentResolver.openOutputStream(Uri.parse(uri))?.use { context.config.writeConfig() - context.config.exportToString().byteInputStream().copyTo(it) + context.config.exportToString(exportSensitiveData).byteInputStream().copyTo(it) context.shortToast(translation["config_export_success_toast"]) } + }.onFailure { + context.longToast(translation.format("config_export_failure_toast", "error" to it.message.toString())) } } + } + } + + AlertDialog( + title = { Text(text = context.translation["manager.dialogs.export_config.title"]) }, + text = { Text(text = context.translation["manager.dialogs.export_config.content"]) }, + onDismissRequest = { showExportDialog = false }, + confirmButton = { + Button( + onClick = { exportConfig(true) } + ) { + Text(text = context.translation["button.positive"]) + } }, + dismissButton = { + Button( + onClick = { exportConfig(false) } + ) { + Text(text = context.translation["button.negative"]) + } + } + ) + } + + val actions = remember { + mapOf( + translation["export_option"] to { showExportDialog = true }, translation["import_option"] to { activityLauncher { openFile("application/json") { uri -> @@ -582,6 +614,9 @@ class FeaturesRoot : Routes.Route() { return@use } context.shortToast(translation["config_import_success_toast"]) + context.coroutineScope.launch(Dispatchers.Main) { + navigateReload() + } } } } diff --git a/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeLogs.kt b/app/src/main/kotlin/me/rhunk/snapenhance/ui/manager/pages/home/HomeLogs.kt @@ -68,9 +68,7 @@ class HomeLogs : Routes.Route() { context.coroutineScope.launch { context.log.clearLogs() } - routes.navController.navigate(routeInfo.id) { - popUpTo(routeInfo.id) { inclusive = true } - } + navigateReload() showDropDown = false }, text = { Text(translation["clear_logs_button"]) diff --git a/common/src/main/assets/lang/en_US.json b/common/src/main/assets/lang/en_US.json @@ -82,6 +82,7 @@ "config_export_success_toast": "Config exported successfully", "config_import_success_toast": "Config imported successfully", "config_import_failure_toast": "Failed to import config {error}", + "config_export_failure_toast": "Failed to export config {error}", "saved_config_snackbar": "Config saved" }, "social": { @@ -159,6 +160,10 @@ "content": "Are you sure you want to reset the config?", "success_toast": "Config reset successfully" }, + "export_config": { + "title": "Export Sensitive Data?", + "content": "Do you want to export the config with sensitive data? (Such as location coordinates, etc.)" + }, "messaging_action": { "title": "Choose content types to process", "select_all_button": "Select All" diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/ConfigContainer.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/ConfigContainer.kt @@ -74,10 +74,11 @@ open class ConfigContainer( params: ConfigParamsBuilder = {} ) = registerProperty(key, DataProcessors.INT_COLOR, PropertyValue(defaultValue), params) - fun toJson(): JsonObject { + fun toJson(exportSensitiveData: Boolean = true): JsonObject { val json = JsonObject() properties.forEach { (propertyKey, propertyValue) -> - val serializedValue = propertyValue.getNullable()?.let { propertyKey.dataType.serializeAny(it) } + if (!exportSensitiveData && propertyKey.params.flags.contains(ConfigFlag.SENSITIVE)) return@forEach + val serializedValue = propertyValue.getNullable()?.let { propertyKey.dataType.serializeAny(it, exportSensitiveData) } json.add(propertyKey.name, serializedValue) } return json diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/ConfigObjects.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/ConfigObjects.kt @@ -29,7 +29,8 @@ enum class ConfigFlag { USER_IMPORT, NO_DISABLE_KEY, REQUIRE_RESTART, - REQUIRE_CLEAN_CACHE; + REQUIRE_CLEAN_CACHE, + SENSITIVE; val id = 1 shl ordinal } diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/DataProcessors.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/DataProcessors.kt @@ -22,17 +22,17 @@ object DataProcessors { data class PropertyDataProcessor<T> internal constructor( val type: Type, - private val serialize: (T) -> JsonElement, + private val serialize: (T, exportSensitiveData: Boolean) -> JsonElement, private val deserialize: (JsonElement) -> T ) { @Suppress("UNCHECKED_CAST") - fun serializeAny(value: Any) = serialize(value as T) + fun serializeAny(value: Any, exportSensitiveData: Boolean) = serialize(value as T, exportSensitiveData) fun deserializeAny(value: JsonElement) = deserialize(value) } val STRING = PropertyDataProcessor( type = Type.STRING, - serialize = { + serialize = { it, _ -> if (it != null) JsonPrimitive(it) else JsonNull.INSTANCE }, @@ -44,7 +44,7 @@ object DataProcessors { val BOOLEAN = PropertyDataProcessor( type = Type.BOOLEAN, - serialize = { + serialize = { it, _ -> if (it) JsonPrimitive(true) else JsonPrimitive(false) }, @@ -53,19 +53,19 @@ object DataProcessors { val INTEGER = PropertyDataProcessor( type = Type.INTEGER, - serialize = { JsonPrimitive(it) }, + serialize = { it, _ -> JsonPrimitive(it) }, deserialize = { it.asInt }, ) val FLOAT = PropertyDataProcessor( type = Type.FLOAT, - serialize = { JsonPrimitive(it) }, + serialize = { it, _ -> JsonPrimitive(it) }, deserialize = { it.asFloat }, ) val STRING_MULTIPLE_SELECTION = PropertyDataProcessor( type = Type.STRING_MULTIPLE_SELECTION, - serialize = { JsonArray().apply { it.forEach { add(it) } } }, + serialize = { it, _ -> JsonArray().apply { it.forEach { add(it) } } }, deserialize = { obj -> obj.asJsonArray.map { it.asString }.toMutableList() }, @@ -73,13 +73,13 @@ object DataProcessors { val STRING_UNIQUE_SELECTION = PropertyDataProcessor( type = Type.STRING_UNIQUE_SELECTION, - serialize = { JsonPrimitive(it) }, + serialize = { it, _ -> JsonPrimitive(it) }, deserialize = { obj -> obj.takeIf { !it.isJsonNull }?.asString?.takeIf { it != "false" && it != "true" } } ) val MAP_COORDINATES = PropertyDataProcessor( type = Type.MAP_COORDINATES, - serialize = { + serialize = { it, _ -> JsonObject().apply { addProperty("lat", it.first.takeIf { it in -90.0..90.0 } ?: 0.0) addProperty("lng", it.second.takeIf { it in -180.0..180.0 } ?: 0.0) @@ -94,7 +94,7 @@ object DataProcessors { val INT_COLOR = PropertyDataProcessor( type = Type.INT_COLOR, - serialize = { + serialize = { it, _ -> it?.let { JsonPrimitive(it) } ?: JsonNull.INSTANCE }, deserialize = { if (it.isJsonNull) null else it.asString.toIntOrNull() }, @@ -102,17 +102,17 @@ object DataProcessors { fun <T : ConfigContainer> container(container: T) = PropertyDataProcessor( type = Type.CONTAINER, - serialize = { + serialize = { it, exportSensitiveData -> JsonObject().apply { addProperty("state", it.globalState) - add("properties", it.toJson()) + add("properties", it.toJson(exportSensitiveData)) } }, deserialize = { obj -> val jsonObject = obj.asJsonObject container.apply { - globalState = jsonObject["state"].takeIf { !it.isJsonNull }?.asBoolean - fromJson(jsonObject["properties"].asJsonObject) + globalState = jsonObject["state"]?.takeIf { !it.isJsonNull }?.asBoolean + jsonObject["properties"]?.asJsonObject?.let { fromJson(it) } } }, ) diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/ModConfig.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/ModConfig.kt @@ -54,10 +54,12 @@ class ModConfig( root.fromJson(configObject) } - fun exportToString(): String { - val configObject = root.toJson() + fun exportToString( + exportSensitiveData: Boolean = true + ): String { + val configObject = root.toJson(exportSensitiveData) configObject.addProperty("_locale", locale) - return configObject.toString() + return gson.toJson(configObject) } fun reset() { diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/DownloaderConfig.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/DownloaderConfig.kt @@ -17,7 +17,7 @@ class DownloaderConfig : ConfigContainer() { val customAudioCodec = string("custom_audio_codec") { addFlags(ConfigFlag.NO_TRANSLATE) } } - val saveFolder = string("save_folder") { addFlags(ConfigFlag.FOLDER); requireRestart() } + val saveFolder = string("save_folder") { addFlags(ConfigFlag.FOLDER, ConfigFlag.SENSITIVE); requireRestart() } val autoDownloadSources = multiple("auto_download_sources", "friend_snaps", "friend_stories", diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Global.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Global.kt @@ -19,9 +19,9 @@ class Global : ConfigContainer() { ) } - inner class BetterLocation : ConfigContainer(hasGlobalState = true) { + inner class BetterLocationConfig : ConfigContainer(hasGlobalState = true) { val spoofLocation = boolean("spoof_location") { requireRestart() } - val coordinates = mapCoordinates("coordinates", 0.0 to 0.0) // lat, long + val coordinates = mapCoordinates("coordinates", 0.0 to 0.0) { addFlags(ConfigFlag.SENSITIVE) } // lat, long val walkRadius = string("walk_radius") { requireRestart(); inputCheck = { it.toDoubleOrNull()?.isFinite() == true && it.toDouble() >= 0.0 } } val alwaysUpdateLocation = boolean("always_update_location") { requireRestart() } val suspendLocationUpdates = boolean("suspend_location_updates") { requireRestart() } @@ -35,7 +35,7 @@ class Global : ConfigContainer() { val customUploadImageFormat = unique("custom_image_upload_format", "jpeg", "png", "webp") { requireRestart(); addFlags(ConfigFlag.NO_TRANSLATE) } } - val betterLocation = container("better_location", BetterLocation()) + val betterLocation = container("better_location", BetterLocationConfig()) val snapchatPlus = boolean("snapchat_plus") { requireRestart() } val mediaUploadQualityConfig = container("media_upload_quality", MediaUploadQualityConfig()) val disableConfirmationDialogs = multiple("disable_confirmation_dialogs", "erase_message", "remove_friend", "block_friend", "ignore_friend", "hide_friend", "hide_conversation", "clear_conversation") { requireRestart() } diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Scripting.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Scripting.kt @@ -5,7 +5,7 @@ import me.rhunk.snapenhance.common.config.ConfigFlag class Scripting : ConfigContainer() { val developerMode = boolean("developer_mode", false) { requireRestart() } - val moduleFolder = string("module_folder", "modules") { addFlags(ConfigFlag.FOLDER); requireRestart() } + val moduleFolder = string("module_folder", "modules") { addFlags(ConfigFlag.FOLDER, ConfigFlag.SENSITIVE); requireRestart() } val autoReload = unique("auto_reload", "snapchat_only", "all") val integratedUI = boolean("integrated_ui", false) { requireRestart() } val disableLogAnonymization = boolean("disable_log_anonymization", false) { requireRestart() } diff --git a/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Spoof.kt b/common/src/main/kotlin/me/rhunk/snapenhance/common/config/impl/Spoof.kt @@ -1,11 +1,12 @@ package me.rhunk.snapenhance.common.config.impl import me.rhunk.snapenhance.common.config.ConfigContainer +import me.rhunk.snapenhance.common.config.ConfigFlag class Spoof : ConfigContainer(hasGlobalState = true) { val overridePlayStoreInstallerPackageName = boolean("play_store_installer_package_name") - val fingerprint = string("fingerprint") - val androidId = string("android_id") + val fingerprint = string("fingerprint") { addFlags(ConfigFlag.SENSITIVE) } + val androidId = string("android_id") { addFlags(ConfigFlag.SENSITIVE) } val removeVpnTransportFlag = boolean("remove_vpn_transport_flag") val removeMockLocationFlag = boolean("remove_mock_location_flag") val randomizePersistentDeviceToken = boolean("randomize_persistent_device_token") diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/BetterLocation.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/BetterLocation.kt @@ -89,7 +89,6 @@ class BetterLocation : Feature("Better Location", loadParams = FeatureLoadParams editor.apply { // SCVSLocationUpdate edit(1) { - context.log.verbose("SCVSLocationUpdate ${this@apply}") if (config.spoofLocation.get()) { remove(1) remove(2)