views.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. from __future__ import unicode_literals
  2. from django.core.mail import EmailMessage
  3. from desecapi.models import Domain, User
  4. from desecapi.serializers import DomainSerializer, DonationSerializer
  5. from rest_framework import generics
  6. from desecapi.permissions import IsOwner
  7. from rest_framework import permissions
  8. from django.http import Http404
  9. from rest_framework.views import APIView
  10. from rest_framework.response import Response
  11. from rest_framework.reverse import reverse
  12. from rest_framework.authentication import TokenAuthentication, get_authorization_header
  13. from rest_framework.renderers import StaticHTMLRenderer
  14. from dns import resolver
  15. from django.template.loader import get_template
  16. from django.template import Context
  17. from desecapi.authentication import BasicTokenAuthentication, URLParamAuthentication
  18. import base64
  19. from desecapi import settings
  20. from rest_framework.exceptions import ValidationError
  21. from djoser import views, signals
  22. from rest_framework import status
  23. from datetime import datetime, timedelta
  24. from django.utils import timezone
  25. from desecapi.forms import UnlockForm
  26. from django.shortcuts import render
  27. from django.http import HttpResponseRedirect
  28. from desecapi.emails import send_account_lock_email
  29. def get_client_ip(request):
  30. x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
  31. if x_forwarded_for:
  32. ip = x_forwarded_for.split(',')[0]
  33. else:
  34. ip = request.META.get('REMOTE_ADDR')
  35. return ip
  36. class DomainList(generics.ListCreateAPIView):
  37. serializer_class = DomainSerializer
  38. permission_classes = (permissions.IsAuthenticated, IsOwner,)
  39. def get_queryset(self):
  40. return Domain.objects.filter(owner=self.request.user.pk)
  41. def perform_create(self, serializer):
  42. queryset = Domain.objects.filter(name=serializer.validated_data['name'])
  43. if queryset.exists():
  44. ex = ValidationError(detail={"detail": "This domain name is already registered.", "code": "domain-taken"})
  45. ex.status_code = 409
  46. raise ex
  47. obj = serializer.save(owner=self.request.user)
  48. def sendDynDnsEmail(domain):
  49. content_tmpl = get_template('emails/domain-dyndns/content.txt')
  50. subject_tmpl = get_template('emails/domain-dyndns/subject.txt')
  51. from_tmpl = get_template('emails/from.txt')
  52. context = Context({
  53. 'domain': domain.name,
  54. 'url': 'https://update.dedyn.io/',
  55. 'username': domain.name,
  56. 'password': self.request.auth.key
  57. })
  58. email = EmailMessage(subject_tmpl.render(context),
  59. content_tmpl.render(context),
  60. from_tmpl.render(context),
  61. [self.request.user.email])
  62. email.send()
  63. if obj.dyn:
  64. sendDynDnsEmail(obj)
  65. class DomainDetail(generics.RetrieveUpdateDestroyAPIView):
  66. serializer_class = DomainSerializer
  67. permission_classes = (permissions.IsAuthenticated, IsOwner,)
  68. def get_queryset(self):
  69. return Domain.objects.filter(owner=self.request.user.pk)
  70. def put(self, request, pk, format=None):
  71. # Don't accept PUT requests for non-existent or non-owned domains.
  72. domain = Domain.objects.filter(owner=self.request.user.pk, pk=pk)
  73. if len(domain) is 0:
  74. raise Http404
  75. return super(DomainDetail, self).put(request, pk, format)
  76. class DomainDetailByName(DomainDetail):
  77. lookup_field = 'name'
  78. class Root(APIView):
  79. def get(self, request, format=None):
  80. if self.request.user and self.request.user.is_authenticated():
  81. return Response({
  82. 'domains': reverse('domain-list'),
  83. 'user': reverse('user'),
  84. 'logout:': reverse('logout'),
  85. })
  86. else:
  87. return Response({
  88. 'login': reverse('login', request=request, format=format),
  89. 'register': reverse('register', request=request, format=format),
  90. })
  91. class DnsQuery(APIView):
  92. def get(self, request, format=None):
  93. desecio = resolver.Resolver()
  94. if not 'domain' in request.GET:
  95. return Response(status=400)
  96. domain = str(request.GET['domain'])
  97. def getRecords(domain, type):
  98. records = []
  99. try:
  100. for ip in desecio.query(domain, type):
  101. records.append(str(ip))
  102. except resolver.NoAnswer:
  103. return []
  104. except resolver.NoNameservers:
  105. return []
  106. except resolver.NXDOMAIN:
  107. return []
  108. return records
  109. # find currently active NS records
  110. nsrecords = getRecords(domain, 'NS')
  111. # find desec.io nameserver IP address with standard nameserver
  112. ips = desecio.query('ns2.desec.io')
  113. desecio.nameservers = []
  114. for ip in ips:
  115. desecio.nameservers.append(str(ip))
  116. # query desec.io nameserver for A and AAAA records
  117. arecords = getRecords(domain, 'A')
  118. aaaarecords = getRecords(domain, 'AAAA')
  119. return Response({
  120. 'domain': domain,
  121. 'ns': nsrecords,
  122. 'a': arecords,
  123. 'aaaa': aaaarecords,
  124. '_nameserver': desecio.nameservers
  125. })
  126. class DynDNS12Update(APIView):
  127. authentication_classes = (TokenAuthentication, BasicTokenAuthentication, URLParamAuthentication,)
  128. renderer_classes = [StaticHTMLRenderer]
  129. def findDomain(self, request):
  130. def findDomainname(request):
  131. # 1. hostname parameter
  132. if 'hostname' in request.query_params and request.query_params['hostname'] != 'YES':
  133. return request.query_params['hostname']
  134. # 2. host_id parameter
  135. if 'host_id' in request.query_params:
  136. return request.query_params['host_id']
  137. # 3. http basic auth username
  138. try:
  139. return base64.b64decode(get_authorization_header(request).split(' ')[1]).split(':')[0]
  140. except:
  141. pass
  142. # 4. username parameter
  143. if 'username' in request.query_params:
  144. return request.query_params['username']
  145. # 5. only domain associated with this user account
  146. if len(request.user.domains.all()) == 1:
  147. return request.user.domains.all()[0].name
  148. return None
  149. domainname = findDomainname(request)
  150. domain = None
  151. # load and check permissions
  152. try:
  153. domain = Domain.objects.filter(owner=self.request.user.pk, name=domainname).all()[0]
  154. except:
  155. pass
  156. return domain
  157. def findIP(self, request, params, version=4):
  158. if version == 4:
  159. lookfor = '.'
  160. elif version == 6:
  161. lookfor = ':'
  162. else:
  163. raise Exception
  164. # Check URL parameters
  165. for p in params:
  166. if p in request.query_params and lookfor in request.query_params[p]:
  167. return request.query_params[p]
  168. # Check remote IP address
  169. client_ip = get_client_ip(request)
  170. if lookfor in client_ip:
  171. return client_ip
  172. # give up
  173. return ''
  174. def findIPv4(self, request):
  175. return self.findIP(request, ['myip', 'myipv4', 'ip'])
  176. def findIPv6(self, request):
  177. return self.findIP(request, ['myipv6', 'ipv6', 'myip', 'ip'], version=6)
  178. def get(self, request, format=None):
  179. domain = self.findDomain(request)
  180. if domain is None:
  181. raise Http404
  182. domain.arecord = self.findIPv4(request)
  183. domain.aaaarecord = self.findIPv6(request)
  184. domain.save()
  185. return Response('good')
  186. class DonationList(generics.CreateAPIView):
  187. serializer_class = DonationSerializer
  188. def perform_create(self, serializer):
  189. iban = serializer.validated_data['iban']
  190. obj = serializer.save()
  191. def sendDonationEmails(donation):
  192. context = Context({
  193. 'donation': donation,
  194. 'creditoridentifier': settings.SEPA['CREDITOR_ID'],
  195. 'complete_iban': iban
  196. })
  197. # internal desec notification
  198. content_tmpl = get_template('emails/donation/desec-content.txt')
  199. subject_tmpl = get_template('emails/donation/desec-subject.txt')
  200. attachment_tmpl = get_template('emails/donation/desec-attachment-jameica.txt')
  201. from_tmpl = get_template('emails/from.txt')
  202. email = EmailMessage(subject_tmpl.render(context),
  203. content_tmpl.render(context),
  204. from_tmpl.render(context),
  205. ['donation@desec.io'],
  206. attachments=[
  207. ('jameica-directdebit.xml',
  208. attachment_tmpl.render(context),
  209. 'text/xml')
  210. ])
  211. email.send()
  212. # donor notification
  213. if donation.email:
  214. content_tmpl = get_template('emails/donation/donor-content.txt')
  215. subject_tmpl = get_template('emails/donation/donor-subject.txt')
  216. test = content_tmpl.render(context)
  217. email = EmailMessage(subject_tmpl.render(context),
  218. content_tmpl.render(context),
  219. from_tmpl.render(context),
  220. [donation.email])
  221. email.send()
  222. # send emails
  223. sendDonationEmails(obj)
  224. class RegistrationView(views.RegistrationView):
  225. """
  226. Extends the djoser RegistrationView to record the remote IP address of any registration.
  227. """
  228. def create(self, request, *args, **kwargs):
  229. serializer = self.get_serializer(data=request.data)
  230. serializer.is_valid(raise_exception=True)
  231. self.perform_create(serializer, get_client_ip(request))
  232. headers = self.get_success_headers(serializer.data)
  233. return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers)
  234. def perform_create(self, serializer, remote_ip):
  235. captcha = User.objects.filter(
  236. created__gte=timezone.now()-timedelta(hours=settings.ABUSE_LOCK_ACCOUNT_BY_REGISTRATION_IP_PERIOD_HRS),
  237. registration_remote_ip=remote_ip
  238. ).exists()
  239. user = serializer.save(registration_remote_ip=remote_ip, captcha_required=captcha)
  240. if captcha:
  241. send_account_lock_email(self.request, user)
  242. signals.user_registered.send(sender=self.__class__, user=user, request=self.request)
  243. def unlock(request, email):
  244. # if this is a POST request we need to process the form data
  245. if request.method == 'POST':
  246. # create a form instance and populate it with data from the request:
  247. form = UnlockForm(request.POST)
  248. # check whether it's valid:
  249. if form.is_valid():
  250. try:
  251. User.objects.get(email=email).unlock()
  252. except User.DoesNotExist:
  253. pass # fail silently, otherwise people can find out if email addresses are registered with us
  254. return HttpResponseRedirect(reverse('unlock/done'))
  255. # if a GET (or any other method) we'll create a blank form
  256. else:
  257. form = UnlockForm()
  258. return render(request, 'unlock.html', {'form': form})
  259. def unlock_done(request):
  260. return render(request, 'unlock-done.html')