models.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354
  1. from django.conf import settings
  2. from django.db import models, transaction
  3. from django.contrib.auth.models import BaseUserManager, AbstractBaseUser
  4. from django.utils import timezone
  5. from django.core.exceptions import SuspiciousOperation, ValidationError
  6. from desecapi import pdns, mixins
  7. import datetime
  8. from django.core.validators import MinValueValidator
  9. from rest_framework.authtoken.models import Token
  10. class MyUserManager(BaseUserManager):
  11. def create_user(self, email, password=None, registration_remote_ip=None, captcha_required=False, dyn=False):
  12. """
  13. Creates and saves a User with the given email, date of
  14. birth and password.
  15. """
  16. if not email:
  17. raise ValueError('Users must have an email address')
  18. user = self.model(
  19. email=self.normalize_email(email),
  20. registration_remote_ip=registration_remote_ip,
  21. captcha_required=captcha_required,
  22. dyn=dyn,
  23. )
  24. user.set_password(password)
  25. user.save(using=self._db)
  26. return user
  27. def create_superuser(self, email, password):
  28. """
  29. Creates and saves a superuser with the given email, date of
  30. birth and password.
  31. """
  32. user = self.create_user(email,
  33. password=password
  34. )
  35. user.is_admin = True
  36. user.save(using=self._db)
  37. return user
  38. class User(AbstractBaseUser):
  39. email = models.EmailField(
  40. verbose_name='email address',
  41. max_length=191,
  42. unique=True,
  43. )
  44. is_active = models.BooleanField(default=True)
  45. is_admin = models.BooleanField(default=False)
  46. registration_remote_ip = models.CharField(max_length=1024, blank=True)
  47. captcha_required = models.BooleanField(default=False)
  48. created = models.DateTimeField(auto_now_add=True)
  49. limit_domains = models.IntegerField(default=settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT,null=True,blank=True)
  50. dyn = models.BooleanField(default=False)
  51. objects = MyUserManager()
  52. USERNAME_FIELD = 'email'
  53. REQUIRED_FIELDS = []
  54. def get_full_name(self):
  55. return self.email
  56. def get_short_name(self):
  57. return self.email
  58. def get_token(self):
  59. token, created = Token.objects.get_or_create(user=self)
  60. return token.key
  61. def __str__(self):
  62. return self.email
  63. def has_perm(self, perm, obj=None):
  64. "Does the user have a specific permission?"
  65. # Simplest possible answer: Yes, always
  66. return True
  67. def has_module_perms(self, app_label):
  68. "Does the user have permissions to view the app `app_label`?"
  69. # Simplest possible answer: Yes, always
  70. return True
  71. @property
  72. def is_staff(self):
  73. "Is the user a member of staff?"
  74. # Simplest possible answer: All admins are staff
  75. return self.is_admin
  76. def unlock(self):
  77. self.captcha_required = False
  78. for domain in self.domains.all():
  79. domain.pdns_resync()
  80. self.save()
  81. class Domain(models.Model, mixins.SetterMixin):
  82. created = models.DateTimeField(auto_now_add=True)
  83. updated = models.DateTimeField(null=True)
  84. name = models.CharField(max_length=191, unique=True)
  85. arecord = models.GenericIPAddressField(protocol='IPv4', blank=False, null=True)
  86. aaaarecord = models.GenericIPAddressField(protocol='IPv6', blank=False, null=True)
  87. owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='domains')
  88. acme_challenge = models.CharField(max_length=255, blank=True)
  89. _dirtyName = False
  90. _dirtyRecords = False
  91. def setter_name(self, val):
  92. if val != self.name:
  93. self._dirtyName = True
  94. return val
  95. def setter_arecord(self, val):
  96. if val != self.arecord:
  97. self._dirtyRecords = True
  98. return val
  99. def setter_aaaarecord(self, val):
  100. if val != self.aaaarecord:
  101. self._dirtyRecords = True
  102. return val
  103. def setter_acme_challenge(self, val):
  104. if val != self.acme_challenge:
  105. self._dirtyRecords = True
  106. return val
  107. def clean(self):
  108. if self._dirtyName:
  109. raise ValidationError('You must not change the domain name')
  110. @property
  111. def keys(self):
  112. return pdns.get_keys(self)
  113. @property
  114. def pdns_id(self):
  115. if '/' in self.name or '?' in self.name:
  116. raise SuspiciousOperation('Invalid hostname ' + self.name)
  117. # Transform to be valid pdns API identifiers (:id in their docs). The
  118. # '/' case here is just a safety measure (this case should never occur due
  119. # to the above check).
  120. # See also pdns code, apiZoneNameToId() in ws-api.cc
  121. name = self.name.translate(str.maketrans({'/': '=2F', '_': '=5F'}))
  122. if not name.endswith('.'):
  123. name += '.'
  124. return name
  125. def pdns_resync(self):
  126. """
  127. Make sure that pdns gets the latest information about this domain/zone.
  128. Re-Syncing is relatively expensive and should not happen routinely.
  129. """
  130. # Create zone if it does not exist yet
  131. try:
  132. pdns.create_zone(self)
  133. except pdns.PdnsException as e:
  134. if not (e.status_code == 422 and e.detail.endswith(' already exists')):
  135. raise e
  136. # update zone to latest information
  137. pdns.set_dyn_records(self)
  138. def pdns_sync(self, new_domain):
  139. """
  140. Command pdns updates as indicated by the local changes.
  141. """
  142. if self.owner.captcha_required:
  143. # suspend all updates
  144. return
  145. # if this zone is new, create it and set dirty flag if necessary
  146. if new_domain:
  147. pdns.create_zone(self)
  148. self._dirtyRecords = bool(self.arecord) or bool(self.aaaarecord) or bool(self.acme_challenge)
  149. # make changes if necessary
  150. if self._dirtyRecords:
  151. pdns.set_dyn_records(self)
  152. self._dirtyRecords = False
  153. def sync_from_pdns(self):
  154. with transaction.atomic():
  155. RRset.objects.filter(domain=self).delete()
  156. rrsets = pdns.get_rrsets(self)
  157. rrsets = [rrset for rrset in rrsets if rrset.type not in RRset.RESTRICTED_TYPES]
  158. RRset.objects.bulk_create(rrsets)
  159. @transaction.atomic
  160. def delete(self, *args, **kwargs):
  161. super(Domain, self).delete(*args, **kwargs)
  162. pdns.delete_zone(self)
  163. if self.name.endswith('.dedyn.io'):
  164. pdns.set_rrset_in_parent(self, 'DS', '')
  165. pdns.set_rrset_in_parent(self, 'NS', '')
  166. @transaction.atomic
  167. def save(self, *args, **kwargs):
  168. # Record here if this is a new domain (self.pk is only None until we call super.save())
  169. new_domain = self.pk is None
  170. self.updated = timezone.now()
  171. self.clean()
  172. super(Domain, self).save(*args, **kwargs)
  173. self.pdns_sync(new_domain)
  174. class Meta:
  175. ordering = ('created',)
  176. def get_default_value_created():
  177. return timezone.now()
  178. def get_default_value_due():
  179. return timezone.now() + datetime.timedelta(days=7)
  180. def get_default_value_mref():
  181. return "ONDON" + str((timezone.now() - timezone.datetime(1970,1,1,tzinfo=timezone.utc)).total_seconds())
  182. class Donation(models.Model):
  183. created = models.DateTimeField(default=get_default_value_created)
  184. name = models.CharField(max_length=255)
  185. iban = models.CharField(max_length=34)
  186. bic = models.CharField(max_length=11)
  187. amount = models.DecimalField(max_digits=8,decimal_places=2)
  188. message = models.CharField(max_length=255, blank=True)
  189. due = models.DateTimeField(default=get_default_value_due)
  190. mref = models.CharField(max_length=32,default=get_default_value_mref)
  191. email = models.EmailField(max_length=255, blank=True)
  192. def save(self, *args, **kwargs):
  193. self.iban = self.iban[:6] + "xxx" # do NOT save account details
  194. super(Donation, self).save(*args, **kwargs) # Call the "real" save() method.
  195. class Meta:
  196. ordering = ('created',)
  197. def validate_upper(value):
  198. if value != value.upper():
  199. raise ValidationError('Invalid value (not uppercase): %(value)s',
  200. code='invalid',
  201. params={'value': value})
  202. class RRset(models.Model, mixins.SetterMixin):
  203. created = models.DateTimeField(auto_now_add=True)
  204. updated = models.DateTimeField(null=True)
  205. domain = models.ForeignKey(Domain, on_delete=models.CASCADE, related_name='rrsets')
  206. subname = models.CharField(max_length=178, blank=True)
  207. type = models.CharField(max_length=10, validators=[validate_upper])
  208. records = models.CharField(max_length=64000, blank=True)
  209. ttl = models.PositiveIntegerField(validators=[MinValueValidator(1)])
  210. _dirty = False
  211. RESTRICTED_TYPES = ('SOA', 'RRSIG', 'DNSKEY', 'NSEC3PARAM')
  212. class Meta:
  213. unique_together = (("domain","subname","type"),)
  214. def __init__(self, *args, **kwargs):
  215. self._dirties = set()
  216. super().__init__(*args, **kwargs)
  217. def setter_domain(self, val):
  218. if val != self.domain:
  219. self._dirties.add('domain')
  220. return val
  221. def setter_subname(self, val):
  222. # On PUT, RRsetSerializer sends None, denoting the unchanged value
  223. if val is None:
  224. return self.subname
  225. if val != self.subname:
  226. self._dirties.add('subname')
  227. return val
  228. def setter_type(self, val):
  229. if val != self.type:
  230. self._dirties.add('type')
  231. return val
  232. def setter_records(self, val):
  233. if val != self.records:
  234. self._dirty = True
  235. return val
  236. def setter_ttl(self, val):
  237. if val != self.ttl:
  238. self._dirty = True
  239. return val
  240. def clean(self):
  241. errors = {}
  242. for field in self._dirties:
  243. errors[field] = ValidationError(
  244. 'You cannot change the `%s` field.' % field)
  245. if errors:
  246. raise ValidationError(errors)
  247. @property
  248. def name(self):
  249. return '.'.join(filter(None, [self.subname, self.domain.name])) + '.'
  250. def update_pdns(self):
  251. pdns.set_rrset(self)
  252. pdns.notify_zone(self.domain)
  253. @transaction.atomic
  254. def delete(self, *args, **kwargs):
  255. # Reset records so that our pdns update later will cause deletion
  256. self.records = '[]'
  257. super().delete(*args, **kwargs)
  258. self.update_pdns()
  259. @transaction.atomic
  260. def save(self, *args, **kwargs):
  261. new = self.pk is None
  262. self.updated = timezone.now()
  263. self.full_clean()
  264. super().save(*args, **kwargs)
  265. if self._dirty or new:
  266. self.update_pdns()