models.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. from __future__ import annotations
  2. import datetime
  3. import random
  4. import time
  5. import uuid
  6. from base64 import b64encode
  7. from os import urandom
  8. import rest_framework.authtoken.models
  9. from django.conf import settings
  10. from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
  11. from django.core.exceptions import ValidationError
  12. from django.core.validators import MinValueValidator, RegexValidator
  13. from django.db import models
  14. from django.db.models import Manager
  15. from django.utils import timezone
  16. from rest_framework.exceptions import APIException
  17. from desecapi import pdns
  18. def validate_lower(value):
  19. if value != value.lower():
  20. raise ValidationError('Invalid value (not lowercase): %(value)s',
  21. code='invalid',
  22. params={'value': value})
  23. def validate_upper(value):
  24. if value != value.upper():
  25. raise ValidationError('Invalid value (not uppercase): %(value)s',
  26. code='invalid',
  27. params={'value': value})
  28. class MyUserManager(BaseUserManager):
  29. def create_user(self, email, password=None, registration_remote_ip=None, lock=False, dyn=False):
  30. """
  31. Creates and saves a User with the given email, date of
  32. birth and password.
  33. """
  34. if not email:
  35. raise ValueError('Users must have an email address')
  36. user = self.model(
  37. email=self.normalize_email(email),
  38. registration_remote_ip=registration_remote_ip,
  39. locked=timezone.now() if lock else None,
  40. dyn=dyn,
  41. )
  42. user.set_password(password)
  43. user.save(using=self._db)
  44. return user
  45. def create_superuser(self, email, password):
  46. """
  47. Creates and saves a superuser with the given email, date of
  48. birth and password.
  49. """
  50. user = self.create_user(email, password=password)
  51. user.is_admin = True
  52. user.save(using=self._db)
  53. return user
  54. class User(AbstractBaseUser):
  55. email = models.EmailField(
  56. verbose_name='email address',
  57. max_length=191,
  58. unique=True,
  59. )
  60. is_active = models.BooleanField(default=True)
  61. is_admin = models.BooleanField(default=False)
  62. registration_remote_ip = models.CharField(max_length=1024, blank=True)
  63. locked = models.DateTimeField(null=True, blank=True)
  64. created = models.DateTimeField(auto_now_add=True)
  65. limit_domains = models.IntegerField(default=settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT, null=True, blank=True)
  66. dyn = models.BooleanField(default=False)
  67. objects = MyUserManager()
  68. USERNAME_FIELD = 'email'
  69. REQUIRED_FIELDS = []
  70. def get_full_name(self):
  71. return self.email
  72. def get_short_name(self):
  73. return self.email
  74. def get_or_create_first_token(self):
  75. try:
  76. token = Token.objects.filter(user=self).earliest('created')
  77. except Token.DoesNotExist:
  78. token = Token.objects.create(user=self)
  79. return token.key
  80. def __str__(self):
  81. return self.email
  82. # noinspection PyMethodMayBeStatic
  83. def has_perm(self, *_):
  84. """Does the user have a specific permission?"""
  85. # Simplest possible answer: Yes, always
  86. return True
  87. # noinspection PyMethodMayBeStatic
  88. def has_module_perms(self, *_):
  89. """Does the user have permissions to view the app `app_label`?"""
  90. # Simplest possible answer: Yes, always
  91. return True
  92. @property
  93. def is_staff(self):
  94. """Is the user a member of staff?"""
  95. # Simplest possible answer: All admins are staff
  96. return self.is_admin
  97. class Token(rest_framework.authtoken.models.Token):
  98. key = models.CharField("Key", max_length=40, db_index=True, unique=True)
  99. # relation to user is a ForeignKey, so each user can have more than one token
  100. user = models.ForeignKey(
  101. User, related_name='auth_tokens',
  102. on_delete=models.CASCADE, verbose_name="User"
  103. )
  104. name = models.CharField("Name", max_length=64, default="")
  105. user_specific_id = models.BigIntegerField("User-Specific ID")
  106. def save(self, *args, **kwargs):
  107. if not self.user_specific_id:
  108. self.user_specific_id = random.randrange(16 ** 8)
  109. super().save(*args, **kwargs) # Call the "real" save() method.
  110. def generate_key(self):
  111. return b64encode(urandom(21)).decode('utf-8').replace('/', '-').replace('=', '_').replace('+', '.')
  112. class Meta:
  113. abstract = False
  114. unique_together = (('user', 'user_specific_id'),)
  115. class Domain(models.Model):
  116. created = models.DateTimeField(auto_now_add=True)
  117. name = models.CharField(max_length=191,
  118. unique=True,
  119. validators=[validate_lower,
  120. RegexValidator(regex=r'^[a-z0-9_.-]*[a-z]$',
  121. message='Domain name malformed.',
  122. code='invalid_domain_name')
  123. ])
  124. owner = models.ForeignKey(User, on_delete=models.PROTECT, related_name='domains')
  125. published = models.DateTimeField(null=True, blank=True)
  126. @property
  127. def keys(self):
  128. return pdns.get_keys(self)
  129. def partition_name(self):
  130. subname, _, parent_name = self.name.partition('.')
  131. return subname, parent_name or None
  132. def save(self, *args, **kwargs):
  133. self.full_clean(validate_unique=False)
  134. super().save(*args, **kwargs)
  135. def update_delegation(self, child_domain: Domain):
  136. child_subname, child_domain_name = child_domain.partition_name()
  137. if self.name != child_domain_name:
  138. raise ValueError('Cannot update delegation of %s as it is not an immediate child domain of %s.' %
  139. (child_domain.name, self.name))
  140. if child_domain.pk:
  141. # Domain real: set delegation
  142. child_keys = child_domain.keys
  143. if not child_keys:
  144. raise APIException('Cannot delegate %s, as it currently has no keys.' % child_domain.name)
  145. RRset.objects.create(domain=self, subname=child_subname, type='NS', ttl=3600, contents=settings.DEFAULT_NS)
  146. RRset.objects.create(domain=self, subname=child_subname, type='DS', ttl=300,
  147. contents=[ds for k in child_keys for ds in k['ds']])
  148. else:
  149. # Domain not real: remove delegation
  150. for rrset in self.rrset_set.filter(subname=child_subname, type__in=['NS', 'DS']):
  151. rrset.delete()
  152. def __str__(self):
  153. return self.name
  154. class Meta:
  155. ordering = ('created',)
  156. def get_default_value_created():
  157. return timezone.now()
  158. def get_default_value_due():
  159. return timezone.now() + datetime.timedelta(days=7)
  160. def get_default_value_mref():
  161. return "ONDON" + str(time.time())
  162. class Donation(models.Model):
  163. created = models.DateTimeField(default=get_default_value_created)
  164. name = models.CharField(max_length=255)
  165. iban = models.CharField(max_length=34)
  166. bic = models.CharField(max_length=11)
  167. amount = models.DecimalField(max_digits=8, decimal_places=2)
  168. message = models.CharField(max_length=255, blank=True)
  169. due = models.DateTimeField(default=get_default_value_due)
  170. mref = models.CharField(max_length=32, default=get_default_value_mref)
  171. email = models.EmailField(max_length=255, blank=True)
  172. def save(self, *args, **kwargs):
  173. self.iban = self.iban[:6] + "xxx" # do NOT save account details
  174. super().save(*args, **kwargs)
  175. class Meta:
  176. ordering = ('created',)
  177. class RRsetManager(Manager):
  178. def create(self, contents=None, **kwargs):
  179. rrset = super().create(**kwargs)
  180. for content in contents or []:
  181. RR.objects.create(rrset=rrset, content=content)
  182. return rrset
  183. class RRset(models.Model):
  184. id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  185. created = models.DateTimeField(auto_now_add=True)
  186. updated = models.DateTimeField(null=True)
  187. domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
  188. subname = models.CharField(
  189. max_length=178,
  190. blank=True,
  191. validators=[
  192. validate_lower,
  193. RegexValidator(
  194. regex=r'^([*]|(([*][.])?[a-z0-9_.-]*))$',
  195. message='Subname can only use (lowercase) a-z, 0-9, ., -, and _, '
  196. 'may start with a \'*.\', or just be \'*\'.',
  197. code='invalid_subname'
  198. )
  199. ]
  200. )
  201. type = models.CharField(
  202. max_length=10,
  203. validators=[
  204. validate_upper,
  205. RegexValidator(
  206. regex=r'^[A-Z][A-Z0-9]*$',
  207. message='Type must be uppercase alphanumeric and start with a letter.',
  208. code='invalid_type'
  209. )
  210. ]
  211. )
  212. ttl = models.PositiveIntegerField(validators=[MinValueValidator(1)])
  213. objects = RRsetManager()
  214. DEAD_TYPES = ('ALIAS', 'DNAME')
  215. RESTRICTED_TYPES = ('SOA', 'RRSIG', 'DNSKEY', 'NSEC3PARAM', 'OPT')
  216. class Meta:
  217. unique_together = (("domain", "subname", "type"),)
  218. @staticmethod
  219. def construct_name(subname, domain_name):
  220. return '.'.join(filter(None, [subname, domain_name])) + '.'
  221. @property
  222. def name(self):
  223. return self.construct_name(self.subname, self.domain.name)
  224. def save(self, *args, **kwargs):
  225. self.updated = timezone.now()
  226. self.full_clean(validate_unique=False)
  227. super().save(*args, **kwargs)
  228. def __str__(self):
  229. return '<RRSet domain=%s type=%s subname=%s>' % (self.domain.name, self.type, self.subname)
  230. class RRManager(Manager):
  231. def bulk_create(self, rrs, **kwargs):
  232. ret = super().bulk_create(rrs, **kwargs)
  233. # For each rrset, save once to update published timestamp and trigger signal for post-save processing
  234. rrsets = {rr.rrset for rr in rrs}
  235. for rrset in rrsets:
  236. rrset.save()
  237. return ret
  238. class RR(models.Model):
  239. created = models.DateTimeField(auto_now_add=True)
  240. rrset = models.ForeignKey(RRset, on_delete=models.CASCADE, related_name='records')
  241. # max_length is determined based on the calculation in
  242. # https://lists.isc.org/pipermail/bind-users/2008-April/070148.html
  243. content = models.CharField(max_length=4092)
  244. objects = RRManager()
  245. def __str__(self):
  246. return '<RR %s>' % self.content