views.py 12 KB

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