test_domains.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739
  1. from django.conf import settings
  2. from django.core import mail
  3. from django.core.exceptions import ValidationError
  4. from rest_framework import status
  5. from desecapi.models import Domain
  6. from desecapi.pdns_change_tracker import PDNSChangeTracker
  7. from desecapi.tests.base import DesecTestCase, DomainOwnerTestCase, PublicSuffixMockMixin
  8. class IsRegistrableTestCase(DesecTestCase, PublicSuffixMockMixin):
  9. """ Tests which domains can be registered by whom, depending on domain ownership and public suffix
  10. configuration. Note that we use "global public suffix" to refer to public suffixes which appear on the
  11. Internet-wide Public Suffix List (accessible, e.g., via psl_dns), and "local public suffix" to public
  12. suffixes which are configured in the local Django settings.LOCAL_PUBLIC_SUFFIXES. Consequently, a
  13. public suffix can be just local, just global, or both. """
  14. def mock(self, global_public_suffixes, local_public_suffixes):
  15. self.setUpMockPatch()
  16. test_case = self
  17. class _MockSuffixLists:
  18. settings_mocker = None
  19. psl_mocker = None
  20. def __enter__(self):
  21. self.settings_mocker = test_case.settings(LOCAL_PUBLIC_SUFFIXES=local_public_suffixes)
  22. self.settings_mocker.__enter__()
  23. self.psl_mocker = test_case.get_psl_context_manager(global_public_suffixes)
  24. self.psl_mocker.__enter__()
  25. def __exit__(self, exc_type, exc_val, exc_tb):
  26. if exc_type or exc_val or exc_tb:
  27. raise exc_val
  28. self.settings_mocker.__exit__(None, None, None)
  29. self.psl_mocker.__exit__(None, None, None)
  30. return _MockSuffixLists()
  31. def assertRegistrable(self, domain_name, user=None):
  32. """ Raises if the given user (fresh if None) cannot register the given domain name. """
  33. self.assertTrue(Domain(name=domain_name, owner=user or self.create_user()).is_registrable(),
  34. f'{domain_name} was expected to be registrable for {user or "a new user"}, but wasn\'t.')
  35. def assertNotRegistrable(self, domain_name, user=None):
  36. """ Raises if the given user (fresh if None) can register the given domain name. """
  37. self.assertFalse(Domain(name=domain_name, owner=user or self.create_user()).is_registrable(),
  38. f'{domain_name} was expected to be not registrable for {user or "a new user"}, but was.')
  39. def test_cant_register_global_non_local_public_suffix(self):
  40. with self.mock(
  41. global_public_suffixes=['com', 'de', 'xxx', 'com.uk'],
  42. local_public_suffixes=['something.else'],
  43. ):
  44. self.assertNotRegistrable('tld')
  45. self.assertNotRegistrable('xxx')
  46. self.assertNotRegistrable('com.uk')
  47. self.assertRegistrable('something.else')
  48. def test_can_register_local_public_suffix(self):
  49. # Avoid side effects from existing domains (such as dedyn.io.example.com being covered by the .com test below)
  50. # Existing domains depend on environment variables. We may want to make the tests "stand-alone" at some point.
  51. Domain.objects.filter(name__in=self.AUTO_DELEGATION_DOMAINS).delete()
  52. local_public_suffixes = ['something.else', 'our.public.suffix', 'com', 'com.uk']
  53. with self.mock(
  54. global_public_suffixes=['com', 'de', 'xxx', 'com.uk'],
  55. local_public_suffixes=local_public_suffixes,
  56. ):
  57. for local_public_suffix in local_public_suffixes:
  58. self.assertRegistrable(local_public_suffix)
  59. self.assertRegistrable('foo.bar.com')
  60. def test_cant_register_reserved_children_of_public_suffix(self):
  61. global_public_suffixes = ['global.public.suffix']
  62. local_public_suffixes = ['local.public.suffix']
  63. reserved_labels = ['_acme-challenge', '_tcp', '_foobar', 'autodiscover', 'autoconfig']
  64. with self.mock(
  65. global_public_suffixes=global_public_suffixes,
  66. local_public_suffixes=local_public_suffixes,
  67. ):
  68. for suffix in local_public_suffixes + global_public_suffixes:
  69. for label in reserved_labels:
  70. self.assertNotRegistrable(f'{label}.{suffix}')
  71. self.assertRegistrable(f'{label}.sub.{suffix}')
  72. def test_cant_register_descendants_of_children_of_public_suffixes(self):
  73. with self.mock(
  74. global_public_suffixes={'public.suffix'},
  75. local_public_suffixes={'public.suffix'},
  76. ):
  77. # let A own a.public.suffix
  78. user_a = self.create_user()
  79. self.assertRegistrable('a.public.suffix', user_a)
  80. self.create_domain(owner=user_a, name='a.public.suffix')
  81. # user B shall not register b.a.public.suffix, but A may
  82. user_b = self.create_user()
  83. self.assertNotRegistrable('b.a.public.suffix', user_b)
  84. self.assertRegistrable('b.a.public.suffix', user_a)
  85. def test_cant_register_ancestors_of_registered_domains(self):
  86. user_a = self.create_user()
  87. user_b = self.create_user()
  88. with self.mock(
  89. global_public_suffixes={'public.suffix'},
  90. local_public_suffixes={'public.suffix'},
  91. ):
  92. # let A own c.b.a.public.suffix
  93. self.assertRegistrable('c.b.a.public.suffix', user_a)
  94. self.create_domain(owner=user_a, name='c.b.a.public.suffix')
  95. # user B shall not register b.a.public.suffix or a.public.suffix, but A may
  96. self.assertNotRegistrable('b.a.public.suffix', user_b)
  97. self.assertNotRegistrable('a.public.suffix', user_b)
  98. self.assertRegistrable('b.a.public.suffix', user_a)
  99. self.assertRegistrable('a.public.suffix', user_a)
  100. # let A own _acme-challenge.foobar.public.suffix
  101. self.assertRegistrable('_acme-challenge.foobar.public.suffix', user_a)
  102. self.create_domain(owner=user_a, name='_acme-challenge.foobar.public.suffix')
  103. # user B shall not register foobar.public.suffix, but A may
  104. user_b = self.create_user()
  105. self.assertNotRegistrable('foobar.public.suffix', user_b)
  106. self.assertRegistrable('foobar.public.suffix', user_a)
  107. def test_can_register_public_suffixes_under_private_domains(self):
  108. with self.mock(
  109. global_public_suffixes={'public.suffix'},
  110. local_public_suffixes={'another.public.suffix.private.public.suffix', 'public.suffix'},
  111. ):
  112. # let A own public.suffix
  113. user_a = self.create_user()
  114. self.assertRegistrable('public.suffix', user_a)
  115. self.create_domain(owner=user_a, name='public.suffix')
  116. # user B may register private.public.suffix
  117. user_b = self.create_user()
  118. self.assertRegistrable('private.public.suffix', user_b)
  119. self.create_domain(owner=user_b, name='private.public.suffix')
  120. # user C may register b.another.public.suffix.private.public.suffix,
  121. # or long.silly.prefix.another.public.suffix.private.public.suffix,
  122. # but not b.private.public.suffix.
  123. user_c = self.create_user()
  124. self.assertRegistrable('b.another.public.suffix.private.public.suffix', user_c)
  125. self.assertRegistrable('long.silly.prefix.another.public.suffix.private.public.suffix', user_c)
  126. self.assertNotRegistrable('b.private.public.suffix', user_c)
  127. self.assertRegistrable('b.private.public.suffix', user_b)
  128. def test_cant_register_internal(self):
  129. self.assertNotRegistrable('internal')
  130. self.assertNotRegistrable('catalog.internal')
  131. self.assertNotRegistrable('some.other.internal')
  132. class UnauthenticatedDomainTests(DesecTestCase):
  133. def test_unauthorized_access(self):
  134. for url in [
  135. self.reverse('v1:domain-list'),
  136. self.reverse('v1:domain-detail', name='example.com.')
  137. ]:
  138. for method in [self.client.put, self.client.delete]:
  139. self.assertStatus(method(url), status.HTTP_401_UNAUTHORIZED)
  140. class DomainOwnerTestCase1(DomainOwnerTestCase):
  141. def test_name_validity(self):
  142. for name in [
  143. 'FOO.BAR.com',
  144. 'tEst.dedyn.io',
  145. 'ORG',
  146. '--BLAH.example.com',
  147. '_ASDF.jp',
  148. 'too.long.x012345678901234567890123456789012345678901234567890123456789012.com',
  149. ]:
  150. with self.assertRaises(ValidationError):
  151. Domain(owner=self.owner, name=name).save()
  152. for name in [
  153. 'org',
  154. 'foobar.io',
  155. 'hyphens--------------------hyphens-hyphens.com',
  156. '_example.com', '_.example.com',
  157. 'exam_ple.com',
  158. '-dedyn.io', '--dedyn.io', '-.dedyn123.io',
  159. '_foobar.example.com',
  160. '-foobar.example.com',
  161. 'hyphen-.example.com',
  162. 'max.length.x01234567890123456789012345678901234567890123456789012345678901.com',
  163. ]:
  164. with self.assertPdnsRequests(
  165. self.requests_desec_domain_creation(name=name, keys=False) # no serializer, no cryptokeys API call
  166. ), PDNSChangeTracker():
  167. Domain(owner=self.owner, name=name).save()
  168. def test_list_domains(self):
  169. response = self.client.get(self.reverse('v1:domain-list'))
  170. self.assertStatus(response, status.HTTP_200_OK)
  171. self.assertEqual(len(response.data), self.NUM_OWNED_DOMAINS)
  172. response_set = {data['name'] for data in response.data}
  173. expected_set = {domain.name for domain in self.my_domains}
  174. self.assertEqual(response_set, expected_set)
  175. self.assertFalse(any('keys' in data for data in response.data))
  176. def test_list_domains_owns_qname(self):
  177. # Domains outside this account or non-existent
  178. for domain in ['non-existent.net', self.other_domain.name, 'domain.invalid/']:
  179. for name in [domain, f'foo.bar.{domain}']:
  180. response = self.client.get(self.reverse('v1:domain-list'), data={'owns_qname': name})
  181. self.assertStatus(response, status.HTTP_200_OK)
  182. self.assertEqual(len(response.data), 0)
  183. # Domains within this account
  184. domains = [
  185. Domain(owner=self.owner, name=name)
  186. # Weird order so that name ownership does not follow domain creation chronologically
  187. for name in ['a.foobar.net', 'foobar.net', 'b.a.foobar.net']
  188. ]
  189. for domain in domains:
  190. domain.save()
  191. for domain in domains:
  192. for name in [domain.name, f'foo.bar.{domain.name}', f'foo.BAR.{domain.name}']:
  193. response = self.client.get(self.reverse('v1:domain-list'), data={'owns_qname': name})
  194. self.assertStatus(response, status.HTTP_200_OK)
  195. self.assertEqual(len(response.data), 1)
  196. self.assertEqual(response.data[0]['name'], domain.name)
  197. def test_delete_my_domain(self):
  198. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  199. with self.assertPdnsRequests(self.requests_desec_domain_deletion(self.my_domain)):
  200. response = self.client.delete(url)
  201. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  202. self.assertFalse(Domain.objects.filter(pk=self.my_domain.pk).exists())
  203. response = self.client.get(url)
  204. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  205. def test_delete_other_domain(self):
  206. url = self.reverse('v1:domain-detail', name=self.other_domain.name)
  207. response = self.client.delete(url)
  208. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  209. self.assertTrue(Domain.objects.filter(pk=self.other_domain.pk).exists())
  210. def test_retrieve_my_domain(self):
  211. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  212. with self.assertPdnsRequests(
  213. self.request_pdns_zone_retrieve_crypto_keys(name=self.my_domain.name)
  214. ):
  215. response = self.client.get(url)
  216. self.assertStatus(response, status.HTTP_200_OK)
  217. self.assertEqual(response.data.keys(), {'created', 'keys', 'minimum_ttl', 'name', 'published', 'touched'})
  218. self.assertEqual(response.data['name'], self.my_domain.name)
  219. self.assertTrue(isinstance(response.data['keys'], list))
  220. def test_retrieve_other_domains(self):
  221. for domain in self.other_domains:
  222. response = self.client.get(self.reverse('v1:domain-detail', name=domain.name))
  223. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  224. def test_update_domain(self):
  225. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  226. with self.assertPdnsRequests(self.request_pdns_zone_retrieve_crypto_keys(name=self.my_domain.name)):
  227. response = self.client.get(url)
  228. self.assertStatus(response, status.HTTP_200_OK)
  229. for method in [self.client.patch, self.client.put]:
  230. response = method(url, response.data, format='json')
  231. self.assertStatus(response, status.HTTP_405_METHOD_NOT_ALLOWED)
  232. def test_create_domains(self):
  233. self.owner.limit_domains = 100
  234. self.owner.save()
  235. for name in [
  236. '0.8.0.0.0.1.c.a.2.4.6.0.c.e.e.d.4.4.0.1.a.0.1.0.8.f.4.0.1.0.a.2.ip6.arpa',
  237. 'very.long.domain.name.' + self.random_domain_name(),
  238. self.random_domain_name(),
  239. 'xn--90aeeb7afyklt.xn--p1ai',
  240. ]:
  241. with self.assertPdnsRequests(self.requests_desec_domain_creation(name)):
  242. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  243. self.assertStatus(response, status.HTTP_201_CREATED)
  244. self.assertTrue(all(field in response.data for field in
  245. ['created', 'published', 'name', 'keys', 'minimum_ttl', 'touched']))
  246. self.assertEqual(len(mail.outbox), 0)
  247. self.assertTrue(isinstance(response.data['keys'], list))
  248. with self.assertPdnsRequests(self.request_pdns_zone_retrieve_crypto_keys(name)):
  249. self.assertStatus(
  250. self.client.get(self.reverse('v1:domain-detail', name=name), {'name': name}),
  251. status.HTTP_200_OK
  252. )
  253. response = self.client.get_rr_sets(name, type='NS', subname='')
  254. self.assertStatus(response, status.HTTP_200_OK)
  255. self.assertContainsRRSets(response.data, [dict(subname='', records=settings.DEFAULT_NS, type='NS')])
  256. domain = Domain.objects.get(name=name)
  257. self.assertFalse(domain.is_locally_registrable)
  258. self.assertEqual(domain.renewal_state, Domain.RenewalState.IMMORTAL);
  259. def test_create_domain_zonefile_import(self):
  260. zonefile = """$ORIGIN .
  261. $TTL 43200 ; 12 hours
  262. import-me.example IN SOA ns1.example.com. hostmaster.example.com. (
  263. 2022021300 ; serial
  264. 10800 ; refresh (3 hours)
  265. 3600 ; retry (1 hour)
  266. 2419000 ; expire (3 weeks 6 days 23 hours 56 minutes 40 seconds)
  267. 43200 ; minimum (12 hours)
  268. )
  269. import-me.example NS ns1.example.com.
  270. import-me.example NS ns2.example.com.
  271. import-me.example NS ns3.example.com.
  272. import-me.example NS ns4.example.com.
  273. import-me.example NS ns5.example.com.
  274. $TTL 300 ; 5 mins
  275. import-me.example A 10.1.1.1
  276. *.import-me.example A 10.1.1.1
  277. import-me.example TXT "v=spf1 -all"
  278. _dmarc.import-me.example TXT "v=DMARC1; p=reject;"
  279. xxx.import-me.example NS ns4.example.
  280. xxx.import-me.example NS ns5.example.
  281. $TTL 43200 ; 12 hours
  282. localhost.import-me.example A 127.0.0.1
  283. # show zone import-me.example
  284. """
  285. name = 'import-me.example'
  286. with self.assertPdnsRequests(
  287. self.requests_desec_domain_creation(name, axfr=False, keys=False) +
  288. self.requests_desec_rr_sets_update(name) +
  289. [self.request_pdns_zone_retrieve_crypto_keys(name)]
  290. ):
  291. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  292. self.assertStatus(response, status.HTTP_201_CREATED)
  293. domain = Domain.objects.get(name=name)
  294. self.assertRRsetDB(domain, subname='', type_='SOA', rr_contents=set())
  295. self.assertRRsetDB(domain, subname='', type_='NS', ttl=settings.DEFAULT_NS_TTL,
  296. rr_contents=set(settings.DEFAULT_NS))
  297. ttl = max(300, settings.MINIMUM_TTL_DEFAULT)
  298. self.assertRRsetDB(domain, subname='', type_='A', ttl=ttl, rr_contents={'10.1.1.1'})
  299. self.assertRRsetDB(domain, subname='*', type_='A', ttl=ttl, rr_contents={'10.1.1.1'})
  300. self.assertRRsetDB(domain, subname='', type_='TXT', ttl=ttl, rr_contents={'"v=spf1 -all"'})
  301. self.assertRRsetDB(domain, subname='_dmarc', type_='TXT', ttl=ttl,
  302. rr_contents={'"v=DMARC1; p=reject;"'})
  303. self.assertRRsetDB(domain, subname='xxx', type_='NS', ttl=ttl,
  304. rr_contents={'ns4.example.', 'ns5.example.'})
  305. self.assertRRsetDB(domain, subname='localhost', type_='A', ttl=43200, rr_contents={'127.0.0.1'})
  306. def test_create_domain_zonefile_import_cname_exclusivity(self):
  307. zonefile = """$ORIGIN .
  308. $TTL 43200 ; 12 hours
  309. import-me.example IN SOA ns1.example.com. hostmaster.example.com. 2022021300 10800 3600 2419000 43200
  310. import-me.example NS ns1.example.com.
  311. www.import-me.example CNAME a.example.
  312. www.import-me.example A 127.0.0.1
  313. """
  314. name = 'import-me.example'
  315. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  316. self.assertResponse(response, status.HTTP_400_BAD_REQUEST)
  317. self.assertEqual(
  318. response.json(),
  319. {'zonefile': ['No other records with the same name are allowed alongside a CNAME record.']},
  320. )
  321. def test_create_domain_zonefile_import_name_non_apex_soa(self):
  322. zonefile = """$ORIGIN .
  323. $TTL 43200 ; 12 hours
  324. asdf.import-me.example IN SOA ns1.example.com. hostmaster.example.com. 2022021300 10800 3600 2419000 43200
  325. import-me.example NS ns1.example.com.
  326. www.import-me.example CNAME a.example.
  327. www.import-me.example A 127.0.0.1
  328. """
  329. name = 'import-me.example'
  330. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  331. self.assertResponse(response, status.HTTP_400_BAD_REQUEST)
  332. self.assertEqual(
  333. response.json(),
  334. {'zonefile': [f'Zonefile includes an SOA record for a name different from {name}.']},
  335. )
  336. def test_create_domain_zonefile_import_syntax_error_line(self):
  337. zonefile = """$ORIGIN .
  338. $TTL 43200 ; 12 hours
  339. import-me.example IN SOA ns1.example.com. hostmaster.example.com. 2022021300 10800 3600 2419000 43200
  340. import-me.example NS ns1.example.com.
  341. www.import-me.example CNAME a.example.
  342. www.import-me.example A asdf
  343. """
  344. name = 'import-me.example'
  345. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  346. self.assertResponse(response, status.HTTP_400_BAD_REQUEST)
  347. self.assertEqual(
  348. response.json(),
  349. {'zonefile': [f'Zonefile contains syntax error in line 6.']},
  350. )
  351. def test_create_domain_zonefile_import_foreign_rrset(self):
  352. zonefile = f"""$ORIGIN .
  353. $TTL 43200 ; 12 hours
  354. import-me.example IN SOA ns1.example.com. hostmaster.example.com. 2022021300 10800 3600 2419000 43200
  355. import-me.example NS ns1.example.com.
  356. import-me.example A 127.0.0.1
  357. inject.{self.other_domain.name}. CNAME a.example.
  358. """
  359. name = 'import-me.example'
  360. with self.assertPdnsRequests(
  361. self.requests_desec_domain_creation(name, axfr=False, keys=False) +
  362. self.requests_desec_rr_sets_update(name) +
  363. [self.request_pdns_zone_retrieve_crypto_keys(name)]
  364. ):
  365. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  366. self.assertResponse(response, status.HTTP_201_CREATED)
  367. self.assertRRsetDB(self.other_domain, subname='inject', type_='CNAME', rr_contents=set())
  368. def test_create_domain_zonefile_import_no_soa(self):
  369. zonefile = f"""$ORIGIN .
  370. $TTL 43200 ; 12 hours
  371. import-me.example A 127.0.0.1
  372. import-me.example A 127.0.0.2
  373. import-me.example MX 10 example.com.
  374. """
  375. name = 'import-me.example'
  376. with self.assertPdnsRequests(
  377. self.requests_desec_domain_creation(name, axfr=False, keys=False) +
  378. self.requests_desec_rr_sets_update(name) +
  379. [self.request_pdns_zone_retrieve_crypto_keys(name)]
  380. ):
  381. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  382. self.assertResponse(response, status.HTTP_201_CREATED)
  383. self.assertRRsetDB(Domain.objects.get(name=name), subname='', type_='MX', rr_contents={'10 example.com.'})
  384. def test_create_domain_zonefile_import_names(self):
  385. """ensures that names on the right-hand-side which are below the zone's name are handled correctly"""
  386. zonefile = """example.net. 3600 MX 10 mail.example.net.
  387. example.net. 3600 MX 10 mail.example.org.
  388. example.net. 3600 PTR mail.example.net.
  389. example.net. 3600 PTR mail.example.org."""
  390. name = 'example.net'
  391. with self.assertPdnsRequests(
  392. self.requests_desec_domain_creation(name, axfr=False, keys=False) +
  393. self.requests_desec_rr_sets_update(name) +
  394. [self.request_pdns_zone_retrieve_crypto_keys(name)]
  395. ):
  396. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  397. self.assertResponse(response, status.HTTP_201_CREATED)
  398. self.assertRRsetDB(Domain.objects.get(name=name), subname='', type_='MX',
  399. rr_contents={'10 mail.example.net.', '10 mail.example.org.'})
  400. self.assertRRsetDB(Domain.objects.get(name=name), subname='', type_='PTR',
  401. rr_contents={'mail.example.net.', 'mail.example.org.'})
  402. def test_create_domain_zonefile_import_non_canonical(self):
  403. zonefile = f"""$ORIGIN .
  404. $TTL 43200 ; 12 hours
  405. import-me.example AAAA 0000::1
  406. """
  407. name = 'import-me.example'
  408. with self.assertPdnsRequests(
  409. self.requests_desec_domain_creation(name, axfr=False, keys=False) +
  410. self.requests_desec_rr_sets_update(name) +
  411. [self.request_pdns_zone_retrieve_crypto_keys(name)]
  412. ):
  413. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  414. self.assertResponse(response, status.HTTP_201_CREATED)
  415. self.assertRRsetDB(Domain.objects.get(name=name), subname='', type_='AAAA', ttl=43200, rr_contents={'::1'})
  416. def test_create_domain_zonefile_import_validation(self):
  417. zonefile = f"""$ORIGIN .
  418. $TTL 43200 ; 12 hours
  419. import-me.example MX 10 $url.
  420. """
  421. name = 'import-me.example'
  422. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  423. self.assertResponse(response, status.HTTP_400_BAD_REQUEST)
  424. self.assertEqual(
  425. response.json(),
  426. {"zonefile": ["import-me.example/MX: Cannot parse record contents: invalid exchange: \\$url."]},
  427. )
  428. self.assertFalse(Domain.objects.filter(name=name).exists())
  429. def test_create_domain_zonefile_import_unsupported_type(self):
  430. zonefile = f"""$ORIGIN .
  431. $TTL 43200 ; 12 hours
  432. import-me.example WKS 10.0.0.1 6 0 1 2 21 23
  433. """
  434. name = 'import-me.example'
  435. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  436. self.assertResponse(response, status.HTTP_400_BAD_REQUEST)
  437. self.assertEqual(
  438. response.json(),
  439. {"zonefile": [
  440. 'import-me.example/WKS: The WKS RR set type is currently unsupported.',
  441. ]},
  442. )
  443. self.assertFalse(Domain.objects.filter(name=name).exists())
  444. def test_create_domain_zonefile_ignore_automatically_managed_rrsets(self):
  445. zonefile = f"""$ORIGIN .
  446. $TTL 43200 ; 12 hours
  447. import-me.example A 127.0.0.1
  448. import-me.example RRSIG A 13 2 3600 20220324000000 20220303000000 40316 @ 4wj6ZrLLLm6ZpvCh/vyqWCEkf2Krwkt8 Fi1/VJgfLMoXZSj6koOzJBMYYCiMm0JP WgQwG54fcw6YJQaOfWX1BA==
  449. """
  450. name = 'import-me.example'
  451. with self.assertPdnsRequests(
  452. self.requests_desec_domain_creation(name, axfr=False, keys=False) +
  453. self.requests_desec_rr_sets_update(name) +
  454. [self.request_pdns_zone_retrieve_crypto_keys(name)]
  455. ):
  456. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': zonefile})
  457. self.assertResponse(response, status.HTTP_201_CREATED)
  458. domain = Domain.objects.get(name=name)
  459. self.assertRRsetDB(domain, subname='', type_='A', ttl=43200, rr_contents={'127.0.0.1'})
  460. self.assertRRsetDB(domain, subname='', type_='RRSIG', rr_contents=set())
  461. def test_create_domain_zonefile_empty(self):
  462. name = 'import-me.example'
  463. with self.assertPdnsRequests(self.requests_desec_domain_creation(name)):
  464. response = self.client.post(self.reverse('v1:domain-list'), {'name': name, 'zonefile': ''})
  465. self.assertResponse(response, status.HTTP_201_CREATED)
  466. def test_create_api_known_domain(self):
  467. url = self.reverse('v1:domain-list')
  468. for name in [
  469. self.random_domain_name(),
  470. 'www.' + self.my_domain.name,
  471. ]:
  472. with self.assertPdnsRequests(self.requests_desec_domain_creation(name)):
  473. response = self.client.post(url, {'name': name})
  474. self.assertStatus(response, status.HTTP_201_CREATED)
  475. response = self.client.post(url, {'name': name})
  476. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  477. def test_create_domain_with_whitespace(self):
  478. for name in [
  479. ' ' + self.random_domain_name(),
  480. self.random_domain_name() + ' ',
  481. ]:
  482. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  483. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  484. self.assertTrue("Domain names must be labels separated by dots. Labels" in response.data['name'][0])
  485. def test_create_public_suffixes(self):
  486. for name in self.PUBLIC_SUFFIXES:
  487. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  488. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  489. self.assertEqual(response.data['name'][0].code, 'name_unavailable')
  490. def test_create_domain_under_public_suffix_with_private_parent(self):
  491. name = 'amazonaws.com'
  492. with self.assertPdnsRequests(self.requests_desec_domain_creation(name, keys=False)), PDNSChangeTracker():
  493. Domain(owner=self.create_user(), name=name).save()
  494. self.assertTrue(Domain.objects.filter(name=name).exists())
  495. # If amazonaws.com is owned by another user, we cannot register test.s4.amazonaws.com
  496. name = 'test.s4.amazonaws.com'
  497. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  498. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  499. self.assertEqual(response.data['name'][0].code, 'name_unavailable')
  500. # s3.amazonaws.com is a public suffix. Therefore, test.s3.amazonaws.com can be
  501. # registered even if the parent zone amazonaws.com is owned by another user
  502. name = 'test.s3.amazonaws.com'
  503. psl_cm = self.get_psl_context_manager('s3.amazonaws.com')
  504. with psl_cm, self.assertPdnsRequests(self.requests_desec_domain_creation(name)):
  505. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  506. self.assertStatus(response, status.HTTP_201_CREATED)
  507. def test_create_domain_policy(self):
  508. for name in ['1.2.3..4.test.dedyn.io', 'test..de', '*.' + self.random_domain_name(), 'a' * 64 + '.bla.test']:
  509. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  510. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  511. self.assertTrue("Domain names must be labels separated by dots. Labels" in response.data['name'][0])
  512. def test_create_domain_other_parent(self):
  513. name = 'something.' + self.other_domain.name
  514. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  515. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  516. self.assertEqual(response.data['name'][0].code, 'name_unavailable')
  517. def test_create_domain_atomicity(self):
  518. name = self.random_domain_name()
  519. with self.assertPdnsRequests(self.request_pdns_zone_create_422()):
  520. with self.assertRaises(ValueError):
  521. self.client.post(self.reverse('v1:domain-list'), {'name': name})
  522. self.assertFalse(Domain.objects.filter(name=name).exists())
  523. def test_create_domain_punycode(self):
  524. names = ['公司.cn', 'aéroport.ci']
  525. for name in names:
  526. self.assertStatus(
  527. self.client.post(self.reverse('v1:domain-list'), {'name': name}),
  528. status.HTTP_400_BAD_REQUEST
  529. )
  530. for name in [n.encode('idna').decode('ascii') for n in names]:
  531. with self.assertPdnsRequests(self.requests_desec_domain_creation(name=name)):
  532. self.assertStatus(
  533. self.client.post(self.reverse('v1:domain-list'), {'name': name}),
  534. status.HTTP_201_CREATED
  535. )
  536. def test_create_domain_name_validation(self):
  537. for name in [
  538. 'with space.dedyn.io',
  539. 'another space.de',
  540. ' spaceatthebeginning.com',
  541. 'percentage%sign.com',
  542. '%percentagesign.dedyn.io',
  543. 'slash/desec.io',
  544. '/slashatthebeginning.dedyn.io',
  545. '\\backslashatthebeginning.dedyn.io',
  546. 'backslash\\inthemiddle.at',
  547. '@atsign.com',
  548. 'at@sign.com',
  549. 'UPPER.case',
  550. 'case.UPPER',
  551. ]:
  552. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  553. self.assertStatus(response, status.HTTP_400_BAD_REQUEST)
  554. self.assertEqual(len(mail.outbox), 0)
  555. def test_domain_minimum_ttl(self):
  556. url = self.reverse('v1:domain-list')
  557. name = self.random_domain_name()
  558. with self.assertPdnsRequests(self.requests_desec_domain_creation(name=name)):
  559. response = self.client.post(url, {'name': name})
  560. self.assertStatus(response, status.HTTP_201_CREATED)
  561. self.assertEqual(response.data['minimum_ttl'], settings.MINIMUM_TTL_DEFAULT)
  562. class AutoDelegationDomainOwnerTests(DomainOwnerTestCase):
  563. DYN = True
  564. def test_delete_my_domain(self):
  565. url = self.reverse('v1:domain-detail', name=self.my_domain.name)
  566. with self.assertPdnsRequests(
  567. self.requests_desec_domain_deletion(domain=self.my_domain)
  568. ):
  569. response = self.client.delete(url)
  570. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  571. response = self.client.get(url)
  572. self.assertStatus(response, status.HTTP_404_NOT_FOUND)
  573. def test_delete_other_domains(self):
  574. url = self.reverse('v1:domain-detail', name=self.other_domain.name)
  575. with self.assertPdnsRequests():
  576. response = self.client.delete(url)
  577. self.assertStatus(response, status.HTTP_204_NO_CONTENT)
  578. self.assertTrue(Domain.objects.filter(pk=self.other_domain.pk).exists())
  579. def test_create_auto_delegated_domains(self):
  580. for i, suffix in enumerate(self.AUTO_DELEGATION_DOMAINS):
  581. name = self.random_domain_name(suffix)
  582. with self.assertPdnsRequests(self.requests_desec_domain_creation_auto_delegation(name=name)):
  583. response = self.client.post(self.reverse('v1:domain-list'), {'name': name})
  584. self.assertStatus(response, status.HTTP_201_CREATED)
  585. self.assertFalse(mail.outbox) # do not send email
  586. domain = Domain.objects.get(name=name)
  587. self.assertTrue(domain.is_locally_registrable)
  588. self.assertEqual(domain.renewal_state, Domain.RenewalState.FRESH);
  589. def test_domain_limit(self):
  590. url = self.reverse('v1:domain-list')
  591. user_quota = settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT - self.NUM_OWNED_DOMAINS
  592. for i in range(user_quota):
  593. name = self.random_domain_name(self.AUTO_DELEGATION_DOMAINS)
  594. with self.assertPdnsRequests(self.requests_desec_domain_creation_auto_delegation(name)):
  595. response = self.client.post(url, {'name': name})
  596. self.assertStatus(response, status.HTTP_201_CREATED)
  597. response = self.client.post(url, {'name': self.random_domain_name(self.AUTO_DELEGATION_DOMAINS)})
  598. self.assertContains(response, 'Domain limit', status_code=status.HTTP_403_FORBIDDEN)
  599. self.assertFalse(mail.outbox) # do not send email
  600. def test_domain_minimum_ttl(self):
  601. url = self.reverse('v1:domain-list')
  602. name = self.random_domain_name(self.AUTO_DELEGATION_DOMAINS)
  603. with self.assertPdnsRequests(self.requests_desec_domain_creation_auto_delegation(name)):
  604. response = self.client.post(url, {'name': name})
  605. self.assertStatus(response, status.HTTP_201_CREATED)
  606. self.assertEqual(response.data['minimum_ttl'], 60)
  607. class DomainManagerTestCase(DesecTestCase):
  608. def test_filter_qname(self):
  609. user1, user2 = self.create_user(), self.create_user()
  610. domains = {
  611. user1: ['domain.dedyn.io', 'foobar.example'],
  612. user2: ['dedyn.io', 'desec.io'],
  613. }
  614. for user, names in domains.items():
  615. for name in names:
  616. Domain(name=name, owner=user).save()
  617. config = {
  618. 'domain.dedyn.io': {
  619. None: ['domain.dedyn.io', 'dedyn.io'],
  620. user1: ['domain.dedyn.io'],
  621. user2: ['dedyn.io'],
  622. },
  623. 'foo.bar.baz.foobar.example': {
  624. None: ['foobar.example'],
  625. user1: ['foobar.example'],
  626. user2: [],
  627. },
  628. 'dedyn.io': {
  629. None: ['dedyn.io'],
  630. user1: [],
  631. user2: ['dedyn.io'],
  632. },
  633. 'foobar.desec.io': {
  634. None: ['desec.io'],
  635. user1: [],
  636. user2: ['desec.io'],
  637. },
  638. }
  639. config['sub.domain.dedyn.io'] = config['domain.dedyn.io']
  640. for qname, cases in config.items():
  641. for qname in [qname, f'*.{qname}']:
  642. for owner, expected in cases.items():
  643. filter_kwargs = dict(owner=owner) if owner is not None else {}
  644. qs = Domain.objects.filter_qname(qname, **filter_kwargs).values_list('name', flat=True)
  645. self.assertListEqual(list(qs), expected)
  646. def test_filter_qname_invalid(self):
  647. for qname in ['foo@bar.com', '*.*.a.example', '*foo.b.example', 'foo.*.example', 'example.com/', 'a_B_example']:
  648. self.assertFalse(Domain.objects.filter_qname(qname))