testdomains.py 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. import json
  2. import random
  3. from django.conf import settings
  4. from django.core import mail
  5. from django.core.exceptions import ValidationError
  6. from rest_framework import status
  7. from desecapi.exceptions import PdnsException
  8. from desecapi.models import Domain
  9. from desecapi.tests.base import DesecTestCase, DomainOwnerTestCase, LockedDomainOwnerTestCase
  10. class UnauthenticatedDomainTests(DesecTestCase):
  11. def test_unauthorized_access(self):
  12. for url in [
  13. self.reverse('v1:domain-list'),
  14. self.reverse('v1:domain-detail', name='example.com.')
  15. ]:
  16. for method in [self.client.put, self.client.delete]:
  17. self.assertStatus(method(url), status.HTTP_401_UNAUTHORIZED)
  18. class DomainOwnerTestCase1(DomainOwnerTestCase):
  19. def test_name_validity(self):
  20. for name in [
  21. 'FOO.BAR.com',
  22. 'tEst.dedyn.io',
  23. 'ORG',
  24. '--BLAH.example.com',
  25. '_ASDF.jp',
  26. ]:
  27. with self.assertRaises(ValidationError):
  28. Domain(owner=self.owner, name=name).save()
  29. for name in [
  30. '_example.com', '_.example.com',
  31. '-dedyn.io', '--dedyn.io', '-.dedyn123.io',
  32. 'foobar.io', 'exam_ple.com',
  33. ]:
  34. with self.assertPdnsRequests(
  35. self.requests_desec_domain_creation(name=name)[:-1] # no serializer, no cryptokeys API call
  36. ):
  37. Domain(owner=self.owner, name=name).save()
  38. def test_list_domains(self):
  39. with self.assertPdnsNoRequestsBut(self.request_pdns_zone_retrieve_crypto_keys()):
  40. response = self.client.get(self.reverse('v1:domain-list'))
  41. self.assertStatus(response, status.HTTP_200_OK)
  42. self.assertEqual(len(response.data), self.NUM_OWNED_DOMAINS)
  43. for i in range(self.NUM_OWNED_DOMAINS):
  44. self.assertEqual(response.data[i]['name'], self.my_domains[i].name)
  45. def test_delete_my_domain(self):
  46. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  47. with self.assertPdnsRequests(self.requests_desec_domain_deletion()):
  48. response = self.client.delete(url)
  49. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  50. self.assertFalse(Domain.objects.filter(pk=self.my_domain.pk).exists())
  51. response = self.client.get(url)
  52. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  53. def test_delete_other_domain(self):
  54. url = self.reverse('v1:domain-detail', name=self.other_domain.name)
  55. response = self.client.delete(url)
  56. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  57. self.assertTrue(Domain.objects.filter(pk=self.other_domain.pk).exists())
  58. def test_retrieve_my_domain(self):
  59. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  60. with self.assertPdnsRequests(
  61. self.request_pdns_zone_retrieve_crypto_keys(name=self.my_domain.name)
  62. ):
  63. response = self.client.get(url)
  64. self.assertStatus(response, status.HTTP_200_OK)
  65. self.assertEqual(response.data['name'], self.my_domain.name)
  66. self.assertTrue(isinstance(response.data['keys'], list))
  67. def test_retrieve_other_domains(self):
  68. for domain in self.other_domains:
  69. response = self.client.get(self.reverse('v1:domain-detail', name=domain.name))
  70. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  71. def test_update_my_domain_name(self):
  72. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  73. with self.assertPdnsRequests(self.request_pdns_zone_retrieve_crypto_keys(name=self.my_domain.name)):
  74. response = self.client.get(url)
  75. self.assertStatus(response, status.HTTP_200_OK)
  76. response.data['name'] = self.random_domain_name()
  77. response = self.client.put(url, json.dumps(response.data), content_type='application/json')
  78. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  79. with self.assertPdnsRequests(self.request_pdns_zone_retrieve_crypto_keys(name=self.my_domain.name)):
  80. response = self.client.get(url)
  81. self.assertStatus(response, status.HTTP_200_OK)
  82. self.assertEqual(response.data['name'], self.my_domain.name)
  83. def test_update_other_domains(self):
  84. url = self.reverse('v1:domain-detail', name=self.other_domain.name)
  85. response = self.client.put(url, json.dumps({}), content_type='application/json')
  86. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  87. def test_create_domains(self):
  88. self.owner.limit_domains = 100
  89. self.owner.save()
  90. for name in [
  91. '0.8.0.0.0.1.c.a.2.4.6.0.c.e.e.d.4.4.0.1.a.0.1.0.8.f.4.0.1.0.a.2.ip6.arpa',
  92. 'very.long.domain.name.' + self.random_domain_name(),
  93. self.random_domain_name(),
  94. 'very.long.domain.name.with_underscore.' + self.random_domain_name(),
  95. ]:
  96. with self.assertPdnsRequests(self.requests_desec_domain_creation(name)):
  97. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  98. self.assertStatus(response, status.HTTP_201_CREATED)
  99. self.assertEqual(len(mail.outbox), 0)
  100. def test_create_api_known_domain(self):
  101. url = self.reverse('v1:domain-list')
  102. for name in [
  103. self.random_domain_name(),
  104. 'www.' + self.my_domain.name,
  105. ]:
  106. with self.assertPdnsRequests(self.requests_desec_domain_creation(name)):
  107. response = self.client.post(url, {'name': name})
  108. self.assertStatus(response, status.HTTP_201_CREATED)
  109. response = self.client.post(url, {'name': name})
  110. self.assertStatus(response, status.HTTP_409_CONFLICT)
  111. def test_create_pdns_known_domain(self):
  112. url = self.reverse('v1:domain-list')
  113. name = self.random_domain_name()
  114. with self.assertPdnsRequests(self.request_pdns_zone_create_already_exists(existing_domains=[name])):
  115. response = self.client.post(url, {'name': name})
  116. self.assertStatus(response, status.HTTP_409_CONFLICT)
  117. def test_create_domain_policy(self):
  118. name = '*.' + self.random_domain_name()
  119. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  120. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  121. self.assertTrue("does not match the required pattern." in response.data['name'][0])
  122. def test_create_domain_other_parent(self):
  123. name = 'something.' + self.other_domain.name
  124. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  125. self.assertStatus(response, status.HTTP_409_CONFLICT)
  126. self.assertIn('domain name is unavailable.', response.data['detail'])
  127. def test_create_domain_atomicity(self):
  128. name = self.random_domain_name()
  129. with self.assertPdnsRequests(self.request_pdns_zone_create_422()):
  130. self.client.post(self.reverse('v1:domain-list'), {'name': name})
  131. self.assertFalse(Domain.objects.filter(name=name).exists())
  132. def test_create_domain_punycode(self):
  133. names = ['公司.cn', 'aéroport.ci']
  134. for name in names:
  135. self.assertStatus(
  136. self.client.post(self.reverse('v1:domain-list'), {'name': name}),
  137. status.HTTP_400_BAD_REQUEST
  138. )
  139. for name in [n.encode('idna').decode('ascii') for n in names]:
  140. with self.assertPdnsRequests(self.requests_desec_domain_creation(name=name)):
  141. self.assertStatus(
  142. self.client.post(self.reverse('v1:domain-list'), {'name': name}),
  143. status.HTTP_201_CREATED
  144. )
  145. def test_create_domain_name_validation(self):
  146. for name in [
  147. 'with space.dedyn.io',
  148. 'another space.de',
  149. ' spaceatthebeginning.com',
  150. 'percentage%sign.com',
  151. '%percentagesign.dedyn.io',
  152. 'slash/desec.io',
  153. '/slashatthebeginning.dedyn.io',
  154. '\\backslashatthebeginning.dedyn.io',
  155. 'backslash\\inthemiddle.at',
  156. '@atsign.com',
  157. 'at@sign.com',
  158. ]:
  159. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  160. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  161. self.assertEqual(len(mail.outbox), 0)
  162. class LockedDomainOwnerTestCase1(LockedDomainOwnerTestCase):
  163. def test_create_domains(self):
  164. self.assertStatus(
  165. self.client.post(self.reverse('v1:domain-list'), {'name': self.random_domain_name()}),
  166. status.HTTP_403_FORBIDDEN
  167. )
  168. def test_update_domains(self):
  169. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  170. data = {'name': self.random_domain_name()}
  171. for method in [self.client.patch, self.client.put]:
  172. response = method(url, data)
  173. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  174. response = self.client.delete(url)
  175. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  176. def test_create_rr_sets(self):
  177. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  178. response = self.client.post_rr_set(self.my_domain.name, **data)
  179. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  180. def test_update_rr_sets(self):
  181. type_ = 'A'
  182. for subname in ['', '*', 'asdf', 'asdf.adsf.asdf']:
  183. data = {'records': ['1.2.3.4'], 'ttl': 60}
  184. response = self.client.put_rr_set(self.my_domain.name, subname, type_, **data)
  185. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  186. for patch_request in [
  187. {'records': ['1.2.3.4'], 'ttl': 60},
  188. {'records': [], 'ttl': 60},
  189. {'records': []},
  190. {'ttl': 60},
  191. {},
  192. ]:
  193. response = self.client.patch_rr_set(self.my_domain.name, subname, type_, **patch_request)
  194. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  195. # Try DELETE
  196. response = self.client.delete_rr_set(self.my_domain.name, subname, type_)
  197. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  198. class AutoDelegationDomainOwnerTests(DomainOwnerTestCase):
  199. DYN = True
  200. def test_delete_my_domain(self):
  201. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  202. with self.assertPdnsRequests(
  203. self.requests_desec_domain_deletion_auto_delegation(name=self.my_domain.name)
  204. ):
  205. response = self.client.delete(url)
  206. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  207. response = self.client.get(url)
  208. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  209. def test_delete_other_domains(self):
  210. url = self.reverse('v1:domain-detail', name=self.other_domain.name)
  211. with self.assertPdnsRequests():
  212. response = self.client.delete(url)
  213. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  214. self.assertTrue(Domain.objects.filter(pk=self.other_domain.pk).exists())
  215. def test_create_auto_delegated_domains(self):
  216. for i, suffix in enumerate(self.AUTO_DELEGATION_DOMAINS):
  217. name = self.random_domain_name(suffix)
  218. with self.assertPdnsRequests(self.requests_desec_domain_creation_auto_delegation(name=name)):
  219. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  220. self.assertStatus(response, status.HTTP_201_CREATED)
  221. self.assertEqual(len(mail.outbox), i + 1)
  222. email = str(mail.outbox[0].message())
  223. self.assertTrue(name in email)
  224. self.assertTrue(self.token.key in email)
  225. self.assertFalse(self.user.plain_password in email)
  226. def test_create_regular_domains(self):
  227. for name in [
  228. self.random_domain_name(),
  229. 'very.long.domain.' + self.random_domain_name()
  230. ]:
  231. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  232. self.assertStatus(response, status.HTTP_409_CONFLICT)
  233. self.assertEqual(response.data['code'], 'domain-illformed')
  234. def test_domain_limit(self):
  235. url = self.reverse('v1:domain-list')
  236. user_quota = settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT - self.NUM_OWNED_DOMAINS
  237. for i in range(user_quota):
  238. name = self.random_domain_name(random.choice(self.AUTO_DELEGATION_DOMAINS))
  239. with self.assertPdnsRequests(self.requests_desec_domain_creation_auto_delegation(name)):
  240. response = self.client.post(url, {'name': name})
  241. self.assertStatus(response, status.HTTP_201_CREATED)
  242. self.assertEqual(len(mail.outbox), i + 1)
  243. response = self.client.post(url, {'name': self.random_domain_name(random.choice(self.AUTO_DELEGATION_DOMAINS))})
  244. self.assertStatus(response, status.HTTP_403_FORBIDDEN)
  245. self.assertEqual(len(mail.outbox), user_quota)
  246. class LockedAutoDelegationDomainOwnerTests(LockedDomainOwnerTestCase):
  247. DYN = True
  248. def test_unlock_user(self):
  249. name = self.random_domain_name(self.AUTO_DELEGATION_DOMAINS[0])
  250. # Users should be able to create domains under auto delegated domains even when locked
  251. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  252. self.assertStatus(response, status.HTTP_201_CREATED)
  253. with self.assertPdnsRequests(
  254. self.request_pdns_zone_create_already_exists(existing_domains=[name])
  255. ), self.assertRaises(PdnsException) as cm:
  256. self.owner.unlock()
  257. self.assertEqual(str(cm.exception), "Domain '" + name + ".' already exists")
  258. # See what happens upon unlock if this domain is new to pdns
  259. with self.assertPdnsRequests(
  260. self.requests_desec_domain_creation_auto_delegation(name=name)[:-1] # No crypto keys retrieved
  261. ):
  262. self.owner.unlock()