testdomains.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418
  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 django.db import transaction
  6. from desecapi.models import Domain
  7. from django.core import mail
  8. import httpretty
  9. from django.conf import settings
  10. import json
  11. class UnauthenticatedDomainTests(APITestCase):
  12. def testExpectUnauthorizedOnGet(self):
  13. url = reverse('domain-list')
  14. response = self.client.get(url, format='json')
  15. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  16. def testExpectUnauthorizedOnPost(self):
  17. url = reverse('domain-list')
  18. response = self.client.post(url, format='json')
  19. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  20. def testExpectUnauthorizedOnPut(self):
  21. url = reverse('domain-detail', args=(1,))
  22. response = self.client.put(url, format='json')
  23. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  24. def testExpectUnauthorizedOnDelete(self):
  25. url = reverse('domain-detail', args=(1,))
  26. response = self.client.delete(url, format='json')
  27. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  28. class AuthenticatedDomainTests(APITestCase):
  29. def setUp(self):
  30. if not hasattr(self, 'owner'):
  31. self.owner = utils.createUser()
  32. self.ownedDomains = [utils.createDomain(self.owner), utils.createDomain(self.owner)]
  33. self.otherDomains = [utils.createDomain(), utils.createDomain()]
  34. self.token = utils.createToken(user=self.owner)
  35. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  36. def tearDown(self):
  37. httpretty.reset()
  38. httpretty.disable()
  39. def testExpectOnlyOwnedDomains(self):
  40. url = reverse('domain-list')
  41. response = self.client.get(url, format='json')
  42. self.assertEqual(response.status_code, status.HTTP_200_OK)
  43. self.assertEqual(len(response.data), 2)
  44. self.assertEqual(response.data[0]['name'], self.ownedDomains[0].name)
  45. self.assertEqual(response.data[1]['name'], self.ownedDomains[1].name)
  46. def testCanDeleteOwnedDomain(self):
  47. httpretty.enable()
  48. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  49. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name+ '.')
  50. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  51. response = self.client.delete(url)
  52. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  53. self.assertEqual(httpretty.last_request().method, 'DELETE')
  54. self.assertEqual(httpretty.last_request().headers['Host'], 'nsmaster:8081')
  55. httpretty.reset()
  56. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  57. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name+ '.')
  58. response = self.client.get(url)
  59. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  60. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  61. def testCantDeleteOtherDomains(self):
  62. httpretty.enable()
  63. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.otherDomains[1].name + '.')
  64. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.otherDomains[1].name+ '.')
  65. url = reverse('domain-detail', args=(self.otherDomains[1].pk,))
  66. response = self.client.delete(url)
  67. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  68. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  69. self.assertTrue(Domain.objects.filter(pk=self.otherDomains[1].pk).exists())
  70. def testCanGetOwnedDomains(self):
  71. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  72. response = self.client.get(url)
  73. self.assertEqual(response.status_code, status.HTTP_200_OK)
  74. self.assertEqual(response.data['name'], self.ownedDomains[1].name)
  75. def testCantGetOtherDomains(self):
  76. url = reverse('domain-detail', args=(self.otherDomains[1].pk,))
  77. response = self.client.get(url)
  78. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  79. def testCanPutOwnedDomain(self):
  80. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  81. response = self.client.get(url)
  82. response.data['arecord'] = '1.2.3.4'
  83. response = self.client.put(url, json.dumps(response.data), content_type='application/json')
  84. self.assertEqual(response.status_code, status.HTTP_200_OK)
  85. response = self.client.get(url)
  86. self.assertEqual(response.status_code, status.HTTP_200_OK)
  87. self.assertEqual(response.data['arecord'], '1.2.3.4')
  88. def testCantChangeDomainName(self):
  89. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  90. response = self.client.get(url)
  91. newname = utils.generateDomainname()
  92. response.data['name'] = newname
  93. response.data['arecord'] = None
  94. response.data['aaaarecord'] = None
  95. response = self.client.put(url, json.dumps(response.data), content_type='application/json')
  96. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  97. response = self.client.get(url)
  98. self.assertEqual(response.status_code, status.HTTP_200_OK)
  99. self.assertEqual(response.data['name'], self.ownedDomains[1].name)
  100. def testCantPutOtherDomains(self):
  101. url = reverse('domain-detail', args=(self.otherDomains[1].pk,))
  102. response = self.client.put(url, json.dumps({}), content_type='application/json')
  103. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  104. def testCanPostDomains(self):
  105. url = reverse('domain-list')
  106. data = {'name': utils.generateDomainname()}
  107. response = self.client.post(url, data)
  108. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  109. self.assertEqual(len(mail.outbox), 0)
  110. def testCantPostSameDomainTwice(self):
  111. url = reverse('domain-list')
  112. data = {'name': utils.generateDomainname()}
  113. response = self.client.post(url, data)
  114. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  115. response = self.client.post(url, data)
  116. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  117. def testCantPostUnavailableDomain(self):
  118. name = utils.generateDomainname()
  119. httpretty.enable()
  120. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones',
  121. body='{"error": "Domain \'' + name + '.\' already exists"}', status=422)
  122. url = reverse('domain-list')
  123. data = {'name': name}
  124. response = self.client.post(url, data)
  125. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  126. def testCanPostComplicatedDomains(self):
  127. url = reverse('domain-list')
  128. data = {'name': 'very.long.domain.name.' + utils.generateDomainname()}
  129. response = self.client.post(url, data)
  130. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  131. def testCanUpdateARecord(self):
  132. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  133. response = self.client.get(url)
  134. response.data['arecord'] = '10.13.3.7'
  135. response.data['aaaarecord'] = None
  136. response = self.client.put(url, json.dumps(response.data), content_type='application/json')
  137. self.assertEqual(response.status_code, status.HTTP_200_OK)
  138. response = self.client.get(url)
  139. self.assertEqual(response.status_code, status.HTTP_200_OK)
  140. self.assertEqual(response.data['arecord'], '10.13.3.7')
  141. def testCanUpdateAAAARecord(self):
  142. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  143. response = self.client.get(url)
  144. response.data['arecord'] = None
  145. response.data['aaaarecord'] = 'fe80::a11:10ff:fee0:ff77'
  146. response = self.client.put(url, json.dumps(response.data), content_type='application/json')
  147. self.assertEqual(response.status_code, status.HTTP_200_OK)
  148. response = self.client.get(url)
  149. self.assertEqual(response.status_code, status.HTTP_200_OK)
  150. self.assertEqual(response.data['aaaarecord'], 'fe80::a11:10ff:fee0:ff77')
  151. def testCanUpdateAcmeChallenge(self):
  152. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  153. response = self.client.get(url)
  154. response.data['acme_challenge'] = 'test_challenge'
  155. response = self.client.put(url, json.dumps(response.data), content_type='application/json')
  156. self.assertEqual(response.status_code, status.HTTP_200_OK)
  157. response = self.client.get(url)
  158. self.assertEqual(response.status_code, status.HTTP_200_OK)
  159. self.assertEqual(response.data['acme_challenge'], 'test_challenge')
  160. def testPostingCausesPdnsAPICalls(self):
  161. name = utils.generateDomainname()
  162. httpretty.enable()
  163. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  164. httpretty.register_uri(httpretty.GET,
  165. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  166. body='{"rrsets": []}',
  167. content_type="application/json")
  168. url = reverse('domain-list')
  169. response = self.client.post(url, {'name': name})
  170. self.assertEqual(httpretty.httpretty.latest_requests[-2].method, 'POST')
  171. self.assertTrue(name in httpretty.httpretty.latest_requests[-2].parsed_body)
  172. self.assertTrue('ns1.desec.io' in httpretty.httpretty.latest_requests[-2].parsed_body)
  173. self.assertEqual(httpretty.last_request().method, 'GET')
  174. def testPostingWithRecordsCausesPdnsAPIPatch(self):
  175. name = utils.generateDomainname()
  176. httpretty.enable()
  177. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  178. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + name + '.')
  179. httpretty.register_uri(httpretty.GET,
  180. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  181. body='{"rrsets": []}',
  182. content_type="application/json")
  183. httpretty.register_uri(httpretty.PUT, settings.NSLORD_PDNS_API + '/zones/' + name + './notify')
  184. url = reverse('domain-list')
  185. data = {'name': name, 'arecord': '1.3.3.7', 'aaaarecord': 'dead::beef', 'acme_challenge': 'letsencrypt_ftw'}
  186. response = self.client.post(url, data)
  187. self.assertEqual(httpretty.httpretty.latest_requests[-2].method, 'PATCH')
  188. self.assertTrue(data['name'] in httpretty.httpretty.latest_requests[-2].parsed_body)
  189. self.assertTrue('1.3.3.7' in httpretty.httpretty.latest_requests[-2].parsed_body)
  190. self.assertTrue('dead::beef' in httpretty.httpretty.latest_requests[-2].parsed_body)
  191. self.assertTrue('letsencrypt_ftw' in httpretty.httpretty.latest_requests[-2].parsed_body)
  192. def testPostDomainCausesPdnsAPIPatch(self):
  193. name = utils.generateDomainname()
  194. httpretty.enable()
  195. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  196. httpretty.register_uri(httpretty.GET,
  197. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  198. body='{"rrsets": []}',
  199. content_type="application/json")
  200. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + name + '.')
  201. httpretty.register_uri(httpretty.PUT, settings.NSLORD_PDNS_API + '/zones/' + name + './notify')
  202. url = reverse('domain-list')
  203. data = {'name': name, 'acme_challenge': 'letsencrypt_ftw'}
  204. self.client.post(url, data)
  205. self.assertEqual(httpretty.httpretty.latest_requests[-2].method, 'PATCH')
  206. self.assertTrue(data['name'] in httpretty.httpretty.latest_requests[-2].parsed_body)
  207. self.assertTrue('letsencrypt_ftw' in httpretty.httpretty.latest_requests[-2].parsed_body)
  208. def testUpdateingCausesPdnsAPIPatchCall(self):
  209. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  210. response = self.client.get(url)
  211. httpretty.enable()
  212. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + response.data['name'] + '.')
  213. httpretty.register_uri(httpretty.PUT, settings.NSLORD_PDNS_API + '/zones/' + response.data['name'] + './notify')
  214. response.data['arecord'] = '10.13.3.7'
  215. self.client.put(url, json.dumps(response.data), content_type='application/json')
  216. self.assertTrue('10.13.3.7' in httpretty.httpretty.latest_requests[-2].parsed_body)
  217. def testUpdateingCausesPdnsAPINotifyCall(self):
  218. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  219. response = self.client.get(url)
  220. httpretty.enable()
  221. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + response.data['name'] + '.')
  222. httpretty.register_uri(httpretty.PUT, settings.NSLORD_PDNS_API + '/zones/' + response.data['name'] + './notify')
  223. response.data['arecord'] = '10.13.3.10'
  224. response = self.client.put(url, json.dumps(response.data), content_type='application/json')
  225. self.assertEqual(httpretty.httpretty.latest_requests[-2].method, 'PATCH')
  226. self.assertTrue('10.13.3.10' in httpretty.httpretty.latest_requests[-2].parsed_body)
  227. self.assertEqual(httpretty.last_request().method, 'PUT')
  228. def testDomainDetailURL(self):
  229. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  230. urlByName = reverse('domain-detail/byName', args=(self.ownedDomains[1].name,))
  231. self.assertTrue(("/%d" % self.ownedDomains[1].pk) in url)
  232. self.assertTrue("/" + self.ownedDomains[1].name in urlByName)
  233. def testRollback(self):
  234. name = utils.generateDomainname()
  235. httpretty.enable()
  236. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones', body="some error", status=500)
  237. url = reverse('domain-list')
  238. data = {'name': name}
  239. try:
  240. response = self.client.post(url, data)
  241. except:
  242. pass
  243. self.assertFalse(Domain.objects.filter(name=name).exists())
  244. class AuthenticatedDynDomainTests(APITestCase):
  245. def setUp(self):
  246. if not hasattr(self, 'owner'):
  247. self.owner = utils.createUser(dyn=True)
  248. self.ownedDomains = [utils.createDomain(self.owner, dyn=True), utils.createDomain(self.owner, dyn=True)]
  249. self.otherDomains = [utils.createDomain(), utils.createDomain()]
  250. self.token = utils.createToken(user=self.owner)
  251. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  252. def tearDown(self):
  253. httpretty.reset()
  254. httpretty.disable()
  255. def testCanDeleteOwnedDynDomain(self):
  256. httpretty.enable()
  257. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  258. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name+ '.')
  259. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/dedyn.io.')
  260. url = reverse('domain-detail', args=(self.ownedDomains[1].pk,))
  261. response = self.client.delete(url)
  262. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  263. self.assertEqual(httpretty.last_request().method, 'PATCH')
  264. self.assertEqual(httpretty.last_request().headers['Host'], 'nslord:8081')
  265. self.assertTrue('"NS"' in httpretty.last_request().parsed_body)
  266. self.assertTrue('"' + self.ownedDomains[1].name + '."' in httpretty.last_request().parsed_body)
  267. self.assertTrue('"DELETE"' in httpretty.last_request().parsed_body)
  268. httpretty.reset()
  269. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  270. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.ownedDomains[1].name+ '.')
  271. response = self.client.get(url)
  272. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  273. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  274. def testCantDeleteOtherDynDomains(self):
  275. httpretty.enable()
  276. httpretty.register_uri(httpretty.DELETE, settings.NSLORD_PDNS_API + '/zones/' + self.otherDomains[1].name + '.')
  277. httpretty.register_uri(httpretty.DELETE, settings.NSMASTER_PDNS_API + '/zones/' + self.otherDomains[1].name+ '.')
  278. url = reverse('domain-detail', args=(self.otherDomains[1].pk,))
  279. response = self.client.delete(url)
  280. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  281. self.assertTrue(isinstance(httpretty.last_request(), httpretty.core.HTTPrettyRequestEmpty))
  282. self.assertTrue(Domain.objects.filter(pk=self.otherDomains[1].pk).exists())
  283. def testCanPostDynDomains(self):
  284. url = reverse('domain-list')
  285. data = {'name': utils.generateDynDomainname()}
  286. response = self.client.post(url, data)
  287. email = str(mail.outbox[0].message())
  288. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  289. self.assertEqual(len(mail.outbox), 1)
  290. self.assertTrue(data['name'] in email)
  291. self.assertTrue(self.token in email)
  292. def testCantPostNonDynDomains(self):
  293. url = reverse('domain-list')
  294. data = {'name': utils.generateDomainname()}
  295. response = self.client.post(url, data)
  296. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  297. data = {'name': 'very.long.domain.' + utils.generateDynDomainname()}
  298. response = self.client.post(url, data)
  299. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  300. def testLimitDynDomains(self):
  301. httpretty.enable()
  302. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  303. outboxlen = len(mail.outbox)
  304. url = reverse('domain-list')
  305. for i in range(settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT-2):
  306. name = utils.generateDynDomainname()
  307. httpretty.register_uri(httpretty.GET,
  308. settings.NSLORD_PDNS_API + '/zones/' + name + '.',
  309. body='{"rrsets": []}',
  310. content_type="application/json")
  311. response = self.client.post(url, {'name': name})
  312. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  313. self.assertEqual(len(mail.outbox), outboxlen+i+1)
  314. data = {'name': utils.generateDynDomainname()}
  315. response = self.client.post(url, data)
  316. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  317. self.assertEqual(len(mail.outbox), outboxlen + settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT-2)
  318. def testCantUseInvalidCharactersInDomainNamePDNS(self):
  319. httpretty.enable()
  320. httpretty.register_uri(httpretty.POST, settings.NSLORD_PDNS_API + '/zones')
  321. outboxlen = len(mail.outbox)
  322. invalidnames = [
  323. 'with space.dedyn.io',
  324. 'another space.de',
  325. ' spaceatthebeginning.com',
  326. 'percentage%sign.com',
  327. '%percentagesign.dedyn.io',
  328. 'slash/desec.io',
  329. '/slashatthebeginning.dedyn.io',
  330. '\\backslashatthebeginning.dedyn.io',
  331. 'backslash\\inthemiddle.at',
  332. '@atsign.com',
  333. 'at@sign.com',
  334. ]
  335. url = reverse('domain-list')
  336. for domainname in invalidnames:
  337. data = {'name': domainname}
  338. response = self.client.post(url, data)
  339. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  340. self.assertEqual(len(mail.outbox), outboxlen)