test_dyndns12update.py 8.8 KB

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