From 8123632cc102f7ee5c29d67f16dfc6e034c4f1ee Mon Sep 17 00:00:00 2001 From: NaiJi Date: Mon, 29 May 2023 23:18:02 -0300 Subject: [PATCH] feat: Implement Cloudflare DNS provider layer and separate from API --- .../cloudflare/cloudflare_api.dart | 249 ++-------------- .../providers/dns_providers/cloudflare.dart | 281 +++++++++++++++++- 2 files changed, 303 insertions(+), 227 deletions(-) diff --git a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart index f063faba..ad3fd460 100644 --- a/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart +++ b/lib/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart @@ -115,26 +115,19 @@ class CloudflareApi extends DnsProviderApi { Future> removeSimilarRecords({ required final ServerDomain domain, - final String? ip4, + required final List records, }) async { - final String domainName = domain.domainName; final String domainZoneId = domain.zoneId; - final String url = '/zones/$domainZoneId/dns_records'; final Dio client = await getClient(); try { - final Response response = await client.get(url); - - final List records = response.data['result'] ?? []; final List allDeleteFutures = []; for (final record in records) { - if (record['zone_name'] == domainName) { - allDeleteFutures.add( - client.delete('$url/${record["id"]}'), - ); - } + allDeleteFutures.add( + client.delete('$url/${record["id"]}'), + ); } await Future.wait(allDeleteFutures); } catch (e) { @@ -151,13 +144,13 @@ class CloudflareApi extends DnsProviderApi { return GenericResult(success: true, data: null); } - Future> getDnsRecords({ + Future> getDnsRecords({ required final ServerDomain domain, }) async { Response response; final String domainName = domain.domainName; final String domainZoneId = domain.zoneId; - final List allRecords = []; + final List allRecords = []; final String url = '/zones/$domainZoneId/dns_records'; @@ -168,39 +161,33 @@ class CloudflareApi extends DnsProviderApi { for (final record in records) { if (record['zone_name'] == domainName) { - allRecords.add( - DnsRecord( - name: record['name'], - type: record['type'], - content: record['content'], - ttl: record['ttl'], - proxied: record['proxied'], - ), - ); + allRecords.add(record); } } } catch (e) { print(e); + return GenericResult( + data: [], + success: false, + message: e.toString(), + ); } finally { close(client); } - return allRecords; + return GenericResult(data: allRecords, success: true); } Future> createMultipleDnsRecords({ required final ServerDomain domain, - final String? ip4, + required final List records, }) async { - final String domainName = domain.domainName; final String domainZoneId = domain.zoneId; - final List listDnsRecords = - getProjectDnsRecords(domainName, ip4); final List allCreateFutures = []; final Dio client = await getClient(); try { - for (final DnsRecord record in listDnsRecords) { + for (final DnsRecord record in records) { allCreateFutures.add( client.post( '/zones/$domainZoneId/dns_records', @@ -246,213 +233,35 @@ class CloudflareApi extends DnsProviderApi { } } - Future> domainList() async { + Future> getDomains() async { final String url = '$rootAddress/zones'; - List domains = []; + List domains = []; + late final Response? response; final Dio client = await getClient(); try { - final Response response = await client.get( + response = await client.get( url, queryParameters: {'per_page': 50}, ); - domains = response.data['result'] - .map((final el) => el['name'] as String) - .toList(); + domains = response.data['result']; } catch (e) { print(e); + return GenericResult( + success: false, + data: domains, + code: response?.statusCode, + message: response?.statusMessage, + ); } finally { close(client); } - return domains; - } - - Future>> validateDnsRecords( - final ServerDomain domain, - final String ip4, - final String dkimPublicKey, - ) async { - final List records = await getDnsRecords(domain: domain); - 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( - (final r) => (r.name == record.name) && r.type == record.type, - orElse: () => DnsRecord( - name: record.name, - type: record.type, - content: '', - ttl: 800, - proxied: false, - ), - ); - // 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 r) => - (r.name == record.name) && - r.type == record.type && - r.content == record.content, - )) { - foundRecords.add(record.copyWith(isSatisfied: true)); - } else { - foundRecords.add(record.copyWith(isSatisfied: false)); - } - } - } - } catch (e) { - print(e); - return GenericResult( - data: [], - success: false, - message: e.toString(), - ); - } return GenericResult( - data: foundRecords, success: true, + data: domains, + code: response.statusCode, + message: response.statusMessage, ); } - - List getDesiredDnsRecords( - final String? domainName, - final String? ip4, - final String? dkimPublicKey, - ) { - if (domainName == null || ip4 == null) { - return []; - } - return [ - DesiredDnsRecord( - name: domainName, - 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: domainName, - content: 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: domainName, - 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, - ), - ]; - } - - 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 - ]; - } } diff --git a/lib/logic/providers/dns_providers/cloudflare.dart b/lib/logic/providers/dns_providers/cloudflare.dart index 192fdd96..bd01b240 100644 --- a/lib/logic/providers/dns_providers/cloudflare.dart +++ b/lib/logic/providers/dns_providers/cloudflare.dart @@ -1,4 +1,7 @@ import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart'; +import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/dns_provider.dart'; +import 'package:selfprivacy/logic/models/hive/server_domain.dart'; +import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart'; class ApiAdapter { @@ -60,27 +63,291 @@ class CloudflareDnsProvider extends DnsProvider { Future> removeDomainRecords({ required final ServerDomain domain, final String? ip4, - }) async {} + }) async { + final result = await _adapter.api().getDnsRecords(domain: domain); + if (result.data.isEmpty || !result.success) { + return GenericResult( + success: result.success, + data: null, + code: result.code, + message: result.message, + ); + } + + return _adapter.api().removeSimilarRecords( + domain: domain, + records: result.data, + ); + } + + @override Future>> getDnsRecords({ required final ServerDomain domain, - }); + }) async { + final List records = []; + final result = await _adapter.api().getDnsRecords(domain: domain); + if (result.data.isEmpty || !result.success) { + return GenericResult( + success: result.success, + data: records, + code: result.code, + message: result.message, + ); + } + + for (final rawRecord in result.data) { + records.add( + DnsRecord( + name: rawRecord['name'], + type: rawRecord['type'], + content: rawRecord['content'], + ttl: rawRecord['ttl'], + proxied: rawRecord['proxied'], + ), + ); + } + + return GenericResult( + success: result.success, + data: records, + ); + } + + @override Future> createDomainRecords({ required final ServerDomain domain, final String? ip4, - }); + }) { + final records = getProjectDnsRecords(domain.domainName, ip4); + return _adapter.api().createMultipleDnsRecords( + domain: domain, + records: records, + ); + } + + @override Future> setDnsRecord( final DnsRecord record, final ServerDomain domain, - ); - Future>> domainList(); + ) async => + _adapter.api().createMultipleDnsRecords( + domain: domain, + records: [record], + ); + + @override + Future>> domainList() async { + List domains = []; + final result = await _adapter.api().getDomains(); + if (result.data.isEmpty || !result.success) { + return GenericResult( + success: result.success, + data: domains, + code: result.code, + message: result.message, + ); + } + + domains = result.data + .map( + (final el) => el['name'] as String, + ) + .toList(); + + return GenericResult( + success: true, + data: domains, + ); + } + + @override Future>> validateDnsRecords( final ServerDomain domain, final String ip4, final String dkimPublicKey, - ); + ) async { + final GenericResult> records = + await getDnsRecords(domain: domain); + 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.data.firstWhere( + (final r) => (r.name == record.name) && r.type == record.type, + orElse: () => DnsRecord( + name: record.name, + type: record.type, + content: '', + ttl: 800, + proxied: false, + ), + ); + // 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.data.any( + (final r) => + (r.name == record.name) && + r.type == record.type && + r.content == record.content, + )) { + foundRecords.add(record.copyWith(isSatisfied: true)); + } else { + foundRecords.add(record.copyWith(isSatisfied: false)); + } + } + } + } catch (e) { + print(e); + return GenericResult( + data: [], + success: false, + message: e.toString(), + ); + } + return GenericResult( + data: foundRecords, + success: true, + ); + } + + @override List getDesiredDnsRecords( final String? domainName, final String? ip4, final String? dkimPublicKey, - ); + ) { + if (domainName == null || ip4 == null) { + return []; + } + return [ + DesiredDnsRecord( + name: domainName, + 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: domainName, + content: 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: domainName, + 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, + ), + ]; + } + + 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 + ]; + } }