From 1df89842a133d2310b628737eabf11ab8bfd41a8 Mon Sep 17 00:00:00 2001 From: crc-32 Date: Thu, 4 Jul 2024 16:14:17 +0100 Subject: [PATCH] sensitive logging flag --- .../cobble/bridges/ui/DebugFlutterBridge.kt | 23 +++++++ .../io/rebble/cobble/di/AppComponent.kt | 2 + .../notifications/NotificationListener.kt | 16 +++-- .../io/rebble/cobble/pigeons/Pigeons.java | 60 +++++++++++++++++++ .../cobble/shared/datastore/KMPPrefs.kt | 12 +++- ios/Runner/Pigeon/Pigeons.h | 2 + ios/Runner/Pigeon/Pigeons.m | 36 +++++++++++ lib/infrastructure/pigeons/pigeons.g.dart | 49 +++++++++++++++ lib/ui/devoptions/debug_options_page.dart | 17 ++++++ pigeons/pigeons.dart | 4 ++ 10 files changed, 216 insertions(+), 5 deletions(-) diff --git a/android/app/src/main/kotlin/io/rebble/cobble/bridges/ui/DebugFlutterBridge.kt b/android/app/src/main/kotlin/io/rebble/cobble/bridges/ui/DebugFlutterBridge.kt index 1e8091cd..96cc5219 100644 --- a/android/app/src/main/kotlin/io/rebble/cobble/bridges/ui/DebugFlutterBridge.kt +++ b/android/app/src/main/kotlin/io/rebble/cobble/bridges/ui/DebugFlutterBridge.kt @@ -2,14 +2,24 @@ package io.rebble.cobble.bridges.ui import android.content.Context import io.rebble.cobble.bridges.FlutterBridge +import io.rebble.cobble.errors.GlobalExceptionHandler import io.rebble.cobble.log.collectAndShareLogs import io.rebble.cobble.pigeons.Pigeons +import io.rebble.cobble.shared.datastore.KMPPrefs +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.launch import javax.inject.Inject class DebugFlutterBridge @Inject constructor( private val context: Context, + private val prefs: KMPPrefs, + private val globalExceptionHandler: GlobalExceptionHandler, bridgeLifecycleController: BridgeLifecycleController ) : FlutterBridge, Pigeons.DebugControl { + private val scope = CoroutineScope(Dispatchers.Main + globalExceptionHandler) init { bridgeLifecycleController.setupControl(Pigeons.DebugControl::setup, this) } @@ -17,4 +27,17 @@ class DebugFlutterBridge @Inject constructor( override fun collectLogs(rwsId: String) { collectAndShareLogs(context, rwsId) } + + override fun getSensitiveLoggingEnabled(result: Pigeons.Result) { + scope.launch { + result.success(prefs.sensitiveDataLoggingEnabled.first()) + } + } + + override fun setSensitiveLoggingEnabled(enabled: Boolean, result: Pigeons.Result) { + scope.launch { + prefs.setSensitiveDataLoggingEnabled(enabled) + result.success(null) + } + } } \ No newline at end of file diff --git a/android/app/src/main/kotlin/io/rebble/cobble/di/AppComponent.kt b/android/app/src/main/kotlin/io/rebble/cobble/di/AppComponent.kt index 54d121e0..85d45abb 100644 --- a/android/app/src/main/kotlin/io/rebble/cobble/di/AppComponent.kt +++ b/android/app/src/main/kotlin/io/rebble/cobble/di/AppComponent.kt @@ -13,6 +13,7 @@ import io.rebble.cobble.datasources.PairedStorage import io.rebble.cobble.datasources.WatchMetadataStore import io.rebble.cobble.errors.GlobalExceptionHandler import io.rebble.cobble.service.ServiceLifecycleControl +import io.rebble.cobble.shared.datastore.KMPPrefs import io.rebble.cobble.shared.domain.calendar.CalendarSync import io.rebble.libpebblecommon.ProtocolHandler import io.rebble.libpebblecommon.services.PhoneControlService @@ -51,6 +52,7 @@ interface AppComponent { //TODO: Unify DI under Koin fun createKMPCalendarSync(): CalendarSync + fun createKMPPrefs(): KMPPrefs @Component.Factory interface Factory { diff --git a/android/app/src/main/kotlin/io/rebble/cobble/notifications/NotificationListener.kt b/android/app/src/main/kotlin/io/rebble/cobble/notifications/NotificationListener.kt index b67a5749..8f1a6530 100644 --- a/android/app/src/main/kotlin/io/rebble/cobble/notifications/NotificationListener.kt +++ b/android/app/src/main/kotlin/io/rebble/cobble/notifications/NotificationListener.kt @@ -15,15 +15,13 @@ import io.rebble.cobble.bridges.background.NotificationsFlutterBridge import io.rebble.cobble.data.NotificationAction import io.rebble.cobble.data.NotificationMessage import io.rebble.cobble.datasources.FlutterPreferences +import io.rebble.cobble.shared.datastore.KMPPrefs import io.rebble.cobble.shared.domain.state.ConnectionState import io.rebble.libpebblecommon.packets.blobdb.BlobResponse import io.rebble.libpebblecommon.packets.blobdb.TimelineItem import io.rebble.libpebblecommon.services.notification.NotificationService import kotlinx.coroutines.* -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.flow.collect -import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.* import timber.log.Timber class NotificationListener : NotificationListenerService() { @@ -37,6 +35,7 @@ class NotificationListener : NotificationListenerService() { private lateinit var notificationService: NotificationService private lateinit var notificationBridge: NotificationsFlutterBridge + private lateinit var prefs: KMPPrefs override fun onCreate() { val injectionComponent = (applicationContext as CobbleApplication).component @@ -49,6 +48,7 @@ class NotificationListener : NotificationListenerService() { notificationService = injectionComponent.createNotificationService() notificationBridge = injectionComponent.createNotificationsFlutterBridge() flutterPreferences = injectionComponent.createFlutterPreferences() + prefs = injectionComponent.createKMPPrefs() super.onCreate() _isActive.value = true @@ -101,6 +101,14 @@ class NotificationListener : NotificationListenerService() { //if (sbn.notification.group != null && !NotificationCompat.isGroupSummary(sbn.notification)) return if (mutedPackages.contains(sbn.packageName)) return // ignore muted packages + + coroutineScope.launch { + if (prefs.sensitiveDataLoggingEnabled.firstOrNull() == true) { + Timber.d("Notification posted: ${sbn.packageName}") + Timber.d("Notification: ${sbn.notification}\n${sbn.notification.extras}") + } + } + var tagId: String? = null if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { tagId = sbn.notification.channelId diff --git a/android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java b/android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java index 53b8d8a3..56c53359 100644 --- a/android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java +++ b/android/app/src/main/kotlin/io/rebble/cobble/pigeons/Pigeons.java @@ -4216,6 +4216,10 @@ public interface DebugControl { void collectLogs(@NonNull String rwsId); + void getSensitiveLoggingEnabled(@NonNull Result result); + + void setSensitiveLoggingEnabled(@NonNull Boolean enabled, @NonNull Result result); + /** The codec used by DebugControl. */ static @NonNull MessageCodec getCodec() { return new StandardMessageCodec(); @@ -4246,6 +4250,62 @@ static void setup(@NonNull BinaryMessenger binaryMessenger, @Nullable DebugContr channel.setMessageHandler(null); } } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.DebugControl.getSensitiveLoggingEnabled", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + Result resultCallback = + new Result() { + public void success(Boolean result) { + wrapped.add(0, result); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.getSensitiveLoggingEnabled(resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } + { + BasicMessageChannel channel = + new BasicMessageChannel<>( + binaryMessenger, "dev.flutter.pigeon.DebugControl.setSensitiveLoggingEnabled", getCodec()); + if (api != null) { + channel.setMessageHandler( + (message, reply) -> { + ArrayList wrapped = new ArrayList(); + ArrayList args = (ArrayList) message; + Boolean enabledArg = (Boolean) args.get(0); + Result resultCallback = + new Result() { + public void success(Void result) { + wrapped.add(0, null); + reply.reply(wrapped); + } + + public void error(Throwable error) { + ArrayList wrappedError = wrapError(error); + reply.reply(wrappedError); + } + }; + + api.setSensitiveLoggingEnabled(enabledArg, resultCallback); + }); + } else { + channel.setMessageHandler(null); + } + } } } diff --git a/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/datastore/KMPPrefs.kt b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/datastore/KMPPrefs.kt index aaf415cd..c09029b1 100644 --- a/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/datastore/KMPPrefs.kt +++ b/android/shared/src/commonMain/kotlin/io/rebble/cobble/shared/datastore/KMPPrefs.kt @@ -19,6 +19,16 @@ class KMPPrefs: KoinComponent { preferences[ENABLE_CALENDAR_KEY] = enabled } } + + val sensitiveDataLoggingEnabled = dataStore.data.map { preferences -> + preferences[SENSITIVE_DATA_LOGGING_KEY] ?: false + } + suspend fun setSensitiveDataLoggingEnabled(enabled: Boolean) { + dataStore.edit { preferences -> + preferences[SENSITIVE_DATA_LOGGING_KEY] = enabled + } + } } -private val ENABLE_CALENDAR_KEY = booleanPreferencesKey("enable_calendar_sync") \ No newline at end of file +private val ENABLE_CALENDAR_KEY = booleanPreferencesKey("enable_calendar_sync") +private val SENSITIVE_DATA_LOGGING_KEY = booleanPreferencesKey("sensitive_data_logging") \ No newline at end of file diff --git a/ios/Runner/Pigeon/Pigeons.h b/ios/Runner/Pigeon/Pigeons.h index ea970350..fde89360 100644 --- a/ios/Runner/Pigeon/Pigeons.h +++ b/ios/Runner/Pigeon/Pigeons.h @@ -506,6 +506,8 @@ NSObject *DebugControlGetCodec(void); @protocol DebugControl - (void)collectLogsRwsId:(NSString *)rwsId error:(FlutterError *_Nullable *_Nonnull)error; +- (void)getSensitiveLoggingEnabledWithCompletion:(void (^)(NSNumber *_Nullable, FlutterError *_Nullable))completion; +- (void)setSensitiveLoggingEnabledEnabled:(NSNumber *)enabled completion:(void (^)(FlutterError *_Nullable))completion; @end extern void DebugControlSetup(id binaryMessenger, NSObject *_Nullable api); diff --git a/ios/Runner/Pigeon/Pigeons.m b/ios/Runner/Pigeon/Pigeons.m index 36986cbc..0a33a7ea 100644 --- a/ios/Runner/Pigeon/Pigeons.m +++ b/ios/Runner/Pigeon/Pigeons.m @@ -2471,6 +2471,42 @@ void DebugControlSetup(id binaryMessenger, NSObject getSensitiveLoggingEnabled() async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.DebugControl.getSensitiveLoggingEnabled', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send(null) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else if (replyList[0] == null) { + throw PlatformException( + code: 'null-error', + message: 'Host platform returned null value for non-null return value.', + ); + } else { + return (replyList[0] as bool?)!; + } + } + + Future setSensitiveLoggingEnabled(bool arg_enabled) async { + final BasicMessageChannel channel = BasicMessageChannel( + 'dev.flutter.pigeon.DebugControl.setSensitiveLoggingEnabled', codec, + binaryMessenger: _binaryMessenger); + final List? replyList = + await channel.send([arg_enabled]) as List?; + if (replyList == null) { + throw PlatformException( + code: 'channel-error', + message: 'Unable to establish connection on channel.', + ); + } else if (replyList.length > 1) { + throw PlatformException( + code: replyList[0]! as String, + message: replyList[1] as String?, + details: replyList[2], + ); + } else { + return; + } + } } class _TimelineControlCodec extends StandardMessageCodec { diff --git a/lib/ui/devoptions/debug_options_page.dart b/lib/ui/devoptions/debug_options_page.dart index 78035f42..e89810ed 100644 --- a/lib/ui/devoptions/debug_options_page.dart +++ b/lib/ui/devoptions/debug_options_page.dart @@ -27,6 +27,13 @@ class DebugOptionsPage extends HookConsumerWidget implements CobbleScreen { final bootOverrideUrlController = useTextEditingController(); final DebugControl debug = DebugControl(); + final sensitiveLoggingEnabled = useState(false); + useEffect(() { + debug.getSensitiveLoggingEnabled().then((value) { + sensitiveLoggingEnabled.value = value; + }); + return null; + }, []); useEffect(() { bootUrlController.text = bootUrl; @@ -88,6 +95,16 @@ class DebugOptionsPage extends HookConsumerWidget implements CobbleScreen { ], ), ), + SwitchListTile( + contentPadding: const EdgeInsets.symmetric(vertical: 10, horizontal: 15), + value: sensitiveLoggingEnabled.value, + onChanged: (value) { + debug.setSensitiveLoggingEnabled(value); + sensitiveLoggingEnabled.value = value; + }, + title: const Text("Enable sensitive data logging"), + subtitle: const Text("Enables more in-depth logging at the cost of privacy (e.g. notification contents)"), + ), CobbleButton( onPressed: () async { try { diff --git a/pigeons/pigeons.dart b/pigeons/pigeons.dart index 31b682da..8022e0a7 100644 --- a/pigeons/pigeons.dart +++ b/pigeons/pigeons.dart @@ -330,6 +330,10 @@ abstract class IntentControl { @HostApi() abstract class DebugControl { void collectLogs(String rwsId); + @async + bool getSensitiveLoggingEnabled(); + @async + void setSensitiveLoggingEnabled(bool enabled); } @HostApi()