From db1a8b6f6731a8a4bd7de953e540e6908b9fc36e Mon Sep 17 00:00:00 2001 From: NaiJi Date: Wed, 8 Nov 2023 18:31:28 +0400 Subject: [PATCH] refactor: Move deSEC DNS conversion into DesecDnsRecord model - Get rid of desired dns records hardcore for deSEC --- lib/logic/models/json/desec_dns_info.dart | 20 ++ lib/logic/providers/dns_providers/desec.dart | 235 ++++-------------- .../providers/dns_providers/dns_provider.dart | 10 +- lib/utils/network_utils.dart | 46 ++++ 4 files changed, 111 insertions(+), 200 deletions(-) diff --git a/lib/logic/models/json/desec_dns_info.dart b/lib/logic/models/json/desec_dns_info.dart index e6023da7..4709f86c 100644 --- a/lib/logic/models/json/desec_dns_info.dart +++ b/lib/logic/models/json/desec_dns_info.dart @@ -1,4 +1,5 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:selfprivacy/logic/models/json/dns_records.dart'; part 'desec_dns_info.g.dart'; @@ -28,6 +29,25 @@ class DesecDomain { /// https://desec.readthedocs.io/en/latest/dns/rrsets.html#retrieving-and-creating-dns-records @JsonSerializable() class DesecDnsRecord { + factory DesecDnsRecord.fromDnsRecord(final DnsRecord record) { + final String type = record.type; + String content = record.content ?? ''; + String name = record.name ?? ''; + if (type == 'MX') { + name = ''; + content = '10 $content'; + } + if (type == 'TXT' && content.isNotEmpty && !content.startsWith('"')) { + content = '"$content"'; + } + + return DesecDnsRecord( + subname: name, + type: type, + ttl: record.ttl, + records: [content], + ); + } DesecDnsRecord({ required this.subname, required this.type, diff --git a/lib/logic/providers/dns_providers/desec.dart b/lib/logic/providers/dns_providers/desec.dart index a5e3e8d7..35364b26 100644 --- a/lib/logic/providers/dns_providers/desec.dart +++ b/lib/logic/providers/dns_providers/desec.dart @@ -4,6 +4,7 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/desec_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart'; +import 'package:selfprivacy/utils/network_utils.dart'; class ApiAdapter { ApiAdapter({final bool isWithToken = true}) @@ -75,21 +76,14 @@ class DesecDnsProvider extends DnsProvider { required final ServerDomain domain, final String? ip4, }) async { - final List listDnsRecords = projectDnsRecords( + final List listDnsRecords = getProjectDnsRecords( domain.domainName, ip4, ); final List bulkRecords = []; for (final DnsRecord record in listDnsRecords) { - bulkRecords.add( - DesecDnsRecord( - subname: record.name ?? '', - type: record.type, - ttl: record.ttl, - records: [extractContent(record) ?? ''], - ), - ); + bulkRecords.add(DesecDnsRecord.fromDnsRecord(record)); } return _adapter.api().createMultipleDnsRecords( @@ -103,18 +97,19 @@ class DesecDnsProvider extends DnsProvider { required final ServerDomain domain, final String? ip4, }) async { - final List listDnsRecords = projectDnsRecords( + final List listDnsRecords = getProjectDnsRecords( domain.domainName, ip4, ); final List bulkRecords = []; for (final DnsRecord record in listDnsRecords) { + final desecRecord = DesecDnsRecord.fromDnsRecord(record); bulkRecords.add( DesecDnsRecord( - subname: record.name ?? '', - type: record.type, - ttl: record.ttl, + subname: desecRecord.subname, + type: desecRecord.type, + ttl: desecRecord.ttl, records: [], ), ); @@ -181,14 +176,7 @@ class DesecDnsProvider extends DnsProvider { ) async { final result = await _adapter.api().createMultipleDnsRecords( domainName: domain.domainName, - records: [ - DesecDnsRecord( - subname: record.name ?? '', - type: record.type, - ttl: record.ttl, - records: [extractContent(record) ?? ''], - ), - ], + records: [DesecDnsRecord.fromDnsRecord(record)], ); return GenericResult( @@ -197,22 +185,14 @@ class DesecDnsProvider extends DnsProvider { ); } - String? extractContent(final DnsRecord record) { - String? content = record.content; - if (record.type == 'TXT' && content != null && !content.startsWith('"')) { - content = '"$content"'; - } - - return content; - } - @override Future>> validateDnsRecords( final ServerDomain domain, final String ip4, final String dkimPublicKey, + final List pendingDnsRecords, ) async { - final result = await getDnsRecords(domain: domain); + final result = await _adapter.api().getDnsRecords(domain.domainName); if (result.data.isEmpty || !result.success) { return GenericResult( success: result.success, @@ -225,45 +205,47 @@ class DesecDnsProvider extends DnsProvider { final records = result.data; final List foundRecords = []; try { - final List desiredRecords = - getDesiredDnsRecords(domain.domainName, ip4, dkimPublicKey); - for (final DesiredDnsRecord record in desiredRecords) { - if (record.description == 'record.dkim') { - final DnsRecord foundRecord = records.firstWhere( + for (final DnsRecord pendingDnsRecord in pendingDnsRecords) { + final record = DesecDnsRecord.fromDnsRecord(pendingDnsRecord); + if (record.subname == 'selector._domainkey') { + final DesecDnsRecord foundRecord = records.firstWhere( (final r) => - ('${r.name}.${domain.domainName}' == record.name) && + ('${r.subname}.${domain.domainName}' == record.subname) && r.type == record.type, - orElse: () => DnsRecord( - name: record.name, + orElse: () => DesecDnsRecord( + subname: record.subname, type: record.type, - content: '', - ttl: 800, - proxied: false, + records: [], + ttl: record.ttl, + ), + ); + final desecRecords = foundRecord.records; + final content = desecRecords.isEmpty ? '' : desecRecords[0]; + final String foundContent = content.replaceAll(RegExp(r'\s+'), ''); + final String desiredContent = + record.records[0].replaceAll(RegExp(r'\s+'), ''); + foundRecords.add( + DesiredDnsRecord( + name: record.subname, + content: record.records[0], + isSatisfied: foundContent == desiredContent, ), ); - // remove all spaces and tabulators from - // the foundRecord.content and the record.content - // to compare them - final String? foundContent = - foundRecord.content?.replaceAll(RegExp(r'\s+'), ''); - final String content = record.content.replaceAll(RegExp(r'\s+'), ''); - if (foundContent == content) { - foundRecords.add(record.copyWith(isSatisfied: true)); - } else { - foundRecords.add(record.copyWith(isSatisfied: false)); - } } else { - if (records.any( + final foundMatch = records.any( (final r) => - ('${r.name}.${domain.domainName}' == record.name || - record.name == '') && + ('${r.subname}.${domain.domainName}' == record.subname || + record.subname == '') && r.type == record.type && - r.content == record.content, - )) { - foundRecords.add(record.copyWith(isSatisfied: true)); - } else { - foundRecords.add(record.copyWith(isSatisfied: false)); - } + r.records[0] == record.records[0], + ); + foundRecords.add( + DesiredDnsRecord( + name: record.subname, + content: record.records[0], + isSatisfied: foundMatch, + ), + ); } } } catch (e) { @@ -279,133 +261,4 @@ class DesecDnsProvider extends DnsProvider { success: true, ); } - - List projectDnsRecords( - final String? domainName, - final String? ip4, - ) { - final DnsRecord domainA = DnsRecord(type: 'A', name: '', content: ip4); - - final DnsRecord mx = - DnsRecord(type: 'MX', name: '', content: '10 $domainName.'); - final DnsRecord apiA = DnsRecord(type: 'A', name: 'api', content: ip4); - final DnsRecord cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4); - final DnsRecord gitA = DnsRecord(type: 'A', name: 'git', content: ip4); - final DnsRecord meetA = DnsRecord(type: 'A', name: 'meet', content: ip4); - final DnsRecord passwordA = - DnsRecord(type: 'A', name: 'password', content: ip4); - final DnsRecord socialA = - DnsRecord(type: 'A', name: 'social', content: ip4); - final DnsRecord vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4); - - final DnsRecord txt1 = DnsRecord( - type: 'TXT', - name: '_dmarc', - content: '"v=DMARC1; p=none"', - ttl: 18000, - ); - - final DnsRecord txt2 = DnsRecord( - type: 'TXT', - name: '', - content: '"v=spf1 a mx ip4:$ip4 -all"', - ttl: 18000, - ); - - return [ - domainA, - apiA, - cloudA, - gitA, - meetA, - passwordA, - socialA, - mx, - txt1, - txt2, - vpn - ]; - } - - @override - List getDesiredDnsRecords( - final String? domainName, - final String? ip4, - final String? dkimPublicKey, - ) { - if (domainName == null || ip4 == null) { - return []; - } - return [ - DesiredDnsRecord( - name: '', - content: ip4, - description: 'record.root', - ), - DesiredDnsRecord( - name: 'api.$domainName', - content: ip4, - description: 'record.api', - ), - DesiredDnsRecord( - name: 'cloud.$domainName', - content: ip4, - description: 'record.cloud', - ), - DesiredDnsRecord( - name: 'git.$domainName', - content: ip4, - description: 'record.git', - ), - DesiredDnsRecord( - name: 'meet.$domainName', - content: ip4, - description: 'record.meet', - ), - DesiredDnsRecord( - name: 'social.$domainName', - content: ip4, - description: 'record.social', - ), - DesiredDnsRecord( - name: 'password.$domainName', - content: ip4, - description: 'record.password', - ), - DesiredDnsRecord( - name: 'vpn.$domainName', - content: ip4, - description: 'record.vpn', - ), - DesiredDnsRecord( - name: '', - content: '10 $domainName.', - description: 'record.mx', - type: 'MX', - category: DnsRecordsCategory.email, - ), - DesiredDnsRecord( - name: '_dmarc.$domainName', - content: '"v=DMARC1; p=none"', - description: 'record.dmarc', - type: 'TXT', - category: DnsRecordsCategory.email, - ), - DesiredDnsRecord( - name: '', - content: '"v=spf1 a mx ip4:$ip4 -all"', - description: 'record.spf', - type: 'TXT', - category: DnsRecordsCategory.email, - ), - if (dkimPublicKey != null) - DesiredDnsRecord( - name: 'selector._domainkey.$domainName', - content: '"$dkimPublicKey"', - description: 'record.dkim', - type: 'TXT', - category: DnsRecordsCategory.email, - ), - ]; - } } diff --git a/lib/logic/providers/dns_providers/dns_provider.dart b/lib/logic/providers/dns_providers/dns_provider.dart index 647e84ea..cd5c39f3 100644 --- a/lib/logic/providers/dns_providers/dns_provider.dart +++ b/lib/logic/providers/dns_providers/dns_provider.dart @@ -63,14 +63,6 @@ abstract class DnsProvider { final ServerDomain domain, final String ip4, final String dkimPublicKey, - ); - - /// Will return list of [DesiredDnsRecord] objects, which represent - /// samples of perfect DNS records we need to know about in order to launch - /// SelfPrivacy application correctly. - List getDesiredDnsRecords( - final String? domainName, - final String? ip4, - final String? dkimPublicKey, + final List pendingDnsRecords, ); } diff --git a/lib/utils/network_utils.dart b/lib/utils/network_utils.dart index a94ecb35..be041fb1 100644 --- a/lib/utils/network_utils.dart +++ b/lib/utils/network_utils.dart @@ -41,3 +41,49 @@ void launchURL(final url) async { print(e); } } + +List getProjectDnsRecords( + final String? domainName, + final String? ip4, +) { + final DnsRecord domainA = + DnsRecord(type: 'A', name: domainName, content: ip4); + + final DnsRecord mx = DnsRecord(type: 'MX', name: '@', content: domainName); + final DnsRecord apiA = DnsRecord(type: 'A', name: 'api', content: ip4); + final DnsRecord cloudA = DnsRecord(type: 'A', name: 'cloud', content: ip4); + final DnsRecord gitA = DnsRecord(type: 'A', name: 'git', content: ip4); + final DnsRecord meetA = DnsRecord(type: 'A', name: 'meet', content: ip4); + final DnsRecord passwordA = + DnsRecord(type: 'A', name: 'password', content: ip4); + final DnsRecord socialA = DnsRecord(type: 'A', name: 'social', content: ip4); + final DnsRecord vpn = DnsRecord(type: 'A', name: 'vpn', content: ip4); + + final DnsRecord txt1 = DnsRecord( + type: 'TXT', + name: '_dmarc', + content: 'v=DMARC1; p=none', + ttl: 18000, + ); + + final DnsRecord txt2 = DnsRecord( + type: 'TXT', + name: domainName, + content: 'v=spf1 a mx ip4:$ip4 -all', + ttl: 18000, + ); + + return [ + domainA, + apiA, + cloudA, + gitA, + meetA, + passwordA, + socialA, + mx, + txt1, + txt2, + vpn, + ]; +}