models.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476
  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, uuid
  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, lock=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. locked=timezone.now() if lock else None,
  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. locked = models.DateTimeField(null=True,blank=True)
  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.locked is used by domain.sync_to_pdns(), so call that first
  78. for domain in self.domains.all():
  79. domain.sync_to_pdns()
  80. self.locked = None
  81. self.save()
  82. class Domain(models.Model, mixins.SetterMixin):
  83. created = models.DateTimeField(auto_now_add=True)
  84. name = models.CharField(max_length=191, unique=True)
  85. owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='domains')
  86. _dirtyName = False
  87. def setter_name(self, val):
  88. if val != self.name:
  89. self._dirtyName = True
  90. return val
  91. def clean(self):
  92. if self._dirtyName:
  93. raise ValidationError('You must not change the domain name')
  94. @property
  95. def keys(self):
  96. return pdns.get_keys(self)
  97. @property
  98. def pdns_id(self):
  99. if '/' in self.name or '?' in self.name:
  100. raise SuspiciousOperation('Invalid hostname ' + self.name)
  101. # Transform to be valid pdns API identifiers (:id in their docs). The
  102. # '/' case here is just a safety measure (this case should never occur due
  103. # to the above check).
  104. # See also pdns code, apiZoneNameToId() in ws-api.cc
  105. name = self.name.translate(str.maketrans({'/': '=2F', '_': '=5F'}))
  106. if not name.endswith('.'):
  107. name += '.'
  108. return name
  109. def sync_to_pdns(self):
  110. """
  111. Make sure that pdns gets the latest information about this domain/zone.
  112. Re-syncing is relatively expensive and should not happen routinely.
  113. This method should only be called for new domains or on user unlocking.
  114. For unlocked users, it assumes that the domain is a new one.
  115. """
  116. # Determine if this domain is expected to be new on pdns. This is the
  117. # case if the user is not locked (by assumption) or if the domain was
  118. # created after the user was locked. (If the user had this domain
  119. # before locking, it is not new on pdns.)
  120. new = self.owner.locked is None or self.owner.locked < self.created
  121. if new:
  122. # Create zone
  123. # Throws exception if pdns already knows this zone for some reason
  124. # which means that it is not ours and we should not mess with it.
  125. # We escalate the exception to let the next level deal with the
  126. # response.
  127. pdns.create_zone(self, settings.DEFAULT_NS)
  128. # Import RRsets that may have been created (e.g. during lock).
  129. rrsets = self.rrset_set.all()
  130. if rrsets:
  131. pdns.set_rrsets(self, rrsets)
  132. # Make our RRsets consistent with pdns (specifically, NS may exist)
  133. self.sync_from_pdns()
  134. # For dedyn.io domains, propagate NS and DS delegation RRsets
  135. subname, parent_pdns_id = self.pdns_id.split('.', 1)
  136. if parent_pdns_id == 'dedyn.io.':
  137. try:
  138. parent = Domain.objects.get(name='dedyn.io')
  139. except Domain.DoesNotExist:
  140. pass
  141. else:
  142. parent.write_rrsets([
  143. {'subname': subname, 'type': 'NS', 'ttl': 3600,
  144. 'contents': settings.DEFAULT_NS},
  145. {'subname': subname, 'type': 'DS', 'ttl': 60,
  146. 'contents': [ds for k in self.keys for ds in k['ds']]}
  147. ])
  148. else:
  149. # Zone exists. For the case that pdns knows records that we do not
  150. # (e.g. if a locked account has deleted an RRset), it is necessary
  151. # to purge all records here. However, there is currently no way to
  152. # do this through the pdns API (not to mention doing it atomically
  153. # with setting the new RRsets). So for now, we have disabled RRset
  154. # deletion for locked accounts.
  155. rrsets = self.rrset_set.all()
  156. if rrsets:
  157. pdns.set_rrsets(self, rrsets)
  158. @transaction.atomic
  159. def sync_from_pdns(self):
  160. self.rrset_set.all().delete()
  161. rrsets = []
  162. rrs = []
  163. for rrset_data in pdns.get_rrset_datas(self):
  164. if rrset_data['type'] in RRset.RESTRICTED_TYPES:
  165. continue
  166. records = rrset_data.pop('records')
  167. rrset = RRset(**rrset_data)
  168. rrsets.append(rrset)
  169. rrs.extend([RR(rrset=rrset, content=record) for record in records])
  170. RRset.objects.bulk_create(rrsets)
  171. RR.objects.bulk_create(rrs)
  172. def write_rrsets(self, datas):
  173. rrsets = {}
  174. for data in datas:
  175. rrset = RRset(domain=self, subname=data['subname'],
  176. type=data['type'], ttl=data['ttl'])
  177. rrsets[rrset] = [RR(rrset=rrset, content=content)
  178. for content in data['contents']]
  179. self._write_rrsets(rrsets)
  180. @transaction.atomic
  181. def _write_rrsets(self, rrsets):
  182. # Base queryset for all RRset of the current domain
  183. rrset_qs = RRset.objects.filter(domain=self)
  184. # Set to check RRset uniqueness
  185. rrsets_seen = set()
  186. # To-do list for non-empty RRsets, indexed by (subname, type)
  187. rrsets_meaty_todo = {}
  188. # Dictionary of RR lists to send to pdns, indexed by their RRset
  189. rrsets_to_write = {}
  190. # Always-false Q object: https://stackoverflow.com/a/35894246/6867099
  191. q_meaty = models.Q(pk__isnull=True)
  192. q_empty = models.Q(pk__isnull=True)
  193. # Determine which RRsets need to be updated or deleted
  194. for rrset, rrs in rrsets.items():
  195. if rrset.domain is not self:
  196. raise ValueError('RRset has wrong domain')
  197. if (rrset.subname, rrset.type) in rrsets_seen:
  198. raise ValueError('RRset repeated with same subname and type')
  199. if not all(rr.rrset is rrset for rr in rrs):
  200. raise ValueError('RR has wrong parent RRset')
  201. rrsets_seen.add((rrset.subname, rrset.type))
  202. q = models.Q(subname=rrset.subname, type=rrset.type)
  203. if rrs:
  204. rrsets_meaty_todo[(rrset.subname, rrset.type)] = rrset
  205. q_meaty |= q
  206. else:
  207. rrsets_to_write[rrset] = []
  208. q_empty |= q
  209. # Construct querysets representing RRsets that do (not) have RR
  210. # contents and lock them
  211. qs_meaty = rrset_qs.filter(q_meaty).select_for_update()
  212. qs_empty = rrset_qs.filter(q_empty).select_for_update()
  213. # For existing RRsets, execute TTL updates and/or mark for RR update
  214. rrsets_same_rrs = []
  215. for rrset in qs_meaty.all():
  216. rrset_temp = rrsets_meaty_todo.pop((rrset.subname, rrset.type))
  217. rrs_temp = {rr.content for rr in rrsets[rrset_temp]}
  218. rrs = {rr.content for rr in rrset.records.all()}
  219. changed_ttl = (rrset_temp.ttl != rrset.ttl)
  220. changed_rrs = (rrs_temp != rrs)
  221. if changed_ttl:
  222. rrset.ttl = rrset_temp.ttl
  223. rrset.save()
  224. if changed_ttl or changed_rrs:
  225. rrsets_to_write[rrset] = [RR(rrset=rrset, content=rr_content)
  226. for rr_content in rrs_temp]
  227. if not changed_rrs:
  228. rrsets_same_rrs.append(rrset)
  229. # At this point, rrsets_meaty_todo contains to new, non-empty RRsets
  230. # only. Let's save them. This does not save the associated RRs yet.
  231. for key, rrset in list(rrsets_meaty_todo.items()):
  232. rrset.save()
  233. rrsets_to_write[rrset] = rrsets[rrset]
  234. # Repeat lock to make sure new RRsets are also locked
  235. rrset_qs.filter(q_meaty).select_for_update()
  236. # Delete empty RRsets
  237. qs_empty.delete()
  238. # Update contents of modified RRsets
  239. RR.objects.filter(rrset__in=qs_meaty).exclude(rrset__in=rrsets_same_rrs).delete()
  240. RR.objects.bulk_create([rr
  241. for (rrset, rrs) in rrsets_to_write.items()
  242. for rr in rrs
  243. if rrset not in rrsets_same_rrs])
  244. # Send RRsets to pdns
  245. if rrsets_to_write and not self.owner.locked:
  246. pdns.set_rrsets(self, rrsets_to_write)
  247. @transaction.atomic
  248. def delete(self, *args, **kwargs):
  249. # Delete delegation for dynDNS domains (direct child of dedyn.io)
  250. subname, parent_pdns_id = self.pdns_id.split('.', 1)
  251. if parent_pdns_id == 'dedyn.io.':
  252. try:
  253. parent = Domain.objects.get(name='dedyn.io')
  254. except Domain.DoesNotExist:
  255. pass
  256. else:
  257. rrsets = parent.rrset_set.filter(subname=subname,
  258. type__in=['NS', 'DS']).all()
  259. # Need to go RRset by RRset to trigger pdns sync
  260. # TODO can optimize using write_rrsets()
  261. for rrset in rrsets:
  262. rrset.delete()
  263. # Delete domain
  264. super().delete(*args, **kwargs)
  265. pdns.delete_zone(self)
  266. @transaction.atomic
  267. def save(self, *args, **kwargs):
  268. new = self.pk is None
  269. self.clean()
  270. super().save(*args, **kwargs)
  271. if new and not self.owner.locked:
  272. self.sync_to_pdns()
  273. def __str__(self):
  274. """
  275. Return domain name. Needed for serialization via StringRelatedField.
  276. (Must be unique.)
  277. """
  278. return self.name
  279. class Meta:
  280. ordering = ('created',)
  281. def get_default_value_created():
  282. return timezone.now()
  283. def get_default_value_due():
  284. return timezone.now() + datetime.timedelta(days=7)
  285. def get_default_value_mref():
  286. return "ONDON" + str((timezone.now() - timezone.datetime(1970,1,1,tzinfo=timezone.utc)).total_seconds())
  287. class Donation(models.Model):
  288. created = models.DateTimeField(default=get_default_value_created)
  289. name = models.CharField(max_length=255)
  290. iban = models.CharField(max_length=34)
  291. bic = models.CharField(max_length=11)
  292. amount = models.DecimalField(max_digits=8,decimal_places=2)
  293. message = models.CharField(max_length=255, blank=True)
  294. due = models.DateTimeField(default=get_default_value_due)
  295. mref = models.CharField(max_length=32,default=get_default_value_mref)
  296. email = models.EmailField(max_length=255, blank=True)
  297. def save(self, *args, **kwargs):
  298. self.iban = self.iban[:6] + "xxx" # do NOT save account details
  299. super().save(*args, **kwargs) # Call the "real" save() method.
  300. class Meta:
  301. ordering = ('created',)
  302. def validate_upper(value):
  303. if value != value.upper():
  304. raise ValidationError('Invalid value (not uppercase): %(value)s',
  305. code='invalid',
  306. params={'value': value})
  307. class RRset(models.Model, mixins.SetterMixin):
  308. id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  309. created = models.DateTimeField(auto_now_add=True)
  310. updated = models.DateTimeField(null=True)
  311. domain = models.ForeignKey(Domain, on_delete=models.CASCADE)
  312. subname = models.CharField(max_length=178, blank=True)
  313. type = models.CharField(max_length=10, validators=[validate_upper])
  314. ttl = models.PositiveIntegerField(validators=[MinValueValidator(1)])
  315. _dirty = False
  316. RESTRICTED_TYPES = ('SOA', 'RRSIG', 'DNSKEY', 'NSEC3PARAM')
  317. class Meta:
  318. unique_together = (("domain","subname","type"),)
  319. def __init__(self, *args, **kwargs):
  320. self._dirties = set()
  321. super().__init__(*args, **kwargs)
  322. def setter_domain(self, val):
  323. if val != self.domain:
  324. self._dirties.add('domain')
  325. return val
  326. def setter_subname(self, val):
  327. # On PUT, RRsetSerializer sends None, denoting the unchanged value
  328. if val is None:
  329. return self.subname
  330. if val != self.subname:
  331. self._dirties.add('subname')
  332. return val
  333. def setter_type(self, val):
  334. if val != self.type:
  335. self._dirties.add('type')
  336. return val
  337. def setter_ttl(self, val):
  338. if val != self.ttl:
  339. self._dirties.add('ttl')
  340. return val
  341. def clean(self):
  342. errors = {}
  343. for field in (self._dirties & {'domain', 'subname', 'type'}):
  344. errors[field] = ValidationError(
  345. 'You cannot change the `%s` field.' % field)
  346. if errors:
  347. raise ValidationError(errors)
  348. def get_dirties(self):
  349. return self._dirties
  350. @property
  351. def name(self):
  352. return '.'.join(filter(None, [self.subname, self.domain.name])) + '.'
  353. @transaction.atomic
  354. def set_rrs(self, contents, sync=True, notify=True):
  355. self.records.all().delete()
  356. self.records.set([RR(content=x) for x in contents], bulk=False)
  357. if sync and not self.domain.owner.locked:
  358. pdns.set_rrset(self, notify=notify)
  359. @transaction.atomic
  360. def delete(self, *args, **kwargs):
  361. # For locked users, we can't easily sync deleted RRsets to pdns later,
  362. # so let's forbid it for now.
  363. assert not self.domain.owner.locked
  364. super().delete(*args, **kwargs)
  365. pdns.set_rrset(self)
  366. self._dirties = {}
  367. def save(self, *args, **kwargs):
  368. # If not new, the only thing that can change is the TTL
  369. if self.created is None or 'ttl' in self.get_dirties():
  370. self.updated = timezone.now()
  371. self.full_clean()
  372. # Tell Django to not attempt an update, although the pk is not None
  373. kwargs['force_insert'] = (self.created is None)
  374. super().save(*args, **kwargs)
  375. self._dirties = {}
  376. class RR(models.Model):
  377. created = models.DateTimeField(auto_now_add=True)
  378. rrset = models.ForeignKey(RRset, on_delete=models.CASCADE, related_name='records')
  379. # max_length is determined based on the calculation in
  380. # https://lists.isc.org/pipermail/bind-users/2008-April/070148.html
  381. content = models.CharField(max_length=4092)