From 36a67ee09a167991d4cecbf7f3c1f5d9fedd8044 Mon Sep 17 00:00:00 2001 From: Fabian Freund Date: Sat, 11 Oct 2025 14:52:09 +0200 Subject: [PATCH] add fingerprinting configuration --- app/lib/core/routing/routes.dart | 1 + app/lib/core/routing/routes.g.dart | 27 +++ app/lib/core/routing/routes.settings.dart | 12 ++ .../services/engine_settings_replication.dart | 6 + .../engine_settings_replication.g.dart | 2 +- .../screens/fingerprint_settings.dart | 155 ++++++++++++++++ .../screens/web_engine_settings.dart | 12 ++ .../user/data/models/engine_settings.dart | 7 + .../user/data/models/engine_settings.g.dart | 22 +++ .../features/user/data/models/rfp_target.dart | 27 +++ .../user/data/models/rfp_target.g.dart | 23 +++ .../entities/fingerprint_overrides.dart | 174 ++++++++++++++++++ app/lib/features/user/domain/providers.dart | 26 +++ app/lib/features/user/domain/providers.g.dart | 43 +++++ .../domain/repositories/engine_settings.dart | 5 + .../repositories/engine_settings.g.dart | 2 +- .../user/domain/services/fingerprinting.dart | 20 ++ .../domain/services/fingerprinting.g.dart | 50 +++++ app/pubspec.yaml | 12 +- .../api/GeckoEngineSettingsApiImpl.kt | 6 + .../pigeons/Gecko.g.kt | 7 +- .../services/gecko_engine_settings.dart | 6 + .../lib/src/pigeons/gecko.g.dart | 5 + .../pigeons/gecko.dart | 2 + 24 files changed, 642 insertions(+), 10 deletions(-) create mode 100644 app/lib/features/settings/presentation/screens/fingerprint_settings.dart create mode 100644 app/lib/features/user/data/models/rfp_target.dart create mode 100644 app/lib/features/user/data/models/rfp_target.g.dart create mode 100644 app/lib/features/user/domain/entities/fingerprint_overrides.dart create mode 100644 app/lib/features/user/domain/services/fingerprinting.dart create mode 100644 app/lib/features/user/domain/services/fingerprinting.g.dart diff --git a/app/lib/core/routing/routes.dart b/app/lib/core/routing/routes.dart index b0d9e60e..217220d6 100644 --- a/app/lib/core/routing/routes.dart +++ b/app/lib/core/routing/routes.dart @@ -43,6 +43,7 @@ import 'package:weblibre/features/settings/presentation/screens/addon_collection import 'package:weblibre/features/settings/presentation/screens/bang_settings.dart'; import 'package:weblibre/features/settings/presentation/screens/developer_settings.dart'; import 'package:weblibre/features/settings/presentation/screens/doh_settings.dart'; +import 'package:weblibre/features/settings/presentation/screens/fingerprint_settings.dart'; import 'package:weblibre/features/settings/presentation/screens/general_settings.dart'; import 'package:weblibre/features/settings/presentation/screens/settings.dart'; import 'package:weblibre/features/settings/presentation/screens/web_engine_hardening.dart'; diff --git a/app/lib/core/routing/routes.g.dart b/app/lib/core/routing/routes.g.dart index 46240340..fe7fcb53 100644 --- a/app/lib/core/routing/routes.g.dart +++ b/app/lib/core/routing/routes.g.dart @@ -111,6 +111,11 @@ RouteBase get $settingsRoute => GoRouteData.$route( name: 'DohSettingsRoute', factory: $DohSettingsRoute._fromState, ), + GoRouteData.$route( + path: 'fingerprint', + name: 'FingerprintSettingsRoute', + factory: $FingerprintSettingsRoute._fromState, + ), ], ), GoRouteData.$route( @@ -279,6 +284,28 @@ mixin $DohSettingsRoute on GoRouteData { void replace(BuildContext context) => context.replace(location); } +mixin $FingerprintSettingsRoute on GoRouteData { + static FingerprintSettingsRoute _fromState(GoRouterState state) => + FingerprintSettingsRoute(); + + @override + String get location => + GoRouteData.$location('/settings/web_engine/fingerprint'); + + @override + void go(BuildContext context) => context.go(location); + + @override + Future push(BuildContext context) => context.push(location); + + @override + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + @override + void replace(BuildContext context) => context.replace(location); +} + mixin $DeveloperSettingsRoute on GoRouteData { static DeveloperSettingsRoute _fromState(GoRouterState state) => DeveloperSettingsRoute(); diff --git a/app/lib/core/routing/routes.settings.dart b/app/lib/core/routing/routes.settings.dart index 544b15dc..2726101b 100644 --- a/app/lib/core/routing/routes.settings.dart +++ b/app/lib/core/routing/routes.settings.dart @@ -43,6 +43,10 @@ part of 'routes.dart'; ], ), TypedGoRoute(name: 'DohSettingsRoute', path: 'doh'), + TypedGoRoute( + name: 'FingerprintSettingsRoute', + path: 'fingerprint', + ), ], ), TypedGoRoute( @@ -85,6 +89,14 @@ class DohSettingsRoute extends GoRouteData with $DohSettingsRoute { } } +class FingerprintSettingsRoute extends GoRouteData + with $FingerprintSettingsRoute { + @override + Widget build(BuildContext context, GoRouterState state) { + return const FingerprintSettingsScreen(); + } +} + class WebEngineSettingsRoute extends GoRouteData with $WebEngineSettingsRoute { @override Widget build(BuildContext context, GoRouterState state) { diff --git a/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.dart b/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.dart index d7fed9ac..8d30f3d2 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.dart @@ -128,6 +128,12 @@ class EngineSettingsReplicationService if (previous.value?.dohSettings != settings.dohSettings) { await _service.dohSettings(settings.dohSettings); } + if (previous.value?.fingerprintingProtectionOverrides != + settings.fingerprintingProtectionOverrides) { + await _service.fingerprintingProtectionOverrides( + settings.fingerprintingProtectionOverrides, + ); + } } else { await _service.setDefaultSettings(settings); initialSettingsSent = true; diff --git a/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.g.dart b/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.g.dart index db69c951..e03abf08 100644 --- a/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.g.dart +++ b/app/lib/features/geckoview/features/browser/domain/services/engine_settings_replication.g.dart @@ -44,7 +44,7 @@ final class EngineSettingsReplicationServiceProvider } String _$engineSettingsReplicationServiceHash() => - r'0bbc80fe0cff79f9e419ffa8c486d232f9eac2ee'; + r'3c96e6e45537399582fdf6e8b1fafc44c613b89d'; abstract class _$EngineSettingsReplicationService extends $Notifier { void build(); diff --git a/app/lib/features/settings/presentation/screens/fingerprint_settings.dart b/app/lib/features/settings/presentation/screens/fingerprint_settings.dart new file mode 100644 index 00000000..32efd445 --- /dev/null +++ b/app/lib/features/settings/presentation/screens/fingerprint_settings.dart @@ -0,0 +1,155 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_material_design_icons/flutter_material_design_icons.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:nullability/nullability.dart'; +import 'package:weblibre/features/settings/presentation/controllers/save_settings.dart'; +import 'package:weblibre/features/user/data/models/engine_settings.dart'; +import 'package:weblibre/features/user/domain/entities/fingerprint_overrides.dart'; +import 'package:weblibre/features/user/domain/providers.dart'; +import 'package:weblibre/features/user/domain/services/fingerprinting.dart'; +import 'package:weblibre/presentation/widgets/failure_widget.dart'; + +class FingerprintSettingsScreen extends HookConsumerWidget { + const FingerprintSettingsScreen(); + + @override + Widget build(BuildContext context, WidgetRef ref) { + final targetsAsync = ref.watch(fingerprintTargetsProvider); + final settingsAsync = ref.watch(fingerprintOverrideSettingsProvider); + + return Scaffold( + appBar: AppBar( + title: const Text('Fingerprint Protection'), + actions: [ + MenuAnchor( + builder: (context, controller, child) { + return IconButton( + onPressed: () { + controller.open(); + }, + icon: const Icon(Icons.more_vert), + ); + }, + menuChildren: [ + MenuItemButton( + leadingIcon: const Icon(MdiIcons.restore), + child: const Text('Load Defaults'), + onPressed: () async { + await ref + .read(saveEngineSettingsControllerProvider.notifier) + .save( + (currentSettings) => currentSettings.copyWith + .fingerprintingProtectionOverrides( + FingerprintOverrides.defaults().toString(), + ), + ); + }, + ), + MenuItemButton( + leadingIcon: const Icon(MdiIcons.restore), + child: const Text('Load Hardened Defaults'), + onPressed: () async { + await ref + .read(saveEngineSettingsControllerProvider.notifier) + .save( + (currentSettings) => currentSettings.copyWith + .fingerprintingProtectionOverrides( + FingerprintOverrides.hardenedDefaults() + .toString(), + ), + ); + }, + ), + ], + ), + ], + ), + body: SafeArea( + child: settingsAsync.when( + data: (result) { + return result.fold( + (overrides) { + return targetsAsync.when( + data: (targets) { + return ListView.builder( + itemCount: targets.length, + itemBuilder: (context, index) { + final target = targets[index]; + final state = overrides.targets[target.name]; + + return CheckboxListTile.adaptive( + value: + (overrides.allTargets == true && + state != false) || + state == true, + onChanged: (value) async { + if (value != null) { + final newOverrides = overrides + .copyWithTarget(target.name, value) + .toString(); + + await ref + .read( + saveEngineSettingsControllerProvider + .notifier, + ) + .save( + (currentSettings) => currentSettings + .copyWith + .fingerprintingProtectionOverrides( + newOverrides, + ), + ); + } + }, + title: Text(target.name), + subtitle: target.description.mapNotNull( + (desc) => Text(desc), + ), + ); + }, + ); + }, + error: (error, stackTrace) { + return Center( + child: FailureWidget( + exception: error, + onRetry: () { + ref.invalidate(fingerprintTargetsProvider); + }, + ), + ); + }, + loading: () => + const Center(child: CircularProgressIndicator()), + ); + }, + onFailure: (errorMessage) { + return Center( + child: FailureWidget( + title: errorMessage.message, + exception: errorMessage.details, + onRetry: () { + ref.invalidate(fingerprintOverrideSettingsProvider); + }, + ), + ); + }, + ); + }, + error: (error, stackTrace) { + return Center( + child: FailureWidget( + exception: error, + onRetry: () { + ref.invalidate(fingerprintOverrideSettingsProvider); + }, + ), + ); + }, + loading: () => const Center(child: CircularProgressIndicator()), + ), + ), + ); + } +} diff --git a/app/lib/features/settings/presentation/screens/web_engine_settings.dart b/app/lib/features/settings/presentation/screens/web_engine_settings.dart index ce64009b..70a2f670 100644 --- a/app/lib/features/settings/presentation/screens/web_engine_settings.dart +++ b/app/lib/features/settings/presentation/screens/web_engine_settings.dart @@ -475,6 +475,18 @@ class WebEngineSettingsScreen extends HookConsumerWidget { await WebEngineHardeningRoute().push(context); }, ), + ListTile( + title: const Text('Fingerprint Protection'), + contentPadding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 16.0, + ), + leading: const Icon(MdiIcons.fingerprint), + trailing: const Icon(Icons.chevron_right), + onTap: () async { + await FingerprintSettingsRoute().push(context); + }, + ), ], ); }, diff --git a/app/lib/features/user/data/models/engine_settings.dart b/app/lib/features/user/data/models/engine_settings.dart index 410242de..1c5a76f2 100644 --- a/app/lib/features/user/data/models/engine_settings.dart +++ b/app/lib/features/user/data/models/engine_settings.dart @@ -24,6 +24,7 @@ import 'package:fast_equatable/fast_equatable.dart'; import 'package:flutter_mozilla_components/flutter_mozilla_components.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:nullability/nullability.dart'; +import 'package:weblibre/features/user/domain/entities/fingerprint_overrides.dart'; part 'engine_settings.g.dart'; @@ -125,6 +126,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable { required this.dohProviderUrl, required this.dohDefaultProviderUrl, required this.dohExceptionsList, + required super.fingerprintingProtectionOverrides, }); EngineSettings.withDefaults({ @@ -147,6 +149,7 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable { String? dohProviderUrl, String? dohDefaultProviderUrl, List? dohExceptionsList, + String? fingerprintingProtectionOverrides, }) : queryParameterStripping = queryParameterStripping ?? QueryParameterStripping.disabled, bounceTrackingProtectionMode = @@ -177,6 +180,9 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable { webContentIsolationStrategy ?? WebContentIsolationStrategy.isolateHighValue, enterpriseRootsEnabled: enterpriseRootsEnabled ?? false, + fingerprintingProtectionOverrides: + fingerprintingProtectionOverrides ?? + FingerprintOverrides.defaults().toString(), ); static AddonCollection? _addonCollectionFromJson(String? json) => @@ -201,5 +207,6 @@ class EngineSettings extends GeckoEngineSettings with FastEquatable { dohProviderUrl, dohDefaultProviderUrl, dohExceptionsList, + fingerprintingProtectionOverrides, ]; } diff --git a/app/lib/features/user/data/models/engine_settings.g.dart b/app/lib/features/user/data/models/engine_settings.g.dart index c867a3f1..d4832b33 100644 --- a/app/lib/features/user/data/models/engine_settings.g.dart +++ b/app/lib/features/user/data/models/engine_settings.g.dart @@ -61,6 +61,10 @@ abstract class _$EngineSettingsCWProxy { EngineSettings dohExceptionsList(List dohExceptionsList); + EngineSettings fingerprintingProtectionOverrides( + String? fingerprintingProtectionOverrides, + ); + /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`. /// @@ -88,6 +92,7 @@ abstract class _$EngineSettingsCWProxy { String dohProviderUrl, String dohDefaultProviderUrl, List dohExceptionsList, + String? fingerprintingProtectionOverrides, }); } @@ -188,6 +193,13 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy { EngineSettings dohExceptionsList(List dohExceptionsList) => call(dohExceptionsList: dohExceptionsList); + @override + EngineSettings fingerprintingProtectionOverrides( + String? fingerprintingProtectionOverrides, + ) => call( + fingerprintingProtectionOverrides: fingerprintingProtectionOverrides, + ); + @override /// Creates a new instance with the provided field values. /// Passing `null` to a nullable field nullifies it, while `null` for a non-nullable field is ignored. To update a single field use `EngineSettings(...).copyWith.fieldName(value)`. @@ -218,6 +230,7 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy { Object? dohProviderUrl = const $CopyWithPlaceholder(), Object? dohDefaultProviderUrl = const $CopyWithPlaceholder(), Object? dohExceptionsList = const $CopyWithPlaceholder(), + Object? fingerprintingProtectionOverrides = const $CopyWithPlaceholder(), }) { return EngineSettings( javascriptEnabled: javascriptEnabled == const $CopyWithPlaceholder() @@ -319,6 +332,11 @@ class _$EngineSettingsCWProxyImpl implements _$EngineSettingsCWProxy { ? _value.dohExceptionsList // ignore: cast_nullable_to_non_nullable : dohExceptionsList as List, + fingerprintingProtectionOverrides: + fingerprintingProtectionOverrides == const $CopyWithPlaceholder() + ? _value.fingerprintingProtectionOverrides + // ignore: cast_nullable_to_non_nullable + : fingerprintingProtectionOverrides as String?, ); } } @@ -388,12 +406,16 @@ EngineSettings _$EngineSettingsFromJson(Map json) => dohExceptionsList: (json['dohExceptionsList'] as List?) ?.map((e) => e as String) .toList(), + fingerprintingProtectionOverrides: + json['fingerprintingProtectionOverrides'] as String?, ); Map _$EngineSettingsToJson( EngineSettings instance, ) => { 'userAgent': instance.userAgent, + 'fingerprintingProtectionOverrides': + instance.fingerprintingProtectionOverrides, 'javascriptEnabled': instance.javascriptEnabled, 'trackingProtectionPolicy': _$TrackingProtectionPolicyEnumMap[instance.trackingProtectionPolicy]!, diff --git a/app/lib/features/user/data/models/rfp_target.dart b/app/lib/features/user/data/models/rfp_target.dart new file mode 100644 index 00000000..d3c69875 --- /dev/null +++ b/app/lib/features/user/data/models/rfp_target.dart @@ -0,0 +1,27 @@ +import 'package:fast_equatable/fast_equatable.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'rfp_target.g.dart'; + +@JsonSerializable() +class RFPTarget with FastEquatable { + final String name; + final int id; + final String? description; + final List keywords; + + RFPTarget({ + required this.name, + required this.id, + this.description, + required this.keywords, + }); + + factory RFPTarget.fromJson(Map json) => + _$RFPTargetFromJson(json); + + Map toJson() => _$RFPTargetToJson(this); + + @override + List get hashParameters => [name, id, description, keywords]; +} diff --git a/app/lib/features/user/data/models/rfp_target.g.dart b/app/lib/features/user/data/models/rfp_target.g.dart new file mode 100644 index 00000000..93aaaadf --- /dev/null +++ b/app/lib/features/user/data/models/rfp_target.g.dart @@ -0,0 +1,23 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'rfp_target.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +RFPTarget _$RFPTargetFromJson(Map json) => RFPTarget( + name: json['name'] as String, + id: (json['id'] as num).toInt(), + description: json['description'] as String?, + keywords: (json['keywords'] as List) + .map((e) => e as String) + .toList(), +); + +Map _$RFPTargetToJson(RFPTarget instance) => { + 'name': instance.name, + 'id': instance.id, + 'description': instance.description, + 'keywords': instance.keywords, +}; diff --git a/app/lib/features/user/domain/entities/fingerprint_overrides.dart b/app/lib/features/user/domain/entities/fingerprint_overrides.dart new file mode 100644 index 00000000..2cfb9bdc --- /dev/null +++ b/app/lib/features/user/domain/entities/fingerprint_overrides.dart @@ -0,0 +1,174 @@ +import 'package:exceptions/exceptions.dart'; +import 'package:fast_equatable/fast_equatable.dart'; + +class FingerprintOverrides with FastEquatable { + static final pattern = RegExp('([+-])([a-zA-Z_][a-zA-Z0-9_]{1,64})'); + + final bool? allTargets; + final Map targets; + + FingerprintOverrides(this.allTargets, this.targets); + + //Monitor https://searchfox.org/firefox-main/source/toolkit/components/resistfingerprinting/RFPTargetsDefault.inc + FingerprintOverrides.defaults() + : this(false, { + 'CanvasRandomization': true, + 'EfficientCanvasRandomization': true, + 'FontVisibilityLangPack': true, + 'JSMathFdlibm': true, + 'ScreenAvailToResolution': true, + 'NavigatorHWConcurrencyTiered': true, + 'MaxTouchPointsCollapse': true, + }); + + FingerprintOverrides.hardenedDefaults() + : this(false, { + 'TouchEvents': true, + 'PointerEvents': true, + 'KeyboardEvents': true, + 'ScreenOrientation': true, + 'SpeechSynthesis': true, + 'CSSPrefersReducedMotion': true, + 'CSSPrefersContrast': true, + 'CanvasRandomization': true, + 'CanvasExtractionFromThirdPartiesIsBlocked': true, + 'JSLocale': true, + 'NavigatorAppVersion': true, + 'NavigatorBuildID': true, + 'NavigatorHWConcurrency': true, + 'NavigatorOscpu': true, + 'NavigatorPlatform': true, + 'NavigatorUserAgent': true, + 'PointerId': true, + 'StreamVideoFacingMode': true, + 'JSDateTimeUTC': true, + 'JSMathFdlibm': true, + 'Gamepad': true, + 'HttpUserAgent': true, + 'WindowOuterSize': true, + 'WindowScreenXY': true, + 'WindowInnerScreenXY': true, + 'ScreenPixelDepth': true, + 'ScreenRect': true, + 'ScreenAvailRect': true, + 'VideoElementMozFrames': true, + 'VideoElementMozFrameDelay': true, + 'VideoElementPlaybackQuality': true, + 'ReduceTimerPrecision': true, + 'WidgetEvents': true, + 'MediaDevices': true, + 'MediaCapabilities': true, + 'AudioSampleRate': true, + 'NetworkConnection': true, + 'WindowDevicePixelRatio': true, + 'MouseEventScreenPoint': true, + 'FontVisibilityBaseSystem': true, + 'FontVisibilityLangPack': true, + 'DeviceSensors': true, + 'RoundWindowSize': true, + 'UseStandinsForNativeColors': true, + 'AudioContext': true, + 'MediaError': true, + 'DOMStyleOsxFontSmoothing': true, + 'CSSDeviceSize': true, + 'CSSColorInfo': true, + 'CSSResolution': true, + 'CSSPrefersReducedTransparency': true, + 'CSSInvertedColors': true, + 'CSSVideoDynamicRange': true, + 'CSSPointerCapabilities': true, + 'WebGLRenderCapability': true, + 'WebGLRenderInfo': true, + 'SiteSpecificZoom': true, + 'FontVisibilityRestrictGenerics': true, + 'WebVTT': true, + 'WebGPULimits': true, + 'WebGPUIsFallbackAdapter': true, + 'WebGPUSubgroupSizes': true, + 'JSLocalePrompt': true, + 'ScreenAvailToResolution': true, + 'UseHardcodedFontSubstitutes': true, + 'DiskStorageLimit': true, + 'WebCodecs': true, + 'MaxTouchPoints': true, + 'MaxTouchPointsCollapse': true, + 'NavigatorHWConcurrencyTiered': true, + }); + + static Result parse( + String input, + Set availableTargets, + ) { + final cleaned = input.replaceAll(RegExp(r'\s'), ''); + if (cleaned.isEmpty) return Result.success(FingerprintOverrides(null, {})); + + bool? allTargets; + final targets = {}; + + for (final word in cleaned.split(',')) { + final match = pattern.firstMatch(word); + if (match == null) { + return Result.failure( + const ErrorMessage(source: 'FpParser', message: 'Invalid Override'), + ); + } + + final enabled = match.group(1) != '-'; + final name = match.group(2)!; + + if (name == 'AllTargets') { + allTargets = enabled; + continue; + } + + if (!availableTargets.contains(name)) { + return Result.failure( + const ErrorMessage( + source: 'FpParser', + message: 'Invalid target name', + ), + ); + } + + targets[name] = enabled; + } + + return Result.success(FingerprintOverrides(allTargets, targets)); + } + + @override + String toString() { + final sb = StringBuffer(); + + if (allTargets != null) { + sb.write('${allTargets! ? '+' : '-'}AllTargets'); + if (targets.isNotEmpty) { + sb.write(','); + } + } + + sb.write( + targets.entries.map((e) => (e.value ? '+' : '-') + e.key).join(','), + ); + + return sb.toString(); + } + + FingerprintOverrides copyWithAllTargetsEnabled(bool value) { + return FingerprintOverrides( + value, + Map.fromEntries(targets.entries.where((e) => e.value != value)), + ); + } + + FingerprintOverrides copyWithTarget(String name, bool value) { + if (value && allTargets == true) { + return this; + } + + return FingerprintOverrides(allTargets, {...targets, name: value}); + } + + @override + List get hashParameters => [allTargets, targets]; +} diff --git a/app/lib/features/user/domain/providers.dart b/app/lib/features/user/domain/providers.dart index 9d9e2d3b..a6ff8018 100644 --- a/app/lib/features/user/domain/providers.dart +++ b/app/lib/features/user/domain/providers.dart @@ -17,11 +17,16 @@ * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ +import 'package:exceptions/exceptions.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:nullability/nullability.dart'; import 'package:riverpod/riverpod.dart'; import 'package:riverpod_annotation/riverpod_annotation.dart'; import 'package:weblibre/features/user/data/providers.dart'; +import 'package:weblibre/features/user/domain/entities/fingerprint_overrides.dart'; +import 'package:weblibre/features/user/domain/repositories/engine_settings.dart'; import 'package:weblibre/features/user/domain/repositories/general_settings.dart'; +import 'package:weblibre/features/user/domain/services/fingerprinting.dart'; part 'providers.g.dart'; @@ -47,3 +52,24 @@ bool incognitoModeEnabled(Ref ref) { ), ); } + +@Riverpod() +Future> fingerprintOverrideSettings( + Ref ref, +) async { + final fingerprintTargets = await ref.watch(fingerprintTargetsProvider.future); + final fingerprintTargetSet = fingerprintTargets.map((e) => e.name).toSet(); + + final overrides = ref.watch( + engineSettingsWithDefaultsProvider.select( + (settings) => + settings.fingerprintingProtectionOverrides.mapNotNull( + (settings) => + FingerprintOverrides.parse(settings, fingerprintTargetSet), + ) ?? + Result.success(FingerprintOverrides.defaults()), + ), + ); + + return overrides; +} diff --git a/app/lib/features/user/domain/providers.g.dart b/app/lib/features/user/domain/providers.g.dart index 871e9156..e8a5bf8e 100644 --- a/app/lib/features/user/domain/providers.g.dart +++ b/app/lib/features/user/domain/providers.g.dart @@ -117,3 +117,46 @@ final class IncognitoModeEnabledProvider String _$incognitoModeEnabledHash() => r'36957b70a5261f9d3ad228e07cc8dd5c8f616082'; + +@ProviderFor(fingerprintOverrideSettings) +const fingerprintOverrideSettingsProvider = + FingerprintOverrideSettingsProvider._(); + +final class FingerprintOverrideSettingsProvider + extends + $FunctionalProvider< + AsyncValue>, + Result, + FutureOr> + > + with + $FutureModifier>, + $FutureProvider> { + const FingerprintOverrideSettingsProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'fingerprintOverrideSettingsProvider', + isAutoDispose: true, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$fingerprintOverrideSettingsHash(); + + @$internal + @override + $FutureProviderElement> $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr> create(Ref ref) { + return fingerprintOverrideSettings(ref); + } +} + +String _$fingerprintOverrideSettingsHash() => + r'd4d40ec425098fb1f5a2f0c4944f058829a41a0a'; diff --git a/app/lib/features/user/domain/repositories/engine_settings.dart b/app/lib/features/user/domain/repositories/engine_settings.dart index 8a316470..0226d1c8 100644 --- a/app/lib/features/user/domain/repositories/engine_settings.dart +++ b/app/lib/features/user/domain/repositories/engine_settings.dart @@ -114,6 +114,11 @@ class EngineSettingsRepository extends _$EngineSettingsRepository { DriftSqlType.string, db.typeMapping, ), + 'fingerprintingProtectionOverrides': + settings['fingerprintingProtectionOverrides']?.readAs( + DriftSqlType.string, + db.typeMapping, + ), }); } diff --git a/app/lib/features/user/domain/repositories/engine_settings.g.dart b/app/lib/features/user/domain/repositories/engine_settings.g.dart index 9cb90ae0..cf6a42c4 100644 --- a/app/lib/features/user/domain/repositories/engine_settings.g.dart +++ b/app/lib/features/user/domain/repositories/engine_settings.g.dart @@ -34,7 +34,7 @@ final class EngineSettingsRepositoryProvider } String _$engineSettingsRepositoryHash() => - r'5e403757036edb4ef964b01cdcf96cbcbdc4d809'; + r'33acd89e783d8d91b9b56e86411fdfcab24a2c54'; abstract class _$EngineSettingsRepository extends $StreamNotifier { diff --git a/app/lib/features/user/domain/services/fingerprinting.dart b/app/lib/features/user/domain/services/fingerprinting.dart new file mode 100644 index 00000000..6a4bc61f --- /dev/null +++ b/app/lib/features/user/domain/services/fingerprinting.dart @@ -0,0 +1,20 @@ +import 'dart:convert'; + +import 'package:flutter/services.dart'; +import 'package:riverpod_annotation/riverpod_annotation.dart'; +import 'package:weblibre/features/user/data/models/rfp_target.dart'; + +part 'fingerprinting.g.dart'; + +@Riverpod(keepAlive: true) +Future> fingerprintTargets(Ref ref) async { + final json = + await rootBundle + .loadString('assets/preferences/rfp_targets.json') + .then(jsonDecode) + as List; + + return json + .map((e) => RFPTarget.fromJson(e as Map)) + .toList(); +} diff --git a/app/lib/features/user/domain/services/fingerprinting.g.dart b/app/lib/features/user/domain/services/fingerprinting.g.dart new file mode 100644 index 00000000..bf52d5f4 --- /dev/null +++ b/app/lib/features/user/domain/services/fingerprinting.g.dart @@ -0,0 +1,50 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'fingerprinting.dart'; + +// ************************************************************************** +// RiverpodGenerator +// ************************************************************************** + +// GENERATED CODE - DO NOT MODIFY BY HAND +// ignore_for_file: type=lint, type=warning + +@ProviderFor(fingerprintTargets) +const fingerprintTargetsProvider = FingerprintTargetsProvider._(); + +final class FingerprintTargetsProvider + extends + $FunctionalProvider< + AsyncValue>, + List, + FutureOr> + > + with $FutureModifier>, $FutureProvider> { + const FingerprintTargetsProvider._() + : super( + from: null, + argument: null, + retry: null, + name: r'fingerprintTargetsProvider', + isAutoDispose: false, + dependencies: null, + $allTransitiveDependencies: null, + ); + + @override + String debugGetCreateSourceHash() => _$fingerprintTargetsHash(); + + @$internal + @override + $FutureProviderElement> $createElement( + $ProviderPointer pointer, + ) => $FutureProviderElement(pointer); + + @override + FutureOr> create(Ref ref) { + return fingerprintTargets(ref); + } +} + +String _$fingerprintTargetsHash() => + r'1ec5933a82941b84fdad2130ccf1ba2156ddc33a'; diff --git a/app/pubspec.yaml b/app/pubspec.yaml index fa4d59c9..455be862 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -33,7 +33,7 @@ dependencies: google_fonts: ^6.3.2 graphview: ^1.5.0 home_widget: ^0.8.0 - hooks_riverpod: ^3.0.2 + hooks_riverpod: ^3.0.3 html: ^0.15.6 http: ^1.5.0 http_parser: ^4.1.2 @@ -55,8 +55,8 @@ dependencies: path: ../packages/pluggable_transports_proxy pretty_qr_code: ^3.5.0 quick_actions: ^1.1.0 - riverpod: ^3.0.2 - riverpod_annotation: ^3.0.2 + riverpod: ^3.0.3 + riverpod_annotation: ^3.0.3 rss_dart: ^1.0.13 rxdart: ^0.28.0 share_plus: ^12.0.0 @@ -69,7 +69,7 @@ dependencies: speech_to_text_google_dialog: git: url: https://github.com/FaFre/speech_to_text_google_dialog.git - sqlite3: ^2.9.1 + sqlite3: ^2.9.2 sqlite3_flutter_libs: ^0.5.40 synchronized: ^3.4.0 text_scroll: ^0.2.1 @@ -96,8 +96,8 @@ dev_dependencies: go_router_builder: ^4.1.0 json_serializable: ^6.11.1 lint: ^2.8.0 - riverpod_generator: ^3.0.2 - riverpod_lint: ^3.0.2 + riverpod_generator: ^3.0.3 + riverpod_lint: ^3.0.3 flutter: uses-material-design: true diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt index 492a3183..be9da4ff 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/api/GeckoEngineSettingsApiImpl.kt @@ -144,6 +144,9 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi { components.core.engineSettings.dohDefaultProviderUrl = settings.dohSettings.dohDefaultProviderUrl components.core.engineSettings.dohExceptionsList = settings.dohSettings.dohExceptionsList } + if(settings.fingerprintingProtectionOverrides != null) { + components.core.engineSettings.fingerprintingProtectionOverrides = settings.fingerprintingProtectionOverrides + } } override fun updateRuntimeSettings(settings: GeckoEngineSettings) { @@ -227,6 +230,9 @@ class GeckoEngineSettingsApiImpl : GeckoEngineSettingsApi { components.core.engine.settings.dohDefaultProviderUrl = settings.dohSettings.dohDefaultProviderUrl components.core.engine.settings.dohExceptionsList = settings.dohSettings.dohExceptionsList } + if(settings.fingerprintingProtectionOverrides != null) { + components.core.engine.settings.fingerprintingProtectionOverrides = settings.fingerprintingProtectionOverrides + } if(reloadSession) { components.useCases.sessionUseCases.reload() diff --git a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt index 4568f15a..a4a1e45b 100644 --- a/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt +++ b/packages/flutter_mozilla_components/android/src/main/kotlin/eu/weblibre/flutter_mozilla_components/pigeons/Gecko.g.kt @@ -1677,7 +1677,8 @@ data class GeckoEngineSettings ( val userAgent: String? = null, val contentBlocking: ContentBlocking? = null, val enterpriseRootsEnabled: Boolean? = null, - val dohSettings: DohSettings? = null + val dohSettings: DohSettings? = null, + val fingerprintingProtectionOverrides: String? = null ) { companion object { @@ -1696,7 +1697,8 @@ data class GeckoEngineSettings ( val contentBlocking = pigeonVar_list[11] as ContentBlocking? val enterpriseRootsEnabled = pigeonVar_list[12] as Boolean? val dohSettings = pigeonVar_list[13] as DohSettings? - return GeckoEngineSettings(javascriptEnabled, trackingProtectionPolicy, httpsOnlyMode, globalPrivacyControlEnabled, preferredColorScheme, cookieBannerHandlingMode, cookieBannerHandlingModePrivateBrowsing, cookieBannerHandlingGlobalRules, cookieBannerHandlingGlobalRulesSubFrames, webContentIsolationStrategy, userAgent, contentBlocking, enterpriseRootsEnabled, dohSettings) + val fingerprintingProtectionOverrides = pigeonVar_list[14] as String? + return GeckoEngineSettings(javascriptEnabled, trackingProtectionPolicy, httpsOnlyMode, globalPrivacyControlEnabled, preferredColorScheme, cookieBannerHandlingMode, cookieBannerHandlingModePrivateBrowsing, cookieBannerHandlingGlobalRules, cookieBannerHandlingGlobalRulesSubFrames, webContentIsolationStrategy, userAgent, contentBlocking, enterpriseRootsEnabled, dohSettings, fingerprintingProtectionOverrides) } } fun toList(): List { @@ -1715,6 +1717,7 @@ data class GeckoEngineSettings ( contentBlocking, enterpriseRootsEnabled, dohSettings, + fingerprintingProtectionOverrides, ) } override fun equals(other: Any?): Boolean { diff --git a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart index 6e2cc91a..4767c0a1 100644 --- a/packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart +++ b/packages/flutter_mozilla_components/lib/src/domain/services/gecko_engine_settings.dart @@ -94,4 +94,10 @@ class GeckoEngineSettingsService { Future dohSettings(DohSettings state) { return _api.updateRuntimeSettings(GeckoEngineSettings(dohSettings: state)); } + + Future fingerprintingProtectionOverrides(String? state) { + return _api.updateRuntimeSettings( + GeckoEngineSettings(fingerprintingProtectionOverrides: state), + ); + } } diff --git a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart index 5da76ec2..0185d6bd 100644 --- a/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart +++ b/packages/flutter_mozilla_components/lib/src/pigeons/gecko.g.dart @@ -2043,6 +2043,7 @@ class GeckoEngineSettings { this.contentBlocking, this.enterpriseRootsEnabled, this.dohSettings, + this.fingerprintingProtectionOverrides, }); bool? javascriptEnabled; @@ -2073,6 +2074,8 @@ class GeckoEngineSettings { DohSettings? dohSettings; + String? fingerprintingProtectionOverrides; + List _toList() { return [ javascriptEnabled, @@ -2089,6 +2092,7 @@ class GeckoEngineSettings { contentBlocking, enterpriseRootsEnabled, dohSettings, + fingerprintingProtectionOverrides, ]; } @@ -2112,6 +2116,7 @@ class GeckoEngineSettings { contentBlocking: result[11] as ContentBlocking?, enterpriseRootsEnabled: result[12] as bool?, dohSettings: result[13] as DohSettings?, + fingerprintingProtectionOverrides: result[14] as String?, ); } diff --git a/packages/flutter_mozilla_components/pigeons/gecko.dart b/packages/flutter_mozilla_components/pigeons/gecko.dart index 50e6b865..fe7e782f 100644 --- a/packages/flutter_mozilla_components/pigeons/gecko.dart +++ b/packages/flutter_mozilla_components/pigeons/gecko.dart @@ -666,6 +666,7 @@ class GeckoEngineSettings { final ContentBlocking? contentBlocking; final bool? enterpriseRootsEnabled; final DohSettings? dohSettings; + final String? fingerprintingProtectionOverrides; GeckoEngineSettings( this.javascriptEnabled, @@ -682,6 +683,7 @@ class GeckoEngineSettings { this.contentBlocking, this.enterpriseRootsEnabled, this.dohSettings, + this.fingerprintingProtectionOverrides, ); }