testrrsets.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674
  1. from rest_framework.reverse import reverse
  2. from rest_framework import status
  3. from rest_framework.test import APITestCase
  4. from desecapi.tests.utils import utils
  5. import httpretty
  6. from django.conf import settings
  7. import json
  8. from django.core.management import call_command
  9. from django.utils import timezone
  10. class UnauthenticatedDomainTests(APITestCase):
  11. def testExpectUnauthorizedOnGet(self):
  12. url = reverse('v1:rrsets', args=('example.com',))
  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('v1:rrsets', args=('example.com',))
  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('v1:rrsets', 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('v1:rrsets', args=('example.com',))
  25. response = self.client.delete(url, format='json')
  26. self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
  27. class AuthenticatedRRsetTests(APITestCase):
  28. dead_types = ('ALIAS', 'DNAME')
  29. restricted_types = ('SOA', 'RRSIG', 'DNSKEY', 'NSEC3PARAM')
  30. def setUp(self):
  31. if not hasattr(self, 'owner'):
  32. self.owner = utils.createUser()
  33. self.ownedDomains = [utils.createDomain(self.owner), utils.createDomain(self.owner)]
  34. self.token = utils.createToken(user=self.owner)
  35. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  36. self.otherOwner = utils.createUser()
  37. self.otherDomains = [utils.createDomain(self.otherOwner), utils.createDomain()]
  38. self.otherToken = utils.createToken(user=self.otherOwner)
  39. httpretty.reset()
  40. httpretty.enable(allow_net_connect=False)
  41. for domain in self.ownedDomains + self.otherDomains:
  42. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + domain.name + '.')
  43. httpretty.register_uri(httpretty.PUT,
  44. settings.NSLORD_PDNS_API + '/zones/' + domain.name + './notify')
  45. def testCanGetOwnRRsets(self):
  46. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  47. response = self.client.get(url)
  48. self.assertEqual(response.status_code, status.HTTP_200_OK)
  49. self.assertEqual(len(response.data), 0) # NS RRset unavailable in mock pdns environment
  50. def testCantGetForeignRRsets(self):
  51. url = reverse('v1:rrsets', args=(self.otherDomains[1].name,))
  52. response = self.client.get(url)
  53. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  54. def testCanGetOwnRRsetsEmptySubname(self):
  55. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  56. response = self.client.get(url + '?subname=')
  57. self.assertEqual(response.status_code, status.HTTP_200_OK)
  58. self.assertEqual(len(response.data), 0) # NS RRset unavailable in mock pdns environment
  59. def testCanGetOwnRRsetsFromSubname(self):
  60. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  61. data = {'records': ['1.2.3.4'], 'ttl': 120, 'type': 'A'}
  62. response = self.client.post(url, json.dumps(data), content_type='application/json')
  63. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  64. data = {'records': ['2.2.3.4'], 'ttl': 120, 'type': 'A', 'subname': 'test'}
  65. response = self.client.post(url, json.dumps(data), content_type='application/json')
  66. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  67. data = {'records': ['"test"'], 'ttl': 120, 'type': 'TXT', 'subname': 'test'}
  68. response = self.client.post(url, json.dumps(data), content_type='application/json')
  69. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  70. response = self.client.get(url)
  71. self.assertEqual(response.status_code, status.HTTP_200_OK)
  72. self.assertEqual(len(response.data), 3) # NS RRset unavailable in mock pdns environment
  73. response = self.client.get(url + '?subname=test')
  74. self.assertEqual(response.status_code, status.HTTP_200_OK)
  75. self.assertEqual(len(response.data), 2)
  76. def testCantGetForeignRRsetsFromSubname(self):
  77. url = reverse('v1:rrsets', args=(self.otherDomains[1].name,))
  78. response = self.client.get(url + '?subname=test')
  79. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  80. def testCanGetOwnRRsetsFromType(self):
  81. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  82. data = {'records': ['1.2.3.4'], 'ttl': 120, 'type': 'A'}
  83. response = self.client.post(url, json.dumps(data), content_type='application/json')
  84. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  85. data = {'records': ['2.2.3.4'], 'ttl': 120, 'type': 'A', 'subname': 'test'}
  86. response = self.client.post(url, json.dumps(data), content_type='application/json')
  87. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  88. data = {'records': ['"test"'], 'ttl': 120, 'type': 'TXT', 'subname': 'test'}
  89. response = self.client.post(url, json.dumps(data), content_type='application/json')
  90. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  91. response = self.client.get(url)
  92. self.assertEqual(response.status_code, status.HTTP_200_OK)
  93. self.assertEqual(len(response.data), 3) # NS RRset unavailable in mock pdns environment
  94. response = self.client.get(url + '?type=A')
  95. self.assertEqual(response.status_code, status.HTTP_200_OK)
  96. self.assertEqual(len(response.data), 2)
  97. def testCantGetForeignRRsetsFromType(self):
  98. url = reverse('v1:rrsets', args=(self.otherDomains[1].name,))
  99. response = self.client.get(url + '?test=A')
  100. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  101. def testCanPostOwnRRsets(self):
  102. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  103. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  104. response = self.client.post(url, json.dumps(data), content_type='application/json')
  105. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  106. response = self.client.get(url)
  107. self.assertEqual(response.status_code, status.HTTP_200_OK)
  108. self.assertEqual(len(response.data), 1) # NS RRset unavailable in mock pdns environment
  109. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', 'A',))
  110. response = self.client.get(url)
  111. self.assertEqual(response.status_code, status.HTTP_200_OK)
  112. self.assertEqual(response.data['records'][0], '1.2.3.4')
  113. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  114. data = {'records': ['desec.io.'], 'ttl': 900, 'type': 'PTR'}
  115. response = self.client.post(url, json.dumps(data), content_type='application/json')
  116. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  117. def testCantPostEmptyRRset(self):
  118. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  119. data = {'records': [], 'ttl': 60, 'type': 'A'}
  120. response = self.client.post(url, json.dumps(data), content_type='application/json')
  121. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  122. data = {'ttl': 60, 'type': 'A'}
  123. response = self.client.post(url, json.dumps(data), content_type='application/json')
  124. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  125. def testCantPostDeadTypes(self):
  126. for type_ in self.dead_types:
  127. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  128. data = {'records': ['www.example.com.'], 'ttl': 60, 'type': type_}
  129. response = self.client.post(url, json.dumps(data), content_type='application/json')
  130. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  131. def testCantPostRestrictedTypes(self):
  132. for type_ in self.restricted_types:
  133. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  134. data = {'records': ['ns1.desec.io. peter.desec.io. 2584 10800 3600 604800 60'], 'ttl': 60, 'type': type_}
  135. response = self.client.post(url, json.dumps(data), content_type='application/json')
  136. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  137. def testCantPostForeignRRsets(self):
  138. url = reverse('v1:rrsets', args=(self.otherDomains[1].name,))
  139. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  140. response = self.client.post(url, json.dumps(data), content_type='application/json')
  141. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  142. def testCantPostTwiceRRsets(self):
  143. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  144. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  145. response = self.client.post(url, json.dumps(data), content_type='application/json')
  146. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  147. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  148. data = {'records': ['3.2.2.1'], 'ttl': 60, 'type': 'A'}
  149. response = self.client.post(url, json.dumps(data), content_type='application/json')
  150. self.assertEqual(response.status_code, status.HTTP_409_CONFLICT)
  151. def testCantPostFaultyRRsets(self):
  152. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  153. # New record without a value is a syntactical error --> 400
  154. data = {'records': [], 'ttl': 60, 'type': 'TXT'}
  155. response = self.client.post(url, json.dumps(data), content_type='application/json')
  156. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  157. # Lower-case type is a syntactical error --> 400
  158. data = {'records': ['123456'], 'ttl': 60, 'type': 'txt'}
  159. response = self.client.post(url, json.dumps(data), content_type='application/json')
  160. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  161. # Unknown type is a semantical error --> 422
  162. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  163. data = {'records': ['123456'], 'ttl': 60, 'type': 'AA'}
  164. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.',
  165. body='', status=422)
  166. response = self.client.post(url, json.dumps(data), content_type='application/json')
  167. self.assertEqual(response.status_code, status.HTTP_422_UNPROCESSABLE_ENTITY)
  168. def testCanGetOwnRRset(self):
  169. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  170. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  171. response = self.client.post(url, json.dumps(data), content_type='application/json')
  172. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  173. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', 'A',))
  174. response = self.client.get(url)
  175. self.assertEqual(response.status_code, status.HTTP_200_OK)
  176. self.assertEqual(response.data['records'][0], '1.2.3.4')
  177. self.assertEqual(response.data['ttl'], 60)
  178. def testCanGetOwnRRsetApex(self):
  179. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  180. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  181. response = self.client.post(url, json.dumps(data), content_type='application/json')
  182. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  183. url = reverse('v1:rrset@', args=(self.ownedDomains[1].name, '', 'A',))
  184. response = self.client.get(url)
  185. self.assertEqual(response.status_code, status.HTTP_200_OK)
  186. self.assertEqual(response.data['records'][0], '1.2.3.4')
  187. self.assertEqual(response.data['ttl'], 60)
  188. def testCantGetRestrictedTypes(self):
  189. for type_ in self.restricted_types:
  190. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  191. response = self.client.get(url + '?type=%s' % type_)
  192. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  193. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', type_,))
  194. response = self.client.get(url)
  195. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  196. def testCantGetForeignRRset(self):
  197. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.otherToken)
  198. url = reverse('v1:rrsets', args=(self.otherDomains[0].name,))
  199. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  200. response = self.client.post(url, json.dumps(data), content_type='application/json')
  201. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  202. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  203. url = reverse('v1:rrset', args=(self.otherDomains[0].name, '', 'A',))
  204. response = self.client.get(url)
  205. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  206. def testCanGetOwnRRsetWithSubname(self):
  207. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  208. data = {'records': ['1.2.3.4'], 'ttl': 120, 'type': 'A'}
  209. response = self.client.post(url, json.dumps(data), content_type='application/json')
  210. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  211. data = {'records': ['2.2.3.4'], 'ttl': 120, 'type': 'A', 'subname': 'test'}
  212. response = self.client.post(url, json.dumps(data), content_type='application/json')
  213. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  214. data = {'records': ['"test"'], 'ttl': 120, 'type': 'TXT', 'subname': 'test'}
  215. response = self.client.post(url, json.dumps(data), content_type='application/json')
  216. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  217. response = self.client.get(url)
  218. self.assertEqual(response.status_code, status.HTTP_200_OK)
  219. self.assertEqual(len(response.data), 3, response.data) # NS RRset unavailable in mock pdns environment
  220. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, 'test', 'A',))
  221. response = self.client.get(url)
  222. self.assertEqual(response.status_code, status.HTTP_200_OK)
  223. self.assertEqual(response.data['records'][0], '2.2.3.4')
  224. self.assertEqual(response.data['ttl'], 120)
  225. self.assertEqual(response.data['name'], 'test.' + self.ownedDomains[1].name + '.')
  226. def testCanGetOwnRRsetWithWildcard(self):
  227. for subname in ('*', '*.foobar'):
  228. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  229. data = {'records': ['"barfoo"'], 'ttl': 120, 'type': 'TXT', 'subname': subname}
  230. response = self.client.post(url, json.dumps(data), content_type='application/json')
  231. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  232. response1 = self.client.get(url + '?subname=' + subname)
  233. self.assertEqual(response1.status_code, status.HTTP_200_OK)
  234. self.assertEqual(response1.data[0]['records'][0], '"barfoo"')
  235. self.assertEqual(response1.data[0]['ttl'], 120)
  236. self.assertEqual(response1.data[0]['name'], subname + '.' + self.ownedDomains[1].name + '.')
  237. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, subname, 'TXT',))
  238. response2 = self.client.get(url)
  239. self.assertEqual(response2.data, response1.data[0])
  240. def testCanPutOwnRRset(self):
  241. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  242. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  243. response = self.client.post(url, json.dumps(data), content_type='application/json')
  244. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  245. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', 'A',))
  246. data = {'records': ['2.2.3.4'], 'ttl': 30}
  247. response = self.client.put(url, json.dumps(data), content_type='application/json')
  248. self.assertEqual(response.status_code, status.HTTP_200_OK)
  249. response = self.client.get(url)
  250. self.assertEqual(response.status_code, status.HTTP_200_OK)
  251. self.assertEqual(response.data['records'][0], '2.2.3.4')
  252. self.assertEqual(response.data['ttl'], 30)
  253. data = {'records': ['3.2.3.4']}
  254. response = self.client.put(url, json.dumps(data), content_type='application/json')
  255. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  256. data = {'ttl': 37}
  257. response = self.client.put(url, json.dumps(data), content_type='application/json')
  258. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  259. def testCanPutOwnRRsetApex(self):
  260. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  261. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  262. response = self.client.post(url, json.dumps(data), content_type='application/json')
  263. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  264. url = reverse('v1:rrset@', args=(self.ownedDomains[1].name, '', 'A',))
  265. data = {'records': ['2.2.3.4'], 'ttl': 30}
  266. response = self.client.put(url, json.dumps(data), content_type='application/json')
  267. self.assertEqual(response.status_code, status.HTTP_200_OK)
  268. response = self.client.get(url)
  269. self.assertEqual(response.status_code, status.HTTP_200_OK)
  270. self.assertEqual(response.data['records'][0], '2.2.3.4')
  271. self.assertEqual(response.data['ttl'], 30)
  272. data = {'records': ['3.2.3.4']}
  273. response = self.client.put(url, json.dumps(data), content_type='application/json')
  274. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  275. data = {'ttl': 37}
  276. response = self.client.put(url, json.dumps(data), content_type='application/json')
  277. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  278. def testCanPatchOwnRRset(self):
  279. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  280. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  281. response = self.client.post(url, json.dumps(data), content_type='application/json')
  282. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  283. # Change records and TTL
  284. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', 'A',))
  285. data = {'records': ['3.2.3.4'], 'ttl': 32}
  286. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  287. self.assertEqual(response.status_code, status.HTTP_200_OK)
  288. response = self.client.get(url)
  289. self.assertEqual(response.status_code, status.HTTP_200_OK)
  290. self.assertEqual(response.data['records'][0], '3.2.3.4')
  291. self.assertEqual(response.data['ttl'], 32)
  292. # Change records alone
  293. data = {'records': ['5.2.3.4']}
  294. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  295. self.assertEqual(response.status_code, status.HTTP_200_OK)
  296. self.assertEqual(response.data['records'][0], '5.2.3.4')
  297. self.assertEqual(response.data['ttl'], 32)
  298. # Change TTL alone
  299. data = {'ttl': 37}
  300. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  301. self.assertEqual(response.status_code, status.HTTP_200_OK)
  302. self.assertEqual(response.data['records'][0], '5.2.3.4')
  303. self.assertEqual(response.data['ttl'], 37)
  304. # Change nothing
  305. data = {}
  306. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  307. self.assertEqual(response.status_code, status.HTTP_200_OK)
  308. self.assertEqual(response.data['records'][0], '5.2.3.4')
  309. self.assertEqual(response.data['ttl'], 37)
  310. def testCanPatchOwnRRsetApex(self):
  311. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  312. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  313. response = self.client.post(url, json.dumps(data), content_type='application/json')
  314. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  315. # Change records and TTL
  316. url = reverse('v1:rrset@', args=(self.ownedDomains[1].name, '', 'A',))
  317. data = {'records': ['3.2.3.4'], 'ttl': 32}
  318. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  319. self.assertEqual(response.status_code, status.HTTP_200_OK)
  320. response = self.client.get(url)
  321. self.assertEqual(response.status_code, status.HTTP_200_OK)
  322. self.assertEqual(response.data['records'][0], '3.2.3.4')
  323. self.assertEqual(response.data['ttl'], 32)
  324. # Change records alone
  325. data = {'records': ['5.2.3.4']}
  326. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  327. self.assertEqual(response.status_code, status.HTTP_200_OK)
  328. self.assertEqual(response.data['records'][0], '5.2.3.4')
  329. self.assertEqual(response.data['ttl'], 32)
  330. # Change TTL alone
  331. data = {'ttl': 37}
  332. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  333. self.assertEqual(response.status_code, status.HTTP_200_OK)
  334. self.assertEqual(response.data['records'][0], '5.2.3.4')
  335. self.assertEqual(response.data['ttl'], 37)
  336. # Change nothing
  337. data = {}
  338. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  339. self.assertEqual(response.status_code, status.HTTP_200_OK)
  340. self.assertEqual(response.data['records'][0], '5.2.3.4')
  341. self.assertEqual(response.data['ttl'], 37)
  342. def testCantChangeForeignRRset(self):
  343. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.otherToken)
  344. url = reverse('v1:rrsets', args=(self.otherDomains[0].name,))
  345. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  346. response = self.client.post(url, json.dumps(data), content_type='application/json')
  347. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  348. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  349. url = reverse('v1:rrset', args=(self.otherDomains[0].name, '', 'A',))
  350. data = {'records': ['3.2.3.4'], 'ttl': 30, 'type': 'A'}
  351. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  352. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  353. response = self.client.put(url, json.dumps(data), content_type='application/json')
  354. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  355. def testCantChangeForeignRRsetApex(self):
  356. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.otherToken)
  357. url = reverse('v1:rrsets', args=(self.otherDomains[0].name,))
  358. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  359. response = self.client.post(url, json.dumps(data), content_type='application/json')
  360. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  361. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  362. url = reverse('v1:rrset@', args=(self.otherDomains[0].name, '', 'A',))
  363. data = {'records': ['3.2.3.4'], 'ttl': 30, 'type': 'A'}
  364. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  365. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  366. response = self.client.put(url, json.dumps(data), content_type='application/json')
  367. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  368. def testCantChangeEssentialProperties(self):
  369. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  370. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A', 'subname': 'test1'}
  371. response = self.client.post(url, json.dumps(data), content_type='application/json')
  372. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  373. # Changing the subname is expected to cause an error
  374. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, 'test1', 'A',))
  375. data = {'records': ['3.2.3.4'], 'ttl': 120, 'subname': 'test2'}
  376. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  377. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  378. response = self.client.put(url, json.dumps(data), content_type='application/json')
  379. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  380. # Changing the type is expected to cause an error
  381. data = {'records': ['3.2.3.4'], 'ttl': 120, 'type': 'TXT'}
  382. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  383. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  384. response = self.client.put(url, json.dumps(data), content_type='application/json')
  385. self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
  386. # Check that nothing changed
  387. response = self.client.get(url)
  388. self.assertEqual(response.status_code, status.HTTP_200_OK)
  389. self.assertEqual(response.data['records'][0], '1.2.3.4')
  390. self.assertEqual(response.data['ttl'], 60)
  391. self.assertEqual(response.data['name'], 'test1.' + self.ownedDomains[1].name + '.')
  392. self.assertEqual(response.data['subname'], 'test1')
  393. self.assertEqual(response.data['type'], 'A')
  394. # This is expected to work, but the fields are ignored
  395. data = {'records': ['3.2.3.4'], 'name': 'example.com.', 'domain': 'example.com'}
  396. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  397. self.assertEqual(response.status_code, status.HTTP_200_OK)
  398. response = self.client.get(url)
  399. self.assertEqual(response.status_code, status.HTTP_200_OK)
  400. self.assertEqual(response.data['records'][0], '3.2.3.4')
  401. self.assertEqual(response.data['domain'], self.ownedDomains[1].name)
  402. self.assertEqual(response.data['name'], 'test1.' + self.ownedDomains[1].name + '.')
  403. def testCanDeleteOwnRRset(self):
  404. # Try PATCH with empty records
  405. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  406. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  407. response = self.client.post(url, json.dumps(data), content_type='application/json')
  408. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  409. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', 'A',))
  410. data = {'records': []}
  411. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  412. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  413. response = self.client.get(url)
  414. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  415. # Try DELETE
  416. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  417. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  418. response = self.client.post(url, json.dumps(data), content_type='application/json')
  419. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  420. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', 'A',))
  421. response = self.client.delete(url)
  422. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  423. response = self.client.get(url)
  424. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  425. def testCanDeleteOwnRRsetApex(self):
  426. # Try PATCH with empty records
  427. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  428. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  429. response = self.client.post(url, json.dumps(data), content_type='application/json')
  430. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  431. url = reverse('v1:rrset@', args=(self.ownedDomains[1].name, '', 'A',))
  432. data = {'records': []}
  433. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  434. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  435. response = self.client.get(url)
  436. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  437. # Try DELETE
  438. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  439. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  440. response = self.client.post(url, json.dumps(data), content_type='application/json')
  441. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  442. url = reverse('v1:rrset@', args=(self.ownedDomains[1].name, '', 'A',))
  443. response = self.client.delete(url)
  444. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  445. response = self.client.get(url)
  446. self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
  447. def testCantDeleteForeignRRset(self):
  448. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.otherToken)
  449. url = reverse('v1:rrsets', args=(self.otherDomains[0].name,))
  450. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  451. response = self.client.post(url, json.dumps(data), content_type='application/json')
  452. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  453. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.token)
  454. url = reverse('v1:rrset', args=(self.otherDomains[0].name, '', 'A',))
  455. # Try PATCH with empty records
  456. data = {'records': []}
  457. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  458. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  459. # Try DELETE
  460. response = self.client.delete(url)
  461. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  462. # Make sure it actually is still there
  463. self.client.credentials(HTTP_AUTHORIZATION='Token ' + self.otherToken)
  464. url = reverse('v1:rrset@', args=(self.otherDomains[0].name, '', 'A',))
  465. response = self.client.get(url)
  466. self.assertEqual(response.status_code, status.HTTP_200_OK)
  467. self.assertEqual(response.data['records'][0], '1.2.3.4')
  468. def testCantCreateRRsetWhileAccountIsLocked(self):
  469. self.owner.locked = timezone.now()
  470. self.owner.save()
  471. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  472. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  473. response = self.client.post(url, json.dumps(data), content_type='application/json')
  474. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  475. def testCantModifyRRsetWhileAccountIsLocked(self):
  476. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  477. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  478. response = self.client.post(url, json.dumps(data), content_type='application/json')
  479. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  480. self.owner.locked = timezone.now()
  481. self.owner.save()
  482. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', 'A',))
  483. # Try PUT
  484. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  485. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  486. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  487. # Try PATCH
  488. data = {'records': ['4.3.2.1']}
  489. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  490. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  491. # Try PATCH to delete
  492. data = {'records': []}
  493. response = self.client.patch(url, json.dumps(data), content_type='application/json')
  494. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  495. # Try DELETE
  496. response = self.client.delete(url)
  497. self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
  498. def testPostCausesPdnsAPICall(self):
  499. httpretty.enable(allow_net_connect=False)
  500. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  501. httpretty.register_uri(httpretty.PUT, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + './notify')
  502. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  503. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  504. self.client.post(url, json.dumps(data), content_type='application/json')
  505. result = json.loads(httpretty.httpretty.latest_requests[-2].parsed_body)
  506. self.assertEqual(result['rrsets'][0]['name'], self.ownedDomains[1].name + '.')
  507. self.assertEqual(result['rrsets'][0]['records'][0]['content'], '1.2.3.4')
  508. self.assertEqual(httpretty.last_request().method, 'PUT')
  509. def testDeleteCausesPdnsAPICall(self):
  510. httpretty.enable(allow_net_connect=False)
  511. httpretty.register_uri(httpretty.PATCH, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.')
  512. httpretty.register_uri(httpretty.PUT, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + './notify')
  513. # Create record, should cause a pdns PATCH request and a notify
  514. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  515. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  516. self.client.post(url, json.dumps(data), content_type='application/json')
  517. # Delete record, should cause a pdns PATCH request and a notify
  518. url = reverse('v1:rrset', args=(self.ownedDomains[1].name, '', 'A',))
  519. response = self.client.delete(url)
  520. self.assertEqual(response.status_code, status.HTTP_204_NO_CONTENT)
  521. # Check pdns requests from creation
  522. result = json.loads(httpretty.httpretty.latest_requests[-4].parsed_body)
  523. self.assertEqual(result['rrsets'][0]['name'], self.ownedDomains[1].name + '.')
  524. self.assertEqual(result['rrsets'][0]['records'][0]['content'], '1.2.3.4')
  525. self.assertEqual(httpretty.httpretty.latest_requests[-3].method, 'PUT')
  526. # Check pdns requests from deletion
  527. result = json.loads(httpretty.httpretty.latest_requests[-2].parsed_body)
  528. self.assertEqual(result['rrsets'][0]['name'], self.ownedDomains[1].name + '.')
  529. self.assertEqual(result['rrsets'][0]['records'], [])
  530. self.assertEqual(httpretty.httpretty.latest_requests[-1].method, 'PUT')
  531. def testImportRRsets(self):
  532. url = reverse('v1:rrsets', args=(self.ownedDomains[1].name,))
  533. data = {'records': ['1.2.3.4'], 'ttl': 60, 'type': 'A'}
  534. response = self.client.post(url, json.dumps(data), content_type='application/json')
  535. self.assertEqual(response.status_code, status.HTTP_201_CREATED)
  536. # Not checking anything here; errors will raise an exception
  537. httpretty.register_uri(httpretty.GET, settings.NSLORD_PDNS_API + '/zones/' + self.ownedDomains[1].name + '.',
  538. status=200, body='{"rrsets":[{"name":"asdf","type":"A",' +
  539. '"records":[{"content":"1.1.1.1"},{"content":"2.2.2.2"}],"ttl":20}]}')
  540. call_command('sync-from-pdns', self.ownedDomains[1].name)