Skip to content

Commit 2f9e910

Browse files
committed
streamline code
1 parent 42725a1 commit 2f9e910

File tree

5 files changed

+70
-167
lines changed

5 files changed

+70
-167
lines changed

api/desecapi/migrations/0015_identities.py

Lines changed: 0 additions & 29 deletions
This file was deleted.

api/desecapi/migrations/0016_identities.py

Lines changed: 0 additions & 43 deletions
This file was deleted.

api/desecapi/migrations/0017_auto_20210213_1840.py

Lines changed: 0 additions & 33 deletions
This file was deleted.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Generated by Django 3.2.7 on 2021-09-19 19:53
2+
3+
from django.conf import settings
4+
from django.db import migrations, models
5+
import django.db.models.deletion
6+
import uuid
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
('desecapi', '0017_alter_user_limit_domains'),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name='TLSIdentity',
18+
fields=[
19+
('id', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
20+
('name', models.CharField(default='', max_length=24)),
21+
('created', models.DateTimeField(auto_now_add=True)),
22+
('default_ttl', models.PositiveIntegerField(default=300)),
23+
('scheduled_removal', models.DateTimeField(null=True)),
24+
('certificate', models.TextField()),
25+
('tlsa_selector', models.IntegerField(choices=[(0, 'Full Certificate'), (1, 'Subject Public Key Info')], default=1)),
26+
('tlsa_matching_type', models.IntegerField(choices=[(0, 'No Hash Used'), (1, 'Sha256'), (2, 'Sha512')], default=1)),
27+
('tlsa_certificate_usage', models.IntegerField(choices=[(0, 'Ca Constraint'), (1, 'Service Certificate Constraint'), (2, 'Trust Anchor Assertion'), (3, 'Domain Issued Certificate')], default=3)),
28+
('port', models.IntegerField(default=443)),
29+
('protocol', models.TextField(choices=[('tcp', 'Tcp'), ('udp', 'Udp'), ('sctp', 'Sctp')], default='tcp')),
30+
('owner', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='identities', to=settings.AUTH_USER_MODEL)),
31+
('rrs', models.ManyToManyField(to='desecapi.RR')),
32+
],
33+
options={
34+
'abstract': False,
35+
},
36+
),
37+
]

api/desecapi/models.py

Lines changed: 33 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,11 @@ def filter_qname(self, qname: str, **kwargs) -> models.query.QuerySet:
219219
name_length=Length('name'),
220220
).filter(dotted_qname__endswith=F('dotted_name'), **kwargs)
221221

222+
def most_specific_zone(self, fqdn: str) -> Tuple[Domain, str]:
223+
domain = self.filter_qname(fqdn).order_by('-name_length').first()
224+
subname = fqdn[:-len(domain.name)].rstrip('.')
225+
return domain, subname
226+
222227

223228
class Domain(ExportModelOperationsMixin('Domain'), models.Model):
224229
@staticmethod
@@ -994,37 +999,39 @@ class Identity(models.Model):
994999
created = models.DateTimeField(auto_now_add=True)
9951000
owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='identities')
9961001
default_ttl = models.PositiveIntegerField(default=300)
1002+
rrs = models.ManyToManyField(to=RR)
1003+
scheduled_removal = models.DateTimeField(null=True)
9971004

9981005
class Meta:
9991006
abstract = True
10001007

1001-
def get_record_contents(self) -> List[str]:
1002-
raise NotImplementedError
1003-
1004-
def save_rrs(self):
1008+
def get_rrs(self) -> List[RR]:
10051009
raise NotImplementedError
10061010

10071011
def save(self, *args, **kwargs):
1008-
self.save_rrs()
1012+
for rr in self.get_rrs():
1013+
self.rrs.add(rr)
1014+
rr.rrset.save()
1015+
rr.save()
10091016
return super().save(*args, **kwargs)
10101017

1011-
def delete_rrs(self):
1012-
raise NotImplementedError
1013-
10141018
def delete(self, using=None, keep_parents=False):
1015-
# TODO this will delete also RRs that may be covered by other identities
1016-
self.delete_rrs()
1019+
for rr in self.rrs.all(): # TODO use one query
1020+
if len(rr.identities) == 1:
1021+
rr.delete()
10171022
return super().delete(using, keep_parents)
10181023

1024+
# TODO move to RRset / RRset manager?
10191025
def get_or_create_rr_set(self, domain: Domain, subname: str) -> RRset:
10201026
try:
10211027
return RRset.objects.get(domain=domain, subname=subname, type=self.rr_type)
10221028
except RRset.DoesNotExist:
1023-
# TODO save this RRset?
10241029
return RRset(domain=domain, subname=subname, type=self.rr_type, ttl=self.default_ttl)
10251030

1026-
@staticmethod
1027-
def get_or_create_rr(rrset: RRset, content: str) -> RR:
1031+
# TODO move to RR / RR manager?
1032+
def get_or_create_rr(self, fqdn: str, content: str) -> RR:
1033+
domain, subname = self.owner.domains.most_specific_zone(fqdn)
1034+
rrset = self.get_or_create_rr_set(domain, subname)
10281035
try:
10291036
return RR.objects.get(rrset=rrset, content=content)
10301037
except RR.DoesNotExist:
@@ -1064,8 +1071,6 @@ class Protocol(models.TextChoices):
10641071
port = models.IntegerField(default=443)
10651072
protocol = models.TextField(choices=Protocol.choices, default=Protocol.TCP)
10661073

1067-
scheduled_removal = models.DateTimeField(null=True)
1068-
10691074
def __init__(self, *args, **kwargs):
10701075
super().__init__(*args, **kwargs)
10711076
if 'not_valid_after' not in kwargs:
@@ -1123,20 +1128,9 @@ def subject_names(self) -> Set[str]:
11231128

11241129
return subject_names | subject_alternative_names
11251130

1126-
@staticmethod
1127-
def get_closest_ancestor(domain_name, owner: User) -> Optional[Domain]:
1128-
# TODO move to Domain?
1129-
labels = domain_name.split('.')
1130-
ancestor_names = ['.'.join(labels[i:]) for i in range(len(labels))]
1131-
for ancestor_name in ancestor_names: # TODO do this with one query
1132-
try:
1133-
return Domain.objects.get(name=ancestor_name, owner=owner)
1134-
except Domain.DoesNotExist:
1135-
continue
1136-
return None
1137-
1138-
def domains_subnames(self) -> Set[Tuple[Domain, str]]:
1139-
domains_subnames = set()
1131+
@property
1132+
def subject_names_clean(self) -> Set[str]:
1133+
clean = set()
11401134
for name in self.subject_names:
11411135
# cut off any wildcard prefix
11421136
name = name.lstrip('*').lstrip('.')
@@ -1147,41 +1141,18 @@ def domains_subnames(self) -> Set[Tuple[Domain, str]]:
11471141
except ValidationError:
11481142
continue
11491143

1150-
# find user-owned parent domain
1151-
domain = self.get_closest_ancestor(name, self.owner)
1152-
if not domain:
1153-
continue
1154-
subname = name[:-len(domain.name)].rstrip('.')
1155-
1156-
# return subname, domain pair
1157-
domains_subnames.add((domain, f"_{self.port:n}._{self.protocol}.{subname}".rstrip('.')))
1158-
return domains_subnames
1159-
1160-
def get_rrsets(self) -> List[RRset]:
1161-
rrsets = []
1162-
for domain, subname in self.domains_subnames():
1163-
rrsets.append(self.get_or_create_rr_set(domain, subname))
1164-
return rrsets
1144+
clean.add(name)
1145+
return clean
11651146

11661147
def get_rrs(self) -> List[RR]:
1167-
rrs = []
1168-
for domain, subname in self.domains_subnames():
1169-
rrset = self.get_or_create_rr_set(domain, subname)
1170-
for content in self.get_record_contents():
1171-
rrs.append(self.get_or_create_rr(rrset=rrset, content=content))
1172-
return rrs
1173-
1174-
def save_rrs(self):
1175-
for rr in self.get_rrs():
1176-
rr.rrset.save()
1177-
rr.save()
1178-
1179-
def delete_rrs(self):
1180-
for domain, subname in self.domains_subnames():
1181-
rrset = self.get_or_create_rr_set(domain, subname)
1182-
rrset.records.filter(content__in=self.get_record_contents()).delete()
1183-
if not len(rrset.records.all()):
1184-
rrset.delete()
1148+
return [
1149+
self.get_or_create_rr(
1150+
fqdn=f"_{self.port:n}._{self.protocol}.{qname}",
1151+
content=content,
1152+
)
1153+
for qname in self.subject_names_clean
1154+
for content in self.get_record_contents()
1155+
]
11851156

11861157
@property
11871158
def not_valid_before(self):

0 commit comments

Comments
 (0)