feat: Implement model adapter for Cloudflare DNS

pull/392/head
NaiJi ✨ 2023-11-09 17:21:56 +04:00
parent e9c01576b2
commit 5a7ae23ec2
5 changed files with 74 additions and 177 deletions

View File

@ -4,7 +4,7 @@ import 'package:dio/dio.dart';
import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/get_it_config.dart';
import 'package:selfprivacy/logic/api_maps/generic_result.dart'; import 'package:selfprivacy/logic/api_maps/generic_result.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/rest_api_map.dart';
import 'package:selfprivacy/logic/models/json/cloudflare_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/cloudflare_dns_info.dart';
class CloudflareApi extends RestApiMap { class CloudflareApi extends RestApiMap {
CloudflareApi({ CloudflareApi({

View File

@ -0,0 +1,27 @@
part of 'cloudflare_dns_info.dart';
CloudflareDnsRecord _fromDnsRecord(
final DnsRecord dnsRecord,
final String rootDomain,
) =>
CloudflareDnsRecord(
content: dnsRecord.content,
name: dnsRecord.name,
type: dnsRecord.type,
zoneName: rootDomain,
id: null,
ttl: dnsRecord.ttl,
);
DnsRecord _toDnsRecord(final CloudflareDnsRecord cloudflareRecord) => DnsRecord(
content: cloudflareRecord.content,
name: cloudflareRecord.name,
type: cloudflareRecord.type,
ttl: cloudflareRecord.ttl,
);
ServerDomain _toServerDomain(final CloudflareZone cloudflareZone) =>
ServerDomain(
domainName: cloudflareZone.name,
provider: DnsProviderType.cloudflare,
);

View File

@ -1,6 +1,9 @@
import 'package:json_annotation/json_annotation.dart'; import 'package:json_annotation/json_annotation.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart';
part 'cloudflare_dns_info.g.dart'; part 'cloudflare_dns_info.g.dart';
part 'cloudflare_dns_adapter.dart';
/// https://developers.cloudflare.com/api/operations/zones-get /// https://developers.cloudflare.com/api/operations/zones-get
@JsonSerializable() @JsonSerializable()
@ -26,6 +29,7 @@ class CloudflareZone {
static CloudflareZone fromJson(final Map<String, dynamic> json) => static CloudflareZone fromJson(final Map<String, dynamic> json) =>
_$CloudflareZoneFromJson(json); _$CloudflareZoneFromJson(json);
ServerDomain toServerDomain() => _toServerDomain(this);
} }
/// https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-list-dns-records /// https://developers.cloudflare.com/api/operations/dns-records-for-a-zone-list-dns-records
@ -41,6 +45,12 @@ class CloudflareDnsRecord {
this.id, this.id,
}); });
factory CloudflareDnsRecord.fromDnsRecord(
final DnsRecord dnsRecord,
final String rootDomain,
) =>
_fromDnsRecord(dnsRecord, rootDomain);
/// Record identifier /// Record identifier
/// ///
/// `<= 32 characters` /// `<= 32 characters`
@ -83,4 +93,5 @@ class CloudflareDnsRecord {
static CloudflareDnsRecord fromJson(final Map<String, dynamic> json) => static CloudflareDnsRecord fromJson(final Map<String, dynamic> json) =>
_$CloudflareDnsRecordFromJson(json); _$CloudflareDnsRecordFromJson(json);
Map<String, dynamic> toJson() => _$CloudflareDnsRecordToJson(this); Map<String, dynamic> toJson() => _$CloudflareDnsRecordToJson(this);
DnsRecord toDnsRecord() => _toDnsRecord(this);
} }

View File

@ -1,9 +1,10 @@
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/cloudflare/cloudflare_api.dart';
import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart';
import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart';
import 'package:selfprivacy/logic/models/json/cloudflare_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/cloudflare_dns_info.dart';
import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart';
import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart'; import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart';
import 'package:selfprivacy/utils/network_utils.dart';
class ApiAdapter { class ApiAdapter {
ApiAdapter({ ApiAdapter({
@ -51,8 +52,8 @@ class CloudflareDnsProvider extends DnsProvider {
} }
@override @override
Future<GenericResult<List<String>>> domainList() async { Future<GenericResult<List<ServerDomain>>> domainList() async {
List<String> domains = []; List<ServerDomain> domains = [];
final result = await _adapter.api().getZones(); final result = await _adapter.api().getZones();
if (result.data.isEmpty || !result.success) { if (result.data.isEmpty || !result.success) {
return GenericResult( return GenericResult(
@ -64,8 +65,8 @@ class CloudflareDnsProvider extends DnsProvider {
} }
domains = result.data domains = result.data
.map<String>( .map<ServerDomain>(
(final el) => el.name, (final el) => el.toServerDomain(),
) )
.toList(); .toList();
@ -90,14 +91,8 @@ class CloudflareDnsProvider extends DnsProvider {
zoneId: _adapter.cachedZoneId, zoneId: _adapter.cachedZoneId,
records: records records: records
.map<CloudflareDnsRecord>( .map<CloudflareDnsRecord>(
(final rec) => CloudflareDnsRecord( (final rec) =>
content: rec.content, CloudflareDnsRecord.fromDnsRecord(rec, domain.domainName),
name: rec.name,
type: rec.type,
zoneName: domain.domainName,
id: null,
ttl: rec.ttl,
),
) )
.toList(), .toList(),
); );
@ -157,14 +152,7 @@ class CloudflareDnsProvider extends DnsProvider {
} }
for (final rawRecord in result.data) { for (final rawRecord in result.data) {
records.add( records.add(rawRecord.toDnsRecord());
DnsRecord(
name: rawRecord.name,
type: rawRecord.type,
content: rawRecord.content,
ttl: rawRecord.ttl,
),
);
} }
return GenericResult( return GenericResult(
@ -185,16 +173,7 @@ class CloudflareDnsProvider extends DnsProvider {
return _adapter.api().createMultipleDnsRecords( return _adapter.api().createMultipleDnsRecords(
zoneId: _adapter.cachedZoneId, zoneId: _adapter.cachedZoneId,
records: [ records: [CloudflareDnsRecord.fromDnsRecord(record, domain.domainName)],
CloudflareDnsRecord(
content: record.content,
id: null,
name: record.name,
type: record.type,
zoneName: domain.domainName,
ttl: record.ttl,
),
],
); );
} }
@ -203,15 +182,18 @@ class CloudflareDnsProvider extends DnsProvider {
final ServerDomain domain, final ServerDomain domain,
final String ip4, final String ip4,
final String dkimPublicKey, final String dkimPublicKey,
final List<DnsRecord> pendingDnsRecords,
) async { ) async {
final GenericResult<List<DnsRecord>> records = final GenericResult<List<DnsRecord>> records =
await getDnsRecords(domain: domain); await getDnsRecords(domain: domain);
final List<DesiredDnsRecord> foundRecords = []; final List<DesiredDnsRecord> foundRecords = [];
try { try {
final List<DesiredDnsRecord> desiredRecords = for (final DnsRecord pendingDnsRecord in pendingDnsRecords) {
getDesiredDnsRecords(domain.domainName, ip4, dkimPublicKey); final record = CloudflareDnsRecord.fromDnsRecord(
for (final DesiredDnsRecord record in desiredRecords) { pendingDnsRecord,
if (record.description == 'record.dkim') { domain.domainName,
);
if (record.name == 'selector._domainkey') {
final DnsRecord foundRecord = records.data.firstWhere( final DnsRecord foundRecord = records.data.firstWhere(
(final r) => (r.name == record.name) && r.type == record.type, (final r) => (r.name == record.name) && r.type == record.type,
orElse: () => DnsRecord( orElse: () => DnsRecord(
@ -227,23 +209,29 @@ class CloudflareDnsProvider extends DnsProvider {
// to compare them // to compare them
final String? foundContent = final String? foundContent =
foundRecord.content?.replaceAll(RegExp(r'\s+'), ''); foundRecord.content?.replaceAll(RegExp(r'\s+'), '');
final String content = record.content.replaceAll(RegExp(r'\s+'), ''); final String content =
if (foundContent == content) { record.content?.replaceAll(RegExp(r'\s+'), '') ?? '';
foundRecords.add(record.copyWith(isSatisfied: true)); foundRecords.add(
} else { DesiredDnsRecord(
foundRecords.add(record.copyWith(isSatisfied: false)); name: record.name ?? '',
} content: record.content ?? '',
isSatisfied: foundContent == content,
),
);
} else { } else {
if (records.data.any( final foundMatch = records.data.any(
(final r) => (final r) =>
(r.name == record.name) && (r.name == record.name) &&
r.type == record.type && r.type == record.type &&
r.content == record.content, r.content == record.content,
)) { );
foundRecords.add(record.copyWith(isSatisfied: true)); foundRecords.add(
} else { DesiredDnsRecord(
foundRecords.add(record.copyWith(isSatisfied: false)); name: record.name ?? '',
} content: record.content ?? '',
isSatisfied: foundMatch,
),
);
} }
} }
} catch (e) { } catch (e) {
@ -260,135 +248,6 @@ class CloudflareDnsProvider extends DnsProvider {
); );
} }
@override
List<DesiredDnsRecord> 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<DnsRecord> 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 <DnsRecord>[
domainA,
apiA,
cloudA,
gitA,
meetA,
passwordA,
socialA,
mx,
txt1,
txt2,
vpn
];
}
Future<GenericResult<void>> syncZoneId(final String domain) async { Future<GenericResult<void>> syncZoneId(final String domain) async {
if (domain == _adapter.cachedDomain && _adapter.cachedZoneId.isNotEmpty) { if (domain == _adapter.cachedDomain && _adapter.cachedZoneId.isNotEmpty) {
return GenericResult( return GenericResult(