test_dyndns12update.py 10 KB

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