test_dyndns12update.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313
  1. import random
  2. from rest_framework import status
  3. from desecapi.tests.base import DynDomainOwnerTestCase
  4. class DynDNS12UpdateTest(DynDomainOwnerTestCase):
  5. def assertIP(self, ipv4=None, ipv6=None, name=None, subname=""):
  6. name = name or self.my_domain.name.lower()
  7. for type_, value in [("A", ipv4), ("AAAA", ipv6)]:
  8. url = self.reverse("v1:rrset", name=name, subname=subname, type=type_)
  9. response = self.client_token_authorized.get(url)
  10. if value:
  11. if not isinstance(value, set):
  12. value = {value}
  13. self.assertStatus(response, status.HTTP_200_OK)
  14. self.assertEqual(set(response.data["records"]), value)
  15. self.assertEqual(response.data["ttl"], 60)
  16. else:
  17. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  18. def test_identification_by_domain_name(self):
  19. self.client.set_credentials_basic_auth(
  20. self.my_domain.name + ".invalid", self.token.plain
  21. )
  22. response = self.assertDynDNS12NoUpdate(mock_remote_addr="10.5.5.6")
  23. self.assertStatus(response, status.HTTP_401_UNAUTHORIZED)
  24. def test_identification_by_query_params(self):
  25. # /update?username=foobar.dedyn.io&password=secret
  26. self.client.set_credentials_basic_auth(None, None)
  27. response = self.assertDynDNS12Update(
  28. username=self.my_domain.name, password=self.token.plain
  29. )
  30. self.assertStatus(response, status.HTTP_200_OK)
  31. self.assertEqual(response.data, "good")
  32. self.assertEqual(response.content_type, "text/plain")
  33. self.assertIP(ipv4="127.0.0.1")
  34. def test_identification_by_query_params_with_subdomain(self):
  35. # /update?username=baz.foobar.dedyn.io&password=secret
  36. self.client.set_credentials_basic_auth(None, None)
  37. response = self.assertDynDNS12NoUpdate(
  38. username="baz", password=self.token.plain
  39. )
  40. self.assertStatus(response, status.HTTP_401_UNAUTHORIZED)
  41. self.assertEqual(response.content, b"badauth")
  42. for subname in ["baz", "*.baz"]:
  43. response = self.assertDynDNS12Update(
  44. username=f"{subname}.{self.my_domain.name}", password=self.token.plain
  45. )
  46. self.assertStatus(response, status.HTTP_200_OK)
  47. self.assertEqual(response.data, "good")
  48. self.assertIP(ipv4="127.0.0.1", subname=subname)
  49. def test_deviant_ttl(self):
  50. """
  51. The dynamic update will try to set the TTL to 60. Here, we create
  52. a record with a different TTL beforehand and then make sure that
  53. updates still work properly.
  54. """
  55. with self.assertPdnsRequests(
  56. self.request_pdns_zone_update(self.my_domain.name),
  57. self.request_pdns_zone_axfr(self.my_domain.name),
  58. ):
  59. response = self.client_token_authorized.patch_rr_set(
  60. self.my_domain.name.lower(), "", "A", {"ttl": 3600}
  61. )
  62. self.assertStatus(response, status.HTTP_200_OK)
  63. response = self.assertDynDNS12Update(self.my_domain.name)
  64. self.assertStatus(response, status.HTTP_200_OK)
  65. self.assertEqual(response.data, "good")
  66. self.assertIP(ipv4="127.0.0.1")
  67. def test_ddclient_dyndns1_v4_success(self):
  68. # /nic/dyndns?action=edit&started=1&hostname=YES&host_id=foobar.dedyn.io&myip=10.1.2.3
  69. with self.assertPdnsRequests(
  70. self.request_pdns_zone_update(self.my_domain.name),
  71. self.request_pdns_zone_axfr(self.my_domain.name),
  72. ):
  73. response = self.client.get(
  74. self.reverse("v1:dyndns12update"),
  75. {
  76. "action": "edit",
  77. "started": 1,
  78. "hostname": "YES",
  79. "host_id": self.my_domain.name,
  80. "myip": "10.1.2.3",
  81. },
  82. )
  83. self.assertStatus(response, status.HTTP_200_OK)
  84. self.assertEqual(response.data, "good")
  85. self.assertIP(ipv4="10.1.2.3")
  86. # Repeat and make sure that no pdns request is made (not even for the empty AAAA record)
  87. response = self.client.get(
  88. self.reverse("v1:dyndns12update"),
  89. {
  90. "action": "edit",
  91. "started": 1,
  92. "hostname": "YES",
  93. "host_id": self.my_domain.name,
  94. "myip": "10.1.2.3",
  95. },
  96. )
  97. self.assertStatus(response, status.HTTP_200_OK)
  98. def test_ddclient_dyndns1_v6_success(self):
  99. # /nic/dyndns?action=edit&started=1&hostname=YES&host_id=foobar.dedyn.io&myipv6=::1337
  100. response = self.assertDynDNS12Update(
  101. domain_name=self.my_domain.name,
  102. action="edit",
  103. started=1,
  104. hostname="YES",
  105. host_id=self.my_domain.name,
  106. myipv6="::1337",
  107. )
  108. self.assertStatus(response, status.HTTP_200_OK)
  109. self.assertEqual(response.data, "good")
  110. self.assertIP(ipv4="127.0.0.1", ipv6="::1337")
  111. # Repeat and make sure that no pdns request is made (not even for the empty A record)
  112. response = self.client.get(
  113. self.reverse("v1:dyndns12update"),
  114. {
  115. "domain_name": self.my_domain.name,
  116. "action": "edit",
  117. "started": 1,
  118. "hostname": "YES",
  119. "host_id": self.my_domain.name,
  120. "myipv6": "::1337",
  121. },
  122. )
  123. self.assertStatus(response, status.HTTP_200_OK)
  124. def test_ddclient_dyndns2_v4_success(self):
  125. # /nic/update?system=dyndns&hostname=foobar.dedyn.io&myip=10.2.3.4
  126. response = self.assertDynDNS12Update(
  127. domain_name=self.my_domain.name,
  128. system="dyndns",
  129. hostname=self.my_domain.name,
  130. myip="10.2.3.4",
  131. )
  132. self.assertStatus(response, status.HTTP_200_OK)
  133. self.assertEqual(response.data, "good")
  134. self.assertIP(ipv4="10.2.3.4")
  135. def test_ddclient_dyndns2_v4_invalid(self):
  136. # /nic/update?system=dyndns&hostname=foobar.dedyn.io&myip=10.2.3.4asdf
  137. params = {
  138. "domain_name": self.my_domain.name,
  139. "system": "dyndns",
  140. "hostname": self.my_domain.name,
  141. "myip": "10.2.3.4asdf",
  142. }
  143. response = self.client.get(self.reverse("v1:dyndns12update"), params)
  144. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  145. self.assertIn("malformed", str(response.data))
  146. def test_ddclient_dyndns2_v4_invalid_or_foreign_domain(self):
  147. # /nic/update?system=dyndns&hostname=<...>&myip=10.2.3.4
  148. for name in [
  149. self.owner.email,
  150. self.other_domain.name,
  151. self.my_domain.parent_domain_name,
  152. ]:
  153. response = self.assertDynDNS12NoUpdate(
  154. system="dyndns",
  155. hostname=name,
  156. myip="10.2.3.4",
  157. )
  158. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  159. self.assertEqual(response.content, b"nohost")
  160. def test_ddclient_dyndns2_v6_success(self):
  161. # /nic/update?system=dyndns&hostname=foobar.dedyn.io&myipv6=::1338
  162. response = self.assertDynDNS12Update(
  163. domain_name=self.my_domain.name,
  164. system="dyndns",
  165. hostname=self.my_domain.name,
  166. myipv6="::666",
  167. )
  168. self.assertStatus(response, status.HTTP_200_OK)
  169. self.assertEqual(response.data, "good")
  170. self.assertIP(ipv4="127.0.0.1", ipv6="::666")
  171. def test_ddclient_dyndns2_mixed_success(self):
  172. response = self.assertDynDNS12Update(
  173. domain_name=self.my_domain.name,
  174. system="dyndns",
  175. hostname=self.my_domain.name,
  176. myip="10.2.3.4, ::2 , 10.6.5.4 ,::4",
  177. )
  178. self.assertStatus(response, status.HTTP_200_OK)
  179. self.assertEqual(response.data, "good")
  180. self.assertIP(ipv4={"10.2.3.4", "10.6.5.4"}, ipv6={"::2", "::4"})
  181. def test_ddclient_dyndns2_mixed_invalid(self):
  182. for myip in ["10.2.3.4, ", "preserve,::2"]:
  183. response = self.assertDynDNS12NoUpdate(
  184. domain_name=self.my_domain.name,
  185. system="dyndns",
  186. hostname=self.my_domain.name,
  187. myip=myip,
  188. )
  189. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  190. self.assertEqual(response.data["code"], "inconsistent-parameter")
  191. def test_fritz_box(self):
  192. # /
  193. response = self.assertDynDNS12Update(self.my_domain.name)
  194. self.assertStatus(response, status.HTTP_200_OK)
  195. self.assertEqual(response.data, "good")
  196. self.assertIP(ipv4="127.0.0.1")
  197. def test_unset_ip(self):
  198. for (v4, v6) in [
  199. ("127.0.0.1", "::1"),
  200. ("127.0.0.1", ""),
  201. ("", "::1"),
  202. ("", ""),
  203. ]:
  204. response = self.assertDynDNS12Update(self.my_domain.name, ip=v4, ipv6=v6)
  205. self.assertStatus(response, status.HTTP_200_OK)
  206. self.assertEqual(response.data, "good")
  207. self.assertIP(ipv4=v4, ipv6=v6)
  208. def test_preserve_ip(self):
  209. current_v4 = "127.0.0.1"
  210. current_v6 = "::1"
  211. self.assertDynDNS12Update(self.my_domain.name, ip=current_v4, ipv6=current_v6)
  212. for (v4, v6) in [
  213. ("preserve", "::3"),
  214. ("1.2.3.4", "preserve"),
  215. ("preserve", "preserve"),
  216. ]:
  217. self.assertDynDNS12Update(
  218. self.my_domain.name, ip=v4, ipv6=v6, expect_update=v4 != v6
  219. )
  220. current_v4 = current_v4 if v4 == "preserve" else v4
  221. current_v6 = current_v6 if v6 == "preserve" else v6
  222. self.assertIP(ipv4=current_v4, ipv6=current_v6)
  223. class SingleDomainDynDNS12UpdateTest(DynDNS12UpdateTest):
  224. NUM_OWNED_DOMAINS = 1
  225. def test_identification_by_token(self):
  226. self.client.set_credentials_basic_auth("", self.token.plain)
  227. response = self.assertDynDNS12Update(
  228. self.my_domain.name, mock_remote_addr="10.5.5.6"
  229. )
  230. self.assertStatus(response, status.HTTP_200_OK)
  231. self.assertEqual(response.data, "good")
  232. self.assertIP(ipv4="10.5.5.6")
  233. def test_identification_by_email(self):
  234. self.client.set_credentials_basic_auth(self.owner.email, self.token.plain)
  235. response = self.assertDynDNS12Update(
  236. self.my_domain.name, mock_remote_addr="10.5.5.6"
  237. )
  238. self.assertStatus(response, status.HTTP_200_OK)
  239. self.assertEqual(response.data, "good")
  240. self.assertIP(ipv4="10.5.5.6")
  241. class MultipleDomainDynDNS12UpdateTest(DynDNS12UpdateTest):
  242. NUM_OWNED_DOMAINS = 4
  243. def test_ignore_minimum_ttl(self):
  244. self.my_domain.minimum_ttl = 61
  245. self.my_domain.save()
  246. # Test that dynDNS updates work both under a local public suffix (self.my_domain) and for a custom domains
  247. for domain in [self.my_domain, self.create_domain(owner=self.owner)]:
  248. self.assertGreater(domain.minimum_ttl, 60)
  249. self.client.set_credentials_basic_auth(
  250. domain.name.lower(), self.token.plain
  251. )
  252. response = self.assertDynDNS12Update(domain.name)
  253. self.assertStatus(response, status.HTTP_200_OK)
  254. self.assertEqual(domain.rrset_set.get(subname="", type="A").ttl, 60)
  255. def test_identification_by_token(self):
  256. """
  257. Test if the conflict of having multiple domains, but not specifying which to update is correctly recognized.
  258. """
  259. self.client.set_credentials_basic_auth("", self.token.plain)
  260. response = self.client.get(
  261. self.reverse("v1:dyndns12update"), REMOTE_ADDR="10.5.5.7"
  262. )
  263. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  264. class MixedCaseDynDNS12UpdateTestCase(DynDNS12UpdateTest):
  265. @staticmethod
  266. def random_casing(s):
  267. return "".join(
  268. [c.lower() if random.choice([True, False]) else c.upper() for c in s]
  269. )
  270. def setUp(self):
  271. super().setUp()
  272. self.my_domain.name = self.random_casing(self.my_domain.name)
  273. class UppercaseDynDNS12UpdateTestCase(DynDNS12UpdateTest):
  274. def setUp(self):
  275. super().setUp()
  276. self.my_domain.name = self.my_domain.name.upper()