testdomains.py 13 KB

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