testdomains.py 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. from django.urls import reverse
  2. from rest_framework import status
  3. from rest_framework.test import APITestCase
  4. from .utils import utils
  5. from desecapi.models import Domain
  6. from django.core import mail
  7. import httpretty
  8. from django.conf import settings
  9. import json
  10. class UnauthenticatedDomainTests(APITestCase):
  11. def testExpectUnauthorizedOnGet(self):
  12. url = reverse('domain-list')
  13. response = self.client.get(url, format='json')
  14. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  15. def testExpectUnauthorizedOnPost(self):
  16. url = reverse('domain-list')
  17. response = self.client.post(url, format='json')
  18. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  19. def testExpectUnauthorizedOnPut(self):
  20. url = reverse('domain-detail', args=('example.com',))
  21. response = self.client.put(url, format='json')
  22. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  23. def testExpectUnauthorizedOnDelete(self):
  24. url = reverse('domain-detail', args=('example.com',))
  25. response = self.client.delete(url, format='json')
  26. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  27. class AuthenticatedDomainTests(APITestCase):
  28. def setUp(self):
  29. if not hasattr(self, 'owner'):
  30. self.owner = utils.createUser()
  31. self.ownedDomains = [utils.createDomain(self.owner), utils.createDomain(self.owner)]
  32. self.otherDomains = [utils.createDomain(), utils.createDomain()]
  33. self.token = utils.createToken(user=self.owner)
  34. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  35. def tearDown(self):
  36. httpretty.reset()
  37. httpretty.disable()
  38. def testExpectOnlyOwnedDomains(self):
  39. url = reverse('domain-list')
  40. response = self.client.get(url, format='json')
  41. self.assertEqual(response.status_code, status.HTTP_200_OK)
  42. self.assertEqual(len(response.data), 2)
  43. self.assertEqual(response.data[0]['name'], self.ownedDomains[0].name)
  44. self.assertEqual(response.data[1]['name'], self.ownedDomains[1].name)
  45. def testCanDeleteOwnedDomain(self):
  46. httpretty.enable()
  47. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  48. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name+ '.')
  49. url = reverse('domain-detail', args=(self.ownedDomains[1].name,))
  50. response = self.client.delete(url)
  51. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  52. self.assertEqual(httpretty.last_request().method, 'DELETE')
  53. self.assertEqual(httpretty.last_request().headers['Host'], 'nsmaster:8081')
  54. httpretty.reset()
  55. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  56. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name+ '.')
  57. response = self.client.get(url)
  58. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  59. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  60. def testCantDeleteOtherDomains(self):
  61. httpretty.enable()
  62. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.otherDomains[1].name + '.')
  63. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.otherDomains[1].name+ '.')
  64. url = reverse('domain-detail', args=(self.otherDomains[1].name,))
  65. response = self.client.delete(url)
  66. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  67. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  68. self.assertTrue(Domain.objects.filter(pk=self.otherDomains[1].pk).exists())
  69. def testCanGetOwnedDomains(self):
  70. httpretty.enable()
  71. httpretty.register_uri(httpretty.GET,
  72. settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + './cryptokeys',
  73. body='[]',
  74. content_type="application/json")
  75. url = reverse('domain-detail', args=(self.ownedDomains[1].name,))
  76. response = self.client.get(url)
  77. self.assertEqual(response.status_code, status.HTTP_200_OK)
  78. self.assertEqual(response.data['name'], self.ownedDomains[1].name)
  79. self.assertTrue(isinstance(response.data['keys'], list))
  80. def testCantGetOtherDomains(self):
  81. url = reverse('domain-detail', args=(self.otherDomains[1].name,))
  82. response = self.client.get(url)
  83. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  84. def testCantChangeDomainName(self):
  85. url = reverse('domain-detail', args=(self.ownedDomains[1].name,))
  86. response = self.client.get(url)
  87. newname = utils.generateDomainname()
  88. response.data['name'] = newname
  89. response = self.client.put(url, json.dumps(response.data), content_type='application/json')
  90. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  91. response = self.client.get(url)
  92. self.assertEqual(response.status_code, status.HTTP_200_OK)
  93. self.assertEqual(response.data['name'], self.ownedDomains[1].name)
  94. def testCantPutOtherDomains(self):
  95. url = reverse('domain-detail', args=(self.otherDomains[1].name,))
  96. response = self.client.put(url, json.dumps({}), content_type='application/json')
  97. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  98. def testCanPostDomains(self):
  99. url = reverse('domain-list')
  100. data = {'name': utils.generateDomainname()}
  101. response = self.client.post(url, data)
  102. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  103. self.assertEqual(len(mail.outbox), 0)
  104. def testCanPostReverseDomains(self):
  105. name = '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'
  106. httpretty.enable()
  107. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones', status=201)
  108. httpretty.register_uri(httpretty.GET,
  109. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  110. body='{"rrsets": []}',
  111. content_type="application/json")
  112. httpretty.register_uri(httpretty.GET,
  113. settings.NSLORD_PDNS_API + '/zones/' + name + './cryptokeys',
  114. body='[]',
  115. content_type="application/json")
  116. httpretty.register_uri(httpretty.PUT, settings.NSLORD_PDNS_API + '/zones/' + name + './notify', status=200)
  117. url = reverse('domain-list')
  118. data = {'name': name}
  119. response = self.client.post(url, data)
  120. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  121. self.assertEqual(len(mail.outbox), 0)
  122. def testCantPostDomainAlreadyTakenInAPI(self):
  123. url = reverse('domain-list')
  124. data = {'name': utils.generateDomainname()}
  125. response = self.client.post(url, data)
  126. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  127. response = self.client.post(url, data)
  128. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  129. data = {'name': 'www.' + self.ownedDomains[0].name}
  130. response = self.client.post(url, data)
  131. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  132. data = {'name': 'www.' + self.otherDomains[0].name}
  133. response = self.client.post(url, data)
  134. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  135. def testCantPostDomainAlreadyTakenInPdns(self):
  136. name = utils.generateDomainname()
  137. httpretty.enable()
  138. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones',
  139. body='{"error": "Domain \'' + name + '.\' already exists"}', status=422)
  140. url = reverse('domain-list')
  141. data = {'name': name}
  142. response = self.client.post(url, data)
  143. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  144. def testCantPostDomainsViolatingPolicy(self):
  145. url = reverse('domain-list')
  146. data = {'name': '*.' + utils.generateDomainname()}
  147. response = self.client.post(url, data)
  148. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  149. self.assertTrue("does not match the required pattern." in response.data['name'][0])
  150. def testCanPostComplicatedDomains(self):
  151. url = reverse('domain-list')
  152. data = {'name': 'very.long.domain.name.' + utils.generateDomainname()}
  153. response = self.client.post(url, data)
  154. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  155. def testPostingCausesPdnsAPICalls(self):
  156. name = utils.generateDomainname()
  157. httpretty.enable()
  158. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  159. httpretty.register_uri(httpretty.GET,
  160. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  161. body='{"rrsets": []}',
  162. content_type="application/json")
  163. httpretty.register_uri(httpretty.GET,
  164. settings.NSLORD_PDNS_API + '/zones/' + name + './cryptokeys',
  165. body='[]',
  166. content_type="application/json")
  167. httpretty.register_uri(httpretty.PUT, settings.NSLORD_PDNS_API + '/zones/' + name + './notify', status=200)
  168. url = reverse('domain-list')
  169. self.client.post(url, {'name': name})
  170. self.assertEqual(httpretty.httpretty.latest_requests[-4].method, 'POST')
  171. self.assertTrue(name in httpretty.httpretty.latest_requests[-4].parsed_body)
  172. self.assertTrue('ns1.desec.io' in httpretty.httpretty.latest_requests[-4].parsed_body)
  173. self.assertEqual(httpretty.httpretty.latest_requests[-3].method, 'PUT')
  174. self.assertEqual(httpretty.httpretty.latest_requests[-2].method, 'GET')
  175. self.assertTrue((settings.NSLORD_PDNS_API + '/zones/' + name + '.').endswith(httpretty.httpretty.latest_requests[-2].path))
  176. def testDomainDetailURL(self):
  177. url = reverse('domain-detail', args=(self.ownedDomains[1].name,))
  178. self.assertTrue("/" + self.ownedDomains[1].name in url)
  179. def testRollback(self):
  180. name = utils.generateDomainname()
  181. httpretty.enable()
  182. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones', body="some error", status=500)
  183. url = reverse('domain-list')
  184. data = {'name': name}
  185. self.client.post(url, data)
  186. self.assertFalse(Domain.objects.filter(name=name).exists())
  187. class AuthenticatedDynDomainTests(APITestCase):
  188. def setUp(self):
  189. if not hasattr(self, 'owner'):
  190. self.owner = utils.createUser(dyn=True)
  191. self.ownedDomains = [utils.createDomain(self.owner, dyn=True), utils.createDomain(self.owner, dyn=True)]
  192. self.otherDomains = [utils.createDomain(), utils.createDomain()]
  193. self.token = utils.createToken(user=self.owner)
  194. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  195. def tearDown(self):
  196. httpretty.reset()
  197. httpretty.disable()
  198. def testCanDeleteOwnedDynDomain(self):
  199. httpretty.enable()
  200. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  201. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  202. url = reverse('domain-detail', args=(self.ownedDomains[1].name,))
  203. response = self.client.delete(url)
  204. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  205. # FIXME In this testing scenario, the parent domain dedyn.io does not
  206. # have the proper NS and DS records set up, so we cannot test their
  207. # deletion.
  208. httpretty.reset()
  209. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  210. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name+ '.')
  211. response = self.client.get(url)
  212. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  213. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  214. def testCantDeleteOtherDynDomains(self):
  215. httpretty.enable()
  216. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.otherDomains[1].name + '.')
  217. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.otherDomains[1].name+ '.')
  218. url = reverse('domain-detail', args=(self.otherDomains[1].name,))
  219. response = self.client.delete(url)
  220. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  221. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  222. self.assertTrue(Domain.objects.filter(pk=self.otherDomains[1].pk).exists())
  223. def testCanPostDynDomains(self):
  224. url = reverse('domain-list')
  225. data = {'name': utils.generateDynDomainname()}
  226. response = self.client.post(url, data)
  227. email = str(mail.outbox[0].message())
  228. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  229. self.assertEqual(len(mail.outbox), 1)
  230. self.assertTrue(data['name'] in email)
  231. self.assertTrue(self.token in email)
  232. # FIXME We also need to test that proper NS and DS records are set up
  233. # in the parent zone dedyn.io. Because this relies on the cron hook,
  234. # it is currently not covered.
  235. def testCantPostNonDynDomains(self):
  236. url = reverse('domain-list')
  237. data = {'name': utils.generateDomainname()}
  238. response = self.client.post(url, data)
  239. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  240. self.assertEqual(response.data['code'], 'domain-illformed')
  241. data = {'name': 'very.long.domain.' + utils.generateDynDomainname()}
  242. response = self.client.post(url, data)
  243. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  244. self.assertEqual(response.data['code'], 'domain-illformed')
  245. def testLimitDynDomains(self):
  246. httpretty.enable()
  247. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  248. outboxlen = len(mail.outbox)
  249. url = reverse('domain-list')
  250. for i in range(settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT-2):
  251. name = utils.generateDynDomainname()
  252. httpretty.register_uri(httpretty.GET,
  253. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  254. body='{"rrsets": []}',
  255. content_type="application/json")
  256. httpretty.register_uri(httpretty.GET,
  257. settings.NSLORD_PDNS_API + '/zones/' + name + './cryptokeys',
  258. body='[]',
  259. content_type="application/json")
  260. httpretty.register_uri(httpretty.PUT,
  261. settings.NSLORD_PDNS_API + '/zones/' + name + './notify',
  262. status=200)
  263. response = self.client.post(url, {'name': name})
  264. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  265. self.assertEqual(len(mail.outbox), outboxlen+i+1)
  266. data = {'name': utils.generateDynDomainname()}
  267. response = self.client.post(url, data)
  268. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  269. self.assertEqual(len(mail.outbox), outboxlen + settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT-2)
  270. def testCantUseInvalidCharactersInDomainNamePDNS(self):
  271. httpretty.enable()
  272. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  273. outboxlen = len(mail.outbox)
  274. invalidnames = [
  275. 'with space.dedyn.io',
  276. 'another space.de',
  277. ' spaceatthebeginning.com',
  278. 'percentage%sign.com',
  279. '%percentagesign.dedyn.io',
  280. 'slash/desec.io',
  281. '/slashatthebeginning.dedyn.io',
  282. '\\backslashatthebeginning.dedyn.io',
  283. 'backslash\\inthemiddle.at',
  284. '@atsign.com',
  285. 'at@sign.com',
  286. ]
  287. url = reverse('domain-list')
  288. for domainname in invalidnames:
  289. data = {'name': domainname}
  290. response = self.client.post(url, data)
  291. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  292. self.assertEqual(len(mail.outbox), outboxlen)