From 2d68495f86a6d0943e1abe42abfa133874500593 Mon Sep 17 00:00:00 2001 From: Jacek Chmielewski Date: Mon, 25 May 2026 11:26:39 +0200 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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,