feat: Implement model adapter for Digital Ocean DNS

- Adapt all external interfaces to new DNS record models
pull/392/head
NaiJi ✨ 2023-11-09 18:48:49 +04:00
parent 5a7ae23ec2
commit d45417effe
9 changed files with 162 additions and 209 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/digital_ocean_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/digital_ocean_dns_info.dart';
class DigitalOceanDnsApi extends RestApiMap { class DigitalOceanDnsApi extends RestApiMap {
DigitalOceanDnsApi({ DigitalOceanDnsApi({

View File

@ -5,7 +5,6 @@ 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/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart';
import 'package:selfprivacy/logic/models/disk_size.dart';
import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/hive/user.dart';
import 'package:selfprivacy/logic/models/json/digital_ocean_server_info.dart'; import 'package:selfprivacy/logic/models/json/digital_ocean_server_info.dart';
import 'package:selfprivacy/utils/password_generator.dart'; import 'package:selfprivacy/utils/password_generator.dart';

View File

@ -23,15 +23,9 @@ class DnsRecordsCubit
@override @override
Future<void> load() async { Future<void> load() async {
emit( emit(
DnsRecordsState( const DnsRecordsState(
dnsState: DnsRecordsStatus.refreshing, dnsState: DnsRecordsStatus.refreshing,
dnsRecords: dnsRecords: [],
ProvidersController.currentDnsProvider?.getDesiredDnsRecords(
serverInstallationCubit.state.serverDomain?.domainName,
'',
'',
) ??
[],
), ),
); );
@ -44,12 +38,12 @@ class DnsRecordsCubit
return; return;
} }
final foundRecords = final foundRecords = await ProvidersController.currentDnsProvider!
await ProvidersController.currentDnsProvider!.validateDnsRecords( .validateDnsRecords(domain!, ipAddress!,
domain!, extractDkimRecord(await api.getDnsRecords())?.content ?? '', []
ipAddress!,
extractDkimRecord(await api.getDnsRecords())?.content ?? '', /// TODO: TOOD!!11kdoikadodsksakdpoadsaspodda
); );
if (!foundRecords.success || foundRecords.data.isEmpty) { if (!foundRecords.success || foundRecords.data.isEmpty) {
emit(const DnsRecordsState()); emit(const DnsRecordsState());

View File

@ -11,14 +11,22 @@ class DomainSetupCubit extends Cubit<DomainSetupState> {
Future<void> load() async { Future<void> load() async {
emit(Loading(LoadingTypes.loadingDomain)); emit(Loading(LoadingTypes.loadingDomain));
final GenericResult<List<String>> result = final GenericResult<List<ServerDomain>> result =
await ProvidersController.currentDnsProvider!.domainList(); await ProvidersController.currentDnsProvider!.domainList();
if (!result.success || result.data.isEmpty) { if (!result.success || result.data.isEmpty) {
emit(Empty()); emit(Empty());
} else if (result.data.length == 1) { } else if (result.data.length == 1) {
emit(Loaded(result.data.first)); emit(Loaded(result.data.first.domainName));
} else { } else {
emit(MoreThenOne(result.data)); emit(
MoreThenOne(
result.data
.map<String>(
(final d) => d.domainName,
)
.toList(),
),
);
} }
} }

View File

@ -0,0 +1,49 @@
part of 'digital_ocean_dns_info.dart';
DigitalOceanDnsRecord _fromDnsRecord(
final DnsRecord dnsRecord,
final String rootDomain,
) {
String convert(final String entry) => (entry == rootDomain) ? '@' : entry;
String name = dnsRecord.name ?? '';
String content = dnsRecord.content ?? '';
name = convert(name);
content = convert(content);
return DigitalOceanDnsRecord(
name: name,
data: content,
ttl: dnsRecord.ttl,
type: dnsRecord.type,
priority: dnsRecord.priority,
id: null,
);
}
DnsRecord _toDnsRecord(
final DigitalOceanDnsRecord digitalOceanRecord,
final String rootDomain,
) {
final String type = digitalOceanRecord.type;
String convert(final String entry) => (entry == '@') ? rootDomain : entry;
String name = digitalOceanRecord.name;
final String content = convert(digitalOceanRecord.data);
if (type != 'MX') {
name = convert(name);
}
return DnsRecord(
name: name,
content: content,
ttl: digitalOceanRecord.ttl,
type: type,
priority: digitalOceanRecord.priority ?? 10,
);
}
ServerDomain _toServerDomain(final DigitalOceanDomain digitalOceanDomain) =>
ServerDomain(
domainName: digitalOceanDomain.name,
provider: DnsProviderType.digitalOcean,
);
DigitalOceanDomain _fromServerDomain(final ServerDomain serverDomain) =>
DigitalOceanDomain(name: serverDomain.domainName);

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 'digital_ocean_dns_info.g.dart'; part 'digital_ocean_dns_info.g.dart';
part 'digital_ocean_dns_adapter.dart';
/// https://docs.digitalocean.com/reference/api/api-reference/#tag/Domains /// https://docs.digitalocean.com/reference/api/api-reference/#tag/Domains
@JsonSerializable() @JsonSerializable()
@ -10,6 +13,11 @@ class DigitalOceanDomain {
this.ttl, this.ttl,
}); });
factory DigitalOceanDomain.fromServerDomain(
final ServerDomain serverDomain,
) =>
_fromServerDomain(serverDomain);
/// The name of the domain itself. /// The name of the domain itself.
/// This should follow the standard domain format of domain.TLD. /// This should follow the standard domain format of domain.TLD.
/// ///
@ -23,6 +31,7 @@ class DigitalOceanDomain {
static DigitalOceanDomain fromJson(final Map<String, dynamic> json) => static DigitalOceanDomain fromJson(final Map<String, dynamic> json) =>
_$DigitalOceanDomainFromJson(json); _$DigitalOceanDomainFromJson(json);
ServerDomain toServerDomain() => _toServerDomain(this);
} }
/// https://docs.digitalocean.com/reference/api/api-reference/#tag/Domain-Records /// https://docs.digitalocean.com/reference/api/api-reference/#tag/Domain-Records
@ -37,6 +46,15 @@ class DigitalOceanDnsRecord {
this.priority, this.priority,
}); });
factory DigitalOceanDnsRecord.fromDnsRecord(
final DnsRecord dnsRecord,
final String rootDomain,
) =>
_fromDnsRecord(
dnsRecord,
rootDomain,
);
/// A unique identifier for each domain record. /// A unique identifier for each domain record.
final int? id; final int? id;
@ -63,4 +81,6 @@ class DigitalOceanDnsRecord {
static DigitalOceanDnsRecord fromJson(final Map<String, dynamic> json) => static DigitalOceanDnsRecord fromJson(final Map<String, dynamic> json) =>
_$DigitalOceanDnsRecordFromJson(json); _$DigitalOceanDnsRecordFromJson(json);
Map<String, dynamic> toJson() => _$DigitalOceanDnsRecordToJson(this); Map<String, dynamic> toJson() => _$DigitalOceanDnsRecordToJson(this);
DnsRecord toDnsRecord(final String rootDomain) =>
_toDnsRecord(this, rootDomain);
} }

View File

@ -184,8 +184,26 @@ class CloudflareDnsProvider extends DnsProvider {
final String dkimPublicKey, final String dkimPublicKey,
final List<DnsRecord> pendingDnsRecords, final List<DnsRecord> pendingDnsRecords,
) async { ) async {
final GenericResult<List<DnsRecord>> records = final syncZoneIdResult = await syncZoneId(domain.domainName);
await getDnsRecords(domain: domain); if (!syncZoneIdResult.success) {
return GenericResult(
success: syncZoneIdResult.success,
data: [],
code: syncZoneIdResult.code,
message: syncZoneIdResult.message,
);
}
final result =
await _adapter.api().getDnsRecords(zoneId: _adapter.cachedZoneId);
if (result.data.isEmpty || !result.success) {
return GenericResult(
success: result.success,
data: [],
code: result.code,
message: result.message,
);
}
final records = result.data;
final List<DesiredDnsRecord> foundRecords = []; final List<DesiredDnsRecord> foundRecords = [];
try { try {
for (final DnsRecord pendingDnsRecord in pendingDnsRecords) { for (final DnsRecord pendingDnsRecord in pendingDnsRecords) {
@ -194,14 +212,14 @@ class CloudflareDnsProvider extends DnsProvider {
domain.domainName, domain.domainName,
); );
if (record.name == 'selector._domainkey') { if (record.name == 'selector._domainkey') {
final DnsRecord foundRecord = records.data.firstWhere( final CloudflareDnsRecord foundRecord = records.firstWhere(
(final r) => (r.name == record.name) && r.type == record.type, (final r) => (r.name == record.name) && r.type == record.type,
orElse: () => DnsRecord( orElse: () => CloudflareDnsRecord(
zoneName: domain.domainName,
name: record.name, name: record.name,
type: record.type, type: record.type,
content: '', content: '',
ttl: 800, ttl: 800,
proxied: false,
), ),
); );
// remove all spaces and tabulators from // remove all spaces and tabulators from
@ -219,7 +237,7 @@ class CloudflareDnsProvider extends DnsProvider {
), ),
); );
} else { } else {
final foundMatch = records.data.any( final foundMatch = records.any(
(final r) => (final r) =>
(r.name == record.name) && (r.name == record.name) &&
r.type == record.type && r.type == record.type &&

View File

@ -1,9 +1,10 @@
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/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns_api.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/digital_ocean_dns/digital_ocean_dns_api.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/digital_ocean_dns_info.dart'; import 'package:selfprivacy/logic/models/json/dns_providers/digital_ocean_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({final bool isWithToken = true}) ApiAdapter({final bool isWithToken = true})
@ -46,8 +47,8 @@ class DigitalOceanDnsProvider 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().getDomains(); final result = await _adapter.api().getDomains();
if (result.data.isEmpty || !result.success) { if (result.data.isEmpty || !result.success) {
return GenericResult( return GenericResult(
@ -59,8 +60,8 @@ class DigitalOceanDnsProvider extends DnsProvider {
} }
domains = result.data domains = result.data
.map<String>( .map<ServerDomain>(
(final el) => el.name, (final el) => el.toServerDomain(),
) )
.toList(); .toList();
@ -82,14 +83,8 @@ class DigitalOceanDnsProvider extends DnsProvider {
ip4, ip4,
) )
.map<DigitalOceanDnsRecord>( .map<DigitalOceanDnsRecord>(
(final e) => DigitalOceanDnsRecord( (final e) =>
name: e.name ?? '', DigitalOceanDnsRecord.fromDnsRecord(e, domain.domainName),
id: null,
data: e.content ?? '',
ttl: e.ttl,
type: e.type,
priority: e.priority,
),
) )
.toList(), .toList(),
); );
@ -139,15 +134,7 @@ class DigitalOceanDnsProvider extends DnsProvider {
} }
for (final rawRecord in result.data) { for (final rawRecord in result.data) {
records.add( records.add(rawRecord.toDnsRecord(domain.domainName));
DnsRecord(
name: rawRecord.name,
type: rawRecord.type,
content: rawRecord.data,
ttl: rawRecord.ttl,
proxied: false,
),
);
} }
return GenericResult(data: records, success: true); return GenericResult(data: records, success: true);
@ -177,47 +164,61 @@ class DigitalOceanDnsProvider 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 result = await _adapter.api().getDnsRecords(domain.domainName);
await getDnsRecords(domain: domain); if (result.data.isEmpty || !result.success) {
return GenericResult(
success: result.success,
data: [],
code: result.code,
message: result.message,
);
}
final records = result.data;
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 = DigitalOceanDnsRecord.fromDnsRecord(
for (final DesiredDnsRecord record in desiredRecords) { pendingDnsRecord, domain.domainName);
if (record.description == 'record.dkim') { if (record.name == 'selector._domainkey') {
final DnsRecord foundRecord = records.data.firstWhere( final DigitalOceanDnsRecord foundRecord = records.firstWhere(
(final r) => (r.name == record.name) && r.type == record.type, (final r) => (r.name == record.name) && r.type == record.type,
orElse: () => DnsRecord( orElse: () => DigitalOceanDnsRecord(
id: null,
name: record.name, name: record.name,
type: record.type, type: record.type,
content: '', data: '',
ttl: 800, ttl: 800,
proxied: false,
), ),
); );
// remove all spaces and tabulators from // remove all spaces and tabulators from
// the foundRecord.content and the record.content // the foundRecord.content and the record.content
// to compare them // to compare them
final String? foundContent = final String foundContent =
foundRecord.content?.replaceAll(RegExp(r'\s+'), ''); foundRecord.data.replaceAll(RegExp(r'\s+'), '');
final String content = record.content.replaceAll(RegExp(r'\s+'), ''); final String content = record.data.replaceAll(RegExp(r'\s+'), '');
if (foundContent == content) { foundRecords.add(
foundRecords.add(record.copyWith(isSatisfied: true)); DesiredDnsRecord(
} else { name: record.name,
foundRecords.add(record.copyWith(isSatisfied: false)); content: record.data,
} isSatisfied: foundContent == content,
),
);
} else { } else {
if (records.data.any( final foundMatch = records.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.data == record.data,
)) { );
foundRecords.add(record.copyWith(isSatisfied: true)); foundRecords.add(
} else { DesiredDnsRecord(
foundRecords.add(record.copyWith(isSatisfied: false)); name: record.name,
} content: record.data,
isSatisfied: foundMatch,
),
);
} }
} }
} catch (e) { } catch (e) {
@ -233,140 +234,4 @@ class DigitalOceanDnsProvider extends DnsProvider {
success: true, success: true,
); );
} }
List<DnsRecord> getProjectDnsRecords(
final String? domainName,
final String? ip4,
) {
final DnsRecord domainA = DnsRecord(type: 'A', name: '@', content: ip4);
final DnsRecord mx = DnsRecord(type: 'MX', name: '@', content: '@');
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 <DnsRecord>[
domainA,
apiA,
cloudA,
gitA,
meetA,
passwordA,
socialA,
mx,
txt1,
txt2,
vpn
];
}
@override
List<DesiredDnsRecord> getDesiredDnsRecords(
final String? domainName,
final String? ip4,
final String? dkimPublicKey,
) {
if (domainName == null || ip4 == null) {
return [];
}
return [
DesiredDnsRecord(
name: '@',
content: ip4,
description: 'record.root',
displayName: domainName,
),
DesiredDnsRecord(
name: 'api',
content: ip4,
description: 'record.api',
displayName: 'api.$domainName',
),
DesiredDnsRecord(
name: 'cloud',
content: ip4,
description: 'record.cloud',
displayName: 'cloud.$domainName',
),
DesiredDnsRecord(
name: 'git',
content: ip4,
description: 'record.git',
displayName: 'git.$domainName',
),
DesiredDnsRecord(
name: 'meet',
content: ip4,
description: 'record.meet',
displayName: 'meet.$domainName',
),
DesiredDnsRecord(
name: 'social',
content: ip4,
description: 'record.social',
displayName: 'social.$domainName',
),
DesiredDnsRecord(
name: 'password',
content: ip4,
description: 'record.password',
displayName: 'password.$domainName',
),
DesiredDnsRecord(
name: 'vpn',
content: ip4,
description: 'record.vpn',
displayName: 'vpn.$domainName',
),
const DesiredDnsRecord(
name: '@',
content: '@',
description: 'record.mx',
type: 'MX',
category: DnsRecordsCategory.email,
),
const DesiredDnsRecord(
name: '_dmarc',
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',
content: dkimPublicKey,
description: 'record.dkim',
type: 'TXT',
category: DnsRecordsCategory.email,
),
];
}
} }