From a0407330a54cf43265f78f1fd8e4cf6415dc4b4c Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Mon, 17 Nov 2025 09:11:07 +0100 Subject: [PATCH 01/44] APK build job (#158) --- .github/workflows/build.yaml | 57 +++++++++++++++++++++++++++++++++- .github/workflows/release.yaml | 1 + 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 45bbb5c..ec1c8ff 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -139,8 +139,63 @@ jobs: path: "client/build/app/outputs/bundle/release/app-release.aab" retention-days: 2 + build-android-apk: + runs-on: [self-hosted, macOS] + env: + ANDROID_HOME: /Users/admin/Library/Android/sdk + ANDROID_SDK_ROOT: /Users/admin/Library/Android/sdk + defaults: + run: + working-directory: ./client + steps: + - uses: actions/checkout@v4 + + - name: Set up Java + uses: actions/setup-java@v3 + with: + distribution: "temurin" + java-version: "17" + + - name: Setup flutter + uses: subosito/flutter-action@v2 + with: + channel: stable + flutter-version: 3.32.7 + + - name: Install Android SDK components + run: | + $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install 'build-tools;29.0.3' + - name: Accept licenses + run: yes | flutter doctor --android-licenses + + - name: Clean flutter + run: flutter clean + + - name: Install deps + run: flutter pub get + + - name: Build Android APK + run: flutter build apk --release --build-number=${{ github.run_number }} + + - name: Sign APK + uses: r0adkll/sign-android-release@v1 + with: + releaseDirectory: client/build/app/outputs/flutter-apk + signingKeyBase64: "${{ secrets.ANDROID_SIGNING_KEY_BASE64 }}" + alias: "${{ secrets.ANDROID_SIGNING_KEY_ALIAS }}" + keyStorePassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" + keyPassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" + + - name: Upload Android Artifact + uses: actions/upload-artifact@v4 + if: startsWith(github.ref, 'refs/tags/') + with: + name: android-app-apk + path: "client/build/app/outputs/flutter-apk/app-release.apk" + retention-days: 2 + release: - needs: [build-ios, build-android] + needs: [build-ios, build-android, build-android-apk] # Create release only if CI was triggered by a tag. if: startsWith(github.ref, 'refs/tags/') uses: ./.github/workflows/release.yaml diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 410f794..7d41668 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -28,6 +28,7 @@ jobs: files: | ./artifacts/Defguard.ipa ./artifacts/app-release.aab + ./artifacts/app-release.apk create-sbom: needs: [create-release] From c01da526e7bfb48890d3929a930ec348f2703eb8 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Fri, 21 Nov 2025 09:58:14 +0100 Subject: [PATCH 02/44] Implement "force all traffic" enterprise setting (#159) Related issue: https://github.com/DefGuard/defguard/issues/880 Adds "force all traffic" option to enterprise settings. When selected, all clients are forced to route all traffic via the vpn. --- client/lib/data/db/database.dart | 5 +- client/lib/data/db/database.g.dart | 145 ++++++++++-------- client/lib/data/db/enums.dart | 31 ++++ client/lib/data/proxy/enrollment.dart | 13 +- client/lib/data/proxy/enrollment.g.dart | 14 ++ client/lib/enterprise/config_update.dart | 3 +- .../screens/name_device_screen.dart | 2 +- .../screens/instance/instance_screen.dart | 27 ++-- .../instance/services/tunnel_service.dart | 6 +- .../widgets/routing_method_dialog.dart | 2 + flake.lock | 6 +- 11 files changed, 164 insertions(+), 90 deletions(-) diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index 5c34f33..4e1a9b3 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -28,8 +28,9 @@ class DefguardInstances extends Table with AutoIncrementingPrimaryKey { TextColumn get poolingToken => text()(); - @JsonKey('disable_all_traffic') - BoolColumn get disableAllTraffic => boolean()(); + @JsonKey('client_traffic_policy') + IntColumn get clientTrafficPolicy => + integer().map(const ClientTrafficPolicyConverter())(); @JsonKey('enterprise_enabled') BoolColumn get enterpriseEnabled => boolean()(); diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index 2fca15c..febbb7a 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -93,20 +93,18 @@ class $DefguardInstancesTable extends DefguardInstances type: DriftSqlType.string, requiredDuringInsert: true, ); - static const VerificationMeta _disableAllTrafficMeta = const VerificationMeta( - 'disableAllTraffic', - ); @override - late final GeneratedColumn disableAllTraffic = GeneratedColumn( - 'disable_all_traffic', - aliasedName, - false, - type: DriftSqlType.bool, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'CHECK ("disable_all_traffic" IN (0, 1))', - ), - ); + late final GeneratedColumnWithTypeConverter + clientTrafficPolicy = + GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ).withConverter( + $DefguardInstancesTable.$converterclientTrafficPolicy, + ); static const VerificationMeta _enterpriseEnabledMeta = const VerificationMeta( 'enterpriseEnabled', ); @@ -165,7 +163,7 @@ class $DefguardInstancesTable extends DefguardInstances proxyUrl, username, poolingToken, - disableAllTraffic, + clientTrafficPolicy, enterpriseEnabled, pubKey, privateKey, @@ -245,17 +243,6 @@ class $DefguardInstancesTable extends DefguardInstances } else if (isInserting) { context.missing(_poolingTokenMeta); } - if (data.containsKey('disable_all_traffic')) { - context.handle( - _disableAllTrafficMeta, - disableAllTraffic.isAcceptableOrUnknown( - data['disable_all_traffic']!, - _disableAllTrafficMeta, - ), - ); - } else if (isInserting) { - context.missing(_disableAllTrafficMeta); - } if (data.containsKey('enterprise_enabled')) { context.handle( _enterpriseEnabledMeta, @@ -335,10 +322,13 @@ class $DefguardInstancesTable extends DefguardInstances DriftSqlType.string, data['${effectivePrefix}pooling_token'], )!, - disableAllTraffic: attachedDatabase.typeMapping.read( - DriftSqlType.bool, - data['${effectivePrefix}disable_all_traffic'], - )!, + clientTrafficPolicy: $DefguardInstancesTable.$converterclientTrafficPolicy + .fromSql( + attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}client_traffic_policy'], + )!, + ), enterpriseEnabled: attachedDatabase.typeMapping.read( DriftSqlType.bool, data['${effectivePrefix}enterprise_enabled'], @@ -362,6 +352,9 @@ class $DefguardInstancesTable extends DefguardInstances $DefguardInstancesTable createAlias(String alias) { return $DefguardInstancesTable(attachedDatabase, alias); } + + static TypeConverter $converterclientTrafficPolicy = + const ClientTrafficPolicyConverter(); } class DefguardInstance extends DataClass @@ -374,7 +367,7 @@ class DefguardInstance extends DataClass final String proxyUrl; final String username; final String poolingToken; - final bool disableAllTraffic; + final ClientTrafficPolicy clientTrafficPolicy; final bool enterpriseEnabled; final String pubKey; final String privateKey; @@ -388,7 +381,7 @@ class DefguardInstance extends DataClass required this.proxyUrl, required this.username, required this.poolingToken, - required this.disableAllTraffic, + required this.clientTrafficPolicy, required this.enterpriseEnabled, required this.pubKey, required this.privateKey, @@ -405,7 +398,13 @@ class DefguardInstance extends DataClass map['proxy_url'] = Variable(proxyUrl); map['username'] = Variable(username); map['pooling_token'] = Variable(poolingToken); - map['disable_all_traffic'] = Variable(disableAllTraffic); + { + map['client_traffic_policy'] = Variable( + $DefguardInstancesTable.$converterclientTrafficPolicy.toSql( + clientTrafficPolicy, + ), + ); + } map['enterprise_enabled'] = Variable(enterpriseEnabled); map['pub_key'] = Variable(pubKey); map['private_key'] = Variable(privateKey); @@ -423,7 +422,7 @@ class DefguardInstance extends DataClass proxyUrl: Value(proxyUrl), username: Value(username), poolingToken: Value(poolingToken), - disableAllTraffic: Value(disableAllTraffic), + clientTrafficPolicy: Value(clientTrafficPolicy), enterpriseEnabled: Value(enterpriseEnabled), pubKey: Value(pubKey), privateKey: Value(privateKey), @@ -445,7 +444,9 @@ class DefguardInstance extends DataClass proxyUrl: serializer.fromJson(json['proxy_url']), username: serializer.fromJson(json['username']), poolingToken: serializer.fromJson(json['poolingToken']), - disableAllTraffic: serializer.fromJson(json['disable_all_traffic']), + clientTrafficPolicy: serializer.fromJson( + json['client_traffic_policy'], + ), enterpriseEnabled: serializer.fromJson(json['enterprise_enabled']), pubKey: serializer.fromJson(json['pubKey']), privateKey: serializer.fromJson(json['privateKey']), @@ -464,7 +465,9 @@ class DefguardInstance extends DataClass 'proxy_url': serializer.toJson(proxyUrl), 'username': serializer.toJson(username), 'poolingToken': serializer.toJson(poolingToken), - 'disable_all_traffic': serializer.toJson(disableAllTraffic), + 'client_traffic_policy': serializer.toJson( + clientTrafficPolicy, + ), 'enterprise_enabled': serializer.toJson(enterpriseEnabled), 'pubKey': serializer.toJson(pubKey), 'privateKey': serializer.toJson(privateKey), @@ -481,7 +484,7 @@ class DefguardInstance extends DataClass String? proxyUrl, String? username, String? poolingToken, - bool? disableAllTraffic, + ClientTrafficPolicy? clientTrafficPolicy, bool? enterpriseEnabled, String? pubKey, String? privateKey, @@ -495,7 +498,7 @@ class DefguardInstance extends DataClass proxyUrl: proxyUrl ?? this.proxyUrl, username: username ?? this.username, poolingToken: poolingToken ?? this.poolingToken, - disableAllTraffic: disableAllTraffic ?? this.disableAllTraffic, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, pubKey: pubKey ?? this.pubKey, privateKey: privateKey ?? this.privateKey, @@ -513,9 +516,9 @@ class DefguardInstance extends DataClass poolingToken: data.poolingToken.present ? data.poolingToken.value : this.poolingToken, - disableAllTraffic: data.disableAllTraffic.present - ? data.disableAllTraffic.value - : this.disableAllTraffic, + clientTrafficPolicy: data.clientTrafficPolicy.present + ? data.clientTrafficPolicy.value + : this.clientTrafficPolicy, enterpriseEnabled: data.enterpriseEnabled.present ? data.enterpriseEnabled.value : this.enterpriseEnabled, @@ -540,7 +543,7 @@ class DefguardInstance extends DataClass ..write('proxyUrl: $proxyUrl, ') ..write('username: $username, ') ..write('poolingToken: $poolingToken, ') - ..write('disableAllTraffic: $disableAllTraffic, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') ..write('enterpriseEnabled: $enterpriseEnabled, ') ..write('pubKey: $pubKey, ') ..write('privateKey: $privateKey, ') @@ -559,7 +562,7 @@ class DefguardInstance extends DataClass proxyUrl, username, poolingToken, - disableAllTraffic, + clientTrafficPolicy, enterpriseEnabled, pubKey, privateKey, @@ -577,7 +580,7 @@ class DefguardInstance extends DataClass other.proxyUrl == this.proxyUrl && other.username == this.username && other.poolingToken == this.poolingToken && - other.disableAllTraffic == this.disableAllTraffic && + other.clientTrafficPolicy == this.clientTrafficPolicy && other.enterpriseEnabled == this.enterpriseEnabled && other.pubKey == this.pubKey && other.privateKey == this.privateKey && @@ -593,7 +596,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { final Value proxyUrl; final Value username; final Value poolingToken; - final Value disableAllTraffic; + final Value clientTrafficPolicy; final Value enterpriseEnabled; final Value pubKey; final Value privateKey; @@ -607,7 +610,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { this.proxyUrl = const Value.absent(), this.username = const Value.absent(), this.poolingToken = const Value.absent(), - this.disableAllTraffic = const Value.absent(), + this.clientTrafficPolicy = const Value.absent(), this.enterpriseEnabled = const Value.absent(), this.pubKey = const Value.absent(), this.privateKey = const Value.absent(), @@ -622,7 +625,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { required String proxyUrl, required String username, required String poolingToken, - required bool disableAllTraffic, + required ClientTrafficPolicy clientTrafficPolicy, required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -634,7 +637,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { proxyUrl = Value(proxyUrl), username = Value(username), poolingToken = Value(poolingToken), - disableAllTraffic = Value(disableAllTraffic), + clientTrafficPolicy = Value(clientTrafficPolicy), enterpriseEnabled = Value(enterpriseEnabled), pubKey = Value(pubKey), privateKey = Value(privateKey), @@ -648,7 +651,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { Expression? proxyUrl, Expression? username, Expression? poolingToken, - Expression? disableAllTraffic, + Expression? clientTrafficPolicy, Expression? enterpriseEnabled, Expression? pubKey, Expression? privateKey, @@ -663,7 +666,8 @@ class DefguardInstancesCompanion extends UpdateCompanion { if (proxyUrl != null) 'proxy_url': proxyUrl, if (username != null) 'username': username, if (poolingToken != null) 'pooling_token': poolingToken, - if (disableAllTraffic != null) 'disable_all_traffic': disableAllTraffic, + if (clientTrafficPolicy != null) + 'client_traffic_policy': clientTrafficPolicy, if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, if (pubKey != null) 'pub_key': pubKey, if (privateKey != null) 'private_key': privateKey, @@ -680,7 +684,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { Value? proxyUrl, Value? username, Value? poolingToken, - Value? disableAllTraffic, + Value? clientTrafficPolicy, Value? enterpriseEnabled, Value? pubKey, Value? privateKey, @@ -695,7 +699,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { proxyUrl: proxyUrl ?? this.proxyUrl, username: username ?? this.username, poolingToken: poolingToken ?? this.poolingToken, - disableAllTraffic: disableAllTraffic ?? this.disableAllTraffic, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, pubKey: pubKey ?? this.pubKey, privateKey: privateKey ?? this.privateKey, @@ -730,8 +734,12 @@ class DefguardInstancesCompanion extends UpdateCompanion { if (poolingToken.present) { map['pooling_token'] = Variable(poolingToken.value); } - if (disableAllTraffic.present) { - map['disable_all_traffic'] = Variable(disableAllTraffic.value); + if (clientTrafficPolicy.present) { + map['client_traffic_policy'] = Variable( + $DefguardInstancesTable.$converterclientTrafficPolicy.toSql( + clientTrafficPolicy.value, + ), + ); } if (enterpriseEnabled.present) { map['enterprise_enabled'] = Variable(enterpriseEnabled.value); @@ -759,7 +767,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { ..write('proxyUrl: $proxyUrl, ') ..write('username: $username, ') ..write('poolingToken: $poolingToken, ') - ..write('disableAllTraffic: $disableAllTraffic, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') ..write('enterpriseEnabled: $enterpriseEnabled, ') ..write('pubKey: $pubKey, ') ..write('privateKey: $privateKey, ') @@ -1632,7 +1640,7 @@ typedef $$DefguardInstancesTableCreateCompanionBuilder = required String proxyUrl, required String username, required String poolingToken, - required bool disableAllTraffic, + required ClientTrafficPolicy clientTrafficPolicy, required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -1648,7 +1656,7 @@ typedef $$DefguardInstancesTableUpdateCompanionBuilder = Value proxyUrl, Value username, Value poolingToken, - Value disableAllTraffic, + Value clientTrafficPolicy, Value enterpriseEnabled, Value pubKey, Value privateKey, @@ -1739,9 +1747,10 @@ class $$DefguardInstancesTableFilterComposer builder: (column) => ColumnFilters(column), ); - ColumnFilters get disableAllTraffic => $composableBuilder( - column: $table.disableAllTraffic, - builder: (column) => ColumnFilters(column), + ColumnWithTypeConverterFilters + get clientTrafficPolicy => $composableBuilder( + column: $table.clientTrafficPolicy, + builder: (column) => ColumnWithTypeConverterFilters(column), ); ColumnFilters get enterpriseEnabled => $composableBuilder( @@ -1839,8 +1848,8 @@ class $$DefguardInstancesTableOrderingComposer builder: (column) => ColumnOrderings(column), ); - ColumnOrderings get disableAllTraffic => $composableBuilder( - column: $table.disableAllTraffic, + ColumnOrderings get clientTrafficPolicy => $composableBuilder( + column: $table.clientTrafficPolicy, builder: (column) => ColumnOrderings(column), ); @@ -1900,8 +1909,9 @@ class $$DefguardInstancesTableAnnotationComposer builder: (column) => column, ); - GeneratedColumn get disableAllTraffic => $composableBuilder( - column: $table.disableAllTraffic, + GeneratedColumnWithTypeConverter + get clientTrafficPolicy => $composableBuilder( + column: $table.clientTrafficPolicy, builder: (column) => column, ); @@ -1990,7 +2000,8 @@ class $$DefguardInstancesTableTableManager Value proxyUrl = const Value.absent(), Value username = const Value.absent(), Value poolingToken = const Value.absent(), - Value disableAllTraffic = const Value.absent(), + Value clientTrafficPolicy = + const Value.absent(), Value enterpriseEnabled = const Value.absent(), Value pubKey = const Value.absent(), Value privateKey = const Value.absent(), @@ -2004,7 +2015,7 @@ class $$DefguardInstancesTableTableManager proxyUrl: proxyUrl, username: username, poolingToken: poolingToken, - disableAllTraffic: disableAllTraffic, + clientTrafficPolicy: clientTrafficPolicy, enterpriseEnabled: enterpriseEnabled, pubKey: pubKey, privateKey: privateKey, @@ -2020,7 +2031,7 @@ class $$DefguardInstancesTableTableManager required String proxyUrl, required String username, required String poolingToken, - required bool disableAllTraffic, + required ClientTrafficPolicy clientTrafficPolicy, required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -2034,7 +2045,7 @@ class $$DefguardInstancesTableTableManager proxyUrl: proxyUrl, username: username, poolingToken: poolingToken, - disableAllTraffic: disableAllTraffic, + clientTrafficPolicy: clientTrafficPolicy, enterpriseEnabled: enterpriseEnabled, pubKey: pubKey, privateKey: privateKey, diff --git a/client/lib/data/db/enums.dart b/client/lib/data/db/enums.dart index 021b076..36b4c07 100644 --- a/client/lib/data/db/enums.dart +++ b/client/lib/data/db/enums.dart @@ -83,3 +83,34 @@ class LocationMfaModeConverter extends TypeConverter { return value.value; } } + +@j.JsonEnum() +enum ClientTrafficPolicy { + @j.JsonValue(0) + none(0), + @j.JsonValue(1) + disableAllTraffic(1), + @j.JsonValue(2) + forceAllTraffic(2); + + final int value; + + const ClientTrafficPolicy(this.value); + + static ClientTrafficPolicy fromValue(int value) => + ClientTrafficPolicy.values.firstWhere((e) => e.value == value); +} + +class ClientTrafficPolicyConverter extends TypeConverter { + const ClientTrafficPolicyConverter(); + + @override + ClientTrafficPolicy fromSql(int fromDb) { + return ClientTrafficPolicy.fromValue(fromDb); + } + + @override + int toSql(ClientTrafficPolicy value) { + return value.value; + } +} diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index cda111f..0c81171 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -252,6 +252,7 @@ class InstanceInfo { final String username; final bool enterpriseEnabled; final bool disableAllTraffic; + final ClientTrafficPolicy? clientTrafficPolicy; const InstanceInfo({ required this.id, @@ -261,6 +262,7 @@ class InstanceInfo { required this.username, required this.enterpriseEnabled, required this.disableAllTraffic, + required this.clientTrafficPolicy, }); factory InstanceInfo.fromJson(Map json) => @@ -275,7 +277,7 @@ class InstanceInfo { proxyUrl == other.proxyUrl && username == other.username && enterpriseEnabled == other.enterpriseEnabled && - disableAllTraffic == other.disableAllTraffic; + getPolicy() == other.clientTrafficPolicy; } DefguardInstancesCompanion toCompanion({DefguardInstance? instance}) { @@ -290,10 +292,17 @@ class InstanceInfo { proxyUrl: d.Value(proxyUrl), username: d.Value(username), enterpriseEnabled: d.Value(enterpriseEnabled), - disableAllTraffic: d.Value(disableAllTraffic), + clientTrafficPolicy: d.Value(getPolicy()), uuid: d.Value(id), ); } + + /// Retrieves `ClientTrafficPolicy` while ensuring backwards compatibility + ClientTrafficPolicy getPolicy() { + return clientTrafficPolicy ?? (disableAllTraffic + ? ClientTrafficPolicy.disableAllTraffic + : ClientTrafficPolicy.none); + } } @JsonSerializable() diff --git a/client/lib/data/proxy/enrollment.g.dart b/client/lib/data/proxy/enrollment.g.dart index 8d83288..df153ce 100644 --- a/client/lib/data/proxy/enrollment.g.dart +++ b/client/lib/data/proxy/enrollment.g.dart @@ -389,6 +389,10 @@ InstanceInfo _$InstanceInfoFromJson(Map json) => 'disable_all_traffic', (v) => v as bool, ), + clientTrafficPolicy: $checkedConvert( + 'client_traffic_policy', + (v) => $enumDecodeNullable(_$ClientTrafficPolicyEnumMap, v), + ), ); return val; }, @@ -396,6 +400,7 @@ InstanceInfo _$InstanceInfoFromJson(Map json) => 'proxyUrl': 'proxy_url', 'enterpriseEnabled': 'enterprise_enabled', 'disableAllTraffic': 'disable_all_traffic', + 'clientTrafficPolicy': 'client_traffic_policy', }, ); @@ -407,6 +412,7 @@ const _$InstanceInfoFieldMap = { 'username': 'username', 'enterpriseEnabled': 'enterprise_enabled', 'disableAllTraffic': 'disable_all_traffic', + 'clientTrafficPolicy': 'client_traffic_policy', }; Map _$InstanceInfoToJson(InstanceInfo instance) => @@ -418,8 +424,16 @@ Map _$InstanceInfoToJson(InstanceInfo instance) => 'username': instance.username, 'enterprise_enabled': instance.enterpriseEnabled, 'disable_all_traffic': instance.disableAllTraffic, + 'client_traffic_policy': + _$ClientTrafficPolicyEnumMap[instance.clientTrafficPolicy], }; +const _$ClientTrafficPolicyEnumMap = { + ClientTrafficPolicy.none: 0, + ClientTrafficPolicy.disableAllTraffic: 1, + ClientTrafficPolicy.forceAllTraffic: 2, +}; + AppInfoResponse _$AppInfoResponseFromJson(Map json) => $checkedCreate('AppInfoResponse', json, ($checkedConvert) { final val = AppInfoResponse( diff --git a/client/lib/enterprise/config_update.dart b/client/lib/enterprise/config_update.dart index 91c7107..f777891 100644 --- a/client/lib/enterprise/config_update.dart +++ b/client/lib/enterprise/config_update.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:mobile/data/db/database.dart'; +import 'package:mobile/data/db/enums.dart'; import 'package:mobile/open/api.dart'; import 'package:mobile/open/widgets/toaster/toast_manager.dart'; import 'package:mobile/utils/update_instance.dart'; @@ -86,7 +87,7 @@ class ConfigurationUpdater extends HookConsumerWidget { // instance lost it's enterprise status if (responseStatus == 402) { final instanceUpdate = instance.copyWith( - disableAllTraffic: false, + clientTrafficPolicy: ClientTrafficPolicy.none, enterpriseEnabled: false, ); await db.managers.defguardInstances.replace(instanceUpdate); diff --git a/client/lib/open/screens/add_instance/screens/name_device_screen.dart b/client/lib/open/screens/add_instance/screens/name_device_screen.dart index a3ce5ce..c80a626 100644 --- a/client/lib/open/screens/add_instance/screens/name_device_screen.dart +++ b/client/lib/open/screens/add_instance/screens/name_device_screen.dart @@ -58,7 +58,7 @@ class NameDeviceScreen extends HookConsumerWidget { uuid: createResponse.instance.id, deviceId: createResponse.device.id, enterpriseEnabled: createResponse.instance.enterpriseEnabled, - disableAllTraffic: createResponse.instance.disableAllTraffic, + clientTrafficPolicy: createResponse.instance.getPolicy(), proxyUrl: createResponse.instance.proxyUrl, url: screenData.startResponse.instance.url, username: createResponse.instance.username, diff --git a/client/lib/open/screens/instance/instance_screen.dart b/client/lib/open/screens/instance/instance_screen.dart index 1197462..a725ec5 100644 --- a/client/lib/open/screens/instance/instance_screen.dart +++ b/client/lib/open/screens/instance/instance_screen.dart @@ -415,19 +415,20 @@ class _LocationItem extends HookConsumerWidget { ); }, ), - if (!instance.disableAllTraffic) - DgMenuItem( - text: "Select Traffic Routing", - onTap: () { - showDialog( - context: context, - builder: (_) => RoutingMethodDialog( - location: location, - intention: RoutingMethodDialogIntention.save, - ), - ); - }, - ), + if (instance.clientTrafficPolicy == ClientTrafficPolicy.none) + DgMenuItem( + text: "Select Traffic Routing", + onTap: () { + showDialog( + context: context, + builder: (_) => RoutingMethodDialog( + location: location, + intention: RoutingMethodDialogIntention.save, + clientTrafficPolicy: instance.clientTrafficPolicy, + ), + ); + }, + ), ]; }, [location, instance]); diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index e156917..a52a774 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -35,9 +35,12 @@ class TunnelService { // handle traffic type selection if necessary late RoutingMethod trafficMethod; - if (instance.disableAllTraffic) { + if (instance.clientTrafficPolicy == ClientTrafficPolicy.disableAllTraffic) { // instance enforces predefined traffic trafficMethod = RoutingMethod.predefined; + } else if (instance.clientTrafficPolicy == ClientTrafficPolicy.forceAllTraffic) { + // instance enforces all traffic + trafficMethod = RoutingMethod.all; } else { // instance allows traffic type selection - use stored method or display selection dialog if (location.trafficMethod != null) { @@ -52,6 +55,7 @@ class TunnelService { builder: (_) => RoutingMethodDialog( location: location, intention: dialogIntention, + clientTrafficPolicy: instance.clientTrafficPolicy, ), ); // smth went wrong or user canceled the operation diff --git a/client/lib/open/screens/instance/widgets/routing_method_dialog.dart b/client/lib/open/screens/instance/widgets/routing_method_dialog.dart index d2cc29b..e64736e 100644 --- a/client/lib/open/screens/instance/widgets/routing_method_dialog.dart +++ b/client/lib/open/screens/instance/widgets/routing_method_dialog.dart @@ -24,12 +24,14 @@ enum RoutingMethodDialogIntention { connect, save, next } class RoutingMethodDialog extends HookConsumerWidget { final Location location; + final ClientTrafficPolicy clientTrafficPolicy; final RoutingMethodDialogIntention intention; const RoutingMethodDialog({ super.key, required this.location, required this.intention, + required this.clientTrafficPolicy, }); String _getSubmitText() { diff --git a/flake.lock b/flake.lock index 5ca09a9..333e23c 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1750776420, - "narHash": "sha256-/CG+w0o0oJ5itVklOoLbdn2dGB0wbZVOoDm4np6w09A=", + "lastModified": 1763421233, + "narHash": "sha256-Stk9ZYRkGrnnpyJ4eqt9eQtdFWRRIvMxpNRf4sIegnw=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "30a61f056ac492e3b7cdcb69c1e6abdcf00e39cf", + "rev": "89c2b2330e733d6cdb5eae7b899326930c2c0648", "type": "github" }, "original": { From cbefcbdefca70472e6af886f189ee22cf1faf6d9 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 24 Nov 2025 07:08:39 +0100 Subject: [PATCH 03/44] Add DB migrations (#160) --- client/README.md | 28 +- client/build.yaml | 4 + .../defguard/drift_schema_v1.json | 1 + .../defguard/drift_schema_v2.json | 1 + client/lib/data/db/database.dart | 26 +- client/lib/data/db/database.g.dart | 11 +- client/lib/data/db/database.steps.dart | 335 +++++ client/lib/data/proxy/enrollment.dart | 2 + .../screens/name_device_screen.dart | 2 +- client/pubspec.lock | 16 +- .../test/drift/defguard/generated/schema.dart | 23 + .../drift/defguard/generated/schema_v1.dart | 1275 +++++++++++++++++ .../drift/defguard/generated/schema_v2.dart | 1275 +++++++++++++++++ .../test/drift/defguard/migration_test.dart | 79 + flake.nix | 3 +- 15 files changed, 3059 insertions(+), 22 deletions(-) create mode 100644 client/drift_schemas/defguard/drift_schema_v1.json create mode 100644 client/drift_schemas/defguard/drift_schema_v2.json create mode 100644 client/lib/data/db/database.steps.dart create mode 100644 client/test/drift/defguard/generated/schema.dart create mode 100644 client/test/drift/defguard/generated/schema_v1.dart create mode 100644 client/test/drift/defguard/generated/schema_v2.dart create mode 100644 client/test/drift/defguard/migration_test.dart diff --git a/client/README.md b/client/README.md index edc1edf..380db3c 100644 --- a/client/README.md +++ b/client/README.md @@ -1,10 +1,8 @@ -# mobile_client - -Defguard mobile client +# Defguard mobile client ## Getting Started -This project is a starting point for a Flutter application. +This is a Flutter application. A few resources to get you started if this is your first Flutter project: @@ -14,3 +12,25 @@ A few resources to get you started if this is your first Flutter project: For help getting started with Flutter development, view the [online documentation](https://docs.flutter.dev/), which offers tutorials, samples, guidance on mobile development, and a full API reference. + +## Database and migrations + +We use [drift](https://drift.simonbinder.eu/) persistence library with [SQLite](https://sqlite.org/index.html) +database. The model is defined in `lib/data/db/database.dart`. + +When changing the schema: + +1. Make changes to the model in `database.dart` file. +2. Bump schema version in `class AppDatabase`. +3. Generate migrations and tests: + +```bash +flutter pub run drift_dev make-migrations +``` + +4. Add your migration step to `AppDatabase` `onUpgrade`. +5. Run the tests: + +```bash +flutter test +``` diff --git a/client/build.yaml b/client/build.yaml index bcfab5a..7fc2687 100644 --- a/client/build.yaml +++ b/client/build.yaml @@ -9,3 +9,7 @@ targets: create_factory: true create_to_json: true create_field_map: true + drift_dev: + options: + databases: + defguard: lib/data/db/database.dart diff --git a/client/drift_schemas/defguard/drift_schema_v1.json b/client/drift_schemas/defguard/drift_schema_v1.json new file mode 100644 index 0000000..df952fb --- /dev/null +++ b/client/drift_schemas/defguard/drift_schema_v1.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pooling_token","getter_name":"poolingToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"disable_all_traffic","getter_name":"disableAllTraffic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"disable_all_traffic\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"disable_all_traffic\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/client/drift_schemas/defguard/drift_schema_v2.json b/client/drift_schemas/defguard/drift_schema_v2.json new file mode 100644 index 0000000..2a2bb13 --- /dev/null +++ b/client/drift_schemas/defguard/drift_schema_v2.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pooling_token","getter_name":"poolingToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"client_traffic_policy","getter_name":"clientTrafficPolicy","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ClientTrafficPolicyConverter()","dart_type_name":"ClientTrafficPolicy"}},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index 4e1a9b3..363f784 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -1,6 +1,7 @@ import "package:drift/drift.dart"; import "package:drift_flutter/drift_flutter.dart"; import "package:flutter_riverpod/flutter_riverpod.dart"; +import "package:mobile/data/db/database.steps.dart"; import "package:mobile/data/db/enums.dart"; import "package:path_provider/path_provider.dart"; import "package:riverpod_annotation/riverpod_annotation.dart"; @@ -29,8 +30,9 @@ class DefguardInstances extends Table with AutoIncrementingPrimaryKey { TextColumn get poolingToken => text()(); @JsonKey('client_traffic_policy') - IntColumn get clientTrafficPolicy => - integer().map(const ClientTrafficPolicyConverter())(); + IntColumn get clientTrafficPolicy => integer() + .withDefault(const Constant(0)) + .map(const ClientTrafficPolicyConverter())(); @JsonKey('enterprise_enabled') BoolColumn get enterpriseEnabled => boolean()(); @@ -96,7 +98,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 1; + int get schemaVersion => 2; @override MigrationStrategy get migration { @@ -104,6 +106,24 @@ class AppDatabase extends _$AppDatabase { beforeOpen: (details) async { await customStatement('PRAGMA foreign_keys = ON'); }, + onUpgrade: stepByStep( + from1To2: (m, schema) async { + // 1. Add the new column manually. + // This ensures Drift doesn't trigger a "Recreate Table" that might + // drop 'disable_all_traffic' before we are done with it. + await customStatement( + 'ALTER TABLE defguard_instances ADD COLUMN client_traffic_policy INTEGER NOT NULL DEFAULT 0', + ); + // 2. Update values derived from the old column + await customStatement(''' + UPDATE defguard_instances + SET client_traffic_policy = + CASE WHEN disable_all_traffic = 1 THEN 1 ELSE 0 END; + '''); + // 3. Drop old "disable_all_traffic" column + await m.dropColumn(defguardInstances, "disable_all_traffic"); + }, + ), ); } diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index febbb7a..2c951d0 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -101,7 +101,8 @@ class $DefguardInstancesTable extends DefguardInstances aliasedName, false, type: DriftSqlType.int, - requiredDuringInsert: true, + requiredDuringInsert: false, + defaultValue: const Constant(0), ).withConverter( $DefguardInstancesTable.$converterclientTrafficPolicy, ); @@ -625,7 +626,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { required String proxyUrl, required String username, required String poolingToken, - required ClientTrafficPolicy clientTrafficPolicy, + this.clientTrafficPolicy = const Value.absent(), required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -637,7 +638,6 @@ class DefguardInstancesCompanion extends UpdateCompanion { proxyUrl = Value(proxyUrl), username = Value(username), poolingToken = Value(poolingToken), - clientTrafficPolicy = Value(clientTrafficPolicy), enterpriseEnabled = Value(enterpriseEnabled), pubKey = Value(pubKey), privateKey = Value(privateKey), @@ -1640,7 +1640,7 @@ typedef $$DefguardInstancesTableCreateCompanionBuilder = required String proxyUrl, required String username, required String poolingToken, - required ClientTrafficPolicy clientTrafficPolicy, + Value clientTrafficPolicy, required bool enterpriseEnabled, required String pubKey, required String privateKey, @@ -2031,7 +2031,8 @@ class $$DefguardInstancesTableTableManager required String proxyUrl, required String username, required String poolingToken, - required ClientTrafficPolicy clientTrafficPolicy, + Value clientTrafficPolicy = + const Value.absent(), required bool enterpriseEnabled, required String pubKey, required String privateKey, diff --git a/client/lib/data/db/database.steps.dart b/client/lib/data/db/database.steps.dart new file mode 100644 index 0000000..c2c8203 --- /dev/null +++ b/client/lib/data/db/database.steps.dart @@ -0,0 +1,335 @@ +// dart format width=80 +import 'package:drift/internal/versioned_schema.dart' as i0; +import 'package:drift/drift.dart' as i1; +import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import + +// GENERATED BY drift_dev, DO NOT MODIFY. +final class Schema2 extends i0.VersionedSchema { + Schema2({required super.database}) : super(version: 2); + @override + late final List entities = [ + defguardInstances, + locations, + ]; + late final Shape0 defguardInstances = Shape0( + source: i0.VersionedTable( + entityName: 'defguard_instances', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape1 locations = Shape1( + source: i0.VersionedTable( + entityName: 'locations', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_13, + _column_14, + _column_1, + _column_15, + _column_10, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape0 extends i0.VersionedTable { + Shape0({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get uuid => + columnsByName['uuid']! as i1.GeneratedColumn; + i1.GeneratedColumn get url => + columnsByName['url']! as i1.GeneratedColumn; + i1.GeneratedColumn get deviceId => + columnsByName['device_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get proxyUrl => + columnsByName['proxy_url']! as i1.GeneratedColumn; + i1.GeneratedColumn get username => + columnsByName['username']! as i1.GeneratedColumn; + i1.GeneratedColumn get poolingToken => + columnsByName['pooling_token']! as i1.GeneratedColumn; + i1.GeneratedColumn get clientTrafficPolicy => + columnsByName['client_traffic_policy']! as i1.GeneratedColumn; + i1.GeneratedColumn get enterpriseEnabled => + columnsByName['enterprise_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get pubKey => + columnsByName['pub_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get privateKey => + columnsByName['private_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaKeysStored => + columnsByName['mfa_keys_stored']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_0(String aliasedName) => + i1.GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: i1.DriftSqlType.int, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); +i1.GeneratedColumn _column_1(String aliasedName) => + i1.GeneratedColumn( + 'name', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_2(String aliasedName) => + i1.GeneratedColumn( + 'uuid', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_3(String aliasedName) => + i1.GeneratedColumn( + 'url', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_4(String aliasedName) => + i1.GeneratedColumn( + 'device_id', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_5(String aliasedName) => + i1.GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_6(String aliasedName) => + i1.GeneratedColumn( + 'username', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_7(String aliasedName) => + i1.GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_8(String aliasedName) => + i1.GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultValue: const CustomExpression('0'), + ); +i1.GeneratedColumn _column_9(String aliasedName) => + i1.GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); +i1.GeneratedColumn _column_10(String aliasedName) => + i1.GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_11(String aliasedName) => + i1.GeneratedColumn( + 'private_key', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_12(String aliasedName) => + i1.GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + +class Shape1 extends i0.VersionedTable { + Shape1({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get instance => + columnsByName['instance']! as i1.GeneratedColumn; + i1.GeneratedColumn get networkId => + columnsByName['network_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get address => + columnsByName['address']! as i1.GeneratedColumn; + i1.GeneratedColumn get pubKey => + columnsByName['pub_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get endpoint => + columnsByName['endpoint']! as i1.GeneratedColumn; + i1.GeneratedColumn get allowedIps => + columnsByName['allowed_ips']! as i1.GeneratedColumn; + i1.GeneratedColumn get dns => + columnsByName['dns']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaEnabled => + columnsByName['mfa_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get trafficMethod => + columnsByName['traffic_method']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaMethod => + columnsByName['mfa_method']! as i1.GeneratedColumn; + i1.GeneratedColumn get keepAliveInterval => + columnsByName['keep_alive_interval']! as i1.GeneratedColumn; + i1.GeneratedColumn get locationMfaMode => + columnsByName['location_mfa_mode']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_13(String aliasedName) => + i1.GeneratedColumn( + 'instance', + aliasedName, + false, + type: i1.DriftSqlType.int, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); +i1.GeneratedColumn _column_14(String aliasedName) => + i1.GeneratedColumn( + 'network_id', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_15(String aliasedName) => + i1.GeneratedColumn( + 'address', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_16(String aliasedName) => + i1.GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_17(String aliasedName) => + i1.GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_18(String aliasedName) => + i1.GeneratedColumn( + 'dns', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_19(String aliasedName) => + i1.GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); +i1.GeneratedColumn _column_20(String aliasedName) => + i1.GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); +i1.GeneratedColumn _column_21(String aliasedName) => + i1.GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_22(String aliasedName) => + i1.GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: i1.DriftSqlType.int, + ); +i1.GeneratedColumn _column_23(String aliasedName) => + i1.GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: i1.DriftSqlType.int, + ); +i0.MigrationStepWithVersion migrationSteps({ + required Future Function(i1.Migrator m, Schema2 schema) from1To2, +}) { + return (currentVersion, database) async { + switch (currentVersion) { + case 1: + final schema = Schema2(database: database); + final migrator = i1.Migrator(database, schema); + await from1To2(migrator, schema); + return 2; + default: + throw ArgumentError.value('Unknown migration from $currentVersion'); + } + }; +} + +i1.OnUpgrade stepByStep({ + required Future Function(i1.Migrator m, Schema2 schema) from1To2, +}) => i0.VersionedSchema.stepByStepHelper( + step: migrationSteps(from1To2: from1To2), +); diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index 0c81171..916ca79 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -261,6 +261,8 @@ class InstanceInfo { required this.proxyUrl, required this.username, required this.enterpriseEnabled, + // deprecated, use clientTrafficPolicy instead + @Deprecated('1.6') required this.disableAllTraffic, required this.clientTrafficPolicy, }); diff --git a/client/lib/open/screens/add_instance/screens/name_device_screen.dart b/client/lib/open/screens/add_instance/screens/name_device_screen.dart index c80a626..ba583b7 100644 --- a/client/lib/open/screens/add_instance/screens/name_device_screen.dart +++ b/client/lib/open/screens/add_instance/screens/name_device_screen.dart @@ -58,7 +58,7 @@ class NameDeviceScreen extends HookConsumerWidget { uuid: createResponse.instance.id, deviceId: createResponse.device.id, enterpriseEnabled: createResponse.instance.enterpriseEnabled, - clientTrafficPolicy: createResponse.instance.getPolicy(), + clientTrafficPolicy: drift.Value(createResponse.instance.getPolicy()), proxyUrl: createResponse.instance.proxyUrl, url: screenData.startResponse.instance.url, username: createResponse.instance.username, diff --git a/client/pubspec.lock b/client/pubspec.lock index 3555290..c1b1ac5 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -884,10 +884,10 @@ packages: dependency: transitive description: name: meta - sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" url: "https://pub.dev" source: hosted - version: "1.16.0" + version: "1.17.0" mime: dependency: transitive description: @@ -1425,26 +1425,26 @@ packages: dependency: transitive description: name: test - sha256: "65e29d831719be0591f7b3b1a32a3cda258ec98c58c7b25f7b84241bc31215bb" + sha256: "75906bf273541b676716d1ca7627a17e4c4070a3a16272b7a3dc7da3b9f3f6b7" url: "https://pub.dev" source: hosted - version: "1.26.2" + version: "1.26.3" test_api: dependency: transitive description: name: test_api - sha256: "522f00f556e73044315fa4585ec3270f1808a4b186c936e612cab0b565ff1e00" + sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 url: "https://pub.dev" source: hosted - version: "0.7.6" + version: "0.7.7" test_core: dependency: transitive description: name: test_core - sha256: "80bf5a02b60af04b09e14f6fe68b921aad119493e26e490deaca5993fef1b05a" + sha256: "0cc24b5ff94b38d2ae73e1eb43cc302b77964fbf67abad1e296025b78deb53d0" url: "https://pub.dev" source: hosted - version: "0.6.11" + version: "0.6.12" timezone: dependency: transitive description: diff --git a/client/test/drift/defguard/generated/schema.dart b/client/test/drift/defguard/generated/schema.dart new file mode 100644 index 0000000..b2b7404 --- /dev/null +++ b/client/test/drift/defguard/generated/schema.dart @@ -0,0 +1,23 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; +import 'package:drift/internal/migrations.dart'; +import 'schema_v1.dart' as v1; +import 'schema_v2.dart' as v2; + +class GeneratedHelper implements SchemaInstantiationHelper { + @override + GeneratedDatabase databaseForVersion(QueryExecutor db, int version) { + switch (version) { + case 1: + return v1.DatabaseAtV1(db); + case 2: + return v2.DatabaseAtV2(db); + default: + throw MissingSchemaException(version, versions); + } + } + + static const versions = const [1, 2]; +} diff --git a/client/test/drift/defguard/generated/schema_v1.dart b/client/test/drift/defguard/generated/schema_v1.dart new file mode 100644 index 0000000..99d607b --- /dev/null +++ b/client/test/drift/defguard/generated/schema_v1.dart @@ -0,0 +1,1275 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class DefguardInstances extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + DefguardInstances(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn proxyUrl = GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn username = GeneratedColumn( + 'username', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn poolingToken = GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn disableAllTraffic = GeneratedColumn( + 'disable_all_traffic', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("disable_all_traffic" IN (0, 1))', + ), + ); + late final GeneratedColumn enterpriseEnabled = GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn privateKey = GeneratedColumn( + 'private_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn mfaKeysStored = GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + disableAllTraffic, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'defguard_instances'; + @override + Set get $primaryKey => {id}; + @override + DefguardInstancesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DefguardInstancesData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + )!, + deviceId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}device_id'], + )!, + proxyUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}proxy_url'], + )!, + username: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}username'], + )!, + poolingToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pooling_token'], + )!, + disableAllTraffic: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}disable_all_traffic'], + )!, + enterpriseEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}enterprise_enabled'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + privateKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}private_key'], + )!, + mfaKeysStored: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_keys_stored'], + )!, + ); + } + + @override + DefguardInstances createAlias(String alias) { + return DefguardInstances(attachedDatabase, alias); + } +} + +class DefguardInstancesData extends DataClass + implements Insertable { + final int id; + final String name; + final String uuid; + final String url; + final int deviceId; + final String proxyUrl; + final String username; + final String poolingToken; + final bool disableAllTraffic; + final bool enterpriseEnabled; + final String pubKey; + final String privateKey; + final bool mfaKeysStored; + const DefguardInstancesData({ + required this.id, + required this.name, + required this.uuid, + required this.url, + required this.deviceId, + required this.proxyUrl, + required this.username, + required this.poolingToken, + required this.disableAllTraffic, + required this.enterpriseEnabled, + required this.pubKey, + required this.privateKey, + required this.mfaKeysStored, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['uuid'] = Variable(uuid); + map['url'] = Variable(url); + map['device_id'] = Variable(deviceId); + map['proxy_url'] = Variable(proxyUrl); + map['username'] = Variable(username); + map['pooling_token'] = Variable(poolingToken); + map['disable_all_traffic'] = Variable(disableAllTraffic); + map['enterprise_enabled'] = Variable(enterpriseEnabled); + map['pub_key'] = Variable(pubKey); + map['private_key'] = Variable(privateKey); + map['mfa_keys_stored'] = Variable(mfaKeysStored); + return map; + } + + DefguardInstancesCompanion toCompanion(bool nullToAbsent) { + return DefguardInstancesCompanion( + id: Value(id), + name: Value(name), + uuid: Value(uuid), + url: Value(url), + deviceId: Value(deviceId), + proxyUrl: Value(proxyUrl), + username: Value(username), + poolingToken: Value(poolingToken), + disableAllTraffic: Value(disableAllTraffic), + enterpriseEnabled: Value(enterpriseEnabled), + pubKey: Value(pubKey), + privateKey: Value(privateKey), + mfaKeysStored: Value(mfaKeysStored), + ); + } + + factory DefguardInstancesData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DefguardInstancesData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + uuid: serializer.fromJson(json['uuid']), + url: serializer.fromJson(json['url']), + deviceId: serializer.fromJson(json['deviceId']), + proxyUrl: serializer.fromJson(json['proxyUrl']), + username: serializer.fromJson(json['username']), + poolingToken: serializer.fromJson(json['poolingToken']), + disableAllTraffic: serializer.fromJson(json['disableAllTraffic']), + enterpriseEnabled: serializer.fromJson(json['enterpriseEnabled']), + pubKey: serializer.fromJson(json['pubKey']), + privateKey: serializer.fromJson(json['privateKey']), + mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'uuid': serializer.toJson(uuid), + 'url': serializer.toJson(url), + 'deviceId': serializer.toJson(deviceId), + 'proxyUrl': serializer.toJson(proxyUrl), + 'username': serializer.toJson(username), + 'poolingToken': serializer.toJson(poolingToken), + 'disableAllTraffic': serializer.toJson(disableAllTraffic), + 'enterpriseEnabled': serializer.toJson(enterpriseEnabled), + 'pubKey': serializer.toJson(pubKey), + 'privateKey': serializer.toJson(privateKey), + 'mfaKeysStored': serializer.toJson(mfaKeysStored), + }; + } + + DefguardInstancesData copyWith({ + int? id, + String? name, + String? uuid, + String? url, + int? deviceId, + String? proxyUrl, + String? username, + String? poolingToken, + bool? disableAllTraffic, + bool? enterpriseEnabled, + String? pubKey, + String? privateKey, + bool? mfaKeysStored, + }) => DefguardInstancesData( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + disableAllTraffic: disableAllTraffic ?? this.disableAllTraffic, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + ); + DefguardInstancesData copyWithCompanion(DefguardInstancesCompanion data) { + return DefguardInstancesData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + uuid: data.uuid.present ? data.uuid.value : this.uuid, + url: data.url.present ? data.url.value : this.url, + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + proxyUrl: data.proxyUrl.present ? data.proxyUrl.value : this.proxyUrl, + username: data.username.present ? data.username.value : this.username, + poolingToken: data.poolingToken.present + ? data.poolingToken.value + : this.poolingToken, + disableAllTraffic: data.disableAllTraffic.present + ? data.disableAllTraffic.value + : this.disableAllTraffic, + enterpriseEnabled: data.enterpriseEnabled.present + ? data.enterpriseEnabled.value + : this.enterpriseEnabled, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + privateKey: data.privateKey.present + ? data.privateKey.value + : this.privateKey, + mfaKeysStored: data.mfaKeysStored.present + ? data.mfaKeysStored.value + : this.mfaKeysStored, + ); + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('disableAllTraffic: $disableAllTraffic, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + disableAllTraffic, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DefguardInstancesData && + other.id == this.id && + other.name == this.name && + other.uuid == this.uuid && + other.url == this.url && + other.deviceId == this.deviceId && + other.proxyUrl == this.proxyUrl && + other.username == this.username && + other.poolingToken == this.poolingToken && + other.disableAllTraffic == this.disableAllTraffic && + other.enterpriseEnabled == this.enterpriseEnabled && + other.pubKey == this.pubKey && + other.privateKey == this.privateKey && + other.mfaKeysStored == this.mfaKeysStored); +} + +class DefguardInstancesCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value uuid; + final Value url; + final Value deviceId; + final Value proxyUrl; + final Value username; + final Value poolingToken; + final Value disableAllTraffic; + final Value enterpriseEnabled; + final Value pubKey; + final Value privateKey; + final Value mfaKeysStored; + const DefguardInstancesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.uuid = const Value.absent(), + this.url = const Value.absent(), + this.deviceId = const Value.absent(), + this.proxyUrl = const Value.absent(), + this.username = const Value.absent(), + this.poolingToken = const Value.absent(), + this.disableAllTraffic = const Value.absent(), + this.enterpriseEnabled = const Value.absent(), + this.pubKey = const Value.absent(), + this.privateKey = const Value.absent(), + this.mfaKeysStored = const Value.absent(), + }); + DefguardInstancesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String uuid, + required String url, + required int deviceId, + required String proxyUrl, + required String username, + required String poolingToken, + required bool disableAllTraffic, + required bool enterpriseEnabled, + required String pubKey, + required String privateKey, + required bool mfaKeysStored, + }) : name = Value(name), + uuid = Value(uuid), + url = Value(url), + deviceId = Value(deviceId), + proxyUrl = Value(proxyUrl), + username = Value(username), + poolingToken = Value(poolingToken), + disableAllTraffic = Value(disableAllTraffic), + enterpriseEnabled = Value(enterpriseEnabled), + pubKey = Value(pubKey), + privateKey = Value(privateKey), + mfaKeysStored = Value(mfaKeysStored); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? uuid, + Expression? url, + Expression? deviceId, + Expression? proxyUrl, + Expression? username, + Expression? poolingToken, + Expression? disableAllTraffic, + Expression? enterpriseEnabled, + Expression? pubKey, + Expression? privateKey, + Expression? mfaKeysStored, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (uuid != null) 'uuid': uuid, + if (url != null) 'url': url, + if (deviceId != null) 'device_id': deviceId, + if (proxyUrl != null) 'proxy_url': proxyUrl, + if (username != null) 'username': username, + if (poolingToken != null) 'pooling_token': poolingToken, + if (disableAllTraffic != null) 'disable_all_traffic': disableAllTraffic, + if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, + if (pubKey != null) 'pub_key': pubKey, + if (privateKey != null) 'private_key': privateKey, + if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + }); + } + + DefguardInstancesCompanion copyWith({ + Value? id, + Value? name, + Value? uuid, + Value? url, + Value? deviceId, + Value? proxyUrl, + Value? username, + Value? poolingToken, + Value? disableAllTraffic, + Value? enterpriseEnabled, + Value? pubKey, + Value? privateKey, + Value? mfaKeysStored, + }) { + return DefguardInstancesCompanion( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + disableAllTraffic: disableAllTraffic ?? this.disableAllTraffic, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (uuid.present) { + map['uuid'] = Variable(uuid.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (proxyUrl.present) { + map['proxy_url'] = Variable(proxyUrl.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (poolingToken.present) { + map['pooling_token'] = Variable(poolingToken.value); + } + if (disableAllTraffic.present) { + map['disable_all_traffic'] = Variable(disableAllTraffic.value); + } + if (enterpriseEnabled.present) { + map['enterprise_enabled'] = Variable(enterpriseEnabled.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (privateKey.present) { + map['private_key'] = Variable(privateKey.value); + } + if (mfaKeysStored.present) { + map['mfa_keys_stored'] = Variable(mfaKeysStored.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('disableAllTraffic: $disableAllTraffic, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored') + ..write(')')) + .toString(); + } +} + +class Locations extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Locations(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn instance = GeneratedColumn( + 'instance', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn networkId = GeneratedColumn( + 'network_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn address = GeneratedColumn( + 'address', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn endpoint = GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn allowedIps = GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn dns = GeneratedColumn( + 'dns', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaEnabled = GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn trafficMethod = GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaMethod = GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn keepAliveInterval = GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn locationMfaMode = GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locations'; + @override + Set get $primaryKey => {id}; + @override + LocationsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocationsData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + instance: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}instance'], + )!, + networkId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}network_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + address: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}address'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + endpoint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}endpoint'], + )!, + allowedIps: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}allowed_ips'], + )!, + dns: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns'], + ), + mfaEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_enabled'], + ), + trafficMethod: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}traffic_method'], + ), + mfaMethod: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}mfa_method'], + ), + keepAliveInterval: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}keep_alive_interval'], + )!, + locationMfaMode: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}location_mfa_mode'], + ), + ); + } + + @override + Locations createAlias(String alias) { + return Locations(attachedDatabase, alias); + } +} + +class LocationsData extends DataClass implements Insertable { + final int id; + final int instance; + final int networkId; + final String name; + final String address; + final String pubKey; + final String endpoint; + final String allowedIps; + final String? dns; + final bool? mfaEnabled; + final String? trafficMethod; + final int? mfaMethod; + final int keepAliveInterval; + final int? locationMfaMode; + const LocationsData({ + required this.id, + required this.instance, + required this.networkId, + required this.name, + required this.address, + required this.pubKey, + required this.endpoint, + required this.allowedIps, + this.dns, + this.mfaEnabled, + this.trafficMethod, + this.mfaMethod, + required this.keepAliveInterval, + this.locationMfaMode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['instance'] = Variable(instance); + map['network_id'] = Variable(networkId); + map['name'] = Variable(name); + map['address'] = Variable(address); + map['pub_key'] = Variable(pubKey); + map['endpoint'] = Variable(endpoint); + map['allowed_ips'] = Variable(allowedIps); + if (!nullToAbsent || dns != null) { + map['dns'] = Variable(dns); + } + if (!nullToAbsent || mfaEnabled != null) { + map['mfa_enabled'] = Variable(mfaEnabled); + } + if (!nullToAbsent || trafficMethod != null) { + map['traffic_method'] = Variable(trafficMethod); + } + if (!nullToAbsent || mfaMethod != null) { + map['mfa_method'] = Variable(mfaMethod); + } + map['keep_alive_interval'] = Variable(keepAliveInterval); + if (!nullToAbsent || locationMfaMode != null) { + map['location_mfa_mode'] = Variable(locationMfaMode); + } + return map; + } + + LocationsCompanion toCompanion(bool nullToAbsent) { + return LocationsCompanion( + id: Value(id), + instance: Value(instance), + networkId: Value(networkId), + name: Value(name), + address: Value(address), + pubKey: Value(pubKey), + endpoint: Value(endpoint), + allowedIps: Value(allowedIps), + dns: dns == null && nullToAbsent ? const Value.absent() : Value(dns), + mfaEnabled: mfaEnabled == null && nullToAbsent + ? const Value.absent() + : Value(mfaEnabled), + trafficMethod: trafficMethod == null && nullToAbsent + ? const Value.absent() + : Value(trafficMethod), + mfaMethod: mfaMethod == null && nullToAbsent + ? const Value.absent() + : Value(mfaMethod), + keepAliveInterval: Value(keepAliveInterval), + locationMfaMode: locationMfaMode == null && nullToAbsent + ? const Value.absent() + : Value(locationMfaMode), + ); + } + + factory LocationsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocationsData( + id: serializer.fromJson(json['id']), + instance: serializer.fromJson(json['instance']), + networkId: serializer.fromJson(json['networkId']), + name: serializer.fromJson(json['name']), + address: serializer.fromJson(json['address']), + pubKey: serializer.fromJson(json['pubKey']), + endpoint: serializer.fromJson(json['endpoint']), + allowedIps: serializer.fromJson(json['allowedIps']), + dns: serializer.fromJson(json['dns']), + mfaEnabled: serializer.fromJson(json['mfaEnabled']), + trafficMethod: serializer.fromJson(json['trafficMethod']), + mfaMethod: serializer.fromJson(json['mfaMethod']), + keepAliveInterval: serializer.fromJson(json['keepAliveInterval']), + locationMfaMode: serializer.fromJson(json['locationMfaMode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'instance': serializer.toJson(instance), + 'networkId': serializer.toJson(networkId), + 'name': serializer.toJson(name), + 'address': serializer.toJson(address), + 'pubKey': serializer.toJson(pubKey), + 'endpoint': serializer.toJson(endpoint), + 'allowedIps': serializer.toJson(allowedIps), + 'dns': serializer.toJson(dns), + 'mfaEnabled': serializer.toJson(mfaEnabled), + 'trafficMethod': serializer.toJson(trafficMethod), + 'mfaMethod': serializer.toJson(mfaMethod), + 'keepAliveInterval': serializer.toJson(keepAliveInterval), + 'locationMfaMode': serializer.toJson(locationMfaMode), + }; + } + + LocationsData copyWith({ + int? id, + int? instance, + int? networkId, + String? name, + String? address, + String? pubKey, + String? endpoint, + String? allowedIps, + Value dns = const Value.absent(), + Value mfaEnabled = const Value.absent(), + Value trafficMethod = const Value.absent(), + Value mfaMethod = const Value.absent(), + int? keepAliveInterval, + Value locationMfaMode = const Value.absent(), + }) => LocationsData( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns.present ? dns.value : this.dns, + mfaEnabled: mfaEnabled.present ? mfaEnabled.value : this.mfaEnabled, + trafficMethod: trafficMethod.present + ? trafficMethod.value + : this.trafficMethod, + mfaMethod: mfaMethod.present ? mfaMethod.value : this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode.present + ? locationMfaMode.value + : this.locationMfaMode, + ); + LocationsData copyWithCompanion(LocationsCompanion data) { + return LocationsData( + id: data.id.present ? data.id.value : this.id, + instance: data.instance.present ? data.instance.value : this.instance, + networkId: data.networkId.present ? data.networkId.value : this.networkId, + name: data.name.present ? data.name.value : this.name, + address: data.address.present ? data.address.value : this.address, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + endpoint: data.endpoint.present ? data.endpoint.value : this.endpoint, + allowedIps: data.allowedIps.present + ? data.allowedIps.value + : this.allowedIps, + dns: data.dns.present ? data.dns.value : this.dns, + mfaEnabled: data.mfaEnabled.present + ? data.mfaEnabled.value + : this.mfaEnabled, + trafficMethod: data.trafficMethod.present + ? data.trafficMethod.value + : this.trafficMethod, + mfaMethod: data.mfaMethod.present ? data.mfaMethod.value : this.mfaMethod, + keepAliveInterval: data.keepAliveInterval.present + ? data.keepAliveInterval.value + : this.keepAliveInterval, + locationMfaMode: data.locationMfaMode.present + ? data.locationMfaMode.value + : this.locationMfaMode, + ); + } + + @override + String toString() { + return (StringBuffer('LocationsData(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocationsData && + other.id == this.id && + other.instance == this.instance && + other.networkId == this.networkId && + other.name == this.name && + other.address == this.address && + other.pubKey == this.pubKey && + other.endpoint == this.endpoint && + other.allowedIps == this.allowedIps && + other.dns == this.dns && + other.mfaEnabled == this.mfaEnabled && + other.trafficMethod == this.trafficMethod && + other.mfaMethod == this.mfaMethod && + other.keepAliveInterval == this.keepAliveInterval && + other.locationMfaMode == this.locationMfaMode); +} + +class LocationsCompanion extends UpdateCompanion { + final Value id; + final Value instance; + final Value networkId; + final Value name; + final Value address; + final Value pubKey; + final Value endpoint; + final Value allowedIps; + final Value dns; + final Value mfaEnabled; + final Value trafficMethod; + final Value mfaMethod; + final Value keepAliveInterval; + final Value locationMfaMode; + const LocationsCompanion({ + this.id = const Value.absent(), + this.instance = const Value.absent(), + this.networkId = const Value.absent(), + this.name = const Value.absent(), + this.address = const Value.absent(), + this.pubKey = const Value.absent(), + this.endpoint = const Value.absent(), + this.allowedIps = const Value.absent(), + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + this.keepAliveInterval = const Value.absent(), + this.locationMfaMode = const Value.absent(), + }); + LocationsCompanion.insert({ + this.id = const Value.absent(), + required int instance, + required int networkId, + required String name, + required String address, + required String pubKey, + required String endpoint, + required String allowedIps, + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + required int keepAliveInterval, + this.locationMfaMode = const Value.absent(), + }) : instance = Value(instance), + networkId = Value(networkId), + name = Value(name), + address = Value(address), + pubKey = Value(pubKey), + endpoint = Value(endpoint), + allowedIps = Value(allowedIps), + keepAliveInterval = Value(keepAliveInterval); + static Insertable custom({ + Expression? id, + Expression? instance, + Expression? networkId, + Expression? name, + Expression? address, + Expression? pubKey, + Expression? endpoint, + Expression? allowedIps, + Expression? dns, + Expression? mfaEnabled, + Expression? trafficMethod, + Expression? mfaMethod, + Expression? keepAliveInterval, + Expression? locationMfaMode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (instance != null) 'instance': instance, + if (networkId != null) 'network_id': networkId, + if (name != null) 'name': name, + if (address != null) 'address': address, + if (pubKey != null) 'pub_key': pubKey, + if (endpoint != null) 'endpoint': endpoint, + if (allowedIps != null) 'allowed_ips': allowedIps, + if (dns != null) 'dns': dns, + if (mfaEnabled != null) 'mfa_enabled': mfaEnabled, + if (trafficMethod != null) 'traffic_method': trafficMethod, + if (mfaMethod != null) 'mfa_method': mfaMethod, + if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, + if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + }); + } + + LocationsCompanion copyWith({ + Value? id, + Value? instance, + Value? networkId, + Value? name, + Value? address, + Value? pubKey, + Value? endpoint, + Value? allowedIps, + Value? dns, + Value? mfaEnabled, + Value? trafficMethod, + Value? mfaMethod, + Value? keepAliveInterval, + Value? locationMfaMode, + }) { + return LocationsCompanion( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns ?? this.dns, + mfaEnabled: mfaEnabled ?? this.mfaEnabled, + trafficMethod: trafficMethod ?? this.trafficMethod, + mfaMethod: mfaMethod ?? this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode ?? this.locationMfaMode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (instance.present) { + map['instance'] = Variable(instance.value); + } + if (networkId.present) { + map['network_id'] = Variable(networkId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (address.present) { + map['address'] = Variable(address.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (endpoint.present) { + map['endpoint'] = Variable(endpoint.value); + } + if (allowedIps.present) { + map['allowed_ips'] = Variable(allowedIps.value); + } + if (dns.present) { + map['dns'] = Variable(dns.value); + } + if (mfaEnabled.present) { + map['mfa_enabled'] = Variable(mfaEnabled.value); + } + if (trafficMethod.present) { + map['traffic_method'] = Variable(trafficMethod.value); + } + if (mfaMethod.present) { + map['mfa_method'] = Variable(mfaMethod.value); + } + if (keepAliveInterval.present) { + map['keep_alive_interval'] = Variable(keepAliveInterval.value); + } + if (locationMfaMode.present) { + map['location_mfa_mode'] = Variable(locationMfaMode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocationsCompanion(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV1 extends GeneratedDatabase { + DatabaseAtV1(QueryExecutor e) : super(e); + late final DefguardInstances defguardInstances = DefguardInstances(this); + late final Locations locations = Locations(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + defguardInstances, + locations, + ]; + @override + int get schemaVersion => 1; +} diff --git a/client/test/drift/defguard/generated/schema_v2.dart b/client/test/drift/defguard/generated/schema_v2.dart new file mode 100644 index 0000000..7419234 --- /dev/null +++ b/client/test/drift/defguard/generated/schema_v2.dart @@ -0,0 +1,1275 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class DefguardInstances extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + DefguardInstances(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn proxyUrl = GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn username = GeneratedColumn( + 'username', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn poolingToken = GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn clientTrafficPolicy = GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn enterpriseEnabled = GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn privateKey = GeneratedColumn( + 'private_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn mfaKeysStored = GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'defguard_instances'; + @override + Set get $primaryKey => {id}; + @override + DefguardInstancesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DefguardInstancesData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + )!, + deviceId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}device_id'], + )!, + proxyUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}proxy_url'], + )!, + username: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}username'], + )!, + poolingToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pooling_token'], + )!, + clientTrafficPolicy: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}client_traffic_policy'], + )!, + enterpriseEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}enterprise_enabled'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + privateKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}private_key'], + )!, + mfaKeysStored: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_keys_stored'], + )!, + ); + } + + @override + DefguardInstances createAlias(String alias) { + return DefguardInstances(attachedDatabase, alias); + } +} + +class DefguardInstancesData extends DataClass + implements Insertable { + final int id; + final String name; + final String uuid; + final String url; + final int deviceId; + final String proxyUrl; + final String username; + final String poolingToken; + final int clientTrafficPolicy; + final bool enterpriseEnabled; + final String pubKey; + final String privateKey; + final bool mfaKeysStored; + const DefguardInstancesData({ + required this.id, + required this.name, + required this.uuid, + required this.url, + required this.deviceId, + required this.proxyUrl, + required this.username, + required this.poolingToken, + required this.clientTrafficPolicy, + required this.enterpriseEnabled, + required this.pubKey, + required this.privateKey, + required this.mfaKeysStored, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['uuid'] = Variable(uuid); + map['url'] = Variable(url); + map['device_id'] = Variable(deviceId); + map['proxy_url'] = Variable(proxyUrl); + map['username'] = Variable(username); + map['pooling_token'] = Variable(poolingToken); + map['client_traffic_policy'] = Variable(clientTrafficPolicy); + map['enterprise_enabled'] = Variable(enterpriseEnabled); + map['pub_key'] = Variable(pubKey); + map['private_key'] = Variable(privateKey); + map['mfa_keys_stored'] = Variable(mfaKeysStored); + return map; + } + + DefguardInstancesCompanion toCompanion(bool nullToAbsent) { + return DefguardInstancesCompanion( + id: Value(id), + name: Value(name), + uuid: Value(uuid), + url: Value(url), + deviceId: Value(deviceId), + proxyUrl: Value(proxyUrl), + username: Value(username), + poolingToken: Value(poolingToken), + clientTrafficPolicy: Value(clientTrafficPolicy), + enterpriseEnabled: Value(enterpriseEnabled), + pubKey: Value(pubKey), + privateKey: Value(privateKey), + mfaKeysStored: Value(mfaKeysStored), + ); + } + + factory DefguardInstancesData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DefguardInstancesData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + uuid: serializer.fromJson(json['uuid']), + url: serializer.fromJson(json['url']), + deviceId: serializer.fromJson(json['deviceId']), + proxyUrl: serializer.fromJson(json['proxyUrl']), + username: serializer.fromJson(json['username']), + poolingToken: serializer.fromJson(json['poolingToken']), + clientTrafficPolicy: serializer.fromJson( + json['clientTrafficPolicy'], + ), + enterpriseEnabled: serializer.fromJson(json['enterpriseEnabled']), + pubKey: serializer.fromJson(json['pubKey']), + privateKey: serializer.fromJson(json['privateKey']), + mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'uuid': serializer.toJson(uuid), + 'url': serializer.toJson(url), + 'deviceId': serializer.toJson(deviceId), + 'proxyUrl': serializer.toJson(proxyUrl), + 'username': serializer.toJson(username), + 'poolingToken': serializer.toJson(poolingToken), + 'clientTrafficPolicy': serializer.toJson(clientTrafficPolicy), + 'enterpriseEnabled': serializer.toJson(enterpriseEnabled), + 'pubKey': serializer.toJson(pubKey), + 'privateKey': serializer.toJson(privateKey), + 'mfaKeysStored': serializer.toJson(mfaKeysStored), + }; + } + + DefguardInstancesData copyWith({ + int? id, + String? name, + String? uuid, + String? url, + int? deviceId, + String? proxyUrl, + String? username, + String? poolingToken, + int? clientTrafficPolicy, + bool? enterpriseEnabled, + String? pubKey, + String? privateKey, + bool? mfaKeysStored, + }) => DefguardInstancesData( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + ); + DefguardInstancesData copyWithCompanion(DefguardInstancesCompanion data) { + return DefguardInstancesData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + uuid: data.uuid.present ? data.uuid.value : this.uuid, + url: data.url.present ? data.url.value : this.url, + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + proxyUrl: data.proxyUrl.present ? data.proxyUrl.value : this.proxyUrl, + username: data.username.present ? data.username.value : this.username, + poolingToken: data.poolingToken.present + ? data.poolingToken.value + : this.poolingToken, + clientTrafficPolicy: data.clientTrafficPolicy.present + ? data.clientTrafficPolicy.value + : this.clientTrafficPolicy, + enterpriseEnabled: data.enterpriseEnabled.present + ? data.enterpriseEnabled.value + : this.enterpriseEnabled, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + privateKey: data.privateKey.present + ? data.privateKey.value + : this.privateKey, + mfaKeysStored: data.mfaKeysStored.present + ? data.mfaKeysStored.value + : this.mfaKeysStored, + ); + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DefguardInstancesData && + other.id == this.id && + other.name == this.name && + other.uuid == this.uuid && + other.url == this.url && + other.deviceId == this.deviceId && + other.proxyUrl == this.proxyUrl && + other.username == this.username && + other.poolingToken == this.poolingToken && + other.clientTrafficPolicy == this.clientTrafficPolicy && + other.enterpriseEnabled == this.enterpriseEnabled && + other.pubKey == this.pubKey && + other.privateKey == this.privateKey && + other.mfaKeysStored == this.mfaKeysStored); +} + +class DefguardInstancesCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value uuid; + final Value url; + final Value deviceId; + final Value proxyUrl; + final Value username; + final Value poolingToken; + final Value clientTrafficPolicy; + final Value enterpriseEnabled; + final Value pubKey; + final Value privateKey; + final Value mfaKeysStored; + const DefguardInstancesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.uuid = const Value.absent(), + this.url = const Value.absent(), + this.deviceId = const Value.absent(), + this.proxyUrl = const Value.absent(), + this.username = const Value.absent(), + this.poolingToken = const Value.absent(), + this.clientTrafficPolicy = const Value.absent(), + this.enterpriseEnabled = const Value.absent(), + this.pubKey = const Value.absent(), + this.privateKey = const Value.absent(), + this.mfaKeysStored = const Value.absent(), + }); + DefguardInstancesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String uuid, + required String url, + required int deviceId, + required String proxyUrl, + required String username, + required String poolingToken, + this.clientTrafficPolicy = const Value.absent(), + required bool enterpriseEnabled, + required String pubKey, + required String privateKey, + required bool mfaKeysStored, + }) : name = Value(name), + uuid = Value(uuid), + url = Value(url), + deviceId = Value(deviceId), + proxyUrl = Value(proxyUrl), + username = Value(username), + poolingToken = Value(poolingToken), + enterpriseEnabled = Value(enterpriseEnabled), + pubKey = Value(pubKey), + privateKey = Value(privateKey), + mfaKeysStored = Value(mfaKeysStored); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? uuid, + Expression? url, + Expression? deviceId, + Expression? proxyUrl, + Expression? username, + Expression? poolingToken, + Expression? clientTrafficPolicy, + Expression? enterpriseEnabled, + Expression? pubKey, + Expression? privateKey, + Expression? mfaKeysStored, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (uuid != null) 'uuid': uuid, + if (url != null) 'url': url, + if (deviceId != null) 'device_id': deviceId, + if (proxyUrl != null) 'proxy_url': proxyUrl, + if (username != null) 'username': username, + if (poolingToken != null) 'pooling_token': poolingToken, + if (clientTrafficPolicy != null) + 'client_traffic_policy': clientTrafficPolicy, + if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, + if (pubKey != null) 'pub_key': pubKey, + if (privateKey != null) 'private_key': privateKey, + if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + }); + } + + DefguardInstancesCompanion copyWith({ + Value? id, + Value? name, + Value? uuid, + Value? url, + Value? deviceId, + Value? proxyUrl, + Value? username, + Value? poolingToken, + Value? clientTrafficPolicy, + Value? enterpriseEnabled, + Value? pubKey, + Value? privateKey, + Value? mfaKeysStored, + }) { + return DefguardInstancesCompanion( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (uuid.present) { + map['uuid'] = Variable(uuid.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (proxyUrl.present) { + map['proxy_url'] = Variable(proxyUrl.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (poolingToken.present) { + map['pooling_token'] = Variable(poolingToken.value); + } + if (clientTrafficPolicy.present) { + map['client_traffic_policy'] = Variable(clientTrafficPolicy.value); + } + if (enterpriseEnabled.present) { + map['enterprise_enabled'] = Variable(enterpriseEnabled.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (privateKey.present) { + map['private_key'] = Variable(privateKey.value); + } + if (mfaKeysStored.present) { + map['mfa_keys_stored'] = Variable(mfaKeysStored.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored') + ..write(')')) + .toString(); + } +} + +class Locations extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Locations(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn instance = GeneratedColumn( + 'instance', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn networkId = GeneratedColumn( + 'network_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn address = GeneratedColumn( + 'address', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn endpoint = GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn allowedIps = GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn dns = GeneratedColumn( + 'dns', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaEnabled = GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn trafficMethod = GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaMethod = GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn keepAliveInterval = GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn locationMfaMode = GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locations'; + @override + Set get $primaryKey => {id}; + @override + LocationsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocationsData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + instance: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}instance'], + )!, + networkId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}network_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + address: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}address'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + endpoint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}endpoint'], + )!, + allowedIps: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}allowed_ips'], + )!, + dns: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns'], + ), + mfaEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_enabled'], + ), + trafficMethod: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}traffic_method'], + ), + mfaMethod: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}mfa_method'], + ), + keepAliveInterval: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}keep_alive_interval'], + )!, + locationMfaMode: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}location_mfa_mode'], + ), + ); + } + + @override + Locations createAlias(String alias) { + return Locations(attachedDatabase, alias); + } +} + +class LocationsData extends DataClass implements Insertable { + final int id; + final int instance; + final int networkId; + final String name; + final String address; + final String pubKey; + final String endpoint; + final String allowedIps; + final String? dns; + final bool? mfaEnabled; + final String? trafficMethod; + final int? mfaMethod; + final int keepAliveInterval; + final int? locationMfaMode; + const LocationsData({ + required this.id, + required this.instance, + required this.networkId, + required this.name, + required this.address, + required this.pubKey, + required this.endpoint, + required this.allowedIps, + this.dns, + this.mfaEnabled, + this.trafficMethod, + this.mfaMethod, + required this.keepAliveInterval, + this.locationMfaMode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['instance'] = Variable(instance); + map['network_id'] = Variable(networkId); + map['name'] = Variable(name); + map['address'] = Variable(address); + map['pub_key'] = Variable(pubKey); + map['endpoint'] = Variable(endpoint); + map['allowed_ips'] = Variable(allowedIps); + if (!nullToAbsent || dns != null) { + map['dns'] = Variable(dns); + } + if (!nullToAbsent || mfaEnabled != null) { + map['mfa_enabled'] = Variable(mfaEnabled); + } + if (!nullToAbsent || trafficMethod != null) { + map['traffic_method'] = Variable(trafficMethod); + } + if (!nullToAbsent || mfaMethod != null) { + map['mfa_method'] = Variable(mfaMethod); + } + map['keep_alive_interval'] = Variable(keepAliveInterval); + if (!nullToAbsent || locationMfaMode != null) { + map['location_mfa_mode'] = Variable(locationMfaMode); + } + return map; + } + + LocationsCompanion toCompanion(bool nullToAbsent) { + return LocationsCompanion( + id: Value(id), + instance: Value(instance), + networkId: Value(networkId), + name: Value(name), + address: Value(address), + pubKey: Value(pubKey), + endpoint: Value(endpoint), + allowedIps: Value(allowedIps), + dns: dns == null && nullToAbsent ? const Value.absent() : Value(dns), + mfaEnabled: mfaEnabled == null && nullToAbsent + ? const Value.absent() + : Value(mfaEnabled), + trafficMethod: trafficMethod == null && nullToAbsent + ? const Value.absent() + : Value(trafficMethod), + mfaMethod: mfaMethod == null && nullToAbsent + ? const Value.absent() + : Value(mfaMethod), + keepAliveInterval: Value(keepAliveInterval), + locationMfaMode: locationMfaMode == null && nullToAbsent + ? const Value.absent() + : Value(locationMfaMode), + ); + } + + factory LocationsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocationsData( + id: serializer.fromJson(json['id']), + instance: serializer.fromJson(json['instance']), + networkId: serializer.fromJson(json['networkId']), + name: serializer.fromJson(json['name']), + address: serializer.fromJson(json['address']), + pubKey: serializer.fromJson(json['pubKey']), + endpoint: serializer.fromJson(json['endpoint']), + allowedIps: serializer.fromJson(json['allowedIps']), + dns: serializer.fromJson(json['dns']), + mfaEnabled: serializer.fromJson(json['mfaEnabled']), + trafficMethod: serializer.fromJson(json['trafficMethod']), + mfaMethod: serializer.fromJson(json['mfaMethod']), + keepAliveInterval: serializer.fromJson(json['keepAliveInterval']), + locationMfaMode: serializer.fromJson(json['locationMfaMode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'instance': serializer.toJson(instance), + 'networkId': serializer.toJson(networkId), + 'name': serializer.toJson(name), + 'address': serializer.toJson(address), + 'pubKey': serializer.toJson(pubKey), + 'endpoint': serializer.toJson(endpoint), + 'allowedIps': serializer.toJson(allowedIps), + 'dns': serializer.toJson(dns), + 'mfaEnabled': serializer.toJson(mfaEnabled), + 'trafficMethod': serializer.toJson(trafficMethod), + 'mfaMethod': serializer.toJson(mfaMethod), + 'keepAliveInterval': serializer.toJson(keepAliveInterval), + 'locationMfaMode': serializer.toJson(locationMfaMode), + }; + } + + LocationsData copyWith({ + int? id, + int? instance, + int? networkId, + String? name, + String? address, + String? pubKey, + String? endpoint, + String? allowedIps, + Value dns = const Value.absent(), + Value mfaEnabled = const Value.absent(), + Value trafficMethod = const Value.absent(), + Value mfaMethod = const Value.absent(), + int? keepAliveInterval, + Value locationMfaMode = const Value.absent(), + }) => LocationsData( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns.present ? dns.value : this.dns, + mfaEnabled: mfaEnabled.present ? mfaEnabled.value : this.mfaEnabled, + trafficMethod: trafficMethod.present + ? trafficMethod.value + : this.trafficMethod, + mfaMethod: mfaMethod.present ? mfaMethod.value : this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode.present + ? locationMfaMode.value + : this.locationMfaMode, + ); + LocationsData copyWithCompanion(LocationsCompanion data) { + return LocationsData( + id: data.id.present ? data.id.value : this.id, + instance: data.instance.present ? data.instance.value : this.instance, + networkId: data.networkId.present ? data.networkId.value : this.networkId, + name: data.name.present ? data.name.value : this.name, + address: data.address.present ? data.address.value : this.address, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + endpoint: data.endpoint.present ? data.endpoint.value : this.endpoint, + allowedIps: data.allowedIps.present + ? data.allowedIps.value + : this.allowedIps, + dns: data.dns.present ? data.dns.value : this.dns, + mfaEnabled: data.mfaEnabled.present + ? data.mfaEnabled.value + : this.mfaEnabled, + trafficMethod: data.trafficMethod.present + ? data.trafficMethod.value + : this.trafficMethod, + mfaMethod: data.mfaMethod.present ? data.mfaMethod.value : this.mfaMethod, + keepAliveInterval: data.keepAliveInterval.present + ? data.keepAliveInterval.value + : this.keepAliveInterval, + locationMfaMode: data.locationMfaMode.present + ? data.locationMfaMode.value + : this.locationMfaMode, + ); + } + + @override + String toString() { + return (StringBuffer('LocationsData(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocationsData && + other.id == this.id && + other.instance == this.instance && + other.networkId == this.networkId && + other.name == this.name && + other.address == this.address && + other.pubKey == this.pubKey && + other.endpoint == this.endpoint && + other.allowedIps == this.allowedIps && + other.dns == this.dns && + other.mfaEnabled == this.mfaEnabled && + other.trafficMethod == this.trafficMethod && + other.mfaMethod == this.mfaMethod && + other.keepAliveInterval == this.keepAliveInterval && + other.locationMfaMode == this.locationMfaMode); +} + +class LocationsCompanion extends UpdateCompanion { + final Value id; + final Value instance; + final Value networkId; + final Value name; + final Value address; + final Value pubKey; + final Value endpoint; + final Value allowedIps; + final Value dns; + final Value mfaEnabled; + final Value trafficMethod; + final Value mfaMethod; + final Value keepAliveInterval; + final Value locationMfaMode; + const LocationsCompanion({ + this.id = const Value.absent(), + this.instance = const Value.absent(), + this.networkId = const Value.absent(), + this.name = const Value.absent(), + this.address = const Value.absent(), + this.pubKey = const Value.absent(), + this.endpoint = const Value.absent(), + this.allowedIps = const Value.absent(), + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + this.keepAliveInterval = const Value.absent(), + this.locationMfaMode = const Value.absent(), + }); + LocationsCompanion.insert({ + this.id = const Value.absent(), + required int instance, + required int networkId, + required String name, + required String address, + required String pubKey, + required String endpoint, + required String allowedIps, + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + required int keepAliveInterval, + this.locationMfaMode = const Value.absent(), + }) : instance = Value(instance), + networkId = Value(networkId), + name = Value(name), + address = Value(address), + pubKey = Value(pubKey), + endpoint = Value(endpoint), + allowedIps = Value(allowedIps), + keepAliveInterval = Value(keepAliveInterval); + static Insertable custom({ + Expression? id, + Expression? instance, + Expression? networkId, + Expression? name, + Expression? address, + Expression? pubKey, + Expression? endpoint, + Expression? allowedIps, + Expression? dns, + Expression? mfaEnabled, + Expression? trafficMethod, + Expression? mfaMethod, + Expression? keepAliveInterval, + Expression? locationMfaMode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (instance != null) 'instance': instance, + if (networkId != null) 'network_id': networkId, + if (name != null) 'name': name, + if (address != null) 'address': address, + if (pubKey != null) 'pub_key': pubKey, + if (endpoint != null) 'endpoint': endpoint, + if (allowedIps != null) 'allowed_ips': allowedIps, + if (dns != null) 'dns': dns, + if (mfaEnabled != null) 'mfa_enabled': mfaEnabled, + if (trafficMethod != null) 'traffic_method': trafficMethod, + if (mfaMethod != null) 'mfa_method': mfaMethod, + if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, + if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + }); + } + + LocationsCompanion copyWith({ + Value? id, + Value? instance, + Value? networkId, + Value? name, + Value? address, + Value? pubKey, + Value? endpoint, + Value? allowedIps, + Value? dns, + Value? mfaEnabled, + Value? trafficMethod, + Value? mfaMethod, + Value? keepAliveInterval, + Value? locationMfaMode, + }) { + return LocationsCompanion( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns ?? this.dns, + mfaEnabled: mfaEnabled ?? this.mfaEnabled, + trafficMethod: trafficMethod ?? this.trafficMethod, + mfaMethod: mfaMethod ?? this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode ?? this.locationMfaMode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (instance.present) { + map['instance'] = Variable(instance.value); + } + if (networkId.present) { + map['network_id'] = Variable(networkId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (address.present) { + map['address'] = Variable(address.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (endpoint.present) { + map['endpoint'] = Variable(endpoint.value); + } + if (allowedIps.present) { + map['allowed_ips'] = Variable(allowedIps.value); + } + if (dns.present) { + map['dns'] = Variable(dns.value); + } + if (mfaEnabled.present) { + map['mfa_enabled'] = Variable(mfaEnabled.value); + } + if (trafficMethod.present) { + map['traffic_method'] = Variable(trafficMethod.value); + } + if (mfaMethod.present) { + map['mfa_method'] = Variable(mfaMethod.value); + } + if (keepAliveInterval.present) { + map['keep_alive_interval'] = Variable(keepAliveInterval.value); + } + if (locationMfaMode.present) { + map['location_mfa_mode'] = Variable(locationMfaMode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocationsCompanion(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV2 extends GeneratedDatabase { + DatabaseAtV2(QueryExecutor e) : super(e); + late final DefguardInstances defguardInstances = DefguardInstances(this); + late final Locations locations = Locations(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + defguardInstances, + locations, + ]; + @override + int get schemaVersion => 2; +} diff --git a/client/test/drift/defguard/migration_test.dart b/client/test/drift/defguard/migration_test.dart new file mode 100644 index 0000000..6ab73b2 --- /dev/null +++ b/client/test/drift/defguard/migration_test.dart @@ -0,0 +1,79 @@ +// dart format width=80 +// ignore_for_file: unused_local_variable, unused_import +import 'package:drift/drift.dart'; +import 'package:drift_dev/api/migrations_native.dart'; +import 'package:mobile/data/db/database.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'generated/schema.dart'; + +import 'generated/schema_v1.dart' as v1; +import 'generated/schema_v2.dart' as v2; + +void main() { + driftRuntimeOptions.dontWarnAboutMultipleDatabases = true; + late SchemaVerifier verifier; + + setUpAll(() { + verifier = SchemaVerifier(GeneratedHelper()); + }); + + group('simple database migrations', () { + // These simple tests verify all possible schema updates with a simple (no + // data) migration. This is a quick way to ensure that written database + // migrations properly alter the schema. + const versions = GeneratedHelper.versions; + for (final (i, fromVersion) in versions.indexed) { + group('from $fromVersion', () { + for (final toVersion in versions.skip(i + 1)) { + test('to $toVersion', () async { + final schema = await verifier.schemaAt(fromVersion); + final db = AppDatabase(schema.newConnection()); + await verifier.migrateAndValidate(db, toVersion); + await db.close(); + }); + } + }); + } + }); + + // The following template shows how to write tests ensuring your migrations + // preserve existing data. + // Testing this can be useful for migrations that change existing columns + // (e.g. by alterating their type or constraints). Migrations that only add + // tables or columns typically don't need these advanced tests. For more + // information, see https://drift.simonbinder.eu/migrations/tests/#verifying-data-integrity + // TODO: This generated template shows how these tests could be written. Adopt + // it to your own needs when testing migrations with data integrity. + test('migration from v1 to v2 does not corrupt data', () async { + // Add data to insert into the old database, and the expected rows after the + // migration. + // TODO: Fill these lists + final oldDefguardInstancesData = []; + final expectedNewDefguardInstancesData = []; + + final oldLocationsData = []; + final expectedNewLocationsData = []; + + await verifier.testWithDataIntegrity( + oldVersion: 1, + newVersion: 2, + createOld: v1.DatabaseAtV1.new, + createNew: v2.DatabaseAtV2.new, + openTestedDatabase: AppDatabase.new, + createItems: (batch, oldDb) { + batch.insertAll(oldDb.defguardInstances, oldDefguardInstancesData); + batch.insertAll(oldDb.locations, oldLocationsData); + }, + validateItems: (newDb) async { + expect( + expectedNewDefguardInstancesData, + await newDb.select(newDb.defguardInstances).get(), + ); + expect( + expectedNewLocationsData, + await newDb.select(newDb.locations).get(), + ); + }, + ); + }); +} diff --git a/flake.nix b/flake.nix index 9d34875..002f76a 100644 --- a/flake.nix +++ b/flake.nix @@ -68,7 +68,7 @@ ''; in { devShell = with pkgs; - mkShell rec { + mkShell { ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk"; buildInputs = [ flutter @@ -85,6 +85,7 @@ export GDK_BACKEND=x11 export LANG=en_US.UTF-8 export QT_QPA_PLATFORM=xcb + export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath (with pkgs; [ sqlite ])}:$LD_LIBRARY_PATH"; ''; }; }); From fbf8c66b520e3a05092bd864a61188e2f5b8acb2 Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:11:13 +0100 Subject: [PATCH 04/44] fix periodic SBOM regeneration (#161) * update gitignore * remove private token * update borintun submodule * another test * restore submodule version --- .github/workflows/build.yaml | 11 ++++------- .github/workflows/lint-and-test.yaml | 13 ++++++------- .github/workflows/release.yaml | 7 ------- .github/workflows/sbom-regenerate.yaml | 7 ++++--- .github/workflows/sbom.yaml | 21 +++++++++------------ .gitignore | 2 ++ 6 files changed, 25 insertions(+), 36 deletions(-) create mode 100644 .gitignore diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index ec1c8ff..c7efee6 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -4,8 +4,8 @@ on: branches: - main - dev - - 'release/**' - - 'hotfix/**' + - "release/**" + - "hotfix/**" tags: - v*.*.* paths-ignore: @@ -24,7 +24,6 @@ jobs: uses: actions/checkout@v4 with: submodules: "recursive" - token: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} - name: Setup flutter uses: subosito/flutter-action@v2 @@ -66,7 +65,7 @@ jobs: issuer-id: ${{ secrets.API_ISSUER_ID }} api-key-id: ${{ secrets.ASC_API_KEY_ID }} api-private-key: ${{ secrets.PRIVATE_KEY_CONTENTS }} - + - name: Upload iOS Artifact uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags/') @@ -161,7 +160,7 @@ jobs: with: channel: stable flutter-version: 3.32.7 - + - name: Install Android SDK components run: | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install 'build-tools;29.0.3' @@ -199,5 +198,3 @@ jobs: # Create release only if CI was triggered by a tag. if: startsWith(github.ref, 'refs/tags/') uses: ./.github/workflows/release.yaml - secrets: - PRIVATE_REPO_CLONING_TOKEN: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 603d5d4..01df6ba 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -5,8 +5,8 @@ on: branches: - main - dev - - 'release/**' - - 'hotfix/**' + - "release/**" + - "hotfix/**" paths-ignore: &ignored_paths - "*.md" - "LICENSE" @@ -15,8 +15,8 @@ on: branches: - main - dev - - 'release/**' - - 'hotfix/**' + - "release/**" + - "hotfix/**" paths-ignore: *ignored_paths jobs: @@ -34,8 +34,8 @@ jobs: - name: Scan code with Trivy uses: aquasecurity/trivy-action@0.33.1 with: - scan-type: 'fs' - scan-ref: '.' + scan-type: "fs" + scan-ref: "." exit-code: "1" ignore-unfixed: true severity: "CRITICAL,HIGH,MEDIUM" @@ -66,7 +66,6 @@ jobs: # uses: actions/checkout@v4 # with: # submodules: "recursive" - # token: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} # - name: setup flutter # uses: subosito/flutter-action@v2 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 7d41668..103af53 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -2,10 +2,6 @@ name: "Release" on: workflow_call: - secrets: - PRIVATE_REPO_CLONING_TOKEN: - description: "Cloning token" - required: true jobs: create-release: @@ -35,6 +31,3 @@ jobs: uses: ./.github/workflows/sbom.yaml with: upload_url: ${{ needs.create-release.outputs.upload_url }} - secrets: - PRIVATE_REPO_CLONING_TOKEN: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} - diff --git a/.github/workflows/sbom-regenerate.yaml b/.github/workflows/sbom-regenerate.yaml index 3bd1f03..dff8a3c 100644 --- a/.github/workflows/sbom-regenerate.yaml +++ b/.github/workflows/sbom-regenerate.yaml @@ -1,8 +1,10 @@ name: Periodic SBOM Regeneration +permissions: + contents: write on: schedule: - - cron: '30 2 * * *' # 2:30 AM UTC + - cron: "30 2 * * *" # 2:30 AM UTC jobs: list-releases: @@ -35,5 +37,4 @@ jobs: with: upload_url: ${{ matrix.release.uploadUrl }} tag: ${{ matrix.release.tagName }} - secrets: - PRIVATE_REPO_CLONING_TOKEN: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} + secrets: inherit diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index 99775f0..67bfc57 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -11,13 +11,11 @@ on: description: "The git tag to generate SBOM for - used in scheduled runs" required: false type: string - secrets: - PRIVATE_REPO_CLONING_TOKEN: - description: "Cloning token" - required: true jobs: create-sbom: + permissions: + contents: write runs-on: [self-hosted, Linux, X64] steps: @@ -33,27 +31,26 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - submodules: recursive ref: ${{ steps.vars.outputs.TAG_NAME }} - token: ${{ secrets.PRIVATE_REPO_CLONING_TOKEN }} + submodules: recursive - name: Create SBOM with Trivy uses: aquasecurity/trivy-action@0.33.1 with: - scan-type: 'fs' - format: 'spdx-json' + scan-type: "fs" + format: "spdx-json" output: "defguard-mobile-${{ steps.vars.outputs.VERSION }}.sbom.json" - scan-ref: '.' + scan-ref: "." severity: "CRITICAL,HIGH,MEDIUM,LOW" scanners: "vuln" - name: Create security advisory file with Trivy uses: aquasecurity/trivy-action@0.33.1 with: - scan-type: 'fs' - format: 'json' + scan-type: "fs" + format: "json" output: "defguard-mobile-${{ steps.vars.outputs.VERSION }}.advisories.json" - scan-ref: '.' + scan-ref: "." severity: "CRITICAL,HIGH,MEDIUM,LOW" scanners: "vuln" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c5edab1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.envrc +.direnv/ From 5c681e23c3feeb6f2762c1927eecc305dd30c1e8 Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 20 Jan 2026 14:04:20 +0100 Subject: [PATCH 05/44] Omit IPA from release artefacts; bump dependencies (#168) --- .github/workflows/build.yaml | 20 +-- .github/workflows/lint-and-test.yaml | 8 +- .github/workflows/sbom.yaml | 2 +- client/ios/Podfile.lock | 13 +- client/pubspec.lock | 176 +++++++++++++++------------ 5 files changed, 114 insertions(+), 105 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 35ad3ca..2d5f473 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -21,7 +21,7 @@ jobs: working-directory: ./client steps: - name: Checkout main repo - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: submodules: "recursive" @@ -29,7 +29,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.35.7 + flutter-version: 3.38.7 - name: Use homebrew ruby run: | @@ -66,14 +66,6 @@ jobs: api-key-id: ${{ secrets.ASC_API_KEY_ID }} api-private-key: ${{ secrets.PRIVATE_KEY_CONTENTS }} - - name: Upload iOS Artifact - uses: actions/upload-artifact@v4 - if: startsWith(github.ref, 'refs/tags/') - with: - name: ios-app - path: "client/build/ios/ipa/Defguard.ipa" - retention-days: 2 - build-android: runs-on: [self-hosted, macOS] env: @@ -83,7 +75,7 @@ jobs: run: working-directory: ./client steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v3 @@ -95,7 +87,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.32.7 + flutter-version: 3.38.7 - name: Accept licenses run: yes | flutter doctor --android-licenses @@ -147,7 +139,7 @@ jobs: run: working-directory: ./client steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Set up Java uses: actions/setup-java@v3 @@ -159,7 +151,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.32.7 + flutter-version: 3.38.7 - name: Install Android SDK components run: | diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 01df6ba..c99855d 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Scan code with Trivy uses: aquasecurity/trivy-action@0.33.1 @@ -45,7 +45,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.32.4 + flutter-version: 3.38.7 - name: get deps run: flutter pub get @@ -63,7 +63,7 @@ jobs: # steps: # - name: Checkout - # uses: actions/checkout@v4 + # uses: actions/checkout@v6 # with: # submodules: "recursive" @@ -71,7 +71,7 @@ jobs: # uses: subosito/flutter-action@v2 # with: # channel: stable - # flutter-version: 3.32.6 + # flutter-version: 3.38.7 # - name: get deps # run: flutter pub get diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index 67bfc57..e827610 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -29,7 +29,7 @@ jobs: echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ steps.vars.outputs.TAG_NAME }} submodules: recursive diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index 2126ffd..fff8239 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -18,9 +18,6 @@ PODS: - FlutterMacOS - package_info_plus (0.4.5): - Flutter - - path_provider_foundation (0.0.1): - - Flutter - - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - share_plus (0.0.1): @@ -66,7 +63,6 @@ DEPENDENCIES: - local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`) - mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) @@ -97,8 +93,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/mobile_scanner/darwin" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" - path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" share_plus: @@ -119,16 +113,15 @@ SPEC CHECKSUMS: flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - local_auth_darwin: fa4b06454df7df8e97c18d7ee55151c57e7af0de + local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 - url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/pubspec.lock b/client/pubspec.lock index c1b1ac5..990a071 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "8e928697a82be082206edb0b9c99c5a4ad6bc31c9e9b8b2f291ae65cd4a25daa" + sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 url: "https://pub.dev" source: hosted - version: "4.0.4" + version: "4.1.1" build_resolvers: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: a30f0a0e38671e89a492c44d005b5545b830a961575bbd8336d42869ff71066d + sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" url: "https://pub.dev" source: hosted - version: "8.12.0" + version: "8.12.3" characters: dependency: transitive description: @@ -233,14 +233,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.2" + code_assets: + dependency: transitive + description: + name: code_assets + sha256: ae0db647e668cbb295a3527f0938e4039e004c80099dce2f964102373f5ce0b5 + url: "https://pub.dev" + source: hosted + version: "0.19.10" code_builder: dependency: transitive description: name: code_builder - sha256: "11654819532ba94c34de52ff5feb52bd81cba1de00ef2ed622fd50295f9d4243" + sha256: "6a6cab2ba4680d6423f34a9b972a4c9a94ebe1b62ecec4e1a1f2cba91fd1319d" url: "https://pub.dev" source: hosted - version: "4.11.0" + version: "4.11.1" collection: dependency: "direct main" description: @@ -277,18 +285,18 @@ packages: dependency: transitive description: name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" url: "https://pub.dev" source: hosted - version: "0.3.4+2" + version: "0.3.5+1" crypto: dependency: transitive description: name: crypto - sha256: "1e445881f28f22d6140f181e07737b22f1e099a5e1ff94b0af2f9e4a463f4855" + sha256: c8ea0233063ba03258fbcf2ca4d6dadfefe14f02fab57702265467a19f27fadf url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.0.7" csslib: dependency: transitive description: @@ -437,10 +445,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" file: dependency: transitive description: @@ -498,10 +506,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: "7ed76be64e8a7d01dfdf250b8434618e2a028c9dfa2a3c41dc9b531d4b3fc8a5" + sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875" url: "https://pub.dev" source: hosted - version: "19.4.2" + version: "19.5.0" flutter_local_notifications_linux: dependency: transitive description: @@ -530,18 +538,18 @@ packages: dependency: "direct main" description: name: flutter_native_splash - sha256: "8321a6d11a8d13977fa780c89de8d257cce3d841eecfb7a4cadffcc4f12d82dc" + sha256: "4fb9f4113350d3a80841ce05ebf1976a36de622af7d19aca0ca9a9911c7ff002" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.7" flutter_plugin_android_lifecycle: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: b0694b7fb1689b0e6cc193b3f1fcac6423c4f93c74fb20b806c6b6f196db0c31 + sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 url: "https://pub.dev" source: hosted - version: "2.0.30" + version: "2.0.33" flutter_riverpod: dependency: "direct main" description: @@ -610,10 +618,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: b9c2ad5872518a27507ab432d1fb97e8813b05f0fc693f9d40fad06d073e0678 + sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.3" flutter_test: dependency: "direct dev" description: flutter @@ -688,6 +696,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + hooks: + dependency: transitive + description: + name: hooks + sha256: "5410b9f4f6c9f01e8ff0eb81c9801ea13a3c3d39f8f0b1613cda08e27eab3c18" + url: "https://pub.dev" + source: hosted + version: "0.20.5" hooks_riverpod: dependency: "direct main" description: @@ -716,10 +732,10 @@ packages: dependency: "direct main" description: name: http - sha256: bb2ce4590bc2667c96f318d68cac1b5a7987ec819351d32b1c987239a815e007 + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.5.0" + version: "1.6.0" http_multi_server: dependency: transitive description: @@ -740,10 +756,10 @@ packages: dependency: transitive description: name: image - sha256: "4e973fcf4caae1a4be2fa0a13157aa38a8f9cb049db6529aa00b4d71abc4d928" + sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" url: "https://pub.dev" source: hosted - version: "4.5.4" + version: "4.7.2" intl: dependency: transitive description: @@ -828,26 +844,26 @@ packages: dependency: transitive description: name: local_auth_android - sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3" + sha256: a0bdfcc0607050a26ef5b31d6b4b254581c3d3ce3c1816ab4d4f4a9173e84467 url: "https://pub.dev" source: hosted - version: "1.0.52" + version: "1.0.56" local_auth_darwin: dependency: transitive description: name: local_auth_darwin - sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055" + sha256: "699873970067a40ef2f2c09b4c72eb1cfef64224ef041b3df9fdc5c4c1f91f49" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.6.1" local_auth_platform_interface: dependency: transitive description: name: local_auth_platform_interface - sha256: "1b842ff177a7068442eae093b64abe3592f816afd2a533c0ebcdbe40f9d2075a" + sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 url: "https://pub.dev" source: hosted - version: "1.0.10" + version: "1.1.0" local_auth_windows: dependency: transitive description: @@ -900,10 +916,18 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: "5e7e09d904dc01de071b79b3f3789b302b0ed3c9c963109cd3f83ad90de62ecf" + sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044 + url: "https://pub.dev" + source: hosted + version: "7.1.4" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: f8872ea6c7a50ce08db9ae280ca2b8efdd973157ce462826c82f3c3051d154ce url: "https://pub.dev" source: hosted - version: "7.1.2" + version: "0.17.2" node_preamble: dependency: transitive description: @@ -912,6 +936,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + objective_c: + dependency: transitive + description: + name: objective_c + sha256: "55eb67ede1002d9771b3f9264d2c9d30bc364f0267bc1c6cc0883280d5f0c7cb" + url: "https://pub.dev" + source: hosted + version: "9.2.2" package_config: dependency: transitive description: @@ -964,18 +996,18 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "993381400e94d18469750e5b9dcb8206f15bc09f9da86b9e44a9b0092a0066db" + sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e url: "https://pub.dev" source: hosted - version: "2.2.18" + version: "2.2.22" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "16eef174aacb07e09c351502740fa6254c165757638eba1e9116b0a781201bbd" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.6.0" path_provider_linux: dependency: transitive description: @@ -1180,26 +1212,26 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" + sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e + sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" url: "https://pub.dev" source: hosted - version: "2.4.13" + version: "2.4.18" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.6" shared_preferences_linux: dependency: transitive description: @@ -1309,22 +1341,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - sprintf: - dependency: transitive - description: - name: sprintf - sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" - url: "https://pub.dev" - source: hosted - version: "7.0.0" sqlite3: dependency: transitive description: name: sqlite3 - sha256: f393d92c71bdcc118d6203d07c991b9be0f84b1a6f89dd4f7eed348131329924 + sha256: "3145bd74dcdb4fd6f5c6dda4d4e4490a8087d7f286a14dee5d37087290f0f8a2" url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.9.4" sqlite3_flutter_libs: dependency: "direct main" description: @@ -1481,10 +1505,10 @@ packages: dependency: transitive description: name: universal_io - sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + sha256: f63cbc48103236abf48e345e07a03ce5757ea86285ed313a6a032596ed9301e2 url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.3.1" url_launcher: dependency: "direct main" description: @@ -1497,34 +1521,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "81777b08c498a292d93ff2feead633174c386291e35612f8da438d6e92c4447e" + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" url: "https://pub.dev" source: hosted - version: "6.3.20" + version: "6.3.28" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: d80b3f567a617cb923546034cc94bfe44eb15f989fe670b37f26abdb9d939cb7 + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "6.3.6" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: c043a77d6600ac9c38300567f33ef12b0ef4f4783a2c1f00231d2b1941fea13f + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.3" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -1537,26 +1561,26 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" uuid: dependency: "direct main" description: name: uuid - sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 url: "https://pub.dev" source: hosted - version: "4.5.1" + version: "4.5.2" vector_graphics: dependency: transitive description: @@ -1593,18 +1617,18 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.0.2" watcher: dependency: transitive description: name: watcher - sha256: "5bf046f41320ac97a469d506261797f35254fa61c641741ef32dacda98b7d39c" + sha256: "1398c9f081a753f9226febe8900fce8f7d0a67163334e1c94a2438339d79d635" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "1.2.1" web: dependency: transitive description: @@ -1641,10 +1665,10 @@ packages: dependency: transitive description: name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.14.0" + version: "5.15.0" win32_registry: dependency: transitive description: @@ -1693,5 +1717,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.8.1 <4.0.0" - flutter: ">=3.32.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" From f31248daf52f3477c19fa46242c5e4c1483e5101 Mon Sep 17 00:00:00 2001 From: Adam Date: Wed, 4 Feb 2026 14:04:14 +0100 Subject: [PATCH 06/44] iOS: clamp IPv6 prefix to /120 (#170) --- client/ios/VPNExtension/IpAddrMask.swift | 76 ++++++++++++------ .../VPNExtension/TunnelConfiguration.swift | 77 +++++++++++++++---- client/ios/boringtun | 2 +- client/pubspec.lock | 36 ++++----- client/pubspec.yaml | 43 +++++------ 5 files changed, 153 insertions(+), 81 deletions(-) diff --git a/client/ios/VPNExtension/IpAddrMask.swift b/client/ios/VPNExtension/IpAddrMask.swift index 53a0a56..50a5ef9 100644 --- a/client/ios/VPNExtension/IpAddrMask.swift +++ b/client/ios/VPNExtension/IpAddrMask.swift @@ -51,28 +51,34 @@ struct IpAddrMask: Codable, Equatable { let address_data = try values.decode(Data.self, forKey: .address) switch address_data.count { - case 4: - guard let ipv4 = IPv4Address(address_data) else { - throw DecodingError - .dataCorrupted(DecodingError.Context( + case 4: + guard let ipv4 = IPv4Address(address_data) else { + throw + DecodingError + .dataCorrupted( + DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "Unable to decode IP v4 address" )) - } - address = ipv4 - case 16: - guard let ipv6 = IPv6Address(address_data) else { - throw DecodingError - .dataCorrupted(DecodingError.Context( + } + address = ipv4 + case 16: + guard let ipv6 = IPv6Address(address_data) else { + throw + DecodingError + .dataCorrupted( + DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "Unable to decode IP v6 address" )) - } - address = ipv6 - default: - throw DecodingError.typeMismatch(IpAddrMask.self, DecodingError.Context( + } + address = ipv6 + default: + throw DecodingError.typeMismatch( + IpAddrMask.self, + DecodingError.Context( codingPath: decoder.codingPath, debugDescription: "Invalid IP address length" )) } @@ -96,17 +102,18 @@ struct IpAddrMask: Codable, Equatable { // Note: UInt128 is available since iOS 18. Use UInt64 implementation. if address is IPv6Address { var bytes = Data(count: 16) - let (mask_upper, mask_lower) = if cidr < 64 { - ( - cidr == 0 ? UInt64.min : UInt64.max << (64 - cidr), - UInt64.min - ) - } else { - ( - UInt64.max, - (cidr - 64) == 0 ? UInt64.min : UInt64.max << (128 - cidr) - ) - } + let (mask_upper, mask_lower) = + if cidr < 64 { + ( + cidr == 0 ? UInt64.min : UInt64.max << (64 - cidr), + UInt64.min + ) + } else { + ( + UInt64.max, + (cidr - 64) == 0 ? UInt64.min : UInt64.max << (128 - cidr) + ) + } for i in 0...7 { bytes[i] = UInt8(truncatingIfNeeded: mask_upper >> (56 - i * 8)) } @@ -117,4 +124,23 @@ struct IpAddrMask: Codable, Equatable { } fatalError() } + + /// Return address with the mask applied. + func maskedAddress() -> IPAddress { + let subnet = mask().rawValue + var masked = Data(address.rawValue) + if subnet.count != masked.count { + fatalError() + } + for i in 0.. ([NEIPv4Route], [NEIPv6Route]) { + var ipv4IncludedRoutes = [NEIPv4Route]() + var ipv6IncludedRoutes = [NEIPv6Route]() + + // Routes to interface addresses. + for addr_mask in interface.addresses { + if addr_mask.address is IPv4Address { + let route = NEIPv4Route( + destinationAddress: "\(addr_mask.maskedAddress())", + subnetMask: "\(addr_mask.mask())") + route.gatewayAddress = "\(addr_mask.address)" + ipv4IncludedRoutes.append(route) + } else if addr_mask.address is IPv6Address { + let route = NEIPv6Route( + destinationAddress: "\(addr_mask.maskedAddress())", + networkPrefixLength: NSNumber(value: addr_mask.cidr) + ) + route.gatewayAddress = "\(addr_mask.address)" + ipv6IncludedRoutes.append(route) + } + } + + // Routes to peer's allowed IPs. + for peer in peers { + for addr_mask in peer.allowedIPs { + if addr_mask.address is IPv4Address { + ipv4IncludedRoutes.append( + NEIPv4Route( + destinationAddress: "\(addr_mask.address)", + subnetMask: "\(addr_mask.mask())")) + } else if addr_mask.address is IPv6Address { + ipv6IncludedRoutes.append( + NEIPv6Route( + destinationAddress: "\(addr_mask.address)", + networkPrefixLength: NSNumber(value: addr_mask.cidr))) + } + } + } + + return (ipv4IncludedRoutes, ipv6IncludedRoutes) + } + /// Helper function allowing to parse comma-separated string of addresses. private func parseAddresses(fromString string: String) -> [IpAddrMask] { var addresses: [IpAddrMask] = [] - for addr in string.split(separator: ",").map({ String($0.trimmingCharacters(in: .whitespaces)) }) { + for addr in string.split(separator: ",").map({ + String($0.trimmingCharacters(in: .whitespaces)) + }) { if let addr_mask = IpAddrMask(fromString: addr) { addresses.append(addr_mask) } @@ -87,9 +132,10 @@ final class TunnelConfiguration: Codable { interface.addresses = self.parseAddresses(fromString: startData.address) // DNS settings - let dnsRecords = startData.dns?.split(separator: ",").map { - $0.trimmingCharacters(in: .whitespaces) - } ?? [] + let dnsRecords = + startData.dns?.split(separator: ",").map { + $0.trimmingCharacters(in: .whitespaces) + } ?? [] if !dnsRecords.isEmpty { for record in dnsRecords { if IPv4Address(record) != nil || IPv6Address(record) != nil { @@ -104,15 +150,16 @@ final class TunnelConfiguration: Codable { peer.preSharedKey = startData.presharedKey peer.endpoint = Endpoint(from: startData.endpoint) peer.persistentKeepAlive = UInt16(startData.keepalive) - peer.allowedIPs = switch startData.traffic { + peer.allowedIPs = + switch startData.traffic { case .All: [ IpAddrMask(address: IPv4Address.any, cidr: 0), - IpAddrMask(address: IPv6Address.any, cidr: 0) + IpAddrMask(address: IPv6Address.any, cidr: 0), ] case .Predefined: self.parseAddresses(fromString: startData.allowedIps) - } + } } } diff --git a/client/ios/boringtun b/client/ios/boringtun index f47e80a..8fe9b1e 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit f47e80a96923733bb9ed2bd5590f882dfb1d9b95 +Subproject commit 8fe9b1edee32e6c1f64b3b2b2c819b199a3a80da diff --git a/client/pubspec.lock b/client/pubspec.lock index 990a071..86f6157 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: code_assets - sha256: ae0db647e668cbb295a3527f0938e4039e004c80099dce2f964102373f5ce0b5 + sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" url: "https://pub.dev" source: hosted - version: "0.19.10" + version: "1.0.0" code_builder: dependency: transitive description: @@ -285,10 +285,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "701dcfc06da0882883a2657c445103380e53e647060ad8d9dfb710c100996608" + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" url: "https://pub.dev" source: hosted - version: "0.3.5+1" + version: "0.3.5+2" crypto: dependency: transitive description: @@ -381,10 +381,10 @@ packages: dependency: "direct main" description: name: dio - sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9 + sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 url: "https://pub.dev" source: hosted - version: "5.9.0" + version: "5.9.1" dio_cookie_manager: dependency: "direct main" description: @@ -700,10 +700,10 @@ packages: dependency: transitive description: name: hooks - sha256: "5410b9f4f6c9f01e8ff0eb81c9801ea13a3c3d39f8f0b1613cda08e27eab3c18" + sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" url: "https://pub.dev" source: hosted - version: "0.20.5" + version: "1.0.1" hooks_riverpod: dependency: "direct main" description: @@ -828,10 +828,10 @@ packages: dependency: transitive description: name: lints - sha256: a5e2b223cb7c9c8efdc663ef484fdd95bb243bff242ef5b13e26883547fce9a0 + sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.0" local_auth: dependency: "direct main" description: @@ -924,10 +924,10 @@ packages: dependency: transitive description: name: native_toolchain_c - sha256: f8872ea6c7a50ce08db9ae280ca2b8efdd973157ce462826c82f3c3051d154ce + sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" url: "https://pub.dev" source: hosted - version: "0.17.2" + version: "0.17.4" node_preamble: dependency: transitive description: @@ -940,10 +940,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "55eb67ede1002d9771b3f9264d2c9d30bc364f0267bc1c6cc0883280d5f0c7cb" + sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e" url: "https://pub.dev" source: hosted - version: "9.2.2" + version: "9.2.5" package_config: dependency: transitive description: @@ -1220,10 +1220,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: "83af5c682796c0f7719c2bbf74792d113e40ae97981b8f266fa84574573556bc" + sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f url: "https://pub.dev" source: hosted - version: "2.4.18" + version: "2.4.20" shared_preferences_foundation: dependency: transitive description: @@ -1601,10 +1601,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: d354a7ec6931e6047785f4db12a1f61ec3d43b207fc0790f863818543f8ff0dc + sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b" url: "https://pub.dev" source: hosted - version: "1.1.19" + version: "1.1.20" vector_math: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index ad1c57d..a307e5e 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -2,7 +2,7 @@ name: mobile description: "Defguard mobile client" # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: "none" # Remove this line if you wish to publish to pub.dev # The following defines the version and build number for your application. # A version number is three numbers separated by dots, like 1.2.43 @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.0+1 +version: 1.6.1+1 environment: sdk: ^3.8.1 @@ -101,34 +101,33 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # The following line ensures that the Material Icons font is # included with your application, so that you can use the icons in # the material Icons class. uses-material-design: true assets: - - assets/icons/ + - assets/icons/ fonts: - - family: Roboto - fonts: - - asset: assets/fonts/roboto-400.ttf - weight: 400 - - asset: assets/fonts/roboto-500.ttf - weight: 500 - - asset: assets/fonts/roboto-600.ttf - weight: 600 - - family: Poppins - fonts: - - asset: assets/fonts/poppins-300.ttf - weight: 300 - - asset: assets/fonts/poppins-400.ttf - weight: 400 - - asset: assets/fonts/poppins-500.ttf - weight: 500 - - asset: assets/fonts/poppins-600.ttf - weight: 600 + - family: Roboto + fonts: + - asset: assets/fonts/roboto-400.ttf + weight: 400 + - asset: assets/fonts/roboto-500.ttf + weight: 500 + - asset: assets/fonts/roboto-600.ttf + weight: 600 + - family: Poppins + fonts: + - asset: assets/fonts/poppins-300.ttf + weight: 300 + - asset: assets/fonts/poppins-400.ttf + weight: 400 + - asset: assets/fonts/poppins-500.ttf + weight: 500 + - asset: assets/fonts/poppins-600.ttf + weight: 600 # To add assets to your application, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg From bdfebf8791d046106fbe24fa91711c67bf55e793 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 12 Feb 2026 14:51:53 +0100 Subject: [PATCH 07/44] Upgrade BoringTun bindings (#172) --- .github/workflows/build.yaml | 6 +++++- .github/workflows/lint-and-test.yaml | 2 ++ .github/workflows/sbom.yaml | 2 +- client/ios/Runner.xcodeproj/project.pbxproj | 15 ++++++--------- client/ios/boringtun | 2 +- client/pubspec.lock | 20 ++++++++++---------- 6 files changed, 25 insertions(+), 22 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 2d5f473..65dcf45 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,7 +29,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.7 + flutter-version: 3.38.9 - name: Use homebrew ruby run: | @@ -76,6 +76,8 @@ jobs: working-directory: ./client steps: - uses: actions/checkout@v6 + with: + submodules: "recursive" - name: Set up Java uses: actions/setup-java@v3 @@ -140,6 +142,8 @@ jobs: working-directory: ./client steps: - uses: actions/checkout@v6 + with: + submodules: "recursive" - name: Set up Java uses: actions/setup-java@v3 diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index c99855d..28834a6 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -30,6 +30,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v6 + with: + submodules: "recursive" - name: Scan code with Trivy uses: aquasecurity/trivy-action@0.33.1 diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index e827610..fd1cd50 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -32,7 +32,7 @@ jobs: uses: actions/checkout@v6 with: ref: ${{ steps.vars.outputs.TAG_NAME }} - submodules: recursive + submodules: "recursive" - name: Create SBOM with Trivy uses: aquasecurity/trivy-action@0.33.1 diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 528e33b..1e87f48 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -642,10 +642,9 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/VPNExtension/BoringTun", - "$(PROJECT_DIR)/VPNExtension/BoringTun-old", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.6.1; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -660,7 +659,7 @@ SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; - SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/boringtunFFI.h"; + SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = ""; @@ -697,10 +696,9 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/VPNExtension/BoringTun", - "$(PROJECT_DIR)/VPNExtension/BoringTun-old", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.6.1; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -713,7 +711,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; - SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/boringtunFFI.h"; + SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = ""; TARGETED_DEVICE_FAMILY = "1,2"; @@ -749,10 +747,9 @@ LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/VPNExtension/BoringTun", - "$(PROJECT_DIR)/VPNExtension/BoringTun-old", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.0; + MARKETING_VERSION = 1.6.1; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -765,7 +762,7 @@ SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; - SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/boringtunFFI.h"; + SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; SWIFT_VERSION = 5.0; SYSTEM_HEADER_SEARCH_PATHS = ""; TARGETED_DEVICE_FAMILY = "1,2"; diff --git a/client/ios/boringtun b/client/ios/boringtun index 8fe9b1e..4645349 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit 8fe9b1edee32e6c1f64b3b2b2c819b199a3a80da +Subproject commit 46453492245605418b13b79019b27a7b4427349b diff --git a/client/pubspec.lock b/client/pubspec.lock index 86f6157..7b27e0a 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -357,10 +357,10 @@ packages: dependency: transitive description: name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 url: "https://pub.dev" source: hosted - version: "0.7.11" + version: "0.7.12" device_info_plus: dependency: "direct main" description: @@ -445,10 +445,10 @@ packages: dependency: transitive description: name: ffi - sha256: d07d37192dbf97461359c1518788f203b0c9102cfd2c35a716b823741219542c + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" file: dependency: transitive description: @@ -940,10 +940,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "983c7fa1501f6dcc0cb7af4e42072e9993cb28d73604d25ebf4dab08165d997e" + sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" url: "https://pub.dev" source: hosted - version: "9.2.5" + version: "9.3.0" package_config: dependency: transitive description: @@ -1337,10 +1337,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" sqlite3: dependency: transitive description: @@ -1529,10 +1529,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + sha256: b1aca26728b7cc7a3af971bb6f601554a8ae9df2e0a006de8450ba06a17ad36a url: "https://pub.dev" source: hosted - version: "6.3.6" + version: "6.4.0" url_launcher_linux: dependency: transitive description: From 94f54d402149b8c584a9608d768d5a4e8d212f01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 13 Feb 2026 11:32:49 +0100 Subject: [PATCH 08/44] Update BoringTun with better bindings.sh --- client/.gitignore | 1 - client/ios/boringtun | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/client/.gitignore b/client/.gitignore index 72abeec..975efdc 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -50,5 +50,4 @@ app.*.map.json .envrc local.properties -ios/boringtun ios/VPNExtension/BoringTun diff --git a/client/ios/boringtun b/client/ios/boringtun index 4645349..df20f08 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit 46453492245605418b13b79019b27a7b4427349b +Subproject commit df20f088a306b449786441a911da69621b8ee2fe From 1a606e5c6398eddafaaad61e82920fdc5c747870 Mon Sep 17 00:00:00 2001 From: jakub-tldr <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 13 Feb 2026 14:37:34 +0100 Subject: [PATCH 09/44] Version reporting (#173) --- README.md | 33 ++-- client/ios/Podfile.lock | 28 ++-- .../data/proto/client_platform_info.pb.dart | 148 ++++++++++++++++++ .../proto/client_platform_info.pbenum.dart | 11 ++ .../proto/client_platform_info.pbjson.dart | 77 +++++++++ client/lib/open/api.dart | 46 ++++++ .../add_instance/add_instance_screen.dart | 8 +- client/proto/client_platform_info.proto | 13 ++ client/pubspec.lock | 8 + client/pubspec.yaml | 1 + 10 files changed, 338 insertions(+), 35 deletions(-) create mode 100644 client/lib/data/proto/client_platform_info.pb.dart create mode 100644 client/lib/data/proto/client_platform_info.pbenum.dart create mode 100644 client/lib/data/proto/client_platform_info.pbjson.dart create mode 100644 client/proto/client_platform_info.proto diff --git a/README.md b/README.md index c166c55..6bf4e75 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,16 @@ The **DefGuard Mobile Client** is a secure, self-hosted **WireGuard VPN mobile c This open-source, cross-platform VPN app supports easy QR code onboarding and flexible traffic routing to meet diverse secure remote access needs. DefGuard is part of a modular ecosystem built for VPN orchestration and identity management . Defguard provides a mobile VPN client with biometrics and TOTP. ## Key Features -- Secure **WireGuard VPN mobile client** with **Multi-Factor Authentication (MFA)** -- Internal SSO/OIDC support with biometrics, TOTP, email verification -- External SSO support: Google, Okta, Microsoft EntraID, JumpCloud, and more -- Quick and easy onboarding via secure **QR code VPN onboarding** or URL/token -- Flexible traffic routing: all traffic via VPN or selective routing -- Real-time synchronization of VPN configurations with the DefGuard server -- Native **cross-platform VPN app** support for **Android VPN client** and iOS VPN client -- Fully **self-hosted VPN solution** for ultimate privacy and control -- Open-source codebase for transparency and customization + +- Secure **WireGuard VPN mobile client** with **Multi-Factor Authentication (MFA)** +- Internal SSO/OIDC support with biometrics, TOTP, email verification +- External SSO support: Google, Okta, Microsoft EntraID, JumpCloud, and more +- Quick and easy onboarding via secure **QR code VPN onboarding** or URL/token +- Flexible traffic routing: all traffic via VPN or selective routing +- Real-time synchronization of VPN configurations with the DefGuard server +- Native **cross-platform VPN app** support for **Android VPN client** and iOS VPN client +- Fully **self-hosted VPN solution** for ultimate privacy and control +- Open-source codebase for transparency and customization ## Screenshots @@ -24,24 +25,26 @@ This open-source, cross-platform VPN app supports easy QR code onboarding and fl Instance list MFA defguard IdP - ## Getting Started -You need to have a running [Defguard Server](https://github.com/DefGuard/defguard) to use the mobile app. +You need to have a running [Defguard Core](https://github.com/DefGuard/defguard) to use the mobile app. ### Install the App Join closed beta for iOS or Android. #### Android + - Download from [Google Play](https://play.google.com/store/apps/details?id=net.defguard.mobile) #### iOS -- Available soon on the [App Store](https://testflight.apple.com/join/Jvdhkt7h) -Documentation available at : [https://docs.defguard.net/help/mobile-client](https://docs.defguard.net/help/mobile-client) +- Available soon on the [App Store](https://apps.apple.com/us/app/defguard-vpn-client/id6748068630) + +Documentation available at : [https://docs.defguard.net/using-defguard-for-end-users/mobile-client](https://docs.defguard.net/using-defguard-for-end-users/mobile-client) ## About DefGuard -DefGuard is a comprehensive platform offering **secure remote access**, **identity management**, and VPN orchestration with a focus on security using **multi-factor authentication for VPN**. -Visit defguard.net for more information. \ No newline at end of file +DefGuard is a comprehensive platform offering **secure remote access**, **identity management**, and VPN orchestration with a focus on security using **multi-factor authentication for VPN**. + +Visit defguard.net for more information. diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index fff8239..fec2a9d 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -107,22 +107,22 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wireguard_plugin/darwin" SPEC CHECKSUMS: - app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 - device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 + app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f - flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 - mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 + flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 - url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa - wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e + sqlite3_flutter_libs: 52ecc4dfaae71f496da86159263dbce5d23a051a + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + wireguard_plugin: c2f4d5382eecd7bcd07c027642c75e0569f91ff8 PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/lib/data/proto/client_platform_info.pb.dart b/client/lib/data/proto/client_platform_info.pb.dart new file mode 100644 index 0000000..bdfab62 --- /dev/null +++ b/client/lib/data/proto/client_platform_info.pb.dart @@ -0,0 +1,148 @@ +// This is a generated file - do not edit. +// +// Generated from client_platform_info.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports + +import 'dart:core' as $core; + +import 'package:protobuf/protobuf.dart' as $pb; + +export 'package:protobuf/protobuf.dart' show GeneratedMessageGenericExtensions; + +class ClientPlatformInfo extends $pb.GeneratedMessage { + factory ClientPlatformInfo({ + $core.String? osFamily, + $core.String? osType, + $core.String? version, + $core.String? edition, + $core.String? codename, + $core.String? bitness, + $core.String? architecture, + }) { + final result = create(); + if (osFamily != null) result.osFamily = osFamily; + if (osType != null) result.osType = osType; + if (version != null) result.version = version; + if (edition != null) result.edition = edition; + if (codename != null) result.codename = codename; + if (bitness != null) result.bitness = bitness; + if (architecture != null) result.architecture = architecture; + return result; + } + + ClientPlatformInfo._(); + + factory ClientPlatformInfo.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory ClientPlatformInfo.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'ClientPlatformInfo', + package: const $pb.PackageName(_omitMessageNames ? '' : 'defguard.proxy'), + createEmptyInstance: create) + ..aOS(1, _omitFieldNames ? '' : 'osFamily') + ..aOS(2, _omitFieldNames ? '' : 'osType') + ..aOS(3, _omitFieldNames ? '' : 'version') + ..aOS(4, _omitFieldNames ? '' : 'edition') + ..aOS(5, _omitFieldNames ? '' : 'codename') + ..aOS(6, _omitFieldNames ? '' : 'bitness') + ..aOS(7, _omitFieldNames ? '' : 'architecture') + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientPlatformInfo clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + ClientPlatformInfo copyWith(void Function(ClientPlatformInfo) updates) => + super.copyWith((message) => updates(message as ClientPlatformInfo)) + as ClientPlatformInfo; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static ClientPlatformInfo create() => ClientPlatformInfo._(); + @$core.override + ClientPlatformInfo createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static ClientPlatformInfo getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static ClientPlatformInfo? _defaultInstance; + + @$pb.TagNumber(1) + $core.String get osFamily => $_getSZ(0); + @$pb.TagNumber(1) + set osFamily($core.String value) => $_setString(0, value); + @$pb.TagNumber(1) + $core.bool hasOsFamily() => $_has(0); + @$pb.TagNumber(1) + void clearOsFamily() => $_clearField(1); + + @$pb.TagNumber(2) + $core.String get osType => $_getSZ(1); + @$pb.TagNumber(2) + set osType($core.String value) => $_setString(1, value); + @$pb.TagNumber(2) + $core.bool hasOsType() => $_has(1); + @$pb.TagNumber(2) + void clearOsType() => $_clearField(2); + + @$pb.TagNumber(3) + $core.String get version => $_getSZ(2); + @$pb.TagNumber(3) + set version($core.String value) => $_setString(2, value); + @$pb.TagNumber(3) + $core.bool hasVersion() => $_has(2); + @$pb.TagNumber(3) + void clearVersion() => $_clearField(3); + + @$pb.TagNumber(4) + $core.String get edition => $_getSZ(3); + @$pb.TagNumber(4) + set edition($core.String value) => $_setString(3, value); + @$pb.TagNumber(4) + $core.bool hasEdition() => $_has(3); + @$pb.TagNumber(4) + void clearEdition() => $_clearField(4); + + @$pb.TagNumber(5) + $core.String get codename => $_getSZ(4); + @$pb.TagNumber(5) + set codename($core.String value) => $_setString(4, value); + @$pb.TagNumber(5) + $core.bool hasCodename() => $_has(4); + @$pb.TagNumber(5) + void clearCodename() => $_clearField(5); + + @$pb.TagNumber(6) + $core.String get bitness => $_getSZ(5); + @$pb.TagNumber(6) + set bitness($core.String value) => $_setString(5, value); + @$pb.TagNumber(6) + $core.bool hasBitness() => $_has(5); + @$pb.TagNumber(6) + void clearBitness() => $_clearField(6); + + @$pb.TagNumber(7) + $core.String get architecture => $_getSZ(6); + @$pb.TagNumber(7) + set architecture($core.String value) => $_setString(6, value); + @$pb.TagNumber(7) + $core.bool hasArchitecture() => $_has(6); + @$pb.TagNumber(7) + void clearArchitecture() => $_clearField(7); +} + +const $core.bool _omitFieldNames = + $core.bool.fromEnvironment('protobuf.omit_field_names'); +const $core.bool _omitMessageNames = + $core.bool.fromEnvironment('protobuf.omit_message_names'); diff --git a/client/lib/data/proto/client_platform_info.pbenum.dart b/client/lib/data/proto/client_platform_info.pbenum.dart new file mode 100644 index 0000000..6160acd --- /dev/null +++ b/client/lib/data/proto/client_platform_info.pbenum.dart @@ -0,0 +1,11 @@ +// This is a generated file - do not edit. +// +// Generated from client_platform_info.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports diff --git a/client/lib/data/proto/client_platform_info.pbjson.dart b/client/lib/data/proto/client_platform_info.pbjson.dart new file mode 100644 index 0000000..a30a95c --- /dev/null +++ b/client/lib/data/proto/client_platform_info.pbjson.dart @@ -0,0 +1,77 @@ +// This is a generated file - do not edit. +// +// Generated from client_platform_info.proto. + +// @dart = 3.3 + +// ignore_for_file: annotate_overrides, camel_case_types, comment_references +// ignore_for_file: constant_identifier_names +// ignore_for_file: curly_braces_in_flow_control_structures +// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes +// ignore_for_file: non_constant_identifier_names, prefer_relative_imports +// ignore_for_file: unused_import + +import 'dart:convert' as $convert; +import 'dart:core' as $core; +import 'dart:typed_data' as $typed_data; + +@$core.Deprecated('Use clientPlatformInfoDescriptor instead') +const ClientPlatformInfo$json = { + '1': 'ClientPlatformInfo', + '2': [ + {'1': 'os_family', '3': 1, '4': 1, '5': 9, '10': 'osFamily'}, + {'1': 'os_type', '3': 2, '4': 1, '5': 9, '10': 'osType'}, + {'1': 'version', '3': 3, '4': 1, '5': 9, '10': 'version'}, + { + '1': 'edition', + '3': 4, + '4': 1, + '5': 9, + '9': 0, + '10': 'edition', + '17': true + }, + { + '1': 'codename', + '3': 5, + '4': 1, + '5': 9, + '9': 1, + '10': 'codename', + '17': true + }, + { + '1': 'bitness', + '3': 6, + '4': 1, + '5': 9, + '9': 2, + '10': 'bitness', + '17': true + }, + { + '1': 'architecture', + '3': 7, + '4': 1, + '5': 9, + '9': 3, + '10': 'architecture', + '17': true + }, + ], + '8': [ + {'1': '_edition'}, + {'1': '_codename'}, + {'1': '_bitness'}, + {'1': '_architecture'}, + ], +}; + +/// Descriptor for `ClientPlatformInfo`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List clientPlatformInfoDescriptor = $convert.base64Decode( + 'ChJDbGllbnRQbGF0Zm9ybUluZm8SGwoJb3NfZmFtaWx5GAEgASgJUghvc0ZhbWlseRIXCgdvc1' + '90eXBlGAIgASgJUgZvc1R5cGUSGAoHdmVyc2lvbhgDIAEoCVIHdmVyc2lvbhIdCgdlZGl0aW9u' + 'GAQgASgJSABSB2VkaXRpb26IAQESHwoIY29kZW5hbWUYBSABKAlIAVIIY29kZW5hbWWIAQESHQ' + 'oHYml0bmVzcxgGIAEoCUgCUgdiaXRuZXNziAEBEicKDGFyY2hpdGVjdHVyZRgHIAEoCUgDUgxh' + 'cmNoaXRlY3R1cmWIAQFCCgoIX2VkaXRpb25CCwoJX2NvZGVuYW1lQgoKCF9iaXRuZXNzQg8KDV' + '9hcmNoaXRlY3R1cmU='); diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index 6b996bd..6641000 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -1,10 +1,14 @@ +import 'dart:convert'; import 'dart:io'; import 'package:cookie_jar/cookie_jar.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; import 'package:mobile/data/db/enums.dart'; +import 'package:mobile/data/proto/client_platform_info.pb.dart'; import 'package:mobile/data/proxy/config.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:mobile/data/proxy/enrollment.dart'; import 'package:mobile/data/proxy/mfa.dart'; @@ -44,6 +48,48 @@ class _ProxyApi { final cookieJar = CookieJar(); _dio.interceptors.add(CookieManager(cookieJar)); _dio.interceptors.add(TalkerDioLogger(talker: talker)); + _initHeaders(); + } + + Future _initHeaders() async { + try { + final deviceInfo = DeviceInfoPlugin(); + final ClientPlatformInfo platformInfo; + + if (Platform.isAndroid) { + final android = await deviceInfo.androidInfo; + platformInfo = ClientPlatformInfo( + osType: 'Android', + version: android.version.release, + codename: android.version.codename, + architecture: android.supportedAbis.first, + bitness: '64', + ); + } else if (Platform.isIOS) { + final ios = await deviceInfo.iosInfo; + platformInfo = ClientPlatformInfo( + osType: 'iOS', + version: ios.systemVersion, + architecture: 'arm64', + bitness: '64', + ); + } else { + platformInfo = ClientPlatformInfo( + osFamily: Platform.operatingSystem, + osType: Platform.operatingSystem, + version: Platform.operatingSystemVersion, + ); + } + + final platformBytes = platformInfo.writeToBuffer(); + final platformBase64 = base64Encode(platformBytes); + + final packageInfo = await PackageInfo.fromPlatform(); + _dio.options.headers['defguard-client-version'] = packageInfo.version; + _dio.options.headers['defguard-client-platform'] = platformBase64; + } catch (e) { + talker.error("Failed to set client headers", e); + } } Future<(ConfigurationPollResponse?, int?, Headers?)> pollConfiguration( diff --git a/client/lib/open/screens/add_instance/add_instance_screen.dart b/client/lib/open/screens/add_instance/add_instance_screen.dart index df16e36..6c1a27b 100644 --- a/client/lib/open/screens/add_instance/add_instance_screen.dart +++ b/client/lib/open/screens/add_instance/add_instance_screen.dart @@ -77,9 +77,7 @@ class AddInstanceScreen extends HookConsumerWidget { if (isAgreed ?? false) { if (context.mounted) { QRScreenRoute( - QrScreenData( - intent: QrScreenIntent.addInstance, - ), + QrScreenData(intent: QrScreenIntent.addInstance), ).push(context); } } else { @@ -92,9 +90,7 @@ class AddInstanceScreen extends HookConsumerWidget { await asyncPrefs.setBool(agreementPrefsKey, true); if (context.mounted) { QRScreenRoute( - QrScreenData( - intent: QrScreenIntent.addInstance, - ), + QrScreenData(intent: QrScreenIntent.addInstance), ).push(context); } } diff --git a/client/proto/client_platform_info.proto b/client/proto/client_platform_info.proto new file mode 100644 index 0000000..c90a3ef --- /dev/null +++ b/client/proto/client_platform_info.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package defguard.proxy; + +message ClientPlatformInfo { + string os_family = 1; + string os_type = 2; + string version = 3; + optional string edition = 4; + optional string codename = 5; + optional string bitness = 6; + optional string architecture = 7; +} diff --git a/client/pubspec.lock b/client/pubspec.lock index 7b27e0a..1105e88 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -1120,6 +1120,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + protobuf: + dependency: "direct main" + description: + name: protobuf + sha256: "75ec242d22e950bdcc79ee38dd520ce4ee0bc491d7fadc4ea47694604d22bf06" + url: "https://pub.dev" + source: hosted + version: "6.0.0" pub_semver: dependency: "direct main" description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index a307e5e..17fc6ad 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -76,6 +76,7 @@ dependencies: shared_preferences: ^2.5.3 flutter_secure_storage: ^9.2.4 device_info_plus: ^11.5.0 + protobuf: ^6.0.0 dev_dependencies: flutter_test: From 50fc76ea9f3b1f1398d5476e235612dadc099c25 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 23 Feb 2026 11:25:51 +0100 Subject: [PATCH 10/44] Fix IpAddrMask (#176) --- .github/workflows/lint-and-test.yaml | 2 +- .github/workflows/sbom.yaml | 4 +-- client/ios/Podfile.lock | 28 ++++++++-------- client/ios/Runner.xcodeproj/project.pbxproj | 6 ++-- client/ios/VPNExtension/IpAddrMask.swift | 5 ++- client/ios/boringtun | 2 +- client/pubspec.lock | 36 ++++++++++----------- client/pubspec.yaml | 2 +- 8 files changed, 44 insertions(+), 41 deletions(-) diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 28834a6..46bcafa 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -34,7 +34,7 @@ jobs: submodules: "recursive" - name: Scan code with Trivy - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@0.34.1 with: scan-type: "fs" scan-ref: "." diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index fd1cd50..aea291c 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -35,7 +35,7 @@ jobs: submodules: "recursive" - name: Create SBOM with Trivy - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@0.34.1 with: scan-type: "fs" format: "spdx-json" @@ -45,7 +45,7 @@ jobs: scanners: "vuln" - name: Create security advisory file with Trivy - uses: aquasecurity/trivy-action@0.33.1 + uses: aquasecurity/trivy-action@0.34.1 with: scan-type: "fs" format: "json" diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index fec2a9d..fff8239 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -107,22 +107,22 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wireguard_plugin/darwin" SPEC CHECKSUMS: - app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 - local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb - mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 + flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 + local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 + mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 52ecc4dfaae71f496da86159263dbce5d23a051a - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b - wireguard_plugin: c2f4d5382eecd7bcd07c027642c75e0569f91ff8 + sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa + wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 1e87f48..d8b5fda 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -644,7 +644,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.1; + MARKETING_VERSION = 1.6.2; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -698,7 +698,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.1; + MARKETING_VERSION = 1.6.2; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -749,7 +749,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.1; + MARKETING_VERSION = 1.6.2; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; diff --git a/client/ios/VPNExtension/IpAddrMask.swift b/client/ios/VPNExtension/IpAddrMask.swift index 50a5ef9..32f972d 100644 --- a/client/ios/VPNExtension/IpAddrMask.swift +++ b/client/ios/VPNExtension/IpAddrMask.swift @@ -15,17 +15,20 @@ struct IpAddrMask: Codable, Equatable { separator: "/", maxSplits: 1, ) + let default_cidr: UInt8 if let ipv4 = IPv4Address(String(parts[0])) { address = ipv4 + default_cidr = 32 } else if let ipv6 = IPv6Address(String(parts[0])) { address = ipv6 + default_cidr = 128 } else { return nil } if parts.count > 1 { cidr = UInt8(parts[1]) ?? 0 } else { - cidr = 0 + cidr = default_cidr } } diff --git a/client/ios/boringtun b/client/ios/boringtun index df20f08..b990805 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit df20f088a306b449786441a911da69621b8ee2fe +Subproject commit b990805fc1637eeaa401bc156adf0dd28b2e50b8 diff --git a/client/pubspec.lock b/client/pubspec.lock index 1105e88..000de1c 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -77,10 +77,10 @@ packages: dependency: transitive description: name: archive - sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" + sha256: a96e8b390886ee8abb49b7bd3ac8df6f451c621619f52a26e815fdcf568959ff url: "https://pub.dev" source: hosted - version: "4.0.7" + version: "4.0.9" args: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "7931c90b84bc573fef103548e354258ae4c9d28d140e41961df6843c5d60d4d8" + sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" url: "https://pub.dev" source: hosted - version: "8.12.3" + version: "8.12.4" characters: dependency: transitive description: @@ -756,10 +756,10 @@ packages: dependency: transitive description: name: image - sha256: "492bd52f6c4fbb6ee41f781ff27765ce5f627910e1e0cbecfa3d9add5562604c" + sha256: f9881ff4998044947ec38d098bc7c8316ae1186fa786eddffdb867b9bc94dfce url: "https://pub.dev" source: hosted - version: "4.7.2" + version: "4.8.0" intl: dependency: transitive description: @@ -916,10 +916,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: c6184bf2913dd66be244108c9c27ca04b01caf726321c44b0e7a7a1e32d41044 + sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce url: "https://pub.dev" source: hosted - version: "7.1.4" + version: "7.2.0" native_toolchain_c: dependency: transitive description: @@ -1084,10 +1084,10 @@ packages: dependency: transitive description: name: petitparser - sha256: "1a97266a94f7350d30ae522c0af07890c70b8e62c71e8e3920d1db4d23c057d1" + sha256: "91bd59303e9f769f108f8df05e371341b15d59e995e6806aefab827b58336675" url: "https://pub.dev" source: hosted - version: "7.0.1" + version: "7.0.2" platform: dependency: transitive description: @@ -1116,10 +1116,10 @@ packages: dependency: transitive description: name: posix - sha256: "6323a5b0fa688b6a010df4905a56b00181479e6d10534cecfecede2aa55add61" + sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "6.5.0" protobuf: dependency: "direct main" description: @@ -1537,10 +1537,10 @@ packages: dependency: transitive description: name: url_launcher_ios - sha256: b1aca26728b7cc7a3af971bb6f601554a8ae9df2e0a006de8450ba06a17ad36a + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.4.1" url_launcher_linux: dependency: transitive description: @@ -1585,10 +1585,10 @@ packages: dependency: "direct main" description: name: uuid - sha256: a11b666489b1954e01d992f3d601b1804a33937b5a8fe677bd26b8a9f96f96e8 + sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" url: "https://pub.dev" source: hosted - version: "4.5.2" + version: "4.5.3" vector_graphics: dependency: transitive description: @@ -1609,10 +1609,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "201e876b5d52753626af64b6359cd13ac6011b80728731428fd34bc840f71c9b" + sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" url: "https://pub.dev" source: hosted - version: "1.1.20" + version: "1.2.0" vector_math: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 17fc6ad..29822c8 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.1+1 +version: 1.6.2+1 environment: sdk: ^3.8.1 From 1b293213402774590f45800b8def4f2f8a2f4ba0 Mon Sep 17 00:00:00 2001 From: Kamil Chudy Date: Fri, 27 Feb 2026 12:06:17 +0100 Subject: [PATCH 11/44] Added new issue templates (#177) --- .github/ISSUE_TEMPLATE/01-feature-request.yml | 56 ++++++++ .github/ISSUE_TEMPLATE/02-bug.yml | 125 ++++++++++++++++++ .github/ISSUE_TEMPLATE/03-internal.yml | 19 +++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ 4 files changed, 208 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/01-feature-request.yml create mode 100644 .github/ISSUE_TEMPLATE/02-bug.yml create mode 100644 .github/ISSUE_TEMPLATE/03-internal.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/01-feature-request.yml b/.github/ISSUE_TEMPLATE/01-feature-request.yml new file mode 100644 index 0000000..13b9410 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/01-feature-request.yml @@ -0,0 +1,56 @@ +name: Feature request +description: Suggest an idea or improvement for Defguard +title: "[Feature]: " +labels: + - feature +type: feature + +body: + - type: markdown + attributes: + value: | + Thank you for suggesting a feature. Your feedback helps improve Defguard. + + - type: textarea + id: problem + attributes: + label: Problem description + description: What problem are you trying to solve? Attach screenshots or recording if it helps illustrate the problem. + placeholder: | + Describe the limitation, friction, or missing capability. + Example: "Users cannot restrict access based on device posture..." + validations: + required: true + + - type: textarea + id: proposed_solution + attributes: + label: Proposed solution + description: Describe the solution you would like. Attach mockups or diagrams if you have them. + placeholder: | + Describe the desired behavior, API, UI, or workflow. + Include examples if possible. + validations: + required: true + + - type: textarea + id: alternatives + attributes: + label: Alternatives considered + description: Any alternative solutions or workarounds you've considered + placeholder: | + Example: "Currently we use separate locations, but this is hard to manage..." + validations: + required: false + + - type: dropdown + id: impact + attributes: + label: Impact + description: How important is this feature for you? + options: + - Nice to have + - Important + - Critical / blocking our usage + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/02-bug.yml b/.github/ISSUE_TEMPLATE/02-bug.yml new file mode 100644 index 0000000..fd045c3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/02-bug.yml @@ -0,0 +1,125 @@ +name: Bug report +description: Report a problem in Defguard so we can reproduce and fix it. +title: "[Bug]: " +labels: + - bug +type: bug + +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to report a bug. + + **Privacy note:** This issue tracker is public. Do not include sensitive data such as secrets, private keys, tokens, passwords, internal domains, or customer data. All data should be anonymised and any secrets must be redacted. + + - type: textarea + id: summary + attributes: + label: Summary + description: A clear, one-paragraph description of what’s broken. + placeholder: "After enabling MFA for a user, Desktop client cannot start a session; it loops on …" + validations: + required: true + + - type: textarea + id: steps + attributes: + label: Steps to reproduce + description: Numbered steps help us reproduce reliably. Attach screenshots or recordings if they help illustrate the steps. + placeholder: | + 1. Go to … + 2. Click … + 3. Configure … + 4. Observe … + validations: + required: true + + - type: textarea + id: expected + attributes: + label: Expected behavior + description: What should happen if the bug were fixed? + placeholder: "VPN connects successfully and a session is established." + validations: + required: true + + - type: textarea + id: actual + attributes: + label: Actual behavior + description: What happens instead? Include error messages, screenshots, or recordings if they help illustrate the issue. + placeholder: "VPN connection fails with … / UI shows … / API returns …" + validations: + required: true + + - type: input + id: version + attributes: + label: Defguard version + description: Exact versions of all components. + placeholder: "Core: v1.6.0, Gateway: v1.6.0, Edge: v1.6.0, Desktop client: v1.6.0, Mobile client: v1.6.0." + validations: + required: true + + - type: input + id: environment + attributes: + label: Environment details + description: Operating systems of all components. + placeholder: "Core: Ubuntu 24.04, Gateway: Ubuntu 24.04, Edge: Ubuntu 24.04, Desktop client: macOS 15.x, Mobile client: iOS 18." + validations: + required: true + + - type: dropdown + id: deployment + attributes: + label: Deployment / install method + description: How is Defguard installed? + multiple: false + options: + - One-line script + - Standalone packages + - Docker / Docker Compose + - Kubernetes / Helm + - Terraform + - AMI + - Custom + - Not installed (WireGuard only) + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Relevant logs / output + description: Paste only what’s necessary. Please redact secrets (tokens, private IPs, domains) if needed. Enable DEBUG log level if you can (https://docs.defguard.net/support-1/how-to-submit-an-issue#id-1.-enable-debug-logging). + render: shell + placeholder: | + # Core logs + [2024-06-01T12:00:00Z] ERROR: ... + + # Edge logs + [2024-06-01T12:00:00Z] ERROR: ... + + # Gateway logs + [2024-06-01T12:00:00Z] ERROR: ... + + # Desktop client logs (you can find them in the app's settings) + [2024-06-01T12:00:00Z] ERROR: ... + + # Mobile client logs (you can find them in the app's main menu - View Application Logs) + [2024-06-01T12:00:00Z] ERROR: ... + validations: + required: false + + - type: textarea + id: config + attributes: + label: Relevant configuration (redacted) + description: If configuration is involved (LDAP, OIDC, WireGuard, gRPC certs), paste the minimum snippet. + render: yaml + placeholder: | + # Redact secrets/certs/private keys + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/03-internal.yml b/.github/ISSUE_TEMPLATE/03-internal.yml new file mode 100644 index 0000000..97278a0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/03-internal.yml @@ -0,0 +1,19 @@ +name: Internal issue +description: Internal Defguard team use only +labels: + - internal +type: task + +body: + - type: markdown + attributes: + value: | + This template is intended for internal Defguard team use. + + - type: textarea + id: description + attributes: + label: Description + placeholder: Detailed description, context, links, notes + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..7987ea4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Defguard Troubleshooting Guide + url: https://docs.defguard.net/support-1/troubleshooting + about: Make sure to check the troubleshooting guide before submitting an issue. It has solutions for common problems and can help you debug faster. + - name: Open a discussion to get help from the community + url: https://github.com/DefGuard/defguard/discussions/new/choose + about: Having trouble with Defguard deployment or configuration? Reach out to our community for help. From 278d056e23600125e21e468e6666836913d73395 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Wed, 6 May 2026 09:38:01 +0200 Subject: [PATCH 12/44] Use system CA's & fix signing (#181) --- .github/workflows/build.yaml | 14 +++++-- .github/workflows/lint-and-test.yaml | 2 +- .github/workflows/release.yaml | 4 +- .github/workflows/sbom.yaml | 4 +- .../android/app/src/main/AndroidManifest.xml | 1 + .../main/res/xml/network_security_config.xml | 9 +++++ client/lib/open/api.dart | 2 + client/pubspec.lock | 40 +++++++++++++++++++ client/pubspec.yaml | 1 + 9 files changed, 69 insertions(+), 8 deletions(-) create mode 100644 client/android/app/src/main/res/xml/network_security_config.xml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 65dcf45..828c357 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -104,6 +104,7 @@ jobs: run: flutter build appbundle --release --build-number=${{ github.run_number }} - name: Sign AAB + id: sign_aab uses: r0adkll/sign-android-release@v1 with: releaseDirectory: client/build/app/outputs/bundle/release @@ -121,15 +122,18 @@ jobs: with: serviceAccountJsonPlainText: "${{ secrets.ANDROID_SERVICE_ACCOUNT_JSON }}" packageName: net.defguard.mobile - releaseFiles: client/build/app/outputs/bundle/release/app-release.aab + releaseFiles: ${{ steps.sign_aab.outputs.signedReleaseFile }} track: internal + - name: Rename AAB + run: cp "${{ steps.sign_aab.outputs.signedReleaseFile }}" Defguard.aab + - name: Upload Android Artifact uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags/') with: name: android-app - path: "client/build/app/outputs/bundle/release/app-release.aab" + path: "client/Defguard.aab" retention-days: 2 build-android-apk: @@ -173,6 +177,7 @@ jobs: run: flutter build apk --release --build-number=${{ github.run_number }} - name: Sign APK + id: sign_apk uses: r0adkll/sign-android-release@v1 with: releaseDirectory: client/build/app/outputs/flutter-apk @@ -181,12 +186,15 @@ jobs: keyStorePassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" keyPassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" + - name: Rename APK + run: cp "${{ steps.sign_apk.outputs.signedReleaseFile }}" Defguard.apk + - name: Upload Android Artifact uses: actions/upload-artifact@v4 if: startsWith(github.ref, 'refs/tags/') with: name: android-app-apk - path: "client/build/app/outputs/flutter-apk/app-release.apk" + path: "client/Defguard.apk" retention-days: 2 release: diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 46bcafa..2b719f3 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -34,7 +34,7 @@ jobs: submodules: "recursive" - name: Scan code with Trivy - uses: aquasecurity/trivy-action@0.34.1 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" scan-ref: "." diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 103af53..a8d647f 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -23,8 +23,8 @@ jobs: draft: true files: | ./artifacts/Defguard.ipa - ./artifacts/app-release.aab - ./artifacts/app-release.apk + ./artifacts/Defguard.aab + ./artifacts/Defguard.apk create-sbom: needs: [create-release] diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index aea291c..ec8c5cf 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -35,7 +35,7 @@ jobs: submodules: "recursive" - name: Create SBOM with Trivy - uses: aquasecurity/trivy-action@0.34.1 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" format: "spdx-json" @@ -45,7 +45,7 @@ jobs: scanners: "vuln" - name: Create security advisory file with Trivy - uses: aquasecurity/trivy-action@0.34.1 + uses: aquasecurity/trivy-action@v0.36.0 with: scan-type: "fs" format: "json" diff --git a/client/android/app/src/main/AndroidManifest.xml b/client/android/app/src/main/AndroidManifest.xml index b602226..72aadc7 100644 --- a/client/android/app/src/main/AndroidManifest.xml +++ b/client/android/app/src/main/AndroidManifest.xml @@ -17,6 +17,7 @@ android:enableOnBackInvokedCallback="true" android:allowBackup="false" android:fullBackupContent="false" + android:networkSecurityConfig="@xml/network_security_config" android:dataExtractionRules="@xml/data_extraction_rules"> + + + + + + + + diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index 6641000..1416845 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -5,6 +5,7 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; +import 'package:native_dio_adapter/native_dio_adapter.dart'; import 'package:mobile/data/db/enums.dart'; import 'package:mobile/data/proto/client_platform_info.pb.dart'; import 'package:mobile/data/proxy/config.dart'; @@ -45,6 +46,7 @@ class _ProxyApi { ); _ProxyApi._internal() { + _dio.httpClientAdapter = NativeAdapter(); final cookieJar = CookieJar(); _dio.interceptors.add(CookieManager(cookieJar)); _dio.interceptors.add(TalkerDioLogger(talker: talker)); diff --git a/client/pubspec.lock b/client/pubspec.lock index 000de1c..dfff894 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -281,6 +281,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + cronet_http: + dependency: transitive + description: + name: cronet_http + sha256: "8e77bc6f203e0bc9126e6a9092508a3435dbcb04da3b53ed1a358909385c5e0e" + url: "https://pub.dev" + source: hosted + version: "1.8.0" cross_file: dependency: transitive description: @@ -305,6 +313,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.2" + cupertino_http: + dependency: transitive + description: + name: cupertino_http + sha256: "82cbec60c90bf785a047a9525688b6dacac444e177e1d5a5876963d3c50369e8" + url: "https://pub.dev" + source: hosted + version: "2.4.0" cupertino_icons: dependency: "direct main" description: @@ -752,6 +768,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + http_profile: + dependency: transitive + description: + name: http_profile + sha256: "7e679e355b09aaee2ab5010915c932cce3f2d1c11c3b2dc177891687014ffa78" + url: "https://pub.dev" + source: hosted + version: "0.1.0" image: dependency: transitive description: @@ -776,6 +800,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + jni: + dependency: transitive + description: + name: jni + sha256: "8706a77e94c76fe9ec9315e18949cc9479cc03af97085ca9c1077b61323ea12d" + url: "https://pub.dev" + source: hosted + version: "0.15.2" js: dependency: transitive description: @@ -920,6 +952,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.2.0" + native_dio_adapter: + dependency: "direct main" + description: + name: native_dio_adapter + sha256: "9bbfa5221fd287eb063962bbe6534290e5f87933e576fac210149fb80253b89a" + url: "https://pub.dev" + source: hosted + version: "1.5.1" native_toolchain_c: dependency: transitive description: diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 29822c8..5c8feae 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -50,6 +50,7 @@ dependencies: cookie_jar: ^4.0.8 dio: ^5.8.0+1 dio_cookie_manager: ^3.2.0 + native_dio_adapter: ^1.3.0 flutter_native_splash: ^2.4.6 flutter_launcher_icons: ^0.14.4 flutter_svg: ^2.1.0 From 9fc08348b691302f9875507f6982f1ec6d2df1a7 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 15 May 2026 13:01:40 +0200 Subject: [PATCH 13/44] Display OpenID provider name (#185) --- .github/workflows/build.yaml | 4 +- .../defguard/drift_schema_v3.json | 1 + client/ios/Podfile.lock | 35 +- client/lib/data/db/database.dart | 11 +- client/lib/data/db/database.g.dart | 86 +- client/lib/data/db/database.steps.dart | 110 +- client/lib/data/proxy/enrollment.dart | 16 +- client/lib/data/proxy/enrollment.g.dart | 7 + .../screens/mfa/openid_mfa_screen.dart | 28 +- .../screens/name_device_screen.dart | 5 +- .../instance/services/tunnel_service.dart | 13 +- .../test/drift/defguard/generated/schema.dart | 5 +- .../drift/defguard/generated/schema_v3.dart | 1321 +++++++++++++++++ 13 files changed, 1602 insertions(+), 40 deletions(-) create mode 100644 client/drift_schemas/defguard/drift_schema_v3.json create mode 100644 client/test/drift/defguard/generated/schema_v3.dart diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 828c357..0c65191 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -126,7 +126,7 @@ jobs: track: internal - name: Rename AAB - run: cp "${{ steps.sign_aab.outputs.signedReleaseFile }}" Defguard.aab + run: cp "$GITHUB_WORKSPACE/${{ steps.sign_aab.outputs.signedReleaseFile }}" Defguard.aab - name: Upload Android Artifact uses: actions/upload-artifact@v4 @@ -187,7 +187,7 @@ jobs: keyPassword: "${{ secrets.ANDROID_KEYSTORE_PASSWORD }}" - name: Rename APK - run: cp "${{ steps.sign_apk.outputs.signedReleaseFile }}" Defguard.apk + run: cp "$GITHUB_WORKSPACE/${{ steps.sign_apk.outputs.signedReleaseFile }}" Defguard.apk - name: Upload Android Artifact uses: actions/upload-artifact@v4 diff --git a/client/drift_schemas/defguard/drift_schema_v3.json b/client/drift_schemas/defguard/drift_schema_v3.json new file mode 100644 index 0000000..ed25a86 --- /dev/null +++ b/client/drift_schemas/defguard/drift_schema_v3.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pooling_token","getter_name":"poolingToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"client_traffic_policy","getter_name":"clientTrafficPolicy","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ClientTrafficPolicyConverter()","dart_type_name":"ClientTrafficPolicy"}},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"openid_display_name","getter_name":"openidDisplayName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index fff8239..c7747bb 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -1,6 +1,9 @@ PODS: - app_links (6.4.1): - Flutter + - cupertino_http (0.0.1): + - Flutter + - FlutterMacOS - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) @@ -55,6 +58,7 @@ PODS: DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) + - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) @@ -77,6 +81,8 @@ SPEC REPOS: EXTERNAL SOURCES: app_links: :path: ".symlinks/plugins/app_links/ios" + cupertino_http: + :path: ".symlinks/plugins/cupertino_http/darwin" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: @@ -107,22 +113,23 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wireguard_plugin/darwin" SPEC CHECKSUMS: - app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 - device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 + app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a + cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c + device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f - flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 - flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 - local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 - mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e - package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 - share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f - shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 + flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb + flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf + flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 + local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb + mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 + package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 + permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d + share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a + shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 - url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa - wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e + sqlite3_flutter_libs: 52ecc4dfaae71f496da86159263dbce5d23a051a + url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b + wireguard_plugin: c2f4d5382eecd7bcd07c027642c75e0569f91ff8 PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index 363f784..65d5e55 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -45,6 +45,9 @@ class DefguardInstances extends Table with AutoIncrementingPrimaryKey { // tells if the secure biometric storage exists for this instance BoolColumn get mfaKeysStored => boolean()(); + + // openid provider display name configured on the server side + TextColumn get openidDisplayName => text().nullable()(); } @DataClassName('Location') @@ -98,7 +101,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 2; + int get schemaVersion => 3; @override MigrationStrategy get migration { @@ -123,6 +126,12 @@ class AppDatabase extends _$AppDatabase { // 3. Drop old "disable_all_traffic" column await m.dropColumn(defguardInstances, "disable_all_traffic"); }, + from2To3: (m, schema) async { + await m.addColumn( + schema.defguardInstances, + schema.defguardInstances.openidDisplayName, + ); + }, ), ); } diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index 2c951d0..da5c8d3 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -154,6 +154,18 @@ class $DefguardInstancesTable extends DefguardInstances 'CHECK ("mfa_keys_stored" IN (0, 1))', ), ); + static const VerificationMeta _openidDisplayNameMeta = const VerificationMeta( + 'openidDisplayName', + ); + @override + late final GeneratedColumn openidDisplayName = + GeneratedColumn( + 'openid_display_name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); @override List get $columns => [ id, @@ -169,6 +181,7 @@ class $DefguardInstancesTable extends DefguardInstances pubKey, privateKey, mfaKeysStored, + openidDisplayName, ]; @override String get aliasedName => _alias ?? actualTableName; @@ -282,6 +295,15 @@ class $DefguardInstancesTable extends DefguardInstances } else if (isInserting) { context.missing(_mfaKeysStoredMeta); } + if (data.containsKey('openid_display_name')) { + context.handle( + _openidDisplayNameMeta, + openidDisplayName.isAcceptableOrUnknown( + data['openid_display_name']!, + _openidDisplayNameMeta, + ), + ); + } return context; } @@ -346,6 +368,10 @@ class $DefguardInstancesTable extends DefguardInstances DriftSqlType.bool, data['${effectivePrefix}mfa_keys_stored'], )!, + openidDisplayName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}openid_display_name'], + ), ); } @@ -373,6 +399,7 @@ class DefguardInstance extends DataClass final String pubKey; final String privateKey; final bool mfaKeysStored; + final String? openidDisplayName; const DefguardInstance({ required this.id, required this.name, @@ -387,6 +414,7 @@ class DefguardInstance extends DataClass required this.pubKey, required this.privateKey, required this.mfaKeysStored, + this.openidDisplayName, }); @override Map toColumns(bool nullToAbsent) { @@ -410,6 +438,9 @@ class DefguardInstance extends DataClass map['pub_key'] = Variable(pubKey); map['private_key'] = Variable(privateKey); map['mfa_keys_stored'] = Variable(mfaKeysStored); + if (!nullToAbsent || openidDisplayName != null) { + map['openid_display_name'] = Variable(openidDisplayName); + } return map; } @@ -428,6 +459,9 @@ class DefguardInstance extends DataClass pubKey: Value(pubKey), privateKey: Value(privateKey), mfaKeysStored: Value(mfaKeysStored), + openidDisplayName: openidDisplayName == null && nullToAbsent + ? const Value.absent() + : Value(openidDisplayName), ); } @@ -452,6 +486,9 @@ class DefguardInstance extends DataClass pubKey: serializer.fromJson(json['pubKey']), privateKey: serializer.fromJson(json['privateKey']), mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + openidDisplayName: serializer.fromJson( + json['openidDisplayName'], + ), ); } @override @@ -473,6 +510,7 @@ class DefguardInstance extends DataClass 'pubKey': serializer.toJson(pubKey), 'privateKey': serializer.toJson(privateKey), 'mfaKeysStored': serializer.toJson(mfaKeysStored), + 'openidDisplayName': serializer.toJson(openidDisplayName), }; } @@ -490,6 +528,7 @@ class DefguardInstance extends DataClass String? pubKey, String? privateKey, bool? mfaKeysStored, + Value openidDisplayName = const Value.absent(), }) => DefguardInstance( id: id ?? this.id, name: name ?? this.name, @@ -504,6 +543,9 @@ class DefguardInstance extends DataClass pubKey: pubKey ?? this.pubKey, privateKey: privateKey ?? this.privateKey, mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName.present + ? openidDisplayName.value + : this.openidDisplayName, ); DefguardInstance copyWithCompanion(DefguardInstancesCompanion data) { return DefguardInstance( @@ -530,6 +572,9 @@ class DefguardInstance extends DataClass mfaKeysStored: data.mfaKeysStored.present ? data.mfaKeysStored.value : this.mfaKeysStored, + openidDisplayName: data.openidDisplayName.present + ? data.openidDisplayName.value + : this.openidDisplayName, ); } @@ -548,7 +593,8 @@ class DefguardInstance extends DataClass ..write('enterpriseEnabled: $enterpriseEnabled, ') ..write('pubKey: $pubKey, ') ..write('privateKey: $privateKey, ') - ..write('mfaKeysStored: $mfaKeysStored') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') ..write(')')) .toString(); } @@ -568,6 +614,7 @@ class DefguardInstance extends DataClass pubKey, privateKey, mfaKeysStored, + openidDisplayName, ); @override bool operator ==(Object other) => @@ -585,7 +632,8 @@ class DefguardInstance extends DataClass other.enterpriseEnabled == this.enterpriseEnabled && other.pubKey == this.pubKey && other.privateKey == this.privateKey && - other.mfaKeysStored == this.mfaKeysStored); + other.mfaKeysStored == this.mfaKeysStored && + other.openidDisplayName == this.openidDisplayName); } class DefguardInstancesCompanion extends UpdateCompanion { @@ -602,6 +650,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { final Value pubKey; final Value privateKey; final Value mfaKeysStored; + final Value openidDisplayName; const DefguardInstancesCompanion({ this.id = const Value.absent(), this.name = const Value.absent(), @@ -616,6 +665,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { this.pubKey = const Value.absent(), this.privateKey = const Value.absent(), this.mfaKeysStored = const Value.absent(), + this.openidDisplayName = const Value.absent(), }); DefguardInstancesCompanion.insert({ this.id = const Value.absent(), @@ -631,6 +681,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { required String pubKey, required String privateKey, required bool mfaKeysStored, + this.openidDisplayName = const Value.absent(), }) : name = Value(name), uuid = Value(uuid), url = Value(url), @@ -656,6 +707,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { Expression? pubKey, Expression? privateKey, Expression? mfaKeysStored, + Expression? openidDisplayName, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -672,6 +724,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { if (pubKey != null) 'pub_key': pubKey, if (privateKey != null) 'private_key': privateKey, if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + if (openidDisplayName != null) 'openid_display_name': openidDisplayName, }); } @@ -689,6 +742,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { Value? pubKey, Value? privateKey, Value? mfaKeysStored, + Value? openidDisplayName, }) { return DefguardInstancesCompanion( id: id ?? this.id, @@ -704,6 +758,7 @@ class DefguardInstancesCompanion extends UpdateCompanion { pubKey: pubKey ?? this.pubKey, privateKey: privateKey ?? this.privateKey, mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName ?? this.openidDisplayName, ); } @@ -753,6 +808,9 @@ class DefguardInstancesCompanion extends UpdateCompanion { if (mfaKeysStored.present) { map['mfa_keys_stored'] = Variable(mfaKeysStored.value); } + if (openidDisplayName.present) { + map['openid_display_name'] = Variable(openidDisplayName.value); + } return map; } @@ -771,7 +829,8 @@ class DefguardInstancesCompanion extends UpdateCompanion { ..write('enterpriseEnabled: $enterpriseEnabled, ') ..write('pubKey: $pubKey, ') ..write('privateKey: $privateKey, ') - ..write('mfaKeysStored: $mfaKeysStored') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') ..write(')')) .toString(); } @@ -1645,6 +1704,7 @@ typedef $$DefguardInstancesTableCreateCompanionBuilder = required String pubKey, required String privateKey, required bool mfaKeysStored, + Value openidDisplayName, }); typedef $$DefguardInstancesTableUpdateCompanionBuilder = DefguardInstancesCompanion Function({ @@ -1661,6 +1721,7 @@ typedef $$DefguardInstancesTableUpdateCompanionBuilder = Value pubKey, Value privateKey, Value mfaKeysStored, + Value openidDisplayName, }); final class $$DefguardInstancesTableReferences @@ -1773,6 +1834,11 @@ class $$DefguardInstancesTableFilterComposer builder: (column) => ColumnFilters(column), ); + ColumnFilters get openidDisplayName => $composableBuilder( + column: $table.openidDisplayName, + builder: (column) => ColumnFilters(column), + ); + Expression locationsRefs( Expression Function($$LocationsTableFilterComposer f) f, ) { @@ -1872,6 +1938,11 @@ class $$DefguardInstancesTableOrderingComposer column: $table.mfaKeysStored, builder: (column) => ColumnOrderings(column), ); + + ColumnOrderings get openidDisplayName => $composableBuilder( + column: $table.openidDisplayName, + builder: (column) => ColumnOrderings(column), + ); } class $$DefguardInstancesTableAnnotationComposer @@ -1933,6 +2004,11 @@ class $$DefguardInstancesTableAnnotationComposer builder: (column) => column, ); + GeneratedColumn get openidDisplayName => $composableBuilder( + column: $table.openidDisplayName, + builder: (column) => column, + ); + Expression locationsRefs( Expression Function($$LocationsTableAnnotationComposer a) f, ) { @@ -2006,6 +2082,7 @@ class $$DefguardInstancesTableTableManager Value pubKey = const Value.absent(), Value privateKey = const Value.absent(), Value mfaKeysStored = const Value.absent(), + Value openidDisplayName = const Value.absent(), }) => DefguardInstancesCompanion( id: id, name: name, @@ -2020,6 +2097,7 @@ class $$DefguardInstancesTableTableManager pubKey: pubKey, privateKey: privateKey, mfaKeysStored: mfaKeysStored, + openidDisplayName: openidDisplayName, ), createCompanionCallback: ({ @@ -2037,6 +2115,7 @@ class $$DefguardInstancesTableTableManager required String pubKey, required String privateKey, required bool mfaKeysStored, + Value openidDisplayName = const Value.absent(), }) => DefguardInstancesCompanion.insert( id: id, name: name, @@ -2051,6 +2130,7 @@ class $$DefguardInstancesTableTableManager pubKey: pubKey, privateKey: privateKey, mfaKeysStored: mfaKeysStored, + openidDisplayName: openidDisplayName, ), withReferenceMapper: (p0) => p0 .map( diff --git a/client/lib/data/db/database.steps.dart b/client/lib/data/db/database.steps.dart index c2c8203..237aeba 100644 --- a/client/lib/data/db/database.steps.dart +++ b/client/lib/data/db/database.steps.dart @@ -312,8 +312,110 @@ i1.GeneratedColumn _column_23(String aliasedName) => true, type: i1.DriftSqlType.int, ); + +final class Schema3 extends i0.VersionedSchema { + Schema3({required super.database}) : super(version: 3); + @override + late final List entities = [ + defguardInstances, + locations, + ]; + late final Shape2 defguardInstances = Shape2( + source: i0.VersionedTable( + entityName: 'defguard_instances', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + _column_24, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape1 locations = Shape1( + source: i0.VersionedTable( + entityName: 'locations', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_13, + _column_14, + _column_1, + _column_15, + _column_10, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape2 extends i0.VersionedTable { + Shape2({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get uuid => + columnsByName['uuid']! as i1.GeneratedColumn; + i1.GeneratedColumn get url => + columnsByName['url']! as i1.GeneratedColumn; + i1.GeneratedColumn get deviceId => + columnsByName['device_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get proxyUrl => + columnsByName['proxy_url']! as i1.GeneratedColumn; + i1.GeneratedColumn get username => + columnsByName['username']! as i1.GeneratedColumn; + i1.GeneratedColumn get poolingToken => + columnsByName['pooling_token']! as i1.GeneratedColumn; + i1.GeneratedColumn get clientTrafficPolicy => + columnsByName['client_traffic_policy']! as i1.GeneratedColumn; + i1.GeneratedColumn get enterpriseEnabled => + columnsByName['enterprise_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get pubKey => + columnsByName['pub_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get privateKey => + columnsByName['private_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaKeysStored => + columnsByName['mfa_keys_stored']! as i1.GeneratedColumn; + i1.GeneratedColumn get openidDisplayName => + columnsByName['openid_display_name']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_24(String aliasedName) => + i1.GeneratedColumn( + 'openid_display_name', + aliasedName, + true, + type: i1.DriftSqlType.string, + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -322,6 +424,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from1To2(migrator, schema); return 2; + case 2: + final schema = Schema3(database: database); + final migrator = i1.Migrator(database, schema); + await from2To3(migrator, schema); + return 3; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -330,6 +437,7 @@ i0.MigrationStepWithVersion migrationSteps({ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, + required Future Function(i1.Migrator m, Schema3 schema) from2To3, }) => i0.VersionedSchema.stepByStepHelper( - step: migrationSteps(from1To2: from1To2), + step: migrationSteps(from1To2: from1To2, from2To3: from2To3), ); diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index 916ca79..87e5d08 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -253,6 +253,7 @@ class InstanceInfo { final bool enterpriseEnabled; final bool disableAllTraffic; final ClientTrafficPolicy? clientTrafficPolicy; + final String? openidDisplayName; const InstanceInfo({ required this.id, @@ -262,9 +263,9 @@ class InstanceInfo { required this.username, required this.enterpriseEnabled, // deprecated, use clientTrafficPolicy instead - @Deprecated('1.6') - required this.disableAllTraffic, + @Deprecated('1.6') required this.disableAllTraffic, required this.clientTrafficPolicy, + this.openidDisplayName, }); factory InstanceInfo.fromJson(Map json) => @@ -279,7 +280,8 @@ class InstanceInfo { proxyUrl == other.proxyUrl && username == other.username && enterpriseEnabled == other.enterpriseEnabled && - getPolicy() == other.clientTrafficPolicy; + getPolicy() == other.clientTrafficPolicy && + openidDisplayName == other.openidDisplayName; } DefguardInstancesCompanion toCompanion({DefguardInstance? instance}) { @@ -296,14 +298,16 @@ class InstanceInfo { enterpriseEnabled: d.Value(enterpriseEnabled), clientTrafficPolicy: d.Value(getPolicy()), uuid: d.Value(id), + openidDisplayName: d.Value(openidDisplayName), ); } /// Retrieves `ClientTrafficPolicy` while ensuring backwards compatibility ClientTrafficPolicy getPolicy() { - return clientTrafficPolicy ?? (disableAllTraffic - ? ClientTrafficPolicy.disableAllTraffic - : ClientTrafficPolicy.none); + return clientTrafficPolicy ?? + (disableAllTraffic + ? ClientTrafficPolicy.disableAllTraffic + : ClientTrafficPolicy.none); } } diff --git a/client/lib/data/proxy/enrollment.g.dart b/client/lib/data/proxy/enrollment.g.dart index df153ce..5f5cf4b 100644 --- a/client/lib/data/proxy/enrollment.g.dart +++ b/client/lib/data/proxy/enrollment.g.dart @@ -393,6 +393,10 @@ InstanceInfo _$InstanceInfoFromJson(Map json) => 'client_traffic_policy', (v) => $enumDecodeNullable(_$ClientTrafficPolicyEnumMap, v), ), + openidDisplayName: $checkedConvert( + 'openid_display_name', + (v) => v as String?, + ), ); return val; }, @@ -401,6 +405,7 @@ InstanceInfo _$InstanceInfoFromJson(Map json) => 'enterpriseEnabled': 'enterprise_enabled', 'disableAllTraffic': 'disable_all_traffic', 'clientTrafficPolicy': 'client_traffic_policy', + 'openidDisplayName': 'openid_display_name', }, ); @@ -413,6 +418,7 @@ const _$InstanceInfoFieldMap = { 'enterpriseEnabled': 'enterprise_enabled', 'disableAllTraffic': 'disable_all_traffic', 'clientTrafficPolicy': 'client_traffic_policy', + 'openidDisplayName': 'openid_display_name', }; Map _$InstanceInfoToJson(InstanceInfo instance) => @@ -426,6 +432,7 @@ Map _$InstanceInfoToJson(InstanceInfo instance) => 'disable_all_traffic': instance.disableAllTraffic, 'client_traffic_policy': _$ClientTrafficPolicyEnumMap[instance.clientTrafficPolicy], + 'openid_display_name': instance.openidDisplayName, }; const _$ClientTrafficPolicyEnumMap = { diff --git a/client/lib/enterprise/screens/mfa/openid_mfa_screen.dart b/client/lib/enterprise/screens/mfa/openid_mfa_screen.dart index 2eb337a..7d4cc69 100644 --- a/client/lib/enterprise/screens/mfa/openid_mfa_screen.dart +++ b/client/lib/enterprise/screens/mfa/openid_mfa_screen.dart @@ -17,18 +17,28 @@ import '../../../open/services/snackbar_service.dart'; class OpenIdMfaScreenData { final String proxyUrl; final String token; + final String? openidDisplayName; - const OpenIdMfaScreenData({required this.proxyUrl, required this.token}); + const OpenIdMfaScreenData({ + required this.proxyUrl, + required this.token, + this.openidDisplayName, + }); } final String _title = "Two-factor authentication"; -final String _mfaMsg1 = - "In order to connect to VPN please login with your OpenID provider. To do so, please click \"Authenticate with OpenId\""; +String _mfaMsg1(String? providerName) { + final name = providerName ?? 'OpenID'; + return "In order to connect to VPN please login with $name. To do so, please click \"Authenticate with $name\" button below"; +} -final String _mfaMsg2 = - "This will open a new window in your web browser and automatically redirect you to your OpenID provider login page. After authenticating please get back here"; +String _mfaMsg2(String? providerName) { + final name = providerName ?? 'OpenID'; + return "This will open a new window in your Web Browser and automatically redirect you to the $name login page. After authenticating with $name please get back here"; +} -final String _authenticateMsg = "Authenticate with OpenID"; +String _authenticateMsg(String? providerName) => + 'Authenticate with ${providerName ?? 'OpenID'}'; class OpenIdMfaScreen extends HookConsumerWidget { final OpenIdMfaScreenData screenData; @@ -67,17 +77,17 @@ class OpenIdMfaScreen extends HookConsumerWidget { ), Center(child: DgIconOpenidOpen(size: 128)), Text( - _mfaMsg1, + _mfaMsg1(screenData.openidDisplayName), style: DgText.modal1.copyWith(color: DgColor.textBodySecondary), textAlign: TextAlign.center, ), Text( - _mfaMsg2, + _mfaMsg2(screenData.openidDisplayName), style: DgText.modal1.copyWith(color: DgColor.textBodySecondary), textAlign: TextAlign.center, ), DgButton( - text: _authenticateMsg, + text: _authenticateMsg(screenData.openidDisplayName), variant: DgButtonVariant.primary, size: DgButtonSize.big, width: double.infinity, diff --git a/client/lib/open/screens/add_instance/screens/name_device_screen.dart b/client/lib/open/screens/add_instance/screens/name_device_screen.dart index ba583b7..6e25082 100644 --- a/client/lib/open/screens/add_instance/screens/name_device_screen.dart +++ b/client/lib/open/screens/add_instance/screens/name_device_screen.dart @@ -64,6 +64,9 @@ class NameDeviceScreen extends HookConsumerWidget { username: createResponse.instance.username, poolingToken: createResponse.token, mfaKeysStored: false, + openidDisplayName: drift.Value( + createResponse.instance.openidDisplayName, + ), ), mode: drift.InsertMode.insertOrFail, ); @@ -101,7 +104,7 @@ class NameDeviceScreen extends HookConsumerWidget { suggestedName = ""; } nameController.text = suggestedName; - } catch(e) { + } catch (e) { talker.error("Failed to get suggested device name! Reason: $e"); } } diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index a52a774..b44cc4a 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -38,7 +38,8 @@ class TunnelService { if (instance.clientTrafficPolicy == ClientTrafficPolicy.disableAllTraffic) { // instance enforces predefined traffic trafficMethod = RoutingMethod.predefined; - } else if (instance.clientTrafficPolicy == ClientTrafficPolicy.forceAllTraffic) { + } else if (instance.clientTrafficPolicy == + ClientTrafficPolicy.forceAllTraffic) { // instance enforces all traffic trafficMethod = RoutingMethod.all; } else { @@ -111,6 +112,7 @@ class TunnelService { payload: payload, method: mfaMethod, secureStorageKey: instance.secureStorageKey, + openidDisplayName: instance.openidDisplayName, ); if (presharedKey == null) { // user dismissed the dialog @@ -139,6 +141,7 @@ class TunnelService { required PluginConnectPayload payload, required MfaMethod method, String? secureStorageKey, + String? openidDisplayName, }) async { // prepare messenger to avoid "context use across async gaps" final messenger = ScaffoldMessenger.of(navigator.context); @@ -157,6 +160,7 @@ class TunnelService { token: startMfaResponse.token, proxyUrl: proxyUrl, method: method, + openidDisplayName: openidDisplayName, ); } if (method == MfaMethod.biometric) { @@ -227,11 +231,16 @@ class TunnelService { required String token, required String proxyUrl, required MfaMethod method, + String? openidDisplayName, }) async { final presharedKey = await Navigator.of(navigator.context).push( MaterialPageRoute( builder: (context) => OpenIdMfaScreen( - screenData: OpenIdMfaScreenData(proxyUrl: proxyUrl, token: token), + screenData: OpenIdMfaScreenData( + proxyUrl: proxyUrl, + token: token, + openidDisplayName: openidDisplayName, + ), ), ), ); diff --git a/client/test/drift/defguard/generated/schema.dart b/client/test/drift/defguard/generated/schema.dart index b2b7404..209e70d 100644 --- a/client/test/drift/defguard/generated/schema.dart +++ b/client/test/drift/defguard/generated/schema.dart @@ -5,6 +5,7 @@ import 'package:drift/drift.dart'; import 'package:drift/internal/migrations.dart'; import 'schema_v1.dart' as v1; import 'schema_v2.dart' as v2; +import 'schema_v3.dart' as v3; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -14,10 +15,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v1.DatabaseAtV1(db); case 2: return v2.DatabaseAtV2(db); + case 3: + return v3.DatabaseAtV3(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2]; + static const versions = const [1, 2, 3]; } diff --git a/client/test/drift/defguard/generated/schema_v3.dart b/client/test/drift/defguard/generated/schema_v3.dart new file mode 100644 index 0000000..54f693b --- /dev/null +++ b/client/test/drift/defguard/generated/schema_v3.dart @@ -0,0 +1,1321 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class DefguardInstances extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + DefguardInstances(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn proxyUrl = GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn username = GeneratedColumn( + 'username', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn poolingToken = GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn clientTrafficPolicy = GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn enterpriseEnabled = GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn privateKey = GeneratedColumn( + 'private_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn mfaKeysStored = GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + late final GeneratedColumn openidDisplayName = + GeneratedColumn( + 'openid_display_name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + openidDisplayName, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'defguard_instances'; + @override + Set get $primaryKey => {id}; + @override + DefguardInstancesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DefguardInstancesData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + )!, + deviceId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}device_id'], + )!, + proxyUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}proxy_url'], + )!, + username: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}username'], + )!, + poolingToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pooling_token'], + )!, + clientTrafficPolicy: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}client_traffic_policy'], + )!, + enterpriseEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}enterprise_enabled'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + privateKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}private_key'], + )!, + mfaKeysStored: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_keys_stored'], + )!, + openidDisplayName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}openid_display_name'], + ), + ); + } + + @override + DefguardInstances createAlias(String alias) { + return DefguardInstances(attachedDatabase, alias); + } +} + +class DefguardInstancesData extends DataClass + implements Insertable { + final int id; + final String name; + final String uuid; + final String url; + final int deviceId; + final String proxyUrl; + final String username; + final String poolingToken; + final int clientTrafficPolicy; + final bool enterpriseEnabled; + final String pubKey; + final String privateKey; + final bool mfaKeysStored; + final String? openidDisplayName; + const DefguardInstancesData({ + required this.id, + required this.name, + required this.uuid, + required this.url, + required this.deviceId, + required this.proxyUrl, + required this.username, + required this.poolingToken, + required this.clientTrafficPolicy, + required this.enterpriseEnabled, + required this.pubKey, + required this.privateKey, + required this.mfaKeysStored, + this.openidDisplayName, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['uuid'] = Variable(uuid); + map['url'] = Variable(url); + map['device_id'] = Variable(deviceId); + map['proxy_url'] = Variable(proxyUrl); + map['username'] = Variable(username); + map['pooling_token'] = Variable(poolingToken); + map['client_traffic_policy'] = Variable(clientTrafficPolicy); + map['enterprise_enabled'] = Variable(enterpriseEnabled); + map['pub_key'] = Variable(pubKey); + map['private_key'] = Variable(privateKey); + map['mfa_keys_stored'] = Variable(mfaKeysStored); + if (!nullToAbsent || openidDisplayName != null) { + map['openid_display_name'] = Variable(openidDisplayName); + } + return map; + } + + DefguardInstancesCompanion toCompanion(bool nullToAbsent) { + return DefguardInstancesCompanion( + id: Value(id), + name: Value(name), + uuid: Value(uuid), + url: Value(url), + deviceId: Value(deviceId), + proxyUrl: Value(proxyUrl), + username: Value(username), + poolingToken: Value(poolingToken), + clientTrafficPolicy: Value(clientTrafficPolicy), + enterpriseEnabled: Value(enterpriseEnabled), + pubKey: Value(pubKey), + privateKey: Value(privateKey), + mfaKeysStored: Value(mfaKeysStored), + openidDisplayName: openidDisplayName == null && nullToAbsent + ? const Value.absent() + : Value(openidDisplayName), + ); + } + + factory DefguardInstancesData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DefguardInstancesData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + uuid: serializer.fromJson(json['uuid']), + url: serializer.fromJson(json['url']), + deviceId: serializer.fromJson(json['deviceId']), + proxyUrl: serializer.fromJson(json['proxyUrl']), + username: serializer.fromJson(json['username']), + poolingToken: serializer.fromJson(json['poolingToken']), + clientTrafficPolicy: serializer.fromJson( + json['clientTrafficPolicy'], + ), + enterpriseEnabled: serializer.fromJson(json['enterpriseEnabled']), + pubKey: serializer.fromJson(json['pubKey']), + privateKey: serializer.fromJson(json['privateKey']), + mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + openidDisplayName: serializer.fromJson( + json['openidDisplayName'], + ), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'uuid': serializer.toJson(uuid), + 'url': serializer.toJson(url), + 'deviceId': serializer.toJson(deviceId), + 'proxyUrl': serializer.toJson(proxyUrl), + 'username': serializer.toJson(username), + 'poolingToken': serializer.toJson(poolingToken), + 'clientTrafficPolicy': serializer.toJson(clientTrafficPolicy), + 'enterpriseEnabled': serializer.toJson(enterpriseEnabled), + 'pubKey': serializer.toJson(pubKey), + 'privateKey': serializer.toJson(privateKey), + 'mfaKeysStored': serializer.toJson(mfaKeysStored), + 'openidDisplayName': serializer.toJson(openidDisplayName), + }; + } + + DefguardInstancesData copyWith({ + int? id, + String? name, + String? uuid, + String? url, + int? deviceId, + String? proxyUrl, + String? username, + String? poolingToken, + int? clientTrafficPolicy, + bool? enterpriseEnabled, + String? pubKey, + String? privateKey, + bool? mfaKeysStored, + Value openidDisplayName = const Value.absent(), + }) => DefguardInstancesData( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName.present + ? openidDisplayName.value + : this.openidDisplayName, + ); + DefguardInstancesData copyWithCompanion(DefguardInstancesCompanion data) { + return DefguardInstancesData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + uuid: data.uuid.present ? data.uuid.value : this.uuid, + url: data.url.present ? data.url.value : this.url, + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + proxyUrl: data.proxyUrl.present ? data.proxyUrl.value : this.proxyUrl, + username: data.username.present ? data.username.value : this.username, + poolingToken: data.poolingToken.present + ? data.poolingToken.value + : this.poolingToken, + clientTrafficPolicy: data.clientTrafficPolicy.present + ? data.clientTrafficPolicy.value + : this.clientTrafficPolicy, + enterpriseEnabled: data.enterpriseEnabled.present + ? data.enterpriseEnabled.value + : this.enterpriseEnabled, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + privateKey: data.privateKey.present + ? data.privateKey.value + : this.privateKey, + mfaKeysStored: data.mfaKeysStored.present + ? data.mfaKeysStored.value + : this.mfaKeysStored, + openidDisplayName: data.openidDisplayName.present + ? data.openidDisplayName.value + : this.openidDisplayName, + ); + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + openidDisplayName, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DefguardInstancesData && + other.id == this.id && + other.name == this.name && + other.uuid == this.uuid && + other.url == this.url && + other.deviceId == this.deviceId && + other.proxyUrl == this.proxyUrl && + other.username == this.username && + other.poolingToken == this.poolingToken && + other.clientTrafficPolicy == this.clientTrafficPolicy && + other.enterpriseEnabled == this.enterpriseEnabled && + other.pubKey == this.pubKey && + other.privateKey == this.privateKey && + other.mfaKeysStored == this.mfaKeysStored && + other.openidDisplayName == this.openidDisplayName); +} + +class DefguardInstancesCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value uuid; + final Value url; + final Value deviceId; + final Value proxyUrl; + final Value username; + final Value poolingToken; + final Value clientTrafficPolicy; + final Value enterpriseEnabled; + final Value pubKey; + final Value privateKey; + final Value mfaKeysStored; + final Value openidDisplayName; + const DefguardInstancesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.uuid = const Value.absent(), + this.url = const Value.absent(), + this.deviceId = const Value.absent(), + this.proxyUrl = const Value.absent(), + this.username = const Value.absent(), + this.poolingToken = const Value.absent(), + this.clientTrafficPolicy = const Value.absent(), + this.enterpriseEnabled = const Value.absent(), + this.pubKey = const Value.absent(), + this.privateKey = const Value.absent(), + this.mfaKeysStored = const Value.absent(), + this.openidDisplayName = const Value.absent(), + }); + DefguardInstancesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String uuid, + required String url, + required int deviceId, + required String proxyUrl, + required String username, + required String poolingToken, + this.clientTrafficPolicy = const Value.absent(), + required bool enterpriseEnabled, + required String pubKey, + required String privateKey, + required bool mfaKeysStored, + this.openidDisplayName = const Value.absent(), + }) : name = Value(name), + uuid = Value(uuid), + url = Value(url), + deviceId = Value(deviceId), + proxyUrl = Value(proxyUrl), + username = Value(username), + poolingToken = Value(poolingToken), + enterpriseEnabled = Value(enterpriseEnabled), + pubKey = Value(pubKey), + privateKey = Value(privateKey), + mfaKeysStored = Value(mfaKeysStored); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? uuid, + Expression? url, + Expression? deviceId, + Expression? proxyUrl, + Expression? username, + Expression? poolingToken, + Expression? clientTrafficPolicy, + Expression? enterpriseEnabled, + Expression? pubKey, + Expression? privateKey, + Expression? mfaKeysStored, + Expression? openidDisplayName, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (uuid != null) 'uuid': uuid, + if (url != null) 'url': url, + if (deviceId != null) 'device_id': deviceId, + if (proxyUrl != null) 'proxy_url': proxyUrl, + if (username != null) 'username': username, + if (poolingToken != null) 'pooling_token': poolingToken, + if (clientTrafficPolicy != null) + 'client_traffic_policy': clientTrafficPolicy, + if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, + if (pubKey != null) 'pub_key': pubKey, + if (privateKey != null) 'private_key': privateKey, + if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + if (openidDisplayName != null) 'openid_display_name': openidDisplayName, + }); + } + + DefguardInstancesCompanion copyWith({ + Value? id, + Value? name, + Value? uuid, + Value? url, + Value? deviceId, + Value? proxyUrl, + Value? username, + Value? poolingToken, + Value? clientTrafficPolicy, + Value? enterpriseEnabled, + Value? pubKey, + Value? privateKey, + Value? mfaKeysStored, + Value? openidDisplayName, + }) { + return DefguardInstancesCompanion( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName ?? this.openidDisplayName, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (uuid.present) { + map['uuid'] = Variable(uuid.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (proxyUrl.present) { + map['proxy_url'] = Variable(proxyUrl.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (poolingToken.present) { + map['pooling_token'] = Variable(poolingToken.value); + } + if (clientTrafficPolicy.present) { + map['client_traffic_policy'] = Variable(clientTrafficPolicy.value); + } + if (enterpriseEnabled.present) { + map['enterprise_enabled'] = Variable(enterpriseEnabled.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (privateKey.present) { + map['private_key'] = Variable(privateKey.value); + } + if (mfaKeysStored.present) { + map['mfa_keys_stored'] = Variable(mfaKeysStored.value); + } + if (openidDisplayName.present) { + map['openid_display_name'] = Variable(openidDisplayName.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') + ..write(')')) + .toString(); + } +} + +class Locations extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Locations(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn instance = GeneratedColumn( + 'instance', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn networkId = GeneratedColumn( + 'network_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn address = GeneratedColumn( + 'address', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn endpoint = GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn allowedIps = GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn dns = GeneratedColumn( + 'dns', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaEnabled = GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn trafficMethod = GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaMethod = GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn keepAliveInterval = GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn locationMfaMode = GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locations'; + @override + Set get $primaryKey => {id}; + @override + LocationsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocationsData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + instance: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}instance'], + )!, + networkId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}network_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + address: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}address'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + endpoint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}endpoint'], + )!, + allowedIps: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}allowed_ips'], + )!, + dns: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns'], + ), + mfaEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_enabled'], + ), + trafficMethod: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}traffic_method'], + ), + mfaMethod: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}mfa_method'], + ), + keepAliveInterval: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}keep_alive_interval'], + )!, + locationMfaMode: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}location_mfa_mode'], + ), + ); + } + + @override + Locations createAlias(String alias) { + return Locations(attachedDatabase, alias); + } +} + +class LocationsData extends DataClass implements Insertable { + final int id; + final int instance; + final int networkId; + final String name; + final String address; + final String pubKey; + final String endpoint; + final String allowedIps; + final String? dns; + final bool? mfaEnabled; + final String? trafficMethod; + final int? mfaMethod; + final int keepAliveInterval; + final int? locationMfaMode; + const LocationsData({ + required this.id, + required this.instance, + required this.networkId, + required this.name, + required this.address, + required this.pubKey, + required this.endpoint, + required this.allowedIps, + this.dns, + this.mfaEnabled, + this.trafficMethod, + this.mfaMethod, + required this.keepAliveInterval, + this.locationMfaMode, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['instance'] = Variable(instance); + map['network_id'] = Variable(networkId); + map['name'] = Variable(name); + map['address'] = Variable(address); + map['pub_key'] = Variable(pubKey); + map['endpoint'] = Variable(endpoint); + map['allowed_ips'] = Variable(allowedIps); + if (!nullToAbsent || dns != null) { + map['dns'] = Variable(dns); + } + if (!nullToAbsent || mfaEnabled != null) { + map['mfa_enabled'] = Variable(mfaEnabled); + } + if (!nullToAbsent || trafficMethod != null) { + map['traffic_method'] = Variable(trafficMethod); + } + if (!nullToAbsent || mfaMethod != null) { + map['mfa_method'] = Variable(mfaMethod); + } + map['keep_alive_interval'] = Variable(keepAliveInterval); + if (!nullToAbsent || locationMfaMode != null) { + map['location_mfa_mode'] = Variable(locationMfaMode); + } + return map; + } + + LocationsCompanion toCompanion(bool nullToAbsent) { + return LocationsCompanion( + id: Value(id), + instance: Value(instance), + networkId: Value(networkId), + name: Value(name), + address: Value(address), + pubKey: Value(pubKey), + endpoint: Value(endpoint), + allowedIps: Value(allowedIps), + dns: dns == null && nullToAbsent ? const Value.absent() : Value(dns), + mfaEnabled: mfaEnabled == null && nullToAbsent + ? const Value.absent() + : Value(mfaEnabled), + trafficMethod: trafficMethod == null && nullToAbsent + ? const Value.absent() + : Value(trafficMethod), + mfaMethod: mfaMethod == null && nullToAbsent + ? const Value.absent() + : Value(mfaMethod), + keepAliveInterval: Value(keepAliveInterval), + locationMfaMode: locationMfaMode == null && nullToAbsent + ? const Value.absent() + : Value(locationMfaMode), + ); + } + + factory LocationsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocationsData( + id: serializer.fromJson(json['id']), + instance: serializer.fromJson(json['instance']), + networkId: serializer.fromJson(json['networkId']), + name: serializer.fromJson(json['name']), + address: serializer.fromJson(json['address']), + pubKey: serializer.fromJson(json['pubKey']), + endpoint: serializer.fromJson(json['endpoint']), + allowedIps: serializer.fromJson(json['allowedIps']), + dns: serializer.fromJson(json['dns']), + mfaEnabled: serializer.fromJson(json['mfaEnabled']), + trafficMethod: serializer.fromJson(json['trafficMethod']), + mfaMethod: serializer.fromJson(json['mfaMethod']), + keepAliveInterval: serializer.fromJson(json['keepAliveInterval']), + locationMfaMode: serializer.fromJson(json['locationMfaMode']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'instance': serializer.toJson(instance), + 'networkId': serializer.toJson(networkId), + 'name': serializer.toJson(name), + 'address': serializer.toJson(address), + 'pubKey': serializer.toJson(pubKey), + 'endpoint': serializer.toJson(endpoint), + 'allowedIps': serializer.toJson(allowedIps), + 'dns': serializer.toJson(dns), + 'mfaEnabled': serializer.toJson(mfaEnabled), + 'trafficMethod': serializer.toJson(trafficMethod), + 'mfaMethod': serializer.toJson(mfaMethod), + 'keepAliveInterval': serializer.toJson(keepAliveInterval), + 'locationMfaMode': serializer.toJson(locationMfaMode), + }; + } + + LocationsData copyWith({ + int? id, + int? instance, + int? networkId, + String? name, + String? address, + String? pubKey, + String? endpoint, + String? allowedIps, + Value dns = const Value.absent(), + Value mfaEnabled = const Value.absent(), + Value trafficMethod = const Value.absent(), + Value mfaMethod = const Value.absent(), + int? keepAliveInterval, + Value locationMfaMode = const Value.absent(), + }) => LocationsData( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns.present ? dns.value : this.dns, + mfaEnabled: mfaEnabled.present ? mfaEnabled.value : this.mfaEnabled, + trafficMethod: trafficMethod.present + ? trafficMethod.value + : this.trafficMethod, + mfaMethod: mfaMethod.present ? mfaMethod.value : this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode.present + ? locationMfaMode.value + : this.locationMfaMode, + ); + LocationsData copyWithCompanion(LocationsCompanion data) { + return LocationsData( + id: data.id.present ? data.id.value : this.id, + instance: data.instance.present ? data.instance.value : this.instance, + networkId: data.networkId.present ? data.networkId.value : this.networkId, + name: data.name.present ? data.name.value : this.name, + address: data.address.present ? data.address.value : this.address, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + endpoint: data.endpoint.present ? data.endpoint.value : this.endpoint, + allowedIps: data.allowedIps.present + ? data.allowedIps.value + : this.allowedIps, + dns: data.dns.present ? data.dns.value : this.dns, + mfaEnabled: data.mfaEnabled.present + ? data.mfaEnabled.value + : this.mfaEnabled, + trafficMethod: data.trafficMethod.present + ? data.trafficMethod.value + : this.trafficMethod, + mfaMethod: data.mfaMethod.present ? data.mfaMethod.value : this.mfaMethod, + keepAliveInterval: data.keepAliveInterval.present + ? data.keepAliveInterval.value + : this.keepAliveInterval, + locationMfaMode: data.locationMfaMode.present + ? data.locationMfaMode.value + : this.locationMfaMode, + ); + } + + @override + String toString() { + return (StringBuffer('LocationsData(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocationsData && + other.id == this.id && + other.instance == this.instance && + other.networkId == this.networkId && + other.name == this.name && + other.address == this.address && + other.pubKey == this.pubKey && + other.endpoint == this.endpoint && + other.allowedIps == this.allowedIps && + other.dns == this.dns && + other.mfaEnabled == this.mfaEnabled && + other.trafficMethod == this.trafficMethod && + other.mfaMethod == this.mfaMethod && + other.keepAliveInterval == this.keepAliveInterval && + other.locationMfaMode == this.locationMfaMode); +} + +class LocationsCompanion extends UpdateCompanion { + final Value id; + final Value instance; + final Value networkId; + final Value name; + final Value address; + final Value pubKey; + final Value endpoint; + final Value allowedIps; + final Value dns; + final Value mfaEnabled; + final Value trafficMethod; + final Value mfaMethod; + final Value keepAliveInterval; + final Value locationMfaMode; + const LocationsCompanion({ + this.id = const Value.absent(), + this.instance = const Value.absent(), + this.networkId = const Value.absent(), + this.name = const Value.absent(), + this.address = const Value.absent(), + this.pubKey = const Value.absent(), + this.endpoint = const Value.absent(), + this.allowedIps = const Value.absent(), + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + this.keepAliveInterval = const Value.absent(), + this.locationMfaMode = const Value.absent(), + }); + LocationsCompanion.insert({ + this.id = const Value.absent(), + required int instance, + required int networkId, + required String name, + required String address, + required String pubKey, + required String endpoint, + required String allowedIps, + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + required int keepAliveInterval, + this.locationMfaMode = const Value.absent(), + }) : instance = Value(instance), + networkId = Value(networkId), + name = Value(name), + address = Value(address), + pubKey = Value(pubKey), + endpoint = Value(endpoint), + allowedIps = Value(allowedIps), + keepAliveInterval = Value(keepAliveInterval); + static Insertable custom({ + Expression? id, + Expression? instance, + Expression? networkId, + Expression? name, + Expression? address, + Expression? pubKey, + Expression? endpoint, + Expression? allowedIps, + Expression? dns, + Expression? mfaEnabled, + Expression? trafficMethod, + Expression? mfaMethod, + Expression? keepAliveInterval, + Expression? locationMfaMode, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (instance != null) 'instance': instance, + if (networkId != null) 'network_id': networkId, + if (name != null) 'name': name, + if (address != null) 'address': address, + if (pubKey != null) 'pub_key': pubKey, + if (endpoint != null) 'endpoint': endpoint, + if (allowedIps != null) 'allowed_ips': allowedIps, + if (dns != null) 'dns': dns, + if (mfaEnabled != null) 'mfa_enabled': mfaEnabled, + if (trafficMethod != null) 'traffic_method': trafficMethod, + if (mfaMethod != null) 'mfa_method': mfaMethod, + if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, + if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + }); + } + + LocationsCompanion copyWith({ + Value? id, + Value? instance, + Value? networkId, + Value? name, + Value? address, + Value? pubKey, + Value? endpoint, + Value? allowedIps, + Value? dns, + Value? mfaEnabled, + Value? trafficMethod, + Value? mfaMethod, + Value? keepAliveInterval, + Value? locationMfaMode, + }) { + return LocationsCompanion( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns ?? this.dns, + mfaEnabled: mfaEnabled ?? this.mfaEnabled, + trafficMethod: trafficMethod ?? this.trafficMethod, + mfaMethod: mfaMethod ?? this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode ?? this.locationMfaMode, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (instance.present) { + map['instance'] = Variable(instance.value); + } + if (networkId.present) { + map['network_id'] = Variable(networkId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (address.present) { + map['address'] = Variable(address.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (endpoint.present) { + map['endpoint'] = Variable(endpoint.value); + } + if (allowedIps.present) { + map['allowed_ips'] = Variable(allowedIps.value); + } + if (dns.present) { + map['dns'] = Variable(dns.value); + } + if (mfaEnabled.present) { + map['mfa_enabled'] = Variable(mfaEnabled.value); + } + if (trafficMethod.present) { + map['traffic_method'] = Variable(trafficMethod.value); + } + if (mfaMethod.present) { + map['mfa_method'] = Variable(mfaMethod.value); + } + if (keepAliveInterval.present) { + map['keep_alive_interval'] = Variable(keepAliveInterval.value); + } + if (locationMfaMode.present) { + map['location_mfa_mode'] = Variable(locationMfaMode.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocationsCompanion(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV3 extends GeneratedDatabase { + DatabaseAtV3(QueryExecutor e) : super(e); + late final DefguardInstances defguardInstances = DefguardInstances(this); + late final Locations locations = Locations(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + defguardInstances, + locations, + ]; + @override + int get schemaVersion => 3; +} From d8fd1252a94b1123948b8a7755386351e5f6f656 Mon Sep 17 00:00:00 2001 From: Kuba <78603704+jakub-tldr@users.noreply.github.com> Date: Fri, 15 May 2026 13:23:36 +0200 Subject: [PATCH 14/44] bump version (#186) --- client/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- client/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index d8b5fda..97f5ddc 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -644,7 +644,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -698,7 +698,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -749,7 +749,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.2; + MARKETING_VERSION = 1.6.3; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 5c8feae..5438494 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.2+1 +version: 1.6.3+1 environment: sdk: ^3.8.1 From 68ac1ea3a923a623d1f9d9e6f633ca1e16feb26a Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 22 May 2026 10:46:36 +0200 Subject: [PATCH 15/44] Sync VPNExtension with Client (#187) --- .github/workflows/build.yaml | 6 +- .github/workflows/lint-and-test.yaml | 4 +- client/ios/Podfile.lock | 30 +- client/ios/VPNExtension/Adapter.swift | 294 +++++++++++------- .../ios/VPNExtension/Decodabe+Encodable.swift | 6 +- client/ios/VPNExtension/Endpoint.swift | 38 ++- client/ios/VPNExtension/FileLogger.swift | 251 +++++++++++++++ .../VPNExtension/InterfaceConfiguration.swift | 15 - client/ios/VPNExtension/IpAddrMask.swift | 44 +-- .../VPNExtension/PacketTunnelProvider.swift | 121 +++---- client/ios/VPNExtension/Peer.swift | 24 +- client/ios/VPNExtension/Stats.swift | 18 ++ .../VPNExtension/TunnelConfiguration.swift | 84 ++--- client/ios/boringtun | 2 +- client/pubspec.lock | 96 +++--- 15 files changed, 696 insertions(+), 337 deletions(-) create mode 100644 client/ios/VPNExtension/FileLogger.swift delete mode 100644 client/ios/VPNExtension/InterfaceConfiguration.swift create mode 100644 client/ios/VPNExtension/Stats.swift diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 0c65191..cddbe40 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -29,7 +29,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.9 + flutter-version: 3.38.10 - name: Use homebrew ruby run: | @@ -89,7 +89,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.7 + flutter-version: 3.38.10 - name: Accept licenses run: yes | flutter doctor --android-licenses @@ -159,7 +159,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.7 + flutter-version: 3.38.10 - name: Install Android SDK components run: | diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 2b719f3..31bb7ce 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -47,7 +47,7 @@ jobs: uses: subosito/flutter-action@v2 with: channel: stable - flutter-version: 3.38.7 + flutter-version: 3.38.10 - name: get deps run: flutter pub get @@ -73,7 +73,7 @@ jobs: # uses: subosito/flutter-action@v2 # with: # channel: stable - # flutter-version: 3.38.7 + # flutter-version: 3.38.10 # - name: get deps # run: flutter pub get diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index c7747bb..ff20001 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -113,23 +113,23 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/wireguard_plugin/darwin" SPEC CHECKSUMS: - app_links: 3dbc685f76b1693c66a6d9dd1e9ab6f73d97dc0a - cupertino_http: 94ac07f5ff090b8effa6c5e2c47871d48ab7c86c - device_info_plus: 21fcca2080fbcd348be798aa36c3e5ed849eefbe + app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 + cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba + device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 - flutter_local_notifications: a5a732f069baa862e728d839dd2ebb904737effb - flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf - flutter_secure_storage: 1ed9476fba7e7a782b22888f956cce43e2c62f13 - local_auth_darwin: c3ee6cce0a8d56be34c8ccb66ba31f7f180aaebb - mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93 - package_info_plus: af8e2ca6888548050f16fa2f1938db7b5a5df499 - permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d - share_plus: 50da8cb520a8f0f65671c6c6a99b3617ed10a58a - shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb + flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f + flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29 + flutter_secure_storage: d33dac7ae2ea08509be337e775f6b59f1ff45f12 + local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 + mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e + package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 + permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f + shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b - sqlite3_flutter_libs: 52ecc4dfaae71f496da86159263dbce5d23a051a - url_launcher_ios: 7a95fa5b60cc718a708b8f2966718e93db0cef1b - wireguard_plugin: c2f4d5382eecd7bcd07c027642c75e0569f91ff8 + sqlite3_flutter_libs: 7bea6d85399aebaeb54e4f9845dcac6f5033cf22 + url_launcher_ios: bb13df5870e8c4234ca12609d04010a21be43dfa + wireguard_plugin: 4d2720563b180d23101f7162ecdbf57203e82e8e PODFILE CHECKSUM: ae9e65fc23486119b8e977fd41d9213f251e537a diff --git a/client/ios/VPNExtension/Adapter.swift b/client/ios/VPNExtension/Adapter.swift index 713fed9..bd36500 100644 --- a/client/ios/VPNExtension/Adapter.swift +++ b/client/ios/VPNExtension/Adapter.swift @@ -1,7 +1,6 @@ import Foundation import Network import NetworkExtension -import os /// State of Adapter. enum State { @@ -13,7 +12,7 @@ enum State { case dormant } -final class Adapter /*: Sendable*/ { +@preconcurrency final class Adapter /*: Sendable*/ { /// Packet tunnel provider. private weak var packetTunnelProvider: NEPacketTunnelProvider? /// BortingTun tunnel @@ -25,17 +24,26 @@ final class Adapter /*: Sendable*/ { /// Network routes monitor. private var networkMonitor: NWPathMonitor? /// Keep alive timer - private var keepAliveTimer: Timer? - /// Logging - private lazy var logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Adapter") + private var keepAliveTimer: DispatchSourceTimer? + /// Unified logger (writes to both system log and file) + private let log = Log(category: "Adapter") /// Adapter state. private var state: State = .stopped - private var reconnectOnExpiry: Bool = false + /// Serialize tunnel I/O and connection state changes off the main queue. + private let ioQueue = DispatchQueue(label: "net.defguard.VPNExtension.adapter") + private let ioQueueKey = DispatchSpecificKey() + + /// For statistics returned to Rust code. + var locationId: UInt64? + var tunnelId: UInt64? + + private let notificationCenter = CFNotificationCenterGetDarwinNotifyCenter() /// Designated initializer. /// - Parameter packetTunnelProvider: an instance of `NEPacketTunnelProvider`. Internally stored init(with packetTunnelProvider: NEPacketTunnelProvider) { self.packetTunnelProvider = packetTunnelProvider + self.ioQueue.setSpecific(key: ioQueueKey, value: ()) } deinit { @@ -43,8 +51,49 @@ final class Adapter /*: Sendable*/ { } func start(tunnelConfiguration: TunnelConfiguration) throws { - if let _ = tunnel { - logger.info("Cleaning exiting Tunnel") + try syncOnQueue { + try startOnQueue(tunnelConfiguration: tunnelConfiguration) + } + } + + func stop() { + syncOnQueue { + stopOnQueue() + } + } + + // Obtain tunnel statistics. + func stats() -> Stats? { + syncOnQueue { + guard let stats = tunnel?.stats() else { return nil } + return Stats( + txBytes: stats.txBytes, + rxBytes: stats.rxBytes, + lastHandshake: stats.lastHandshake, + locationId: locationId, + tunnelId: tunnelId + ) + } + } + + private func syncOnQueue(_ work: () throws -> T) rethrows -> T { + if DispatchQueue.getSpecific(key: ioQueueKey) == nil { + return try ioQueue.sync { + try work() + } + } + return try work() + } + + private func startOnQueue(tunnelConfiguration: TunnelConfiguration) throws { + guard case .stopped = self.state else { + log.error("Invalid state - cannot start tunnel") + // TODO: throw invalid state + return + } + + if tunnel != nil { + log.info("Cleaning existing Tunnel") tunnel = nil connection = nil } @@ -53,108 +102,102 @@ final class Adapter /*: Sendable*/ { networkMonitor.pathUpdateHandler = { [weak self] path in self?.networkPathUpdate(path: path) } - networkMonitor.start(queue: .main) + networkMonitor.start(queue: ioQueue) self.networkMonitor = networkMonitor - logger.info("Initializing Tunnel") + log.info("Initializing Tunnel") tunnel = try Tunnel.init( - privateKey: tunnelConfiguration.interface.privateKey, + privateKey: tunnelConfiguration.privateKey, serverPublicKey: tunnelConfiguration.peers[0].publicKey, presharedKey: tunnelConfiguration.peers[0].preSharedKey, keepAlive: tunnelConfiguration.peers[0].persistentKeepAlive, index: 0 ) + locationId = tunnelConfiguration.locationId + tunnelId = tunnelConfiguration.tunnelId - if tunnelConfiguration.peers[0].preSharedKey != nil { - logger.info("Using pre-shared key, the tunnel won't be re-established on expiry") - reconnectOnExpiry = false - } else { - logger.info("No pre-shared key, the tunnel will be re-established on expiry") - reconnectOnExpiry = true - } - - logger.info("Connecting to endpoint") + log.info( + "Connecting to endpoint (locationId: \(tunnelConfiguration.locationId ?? 0), tunnelId: \(tunnelConfiguration.tunnelId ?? 0))" + ) guard let endpoint = tunnelConfiguration.peers[0].endpoint else { - logger.error("Endpoint is nil") + log.error("Endpoint is nil, cannot connect") return } self.endpoint = endpoint.asNWEndpoint() initEndpoint() - logger.info("Sniffing packets") + log.info("Starting to sniff packets") readPackets() state = .running + log.info("Tunnel started successfully") } - func stop() { - logger.info("Stopping Adapter") + private func stopOnQueue() { + log.info("Stopping Adapter") connection?.cancel() connection = nil tunnel = nil - keepAliveTimer?.invalidate() + keepAliveTimer?.cancel() keepAliveTimer = nil // Cancel network monitor networkMonitor?.cancel() networkMonitor = nil + state = .stopped - logger.info("Tunnel stopped") + log.info("Tunnel stopped") + log.flush() } private func handleTunnelResult(_ result: TunnelResult) { + var tunnelPackets = [NEPacket]() + handleTunnelResult(result, tunnelPackets: &tunnelPackets) + flushTunnelPackets(tunnelPackets) + } + + private func handleTunnelResult(_ result: TunnelResult, tunnelPackets: inout [NEPacket]) { switch result { - case .done: - // Nothing to do. - break - case .err(let error): - logger.error("Tunnel error \(error, privacy: .public)") - switch error { - case .InvalidAeadTag: - logger.error("Invalid pre-shared key; stopping tunnel") - // The correct way is to call the packet tunnel provider, if there is one. - if let provider = packetTunnelProvider { - provider.cancelTunnelWithError(error) - } else { - stop() - } - case .ConnectionExpired: - packetTunnelProvider?.reasserting = true - if self.reconnectOnExpiry { - logger.error("Connecion has expired; re-connecting") - initEndpoint() - logger.info("Finished re-connecting") - } else { - logger.error("Connection has expired; stopping tunnel") - let defaults = UserDefaults(suiteName: suiteName) - defaults?.set( - TunnelStopError.mfaSessionExpired.rawValue - , forKey: "lastTunnelError") - if let provider = packetTunnelProvider { - provider.cancelTunnelWithError(error) - } else { - stop() - } - } - packetTunnelProvider?.reasserting = false - default: - break + case .done: + // Nothing to do. + break + case .err(let error): + log.error("Tunnel error: \(error)") + switch error { + case .InvalidAeadTag: + log.error("Invalid pre-shared key; stopping tunnel") + // The correct way is to call the packet tunnel provider, if there is one. + if let provider = packetTunnelProvider { + provider.cancelTunnelWithError(error) + } else { + stop() } - case .writeToNetwork(let data): - sendToEndpoint(data: data) - case .writeToTunnelV4(let data): - packetTunnelProvider?.packetFlow.writePacketObjects([ - NEPacket(data: data,protocolFamily: sa_family_t(AF_INET))]) - case .writeToTunnelV6(let data): - packetTunnelProvider?.packetFlow.writePacketObjects([ - NEPacket(data: data, protocolFamily: sa_family_t(AF_INET6))]) + case .ConnectionExpired: + log.warning("Connection has expired; re-connecting") + packetTunnelProvider?.reasserting = true + initEndpoint() + packetTunnelProvider?.reasserting = false + default: + break + } + case .writeToNetwork(let data): + sendToEndpoint(data: data) + case .writeToTunnelV4(let data): + tunnelPackets.append(NEPacket(data: data, protocolFamily: sa_family_t(AF_INET))) + case .writeToTunnelV6(let data): + tunnelPackets.append(NEPacket(data: data, protocolFamily: sa_family_t(AF_INET6))) } } + private func flushTunnelPackets(_ tunnelPackets: [NEPacket]) { + guard !tunnelPackets.isEmpty else { return } + packetTunnelProvider?.packetFlow.writePacketObjects(tunnelPackets) + } + /// Initialise UDP connection to endpoint. private func initEndpoint() { guard let endpoint = endpoint else { return } - logger.info("Init Endpoint") + log.info("Initializing endpoint connection to: \(endpoint)") // Cancel previous connection connection?.cancel() connection = nil @@ -166,43 +209,53 @@ final class Adapter /*: Sendable*/ { self?.endpointStateChange(state: state) } - connection.start(queue: .main) + connection.start(queue: ioQueue) self.connection = connection } /// Setup UDP connection to endpoint. This method should be called when UDP connection is ready to send and receive. private func setupEndpoint() { - logger.info("Setup endpoint") + log.info("Setting up endpoint") // Send initial handshake packet if let tunnel = self.tunnel { + log.info("Sending initial handshake") handleTunnelResult(tunnel.forceHandshake()) } - logger.info("Receiving UDP from endpoint") + log.info("Starting UDP receive loop") + log.debug("NWConnection path: \(String(describing: self.connection?.currentPath))") receive() - // Use Timer to send keep-alive packets. - keepAliveTimer?.invalidate() - logger.info("Creating keep-alive timer") - let timer = Timer(timeInterval: 0.25, repeats: true) { [weak self] timer in + // Use a dispatch timer to avoid bouncing keep-alives through the main run loop. + keepAliveTimer?.cancel() + log.info("Creating keep-alive timer") + let timer = DispatchSource.makeTimerSource(queue: ioQueue) + timer.schedule( + deadline: .now() + .milliseconds(250), + repeating: .milliseconds(250), + leeway: .milliseconds(25) + ) + timer.setEventHandler { [weak self] in guard let self = self, let tunnel = self.tunnel else { return } self.handleTunnelResult(tunnel.tick()) } keepAliveTimer = timer - RunLoop.main.add(timer, forMode: .common) + timer.resume() } /// Send packets to UDP endpoint. private func sendToEndpoint(data: Data) { guard let connection = connection else { return } if connection.state == .ready { - connection.send(content: data, completion: .contentProcessed { error in - if let error = error { - self.logger.error("UDP connection send error: \(error, privacy: .public)") - } - }) + connection.send( + content: data, + completion: .contentProcessed { [weak self] error in + if let error = error { + self?.log.error("UDP connection send error: \(error)") + } + }) } else { - logger.warning("UDP connection not ready to send") + log.warning("UDP connection not ready to send") } } @@ -211,60 +264,89 @@ final class Adapter /*: Sendable*/ { connection?.receiveMessage { [weak self] data, context, isComplete, error in guard let self = self else { return } if let data = data, let tunnel = self.tunnel { - self.handleTunnelResult(tunnel.read(src: data)) + autoreleasepool { + self.handleTunnelResult(tunnel.read(src: data)) + } } if error == nil { // continue receiving self.receive() + } else { + self.log.error("receive() error: \(String(describing: error))") } } } /// Read tunnel packets. private func readPackets() { + // Packets received to the tunnel's virtual interface. + packetTunnelProvider?.packetFlow.readPacketObjects { [weak self] packets in + guard let self = self else { return } + + self.ioQueue.async { + self.processTunnelPackets(packets) + + // continue reading + self.readPackets() + } + } + } + + private func processTunnelPackets(_ packets: [NEPacket]) { guard let tunnel = self.tunnel else { return } - // Packets received to the tunnel's virtual interface. - packetTunnelProvider?.packetFlow.readPacketObjects { packets in - for packet in packets { - self.handleTunnelResult(tunnel.write(src: packet.data)) + var tunnelPackets = [NEPacket]() + tunnelPackets.reserveCapacity(packets.count) + + for packet in packets { + autoreleasepool { + self.handleTunnelResult(tunnel.write(src: packet.data), tunnelPackets: &tunnelPackets) } - // continue reading - self.readPackets() } + + flushTunnelPackets(tunnelPackets) } /// Handle UDP connection state changes. private func endpointStateChange(state: NWConnection.State) { - logger.debug("UDP connection state: \(String(describing: state), privacy: .public)") + log.debug("UDP connection state changed: \(state)") switch state { - case .ready: - setupEndpoint() - case .failed(let error): - logger.error("Failed to establish endpoint connection: \(error)") - // The correct way is to call the packet tunnel provider, if there is one. - if let provider = packetTunnelProvider { - provider.cancelTunnelWithError(error) - } else { - stop() - } - default: - break + case .ready: + setupEndpoint() + //case .waiting(let error): + // switch error { + // case .posix(_): + // connection?.restart() + // default: + // self.stop() + // } + case .failed(let error): + log.error("Failed to establish endpoint connection: \(error)") + // The correct way is to call the packet tunnel provider, if there is one. + if let provider = packetTunnelProvider { + provider.cancelTunnelWithError(error) + } else { + stop() + } + default: + break } } /// Handle network path updates. private func networkPathUpdate(path: Network.NWPath) { + log.debug( + "Network path update - status: \(path.status), interfaces: \(path.availableInterfaces)") if path.status == .unsatisfied { if state == .running { - logger.warning("Unsatisfied network path: going dormant") + log.warning("Unsatisfied network path: going dormant") connection?.cancel() connection = nil state = .dormant } } else { if state == .dormant { - logger.warning("Satisfied network path: going running") + log.warning("Satisfied network path: going running") initEndpoint() state = .running } diff --git a/client/ios/VPNExtension/Decodabe+Encodable.swift b/client/ios/VPNExtension/Decodabe+Encodable.swift index bf01a54..d3f8a8f 100644 --- a/client/ios/VPNExtension/Decodabe+Encodable.swift +++ b/client/ios/VPNExtension/Decodabe+Encodable.swift @@ -2,7 +2,7 @@ import Foundation extension Decodable { static func from(dictionary: [String: Any]) throws -> Self { - let data = try JSONSerialization.data(withJSONObject: dictionary, options: []) + let data = try JSONSerialization.data(withJSONObject: dictionary) let decoder = JSONDecoder() return try decoder.decode(Self.self, from: data) } @@ -13,7 +13,9 @@ extension Encodable { let data = try JSONEncoder().encode(self) let jsonObject = try JSONSerialization.jsonObject(with: data, options: .allowFragments) guard let dictionary = jsonObject as? [String: Any] else { - throw NSError(domain: "EncodingError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to convert to dictionary"]) + throw NSError( + domain: "EncodingError", code: 0, + userInfo: [NSLocalizedDescriptionKey: "Failed to convert to dictionary"]) } return dictionary } diff --git a/client/ios/VPNExtension/Endpoint.swift b/client/ios/VPNExtension/Endpoint.swift index 790aee3..64f50a4 100644 --- a/client/ios/VPNExtension/Endpoint.swift +++ b/client/ios/VPNExtension/Endpoint.swift @@ -1,3 +1,4 @@ +import Foundation import Network struct Endpoint: Codable, CustomStringConvertible { @@ -15,9 +16,11 @@ struct Endpoint: Codable, CustomStringConvertible { var endpointHost = trimmedEndpoint // Extract host, supporting IPv4, IPv6, and domains - if trimmedEndpoint.hasPrefix("[") { // IPv6 with port, e.g. [fd00::1]:51820 + if trimmedEndpoint.hasPrefix("[") { // IPv6 with port, e.g. [fd00::1]:51820 if let closing = trimmedEndpoint.firstIndex(of: "]") { - endpointHost = String(trimmedEndpoint[trimmedEndpoint.index(after: trimmedEndpoint.startIndex).. String { + "\(host):\(port)" } + // Encode to a single string "host:port", to smoothly encode into JSON. func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode("\(host)", forKey: .host) - try container.encode(port.rawValue, forKey: .port) + var container = encoder.singleValueContainer() + try container.encode(self.toString()) } + // Decode from a single string "host:port", to smoothly decode from JSON. init(from decoder: Decoder) throws { - let values = try decoder.container(keyedBy: CodingKeys.self) - - host = try NWEndpoint.Host(values.decode(String.self, forKey: .host)) - port = try NWEndpoint.Port(rawValue: values.decode(UInt16.self, forKey: .port)) ?? 0 + let container = try decoder.singleValueContainer() + let value = try container.decode(String.self) + guard let endpoint = Endpoint(from: value) else { + throw + DecodingError + .dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Not in host:port format") + ) + } + self = endpoint } func asNWEndpoint() -> NWEndpoint { diff --git a/client/ios/VPNExtension/FileLogger.swift b/client/ios/VPNExtension/FileLogger.swift new file mode 100644 index 0000000..71d0297 --- /dev/null +++ b/client/ios/VPNExtension/FileLogger.swift @@ -0,0 +1,251 @@ +import Foundation +import os + +/// Log levels +enum LogLevel: String { + case debug = "DEBUG" + case info = "INFO" + case warning = "WARN" + case error = "ERROR" + + var osLogType: OSLogType { + switch self { + case .debug: return .debug + case .info: return .info + case .warning: return .default + case .error: return .error + } + } +} + +/// Logger that writes to both system log (os.Logger) and file. +/// Use this instead of os.Logger directly to get dual logging with a single call. +final class Log { + /// The category for this logger instance (usually class name), e.g. "PacketTunnelProvider" + let category: String + private let systemLogger: Logger +#if os(macOS) + private let fileLogger = FileLogger.shared +#endif + + init(category: String) { + self.category = category + self.systemLogger = Logger( + subsystem: Bundle.main.bundleIdentifier ?? "net.defguard.VPNExtension", + category: category + ) + } + + func debug(_ message: String) { + systemLogger.debug("\(message, privacy: .public)") +#if os(macOS) + fileLogger.log(level: .debug, message: message, category: category) +#endif + } + + func info(_ message: String) { + systemLogger.info("\(message, privacy: .public)") +#if os(macOS) + fileLogger.log(level: .info, message: message, category: category) +#endif + } + + func warning(_ message: String) { + systemLogger.warning("\(message, privacy: .public)") +#if os(macOS) + fileLogger.log(level: .warning, message: message, category: category) +#endif + } + + func error(_ message: String) { + systemLogger.error("\(message, privacy: .public)") +#if os(macOS) + fileLogger.log(level: .error, message: message, category: category) +#endif + } + + func flush() { +#if os(macOS) + fileLogger.flush() +#endif + } +} + +#if os(macOS) +/// A file-based logger that writes to an App Group shared container. +/// This allows the main rust app to read logs from the network extension. +/// Use the `Log` class instead of this directly for unified logging. +final class FileLogger { + static let shared = FileLogger() + static let appGroupIdentifier = "group.net.defguard" + private let logFileName = "vpn-extension.log" + private let maxLogFileSize: UInt64 = 5 * 1024 * 1024 // 5 MB + private let maxBackupFiles = 3 + private let flushInterval = 5 // Flush every N log entries + private var fileHandle: FileHandle? + private var logFileURL: URL? + private var unflushedCount = 0 + + private let dateFormatter: DateFormatter = { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS" + formatter.locale = Locale(identifier: "en_US_POSIX") + formatter.timeZone = TimeZone(identifier: "UTC") + return formatter + }() + + private let queue = DispatchQueue(label: "net.defguard.VPNExtension.filelogger") + + private let internalLogger = Logger( + subsystem: Bundle.main.bundleIdentifier ?? "net.defguard.VPNExtension", + category: "FileLogger") + + private init() { + setupLogFile() + } + + deinit { + closeLogFile() + } + + private func setupLogFile() { + guard + let containerURL = FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: Self.appGroupIdentifier) + else { + internalLogger.error( + "Failed to get App Group container URL for \(Self.appGroupIdentifier)") + return + } + + let logsDirectory = containerURL.appendingPathComponent("Logs", isDirectory: true) + + do { + try FileManager.default.createDirectory( + at: logsDirectory, withIntermediateDirectories: true, attributes: nil) + } catch { + internalLogger.error("Failed to create Logs directory: \(error.localizedDescription)") + return + } + + logFileURL = logsDirectory.appendingPathComponent(logFileName) + + guard let logFileURL = logFileURL else { return } + + if !FileManager.default.fileExists(atPath: logFileURL.path) { + FileManager.default.createFile(atPath: logFileURL.path, contents: nil, attributes: nil) + } + + do { + fileHandle = try FileHandle(forWritingTo: logFileURL) + fileHandle?.seekToEndOfFile() + + let startupMessage = + "# VPN Extension Log Started at \(dateFormatter.string(from: Date()))\n" + if let data = startupMessage.data(using: .utf8) { + fileHandle?.write(data) + } + } catch { + internalLogger.error( + "Failed to open log file for writing: \(error.localizedDescription)") + } + + internalLogger.info("FileLogger initialized at: \(logFileURL.path)") + } + + private func closeLogFile() { + queue.sync { + try? fileHandle?.synchronize() + try? fileHandle?.close() + fileHandle = nil + } + } + + /// Rotate log files if the current one exceeds the maximum size + private func rotateLogFilesIfNeeded() { + guard let logFileURL = logFileURL else { return } + + do { + let attributes = try FileManager.default.attributesOfItem(atPath: logFileURL.path) + if let fileSize = attributes[.size] as? UInt64, fileSize >= maxLogFileSize { + rotateLogFiles() + } + } catch { + } + } + + private func rotateLogFiles() { + guard let logFileURL = logFileURL else { return } + + try? fileHandle?.synchronize() + try? fileHandle?.close() + fileHandle = nil + + let fileManager = FileManager.default + let directory = logFileURL.deletingLastPathComponent() + let baseName = logFileURL.deletingPathExtension().lastPathComponent + let ext = logFileURL.pathExtension + + // Remove oldest backup if it exists + let oldestBackup = directory.appendingPathComponent("\(baseName).\(maxBackupFiles).\(ext)") + try? fileManager.removeItem(at: oldestBackup) + + for i in stride(from: maxBackupFiles - 1, through: 1, by: -1) { + let current = directory.appendingPathComponent("\(baseName).\(i).\(ext)") + let next = directory.appendingPathComponent("\(baseName).\(i + 1).\(ext)") + try? fileManager.moveItem(at: current, to: next) + } + + let firstBackup = directory.appendingPathComponent("\(baseName).1.\(ext)") + try? fileManager.moveItem(at: logFileURL, to: firstBackup) + + fileManager.createFile(atPath: logFileURL.path, contents: nil, attributes: nil) + + do { + fileHandle = try FileHandle(forWritingTo: logFileURL) + fileHandle?.seekToEndOfFile() + } catch { + internalLogger.error( + "Failed to reopen log file after rotation: \(error.localizedDescription)") + } + } + + /// Write a log message to the file + /// - level: Log level (debug, info, warning, error) + /// - message: The message to log + /// - category: Optional category/subsystem + func log(level: LogLevel, message: String, category: String? = nil) { + queue.async { [weak self] in + guard let self = self, let fileHandle = self.fileHandle else { return } + + self.rotateLogFilesIfNeeded() + + let timestamp = self.dateFormatter.string(from: Date()) + let categoryStr = category.map { "[\($0)] " } ?? "" + let logLine = "\(timestamp) [\(level.rawValue)] \(categoryStr)\(message)\n" + + if let data = logLine.data(using: .utf8) { + fileHandle.write(data) + self.unflushedCount += 1 + + // Flush for important messages or periodically + if level == .error || level == .warning || self.unflushedCount >= self.flushInterval + { + try? fileHandle.synchronize() + self.unflushedCount = 0 + } + } + } + } + + func flush() { + queue.sync { + try? fileHandle?.synchronize() + } + } + + var logFilePath: String? { + return logFileURL?.path + } +} +#endif diff --git a/client/ios/VPNExtension/InterfaceConfiguration.swift b/client/ios/VPNExtension/InterfaceConfiguration.swift deleted file mode 100644 index c73f539..0000000 --- a/client/ios/VPNExtension/InterfaceConfiguration.swift +++ /dev/null @@ -1,15 +0,0 @@ -import Foundation -import NetworkExtension - -final class InterfaceConfiguration: Codable { - var privateKey: String - var addresses: [IpAddrMask] = [] - var listenPort: UInt16? - var mtu: UInt32? - var dns: [String] = [] - var dnsSearch: [String] = [] - - init(privateKey: String) { - self.privateKey = privateKey - } -} diff --git a/client/ios/VPNExtension/IpAddrMask.swift b/client/ios/VPNExtension/IpAddrMask.swift index 32f972d..fefd845 100644 --- a/client/ios/VPNExtension/IpAddrMask.swift +++ b/client/ios/VPNExtension/IpAddrMask.swift @@ -44,7 +44,7 @@ struct IpAddrMask: Codable, Equatable { /// Conform to `Encodable`. func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(address.rawValue, forKey: .address) + try container.encode("\(address)", forKey: .address) try container.encode(cidr, forKey: .cidr) } @@ -52,39 +52,21 @@ struct IpAddrMask: Codable, Equatable { init(from decoder: Decoder) throws { let values = try decoder.container(keyedBy: CodingKeys.self) - let address_data = try values.decode(Data.self, forKey: .address) - switch address_data.count { - case 4: - guard let ipv4 = IPv4Address(address_data) else { - throw - DecodingError - .dataCorrupted( - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unable to decode IP v4 address" - )) - - } + let address_string = try values.decode(String.self, forKey: .address) + if let ipv4 = IPv4Address(address_string) { address = ipv4 - case 16: - guard let ipv6 = IPv6Address(address_data) else { - throw - DecodingError - .dataCorrupted( - DecodingError.Context( - codingPath: decoder.codingPath, - debugDescription: "Unable to decode IP v6 address" - )) - - } + } else if let ipv6 = IPv6Address(address_string) { address = ipv6 - default: - throw DecodingError.typeMismatch( - IpAddrMask.self, - DecodingError.Context( - codingPath: decoder.codingPath, debugDescription: "Invalid IP address length" - )) + } else { + throw + DecodingError + .dataCorrupted( + DecodingError.Context( + codingPath: decoder.codingPath, + debugDescription: "Unable to decode IP address" + )) } + cidr = try values.decode(UInt8.self, forKey: .cidr) } diff --git a/client/ios/VPNExtension/PacketTunnelProvider.swift b/client/ios/VPNExtension/PacketTunnelProvider.swift index 159bf97..d6aa56a 100644 --- a/client/ios/VPNExtension/PacketTunnelProvider.swift +++ b/client/ios/VPNExtension/PacketTunnelProvider.swift @@ -1,96 +1,97 @@ import NetworkExtension -import os -import Network -enum VPNEventType: String { - case tunnelUp = "tunnel_up" - case tunnelDown = "tunnel_down" - case tunnelError = "tunnel_error" - case connectionStatusChanged = "connection_status_changed" - case bytesTransferred = "bytes_transferred" +enum WireGuardTunnelError: Error { + case invalidTunnelConfiguration } class PacketTunnelProvider: NEPacketTunnelProvider { - /// Logging - private var logger = Logger( - subsystem: Bundle.main.bundleIdentifier!, - category: "PacketTunnelProvider" - ) + /// Unified logger (writes to both system log and file) + private let log = Log(category: "PacketTunnelProvider") private lazy var adapter: Adapter = { return Adapter(with: self) }() - override func startTunnel(options: [String : NSObject]?, completionHandler: @escaping (Error?) -> Void) { - guard let tunnelConfig = extractTunnelConfiguration() else { - let error = NSError(domain: "VPNExtension", code: -1, - userInfo: [NSLocalizedDescriptionKey: "Tunnel configuration is missing or invalid."]) - logger.error("Tunnel configuration is missing or invalid.") - completionHandler(error) - return + override func startTunnel( + options: [String: NSObject]?, completionHandler: @escaping (Error?) -> Void + ) { + if let options = options { + log.debug("Options: \(options)") } - logger.log("Starting tunnel with configuration: \(String(describing: tunnelConfig), privacy: .public)") + guard let protocolConfig = self.protocolConfiguration as? NETunnelProviderProtocol, + let providerConfig = protocolConfig.providerConfiguration + else { + log.error("Failed to parse provider configuration") + completionHandler(WireGuardTunnelError.invalidTunnelConfiguration) + return + } - guard Endpoint(from: tunnelConfig.endpoint) != nil else { - let error = NSError(domain: "VPNExtension", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid endpoint format: \(tunnelConfig.endpoint)"]) - logger.error("Invalid endpoint format: \(tunnelConfig.endpoint, privacy: .public)") - completionHandler(error) +#if os(macOS) + guard let tunnelConfig = try? TunnelConfiguration.from(dictionary: providerConfig) + else { + log.error("Failed to parse tunnel configuration") + completionHandler(WireGuardTunnelError.invalidTunnelConfiguration) return } +#else + guard let startData = try? TunnelStartData.from(dictionary: providerConfig) + else { + log.error("Failed to parse tunnel configuration") + completionHandler(WireGuardTunnelError.invalidTunnelConfiguration) + return + } + let tunnelConfig = TunnelConfiguration(fromStartData: startData) +#endif - let tunnelConfiguration = TunnelConfiguration(fromStartData: tunnelConfig) - let networkSettings = tunnelConfiguration.asNetworkSettings() - - setTunnelNetworkSettings(networkSettings) { [weak self] error in - guard let self = self else { return } - - if let error = error { - logger.warning("Set tunnel network settings returned an error \(error, privacy: .public)") - completionHandler(error) - return - } - - do { - try self.adapter.start(tunnelConfiguration: tunnelConfiguration) - } catch { - logger.error("Failed to start adapter with error: \(error.localizedDescription, privacy: .public)") - completionHandler(error) - return + let networkSettings = tunnelConfig.asNetworkSettings() + self.setTunnelNetworkSettings(networkSettings) { error in + if error != nil { + self.log.error("Failed to set tunnel network settings: \(String(describing: error))") } + completionHandler(error) + return + } - logger.log("Tunnel started successfully") - completionHandler(nil) + do { + try adapter.start(tunnelConfiguration: tunnelConfig) + } catch { + log.error("Failed to start tunnel: \(error)") + completionHandler(error) } + log.info("Tunnel started successfully") + + completionHandler(nil) } - override func stopTunnel(with reason: NEProviderStopReason, completionHandler: @escaping () -> Void) { - self.adapter.stop() + override func stopTunnel( + with reason: NEProviderStopReason, completionHandler: @escaping () -> Void + ) { + adapter.stop() + log.info("Tunnel stopped") completionHandler() } override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) { - logger.debug("\(#function)") + // TODO: messageData should contain a valid message. if let handler = completionHandler { - handler(messageData) + if let stats = adapter.stats() { + let data = try? JSONEncoder().encode(stats) + handler(data) + } else { + handler(nil) + } } } override func sleep(completionHandler: @escaping () -> Void) { - logger.debug("\(#function)") + log.info("System going to sleep") + // Add code here to get ready to sleep. completionHandler() } override func wake() { - logger.debug("\(#function)") - } - - // MARK: - Helpers - - private func extractTunnelConfiguration() -> TunnelStartData? { - guard let providerConfig = (self.protocolConfiguration as? NETunnelProviderProtocol)?.providerConfiguration as? [String: Any] else { - return nil - } - return try? TunnelStartData.from(dictionary: providerConfig) + log.info("System waking up") + // Add code here to wake up. } } diff --git a/client/ios/VPNExtension/Peer.swift b/client/ios/VPNExtension/Peer.swift index e30b61b..fc07220 100644 --- a/client/ios/VPNExtension/Peer.swift +++ b/client/ios/VPNExtension/Peer.swift @@ -4,23 +4,26 @@ final class Peer: Codable { var publicKey: String var preSharedKey: String? var endpoint: Endpoint? + var persistentKeepAlive: UInt16? + var allowedIPs = [IpAddrMask]() + // Statistics var lastHandshake: Date? var txBytes: UInt64 = 0 var rxBytes: UInt64 = 0 - var persistentKeepAlive: UInt16? - var allowedIPs = [IpAddrMask]() - init(publicKey: String, preSharedKey: String? = nil, endpoint: Endpoint? = nil, - lastHandshake: Date? = nil, txBytes: UInt64 = 0, rxBytes: UInt64 = 0, - persistentKeepAlive: UInt16? = nil, allowedIPs: [IpAddrMask] = [IpAddrMask]()) { + init( + publicKey: String, preSharedKey: String? = nil, endpoint: Endpoint? = nil, + persistentKeepAlive: UInt16? = nil, allowedIPs: [IpAddrMask] = [IpAddrMask](), + lastHandshake: Date? = nil, txBytes: UInt64 = 0, rxBytes: UInt64 = 0, + ) { self.publicKey = publicKey self.preSharedKey = preSharedKey self.endpoint = endpoint + self.persistentKeepAlive = persistentKeepAlive + self.allowedIPs = allowedIPs self.lastHandshake = lastHandshake self.txBytes = txBytes self.rxBytes = rxBytes - self.persistentKeepAlive = persistentKeepAlive - self.allowedIPs = allowedIPs } init(publicKey: String) { @@ -31,10 +34,11 @@ final class Peer: Codable { case publicKey case preSharedKey case endpoint - case lastHandshake - case txBytes - case rxBytes case persistentKeepAlive case allowedIPs + // There isn't any need to encode/decode these ephemeral fields. + // case lastHandshake + // case txBytes + // case rxBytes } } diff --git a/client/ios/VPNExtension/Stats.swift b/client/ios/VPNExtension/Stats.swift new file mode 100644 index 0000000..2c83344 --- /dev/null +++ b/client/ios/VPNExtension/Stats.swift @@ -0,0 +1,18 @@ +import ObjectiveC + +public class Stats: NSObject, Codable { + var txBytes: UInt64 + var rxBytes: UInt64 + var lastHandshake: UInt64 + // One or the other. + var locationId: UInt64? + var tunnelId: UInt64? + + init(txBytes: UInt64, rxBytes: UInt64, lastHandshake: UInt64, locationId: UInt64?, tunnelId: UInt64?) { + self.txBytes = txBytes + self.rxBytes = rxBytes + self.lastHandshake = lastHandshake + self.locationId = locationId + self.tunnelId = tunnelId + } +} diff --git a/client/ios/VPNExtension/TunnelConfiguration.swift b/client/ios/VPNExtension/TunnelConfiguration.swift index 33765df..ca4f76e 100644 --- a/client/ios/VPNExtension/TunnelConfiguration.swift +++ b/client/ios/VPNExtension/TunnelConfiguration.swift @@ -2,14 +2,23 @@ import Foundation import NetworkExtension final class TunnelConfiguration: Codable { - var name: String - var interface: InterfaceConfiguration - var peers: [Peer] + // One or the other. + var locationId: UInt64? + var tunnelId: UInt64? - init(name: String, interface: InterfaceConfiguration, peers: [Peer]) { - self.interface = interface - self.peers = peers + var name: String + var privateKey: String + var addresses: [IpAddrMask] = [] + var listenPort: UInt16? + var peers: [Peer] = [] + var mtu: UInt32? + var dns: [String] = [] + var dnsSearch: [String] = [] + + init(name: String, privateKey: String, peers: [Peer]) { self.name = name + self.privateKey = privateKey + self.peers = peers let peerPublicKeysArray = peers.map { $0.publicKey } let peerPublicKeysSet = Set(peerPublicKeysArray) @@ -18,11 +27,18 @@ final class TunnelConfiguration: Codable { } } - // Only encode these properties. + /// Only encode these properties. enum CodingKeys: String, CodingKey { + case locationId + case tunnelId case name - case interface + case privateKey + case addresses + case listenPort case peers + case mtu + case dns + case dnsSearch } func asNetworkSettings() -> NEPacketTunnelNetworkSettings { @@ -32,31 +48,31 @@ final class TunnelConfiguration: Codable { let (ipv4IncludedRoutes, ipv6IncludedRoutes) = routes() // IPv4 addresses - let addrs_v4 = interface.addresses.filter { $0.address is IPv4Address } + let addrs_v4 = addresses.filter { $0.address is IPv4Address } .map { String(describing: $0.address) } - let masks_v4 = interface.addresses.filter { $0.address is IPv4Address } + let masks_v4 = addresses.filter { $0.address is IPv4Address } .map { String(describing: $0.mask()) } let ipv4Settings = NEIPv4Settings(addresses: addrs_v4, subnetMasks: masks_v4) ipv4Settings.includedRoutes = ipv4IncludedRoutes networkSettings.ipv4Settings = ipv4Settings // IPv6 addresses - let addrs_v6 = interface.addresses.filter { $0.address is IPv6Address } + let addrs_v6 = addresses.filter { $0.address is IPv6Address } .map { String(describing: $0.address) } // IMPORTANT: macOS/iOS has limitations handling IPv6 prefix masks longer than /120 due to // standards compliance and implementation choices in its network stack. - let masks_v6 = interface.addresses.filter { $0.address is IPv6Address } + let masks_v6 = addresses.filter { $0.address is IPv6Address } .map { NSNumber(value: min(120, $0.cidr)) } let ipv6Settings = NEIPv6Settings(addresses: addrs_v6, networkPrefixLengths: masks_v6) ipv6Settings.includedRoutes = ipv6IncludedRoutes networkSettings.ipv6Settings = ipv6Settings - networkSettings.mtu = interface.mtu as NSNumber? + networkSettings.mtu = mtu as NSNumber? networkSettings.tunnelOverheadBytes = 80 - let dnsSettings = NEDNSSettings(servers: interface.dns) - dnsSettings.searchDomains = interface.dnsSearch - if !interface.dns.isEmpty { + let dnsSettings = NEDNSSettings(servers: dns) + dnsSettings.searchDomains = dnsSearch + if !dns.isEmpty { // Make all DNS queries go through the tunnel. dnsSettings.matchDomains = [""] } @@ -71,7 +87,7 @@ final class TunnelConfiguration: Codable { var ipv6IncludedRoutes = [NEIPv6Route]() // Routes to interface addresses. - for addr_mask in interface.addresses { + for addr_mask in addresses { if addr_mask.address is IPv4Address { let route = NEIPv4Route( destinationAddress: "\(addr_mask.maskedAddress())", @@ -108,6 +124,7 @@ final class TunnelConfiguration: Codable { return (ipv4IncludedRoutes, ipv6IncludedRoutes) } +#if os(iOS) /// Helper function allowing to parse comma-separated string of addresses. private func parseAddresses(fromString string: String) -> [IpAddrMask] { var addresses: [IpAddrMask] = [] @@ -125,23 +142,22 @@ final class TunnelConfiguration: Codable { init(fromStartData startData: TunnelStartData) { name = startData.locationName - interface = InterfaceConfiguration(privateKey: startData.privateKey) + privateKey = startData.privateKey let peer = Peer(publicKey: startData.publicKey) peers = [peer] - interface.addresses = self.parseAddresses(fromString: startData.address) + addresses = self.parseAddresses(fromString: startData.address) // DNS settings - let dnsRecords = - startData.dns?.split(separator: ",").map { - $0.trimmingCharacters(in: .whitespaces) - } ?? [] + let dnsRecords = startData.dns?.split(separator: ",").map { + $0.trimmingCharacters(in: .whitespaces) + } ?? [] if !dnsRecords.isEmpty { for record in dnsRecords { if IPv4Address(record) != nil || IPv6Address(record) != nil { - interface.dns.append(record) + dns.append(record) } else { - interface.dnsSearch.append(record) + dnsSearch.append(record) } } } @@ -151,7 +167,7 @@ final class TunnelConfiguration: Codable { peer.endpoint = Endpoint(from: startData.endpoint) peer.persistentKeepAlive = UInt16(startData.keepalive) peer.allowedIPs = - switch startData.traffic { + switch startData.traffic { case .All: [ IpAddrMask(address: IPv4Address.any, cidr: 0), @@ -159,14 +175,12 @@ final class TunnelConfiguration: Codable { ] case .Predefined: self.parseAddresses(fromString: startData.allowedIps) - } + } } -} +#endif -//extension TunnelConfiguration: Equatable { -// public static func == (lhs: TunnelConfiguration, rhs: TunnelConfiguration) -> Bool { -// return lhs.name == rhs.name && -// lhs.interface == rhs.interface && -// Set(lhs.peers) == Set(rhs.peers) -// } -//} + /// Client connection expects one peer, so check for that. + func isValidForClientConnection() -> Bool { + return peers.count == 1 + } +} diff --git a/client/ios/boringtun b/client/ios/boringtun index b990805..b7c2922 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit b990805fc1637eeaa401bc156adf0dd28b2e50b8 +Subproject commit b7c29222f9881165e514088cc8f6c6463e0aa452 diff --git a/client/pubspec.lock b/client/pubspec.lock index dfff894..11d313c 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -93,10 +93,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" boolean_selector: dependency: transitive description: @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "6ae8a6435a8c6520c7077b107e77f1fb4ba7009633259a4d49a8afd8e7efc5e9" + sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" url: "https://pub.dev" source: hosted - version: "8.12.4" + version: "8.12.6" characters: dependency: transitive description: @@ -269,10 +269,10 @@ packages: dependency: "direct main" description: name: cookie_jar - sha256: a6ac027d3ed6ed756bfce8f3ff60cb479e266f3b0fdabd6242b804b6765e52de + sha256: "963da02c1ef64cb5ac20de948c9e5940aa351f1e34a12b1d327c83d85b7e8fff" url: "https://pub.dev" source: hosted - version: "4.0.8" + version: "4.0.9" coverage: dependency: transitive description: @@ -325,10 +325,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 + sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd" url: "https://pub.dev" source: hosted - version: "1.0.8" + version: "1.0.9" custom_lint: dependency: "direct dev" description: @@ -397,26 +397,26 @@ packages: dependency: "direct main" description: name: dio - sha256: b9d46faecab38fc8cc286f80bc4d61a3bb5d4ac49e51ed877b4d6706efe57b25 + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c url: "https://pub.dev" source: hosted - version: "5.9.1" + version: "5.9.2" dio_cookie_manager: dependency: "direct main" description: name: dio_cookie_manager - sha256: d39c16abcc711c871b7b29bd51c6b5f3059ef39503916c6a9df7e22c4fc595e0 + sha256: "0db1a7b997a0455e488ac35744c68eed3f2a4280d3ab531835a65641b0a08744" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.4.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: "7586e476d70caecaf1686d21eee7247ea43ef5c345eab9e0cc3583ff13378d78" + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" drift: dependency: "direct main" description: @@ -562,10 +562,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: ee8068e0e1cd16c4a82714119918efdeed33b3ba7772c54b5d094ab53f9b7fd1 + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" url: "https://pub.dev" source: hosted - version: "2.0.33" + version: "2.0.34" flutter_riverpod: dependency: "direct main" description: @@ -634,10 +634,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: "87fbd7c534435b6c5d9d98b01e1fd527812b82e68ddd8bd35fc45ed0fa8f0a95" + sha256: "35882981abcbfb8c15b286f0cd690ff25bac12d95eff3e25ee207f37d4c42e7f" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.0" flutter_test: dependency: "direct dev" description: flutter @@ -708,18 +708,18 @@ packages: dependency: transitive description: name: gtk - sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c + sha256: "4ff85b2a16724029dd9e5bbb5a94b6918f9973f74ba571c949d2002801879cf5" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" hooks: dependency: transitive description: name: hooks - sha256: "7a08a0d684cb3b8fb604b78455d5d352f502b68079f7b80b831c62220ab0a4f6" + sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.3" hooks_riverpod: dependency: "direct main" description: @@ -732,10 +732,10 @@ packages: dependency: transitive description: name: hotreloader - sha256: bc167a1163807b03bada490bfe2df25b0d744df359227880220a5cbd04e5734b + sha256: "66871df468fc24eee81f1a0a7cb98acc104716f9b7376d355437b48d633c4ebf" url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" html: dependency: transitive description: @@ -964,10 +964,10 @@ packages: dependency: transitive description: name: native_toolchain_c - sha256: "89e83885ba09da5fdf2cdacc8002a712ca238c28b7f717910b34bcd27b0d03ac" + sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" url: "https://pub.dev" source: hosted - version: "0.17.4" + version: "0.17.6" node_preamble: dependency: transitive description: @@ -1036,10 +1036,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: f2c65e21139ce2c3dad46922be8272bb5963516045659e71bb16e151c93b580e + sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" url: "https://pub.dev" source: hosted - version: "2.2.22" + version: "2.2.23" path_provider_foundation: dependency: transitive description: @@ -1192,6 +1192,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + record_use: + dependency: transitive + description: + name: record_use + sha256: "2551bd8eecfe95d14ae75f6021ad0248be5c27f138c2ec12fcb52b500b3ba1ed" + url: "https://pub.dev" + source: hosted + version: "0.6.0" riverpod: dependency: transitive description: @@ -1260,18 +1268,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "2939ae520c9024cb197fc20dee269cd8cdbf564c8b5746374ec6cacdc5169e64" + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf url: "https://pub.dev" source: hosted - version: "2.5.4" + version: "2.5.5" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: cbc40be9be1c5af4dab4d6e0de4d5d3729e6f3d65b89d21e1815d57705644a6f + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 url: "https://pub.dev" source: hosted - version: "2.4.20" + version: "2.4.23" shared_preferences_foundation: dependency: transitive description: @@ -1292,10 +1300,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" shared_preferences_web: dependency: transitive description: @@ -1569,10 +1577,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" url: "https://pub.dev" source: hosted - version: "6.3.28" + version: "6.3.29" url_launcher_ios: dependency: transitive description: @@ -1609,10 +1617,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.4.3" url_launcher_windows: dependency: transitive description: @@ -1633,10 +1641,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6 + sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" url: "https://pub.dev" source: hosted - version: "1.1.19" + version: "1.2.2" vector_graphics_codec: dependency: transitive description: @@ -1649,10 +1657,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "5a88dd14c0954a5398af544651c7fb51b457a2a556949bfb25369b210ef73a74" + sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.3" vector_math: dependency: transitive description: @@ -1665,10 +1673,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "45caa6c5917fa127b5dbcfbd1fa60b14e583afdc08bfc96dda38886ca252eb60" + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.2" + version: "15.2.0" watcher: dependency: transitive description: From 2d68495f86a6d0943e1abe42abfa133874500593 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 25 May 2026 11:26:39 +0200 Subject: [PATCH 16/44] add Location::posture_check_required field --- .../defguard/drift_schema_v4.json | 1 + client/lib/data/db/database.dart | 10 +- client/lib/data/db/database.g.dart | 90 +- client/lib/data/db/database.steps.dart | 120 +- client/lib/data/proxy/enrollment.dart | 5 +- client/lib/data/proxy/enrollment.g.dart | 7 + .../test/drift/defguard/generated/schema.dart | 5 +- .../drift/defguard/generated/schema_v4.dart | 1372 +++++++++++++++++ 8 files changed, 1603 insertions(+), 7 deletions(-) create mode 100644 client/drift_schemas/defguard/drift_schema_v4.json create mode 100644 client/test/drift/defguard/generated/schema_v4.dart diff --git a/client/drift_schemas/defguard/drift_schema_v4.json b/client/drift_schemas/defguard/drift_schema_v4.json new file mode 100644 index 0000000..22312c3 --- /dev/null +++ b/client/drift_schemas/defguard/drift_schema_v4.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"defguard_instances","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"uuid","getter_name":"uuid","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"url","getter_name":"url","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"device_id","getter_name":"deviceId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"proxy_url","getter_name":"proxyUrl","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pooling_token","getter_name":"poolingToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"client_traffic_policy","getter_name":"clientTrafficPolicy","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const CustomExpression('0')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const ClientTrafficPolicyConverter()","dart_type_name":"ClientTrafficPolicy"}},{"name":"enterprise_enabled","getter_name":"enterpriseEnabled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enterprise_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enterprise_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"private_key","getter_name":"privateKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_keys_stored","getter_name":"mfaKeysStored","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_keys_stored\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_keys_stored\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"openid_display_name","getter_name":"openidDisplayName","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[0],"type":"table","data":{"name":"locations","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"instance","getter_name":"instance","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES defguard_instances (id) ON DELETE CASCADE","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES defguard_instances (id) ON DELETE CASCADE"},"default_dart":null,"default_client_dart":null,"dsl_features":[{"foreign_key":{"to":{"table":"defguard_instances","column":"id"},"initially_deferred":false,"on_update":null,"on_delete":"cascade"}}]},{"name":"network_id","getter_name":"networkId","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"address","getter_name":"address","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"pub_key","getter_name":"pubKey","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"endpoint","getter_name":"endpoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"allowed_ips","getter_name":"allowedIps","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"dns","getter_name":"dns","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"mfa_enabled","getter_name":"mfaEnabled","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"mfa_enabled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"mfa_enabled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"traffic_method","getter_name":"trafficMethod","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(RoutingMethod.values)","dart_type_name":"RoutingMethod"}},{"name":"mfa_method","getter_name":"mfaMethod","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MfaMethodConverter()","dart_type_name":"MfaMethod"}},{"name":"keep_alive_interval","getter_name":"keepAliveInterval","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"location_mfa_mode","getter_name":"locationMfaMode","moor_type":"int","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocationMfaModeConverter()","dart_type_name":"LocationMfaMode"}},{"name":"posture_check_required","getter_name":"postureCheckRequired","moor_type":"bool","nullable":true,"customConstraints":null,"defaultConstraints":"CHECK (\"posture_check_required\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"posture_check_required\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}}]} \ No newline at end of file diff --git a/client/lib/data/db/database.dart b/client/lib/data/db/database.dart index 65d5e55..eb9c7aa 100644 --- a/client/lib/data/db/database.dart +++ b/client/lib/data/db/database.dart @@ -94,6 +94,8 @@ class Locations extends Table with AutoIncrementingPrimaryKey { @JsonKey('location_mfa_mode') IntColumn get locationMfaMode => integer().nullable().map(const LocationMfaModeConverter())(); + @JsonKey('posture_check_required') + BoolColumn get postureCheckRequired => boolean().nullable()(); } @DriftDatabase(tables: [DefguardInstances, Locations]) @@ -101,7 +103,7 @@ class AppDatabase extends _$AppDatabase { AppDatabase([QueryExecutor? executor]) : super(executor ?? _openConnection()); @override - int get schemaVersion => 3; + int get schemaVersion => 4; @override MigrationStrategy get migration { @@ -132,6 +134,12 @@ class AppDatabase extends _$AppDatabase { schema.defguardInstances.openidDisplayName, ); }, + from3To4: (m, schema) async { + await m.addColumn( + schema.locations, + schema.locations.postureCheckRequired, + ); + }, ), ); } diff --git a/client/lib/data/db/database.g.dart b/client/lib/data/db/database.g.dart index da5c8d3..1638e6f 100644 --- a/client/lib/data/db/database.g.dart +++ b/client/lib/data/db/database.g.dart @@ -992,6 +992,19 @@ class $LocationsTable extends Locations type: DriftSqlType.int, requiredDuringInsert: false, ).withConverter($LocationsTable.$converterlocationMfaModen); + static const VerificationMeta _postureCheckRequiredMeta = + const VerificationMeta('postureCheckRequired'); + @override + late final GeneratedColumn postureCheckRequired = GeneratedColumn( + 'posture_check_required', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("posture_check_required" IN (0, 1))', + ), + ); @override List get $columns => [ id, @@ -1008,6 +1021,7 @@ class $LocationsTable extends Locations mfaMethod, keepAliveInterval, locationMfaMode, + postureCheckRequired, ]; @override String get aliasedName => _alias ?? actualTableName; @@ -1103,6 +1117,15 @@ class $LocationsTable extends Locations } else if (isInserting) { context.missing(_keepAliveIntervalMeta); } + if (data.containsKey('posture_check_required')) { + context.handle( + _postureCheckRequiredMeta, + postureCheckRequired.isAcceptableOrUnknown( + data['posture_check_required']!, + _postureCheckRequiredMeta, + ), + ); + } return context; } @@ -1174,6 +1197,10 @@ class $LocationsTable extends Locations data['${effectivePrefix}location_mfa_mode'], ), ), + postureCheckRequired: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}posture_check_required'], + ), ); } @@ -1215,6 +1242,7 @@ class Location extends DataClass implements Insertable { final MfaMethod? mfaMethod; final int keepAliveInterval; final LocationMfaMode? locationMfaMode; + final bool? postureCheckRequired; const Location({ required this.id, required this.instance, @@ -1230,6 +1258,7 @@ class Location extends DataClass implements Insertable { this.mfaMethod, required this.keepAliveInterval, this.locationMfaMode, + this.postureCheckRequired, }); @override Map toColumns(bool nullToAbsent) { @@ -1264,6 +1293,9 @@ class Location extends DataClass implements Insertable { $LocationsTable.$converterlocationMfaModen.toSql(locationMfaMode), ); } + if (!nullToAbsent || postureCheckRequired != null) { + map['posture_check_required'] = Variable(postureCheckRequired); + } return map; } @@ -1291,6 +1323,9 @@ class Location extends DataClass implements Insertable { locationMfaMode: locationMfaMode == null && nullToAbsent ? const Value.absent() : Value(locationMfaMode), + postureCheckRequired: postureCheckRequired == null && nullToAbsent + ? const Value.absent() + : Value(postureCheckRequired), ); } @@ -1318,6 +1353,9 @@ class Location extends DataClass implements Insertable { locationMfaMode: serializer.fromJson( json['location_mfa_mode'], ), + postureCheckRequired: serializer.fromJson( + json['posture_check_required'], + ), ); } @override @@ -1340,6 +1378,7 @@ class Location extends DataClass implements Insertable { 'mfa_method': serializer.toJson(mfaMethod), 'keepalive_interval': serializer.toJson(keepAliveInterval), 'location_mfa_mode': serializer.toJson(locationMfaMode), + 'posture_check_required': serializer.toJson(postureCheckRequired), }; } @@ -1358,6 +1397,7 @@ class Location extends DataClass implements Insertable { Value mfaMethod = const Value.absent(), int? keepAliveInterval, Value locationMfaMode = const Value.absent(), + Value postureCheckRequired = const Value.absent(), }) => Location( id: id ?? this.id, instance: instance ?? this.instance, @@ -1377,6 +1417,9 @@ class Location extends DataClass implements Insertable { locationMfaMode: locationMfaMode.present ? locationMfaMode.value : this.locationMfaMode, + postureCheckRequired: postureCheckRequired.present + ? postureCheckRequired.value + : this.postureCheckRequired, ); Location copyWithCompanion(LocationsCompanion data) { return Location( @@ -1404,6 +1447,9 @@ class Location extends DataClass implements Insertable { locationMfaMode: data.locationMfaMode.present ? data.locationMfaMode.value : this.locationMfaMode, + postureCheckRequired: data.postureCheckRequired.present + ? data.postureCheckRequired.value + : this.postureCheckRequired, ); } @@ -1423,7 +1469,8 @@ class Location extends DataClass implements Insertable { ..write('trafficMethod: $trafficMethod, ') ..write('mfaMethod: $mfaMethod, ') ..write('keepAliveInterval: $keepAliveInterval, ') - ..write('locationMfaMode: $locationMfaMode') + ..write('locationMfaMode: $locationMfaMode, ') + ..write('postureCheckRequired: $postureCheckRequired') ..write(')')) .toString(); } @@ -1444,6 +1491,7 @@ class Location extends DataClass implements Insertable { mfaMethod, keepAliveInterval, locationMfaMode, + postureCheckRequired, ); @override bool operator ==(Object other) => @@ -1462,7 +1510,8 @@ class Location extends DataClass implements Insertable { other.trafficMethod == this.trafficMethod && other.mfaMethod == this.mfaMethod && other.keepAliveInterval == this.keepAliveInterval && - other.locationMfaMode == this.locationMfaMode); + other.locationMfaMode == this.locationMfaMode && + other.postureCheckRequired == this.postureCheckRequired); } class LocationsCompanion extends UpdateCompanion { @@ -1480,6 +1529,7 @@ class LocationsCompanion extends UpdateCompanion { final Value mfaMethod; final Value keepAliveInterval; final Value locationMfaMode; + final Value postureCheckRequired; const LocationsCompanion({ this.id = const Value.absent(), this.instance = const Value.absent(), @@ -1495,6 +1545,7 @@ class LocationsCompanion extends UpdateCompanion { this.mfaMethod = const Value.absent(), this.keepAliveInterval = const Value.absent(), this.locationMfaMode = const Value.absent(), + this.postureCheckRequired = const Value.absent(), }); LocationsCompanion.insert({ this.id = const Value.absent(), @@ -1511,6 +1562,7 @@ class LocationsCompanion extends UpdateCompanion { this.mfaMethod = const Value.absent(), required int keepAliveInterval, this.locationMfaMode = const Value.absent(), + this.postureCheckRequired = const Value.absent(), }) : instance = Value(instance), networkId = Value(networkId), name = Value(name), @@ -1534,6 +1586,7 @@ class LocationsCompanion extends UpdateCompanion { Expression? mfaMethod, Expression? keepAliveInterval, Expression? locationMfaMode, + Expression? postureCheckRequired, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -1550,6 +1603,8 @@ class LocationsCompanion extends UpdateCompanion { if (mfaMethod != null) 'mfa_method': mfaMethod, if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + if (postureCheckRequired != null) + 'posture_check_required': postureCheckRequired, }); } @@ -1568,6 +1623,7 @@ class LocationsCompanion extends UpdateCompanion { Value? mfaMethod, Value? keepAliveInterval, Value? locationMfaMode, + Value? postureCheckRequired, }) { return LocationsCompanion( id: id ?? this.id, @@ -1584,6 +1640,7 @@ class LocationsCompanion extends UpdateCompanion { mfaMethod: mfaMethod ?? this.mfaMethod, keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, locationMfaMode: locationMfaMode ?? this.locationMfaMode, + postureCheckRequired: postureCheckRequired ?? this.postureCheckRequired, ); } @@ -1638,6 +1695,11 @@ class LocationsCompanion extends UpdateCompanion { $LocationsTable.$converterlocationMfaModen.toSql(locationMfaMode.value), ); } + if (postureCheckRequired.present) { + map['posture_check_required'] = Variable( + postureCheckRequired.value, + ); + } return map; } @@ -1657,7 +1719,8 @@ class LocationsCompanion extends UpdateCompanion { ..write('trafficMethod: $trafficMethod, ') ..write('mfaMethod: $mfaMethod, ') ..write('keepAliveInterval: $keepAliveInterval, ') - ..write('locationMfaMode: $locationMfaMode') + ..write('locationMfaMode: $locationMfaMode, ') + ..write('postureCheckRequired: $postureCheckRequired') ..write(')')) .toString(); } @@ -2204,6 +2267,7 @@ typedef $$LocationsTableCreateCompanionBuilder = Value mfaMethod, required int keepAliveInterval, Value locationMfaMode, + Value postureCheckRequired, }); typedef $$LocationsTableUpdateCompanionBuilder = LocationsCompanion Function({ @@ -2221,6 +2285,7 @@ typedef $$LocationsTableUpdateCompanionBuilder = Value mfaMethod, Value keepAliveInterval, Value locationMfaMode, + Value postureCheckRequired, }); final class $$LocationsTableReferences @@ -2324,6 +2389,11 @@ class $$LocationsTableFilterComposer builder: (column) => ColumnWithTypeConverterFilters(column), ); + ColumnFilters get postureCheckRequired => $composableBuilder( + column: $table.postureCheckRequired, + builder: (column) => ColumnFilters(column), + ); + $$DefguardInstancesTableFilterComposer get instance { final $$DefguardInstancesTableFilterComposer composer = $composerBuilder( composer: this, @@ -2422,6 +2492,11 @@ class $$LocationsTableOrderingComposer builder: (column) => ColumnOrderings(column), ); + ColumnOrderings get postureCheckRequired => $composableBuilder( + column: $table.postureCheckRequired, + builder: (column) => ColumnOrderings(column), + ); + $$DefguardInstancesTableOrderingComposer get instance { final $$DefguardInstancesTableOrderingComposer composer = $composerBuilder( composer: this, @@ -2506,6 +2581,11 @@ class $$LocationsTableAnnotationComposer builder: (column) => column, ); + GeneratedColumn get postureCheckRequired => $composableBuilder( + column: $table.postureCheckRequired, + builder: (column) => column, + ); + $$DefguardInstancesTableAnnotationComposer get instance { final $$DefguardInstancesTableAnnotationComposer composer = $composerBuilder( @@ -2573,6 +2653,7 @@ class $$LocationsTableTableManager Value mfaMethod = const Value.absent(), Value keepAliveInterval = const Value.absent(), Value locationMfaMode = const Value.absent(), + Value postureCheckRequired = const Value.absent(), }) => LocationsCompanion( id: id, instance: instance, @@ -2588,6 +2669,7 @@ class $$LocationsTableTableManager mfaMethod: mfaMethod, keepAliveInterval: keepAliveInterval, locationMfaMode: locationMfaMode, + postureCheckRequired: postureCheckRequired, ), createCompanionCallback: ({ @@ -2605,6 +2687,7 @@ class $$LocationsTableTableManager Value mfaMethod = const Value.absent(), required int keepAliveInterval, Value locationMfaMode = const Value.absent(), + Value postureCheckRequired = const Value.absent(), }) => LocationsCompanion.insert( id: id, instance: instance, @@ -2620,6 +2703,7 @@ class $$LocationsTableTableManager mfaMethod: mfaMethod, keepAliveInterval: keepAliveInterval, locationMfaMode: locationMfaMode, + postureCheckRequired: postureCheckRequired, ), withReferenceMapper: (p0) => p0 .map( diff --git a/client/lib/data/db/database.steps.dart b/client/lib/data/db/database.steps.dart index 237aeba..8f97ee0 100644 --- a/client/lib/data/db/database.steps.dart +++ b/client/lib/data/db/database.steps.dart @@ -413,9 +413,117 @@ i1.GeneratedColumn _column_24(String aliasedName) => true, type: i1.DriftSqlType.string, ); + +final class Schema4 extends i0.VersionedSchema { + Schema4({required super.database}) : super(version: 4); + @override + late final List entities = [ + defguardInstances, + locations, + ]; + late final Shape2 defguardInstances = Shape2( + source: i0.VersionedTable( + entityName: 'defguard_instances', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + _column_4, + _column_5, + _column_6, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + _column_24, + ], + attachedDatabase: database, + ), + alias: null, + ); + late final Shape3 locations = Shape3( + source: i0.VersionedTable( + entityName: 'locations', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_13, + _column_14, + _column_1, + _column_15, + _column_10, + _column_16, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + _column_25, + ], + attachedDatabase: database, + ), + alias: null, + ); +} + +class Shape3 extends i0.VersionedTable { + Shape3({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get instance => + columnsByName['instance']! as i1.GeneratedColumn; + i1.GeneratedColumn get networkId => + columnsByName['network_id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get address => + columnsByName['address']! as i1.GeneratedColumn; + i1.GeneratedColumn get pubKey => + columnsByName['pub_key']! as i1.GeneratedColumn; + i1.GeneratedColumn get endpoint => + columnsByName['endpoint']! as i1.GeneratedColumn; + i1.GeneratedColumn get allowedIps => + columnsByName['allowed_ips']! as i1.GeneratedColumn; + i1.GeneratedColumn get dns => + columnsByName['dns']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaEnabled => + columnsByName['mfa_enabled']! as i1.GeneratedColumn; + i1.GeneratedColumn get trafficMethod => + columnsByName['traffic_method']! as i1.GeneratedColumn; + i1.GeneratedColumn get mfaMethod => + columnsByName['mfa_method']! as i1.GeneratedColumn; + i1.GeneratedColumn get keepAliveInterval => + columnsByName['keep_alive_interval']! as i1.GeneratedColumn; + i1.GeneratedColumn get locationMfaMode => + columnsByName['location_mfa_mode']! as i1.GeneratedColumn; + i1.GeneratedColumn get postureCheckRequired => + columnsByName['posture_check_required']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_25(String aliasedName) => + i1.GeneratedColumn( + 'posture_check_required', + aliasedName, + true, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("posture_check_required" IN (0, 1))', + ), + ); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -429,6 +537,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from2To3(migrator, schema); return 3; + case 3: + final schema = Schema4(database: database); + final migrator = i1.Migrator(database, schema); + await from3To4(migrator, schema); + return 4; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -438,6 +551,11 @@ i0.MigrationStepWithVersion migrationSteps({ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, + required Future Function(i1.Migrator m, Schema4 schema) from3To4, }) => i0.VersionedSchema.stepByStepHelper( - step: migrationSteps(from1To2: from1To2, from2To3: from2To3), + step: migrationSteps( + from1To2: from1To2, + from2To3: from2To3, + from3To4: from3To4, + ), ); diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index 87e5d08..adf0c12 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -152,6 +152,7 @@ class DeviceConfig { final bool mfaEnabled; final int keepaliveInterval; final LocationMfaMode? locationMfaMode; + final bool? postureCheckRequired; factory DeviceConfig.fromJson(Map json) => _$DeviceConfigFromJson(json); @@ -170,6 +171,7 @@ class DeviceConfig { required this.mfaEnabled, required this.keepaliveInterval, this.locationMfaMode, + this.postureCheckRequired, }); bool matchesLocation(Location other) { @@ -182,7 +184,8 @@ class DeviceConfig { dns == other.dns && mfaEnabled == other.mfaEnabled && keepaliveInterval == other.keepAliveInterval && - locationMfaMode == other.locationMfaMode; + locationMfaMode == other.locationMfaMode && + postureCheckRequired == other.postureCheckRequired; } LocationsCompanion toCompanion({ diff --git a/client/lib/data/proxy/enrollment.g.dart b/client/lib/data/proxy/enrollment.g.dart index 5f5cf4b..1eb3807 100644 --- a/client/lib/data/proxy/enrollment.g.dart +++ b/client/lib/data/proxy/enrollment.g.dart @@ -263,6 +263,10 @@ DeviceConfig _$DeviceConfigFromJson(Map json) => 'location_mfa_mode', (v) => $enumDecodeNullable(_$LocationMfaModeEnumMap, v), ), + postureCheckRequired: $checkedConvert( + 'posture_check_required', + (v) => v as bool?, + ), ); return val; }, @@ -274,6 +278,7 @@ DeviceConfig _$DeviceConfigFromJson(Map json) => 'mfaEnabled': 'mfa_enabled', 'keepaliveInterval': 'keepalive_interval', 'locationMfaMode': 'location_mfa_mode', + 'postureCheckRequired': 'posture_check_required', }, ); @@ -289,6 +294,7 @@ const _$DeviceConfigFieldMap = { 'mfaEnabled': 'mfa_enabled', 'keepaliveInterval': 'keepalive_interval', 'locationMfaMode': 'location_mfa_mode', + 'postureCheckRequired': 'posture_check_required', }; Map _$DeviceConfigToJson(DeviceConfig instance) => @@ -304,6 +310,7 @@ Map _$DeviceConfigToJson(DeviceConfig instance) => 'mfa_enabled': instance.mfaEnabled, 'keepalive_interval': instance.keepaliveInterval, 'location_mfa_mode': _$LocationMfaModeEnumMap[instance.locationMfaMode], + 'posture_check_required': instance.postureCheckRequired, }; const _$LocationMfaModeEnumMap = { diff --git a/client/test/drift/defguard/generated/schema.dart b/client/test/drift/defguard/generated/schema.dart index 209e70d..22131b1 100644 --- a/client/test/drift/defguard/generated/schema.dart +++ b/client/test/drift/defguard/generated/schema.dart @@ -6,6 +6,7 @@ import 'package:drift/internal/migrations.dart'; import 'schema_v1.dart' as v1; import 'schema_v2.dart' as v2; import 'schema_v3.dart' as v3; +import 'schema_v4.dart' as v4; class GeneratedHelper implements SchemaInstantiationHelper { @override @@ -17,10 +18,12 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v2.DatabaseAtV2(db); case 3: return v3.DatabaseAtV3(db); + case 4: + return v4.DatabaseAtV4(db); default: throw MissingSchemaException(version, versions); } } - static const versions = const [1, 2, 3]; + static const versions = const [1, 2, 3, 4]; } diff --git a/client/test/drift/defguard/generated/schema_v4.dart b/client/test/drift/defguard/generated/schema_v4.dart new file mode 100644 index 0000000..c6c2fcc --- /dev/null +++ b/client/test/drift/defguard/generated/schema_v4.dart @@ -0,0 +1,1372 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class DefguardInstances extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + DefguardInstances(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn uuid = GeneratedColumn( + 'uuid', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn url = GeneratedColumn( + 'url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn deviceId = GeneratedColumn( + 'device_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn proxyUrl = GeneratedColumn( + 'proxy_url', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn username = GeneratedColumn( + 'username', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn poolingToken = GeneratedColumn( + 'pooling_token', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn clientTrafficPolicy = GeneratedColumn( + 'client_traffic_policy', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const CustomExpression('0'), + ); + late final GeneratedColumn enterpriseEnabled = GeneratedColumn( + 'enterprise_enabled', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enterprise_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn privateKey = GeneratedColumn( + 'private_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn mfaKeysStored = GeneratedColumn( + 'mfa_keys_stored', + aliasedName, + false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_keys_stored" IN (0, 1))', + ), + ); + late final GeneratedColumn openidDisplayName = + GeneratedColumn( + 'openid_display_name', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + @override + List get $columns => [ + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + openidDisplayName, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'defguard_instances'; + @override + Set get $primaryKey => {id}; + @override + DefguardInstancesData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return DefguardInstancesData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + uuid: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}uuid'], + )!, + url: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}url'], + )!, + deviceId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}device_id'], + )!, + proxyUrl: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}proxy_url'], + )!, + username: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}username'], + )!, + poolingToken: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pooling_token'], + )!, + clientTrafficPolicy: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}client_traffic_policy'], + )!, + enterpriseEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}enterprise_enabled'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + privateKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}private_key'], + )!, + mfaKeysStored: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_keys_stored'], + )!, + openidDisplayName: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}openid_display_name'], + ), + ); + } + + @override + DefguardInstances createAlias(String alias) { + return DefguardInstances(attachedDatabase, alias); + } +} + +class DefguardInstancesData extends DataClass + implements Insertable { + final int id; + final String name; + final String uuid; + final String url; + final int deviceId; + final String proxyUrl; + final String username; + final String poolingToken; + final int clientTrafficPolicy; + final bool enterpriseEnabled; + final String pubKey; + final String privateKey; + final bool mfaKeysStored; + final String? openidDisplayName; + const DefguardInstancesData({ + required this.id, + required this.name, + required this.uuid, + required this.url, + required this.deviceId, + required this.proxyUrl, + required this.username, + required this.poolingToken, + required this.clientTrafficPolicy, + required this.enterpriseEnabled, + required this.pubKey, + required this.privateKey, + required this.mfaKeysStored, + this.openidDisplayName, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['uuid'] = Variable(uuid); + map['url'] = Variable(url); + map['device_id'] = Variable(deviceId); + map['proxy_url'] = Variable(proxyUrl); + map['username'] = Variable(username); + map['pooling_token'] = Variable(poolingToken); + map['client_traffic_policy'] = Variable(clientTrafficPolicy); + map['enterprise_enabled'] = Variable(enterpriseEnabled); + map['pub_key'] = Variable(pubKey); + map['private_key'] = Variable(privateKey); + map['mfa_keys_stored'] = Variable(mfaKeysStored); + if (!nullToAbsent || openidDisplayName != null) { + map['openid_display_name'] = Variable(openidDisplayName); + } + return map; + } + + DefguardInstancesCompanion toCompanion(bool nullToAbsent) { + return DefguardInstancesCompanion( + id: Value(id), + name: Value(name), + uuid: Value(uuid), + url: Value(url), + deviceId: Value(deviceId), + proxyUrl: Value(proxyUrl), + username: Value(username), + poolingToken: Value(poolingToken), + clientTrafficPolicy: Value(clientTrafficPolicy), + enterpriseEnabled: Value(enterpriseEnabled), + pubKey: Value(pubKey), + privateKey: Value(privateKey), + mfaKeysStored: Value(mfaKeysStored), + openidDisplayName: openidDisplayName == null && nullToAbsent + ? const Value.absent() + : Value(openidDisplayName), + ); + } + + factory DefguardInstancesData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return DefguardInstancesData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + uuid: serializer.fromJson(json['uuid']), + url: serializer.fromJson(json['url']), + deviceId: serializer.fromJson(json['deviceId']), + proxyUrl: serializer.fromJson(json['proxyUrl']), + username: serializer.fromJson(json['username']), + poolingToken: serializer.fromJson(json['poolingToken']), + clientTrafficPolicy: serializer.fromJson( + json['clientTrafficPolicy'], + ), + enterpriseEnabled: serializer.fromJson(json['enterpriseEnabled']), + pubKey: serializer.fromJson(json['pubKey']), + privateKey: serializer.fromJson(json['privateKey']), + mfaKeysStored: serializer.fromJson(json['mfaKeysStored']), + openidDisplayName: serializer.fromJson( + json['openidDisplayName'], + ), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'uuid': serializer.toJson(uuid), + 'url': serializer.toJson(url), + 'deviceId': serializer.toJson(deviceId), + 'proxyUrl': serializer.toJson(proxyUrl), + 'username': serializer.toJson(username), + 'poolingToken': serializer.toJson(poolingToken), + 'clientTrafficPolicy': serializer.toJson(clientTrafficPolicy), + 'enterpriseEnabled': serializer.toJson(enterpriseEnabled), + 'pubKey': serializer.toJson(pubKey), + 'privateKey': serializer.toJson(privateKey), + 'mfaKeysStored': serializer.toJson(mfaKeysStored), + 'openidDisplayName': serializer.toJson(openidDisplayName), + }; + } + + DefguardInstancesData copyWith({ + int? id, + String? name, + String? uuid, + String? url, + int? deviceId, + String? proxyUrl, + String? username, + String? poolingToken, + int? clientTrafficPolicy, + bool? enterpriseEnabled, + String? pubKey, + String? privateKey, + bool? mfaKeysStored, + Value openidDisplayName = const Value.absent(), + }) => DefguardInstancesData( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName.present + ? openidDisplayName.value + : this.openidDisplayName, + ); + DefguardInstancesData copyWithCompanion(DefguardInstancesCompanion data) { + return DefguardInstancesData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + uuid: data.uuid.present ? data.uuid.value : this.uuid, + url: data.url.present ? data.url.value : this.url, + deviceId: data.deviceId.present ? data.deviceId.value : this.deviceId, + proxyUrl: data.proxyUrl.present ? data.proxyUrl.value : this.proxyUrl, + username: data.username.present ? data.username.value : this.username, + poolingToken: data.poolingToken.present + ? data.poolingToken.value + : this.poolingToken, + clientTrafficPolicy: data.clientTrafficPolicy.present + ? data.clientTrafficPolicy.value + : this.clientTrafficPolicy, + enterpriseEnabled: data.enterpriseEnabled.present + ? data.enterpriseEnabled.value + : this.enterpriseEnabled, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + privateKey: data.privateKey.present + ? data.privateKey.value + : this.privateKey, + mfaKeysStored: data.mfaKeysStored.present + ? data.mfaKeysStored.value + : this.mfaKeysStored, + openidDisplayName: data.openidDisplayName.present + ? data.openidDisplayName.value + : this.openidDisplayName, + ); + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + name, + uuid, + url, + deviceId, + proxyUrl, + username, + poolingToken, + clientTrafficPolicy, + enterpriseEnabled, + pubKey, + privateKey, + mfaKeysStored, + openidDisplayName, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is DefguardInstancesData && + other.id == this.id && + other.name == this.name && + other.uuid == this.uuid && + other.url == this.url && + other.deviceId == this.deviceId && + other.proxyUrl == this.proxyUrl && + other.username == this.username && + other.poolingToken == this.poolingToken && + other.clientTrafficPolicy == this.clientTrafficPolicy && + other.enterpriseEnabled == this.enterpriseEnabled && + other.pubKey == this.pubKey && + other.privateKey == this.privateKey && + other.mfaKeysStored == this.mfaKeysStored && + other.openidDisplayName == this.openidDisplayName); +} + +class DefguardInstancesCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value uuid; + final Value url; + final Value deviceId; + final Value proxyUrl; + final Value username; + final Value poolingToken; + final Value clientTrafficPolicy; + final Value enterpriseEnabled; + final Value pubKey; + final Value privateKey; + final Value mfaKeysStored; + final Value openidDisplayName; + const DefguardInstancesCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.uuid = const Value.absent(), + this.url = const Value.absent(), + this.deviceId = const Value.absent(), + this.proxyUrl = const Value.absent(), + this.username = const Value.absent(), + this.poolingToken = const Value.absent(), + this.clientTrafficPolicy = const Value.absent(), + this.enterpriseEnabled = const Value.absent(), + this.pubKey = const Value.absent(), + this.privateKey = const Value.absent(), + this.mfaKeysStored = const Value.absent(), + this.openidDisplayName = const Value.absent(), + }); + DefguardInstancesCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String uuid, + required String url, + required int deviceId, + required String proxyUrl, + required String username, + required String poolingToken, + this.clientTrafficPolicy = const Value.absent(), + required bool enterpriseEnabled, + required String pubKey, + required String privateKey, + required bool mfaKeysStored, + this.openidDisplayName = const Value.absent(), + }) : name = Value(name), + uuid = Value(uuid), + url = Value(url), + deviceId = Value(deviceId), + proxyUrl = Value(proxyUrl), + username = Value(username), + poolingToken = Value(poolingToken), + enterpriseEnabled = Value(enterpriseEnabled), + pubKey = Value(pubKey), + privateKey = Value(privateKey), + mfaKeysStored = Value(mfaKeysStored); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? uuid, + Expression? url, + Expression? deviceId, + Expression? proxyUrl, + Expression? username, + Expression? poolingToken, + Expression? clientTrafficPolicy, + Expression? enterpriseEnabled, + Expression? pubKey, + Expression? privateKey, + Expression? mfaKeysStored, + Expression? openidDisplayName, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (uuid != null) 'uuid': uuid, + if (url != null) 'url': url, + if (deviceId != null) 'device_id': deviceId, + if (proxyUrl != null) 'proxy_url': proxyUrl, + if (username != null) 'username': username, + if (poolingToken != null) 'pooling_token': poolingToken, + if (clientTrafficPolicy != null) + 'client_traffic_policy': clientTrafficPolicy, + if (enterpriseEnabled != null) 'enterprise_enabled': enterpriseEnabled, + if (pubKey != null) 'pub_key': pubKey, + if (privateKey != null) 'private_key': privateKey, + if (mfaKeysStored != null) 'mfa_keys_stored': mfaKeysStored, + if (openidDisplayName != null) 'openid_display_name': openidDisplayName, + }); + } + + DefguardInstancesCompanion copyWith({ + Value? id, + Value? name, + Value? uuid, + Value? url, + Value? deviceId, + Value? proxyUrl, + Value? username, + Value? poolingToken, + Value? clientTrafficPolicy, + Value? enterpriseEnabled, + Value? pubKey, + Value? privateKey, + Value? mfaKeysStored, + Value? openidDisplayName, + }) { + return DefguardInstancesCompanion( + id: id ?? this.id, + name: name ?? this.name, + uuid: uuid ?? this.uuid, + url: url ?? this.url, + deviceId: deviceId ?? this.deviceId, + proxyUrl: proxyUrl ?? this.proxyUrl, + username: username ?? this.username, + poolingToken: poolingToken ?? this.poolingToken, + clientTrafficPolicy: clientTrafficPolicy ?? this.clientTrafficPolicy, + enterpriseEnabled: enterpriseEnabled ?? this.enterpriseEnabled, + pubKey: pubKey ?? this.pubKey, + privateKey: privateKey ?? this.privateKey, + mfaKeysStored: mfaKeysStored ?? this.mfaKeysStored, + openidDisplayName: openidDisplayName ?? this.openidDisplayName, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (uuid.present) { + map['uuid'] = Variable(uuid.value); + } + if (url.present) { + map['url'] = Variable(url.value); + } + if (deviceId.present) { + map['device_id'] = Variable(deviceId.value); + } + if (proxyUrl.present) { + map['proxy_url'] = Variable(proxyUrl.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (poolingToken.present) { + map['pooling_token'] = Variable(poolingToken.value); + } + if (clientTrafficPolicy.present) { + map['client_traffic_policy'] = Variable(clientTrafficPolicy.value); + } + if (enterpriseEnabled.present) { + map['enterprise_enabled'] = Variable(enterpriseEnabled.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (privateKey.present) { + map['private_key'] = Variable(privateKey.value); + } + if (mfaKeysStored.present) { + map['mfa_keys_stored'] = Variable(mfaKeysStored.value); + } + if (openidDisplayName.present) { + map['openid_display_name'] = Variable(openidDisplayName.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('DefguardInstancesCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('uuid: $uuid, ') + ..write('url: $url, ') + ..write('deviceId: $deviceId, ') + ..write('proxyUrl: $proxyUrl, ') + ..write('username: $username, ') + ..write('poolingToken: $poolingToken, ') + ..write('clientTrafficPolicy: $clientTrafficPolicy, ') + ..write('enterpriseEnabled: $enterpriseEnabled, ') + ..write('pubKey: $pubKey, ') + ..write('privateKey: $privateKey, ') + ..write('mfaKeysStored: $mfaKeysStored, ') + ..write('openidDisplayName: $openidDisplayName') + ..write(')')) + .toString(); + } +} + +class Locations extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + Locations(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', + aliasedName, + false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'PRIMARY KEY AUTOINCREMENT', + ), + ); + late final GeneratedColumn instance = GeneratedColumn( + 'instance', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES defguard_instances (id) ON DELETE CASCADE', + ), + ); + late final GeneratedColumn networkId = GeneratedColumn( + 'network_id', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn name = GeneratedColumn( + 'name', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn address = GeneratedColumn( + 'address', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn pubKey = GeneratedColumn( + 'pub_key', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn endpoint = GeneratedColumn( + 'endpoint', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn allowedIps = GeneratedColumn( + 'allowed_ips', + aliasedName, + false, + type: DriftSqlType.string, + requiredDuringInsert: true, + ); + late final GeneratedColumn dns = GeneratedColumn( + 'dns', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaEnabled = GeneratedColumn( + 'mfa_enabled', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("mfa_enabled" IN (0, 1))', + ), + ); + late final GeneratedColumn trafficMethod = GeneratedColumn( + 'traffic_method', + aliasedName, + true, + type: DriftSqlType.string, + requiredDuringInsert: false, + ); + late final GeneratedColumn mfaMethod = GeneratedColumn( + 'mfa_method', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn keepAliveInterval = GeneratedColumn( + 'keep_alive_interval', + aliasedName, + false, + type: DriftSqlType.int, + requiredDuringInsert: true, + ); + late final GeneratedColumn locationMfaMode = GeneratedColumn( + 'location_mfa_mode', + aliasedName, + true, + type: DriftSqlType.int, + requiredDuringInsert: false, + ); + late final GeneratedColumn postureCheckRequired = GeneratedColumn( + 'posture_check_required', + aliasedName, + true, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("posture_check_required" IN (0, 1))', + ), + ); + @override + List get $columns => [ + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + postureCheckRequired, + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'locations'; + @override + Set get $primaryKey => {id}; + @override + LocationsData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LocationsData( + id: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}id'], + )!, + instance: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}instance'], + )!, + networkId: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}network_id'], + )!, + name: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}name'], + )!, + address: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}address'], + )!, + pubKey: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}pub_key'], + )!, + endpoint: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}endpoint'], + )!, + allowedIps: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}allowed_ips'], + )!, + dns: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}dns'], + ), + mfaEnabled: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}mfa_enabled'], + ), + trafficMethod: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}traffic_method'], + ), + mfaMethod: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}mfa_method'], + ), + keepAliveInterval: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}keep_alive_interval'], + )!, + locationMfaMode: attachedDatabase.typeMapping.read( + DriftSqlType.int, + data['${effectivePrefix}location_mfa_mode'], + ), + postureCheckRequired: attachedDatabase.typeMapping.read( + DriftSqlType.bool, + data['${effectivePrefix}posture_check_required'], + ), + ); + } + + @override + Locations createAlias(String alias) { + return Locations(attachedDatabase, alias); + } +} + +class LocationsData extends DataClass implements Insertable { + final int id; + final int instance; + final int networkId; + final String name; + final String address; + final String pubKey; + final String endpoint; + final String allowedIps; + final String? dns; + final bool? mfaEnabled; + final String? trafficMethod; + final int? mfaMethod; + final int keepAliveInterval; + final int? locationMfaMode; + final bool? postureCheckRequired; + const LocationsData({ + required this.id, + required this.instance, + required this.networkId, + required this.name, + required this.address, + required this.pubKey, + required this.endpoint, + required this.allowedIps, + this.dns, + this.mfaEnabled, + this.trafficMethod, + this.mfaMethod, + required this.keepAliveInterval, + this.locationMfaMode, + this.postureCheckRequired, + }); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['instance'] = Variable(instance); + map['network_id'] = Variable(networkId); + map['name'] = Variable(name); + map['address'] = Variable(address); + map['pub_key'] = Variable(pubKey); + map['endpoint'] = Variable(endpoint); + map['allowed_ips'] = Variable(allowedIps); + if (!nullToAbsent || dns != null) { + map['dns'] = Variable(dns); + } + if (!nullToAbsent || mfaEnabled != null) { + map['mfa_enabled'] = Variable(mfaEnabled); + } + if (!nullToAbsent || trafficMethod != null) { + map['traffic_method'] = Variable(trafficMethod); + } + if (!nullToAbsent || mfaMethod != null) { + map['mfa_method'] = Variable(mfaMethod); + } + map['keep_alive_interval'] = Variable(keepAliveInterval); + if (!nullToAbsent || locationMfaMode != null) { + map['location_mfa_mode'] = Variable(locationMfaMode); + } + if (!nullToAbsent || postureCheckRequired != null) { + map['posture_check_required'] = Variable(postureCheckRequired); + } + return map; + } + + LocationsCompanion toCompanion(bool nullToAbsent) { + return LocationsCompanion( + id: Value(id), + instance: Value(instance), + networkId: Value(networkId), + name: Value(name), + address: Value(address), + pubKey: Value(pubKey), + endpoint: Value(endpoint), + allowedIps: Value(allowedIps), + dns: dns == null && nullToAbsent ? const Value.absent() : Value(dns), + mfaEnabled: mfaEnabled == null && nullToAbsent + ? const Value.absent() + : Value(mfaEnabled), + trafficMethod: trafficMethod == null && nullToAbsent + ? const Value.absent() + : Value(trafficMethod), + mfaMethod: mfaMethod == null && nullToAbsent + ? const Value.absent() + : Value(mfaMethod), + keepAliveInterval: Value(keepAliveInterval), + locationMfaMode: locationMfaMode == null && nullToAbsent + ? const Value.absent() + : Value(locationMfaMode), + postureCheckRequired: postureCheckRequired == null && nullToAbsent + ? const Value.absent() + : Value(postureCheckRequired), + ); + } + + factory LocationsData.fromJson( + Map json, { + ValueSerializer? serializer, + }) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LocationsData( + id: serializer.fromJson(json['id']), + instance: serializer.fromJson(json['instance']), + networkId: serializer.fromJson(json['networkId']), + name: serializer.fromJson(json['name']), + address: serializer.fromJson(json['address']), + pubKey: serializer.fromJson(json['pubKey']), + endpoint: serializer.fromJson(json['endpoint']), + allowedIps: serializer.fromJson(json['allowedIps']), + dns: serializer.fromJson(json['dns']), + mfaEnabled: serializer.fromJson(json['mfaEnabled']), + trafficMethod: serializer.fromJson(json['trafficMethod']), + mfaMethod: serializer.fromJson(json['mfaMethod']), + keepAliveInterval: serializer.fromJson(json['keepAliveInterval']), + locationMfaMode: serializer.fromJson(json['locationMfaMode']), + postureCheckRequired: serializer.fromJson( + json['postureCheckRequired'], + ), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'instance': serializer.toJson(instance), + 'networkId': serializer.toJson(networkId), + 'name': serializer.toJson(name), + 'address': serializer.toJson(address), + 'pubKey': serializer.toJson(pubKey), + 'endpoint': serializer.toJson(endpoint), + 'allowedIps': serializer.toJson(allowedIps), + 'dns': serializer.toJson(dns), + 'mfaEnabled': serializer.toJson(mfaEnabled), + 'trafficMethod': serializer.toJson(trafficMethod), + 'mfaMethod': serializer.toJson(mfaMethod), + 'keepAliveInterval': serializer.toJson(keepAliveInterval), + 'locationMfaMode': serializer.toJson(locationMfaMode), + 'postureCheckRequired': serializer.toJson(postureCheckRequired), + }; + } + + LocationsData copyWith({ + int? id, + int? instance, + int? networkId, + String? name, + String? address, + String? pubKey, + String? endpoint, + String? allowedIps, + Value dns = const Value.absent(), + Value mfaEnabled = const Value.absent(), + Value trafficMethod = const Value.absent(), + Value mfaMethod = const Value.absent(), + int? keepAliveInterval, + Value locationMfaMode = const Value.absent(), + Value postureCheckRequired = const Value.absent(), + }) => LocationsData( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns.present ? dns.value : this.dns, + mfaEnabled: mfaEnabled.present ? mfaEnabled.value : this.mfaEnabled, + trafficMethod: trafficMethod.present + ? trafficMethod.value + : this.trafficMethod, + mfaMethod: mfaMethod.present ? mfaMethod.value : this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode.present + ? locationMfaMode.value + : this.locationMfaMode, + postureCheckRequired: postureCheckRequired.present + ? postureCheckRequired.value + : this.postureCheckRequired, + ); + LocationsData copyWithCompanion(LocationsCompanion data) { + return LocationsData( + id: data.id.present ? data.id.value : this.id, + instance: data.instance.present ? data.instance.value : this.instance, + networkId: data.networkId.present ? data.networkId.value : this.networkId, + name: data.name.present ? data.name.value : this.name, + address: data.address.present ? data.address.value : this.address, + pubKey: data.pubKey.present ? data.pubKey.value : this.pubKey, + endpoint: data.endpoint.present ? data.endpoint.value : this.endpoint, + allowedIps: data.allowedIps.present + ? data.allowedIps.value + : this.allowedIps, + dns: data.dns.present ? data.dns.value : this.dns, + mfaEnabled: data.mfaEnabled.present + ? data.mfaEnabled.value + : this.mfaEnabled, + trafficMethod: data.trafficMethod.present + ? data.trafficMethod.value + : this.trafficMethod, + mfaMethod: data.mfaMethod.present ? data.mfaMethod.value : this.mfaMethod, + keepAliveInterval: data.keepAliveInterval.present + ? data.keepAliveInterval.value + : this.keepAliveInterval, + locationMfaMode: data.locationMfaMode.present + ? data.locationMfaMode.value + : this.locationMfaMode, + postureCheckRequired: data.postureCheckRequired.present + ? data.postureCheckRequired.value + : this.postureCheckRequired, + ); + } + + @override + String toString() { + return (StringBuffer('LocationsData(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode, ') + ..write('postureCheckRequired: $postureCheckRequired') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash( + id, + instance, + networkId, + name, + address, + pubKey, + endpoint, + allowedIps, + dns, + mfaEnabled, + trafficMethod, + mfaMethod, + keepAliveInterval, + locationMfaMode, + postureCheckRequired, + ); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LocationsData && + other.id == this.id && + other.instance == this.instance && + other.networkId == this.networkId && + other.name == this.name && + other.address == this.address && + other.pubKey == this.pubKey && + other.endpoint == this.endpoint && + other.allowedIps == this.allowedIps && + other.dns == this.dns && + other.mfaEnabled == this.mfaEnabled && + other.trafficMethod == this.trafficMethod && + other.mfaMethod == this.mfaMethod && + other.keepAliveInterval == this.keepAliveInterval && + other.locationMfaMode == this.locationMfaMode && + other.postureCheckRequired == this.postureCheckRequired); +} + +class LocationsCompanion extends UpdateCompanion { + final Value id; + final Value instance; + final Value networkId; + final Value name; + final Value address; + final Value pubKey; + final Value endpoint; + final Value allowedIps; + final Value dns; + final Value mfaEnabled; + final Value trafficMethod; + final Value mfaMethod; + final Value keepAliveInterval; + final Value locationMfaMode; + final Value postureCheckRequired; + const LocationsCompanion({ + this.id = const Value.absent(), + this.instance = const Value.absent(), + this.networkId = const Value.absent(), + this.name = const Value.absent(), + this.address = const Value.absent(), + this.pubKey = const Value.absent(), + this.endpoint = const Value.absent(), + this.allowedIps = const Value.absent(), + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + this.keepAliveInterval = const Value.absent(), + this.locationMfaMode = const Value.absent(), + this.postureCheckRequired = const Value.absent(), + }); + LocationsCompanion.insert({ + this.id = const Value.absent(), + required int instance, + required int networkId, + required String name, + required String address, + required String pubKey, + required String endpoint, + required String allowedIps, + this.dns = const Value.absent(), + this.mfaEnabled = const Value.absent(), + this.trafficMethod = const Value.absent(), + this.mfaMethod = const Value.absent(), + required int keepAliveInterval, + this.locationMfaMode = const Value.absent(), + this.postureCheckRequired = const Value.absent(), + }) : instance = Value(instance), + networkId = Value(networkId), + name = Value(name), + address = Value(address), + pubKey = Value(pubKey), + endpoint = Value(endpoint), + allowedIps = Value(allowedIps), + keepAliveInterval = Value(keepAliveInterval); + static Insertable custom({ + Expression? id, + Expression? instance, + Expression? networkId, + Expression? name, + Expression? address, + Expression? pubKey, + Expression? endpoint, + Expression? allowedIps, + Expression? dns, + Expression? mfaEnabled, + Expression? trafficMethod, + Expression? mfaMethod, + Expression? keepAliveInterval, + Expression? locationMfaMode, + Expression? postureCheckRequired, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (instance != null) 'instance': instance, + if (networkId != null) 'network_id': networkId, + if (name != null) 'name': name, + if (address != null) 'address': address, + if (pubKey != null) 'pub_key': pubKey, + if (endpoint != null) 'endpoint': endpoint, + if (allowedIps != null) 'allowed_ips': allowedIps, + if (dns != null) 'dns': dns, + if (mfaEnabled != null) 'mfa_enabled': mfaEnabled, + if (trafficMethod != null) 'traffic_method': trafficMethod, + if (mfaMethod != null) 'mfa_method': mfaMethod, + if (keepAliveInterval != null) 'keep_alive_interval': keepAliveInterval, + if (locationMfaMode != null) 'location_mfa_mode': locationMfaMode, + if (postureCheckRequired != null) + 'posture_check_required': postureCheckRequired, + }); + } + + LocationsCompanion copyWith({ + Value? id, + Value? instance, + Value? networkId, + Value? name, + Value? address, + Value? pubKey, + Value? endpoint, + Value? allowedIps, + Value? dns, + Value? mfaEnabled, + Value? trafficMethod, + Value? mfaMethod, + Value? keepAliveInterval, + Value? locationMfaMode, + Value? postureCheckRequired, + }) { + return LocationsCompanion( + id: id ?? this.id, + instance: instance ?? this.instance, + networkId: networkId ?? this.networkId, + name: name ?? this.name, + address: address ?? this.address, + pubKey: pubKey ?? this.pubKey, + endpoint: endpoint ?? this.endpoint, + allowedIps: allowedIps ?? this.allowedIps, + dns: dns ?? this.dns, + mfaEnabled: mfaEnabled ?? this.mfaEnabled, + trafficMethod: trafficMethod ?? this.trafficMethod, + mfaMethod: mfaMethod ?? this.mfaMethod, + keepAliveInterval: keepAliveInterval ?? this.keepAliveInterval, + locationMfaMode: locationMfaMode ?? this.locationMfaMode, + postureCheckRequired: postureCheckRequired ?? this.postureCheckRequired, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (instance.present) { + map['instance'] = Variable(instance.value); + } + if (networkId.present) { + map['network_id'] = Variable(networkId.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (address.present) { + map['address'] = Variable(address.value); + } + if (pubKey.present) { + map['pub_key'] = Variable(pubKey.value); + } + if (endpoint.present) { + map['endpoint'] = Variable(endpoint.value); + } + if (allowedIps.present) { + map['allowed_ips'] = Variable(allowedIps.value); + } + if (dns.present) { + map['dns'] = Variable(dns.value); + } + if (mfaEnabled.present) { + map['mfa_enabled'] = Variable(mfaEnabled.value); + } + if (trafficMethod.present) { + map['traffic_method'] = Variable(trafficMethod.value); + } + if (mfaMethod.present) { + map['mfa_method'] = Variable(mfaMethod.value); + } + if (keepAliveInterval.present) { + map['keep_alive_interval'] = Variable(keepAliveInterval.value); + } + if (locationMfaMode.present) { + map['location_mfa_mode'] = Variable(locationMfaMode.value); + } + if (postureCheckRequired.present) { + map['posture_check_required'] = Variable( + postureCheckRequired.value, + ); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LocationsCompanion(') + ..write('id: $id, ') + ..write('instance: $instance, ') + ..write('networkId: $networkId, ') + ..write('name: $name, ') + ..write('address: $address, ') + ..write('pubKey: $pubKey, ') + ..write('endpoint: $endpoint, ') + ..write('allowedIps: $allowedIps, ') + ..write('dns: $dns, ') + ..write('mfaEnabled: $mfaEnabled, ') + ..write('trafficMethod: $trafficMethod, ') + ..write('mfaMethod: $mfaMethod, ') + ..write('keepAliveInterval: $keepAliveInterval, ') + ..write('locationMfaMode: $locationMfaMode, ') + ..write('postureCheckRequired: $postureCheckRequired') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV4 extends GeneratedDatabase { + DatabaseAtV4(QueryExecutor e) : super(e); + late final DefguardInstances defguardInstances = DefguardInstances(this); + late final Locations locations = Locations(this); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + defguardInstances, + locations, + ]; + @override + int get schemaVersion => 4; +} From c1ed5b826759271cb85d04f3363d85a9e40ec0c6 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 25 May 2026 11:46:30 +0200 Subject: [PATCH 17/44] update deps, fix ndk version mismatch --- client/android/app/build.gradle.kts | 2 +- client/pubspec.lock | 32 +++++++++++------------------ flake.nix | 2 +- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/client/android/app/build.gradle.kts b/client/android/app/build.gradle.kts index 3082919..4c434a4 100644 --- a/client/android/app/build.gradle.kts +++ b/client/android/app/build.gradle.kts @@ -8,7 +8,7 @@ plugins { android { namespace = "net.defguard.mobile" compileSdk = 36 - ndkVersion = "27.0.12077973" + ndkVersion = flutter.ndkVersion compileOptions { isCoreLibraryDesugaringEnabled = true diff --git a/client/pubspec.lock b/client/pubspec.lock index 11d313c..4f57d02 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: code_assets - sha256: "83ccdaa064c980b5596c35dd64a8d3ecc68620174ab9b90b6343b753aa721687" + sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.1.0" code_builder: dependency: transitive description: @@ -716,10 +716,10 @@ packages: dependency: transitive description: name: hooks - sha256: "025f060e86d2d4c3c47b56e33caf7f93bf9283340f26d23424ebcfccf34f621e" + sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31 url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "2.0.0" hooks_riverpod: dependency: "direct main" description: @@ -960,14 +960,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - native_toolchain_c: - dependency: transitive - description: - name: native_toolchain_c - sha256: "6ba77bb18063eebe9de401f5e6437e95e1438af0a87a3a39084fbd37c90df572" - url: "https://pub.dev" - source: hosted - version: "0.17.6" node_preamble: dependency: transitive description: @@ -980,10 +972,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "100a1c87616ab6ed41ec263b083c0ef3261ee6cd1dc3b0f35f8ddfa4f996fe52" + sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" url: "https://pub.dev" source: hosted - version: "9.3.0" + version: "9.4.1" package_config: dependency: transitive description: @@ -1044,10 +1036,10 @@ packages: dependency: transitive description: name: path_provider_foundation - sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" + sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.5.1" path_provider_linux: dependency: transitive description: @@ -1577,10 +1569,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" + sha256: "17bc677f0b301615530dd1d67e0a9828cafa2d0b6b6eae4cd3679b7eac4a273c" url: "https://pub.dev" source: hosted - version: "6.3.29" + version: "6.3.30" url_launcher_ios: dependency: transitive description: @@ -1773,5 +1765,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.3 <4.0.0" - flutter: ">=3.38.4" + dart: ">=3.10.0 <4.0.0" + flutter: ">=3.38.0" diff --git a/flake.nix b/flake.nix index 002f76a..c2ca9d3 100644 --- a/flake.nix +++ b/flake.nix @@ -17,7 +17,7 @@ allowUnfree = true; }; }; - ndkVersion = "27.0.12077973"; + ndkVersion = "28.2.13676358"; androidComposition = pkgs.androidenv.composeAndroidPackages { # buildToolsVersions = [ buildToolsVersion "28.0.3" ]; # platformVersions = [ "34" "28" ]; From 423c18406cc566c8d8f380aeeb08ddd8f5647597 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 25 May 2026 12:42:56 +0200 Subject: [PATCH 18/44] allow plain http communication --- client/android/app/src/main/res/xml/network_security_config.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/android/app/src/main/res/xml/network_security_config.xml b/client/android/app/src/main/res/xml/network_security_config.xml index d20fb83..8a76775 100644 --- a/client/android/app/src/main/res/xml/network_security_config.xml +++ b/client/android/app/src/main/res/xml/network_security_config.xml @@ -1,6 +1,6 @@ - + From 822eb15df12e8e92a8f70ee7567316bb30214426 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 09:24:53 +0200 Subject: [PATCH 19/44] connect using dummy posture data --- client/lib/data/plugin/plugin.dart | 8 +- client/lib/data/plugin/plugin.g.dart | 7 + client/lib/data/proxy/enrollment.dart | 1 + client/lib/data/proxy/mfa.dart | 122 +++++++++++++ client/lib/data/proxy/mfa.g.dart | 168 ++++++++++++++++-- .../instance/services/tunnel_service.dart | 5 + 6 files changed, 297 insertions(+), 14 deletions(-) diff --git a/client/lib/data/plugin/plugin.dart b/client/lib/data/plugin/plugin.dart index 5bbc54c..4b7b7a9 100644 --- a/client/lib/data/plugin/plugin.dart +++ b/client/lib/data/plugin/plugin.dart @@ -4,8 +4,6 @@ import '../db/enums.dart'; part 'plugin.g.dart'; - - @JsonSerializable() class PluginConnectPayload { // config @@ -25,6 +23,7 @@ class PluginConnectPayload { final int instanceId; final int networkId; RoutingMethod traffic; + final bool postureCheckRequired; PluginConnectPayload({ required this.publicKey, @@ -41,6 +40,7 @@ class PluginConnectPayload { required this.networkId, this.dns, this.presharedKey, + required this.postureCheckRequired, }); factory PluginConnectPayload.fromJson(Map json) => @@ -49,7 +49,6 @@ class PluginConnectPayload { Map toJson() => _$PluginConnectPayloadToJson(this); } - @JsonSerializable() class PluginTunnelEventData { final int instanceId; @@ -62,7 +61,8 @@ class PluginTunnelEventData { required this.traffic, }); - factory PluginTunnelEventData.fromJson(Map json) => _$PluginTunnelEventDataFromJson(json); + factory PluginTunnelEventData.fromJson(Map json) => + _$PluginTunnelEventDataFromJson(json); Map toJson() => _$PluginTunnelEventDataToJson(this); } diff --git a/client/lib/data/plugin/plugin.g.dart b/client/lib/data/plugin/plugin.g.dart index 1b85e6a..6fb711c 100644 --- a/client/lib/data/plugin/plugin.g.dart +++ b/client/lib/data/plugin/plugin.g.dart @@ -30,6 +30,10 @@ PluginConnectPayload _$PluginConnectPayloadFromJson( networkId: $checkedConvert('network_id', (v) => (v as num).toInt()), dns: $checkedConvert('dns', (v) => v as String?), presharedKey: $checkedConvert('preshared_key', (v) => v as String?), + postureCheckRequired: $checkedConvert( + 'posture_check_required', + (v) => v as bool, + ), ); return val; }, @@ -43,6 +47,7 @@ PluginConnectPayload _$PluginConnectPayloadFromJson( 'instanceId': 'instance_id', 'networkId': 'network_id', 'presharedKey': 'preshared_key', + 'postureCheckRequired': 'posture_check_required', }, ); @@ -61,6 +66,7 @@ const _$PluginConnectPayloadFieldMap = { 'instanceId': 'instance_id', 'networkId': 'network_id', 'traffic': 'traffic', + 'postureCheckRequired': 'posture_check_required', }; Map _$PluginConnectPayloadToJson( @@ -80,6 +86,7 @@ Map _$PluginConnectPayloadToJson( 'instance_id': instance.instanceId, 'network_id': instance.networkId, 'traffic': _$RoutingMethodEnumMap[instance.traffic]!, + 'posture_check_required': instance.postureCheckRequired, }; const _$RoutingMethodEnumMap = { diff --git a/client/lib/data/proxy/enrollment.dart b/client/lib/data/proxy/enrollment.dart index adf0c12..ffa0990 100644 --- a/client/lib/data/proxy/enrollment.dart +++ b/client/lib/data/proxy/enrollment.dart @@ -209,6 +209,7 @@ class DeviceConfig { allowedIps: d.Value(allowedIps), address: d.Value(assignedIp), locationMfaMode: d.Value(locationMfaMode), + postureCheckRequired: d.Value(postureCheckRequired), ); } } diff --git a/client/lib/data/proxy/mfa.dart b/client/lib/data/proxy/mfa.dart index 55949ab..f9c2e6c 100644 --- a/client/lib/data/proxy/mfa.dart +++ b/client/lib/data/proxy/mfa.dart @@ -3,16 +3,138 @@ import 'package:mobile/data/db/enums.dart'; part 'mfa.g.dart'; +enum UnavailableReason { + unspecified(0), + insufficientPermissions(1), + notApplicable(2), + detectionFailed(3); + + final int value; + + const UnavailableReason(this.value); +} + +@JsonSerializable() +class StringCheck { + final Map result; + + const StringCheck({required this.result}); + + factory StringCheck.value(String value) => StringCheck( + result: {'Value': value}, + ); + + factory StringCheck.unavailable(UnavailableReason reason) => StringCheck( + result: {'Unavailable': reason.value}, + ); + + factory StringCheck.fromJson(Map json) => + _$StringCheckFromJson(json); + + Map toJson() => _$StringCheckToJson(this); +} + +@JsonSerializable() +class BoolCheck { + final Map result; + + const BoolCheck({required this.result}); + + factory BoolCheck.value(bool value) => BoolCheck( + result: {'Value': value}, + ); + + factory BoolCheck.unavailable(UnavailableReason reason) => BoolCheck( + result: {'Unavailable': reason.value}, + ); + + factory BoolCheck.fromJson(Map json) => + _$BoolCheckFromJson(json); + + Map toJson() => _$BoolCheckToJson(this); +} + +@JsonSerializable() +class Int32Check { + final Map result; + + const Int32Check({required this.result}); + + factory Int32Check.value(int value) => Int32Check( + result: {'Value': value}, + ); + + factory Int32Check.unavailable(UnavailableReason reason) => Int32Check( + result: {'Unavailable': reason.value}, + ); + + factory Int32Check.fromJson(Map json) => + _$Int32CheckFromJson(json); + + Map toJson() => _$Int32CheckToJson(this); +} + +@JsonSerializable() +class DevicePostureData { + final String defguardClientVersion; + final String osType; + final StringCheck? osName; + final StringCheck? osVersion; + final BoolCheck? diskEncryption; + final BoolCheck? antivirusPresent; + final BoolCheck? windowsAdDomainJoined; + final Int32Check? windowsSecurityUpdateAgeDays; + final StringCheck? linuxKernelVersion; + final BoolCheck? deviceIntegrity; + + const DevicePostureData({ + required this.defguardClientVersion, + required this.osType, + this.osName, + this.osVersion, + this.diskEncryption, + this.antivirusPresent, + this.windowsAdDomainJoined, + this.windowsSecurityUpdateAgeDays, + this.linuxKernelVersion, + this.deviceIntegrity, + }); + + factory DevicePostureData.fromJson(Map json) => + _$DevicePostureDataFromJson(json); + + Map toJson() => _$DevicePostureDataToJson(this); +} + +DevicePostureData getPosture() { + final notApplicable = UnavailableReason.notApplicable; + + return DevicePostureData( + defguardClientVersion: '2.1.0', + osType: 'Android', + osName: StringCheck.value('Android'), + osVersion: StringCheck.value('16'), + diskEncryption: BoolCheck.unavailable(notApplicable), + antivirusPresent: BoolCheck.unavailable(notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable(notApplicable), + windowsSecurityUpdateAgeDays: Int32Check.unavailable(notApplicable), + linuxKernelVersion: StringCheck.unavailable(notApplicable), + deviceIntegrity: BoolCheck.value(true), + ); +} + @JsonSerializable() class StartMfaRequest { final String pubkey; final int locationId; final MfaMethod method; + final DevicePostureData? postureData; const StartMfaRequest({ required this.pubkey, required this.locationId, required this.method, + this.postureData, }); factory StartMfaRequest.fromJson(Map json) => diff --git a/client/lib/data/proxy/mfa.g.dart b/client/lib/data/proxy/mfa.g.dart index b439a83..526ed52 100644 --- a/client/lib/data/proxy/mfa.g.dart +++ b/client/lib/data/proxy/mfa.g.dart @@ -6,23 +6,170 @@ part of 'mfa.dart'; // JsonSerializableGenerator // ************************************************************************** -StartMfaRequest _$StartMfaRequestFromJson(Map json) => - $checkedCreate('StartMfaRequest', json, ($checkedConvert) { - final val = StartMfaRequest( - pubkey: $checkedConvert('pubkey', (v) => v as String), - locationId: $checkedConvert('location_id', (v) => (v as num).toInt()), - method: $checkedConvert( - 'method', - (v) => $enumDecode(_$MfaMethodEnumMap, v), - ), +StringCheck _$StringCheckFromJson(Map json) => + $checkedCreate('StringCheck', json, ($checkedConvert) { + final val = StringCheck( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$StringCheckFieldMap = {'result': 'result'}; + +Map _$StringCheckToJson(StringCheck instance) => + {'result': instance.result}; + +BoolCheck _$BoolCheckFromJson(Map json) => + $checkedCreate('BoolCheck', json, ($checkedConvert) { + final val = BoolCheck( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$BoolCheckFieldMap = {'result': 'result'}; + +Map _$BoolCheckToJson(BoolCheck instance) => { + 'result': instance.result, +}; + +Int32Check _$Int32CheckFromJson(Map json) => + $checkedCreate('Int32Check', json, ($checkedConvert) { + final val = Int32Check( + result: $checkedConvert('result', (v) => v as Map), ); return val; - }, fieldKeyMap: const {'locationId': 'location_id'}); + }); + +const _$Int32CheckFieldMap = {'result': 'result'}; + +Map _$Int32CheckToJson(Int32Check instance) => + {'result': instance.result}; + +DevicePostureData _$DevicePostureDataFromJson( + Map json, +) => $checkedCreate( + 'DevicePostureData', + json, + ($checkedConvert) { + final val = DevicePostureData( + defguardClientVersion: $checkedConvert( + 'defguard_client_version', + (v) => v as String, + ), + osType: $checkedConvert('os_type', (v) => v as String), + osName: $checkedConvert( + 'os_name', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + osVersion: $checkedConvert( + 'os_version', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + diskEncryption: $checkedConvert( + 'disk_encryption', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + antivirusPresent: $checkedConvert( + 'antivirus_present', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + windowsAdDomainJoined: $checkedConvert( + 'windows_ad_domain_joined', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + windowsSecurityUpdateAgeDays: $checkedConvert( + 'windows_security_update_age_days', + (v) => + v == null ? null : Int32Check.fromJson(v as Map), + ), + linuxKernelVersion: $checkedConvert( + 'linux_kernel_version', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + deviceIntegrity: $checkedConvert( + 'device_integrity', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const { + 'defguardClientVersion': 'defguard_client_version', + 'osType': 'os_type', + 'osName': 'os_name', + 'osVersion': 'os_version', + 'diskEncryption': 'disk_encryption', + 'antivirusPresent': 'antivirus_present', + 'windowsAdDomainJoined': 'windows_ad_domain_joined', + 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', + 'linuxKernelVersion': 'linux_kernel_version', + 'deviceIntegrity': 'device_integrity', + }, +); + +const _$DevicePostureDataFieldMap = { + 'defguardClientVersion': 'defguard_client_version', + 'osType': 'os_type', + 'osName': 'os_name', + 'osVersion': 'os_version', + 'diskEncryption': 'disk_encryption', + 'antivirusPresent': 'antivirus_present', + 'windowsAdDomainJoined': 'windows_ad_domain_joined', + 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', + 'linuxKernelVersion': 'linux_kernel_version', + 'deviceIntegrity': 'device_integrity', +}; + +Map _$DevicePostureDataToJson(DevicePostureData instance) => + { + 'defguard_client_version': instance.defguardClientVersion, + 'os_type': instance.osType, + 'os_name': instance.osName, + 'os_version': instance.osVersion, + 'disk_encryption': instance.diskEncryption, + 'antivirus_present': instance.antivirusPresent, + 'windows_ad_domain_joined': instance.windowsAdDomainJoined, + 'windows_security_update_age_days': instance.windowsSecurityUpdateAgeDays, + 'linux_kernel_version': instance.linuxKernelVersion, + 'device_integrity': instance.deviceIntegrity, + }; + +StartMfaRequest _$StartMfaRequestFromJson(Map json) => + $checkedCreate( + 'StartMfaRequest', + json, + ($checkedConvert) { + final val = StartMfaRequest( + pubkey: $checkedConvert('pubkey', (v) => v as String), + locationId: $checkedConvert('location_id', (v) => (v as num).toInt()), + method: $checkedConvert( + 'method', + (v) => $enumDecode(_$MfaMethodEnumMap, v), + ), + postureData: $checkedConvert( + 'posture_data', + (v) => v == null + ? null + : DevicePostureData.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const { + 'locationId': 'location_id', + 'postureData': 'posture_data', + }, + ); const _$StartMfaRequestFieldMap = { 'pubkey': 'pubkey', 'locationId': 'location_id', 'method': 'method', + 'postureData': 'posture_data', }; Map _$StartMfaRequestToJson(StartMfaRequest instance) => @@ -30,6 +177,7 @@ Map _$StartMfaRequestToJson(StartMfaRequest instance) => 'pubkey': instance.pubkey, 'location_id': instance.locationId, 'method': _$MfaMethodEnumMap[instance.method]!, + 'posture_data': instance.postureData, }; const _$MfaMethodEnumMap = { diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index b44cc4a..97e1137 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -152,6 +152,7 @@ class TunnelService { payload.devicePublicKey, payload.networkId, method, + payload.postureCheckRequired, ); if (method == MfaMethod.openid) { // perform openid-based MFA @@ -285,14 +286,17 @@ class TunnelService { String pubkey, int networkId, MfaMethod method, + bool postureCheckRequired, ) async { talker.debug( "Starting MFA for networkId: $networkId, method: ${method.toReadableString()}", ); + final postureData = postureCheckRequired ? getPosture() : null; final request = StartMfaRequest( pubkey: pubkey, locationId: networkId, method: method, + postureData: postureData, ); final uri = Uri.parse(url); @@ -319,6 +323,7 @@ class TunnelService { networkId: location.networkId, instanceId: instance.id, traffic: trafficMethod, + postureCheckRequired: location.postureCheckRequired == true, ); } From 6ab54b38318474398a86d51f3a084fed8c95be17 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 09:35:50 +0200 Subject: [PATCH 20/44] move posture structs to enterprise module --- client/lib/data/proxy/mfa.dart | 121 +-------------- client/lib/data/proxy/mfa.g.dart | 132 ----------------- client/lib/enterprise/postures.dart | 115 +++++++++++++++ client/lib/enterprise/postures.g.dart | 139 ++++++++++++++++++ .../instance/services/tunnel_service.dart | 1 + 5 files changed, 256 insertions(+), 252 deletions(-) create mode 100644 client/lib/enterprise/postures.dart create mode 100644 client/lib/enterprise/postures.g.dart diff --git a/client/lib/data/proxy/mfa.dart b/client/lib/data/proxy/mfa.dart index f9c2e6c..a760191 100644 --- a/client/lib/data/proxy/mfa.dart +++ b/client/lib/data/proxy/mfa.dart @@ -1,128 +1,9 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:mobile/data/db/enums.dart'; +import 'package:mobile/enterprise/postures.dart'; part 'mfa.g.dart'; -enum UnavailableReason { - unspecified(0), - insufficientPermissions(1), - notApplicable(2), - detectionFailed(3); - - final int value; - - const UnavailableReason(this.value); -} - -@JsonSerializable() -class StringCheck { - final Map result; - - const StringCheck({required this.result}); - - factory StringCheck.value(String value) => StringCheck( - result: {'Value': value}, - ); - - factory StringCheck.unavailable(UnavailableReason reason) => StringCheck( - result: {'Unavailable': reason.value}, - ); - - factory StringCheck.fromJson(Map json) => - _$StringCheckFromJson(json); - - Map toJson() => _$StringCheckToJson(this); -} - -@JsonSerializable() -class BoolCheck { - final Map result; - - const BoolCheck({required this.result}); - - factory BoolCheck.value(bool value) => BoolCheck( - result: {'Value': value}, - ); - - factory BoolCheck.unavailable(UnavailableReason reason) => BoolCheck( - result: {'Unavailable': reason.value}, - ); - - factory BoolCheck.fromJson(Map json) => - _$BoolCheckFromJson(json); - - Map toJson() => _$BoolCheckToJson(this); -} - -@JsonSerializable() -class Int32Check { - final Map result; - - const Int32Check({required this.result}); - - factory Int32Check.value(int value) => Int32Check( - result: {'Value': value}, - ); - - factory Int32Check.unavailable(UnavailableReason reason) => Int32Check( - result: {'Unavailable': reason.value}, - ); - - factory Int32Check.fromJson(Map json) => - _$Int32CheckFromJson(json); - - Map toJson() => _$Int32CheckToJson(this); -} - -@JsonSerializable() -class DevicePostureData { - final String defguardClientVersion; - final String osType; - final StringCheck? osName; - final StringCheck? osVersion; - final BoolCheck? diskEncryption; - final BoolCheck? antivirusPresent; - final BoolCheck? windowsAdDomainJoined; - final Int32Check? windowsSecurityUpdateAgeDays; - final StringCheck? linuxKernelVersion; - final BoolCheck? deviceIntegrity; - - const DevicePostureData({ - required this.defguardClientVersion, - required this.osType, - this.osName, - this.osVersion, - this.diskEncryption, - this.antivirusPresent, - this.windowsAdDomainJoined, - this.windowsSecurityUpdateAgeDays, - this.linuxKernelVersion, - this.deviceIntegrity, - }); - - factory DevicePostureData.fromJson(Map json) => - _$DevicePostureDataFromJson(json); - - Map toJson() => _$DevicePostureDataToJson(this); -} - -DevicePostureData getPosture() { - final notApplicable = UnavailableReason.notApplicable; - - return DevicePostureData( - defguardClientVersion: '2.1.0', - osType: 'Android', - osName: StringCheck.value('Android'), - osVersion: StringCheck.value('16'), - diskEncryption: BoolCheck.unavailable(notApplicable), - antivirusPresent: BoolCheck.unavailable(notApplicable), - windowsAdDomainJoined: BoolCheck.unavailable(notApplicable), - windowsSecurityUpdateAgeDays: Int32Check.unavailable(notApplicable), - linuxKernelVersion: StringCheck.unavailable(notApplicable), - deviceIntegrity: BoolCheck.value(true), - ); -} - @JsonSerializable() class StartMfaRequest { final String pubkey; diff --git a/client/lib/data/proxy/mfa.g.dart b/client/lib/data/proxy/mfa.g.dart index 526ed52..3b09b30 100644 --- a/client/lib/data/proxy/mfa.g.dart +++ b/client/lib/data/proxy/mfa.g.dart @@ -6,138 +6,6 @@ part of 'mfa.dart'; // JsonSerializableGenerator // ************************************************************************** -StringCheck _$StringCheckFromJson(Map json) => - $checkedCreate('StringCheck', json, ($checkedConvert) { - final val = StringCheck( - result: $checkedConvert('result', (v) => v as Map), - ); - return val; - }); - -const _$StringCheckFieldMap = {'result': 'result'}; - -Map _$StringCheckToJson(StringCheck instance) => - {'result': instance.result}; - -BoolCheck _$BoolCheckFromJson(Map json) => - $checkedCreate('BoolCheck', json, ($checkedConvert) { - final val = BoolCheck( - result: $checkedConvert('result', (v) => v as Map), - ); - return val; - }); - -const _$BoolCheckFieldMap = {'result': 'result'}; - -Map _$BoolCheckToJson(BoolCheck instance) => { - 'result': instance.result, -}; - -Int32Check _$Int32CheckFromJson(Map json) => - $checkedCreate('Int32Check', json, ($checkedConvert) { - final val = Int32Check( - result: $checkedConvert('result', (v) => v as Map), - ); - return val; - }); - -const _$Int32CheckFieldMap = {'result': 'result'}; - -Map _$Int32CheckToJson(Int32Check instance) => - {'result': instance.result}; - -DevicePostureData _$DevicePostureDataFromJson( - Map json, -) => $checkedCreate( - 'DevicePostureData', - json, - ($checkedConvert) { - final val = DevicePostureData( - defguardClientVersion: $checkedConvert( - 'defguard_client_version', - (v) => v as String, - ), - osType: $checkedConvert('os_type', (v) => v as String), - osName: $checkedConvert( - 'os_name', - (v) => - v == null ? null : StringCheck.fromJson(v as Map), - ), - osVersion: $checkedConvert( - 'os_version', - (v) => - v == null ? null : StringCheck.fromJson(v as Map), - ), - diskEncryption: $checkedConvert( - 'disk_encryption', - (v) => v == null ? null : BoolCheck.fromJson(v as Map), - ), - antivirusPresent: $checkedConvert( - 'antivirus_present', - (v) => v == null ? null : BoolCheck.fromJson(v as Map), - ), - windowsAdDomainJoined: $checkedConvert( - 'windows_ad_domain_joined', - (v) => v == null ? null : BoolCheck.fromJson(v as Map), - ), - windowsSecurityUpdateAgeDays: $checkedConvert( - 'windows_security_update_age_days', - (v) => - v == null ? null : Int32Check.fromJson(v as Map), - ), - linuxKernelVersion: $checkedConvert( - 'linux_kernel_version', - (v) => - v == null ? null : StringCheck.fromJson(v as Map), - ), - deviceIntegrity: $checkedConvert( - 'device_integrity', - (v) => v == null ? null : BoolCheck.fromJson(v as Map), - ), - ); - return val; - }, - fieldKeyMap: const { - 'defguardClientVersion': 'defguard_client_version', - 'osType': 'os_type', - 'osName': 'os_name', - 'osVersion': 'os_version', - 'diskEncryption': 'disk_encryption', - 'antivirusPresent': 'antivirus_present', - 'windowsAdDomainJoined': 'windows_ad_domain_joined', - 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', - 'linuxKernelVersion': 'linux_kernel_version', - 'deviceIntegrity': 'device_integrity', - }, -); - -const _$DevicePostureDataFieldMap = { - 'defguardClientVersion': 'defguard_client_version', - 'osType': 'os_type', - 'osName': 'os_name', - 'osVersion': 'os_version', - 'diskEncryption': 'disk_encryption', - 'antivirusPresent': 'antivirus_present', - 'windowsAdDomainJoined': 'windows_ad_domain_joined', - 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', - 'linuxKernelVersion': 'linux_kernel_version', - 'deviceIntegrity': 'device_integrity', -}; - -Map _$DevicePostureDataToJson(DevicePostureData instance) => - { - 'defguard_client_version': instance.defguardClientVersion, - 'os_type': instance.osType, - 'os_name': instance.osName, - 'os_version': instance.osVersion, - 'disk_encryption': instance.diskEncryption, - 'antivirus_present': instance.antivirusPresent, - 'windows_ad_domain_joined': instance.windowsAdDomainJoined, - 'windows_security_update_age_days': instance.windowsSecurityUpdateAgeDays, - 'linux_kernel_version': instance.linuxKernelVersion, - 'device_integrity': instance.deviceIntegrity, - }; - StartMfaRequest _$StartMfaRequestFromJson(Map json) => $checkedCreate( 'StartMfaRequest', diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart new file mode 100644 index 0000000..18eab89 --- /dev/null +++ b/client/lib/enterprise/postures.dart @@ -0,0 +1,115 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'postures.g.dart'; + +enum UnavailableReason { + unspecified(0), + insufficientPermissions(1), + notApplicable(2), + detectionFailed(3); + + final int value; + + const UnavailableReason(this.value); +} + +@JsonSerializable() +class StringCheck { + final Map result; + + const StringCheck({required this.result}); + + factory StringCheck.value(String value) => + StringCheck(result: {'Value': value}); + + factory StringCheck.unavailable(UnavailableReason reason) => + StringCheck(result: {'Unavailable': reason.value}); + + factory StringCheck.fromJson(Map json) => + _$StringCheckFromJson(json); + + Map toJson() => _$StringCheckToJson(this); +} + +@JsonSerializable() +class BoolCheck { + final Map result; + + const BoolCheck({required this.result}); + + factory BoolCheck.value(bool value) => BoolCheck(result: {'Value': value}); + + factory BoolCheck.unavailable(UnavailableReason reason) => + BoolCheck(result: {'Unavailable': reason.value}); + + factory BoolCheck.fromJson(Map json) => + _$BoolCheckFromJson(json); + + Map toJson() => _$BoolCheckToJson(this); +} + +@JsonSerializable() +class Int32Check { + final Map result; + + const Int32Check({required this.result}); + + factory Int32Check.value(int value) => Int32Check(result: {'Value': value}); + + factory Int32Check.unavailable(UnavailableReason reason) => + Int32Check(result: {'Unavailable': reason.value}); + + factory Int32Check.fromJson(Map json) => + _$Int32CheckFromJson(json); + + Map toJson() => _$Int32CheckToJson(this); +} + +@JsonSerializable() +class DevicePostureData { + final String defguardClientVersion; + final String osType; + final StringCheck? osName; + final StringCheck? osVersion; + final BoolCheck? diskEncryption; + final BoolCheck? antivirusPresent; + final BoolCheck? windowsAdDomainJoined; + final Int32Check? windowsSecurityUpdateAgeDays; + final StringCheck? linuxKernelVersion; + final BoolCheck? deviceIntegrity; + + const DevicePostureData({ + required this.defguardClientVersion, + required this.osType, + this.osName, + this.osVersion, + this.diskEncryption, + this.antivirusPresent, + this.windowsAdDomainJoined, + this.windowsSecurityUpdateAgeDays, + this.linuxKernelVersion, + this.deviceIntegrity, + }); + + factory DevicePostureData.fromJson(Map json) => + _$DevicePostureDataFromJson(json); + + Map toJson() => _$DevicePostureDataToJson(this); +} + +DevicePostureData getPosture() { + final notApplicable = UnavailableReason.notApplicable; + + return DevicePostureData( + defguardClientVersion: '2.1.0', + osType: 'Android', + osName: StringCheck.value('Android'), + osVersion: StringCheck.value('16'), + diskEncryption: BoolCheck.unavailable(notApplicable), + antivirusPresent: BoolCheck.unavailable(notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable(notApplicable), + windowsSecurityUpdateAgeDays: Int32Check.unavailable(notApplicable), + linuxKernelVersion: StringCheck.unavailable(notApplicable), + deviceIntegrity: BoolCheck.value(true), + ); +} diff --git a/client/lib/enterprise/postures.g.dart b/client/lib/enterprise/postures.g.dart new file mode 100644 index 0000000..83faf88 --- /dev/null +++ b/client/lib/enterprise/postures.g.dart @@ -0,0 +1,139 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'postures.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +StringCheck _$StringCheckFromJson(Map json) => + $checkedCreate('StringCheck', json, ($checkedConvert) { + final val = StringCheck( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$StringCheckFieldMap = {'result': 'result'}; + +Map _$StringCheckToJson(StringCheck instance) => + {'result': instance.result}; + +BoolCheck _$BoolCheckFromJson(Map json) => + $checkedCreate('BoolCheck', json, ($checkedConvert) { + final val = BoolCheck( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$BoolCheckFieldMap = {'result': 'result'}; + +Map _$BoolCheckToJson(BoolCheck instance) => { + 'result': instance.result, +}; + +Int32Check _$Int32CheckFromJson(Map json) => + $checkedCreate('Int32Check', json, ($checkedConvert) { + final val = Int32Check( + result: $checkedConvert('result', (v) => v as Map), + ); + return val; + }); + +const _$Int32CheckFieldMap = {'result': 'result'}; + +Map _$Int32CheckToJson(Int32Check instance) => + {'result': instance.result}; + +DevicePostureData _$DevicePostureDataFromJson( + Map json, +) => $checkedCreate( + 'DevicePostureData', + json, + ($checkedConvert) { + final val = DevicePostureData( + defguardClientVersion: $checkedConvert( + 'defguard_client_version', + (v) => v as String, + ), + osType: $checkedConvert('os_type', (v) => v as String), + osName: $checkedConvert( + 'os_name', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + osVersion: $checkedConvert( + 'os_version', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + diskEncryption: $checkedConvert( + 'disk_encryption', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + antivirusPresent: $checkedConvert( + 'antivirus_present', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + windowsAdDomainJoined: $checkedConvert( + 'windows_ad_domain_joined', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + windowsSecurityUpdateAgeDays: $checkedConvert( + 'windows_security_update_age_days', + (v) => + v == null ? null : Int32Check.fromJson(v as Map), + ), + linuxKernelVersion: $checkedConvert( + 'linux_kernel_version', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), + deviceIntegrity: $checkedConvert( + 'device_integrity', + (v) => v == null ? null : BoolCheck.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const { + 'defguardClientVersion': 'defguard_client_version', + 'osType': 'os_type', + 'osName': 'os_name', + 'osVersion': 'os_version', + 'diskEncryption': 'disk_encryption', + 'antivirusPresent': 'antivirus_present', + 'windowsAdDomainJoined': 'windows_ad_domain_joined', + 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', + 'linuxKernelVersion': 'linux_kernel_version', + 'deviceIntegrity': 'device_integrity', + }, +); + +const _$DevicePostureDataFieldMap = { + 'defguardClientVersion': 'defguard_client_version', + 'osType': 'os_type', + 'osName': 'os_name', + 'osVersion': 'os_version', + 'diskEncryption': 'disk_encryption', + 'antivirusPresent': 'antivirus_present', + 'windowsAdDomainJoined': 'windows_ad_domain_joined', + 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', + 'linuxKernelVersion': 'linux_kernel_version', + 'deviceIntegrity': 'device_integrity', +}; + +Map _$DevicePostureDataToJson(DevicePostureData instance) => + { + 'defguard_client_version': instance.defguardClientVersion, + 'os_type': instance.osType, + 'os_name': instance.osName, + 'os_version': instance.osVersion, + 'disk_encryption': instance.diskEncryption, + 'antivirus_present': instance.antivirusPresent, + 'windows_ad_domain_joined': instance.windowsAdDomainJoined, + 'windows_security_update_age_days': instance.windowsSecurityUpdateAgeDays, + 'linux_kernel_version': instance.linuxKernelVersion, + 'device_integrity': instance.deviceIntegrity, + }; diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index 97e1137..e7a3893 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:mobile/data/db/database.dart'; import 'package:mobile/data/proxy/mfa.dart'; +import 'package:mobile/enterprise/postures.dart'; import 'package:mobile/enterprise/screens/mfa/openid_mfa_screen.dart'; import 'package:mobile/open/api.dart'; import 'package:mobile/data/plugin/plugin.dart'; From dcebbf663a313d237ac4b8fb70133df6d9aa91e2 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 10:07:39 +0200 Subject: [PATCH 21/44] gather real posture report --- client/lib/enterprise/postures.dart | 87 ++++++++++++++++--- .../instance/services/tunnel_service.dart | 2 +- 2 files changed, 75 insertions(+), 14 deletions(-) diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index 18eab89..4fa63ea 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -1,4 +1,8 @@ +import 'dart:io'; + +import 'package:device_info_plus/device_info_plus.dart'; import 'package:json_annotation/json_annotation.dart'; +import 'package:package_info_plus/package_info_plus.dart'; part 'postures.g.dart'; @@ -97,19 +101,76 @@ class DevicePostureData { Map toJson() => _$DevicePostureDataToJson(this); } -DevicePostureData getPosture() { - final notApplicable = UnavailableReason.notApplicable; - +Future getPosture() async { + final packageInfo = await PackageInfo.fromPlatform(); + final deviceInfo = DeviceInfoPlugin(); + + // Handle Android + if (Platform.isAndroid) { + final android = await deviceInfo.androidInfo; + return DevicePostureData( + defguardClientVersion: packageInfo.version, + osType: "Android", + osName: StringCheck.value(android.version.release), + osVersion: StringCheck.value(android.version.release), + // TODO + deviceIntegrity: BoolCheck.value(true), + + diskEncryption: BoolCheck.unavailable(UnavailableReason.notApplicable), + antivirusPresent: BoolCheck.unavailable(UnavailableReason.notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable( + UnavailableReason.notApplicable, + ), + windowsSecurityUpdateAgeDays: Int32Check.unavailable( + UnavailableReason.notApplicable, + ), + linuxKernelVersion: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), + ); + } + + // Handle iOS + if (Platform.isIOS) { + final ios = await deviceInfo.iosInfo; + return DevicePostureData( + defguardClientVersion: packageInfo.version, + osType: "iOS", + osName: StringCheck.value(ios.systemName), + osVersion: StringCheck.value(ios.systemVersion), + deviceIntegrity: BoolCheck.unavailable(UnavailableReason.notApplicable), + + diskEncryption: BoolCheck.unavailable(UnavailableReason.notApplicable), + antivirusPresent: BoolCheck.unavailable(UnavailableReason.notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable( + UnavailableReason.notApplicable, + ), + windowsSecurityUpdateAgeDays: Int32Check.unavailable( + UnavailableReason.notApplicable, + ), + linuxKernelVersion: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), + ); + } + + // Fallback for unsupported platforms: report the generic Dart OS values return DevicePostureData( - defguardClientVersion: '2.1.0', - osType: 'Android', - osName: StringCheck.value('Android'), - osVersion: StringCheck.value('16'), - diskEncryption: BoolCheck.unavailable(notApplicable), - antivirusPresent: BoolCheck.unavailable(notApplicable), - windowsAdDomainJoined: BoolCheck.unavailable(notApplicable), - windowsSecurityUpdateAgeDays: Int32Check.unavailable(notApplicable), - linuxKernelVersion: StringCheck.unavailable(notApplicable), - deviceIntegrity: BoolCheck.value(true), + defguardClientVersion: packageInfo.version, + osType: Platform.operatingSystem, + osName: StringCheck.value(Platform.operatingSystem), + osVersion: StringCheck.unavailable(UnavailableReason.unspecified), + diskEncryption: BoolCheck.unavailable(UnavailableReason.notApplicable), + antivirusPresent: BoolCheck.unavailable(UnavailableReason.notApplicable), + windowsAdDomainJoined: BoolCheck.unavailable( + UnavailableReason.notApplicable, + ), + windowsSecurityUpdateAgeDays: Int32Check.unavailable( + UnavailableReason.notApplicable, + ), + linuxKernelVersion: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), + deviceIntegrity: BoolCheck.unavailable(UnavailableReason.notApplicable), ); } diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index e7a3893..d39dc84 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -292,7 +292,7 @@ class TunnelService { talker.debug( "Starting MFA for networkId: $networkId, method: ${method.toReadableString()}", ); - final postureData = postureCheckRequired ? getPosture() : null; + final postureData = postureCheckRequired ? await getPosture() : null; final request = StartMfaRequest( pubkey: pubkey, locationId: networkId, From f06a287301804aba85f09cc3d5ee9b5d58d42d48 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 11:57:23 +0200 Subject: [PATCH 22/44] comment --- client/lib/enterprise/postures.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index 4fa63ea..36a4070 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -113,8 +113,9 @@ Future getPosture() async { osType: "Android", osName: StringCheck.value(android.version.release), osVersion: StringCheck.value(android.version.release), - // TODO - deviceIntegrity: BoolCheck.value(true), + // TODO: implement full google play integrity check flow + // TODO: https://github.com/DefGuard/defguard/issues/2986 + deviceIntegrity: BoolCheck.unavailable(UnavailableReason.unspecified), diskEncryption: BoolCheck.unavailable(UnavailableReason.notApplicable), antivirusPresent: BoolCheck.unavailable(UnavailableReason.notApplicable), From c7a8ff7b8343a3311ef5ba42860a37911239a99c Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Tue, 26 May 2026 14:32:23 +0200 Subject: [PATCH 23/44] posture-check-only connection flow --- client/lib/enterprise/postures.dart | 30 ++++++++++ client/lib/enterprise/postures.g.dart | 58 ++++++++++++++++++ client/lib/open/api.dart | 43 ++++++++++++++ .../instance/services/tunnel_service.dart | 59 +++++++++++++++++++ 4 files changed, 190 insertions(+) diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index 36a4070..d227f3a 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -101,6 +101,36 @@ class DevicePostureData { Map toJson() => _$DevicePostureDataToJson(this); } +@JsonSerializable() +class PostureConnectRequest { + final int locationId; + final String pubkey; + final DevicePostureData devicePostureData; + + const PostureConnectRequest({ + required this.locationId, + required this.pubkey, + required this.devicePostureData, + }); + + factory PostureConnectRequest.fromJson(Map json) => + _$PostureConnectRequestFromJson(json); + + Map toJson() => _$PostureConnectRequestToJson(this); +} + +@JsonSerializable() +class PostureConnectResponse { + final String presharedKey; + + const PostureConnectResponse({required this.presharedKey}); + + factory PostureConnectResponse.fromJson(Map json) => + _$PostureConnectResponseFromJson(json); + + Map toJson() => _$PostureConnectResponseToJson(this); +} + Future getPosture() async { final packageInfo = await PackageInfo.fromPlatform(); final deviceInfo = DeviceInfoPlugin(); diff --git a/client/lib/enterprise/postures.g.dart b/client/lib/enterprise/postures.g.dart index 83faf88..f25bdc6 100644 --- a/client/lib/enterprise/postures.g.dart +++ b/client/lib/enterprise/postures.g.dart @@ -137,3 +137,61 @@ Map _$DevicePostureDataToJson(DevicePostureData instance) => 'linux_kernel_version': instance.linuxKernelVersion, 'device_integrity': instance.deviceIntegrity, }; + +PostureConnectRequest _$PostureConnectRequestFromJson( + Map json, +) => $checkedCreate( + 'PostureConnectRequest', + json, + ($checkedConvert) { + final val = PostureConnectRequest( + locationId: $checkedConvert('location_id', (v) => (v as num).toInt()), + pubkey: $checkedConvert('pubkey', (v) => v as String), + devicePostureData: $checkedConvert( + 'device_posture_data', + (v) => DevicePostureData.fromJson(v as Map), + ), + ); + return val; + }, + fieldKeyMap: const { + 'locationId': 'location_id', + 'devicePostureData': 'device_posture_data', + }, +); + +const _$PostureConnectRequestFieldMap = { + 'locationId': 'location_id', + 'pubkey': 'pubkey', + 'devicePostureData': 'device_posture_data', +}; + +Map _$PostureConnectRequestToJson( + PostureConnectRequest instance, +) => { + 'location_id': instance.locationId, + 'pubkey': instance.pubkey, + 'device_posture_data': instance.devicePostureData, +}; + +PostureConnectResponse _$PostureConnectResponseFromJson( + Map json, +) => $checkedCreate( + 'PostureConnectResponse', + json, + ($checkedConvert) { + final val = PostureConnectResponse( + presharedKey: $checkedConvert('preshared_key', (v) => v as String), + ); + return val; + }, + fieldKeyMap: const {'presharedKey': 'preshared_key'}, +); + +const _$PostureConnectResponseFieldMap = { + 'presharedKey': 'preshared_key', +}; + +Map _$PostureConnectResponseToJson( + PostureConnectResponse instance, +) => {'preshared_key': instance.presharedKey}; diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index 1416845..cd03318 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -9,6 +9,7 @@ import 'package:native_dio_adapter/native_dio_adapter.dart'; import 'package:mobile/data/db/enums.dart'; import 'package:mobile/data/proto/client_platform_info.pb.dart'; import 'package:mobile/data/proxy/config.dart'; +import 'package:mobile/enterprise/postures.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:mobile/data/proxy/enrollment.dart'; import 'package:mobile/data/proxy/mfa.dart'; @@ -20,6 +21,16 @@ import '../logging.dart'; const _apiV1Segments = ['api', 'v1']; final enrollmentPathSegments = ['api', 'v1', 'enrollment']; final mfaPathSegments = ['api', 'v1', 'client-mfa']; +final posturePathSegments = ['api', 'v1', 'posture']; + +class PostureCheckException implements Exception { + final String message; + + const PostureCheckException(this.message); + + @override + String toString() => 'Posture error: $message'; +} class MfaMethodNotAvailableException implements Exception { final MfaMethod method; @@ -213,6 +224,38 @@ class _ProxyApi { } } + Future postureConnect( + Uri url, + PostureConnectRequest data, + ) async { + final endpoint = url.replace( + pathSegments: [...url.pathSegments, ...posturePathSegments, 'connect'], + ); + + try { + final response = await _dio.postUri(endpoint, data: data.toJson()); + return PostureConnectResponse.fromJson(response.data); + } on DioException catch (e) { + final responseData = e.response?.data; + final dataError = responseData is Map + ? responseData['error'] + : null; + if (e.response?.statusCode == 403 && dataError is String) { + throw PostureCheckException(dataError); + } + if (e.response != null) { + throw HttpException( + 'Failed to perform posture check. Status: ${e.response?.statusCode} Body: ${e.response?.data}', + ); + } + rethrow; + } catch (e) { + throw FormatException( + 'Invalid JSON sent by posture check endpoint! Error: $e', + ); + } + } + Future finishMfa(Uri url, FinishMfaRequest data) async { final endpoint = url.replace( pathSegments: [...url.pathSegments, ...mfaPathSegments, 'finish'], diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index d39dc84..664eef1 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -120,6 +120,16 @@ class TunnelService { return; } payload.presharedKey = presharedKey; + } else if (payload.postureCheckRequired) { + final presharedKey = await _performPostureCheck( + navigator: navigator, + proxyUrl: instance.proxyUrl, + payload: payload, + ); + if (presharedKey == null) { + return; + } + payload.presharedKey = presharedKey; } // start the tunnel @@ -134,6 +144,38 @@ class TunnelService { location.locationMfaMode == LocationMfaMode.external; } + /// Performs posture-only authorization and returns runtime preshared key. + static Future _performPostureCheck({ + required NavigatorState navigator, + required String proxyUrl, + required PluginConnectPayload payload, + }) async { + final messenger = ScaffoldMessenger.of(navigator.context); + try { + return await _authorizePostureOnly( + proxyUrl, + payload.devicePublicKey, + payload.networkId, + ); + } on PostureCheckException catch (e) { + talker.error('Posture check failed', e); + messenger.showSnackBar( + dgSnackBar(text: e.toString(), textColor: DgColor.textAlert), + ); + } on HttpException catch (e) { + talker.error('Posture check request failed', e); + messenger.showSnackBar( + dgSnackBar(text: 'Error: ${e.message}', textColor: DgColor.textAlert), + ); + } catch (e) { + talker.error('Posture-only connect failed: $e'); + messenger.showSnackBar( + dgSnackBar(text: 'Error: $e', textColor: DgColor.textAlert), + ); + } + return null; + } + /// Performs MFA using specified method. /// Returns preshared key. static Future _performMfa({ @@ -304,6 +346,23 @@ class TunnelService { return await proxyApi.startMfa(uri, request); } + /// Calls `/posture/connect` endpoint and returns runtime preshared key. + static Future _authorizePostureOnly( + String url, + String pubkey, + int networkId, + ) async { + talker.debug('Starting posture check for networkId: $networkId'); + final request = PostureConnectRequest( + locationId: networkId, + pubkey: pubkey, + devicePostureData: await getPosture(), + ); + + final response = await proxyApi.postureConnect(Uri.parse(url), request); + return response.presharedKey; + } + /// Prepares wireguard plugin configuration static PluginConnectPayload _makePayload( DefguardInstance instance, From 5f4916ec5e1e4da975bd3deba9bfeb65b107db27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 19 Jun 2026 15:16:15 +0200 Subject: [PATCH 24/44] WhatsNew for TestFlight --- .github/workflows/build.yaml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index cddbe40..9b26104 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -54,8 +54,11 @@ jobs: - name: Build iOS run: flutter build ipa --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} + - name: Get last commit message + run: echo "COMMIT_MSG=$(git log -1 --pretty=%B)" >> $GITHUB_ENV + - name: Upload app to TestFlight - uses: apple-actions/upload-testflight-build@v3 + uses: apple-actions/upload-testflight-build@v5 # Mobile applications are published to the App Store manually, with release tags applied # post-publication. To avoid redundant uploads, this step executes only for non-tagged # builds, ensuring tagged releases are distributed exclusively to GitHub. @@ -65,6 +68,7 @@ jobs: issuer-id: ${{ secrets.API_ISSUER_ID }} api-key-id: ${{ secrets.ASC_API_KEY_ID }} api-private-key: ${{ secrets.PRIVATE_KEY_CONTENTS }} + release-notes: ${{ env.COMMIT_MSG }} build-android: runs-on: [self-hosted, macOS] From 19613cdd30ea5be409cbba68afa357305bb2ec94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Fri, 19 Jun 2026 15:25:42 +0200 Subject: [PATCH 25/44] Bump version --- client/ios/Runner.xcodeproj/project.pbxproj | 6 +++--- client/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 97f5ddc..3309e0e 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -644,7 +644,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.3; + MARKETING_VERSION = 1.6.4; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -698,7 +698,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.3; + MARKETING_VERSION = 1.6.4; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -749,7 +749,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.3; + MARKETING_VERSION = 1.6.4; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; diff --git a/client/pubspec.yaml b/client/pubspec.yaml index 5438494..ac44d0c 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.3+1 +version: 1.6.4+1 environment: sdk: ^3.8.1 From 64ea8cd2b2540b70f0e44e8a9cdb969ce1eabd26 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= <102536422+filipslezaklab@users.noreply.github.com> Date: Thu, 25 Jun 2026 14:46:26 +0200 Subject: [PATCH 26/44] add security level patch posture check data (#192) * Update postures.dart * fix posture error display, ran dart format --- .fvmrc | 3 ++ client/lib/data/db/enums.dart | 3 +- client/lib/data/proxy/config.dart | 6 ++-- client/lib/enterprise/config_update.dart | 27 +++++++----------- client/lib/enterprise/postures.dart | 11 ++++++++ client/lib/enterprise/postures.g.dart | 8 ++++++ client/lib/logging.dart | 2 +- client/lib/open/api.dart | 25 +++++++++++------ .../lib/open/riverpod/biometrics_state.dart | 1 - .../riverpod/package_info/package_info.dart | 2 +- client/lib/open/riverpod/plugin/plugin.dart | 2 +- .../add_instance/generate_wireguard.dart | 6 +--- .../screens/instance/instance_screen.dart | 28 +++++++++---------- .../widgets/delete_instance_dialog.dart | 9 +++--- .../open/widgets/buttons/dg_text_button.dart | 5 +++- client/lib/open/widgets/dg_checkbox.dart | 2 +- client/lib/open/widgets/dg_menu.dart | 12 +++++--- client/lib/open/widgets/loading_screen.dart | 2 +- client/lib/router/routes.dart | 27 ++++++------------ client/lib/theme/color.dart | 2 +- client/lib/theme/text.dart | 2 +- client/lib/utils/position.dart | 11 +++----- client/lib/utils/safe_insets.dart | 16 ++++++----- client/lib/utils/update_instance.dart | 5 ++-- client/pubspec.yaml | 2 +- 25 files changed, 118 insertions(+), 101 deletions(-) create mode 100644 .fvmrc diff --git a/.fvmrc b/.fvmrc new file mode 100644 index 0000000..ac62dd3 --- /dev/null +++ b/.fvmrc @@ -0,0 +1,3 @@ +{ + "flutter": "3.38.10" +} \ No newline at end of file diff --git a/client/lib/data/db/enums.dart b/client/lib/data/db/enums.dart index 36b4c07..85c264e 100644 --- a/client/lib/data/db/enums.dart +++ b/client/lib/data/db/enums.dart @@ -101,7 +101,8 @@ enum ClientTrafficPolicy { ClientTrafficPolicy.values.firstWhere((e) => e.value == value); } -class ClientTrafficPolicyConverter extends TypeConverter { +class ClientTrafficPolicyConverter + extends TypeConverter { const ClientTrafficPolicyConverter(); @override diff --git a/client/lib/data/proxy/config.dart b/client/lib/data/proxy/config.dart index 3bc931d..ffdb4d1 100644 --- a/client/lib/data/proxy/config.dart +++ b/client/lib/data/proxy/config.dart @@ -49,8 +49,8 @@ class NetworkInfoResponse { this.token, }); - factory NetworkInfoResponse.fromJson(Map json) => - _$NetworkInfoResponseFromJson(json); + factory NetworkInfoResponse.fromJson(Map json) => + _$NetworkInfoResponseFromJson(json); - Map toJson() => _$NetworkInfoResponseToJson(this); + Map toJson() => _$NetworkInfoResponseToJson(this); } diff --git a/client/lib/enterprise/config_update.dart b/client/lib/enterprise/config_update.dart index f777891..d2360f6 100644 --- a/client/lib/enterprise/config_update.dart +++ b/client/lib/enterprise/config_update.dart @@ -44,8 +44,7 @@ class ConfigurationUpdater extends HookConsumerWidget { ); for (final instance in instances) { talker.debug( - "Auto configuration update started for ${instance.name} (${instance - .id})", + "Auto configuration update started for ${instance.name} (${instance.id})", ); final (responseData, responseStatus, headers) = await proxyApi .pollConfiguration(instance.proxyUrl, instance.poolingToken); @@ -61,8 +60,7 @@ class ConfigurationUpdater extends HookConsumerWidget { headers['defguard-component-version']?.first; if (coreVersionStr == null || proxyVersionStr == null) { talker.error( - "Version headers missing for ${instance - .logName}, treating as unsupported", + "Version headers missing for ${instance.logName}, treating as unsupported", ); versionUnsupportedInstances.add({ 'name': instance.name, @@ -95,9 +93,7 @@ class ConfigurationUpdater extends HookConsumerWidget { } if (responseData == null) { talker.error( - "Auto configuration update failed for ${instance - .logName} ! Update data retrieval failed, status: ${responseStatus ?? - "unknown"}!", + "Auto configuration update failed for ${instance.logName} ! Update data retrieval failed, status: ${responseStatus ?? "unknown"}!", ); continue; } @@ -112,12 +108,7 @@ class ConfigurationUpdater extends HookConsumerWidget { ); if (updateResult != null) { talker.info( - "Instance ${instance - .logName} results: Instance updated: ${updateResult - .instanceChanged} | Locations updated: ${updateResult - .locationsUpdated} | Locations removed: ${updateResult - .locationsRemoved} | Locations added: ${updateResult - .locationsAdded}", + "Instance ${instance.logName} results: Instance updated: ${updateResult.instanceChanged} | Locations updated: ${updateResult.locationsUpdated} | Locations removed: ${updateResult.locationsRemoved} | Locations added: ${updateResult.locationsAdded}", ); if (updateResult.didChange) { final message = getInstanceUpdateMessage( @@ -144,7 +135,7 @@ class ConfigurationUpdater extends HookConsumerWidget { "The following instances have versions that are incompatible with your Defguard Mobile Client and may not work correctly:\n\n"; for (final instance in versionUnsupportedInstances) { message += - "- ${instance['name']}: Defguard Core ${instance['coreVersion']} (expected >=$supportedCoreVersion), Defguard Proxy ${instance['proxyVersion']} (expected >=$supportedProxyVersion)\n"; + "- ${instance['name']}: Defguard Core ${instance['coreVersion']} (expected >=$supportedCoreVersion), Defguard Proxy ${instance['proxyVersion']} (expected >=$supportedProxyVersion)\n"; } message += "\nPlease contact your administrator."; toaster.showInfo( @@ -170,10 +161,12 @@ class ConfigurationUpdater extends HookConsumerWidget { // update when user wakes up application useEffect(() { final timeTick = DateTime.now(); - final afterCooldown = lastConfigUpdate.value == null || + final afterCooldown = + lastConfigUpdate.value == null || (lastConfigUpdate.value != null && - lastConfigUpdate.value!.add(Duration(seconds: 60)).isBefore( - timeTick)); + lastConfigUpdate.value! + .add(Duration(seconds: 60)) + .isBefore(timeTick)); if (lifecycle == AppLifecycleState.resumed && afterCooldown) { lastConfigUpdate.value = timeTick; updateConfiguration(); diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index d227f3a..f40d3e0 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -81,6 +81,7 @@ class DevicePostureData { final Int32Check? windowsSecurityUpdateAgeDays; final StringCheck? linuxKernelVersion; final BoolCheck? deviceIntegrity; + final StringCheck? androidSecurityPatchDate; const DevicePostureData({ required this.defguardClientVersion, @@ -93,6 +94,7 @@ class DevicePostureData { this.windowsSecurityUpdateAgeDays, this.linuxKernelVersion, this.deviceIntegrity, + this.androidSecurityPatchDate, }); factory DevicePostureData.fromJson(Map json) => @@ -158,6 +160,9 @@ Future getPosture() async { linuxKernelVersion: StringCheck.unavailable( UnavailableReason.notApplicable, ), + androidSecurityPatchDate: android.version.securityPatch != null + ? StringCheck.value(android.version.securityPatch!) + : StringCheck.unavailable(UnavailableReason.detectionFailed), ); } @@ -182,6 +187,9 @@ Future getPosture() async { linuxKernelVersion: StringCheck.unavailable( UnavailableReason.notApplicable, ), + androidSecurityPatchDate: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), ); } @@ -203,5 +211,8 @@ Future getPosture() async { UnavailableReason.notApplicable, ), deviceIntegrity: BoolCheck.unavailable(UnavailableReason.notApplicable), + androidSecurityPatchDate: StringCheck.unavailable( + UnavailableReason.notApplicable, + ), ); } diff --git a/client/lib/enterprise/postures.g.dart b/client/lib/enterprise/postures.g.dart index f25bdc6..b2903e4 100644 --- a/client/lib/enterprise/postures.g.dart +++ b/client/lib/enterprise/postures.g.dart @@ -94,6 +94,11 @@ DevicePostureData _$DevicePostureDataFromJson( 'device_integrity', (v) => v == null ? null : BoolCheck.fromJson(v as Map), ), + androidSecurityPatchDate: $checkedConvert( + 'android_security_patch_date', + (v) => + v == null ? null : StringCheck.fromJson(v as Map), + ), ); return val; }, @@ -108,6 +113,7 @@ DevicePostureData _$DevicePostureDataFromJson( 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', 'linuxKernelVersion': 'linux_kernel_version', 'deviceIntegrity': 'device_integrity', + 'androidSecurityPatchDate': 'android_security_patch_date', }, ); @@ -122,6 +128,7 @@ const _$DevicePostureDataFieldMap = { 'windowsSecurityUpdateAgeDays': 'windows_security_update_age_days', 'linuxKernelVersion': 'linux_kernel_version', 'deviceIntegrity': 'device_integrity', + 'androidSecurityPatchDate': 'android_security_patch_date', }; Map _$DevicePostureDataToJson(DevicePostureData instance) => @@ -136,6 +143,7 @@ Map _$DevicePostureDataToJson(DevicePostureData instance) => 'windows_security_update_age_days': instance.windowsSecurityUpdateAgeDays, 'linux_kernel_version': instance.linuxKernelVersion, 'device_integrity': instance.deviceIntegrity, + 'android_security_patch_date': instance.androidSecurityPatchDate, }; PostureConnectRequest _$PostureConnectRequestFromJson( diff --git a/client/lib/logging.dart b/client/lib/logging.dart index f1284bb..8d26881 100644 --- a/client/lib/logging.dart +++ b/client/lib/logging.dart @@ -1,3 +1,3 @@ import 'package:talker_flutter/talker_flutter.dart'; -final talker = TalkerFlutter.init(); \ No newline at end of file +final talker = TalkerFlutter.init(); diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index cd03318..2f46fbb 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -5,15 +5,14 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; -import 'package:native_dio_adapter/native_dio_adapter.dart'; import 'package:mobile/data/db/enums.dart'; import 'package:mobile/data/proto/client_platform_info.pb.dart'; import 'package:mobile/data/proxy/config.dart'; -import 'package:mobile/enterprise/postures.dart'; -import 'package:package_info_plus/package_info_plus.dart'; import 'package:mobile/data/proxy/enrollment.dart'; import 'package:mobile/data/proxy/mfa.dart'; - +import 'package:mobile/enterprise/postures.dart'; +import 'package:native_dio_adapter/native_dio_adapter.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:talker_dio_logger/talker_dio_logger_interceptor.dart'; import '../logging.dart'; @@ -211,6 +210,13 @@ class _ProxyApi { dataError.toLowerCase().trim() == missingMFAMethodError) { throw MfaMethodNotAvailableException(data.method); } + + if (e.response?.statusCode == 403) { + final error = responseData['error'] ?? responseData['message']; + if (error is String) { + throw HttpException(error); + } + } } throw HttpException( "Failed to start MFA. Status: ${e.response?.statusCode} Body: ${e.response?.data}", @@ -237,11 +243,12 @@ class _ProxyApi { return PostureConnectResponse.fromJson(response.data); } on DioException catch (e) { final responseData = e.response?.data; - final dataError = responseData is Map - ? responseData['error'] - : null; - if (e.response?.statusCode == 403 && dataError is String) { - throw PostureCheckException(dataError); + if (e.response?.statusCode == 403 && + responseData is Map) { + final error = responseData['error'] ?? responseData['message']; + if (error is String) { + throw PostureCheckException(error); + } } if (e.response != null) { throw HttpException( diff --git a/client/lib/open/riverpod/biometrics_state.dart b/client/lib/open/riverpod/biometrics_state.dart index 8b5ddd6..7c5421a 100644 --- a/client/lib/open/riverpod/biometrics_state.dart +++ b/client/lib/open/riverpod/biometrics_state.dart @@ -7,7 +7,6 @@ import 'package:riverpod_annotation/riverpod_annotation.dart'; part 'biometrics_state.g.dart'; - class BiometricsState { bool isSupported; bool canCheck; diff --git a/client/lib/open/riverpod/package_info/package_info.dart b/client/lib/open/riverpod/package_info/package_info.dart index bf6a28e..d46691a 100644 --- a/client/lib/open/riverpod/package_info/package_info.dart +++ b/client/lib/open/riverpod/package_info/package_info.dart @@ -10,4 +10,4 @@ Future packageInfo(Ref ref) async { WidgetsFlutterBinding.ensureInitialized(); final info = await PackageInfo.fromPlatform(); return info; -} \ No newline at end of file +} diff --git a/client/lib/open/riverpod/plugin/plugin.dart b/client/lib/open/riverpod/plugin/plugin.dart index 2d7b177..4f2f6e0 100644 --- a/client/lib/open/riverpod/plugin/plugin.dart +++ b/client/lib/open/riverpod/plugin/plugin.dart @@ -15,4 +15,4 @@ class PluginActiveTunnelState extends _$PluginActiveTunnelState { void clear() { state = null; } -} \ No newline at end of file +} diff --git a/client/lib/open/screens/add_instance/generate_wireguard.dart b/client/lib/open/screens/add_instance/generate_wireguard.dart index f9cd791..61c7d5d 100644 --- a/client/lib/open/screens/add_instance/generate_wireguard.dart +++ b/client/lib/open/screens/add_instance/generate_wireguard.dart @@ -3,14 +3,10 @@ import 'dart:convert'; import 'package:mobile/data/proxy/enrollment.dart'; import 'package:x25519/x25519.dart' as x; - Future generateWireguardKeyPair() async { final keyPair = x.generateKeyPair(); final encodedPriv = base64Encode(keyPair.privateKey); final encodedPub = base64Encode(keyPair.publicKey); - return WireguardEncodedKeyPair( - privKey: encodedPriv, - pubKey: encodedPub, - ); + return WireguardEncodedKeyPair(privKey: encodedPriv, pubKey: encodedPub); } diff --git a/client/lib/open/screens/instance/instance_screen.dart b/client/lib/open/screens/instance/instance_screen.dart index a725ec5..310acef 100644 --- a/client/lib/open/screens/instance/instance_screen.dart +++ b/client/lib/open/screens/instance/instance_screen.dart @@ -415,20 +415,20 @@ class _LocationItem extends HookConsumerWidget { ); }, ), - if (instance.clientTrafficPolicy == ClientTrafficPolicy.none) - DgMenuItem( - text: "Select Traffic Routing", - onTap: () { - showDialog( - context: context, - builder: (_) => RoutingMethodDialog( - location: location, - intention: RoutingMethodDialogIntention.save, - clientTrafficPolicy: instance.clientTrafficPolicy, - ), - ); - }, - ), + if (instance.clientTrafficPolicy == ClientTrafficPolicy.none) + DgMenuItem( + text: "Select Traffic Routing", + onTap: () { + showDialog( + context: context, + builder: (_) => RoutingMethodDialog( + location: location, + intention: RoutingMethodDialogIntention.save, + clientTrafficPolicy: instance.clientTrafficPolicy, + ), + ); + }, + ), ]; }, [location, instance]); diff --git a/client/lib/open/screens/instance/widgets/delete_instance_dialog.dart b/client/lib/open/screens/instance/widgets/delete_instance_dialog.dart index 26976d6..e60477d 100644 --- a/client/lib/open/screens/instance/widgets/delete_instance_dialog.dart +++ b/client/lib/open/screens/instance/widgets/delete_instance_dialog.dart @@ -10,7 +10,6 @@ import 'package:mobile/utils/secure_storage.dart'; import '../../../services/snackbar_service.dart'; - class DeleteInstanceDialog extends HookConsumerWidget { final DefguardInstance instance; @@ -25,7 +24,7 @@ class DeleteInstanceDialog extends HookConsumerWidget { Future deleteInstance(BuildContext context) async { try { - if(instance.mfaKeysStored) { + if (instance.mfaKeysStored) { await removeInstanceStorage(instance.secureStorageKey); } await db.managers.defguardInstances @@ -35,8 +34,10 @@ class DeleteInstanceDialog extends HookConsumerWidget { SnackbarService.show("Instance deleted"); Navigator.of(context).pop(); } - } catch(e) { - talker.error("Failed to delete instance ${instance.logName}! Reason: \n $e"); + } catch (e) { + talker.error( + "Failed to delete instance ${instance.logName}! Reason: \n $e", + ); } } diff --git a/client/lib/open/widgets/buttons/dg_text_button.dart b/client/lib/open/widgets/buttons/dg_text_button.dart index 72d43ad..297078f 100644 --- a/client/lib/open/widgets/buttons/dg_text_button.dart +++ b/client/lib/open/widgets/buttons/dg_text_button.dart @@ -39,7 +39,10 @@ class DgTextButton extends StatelessWidget { child: Text( text, textAlign: TextAlign.center, - style: textStyle.copyWith(decoration: TextDecoration.underline, decorationColor: textStyle.color), + style: textStyle.copyWith( + decoration: TextDecoration.underline, + decorationColor: textStyle.color, + ), ), ), ), diff --git a/client/lib/open/widgets/dg_checkbox.dart b/client/lib/open/widgets/dg_checkbox.dart index 90a7d5e..a71a950 100644 --- a/client/lib/open/widgets/dg_checkbox.dart +++ b/client/lib/open/widgets/dg_checkbox.dart @@ -31,7 +31,7 @@ class DgCheckbox extends StatelessWidget { Widget _getBody() { final icon = DgIconCheckbox(size: iconSize, variant: _getIconVariant()); TextStyle textStyleInner; - if(textStyle == null) { + if (textStyle == null) { textStyleInner = DgText.modal1.copyWith(color: DgColor.textBodySecondary); } else { textStyleInner = textStyle!; diff --git a/client/lib/open/widgets/dg_menu.dart b/client/lib/open/widgets/dg_menu.dart index 651d95e..f6d3c47 100644 --- a/client/lib/open/widgets/dg_menu.dart +++ b/client/lib/open/widgets/dg_menu.dart @@ -1,4 +1,3 @@ - import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -31,7 +30,9 @@ class DgMenu extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final topOffset = useMemoized(() => anchorGeometry.position.dy + 10 + anchorGeometry.size.height); + final topOffset = useMemoized( + () => anchorGeometry.position.dy + 10 + anchorGeometry.size.height, + ); final leftOffset = useMemoized(() => anchorGeometry.position.dx); final animationController = useAnimationController( duration: 100.ms, @@ -67,8 +68,11 @@ class DgMenu extends HookConsumerWidget { builder: (context, _) => FadeTransition( opacity: animationController, child: SlideTransition( - position: Tween(begin: Offset(0, -0.05), end: Offset.zero) - .animate( + position: + Tween( + begin: Offset(0, -0.05), + end: Offset.zero, + ).animate( CurvedAnimation( parent: animationController, curve: Curves.easeOut, diff --git a/client/lib/open/widgets/loading_screen.dart b/client/lib/open/widgets/loading_screen.dart index a5e1bc5..4ed189a 100644 --- a/client/lib/open/widgets/loading_screen.dart +++ b/client/lib/open/widgets/loading_screen.dart @@ -29,7 +29,7 @@ class LoadingView extends StatelessWidget { mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: [ - DgCircularProgress(color: DgColor.iconSecondary, size: 92), + DgCircularProgress(color: DgColor.iconSecondary, size: 92), ], ), ); diff --git a/client/lib/router/routes.dart b/client/lib/router/routes.dart index 3994ccd..c7c4f22 100644 --- a/client/lib/router/routes.dart +++ b/client/lib/router/routes.dart @@ -21,8 +21,7 @@ part 'routes.g.dart'; @TypedGoRoute(path: "/process_qr") @immutable -class ProcessQrScreenRoute extends GoRouteData - with _$ProcessQrScreenRoute { +class ProcessQrScreenRoute extends GoRouteData with _$ProcessQrScreenRoute { const ProcessQrScreenRoute(this.$extra); final ProcessQrScreenData $extra; @@ -35,8 +34,7 @@ class ProcessQrScreenRoute extends GoRouteData @TypedGoRoute(path: '/') @immutable -class HomeScreenRoute extends GoRouteData - with _$HomeScreenRoute { +class HomeScreenRoute extends GoRouteData with _$HomeScreenRoute { const HomeScreenRoute(); @override @@ -47,8 +45,7 @@ class HomeScreenRoute extends GoRouteData @TypedGoRoute(path: "/qr") @immutable -class QRScreenRoute extends GoRouteData - with _$QRScreenRoute { +class QRScreenRoute extends GoRouteData with _$QRScreenRoute { const QRScreenRoute(this.$extra); final QrScreenData $extra; @@ -61,8 +58,7 @@ class QRScreenRoute extends GoRouteData @TypedGoRoute(path: "/instance/:id") @immutable -class InstanceScreenRoute extends GoRouteData - with _$InstanceScreenRoute { +class InstanceScreenRoute extends GoRouteData with _$InstanceScreenRoute { final String id; const InstanceScreenRoute({required this.id}); @@ -75,8 +71,7 @@ class InstanceScreenRoute extends GoRouteData @TypedGoRoute(path: "/add_instance/name_device") @immutable -class NameDeviceScreenRoute extends GoRouteData - with _$NameDeviceScreenRoute { +class NameDeviceScreenRoute extends GoRouteData with _$NameDeviceScreenRoute { const NameDeviceScreenRoute(this.$extra); final NameDeviceScreenData $extra; @@ -99,8 +94,7 @@ class AddInstanceFormScreenRoute extends GoRouteData @TypedGoRoute(path: '/add_instance/init') @immutable -class AddInstanceScreenRoute extends GoRouteData - with _$AddInstanceScreenRoute { +class AddInstanceScreenRoute extends GoRouteData with _$AddInstanceScreenRoute { const AddInstanceScreenRoute(); @override @@ -111,8 +105,7 @@ class AddInstanceScreenRoute extends GoRouteData @TypedGoRoute(path: "/talker") @immutable -class TalkerScreenRoute extends GoRouteData - with _$TalkerScreenRoute { +class TalkerScreenRoute extends GoRouteData with _$TalkerScreenRoute { @override Widget build(BuildContext context, GoRouterState state) { return TalkerScreen(talker: talker); @@ -121,8 +114,7 @@ class TalkerScreenRoute extends GoRouteData @TypedGoRoute(path: "/mfa/openid") @immutable -class OpenIdMfaScreenRoute extends GoRouteData - with _$OpenIdMfaScreenRoute { +class OpenIdMfaScreenRoute extends GoRouteData with _$OpenIdMfaScreenRoute { const OpenIdMfaScreenRoute(this.$extra); final OpenIdMfaScreenData $extra; @@ -149,8 +141,7 @@ class OpenIdMfaWaitingScreenRoute extends GoRouteData @TypedGoRoute(path: "/mfa/code") @immutable -class MfaCodeScreenRoute extends GoRouteData - with _$MfaCodeScreenRoute { +class MfaCodeScreenRoute extends GoRouteData with _$MfaCodeScreenRoute { const MfaCodeScreenRoute(this.$extra); final MfaCodeScreenData $extra; diff --git a/client/lib/theme/color.dart b/client/lib/theme/color.dart index dac138c..b797de5 100644 --- a/client/lib/theme/color.dart +++ b/client/lib/theme/color.dart @@ -81,4 +81,4 @@ final BoxShadow dgBoxShadow = BoxShadow( offset: Offset(0, 12), blurRadius: 24, spreadRadius: 0, -); \ No newline at end of file +); diff --git a/client/lib/theme/text.dart b/client/lib/theme/text.dart index 2e37da8..484c6dd 100644 --- a/client/lib/theme/text.dart +++ b/client/lib/theme/text.dart @@ -25,7 +25,7 @@ class DgText { static const TextStyle body2 = TextStyle( fontFamily: _poppins, fontWeight: FontWeight.w400, - fontSize: 15 + fontSize: 15, ); static const TextStyle modal1 = TextStyle( fontFamily: _roboto, diff --git a/client/lib/utils/position.dart b/client/lib/utils/position.dart index c09f723..4984d53 100644 --- a/client/lib/utils/position.dart +++ b/client/lib/utils/position.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; Offset? getRenderObjectPosition(RenderObject? renderObject) { - if(renderObject is RenderBox && renderObject.hasSize) { + if (renderObject is RenderBox && renderObject.hasSize) { return renderObject.localToGlobal(Offset.zero); } return null; @@ -11,18 +11,15 @@ class WidgetGeometry { final Offset position; final Size size; - const WidgetGeometry({ - required this.position, - required this.size, - }); + const WidgetGeometry({required this.position, required this.size}); static WidgetGeometry fromKey(GlobalKey key) { final renderObject = key.currentContext?.findRenderObject(); - if(renderObject is RenderBox && renderObject.hasSize) { + if (renderObject is RenderBox && renderObject.hasSize) { final position = renderObject.localToGlobal(Offset.zero); final size = renderObject.size; return WidgetGeometry(position: position, size: size); } return WidgetGeometry(position: Offset.zero, size: Size.zero); } -} \ No newline at end of file +} diff --git a/client/lib/utils/safe_insets.dart b/client/lib/utils/safe_insets.dart index 6bd86a2..cb9b9b7 100644 --- a/client/lib/utils/safe_insets.dart +++ b/client/lib/utils/safe_insets.dart @@ -1,11 +1,13 @@ import 'dart:math' as math; import 'package:flutter/material.dart'; -(double, double) safeInsetHorizontal(BuildContext context, - double preferredPadding) { - final safe = MediaQuery - .of(context) - .padding; - return (math.max(safe.left, preferredPadding), math.max( - safe.right, preferredPadding)); +(double, double) safeInsetHorizontal( + BuildContext context, + double preferredPadding, +) { + final safe = MediaQuery.of(context).padding; + return ( + math.max(safe.left, preferredPadding), + math.max(safe.right, preferredPadding), + ); } diff --git a/client/lib/utils/update_instance.dart b/client/lib/utils/update_instance.dart index 81b079f..5280494 100644 --- a/client/lib/utils/update_instance.dart +++ b/client/lib/utils/update_instance.dart @@ -60,7 +60,8 @@ Future updateInstance({ await db.managers.defguardInstances .filter((row) => row.id.equals(instance.id)) .update( - (_) => DefguardInstancesCompanion(poolingToken: drift.Value(token)), + (_) => + DefguardInstancesCompanion(poolingToken: drift.Value(token)), ); talker.debug("${instance.logName} token updated"); } @@ -124,7 +125,7 @@ String getInstanceUpdateMessage( UpdateInstanceResult updateResult, ) { final buffer = StringBuffer(); - if(updateResult.instanceChanged) { + if (updateResult.instanceChanged) { buffer.write("Instance information updated. "); } if (updateResult.locationsRemoved.isNotEmpty) { diff --git a/client/pubspec.yaml b/client/pubspec.yaml index ac44d0c..cfbb766 100644 --- a/client/pubspec.yaml +++ b/client/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.6.4+1 +version: 1.7.0+1 environment: sdk: ^3.8.1 From f2cc4dd6b88ecfbce319ee76f1d874ac5d2e7de8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 08:11:15 +0200 Subject: [PATCH 27/44] Update Xcode project --- client/ios/Podfile.lock | 11 +-- client/ios/Runner.xcodeproj/project.pbxproj | 12 +++ client/pubspec.lock | 92 ++++++++++++--------- 3 files changed, 68 insertions(+), 47 deletions(-) diff --git a/client/ios/Podfile.lock b/client/ios/Podfile.lock index ff20001..ce14a2c 100644 --- a/client/ios/Podfile.lock +++ b/client/ios/Podfile.lock @@ -1,9 +1,6 @@ PODS: - app_links (6.4.1): - Flutter - - cupertino_http (0.0.1): - - Flutter - - FlutterMacOS - device_info_plus (0.0.1): - Flutter - Flutter (1.0.0) @@ -21,7 +18,7 @@ PODS: - FlutterMacOS - package_info_plus (0.4.5): - Flutter - - permission_handler_apple (9.3.0): + - permission_handler_apple (9.4.8): - Flutter - share_plus (0.0.1): - Flutter @@ -58,7 +55,6 @@ PODS: DEPENDENCIES: - app_links (from `.symlinks/plugins/app_links/ios`) - - cupertino_http (from `.symlinks/plugins/cupertino_http/darwin`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - Flutter (from `Flutter`) - flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`) @@ -81,8 +77,6 @@ SPEC REPOS: EXTERNAL SOURCES: app_links: :path: ".symlinks/plugins/app_links/ios" - cupertino_http: - :path: ".symlinks/plugins/cupertino_http/darwin" device_info_plus: :path: ".symlinks/plugins/device_info_plus/ios" Flutter: @@ -114,7 +108,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: app_links: 585674be3c6661708e6cd794ab4f39fb9d8356f9 - cupertino_http: 947a233f40cfea55167a49f2facc18434ea117ba device_info_plus: bf2e3232933866d73fe290f2942f2156cdd10342 Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467 flutter_local_notifications: ff50f8405aaa0ccdc7dcfb9022ca192e8ad9688f @@ -123,7 +116,7 @@ SPEC CHECKSUMS: local_auth_darwin: 63c73d6d28cc3e239be2b6aa460ea6e317cd5100 mobile_scanner: 77265f3dc8d580810e91849d4a0811a90467ed5e package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4 - permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2 + permission_handler_apple: ee2fe0fd04551b304eb002714ff067c371c822ed share_plus: 8b6f8b3447e494cca5317c8c3073de39b3600d1f shared_preferences_foundation: 5086985c1d43c5ba4d5e69a4e8083a389e2909e6 sqlite3: 73513155ec6979715d3904ef53a8d68892d4032b diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 3309e0e..d6e1d13 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -593,7 +593,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; @@ -608,6 +611,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; @@ -948,7 +952,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; @@ -963,6 +970,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; @@ -977,7 +985,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; @@ -992,6 +1003,7 @@ ); PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; VERSIONING_SYSTEM = "apple-generic"; diff --git a/client/pubspec.lock b/client/pubspec.lock index 4f57d02..9041cb5 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -237,10 +237,10 @@ packages: dependency: transitive description: name: code_assets - sha256: dad6bf6b9f4f378b0a69edbf42584d336efd1a9ce15deb1ba591cbb1b5ff440f + sha256: bf394f466ba9205f1812a0433b392d6af280f155f56651eda7c18cc32ed493b8 url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.2.1" code_builder: dependency: transitive description: @@ -277,18 +277,18 @@ packages: dependency: transitive description: name: coverage - sha256: "5da775aa218eaf2151c721b16c01c7676fbfdd99cebba2bf64e8b807a28ff94d" + sha256: "956a3de0725ca232ad353565a8290d3357592bf4250f6f298a185e2d949c5d3d" url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.15.1" cronet_http: dependency: transitive description: name: cronet_http - sha256: "8e77bc6f203e0bc9126e6a9092508a3435dbcb04da3b53ed1a358909385c5e0e" + sha256: "9da9860b409d71e4b8259e3dee631176d499dee23e7cd45a3024ebd5181997d8" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.0" cross_file: dependency: transitive description: @@ -317,10 +317,10 @@ packages: dependency: transitive description: name: cupertino_http - sha256: "82cbec60c90bf785a047a9525688b6dacac444e177e1d5a5876963d3c50369e8" + sha256: "3c8c69cc1b94b9c7570d9454bf1e11fa7010b248547a0760843adc71f0c08fbe" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "3.0.2" cupertino_icons: dependency: "direct main" description: @@ -373,10 +373,10 @@ packages: dependency: transitive description: name: dbus - sha256: d0c98dcd4f5169878b6cf8f6e0a52403a9dff371a3e2f019697accbf6f44a270 + sha256: "0ce9b0a839e6dee59a37a623d2fc26a35bbbe6404213e419b0d6411023d62645" url: "https://pub.dev" source: hosted - version: "0.7.12" + version: "0.7.14" device_info_plus: dependency: "direct main" description: @@ -562,10 +562,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" + sha256: "3854fe5e3bff0b113c658f260b90c95dea17c92db0f2addeac2e343dd9969785" url: "https://pub.dev" source: hosted - version: "2.0.34" + version: "2.0.35" flutter_riverpod: dependency: "direct main" description: @@ -716,10 +716,10 @@ packages: dependency: transitive description: name: hooks - sha256: a41af4e8fc687cd6d33de9751eb936c8c0204ebe2bcb6c15ecf707504bf47f31 + sha256: "9a62a50b50b769a737bc0a8ff381f333529df3ab746b2f6b02e83760231455ba" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "2.0.2" hooks_riverpod: dependency: "direct main" description: @@ -788,10 +788,10 @@ packages: dependency: transitive description: name: intl - sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + sha256: "1ca20c894b1717686a2319b8548763d812bc0aabdac580420a44c5178c57a867" url: "https://pub.dev" source: hosted - version: "0.20.2" + version: "0.20.3" io: dependency: transitive description: @@ -804,10 +804,18 @@ packages: dependency: transitive description: name: jni - sha256: "8706a77e94c76fe9ec9315e18949cc9479cc03af97085ca9c1077b61323ea12d" + sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + url: "https://pub.dev" + source: hosted + version: "1.0.0" + jni_flutter: + dependency: transitive + description: + name: jni_flutter + sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" url: "https://pub.dev" source: hosted - version: "0.15.2" + version: "1.0.1" js: dependency: transitive description: @@ -956,10 +964,18 @@ packages: dependency: "direct main" description: name: native_dio_adapter - sha256: "9bbfa5221fd287eb063962bbe6534290e5f87933e576fac210149fb80253b89a" + sha256: "89a84d8936a108c206e481b8da090422abb1351febac4956cd4a8113627ebb5e" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + native_toolchain_c: + dependency: transitive + description: + name: native_toolchain_c + sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96 url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "0.19.1" node_preamble: dependency: transitive description: @@ -1020,42 +1036,42 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + sha256: a7f4874f987173da295a61c181b8ee71dab59b332a486b391babf26a1b884825 url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.1.6" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "149441ca6e4f38193b2e004c0ca6376a3d11f51fa5a77552d8bd4d2b0c0912ba" + sha256: "69cbd515a62b94d32a7944f086b2f82b4ac40a1d45bebfc00813a430ab2dabcd" url: "https://pub.dev" source: hosted - version: "2.2.23" + version: "2.3.1" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "6d13aece7b3f5c5a9731eaf553ff9dcbc2eff41087fd2df587fd0fed9a3eb0c4" + sha256: "2a376b7d6392d80cd3705782d2caa734ca4727776db0b6ec36ef3f1855197699" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + sha256: "58c2005f147315b11e9b4a7bc889cd5203e250cba8e3f012dae259b4972b5c16" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + sha256: "484838772624c3a4b94f1e44a3e19897fee738f2d5c4ce448443b0417f7c9dda" url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_windows: dependency: transitive description: @@ -1068,10 +1084,10 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1 + sha256: fe54465bcc62a4564c6e4db337bbaded6c0c0fa6e10487414436d163114784f6 url: "https://pub.dev" source: hosted - version: "12.0.1" + version: "12.0.3" permission_handler_android: dependency: transitive description: @@ -1084,10 +1100,10 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420" url: "https://pub.dev" source: hosted - version: "9.4.7" + version: "9.4.10" permission_handler_html: dependency: transitive description: @@ -1649,10 +1665,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: b9b3f391857781aa96acacef96066f2f49b4cd03cf9fce3ca4d8da2ef5ea129e + sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3" url: "https://pub.dev" source: hosted - version: "1.2.3" + version: "1.2.6" vector_math: dependency: transitive description: @@ -1765,5 +1781,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.10.0 <4.0.0" - flutter: ">=3.38.0" + dart: ">=3.10.3 <4.0.0" + flutter: ">=3.38.4" From 9cc041188257b21680e082688f7441f4c67fdde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 08:23:42 +0200 Subject: [PATCH 28/44] Bump version to 1.7.0 in Xcode project --- client/ios/Runner.xcodeproj/project.pbxproj | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index d6e1d13..f994d6f 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -609,6 +609,7 @@ "$(inherited)", "$(PROJECT_DIR)/VPNExtension/BoringTun", ); + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -648,7 +649,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.4; + MARKETING_VERSION = 1.7.0; MODULEMAP_FILE = ""; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; @@ -702,7 +703,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.4; + MARKETING_VERSION = 1.7.0; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -753,7 +754,7 @@ "$(PROJECT_DIR)/VPNExtension/BoringTun", ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; - MARKETING_VERSION = 1.6.4; + MARKETING_VERSION = 1.7.0; MODULEMAP_FILE = ""; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ""; @@ -968,6 +969,7 @@ "$(inherited)", "$(PROJECT_DIR)/boringtun/target/release", ); + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1001,6 +1003,7 @@ "$(inherited)", "$(PROJECT_DIR)/boringtun/target/release", ); + MARKETING_VERSION = 1.7.0; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; From 3c5f541f2fcfcb4f1c30c46dda78b8e84e6f24ec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 08:27:29 +0200 Subject: [PATCH 29/44] Add verbosity --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9b26104..977ee13 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -52,7 +52,7 @@ jobs: run: pod repo update - name: Build iOS - run: flutter build ipa --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} + run: flutter build ipa -v --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} - name: Get last commit message run: echo "COMMIT_MSG=$(git log -1 --pretty=%B)" >> $GITHUB_ENV From 53d6b8bbf1a41ee55716a19ea47cc43b2de8f1a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 10:18:51 +0200 Subject: [PATCH 30/44] Auto sign VPNExtension --- client/ios/Runner.xcodeproj/project.pbxproj | 48 ++++++++++++++------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index f994d6f..539ded9 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -577,9 +577,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -600,6 +599,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -613,8 +613,12 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Profile; @@ -629,8 +633,10 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtensionDebug.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -638,7 +644,6 @@ INFOPLIST_FILE = VPNExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VPNExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -656,11 +661,12 @@ OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; @@ -683,8 +689,10 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -692,7 +700,6 @@ INFOPLIST_FILE = VPNExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VPNExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -709,11 +716,12 @@ OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; @@ -734,8 +742,10 @@ CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtension.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = 82GZ7KN29J; ENABLE_USER_SCRIPT_SANDBOXING = YES; GCC_C_LANGUAGE_STANDARD = gnu17; GENERATE_INFOPLIST_FILE = YES; @@ -743,7 +753,6 @@ INFOPLIST_FILE = VPNExtension/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = VPNExtension; INFOPLIST_KEY_NSHumanReadableCopyright = ""; - IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -760,11 +769,12 @@ OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; - SUPPORTED_PLATFORMS = "iphonesimulator iphoneos"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; - SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = YES; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_INCLUDE_PATHS = "$(PROJECT_DIR)/VPNExtension/BoringTun"; SWIFT_OBJC_BRIDGING_HEADER = "$(PROJECT_DIR)/VPNExtension/BoringTun/defguard_boringtunFFI.h"; @@ -882,10 +892,9 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -935,9 +944,8 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = NO; - SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -960,6 +968,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -973,9 +982,13 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; @@ -994,6 +1007,7 @@ ENABLE_BITCODE = NO; INFOPLIST_FILE = Runner/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = Defguard; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; IPHONEOS_DEPLOYMENT_TARGET = 15.6; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -1007,8 +1021,12 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = "1,2"; VERSIONING_SYSTEM = "apple-generic"; }; name = Release; From e3b86b04f991d7b5145cabcdcfcab61d80ac5b74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 10:36:26 +0200 Subject: [PATCH 31/44] One entitlements --- client/ios/Runner.xcodeproj/project.pbxproj | 2 +- .../VPNExtension/VPNExtension.entitlements | 4 ++++ .../VPNExtensionDebug.entitlements | 24 ------------------- 3 files changed, 5 insertions(+), 25 deletions(-) delete mode 100644 client/ios/VPNExtension/VPNExtensionDebug.entitlements diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 539ded9..943efa9 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -632,7 +632,7 @@ CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; - CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtensionDebug.entitlements; + CODE_SIGN_ENTITLEMENTS = VPNExtension/VPNExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; diff --git a/client/ios/VPNExtension/VPNExtension.entitlements b/client/ios/VPNExtension/VPNExtension.entitlements index 76663a0..8b31f34 100644 --- a/client/ios/VPNExtension/VPNExtension.entitlements +++ b/client/ios/VPNExtension/VPNExtension.entitlements @@ -10,6 +10,10 @@ allow-vpn + com.apple.security.application-groups + + group.net.defguard.mobile + com.apple.security.app-sandbox com.apple.security.network.client diff --git a/client/ios/VPNExtension/VPNExtensionDebug.entitlements b/client/ios/VPNExtension/VPNExtensionDebug.entitlements deleted file mode 100644 index e4dc971..0000000 --- a/client/ios/VPNExtension/VPNExtensionDebug.entitlements +++ /dev/null @@ -1,24 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.developer.networking.vpn.api - - allow-vpn - - com.apple.security.app-sandbox - - com.apple.security.application-groups - - group.net.defguard.mobile - - com.apple.security.network.client - - com.apple.security.network.server - - - From 0dd18f043c16981768a7e2819cbd95f13ecb7521 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 10:57:51 +0200 Subject: [PATCH 32/44] pod reintegrate --- client/ios/Runner.xcodeproj/project.pbxproj | 96 ++++++++++----------- 1 file changed, 48 insertions(+), 48 deletions(-) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 943efa9..24c71e3 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 13AC128328D127CC1FFFF558 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B65B0851E892D176C6678C51 /* Pods_Runner.framework */; }; 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 284986622E1FAAA700BBCE47 /* NetworkExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 287C99B22E1D2FA400965674 /* NetworkExtension.framework */; }; 287C99BB2E1D2FA400965674 /* VPNExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 287C99B12E1D2FA400965674 /* VPNExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -17,8 +18,7 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - D44F9D7BC923612123D748C1 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E7D7C0FCE946457ECD05E164 /* Pods_RunnerTests.framework */; }; - FC45D341450122778AD42105 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6CD43F789A53D9955D4328EF /* Pods_Runner.framework */; }; + DACB2DBAE80BDD7607EE4779 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2EA0093455776E9C2E8600EF /* Pods_RunnerTests.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -53,10 +53,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 086F73C348A8C9390DD1F1F3 /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 0CD8742B0F10B07586941587 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 201DFD21B12CFE608884216D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 2870EE742E20076800A83A9A /* wireguard_plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = wireguard_plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2870EE782E2008B400A83A9A /* wireguard_plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = wireguard_plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 287C99B12E1D2FA400965674 /* VPNExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = VPNExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -65,16 +64,17 @@ 2886A14D2E28ED64006A7931 /* MockVPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockVPNManager.swift; sourceTree = ""; }; 28DD48A42E3B5A7B008D3F6D /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 28F6C5EA2E1FD71200C01098 /* wireguard_plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = wireguard_plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 2E3317479FFAF5BBC897DD53 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 2EA0093455776E9C2E8600EF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 4760374363F2DA4B1CE23DB5 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 6C3E6D10E91648BACCEB24E2 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - 6CD43F789A53D9955D4328EF /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 424B254BB5DDF4D176DA0E7E /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 5DA50AA8F82E69D4A7D35633 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 6E29205A5B56791C6A58AB8C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 8DA37A462F22DF4069A30F0C /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -82,8 +82,8 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - E7D7C0FCE946457ECD05E164 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - EE706D7345143032DF760E81 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + B65B0851E892D176C6678C51 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BD3CFBCA806DBFB89630EE74 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -120,7 +120,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D44F9D7BC923612123D748C1 /* Pods_RunnerTests.framework in Frameworks */, + DACB2DBAE80BDD7607EE4779 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -128,7 +128,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - FC45D341450122778AD42105 /* Pods_Runner.framework in Frameworks */, + 13AC128328D127CC1FFFF558 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -202,8 +202,8 @@ 2870EE742E20076800A83A9A /* wireguard_plugin.framework */, 28F6C5EA2E1FD71200C01098 /* wireguard_plugin.framework */, 287C99B22E1D2FA400965674 /* NetworkExtension.framework */, - 6CD43F789A53D9955D4328EF /* Pods_Runner.framework */, - E7D7C0FCE946457ECD05E164 /* Pods_RunnerTests.framework */, + B65B0851E892D176C6678C51 /* Pods_Runner.framework */, + 2EA0093455776E9C2E8600EF /* Pods_RunnerTests.framework */, ); name = Frameworks; sourceTree = ""; @@ -211,12 +211,12 @@ DE2536E81B1975928E1692FE /* Pods */ = { isa = PBXGroup; children = ( - 201DFD21B12CFE608884216D /* Pods-Runner.debug.xcconfig */, - 2E3317479FFAF5BBC897DD53 /* Pods-Runner.release.xcconfig */, - EE706D7345143032DF760E81 /* Pods-Runner.profile.xcconfig */, - 086F73C348A8C9390DD1F1F3 /* Pods-RunnerTests.debug.xcconfig */, - 6C3E6D10E91648BACCEB24E2 /* Pods-RunnerTests.release.xcconfig */, - 4760374363F2DA4B1CE23DB5 /* Pods-RunnerTests.profile.xcconfig */, + BD3CFBCA806DBFB89630EE74 /* Pods-Runner.debug.xcconfig */, + 6E29205A5B56791C6A58AB8C /* Pods-Runner.release.xcconfig */, + 5DA50AA8F82E69D4A7D35633 /* Pods-Runner.profile.xcconfig */, + 424B254BB5DDF4D176DA0E7E /* Pods-RunnerTests.debug.xcconfig */, + 8DA37A462F22DF4069A30F0C /* Pods-RunnerTests.release.xcconfig */, + 0CD8742B0F10B07586941587 /* Pods-RunnerTests.profile.xcconfig */, ); path = Pods; sourceTree = ""; @@ -248,7 +248,7 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 3CB5BDD8022927BDC0BBED95 /* [CP] Check Pods Manifest.lock */, + C42D5465F1F179E1EECF14FB /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 46C1966B05CC6B3688F00188 /* Frameworks */, @@ -267,15 +267,15 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 25F4A7C7AC197D56B91A98A9 /* [CP] Check Pods Manifest.lock */, + 12E55229B7D8012912191D7E /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 287C99BC2E1D2FA400965674 /* Embed Foundation Extensions */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - EF72AEB02D90D038422B97CE /* [CP] Embed Pods Frameworks */, - A2C99C1540BD0C64E164ABC6 /* [CP] Copy Pods Resources */, + BCC7D29F7E18EB3A195C9935 /* [CP] Embed Pods Frameworks */, + 7A28395B068B05A97BF2ECFE /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -360,7 +360,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 25F4A7C7AC197D56B91A98A9 /* [CP] Check Pods Manifest.lock */ = { + 12E55229B7D8012912191D7E /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -398,26 +398,21 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin\n"; }; - 3CB5BDD8022927BDC0BBED95 /* [CP] Check Pods Manifest.lock */ = { + 7A28395B068B05A97BF2ECFE /* [CP] Copy Pods Resources */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; + name = "[CP] Copy Pods Resources"; outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; showEnvVarsInLog = 0; }; 9740EEB61CF901F6004384FC /* Run Script */ = { @@ -435,38 +430,43 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build\n"; }; - A2C99C1540BD0C64E164ABC6 /* [CP] Copy Pods Resources */ = { + BCC7D29F7E18EB3A195C9935 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Copy Pods Resources"; + name = "[CP] Embed Pods Frameworks"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist", + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - EF72AEB02D90D038422B97CE /* [CP] Embed Pods Frameworks */ = { + C42D5465F1F179E1EECF14FB /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); - name = "[CP] Embed Pods Frameworks"; + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -787,7 +787,7 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 086F73C348A8C9390DD1F1F3 /* Pods-RunnerTests.debug.xcconfig */; + baseConfigurationReference = 424B254BB5DDF4D176DA0E7E /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; @@ -807,7 +807,7 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 6C3E6D10E91648BACCEB24E2 /* Pods-RunnerTests.release.xcconfig */; + baseConfigurationReference = 8DA37A462F22DF4069A30F0C /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; @@ -825,7 +825,7 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 4760374363F2DA4B1CE23DB5 /* Pods-RunnerTests.profile.xcconfig */; + baseConfigurationReference = 0CD8742B0F10B07586941587 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; From f96b5819b7ddd5df9c59247658d5a46590370384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 11:12:16 +0200 Subject: [PATCH 33/44] CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO --- client/ios/Runner.xcodeproj/project.pbxproj | 3 +++ 1 file changed, 3 insertions(+) diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 24c71e3..7a84cbe 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -563,6 +563,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 82GZ7KN29J; @@ -872,6 +873,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; DEVELOPMENT_TEAM = 82GZ7KN29J; @@ -930,6 +932,7 @@ CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_INJECT_BASE_ENTITLEMENTS = NO; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = 82GZ7KN29J; From aa5df25ecd46f593ce505c1aa684e9acd9d0db25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 11:53:39 +0200 Subject: [PATCH 34/44] One entitlements for Runner --- client/ios/Runner.xcodeproj/project.pbxproj | 10 ++++------ client/ios/Runner/RunnerDebug.entitlements | 22 --------------------- 2 files changed, 4 insertions(+), 28 deletions(-) delete mode 100644 client/ios/Runner/RunnerDebug.entitlements diff --git a/client/ios/Runner.xcodeproj/project.pbxproj b/client/ios/Runner.xcodeproj/project.pbxproj index 7a84cbe..39b0488 100644 --- a/client/ios/Runner.xcodeproj/project.pbxproj +++ b/client/ios/Runner.xcodeproj/project.pbxproj @@ -62,7 +62,6 @@ 287C99B22E1D2FA400965674 /* NetworkExtension.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetworkExtension.framework; path = System/Library/Frameworks/NetworkExtension.framework; sourceTree = SDKROOT; }; 287C9A272E1D43DB00965674 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; 2886A14D2E28ED64006A7931 /* MockVPNManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockVPNManager.swift; sourceTree = ""; }; - 28DD48A42E3B5A7B008D3F6D /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; 28F6C5EA2E1FD71200C01098 /* wireguard_plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = wireguard_plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 2EA0093455776E9C2E8600EF /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; @@ -181,7 +180,6 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 28DD48A42E3B5A7B008D3F6D /* RunnerDebug.entitlements */, 287C9A272E1D43DB00965674 /* Runner.entitlements */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, @@ -580,6 +578,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = NO; + REGISTER_APP_GROUPS = YES; SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -663,7 +662,6 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -718,7 +716,6 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -771,7 +768,6 @@ PRODUCT_BUNDLE_IDENTIFIER = net.defguard.mobile.VPNExtension; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; - REGISTER_APP_GROUPS = YES; SKIP_INSTALL = YES; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; @@ -897,6 +893,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + REGISTER_APP_GROUPS = YES; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -949,6 +946,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.6; MTL_ENABLE_DEBUG_INFO = NO; + REGISTER_APP_GROUPS = YES; SUPPORTED_PLATFORMS = iphoneos; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -963,7 +961,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; + CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; diff --git a/client/ios/Runner/RunnerDebug.entitlements b/client/ios/Runner/RunnerDebug.entitlements deleted file mode 100644 index 1e76d2f..0000000 --- a/client/ios/Runner/RunnerDebug.entitlements +++ /dev/null @@ -1,22 +0,0 @@ - - - - - com.apple.developer.networking.networkextension - - packet-tunnel-provider - - com.apple.developer.networking.vpn.api - - allow-vpn - - com.apple.security.app-sandbox - - com.apple.security.application-groups - - group.net.defguard.mobile - - keychain-access-groups - - - From e54c175281ff8f8648fc831afa83e47b2fa60661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adam=20Ciarcin=CC=81ski?= Date: Mon, 29 Jun 2026 12:01:26 +0200 Subject: [PATCH 35/44] Decrease verbosity --- .github/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 977ee13..9b26104 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -52,7 +52,7 @@ jobs: run: pod repo update - name: Build iOS - run: flutter build ipa -v --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} + run: flutter build ipa --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} - name: Get last commit message run: echo "COMMIT_MSG=$(git log -1 --pretty=%B)" >> $GITHUB_ENV From b710298621b6fe42608218f198660351dac3f979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= <102536422+filipslezaklab@users.noreply.github.com> Date: Thu, 2 Jul 2026 12:37:21 +0200 Subject: [PATCH 36/44] fix dev build fix platform info report to core (#195) --- .gitignore | 3 +++ client/android/app/build.gradle.kts | 5 +++++ client/android/app/proguard-rules.pro | 3 +++ client/lib/open/api.dart | 2 ++ 4 files changed, 13 insertions(+) create mode 100644 client/android/app/proguard-rules.pro diff --git a/.gitignore b/.gitignore index c5edab1..7b02be6 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ .envrc .direnv/ + +# FVM Version Cache +.fvm/ \ No newline at end of file diff --git a/client/android/app/build.gradle.kts b/client/android/app/build.gradle.kts index 4c434a4..760519f 100644 --- a/client/android/app/build.gradle.kts +++ b/client/android/app/build.gradle.kts @@ -31,12 +31,17 @@ android { buildTypes { release { // let r0adkll/sign-android-release@v1 in CI do the signing + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) } } } dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") + implementation("com.google.android.gms:play-services-cronet:18.1.1") implementation(files("../../../lib/tunnel.aar")) } diff --git a/client/android/app/proguard-rules.pro b/client/android/app/proguard-rules.pro new file mode 100644 index 0000000..1d11401 --- /dev/null +++ b/client/android/app/proguard-rules.pro @@ -0,0 +1,3 @@ +# ProGuard rules for cronet_http +-keep class io.flutter.plugins.cronet_http.** { *; } +-keep class org.chromium.net.** { *; } diff --git a/client/lib/open/api.dart b/client/lib/open/api.dart index 2f46fbb..60870d6 100644 --- a/client/lib/open/api.dart +++ b/client/lib/open/api.dart @@ -71,6 +71,7 @@ class _ProxyApi { if (Platform.isAndroid) { final android = await deviceInfo.androidInfo; platformInfo = ClientPlatformInfo( + osFamily: 'android', osType: 'Android', version: android.version.release, codename: android.version.codename, @@ -80,6 +81,7 @@ class _ProxyApi { } else if (Platform.isIOS) { final ios = await deviceInfo.iosInfo; platformInfo = ClientPlatformInfo( + osFamily: 'ios', osType: 'iOS', version: ios.systemVersion, architecture: 'arm64', From 8eac50610bda411b9ae602e17bead305c346540e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20=C5=9Al=C4=99zak?= <102536422+filipslezaklab@users.noreply.github.com> Date: Fri, 3 Jul 2026 11:03:11 +0200 Subject: [PATCH 37/44] Proxy request error (#196) * human readable errors during oidc flow * formatting --- .../mfa/openid_mfa_waiting_screen.dart | 18 +++++-- .../lib/open/screens/mfa/mfa_code_screen.dart | 9 +++- client/lib/utils/error_handler.dart | 49 +++++++++++++++++++ 3 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 client/lib/utils/error_handler.dart diff --git a/client/lib/enterprise/screens/mfa/openid_mfa_waiting_screen.dart b/client/lib/enterprise/screens/mfa/openid_mfa_waiting_screen.dart index 6d12012..3970b96 100644 --- a/client/lib/enterprise/screens/mfa/openid_mfa_waiting_screen.dart +++ b/client/lib/enterprise/screens/mfa/openid_mfa_waiting_screen.dart @@ -13,6 +13,7 @@ import 'package:mobile/theme/text.dart'; import '../../../../../logging.dart'; import '../../../open/services/snackbar_service.dart'; +import '../../../utils/error_handler.dart'; class OpenIdMfaWaitingScreenData { final String proxyUrl; @@ -51,8 +52,18 @@ class OpenIdMfaWaitingScreen extends HookConsumerWidget { final response = await proxyApi.finishMfa(uri, request); return response; } on DioException catch (e) { - if (e.response?.statusCode == 428) { - talker.debug("User did not complete openid browser login, waiting"); + final isNetworkError = + e.type == DioExceptionType.connectionError || + e.type == DioExceptionType.connectionTimeout || + (e.error?.toString().contains("-1005") ?? false) || + (e.message?.contains("-1005") ?? false); + + if (e.response?.statusCode == 428 || isNetworkError) { + if (isNetworkError) { + talker.warning("Network error during MFA polling, retrying: $e"); + } else { + talker.debug("User did not complete openid browser login, waiting"); + } await Future.delayed(Duration(seconds: 2)); } else { rethrow; @@ -83,8 +94,9 @@ class OpenIdMfaWaitingScreen extends HookConsumerWidget { }) .catchError((error) { talker.error("OpenID MFA polling error: $error"); + final message = ErrorHandler.getHumanReadableError(error); SnackbarService.show( - "Error: $error", + message, textColor: DgColor.textAlert, dismissable: true, ); diff --git a/client/lib/open/screens/mfa/mfa_code_screen.dart b/client/lib/open/screens/mfa/mfa_code_screen.dart index 1a71e0f..5a01c1a 100644 --- a/client/lib/open/screens/mfa/mfa_code_screen.dart +++ b/client/lib/open/screens/mfa/mfa_code_screen.dart @@ -11,6 +11,7 @@ import 'package:mobile/open/widgets/navigation/dg_scaffold.dart'; import 'package:mobile/theme/color.dart'; import 'package:mobile/theme/spacing.dart'; import 'package:mobile/theme/text.dart'; +import 'package:mobile/utils/error_handler.dart'; import 'package:mobile/utils/screen_padding.dart'; import '../../../../../data/db/enums.dart'; @@ -151,9 +152,15 @@ class _CodeForm extends HookConsumerWidget { if (e.response?.statusCode == 401) { codeInvalid.value = true; formKey.currentState?.validate(); + } else { + SnackbarService.showError( + ErrorHandler.getHumanReadableError(e), + ); } } catch (e) { - SnackbarService.showError("Error: $e"); + SnackbarService.showError( + ErrorHandler.getHumanReadableError(e), + ); } finally { isLoading.value = false; } diff --git a/client/lib/utils/error_handler.dart b/client/lib/utils/error_handler.dart new file mode 100644 index 0000000..6738769 --- /dev/null +++ b/client/lib/utils/error_handler.dart @@ -0,0 +1,49 @@ +import 'package:dio/dio.dart'; + +class ErrorHandler { + static String getHumanReadableError(Object e) { + if (e is DioException) { + switch (e.type) { + case DioExceptionType.connectionTimeout: + case DioExceptionType.sendTimeout: + case DioExceptionType.receiveTimeout: + return "Connection timed out. Please check your internet connection."; + case DioExceptionType.connectionError: + return "Unable to connect to the server. Please check your internet connection."; + case DioExceptionType.badResponse: + if (e.response?.statusCode == 401) { + return "Unauthorized. Please check your credentials."; + } + if (e.response?.statusCode == 403) { + return "Access forbidden."; + } + if (e.response?.statusCode == 404) { + return "Service not found."; + } + if (e.response?.statusCode != null && + e.response!.statusCode! >= 500) { + return "Server error. Please try again later."; + } + return "Server returned an error: ${e.response?.statusCode}"; + case DioExceptionType.cancel: + return "Request was cancelled."; + default: + // Handle specific iOS error -1005 (Network connection lost) + final errorString = e.error?.toString() ?? ""; + final messageString = e.message ?? ""; + if (errorString.contains("-1005") || + messageString.contains("-1005")) { + return "Network connection lost. Please try again."; + } + + return "An unexpected network error occurred."; + } + } + + final s = e.toString(); + if (s.startsWith("Exception: ")) { + return s.substring(11); + } + return s; + } +} From bebbfd8b729068dc34fc2e8937ffa9281c381cbd Mon Sep 17 00:00:00 2001 From: Maciek <19913370+wojcik91@users.noreply.github.com> Date: Mon, 6 Jul 2026 10:16:19 +0200 Subject: [PATCH 38/44] chore: add Renovate config --- renovate.json | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..75ec2ad --- /dev/null +++ b/renovate.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>DefGuard/ci-workflows//renovate/default.json"], + "baseBranches": ["dev", "main"] +} From a47939e526e164c5e683182ba10ad8ba6876b90d Mon Sep 17 00:00:00 2001 From: Adam Date: Fri, 10 Jul 2026 11:43:28 +0200 Subject: [PATCH 39/44] iOS: fix split DNS (#240) --- .../VPNExtension/TunnelConfiguration.swift | 11 +++++++--- client/pubspec.lock | 20 +++++++++---------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/client/ios/VPNExtension/TunnelConfiguration.swift b/client/ios/VPNExtension/TunnelConfiguration.swift index ca4f76e..619a17c 100644 --- a/client/ios/VPNExtension/TunnelConfiguration.swift +++ b/client/ios/VPNExtension/TunnelConfiguration.swift @@ -71,10 +71,15 @@ final class TunnelConfiguration: Codable { networkSettings.tunnelOverheadBytes = 80 let dnsSettings = NEDNSSettings(servers: dns) - dnsSettings.searchDomains = dnsSearch if !dns.isEmpty { - // Make all DNS queries go through the tunnel. - dnsSettings.matchDomains = [""] + if dnsSearch.isEmpty { + // Resolve all DNS queries. + dnsSettings.matchDomains = [""] + } else { + // Split DNS queries. + dnsSettings.matchDomains = dnsSearch + dnsSettings.searchDomains = dnsSearch + } } networkSettings.dnsSettings = dnsSettings diff --git a/client/pubspec.lock b/client/pubspec.lock index 9041cb5..65aceba 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -125,10 +125,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: bf05f6e12cfea92d3c09308d7bcdab1906cd8a179b023269eed00c071004b957 + sha256: fd754058c342243718d5171a95f352cfc9fcf0cba8cfa26df67cb13a5836db78 url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.1.2" build_resolvers: dependency: transitive description: @@ -293,10 +293,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" + sha256: "92c9c43c383bfa1c32079d3bc492d55d6d4318044b7b47edaff8971cbb555c51" url: "https://pub.dev" source: hosted - version: "0.3.5+2" + version: "0.3.5+4" crypto: dependency: transitive description: @@ -397,10 +397,10 @@ packages: dependency: "direct main" description: name: dio - sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 url: "https://pub.dev" source: hosted - version: "5.9.2" + version: "5.10.0" dio_cookie_manager: dependency: "direct main" description: @@ -413,10 +413,10 @@ packages: dependency: transitive description: name: dio_web_adapter - sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.2.0" drift: dependency: "direct main" description: @@ -972,10 +972,10 @@ packages: dependency: transitive description: name: native_toolchain_c - sha256: f59351d28f49520cd3a74eb1f41c5f19ae15e53c65a3231d14af672e46510a96 + sha256: f9c168717100ae6d9fee9ffb0be379bf1f8b26b0f6bcbd4fdddcd931993a6a72 url: "https://pub.dev" source: hosted - version: "0.19.1" + version: "0.19.2" node_preamble: dependency: transitive description: From 4f60fa15f47e6a0acb5c3e082b0623a60493bd2f Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 13 Jul 2026 11:22:44 +0200 Subject: [PATCH 40/44] New iOS build (#244) --- .github/workflows/build.yaml | 49 ++++++++++++++++++---------- .github/workflows/lint-and-test.yaml | 9 ++--- .github/workflows/sbom.yaml | 2 +- 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 9b26104..d2ce503 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -15,13 +15,13 @@ on: jobs: build-ios: - runs-on: [self-hosted, macOS] + runs-on: [self-hosted, macOS, native] defaults: run: working-directory: ./client steps: - name: Checkout main repo - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" @@ -40,7 +40,7 @@ jobs: run: flutter pub get - name: Unlock Keychain - run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain + run: security unlock-keychain -p "${{ secrets.BUILD_KEYCHAIN_PASSWORD }}" build.keychain - name: Create BoringTun directory run: mkdir -p ios/VPNExtension/BoringTun @@ -54,24 +54,37 @@ jobs: - name: Build iOS run: flutter build ipa --release --obfuscate --split-debug-info=build/debug-info --build-number=${{ github.run_number }} - - name: Get last commit message - run: echo "COMMIT_MSG=$(git log -1 --pretty=%B)" >> $GITHUB_ENV - - name: Upload app to TestFlight - uses: apple-actions/upload-testflight-build@v5 # Mobile applications are published to the App Store manually, with release tags applied # post-publication. To avoid redundant uploads, this step executes only for non-tagged # builds, ensuring tagged releases are distributed exclusively to GitHub. - if: "!startsWith(github.ref, 'refs/tags/')" - with: - app-path: "client/build/ios/ipa/Defguard.ipa" - issuer-id: ${{ secrets.API_ISSUER_ID }} - api-key-id: ${{ secrets.ASC_API_KEY_ID }} - api-private-key: ${{ secrets.PRIVATE_KEY_CONTENTS }} - release-notes: ${{ env.COMMIT_MSG }} + run: | + xcrun altool --api-key ${{ secrets.ASC_API_KEY_ID }} \ + --api-issuer ${{ secrets.API_ISSUER_ID }} \ + --upload-app --platform ios --file build/ios/ipa/Defguard.ipa --wait + + - name: Upload What's New + env: + APP_ID: "6748068630" + run: | + UPLOAD_DIR=$(mktemp -d) + VERSION=$(grep '^version:' pubspec.yaml | cut -d ' ' -f 2 | cut -d '+' -f 1) + mkdir -p "${UPLOAD_DIR}/beta-${APP_ID}/upload/IOS" + git log -1 --pretty='"whatsNew" = "%B";' > "${UPLOAD_DIR}/beta-${APP_ID}/upload/IOS/en-GB.txt" + RETRIES=0 + until [ ${RETRIES} -gt 6 ] + do + xcrun altool --api-key ${{ secrets.ASC_API_KEY_ID }} --api-issuer ${{ secrets.API_ISSUER_ID }} \ + --apple-id ${APP_ID} --bundle-version ${{ github.run_number }} \--bundle-short-version-string ${VERSION} \ + --platform macos --beta-app-store-text "${UPLOAD_DIR}" --upload && break + echo "Waiting for app ${APP_ID} build ${{ github.run_number }} version ${VERSION}" + sleep 10 + ((RETRIES++)) + done + rm -f -r "${UPLOAD_DIR}" build-android: - runs-on: [self-hosted, macOS] + runs-on: [self-hosted, macOS, native] env: ANDROID_HOME: /Users/admin/Library/Android/sdk ANDROID_SDK_ROOT: /Users/admin/Library/Android/sdk @@ -79,7 +92,7 @@ jobs: run: working-directory: ./client steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: submodules: "recursive" @@ -141,7 +154,7 @@ jobs: retention-days: 2 build-android-apk: - runs-on: [self-hosted, macOS] + runs-on: [self-hosted, macOS, native] env: ANDROID_HOME: /Users/admin/Library/Android/sdk ANDROID_SDK_ROOT: /Users/admin/Library/Android/sdk @@ -149,7 +162,7 @@ jobs: run: working-directory: ./client steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: submodules: "recursive" diff --git a/.github/workflows/lint-and-test.yaml b/.github/workflows/lint-and-test.yaml index 31bb7ce..48a2de8 100644 --- a/.github/workflows/lint-and-test.yaml +++ b/.github/workflows/lint-and-test.yaml @@ -29,7 +29,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: submodules: "recursive" @@ -57,7 +57,7 @@ jobs: # test-ios: # name: Run iOS tests - # runs-on: [self-hosted, macOS] + # runs-on: [self-hosted, macOS, native] # needs: lint # defaults: # run: @@ -65,7 +65,7 @@ jobs: # steps: # - name: Checkout - # uses: actions/checkout@v6 + # uses: actions/checkout@v7 # with: # submodules: "recursive" @@ -90,8 +90,5 @@ jobs: # # - name: build project # # run: flutter build ios - # - name: Unlock Keychain - # run: security -v unlock-keychain -p "${{ secrets.KEYCHAIN_PASSWORD }}" /Users/admin/Library/Keychains/login.keychain - # - name: run plugin tests # run: xcodebuild test -workspace Runner.xcworkspace -scheme Runner diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index ec8c5cf..425331a 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -29,7 +29,7 @@ jobs: echo "VERSION=$VERSION" >> $GITHUB_OUTPUT - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v7 with: ref: ${{ steps.vars.outputs.TAG_NAME }} submodules: "recursive" From 9a33f004154f2715276e5cc389e8e4993c8c318c Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 20 Jul 2026 15:29:41 +0200 Subject: [PATCH 41/44] KeepAlive fix for iOS (#248) --- client/ios/VPNExtension/Adapter.swift | 4 ++-- client/ios/boringtun | 2 +- client/pubspec.lock | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/ios/VPNExtension/Adapter.swift b/client/ios/VPNExtension/Adapter.swift index bd36500..103c163 100644 --- a/client/ios/VPNExtension/Adapter.swift +++ b/client/ios/VPNExtension/Adapter.swift @@ -231,8 +231,8 @@ enum State { log.info("Creating keep-alive timer") let timer = DispatchSource.makeTimerSource(queue: ioQueue) timer.schedule( - deadline: .now() + .milliseconds(250), - repeating: .milliseconds(250), + deadline: .now() + .seconds(1), + repeating: .seconds(1), leeway: .milliseconds(25) ) timer.setEventHandler { [weak self] in diff --git a/client/ios/boringtun b/client/ios/boringtun index b7c2922..bb10112 160000 --- a/client/ios/boringtun +++ b/client/ios/boringtun @@ -1 +1 @@ -Subproject commit b7c29222f9881165e514088cc8f6c6463e0aa452 +Subproject commit bb1011289f31ad7544f6dfb1fcaac74e608f7911 diff --git a/client/pubspec.lock b/client/pubspec.lock index 65aceba..c022cc4 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -956,10 +956,10 @@ packages: dependency: "direct main" description: name: mobile_scanner - sha256: c92c26bf2231695b6d3477c8dcf435f51e28f87b1745966b1fe4c47a286171ce + sha256: ce3f059ebd6dbfab7292bba0e893e354b46730636820d3c9ef69005ce2d55bce url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.4.0" native_dio_adapter: dependency: "direct main" description: @@ -1641,10 +1641,10 @@ packages: dependency: "direct main" description: name: uuid - sha256: "1fef9e8e11e2991bb773070d4656b7bd5d850967a2456cfc83cf47925ba79489" + sha256: "9b129329f58692f6e6578329498a8fe9fbe98f090beb764ffbb8ee2eadd01dcd" url: "https://pub.dev" source: hosted - version: "4.5.3" + version: "4.6.0" vector_graphics: dependency: transitive description: From 30e8047d366412e9649416f1846fc5a81276de85 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 12 Aug 2026 07:58:09 +0200 Subject: [PATCH 42/44] add polling token to posture request --- client/lib/enterprise/postures.dart | 2 ++ client/lib/enterprise/postures.g.dart | 3 +++ .../lib/open/screens/instance/services/tunnel_service.dart | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/client/lib/enterprise/postures.dart b/client/lib/enterprise/postures.dart index f40d3e0..d4d0447 100644 --- a/client/lib/enterprise/postures.dart +++ b/client/lib/enterprise/postures.dart @@ -108,11 +108,13 @@ class PostureConnectRequest { final int locationId; final String pubkey; final DevicePostureData devicePostureData; + final String token; const PostureConnectRequest({ required this.locationId, required this.pubkey, required this.devicePostureData, + required this.token, }); factory PostureConnectRequest.fromJson(Map json) => diff --git a/client/lib/enterprise/postures.g.dart b/client/lib/enterprise/postures.g.dart index b2903e4..974c789 100644 --- a/client/lib/enterprise/postures.g.dart +++ b/client/lib/enterprise/postures.g.dart @@ -159,6 +159,7 @@ PostureConnectRequest _$PostureConnectRequestFromJson( 'device_posture_data', (v) => DevicePostureData.fromJson(v as Map), ), + token: $checkedConvert('token', (v) => v as String), ); return val; }, @@ -172,6 +173,7 @@ const _$PostureConnectRequestFieldMap = { 'locationId': 'location_id', 'pubkey': 'pubkey', 'devicePostureData': 'device_posture_data', + 'token': 'token', }; Map _$PostureConnectRequestToJson( @@ -180,6 +182,7 @@ Map _$PostureConnectRequestToJson( 'location_id': instance.locationId, 'pubkey': instance.pubkey, 'device_posture_data': instance.devicePostureData, + 'token': instance.token, }; PostureConnectResponse _$PostureConnectResponseFromJson( diff --git a/client/lib/open/screens/instance/services/tunnel_service.dart b/client/lib/open/screens/instance/services/tunnel_service.dart index 664eef1..62ab24d 100644 --- a/client/lib/open/screens/instance/services/tunnel_service.dart +++ b/client/lib/open/screens/instance/services/tunnel_service.dart @@ -125,6 +125,7 @@ class TunnelService { navigator: navigator, proxyUrl: instance.proxyUrl, payload: payload, + pollingToken: instance.poolingToken, ); if (presharedKey == null) { return; @@ -149,6 +150,7 @@ class TunnelService { required NavigatorState navigator, required String proxyUrl, required PluginConnectPayload payload, + required String pollingToken, }) async { final messenger = ScaffoldMessenger.of(navigator.context); try { @@ -156,6 +158,7 @@ class TunnelService { proxyUrl, payload.devicePublicKey, payload.networkId, + pollingToken, ); } on PostureCheckException catch (e) { talker.error('Posture check failed', e); @@ -351,12 +354,14 @@ class TunnelService { String url, String pubkey, int networkId, + String pollingToken, ) async { talker.debug('Starting posture check for networkId: $networkId'); final request = PostureConnectRequest( locationId: networkId, pubkey: pubkey, devicePostureData: await getPosture(), + token: pollingToken, ); final response = await proxyApi.postureConnect(Uri.parse(url), request); From f18e4858a799d63469d26d768acd2222db240463 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 12 Aug 2026 08:58:15 +0200 Subject: [PATCH 43/44] update dependencies --- .gitignore | 2 +- client/pubspec.lock | 68 +++++++++++++++++++++++++-------------------- 2 files changed, 39 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 7b02be6..bf7e962 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,4 @@ .direnv/ # FVM Version Cache -.fvm/ \ No newline at end of file +.fvm/ diff --git a/client/pubspec.lock b/client/pubspec.lock index c022cc4..05b6566 100644 --- a/client/pubspec.lock +++ b/client/pubspec.lock @@ -173,10 +173,10 @@ packages: dependency: transitive description: name: built_value - sha256: "34e4067d30ce212937df995f03b69992eea683539ceeac7f679a1f1eba055b56" + sha256: "31b24be6615ec7fcf70b3aa5a7469fe35826485e639a16dd7eb83ba30e4cc6a8" url: "https://pub.dev" source: hosted - version: "8.12.6" + version: "8.12.7" characters: dependency: transitive description: @@ -397,26 +397,26 @@ packages: dependency: "direct main" description: name: dio - sha256: ea2bad3c89a27635ce2d85cce4d6b199da49a5a48ec77b03e45b65a3b90922b0 + sha256: "0df44ebba85e503958eb75d07eedd3c86275a58c1d3eda2f2ce8f0a2c3abbb3c" url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.11.0" dio_cookie_manager: dependency: "direct main" description: name: dio_cookie_manager - sha256: "0db1a7b997a0455e488ac35744c68eed3f2a4280d3ab531835a65641b0a08744" + sha256: "4ed4669cacb11931517c1158876a2189f19386674b9dab498abcca063dbe4c61" url: "https://pub.dev" source: hosted - version: "3.4.0" + version: "3.5.0" dio_web_adapter: dependency: transitive description: name: dio_web_adapter - sha256: dd58dc3861eb36edb13b217efc006a1c21e5bbc341de8c229b85634fa5e362e4 + sha256: "0786d0b7295a373de356fc0af4f6f1d0ab2844ed31b19dfc5e7556b70e24212c" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" drift: dependency: "direct main" description: @@ -804,18 +804,26 @@ packages: dependency: transitive description: name: jni - sha256: c2230682d5bc2362c1c9e8d3c7f406d9cbba23ab3f2e203a025dd47e0fb2e68f + sha256: f038e58b4dc2c9037f50e233175086337e0b305e356d28211bf55f21c504cbd3 url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.3" jni_flutter: dependency: transitive description: name: jni_flutter - sha256: "8b59e590786050b1cd866677dddaf76b1ade5e7bc751abe04b86e84d379d3ba6" + sha256: "7b717011ea40d04fd47c2731d3d1d36eb99eba3435c2753d62489e8c3c9991d5" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" + jni_util: + dependency: transitive + description: + name: jni_util + sha256: "1ba86da04a5f2bf18fde2edb235587e70c5b0fc5bd4ba955f46b00942c3fc35f" + url: "https://pub.dev" + source: hosted + version: "1.0.0" js: dependency: transitive description: @@ -964,10 +972,10 @@ packages: dependency: "direct main" description: name: native_dio_adapter - sha256: "89a84d8936a108c206e481b8da090422abb1351febac4956cd4a8113627ebb5e" + sha256: "7ca3d04c76095a02c3b0d2e6f3c90e7781373671f7aacad34b81f31074a7369b" url: "https://pub.dev" source: hosted - version: "1.6.0" + version: "1.8.0" native_toolchain_c: dependency: transitive description: @@ -988,10 +996,10 @@ packages: dependency: transitive description: name: objective_c - sha256: "6cb691c686fa2838c6deb34980d426145c2a5d537491cb83d463c33cdbc726ed" + sha256: b7fb95a6d9a4f009edd63dc5ac69f07420b23a16161c6dd8660290b59c602e8e url: "https://pub.dev" source: hosted - version: "9.4.1" + version: "9.5.0" package_config: dependency: transitive description: @@ -1100,34 +1108,34 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: "79dfa1df734798aa3cfdad166d3a3698c206d8813de13516ea1071b5d7e2f420" + sha256: f49cb15a064ea9d974fc7fbb302099353b7b170d07284e86e264561579e5bcf8 url: "https://pub.dev" source: hosted - version: "9.4.10" + version: "9.6.1" permission_handler_html: dependency: transitive description: name: permission_handler_html - sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + sha256: "6ea98b3f17f60d3b527f2647ed2ab4dc0f6bfe25b22cb1c363f5d8f62252f6ac" url: "https://pub.dev" source: hosted - version: "0.1.3+5" + version: "0.1.4+1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + sha256: a5c8a97ecf5616112a5b16d4b8e9ec0e5ae90ef63ac69c0d7b8ae240be760b23 url: "https://pub.dev" source: hosted - version: "4.3.0" + version: "4.4.0" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + sha256: caeae01858a0a7d2df67a445ac98e1ad95e55a0e77c73044f4e9b1c8c2289cbd url: "https://pub.dev" source: hosted - version: "0.2.1" + version: "0.2.2" petitparser: dependency: transitive description: @@ -1164,10 +1172,10 @@ packages: dependency: transitive description: name: posix - sha256: "185ef7606574f789b40f289c233efa52e96dead518aed988e040a10737febb07" + sha256: bc1bad54ad2b735816e31f8d4600cfde6c7839975085ddfbca48b6c9f7c4044e url: "https://pub.dev" source: hosted - version: "6.5.0" + version: "6.5.2" protobuf: dependency: "direct main" description: @@ -1649,10 +1657,10 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "2306c03da2ba81724afeb589c351ebbc0aa7d86005925be8f8735856dbe5e42d" + sha256: "9d0e3b9cb16542ad660daee871e726a10d13a93b7b5391677c3160e8f5e83935" url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" vector_graphics_codec: dependency: transitive description: @@ -1665,10 +1673,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "142a9146f447d15b10bdc00e21d5f4d83e5b32bb5f8f8f5a04c75311344923a3" + sha256: "4dca4feb77dc3ec7f6e27e49c53241eb8217f55e4f9b12599a27f8903bca5682" url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "1.3.0" vector_math: dependency: transitive description: From 0eeb5184964e09fd97cf6d05ce846711a185faa5 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Wed, 12 Aug 2026 09:41:51 +0200 Subject: [PATCH 44/44] fix CI flutter jdk --- .github/workflows/build.yaml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d2ce503..de22c52 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -108,6 +108,9 @@ jobs: channel: stable flutter-version: 3.38.10 + - name: Configure Flutter JDK + run: flutter config --jdk-dir "$JAVA_HOME" + - name: Accept licenses run: yes | flutter doctor --android-licenses @@ -178,6 +181,9 @@ jobs: channel: stable flutter-version: 3.38.10 + - name: Configure Flutter JDK + run: flutter config --jdk-dir "$JAVA_HOME" + - name: Install Android SDK components run: | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --install 'build-tools;29.0.3'