testdomains.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. from django.core.urlresolvers 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=(1,))
  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=(1,))
  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].pk,))
  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].pk,))
  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].pk,))
  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].pk,))
  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].pk,))
  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].pk,))
  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. url = reverse('domain-list')
  113. data = {'name': name}
  114. response = self.client.post(url, data)
  115. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  116. self.assertEqual(len(mail.outbox), 0)
  117. def testCantPostDomainAlreadyTakenInAPI(self):
  118. url = reverse('domain-list')
  119. data = {'name': utils.generateDomainname()}
  120. response = self.client.post(url, data)
  121. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  122. response = self.client.post(url, data)
  123. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  124. data = {'name': 'www.' + self.ownedDomains[0].name}
  125. response = self.client.post(url, data)
  126. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  127. data = {'name': 'www.' + self.otherDomains[0].name}
  128. response = self.client.post(url, data)
  129. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  130. def testCantPostDomainAlreadyTakenInPdns(self):
  131. name = utils.generateDomainname()
  132. httpretty.enable()
  133. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones',
  134. body='{"error": "Domain \'' + name + '.\' already exists"}', status=422)
  135. url = reverse('domain-list')
  136. data = {'name': name}
  137. response = self.client.post(url, data)
  138. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  139. def testCantPostDomainsViolatingPolicy(self):
  140. url = reverse('domain-list')
  141. data = {'name': '*.' + utils.generateDomainname()}
  142. response = self.client.post(url, data)
  143. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  144. self.assertTrue("does not match the required pattern." in response.data['name'][0])
  145. def testCanPostComplicatedDomains(self):
  146. url = reverse('domain-list')
  147. data = {'name': 'very.long.domain.name.' + utils.generateDomainname()}
  148. response = self.client.post(url, data)
  149. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  150. def testPostingCausesPdnsAPICalls(self):
  151. name = utils.generateDomainname()
  152. httpretty.enable()
  153. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  154. httpretty.register_uri(httpretty.GET,
  155. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  156. body='{"rrsets": []}',
  157. content_type="application/json")
  158. httpretty.register_uri(httpretty.GET,
  159. settings.NSLORD_PDNS_API + '/zones/' + name + './cryptokeys',
  160. body='[]',
  161. content_type="application/json")
  162. url = reverse('domain-list')
  163. self.client.post(url, {'name': name})
  164. self.assertEqual(httpretty.httpretty.latest_requests[-3].method, 'POST')
  165. self.assertTrue(name in httpretty.httpretty.latest_requests[-3].parsed_body)
  166. self.assertTrue('ns1.desec.io' in httpretty.httpretty.latest_requests[-3].parsed_body)
  167. self.assertEqual(httpretty.httpretty.latest_requests[-2].method, 'GET')
  168. self.assertTrue((settings.NSLORD_PDNS_API + '/zones/' + name + '.').endswith(httpretty.httpretty.latest_requests[-2].path))
  169. def testDomainDetailURL(self):
  170. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  171. urlByName = reverse('domain-detail/byName', args=(self.ownedDomains[1].name,))
  172. self.assertTrue(("/%d" % self.ownedDomains[1].pk) in url)
  173. self.assertTrue("/" + self.ownedDomains[1].name in urlByName)
  174. def testRollback(self):
  175. name = utils.generateDomainname()
  176. httpretty.enable()
  177. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones', body="some error", status=500)
  178. url = reverse('domain-list')
  179. data = {'name': name}
  180. self.client.post(url, data)
  181. self.assertFalse(Domain.objects.filter(name=name).exists())
  182. class AuthenticatedDynDomainTests(APITestCase):
  183. def setUp(self):
  184. if not hasattr(self, 'owner'):
  185. self.owner = utils.createUser(dyn=True)
  186. self.ownedDomains = [utils.createDomain(self.owner, dyn=True), utils.createDomain(self.owner, dyn=True)]
  187. self.otherDomains = [utils.createDomain(), utils.createDomain()]
  188. self.token = utils.createToken(user=self.owner)
  189. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  190. def tearDown(self):
  191. httpretty.reset()
  192. httpretty.disable()
  193. def testCanDeleteOwnedDynDomain(self):
  194. httpretty.enable()
  195. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  196. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  197. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  198. response = self.client.delete(url)
  199. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  200. # FIXME In this testing scenario, the parent domain dedyn.io does not
  201. # have the proper NS and DS records set up, so we cannot test their
  202. # deletion.
  203. httpretty.reset()
  204. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  205. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name+ '.')
  206. response = self.client.get(url)
  207. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  208. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  209. def testCantDeleteOtherDynDomains(self):
  210. httpretty.enable()
  211. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.otherDomains[1].name + '.')
  212. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.otherDomains[1].name+ '.')
  213. url = reverse('domain-detail', args=(self.otherDomains[1].pk,))
  214. response = self.client.delete(url)
  215. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  216. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  217. self.assertTrue(Domain.objects.filter(pk=self.otherDomains[1].pk).exists())
  218. def testCanPostDynDomains(self):
  219. url = reverse('domain-list')
  220. data = {'name': utils.generateDynDomainname()}
  221. response = self.client.post(url, data)
  222. email = str(mail.outbox[0].message())
  223. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  224. self.assertEqual(len(mail.outbox), 1)
  225. self.assertTrue(data['name'] in email)
  226. self.assertTrue(self.token in email)
  227. # FIXME We also need to test that proper NS and DS records are set up
  228. # in the parent zone dedyn.io. Because this relies on the cron hook,
  229. # it is currently not covered.
  230. def testCantPostNonDynDomains(self):
  231. url = reverse('domain-list')
  232. data = {'name': utils.generateDomainname()}
  233. response = self.client.post(url, data)
  234. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  235. self.assertEqual(response.data['code'], 'domain-illformed')
  236. data = {'name': 'very.long.domain.' + utils.generateDynDomainname()}
  237. response = self.client.post(url, data)
  238. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  239. self.assertEqual(response.data['code'], 'domain-illformed')
  240. def testLimitDynDomains(self):
  241. httpretty.enable()
  242. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  243. outboxlen = len(mail.outbox)
  244. url = reverse('domain-list')
  245. for i in range(settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT-2):
  246. name = utils.generateDynDomainname()
  247. httpretty.register_uri(httpretty.GET,
  248. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  249. body='{"rrsets": []}',
  250. content_type="application/json")
  251. httpretty.register_uri(httpretty.GET,
  252. settings.NSLORD_PDNS_API + '/zones/' + name + './cryptokeys',
  253. body='[]',
  254. content_type="application/json")
  255. response = self.client.post(url, {'name': name})
  256. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  257. self.assertEqual(len(mail.outbox), outboxlen+i+1)
  258. data = {'name': utils.generateDynDomainname()}
  259. response = self.client.post(url, data)
  260. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  261. self.assertEqual(len(mail.outbox), outboxlen + settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT-2)
  262. def testCantUseInvalidCharactersInDomainNamePDNS(self):
  263. httpretty.enable()
  264. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  265. outboxlen = len(mail.outbox)
  266. invalidnames = [
  267. 'with space.dedyn.io',
  268. 'another space.de',
  269. ' spaceatthebeginning.com',
  270. 'percentage%sign.com',
  271. '%percentagesign.dedyn.io',
  272. 'slash/desec.io',
  273. '/slashatthebeginning.dedyn.io',
  274. '\\backslashatthebeginning.dedyn.io',
  275. 'backslash\\inthemiddle.at',
  276. '@atsign.com',
  277. 'at@sign.com',
  278. ]
  279. url = reverse('domain-list')
  280. for domainname in invalidnames:
  281. data = {'name': domainname}
  282. response = self.client.post(url, data)
  283. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  284. self.assertEqual(len(mail.outbox), outboxlen)