users.py 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. from datetime import timedelta
  2. from django.conf import settings
  3. from django.contrib.auth import user_logged_in
  4. from rest_framework import generics, mixins, status
  5. from rest_framework.exceptions import ValidationError
  6. from rest_framework.permissions import IsAuthenticated
  7. from rest_framework.response import Response
  8. from rest_framework.views import APIView
  9. from desecapi import authentication, permissions, serializers
  10. from desecapi.models import Token, User
  11. class AccountCreateView(generics.CreateAPIView):
  12. serializer_class = serializers.RegisterAccountSerializer
  13. throttle_scope = "account_management_active"
  14. def create(self, request, *args, **kwargs):
  15. # Create user and send trigger email verification.
  16. # Alternative would be to create user once email is verified, but this could be abused for bulk email.
  17. serializer = self.get_serializer(data=request.data)
  18. activation_required = settings.USER_ACTIVATION_REQUIRED
  19. try:
  20. serializer.is_valid(raise_exception=True)
  21. except ValidationError as e:
  22. # Hide existing users
  23. email_detail = e.detail.pop("email", [])
  24. email_detail = [
  25. detail for detail in email_detail if detail.code != "unique"
  26. ]
  27. if email_detail:
  28. e.detail["email"] = email_detail
  29. if e.detail:
  30. raise e
  31. else:
  32. # create user
  33. user = serializer.save(is_active=None if activation_required else True)
  34. # send email if needed
  35. domain = serializer.validated_data.get("domain")
  36. if domain or activation_required:
  37. serializers.AuthenticatedActivateUserActionSerializer.build_and_save(
  38. user=user, domain=domain
  39. )
  40. # This request is unauthenticated, so don't expose whether we did anything.
  41. message = (
  42. "Welcome! Please check your mailbox." if activation_required else "Welcome!"
  43. )
  44. return Response(data={"detail": message}, status=status.HTTP_202_ACCEPTED)
  45. class AccountView(generics.RetrieveUpdateAPIView):
  46. permission_classes = (
  47. IsAuthenticated,
  48. permissions.IsAPIToken | permissions.MFARequiredIfEnabled,
  49. permissions.HasManageTokensPermission,
  50. )
  51. serializer_class = serializers.UserSerializer
  52. throttle_scope = "account_management_passive"
  53. def get_object(self):
  54. return self.request.user
  55. class AccountDeleteView(APIView):
  56. authentication_classes = (authentication.EmailPasswordPayloadAuthentication,)
  57. permission_classes = (IsAuthenticated,)
  58. response_still_has_domains = Response(
  59. data={
  60. "detail": "To delete your user account, first delete all of your domains."
  61. },
  62. status=status.HTTP_409_CONFLICT,
  63. )
  64. throttle_scope = "account_management_active"
  65. def post(self, request, *args, **kwargs):
  66. if request.user.domains.exists():
  67. return self.response_still_has_domains
  68. serializers.AuthenticatedDeleteUserActionSerializer.build_and_save(
  69. user=request.user
  70. )
  71. return Response(
  72. data={
  73. "detail": "Please check your mailbox for further account deletion instructions."
  74. },
  75. status=status.HTTP_202_ACCEPTED,
  76. )
  77. class AccountLoginView(generics.GenericAPIView):
  78. authentication_classes = (authentication.EmailPasswordPayloadAuthentication,)
  79. permission_classes = (IsAuthenticated,)
  80. serializer_class = serializers.TokenSerializer
  81. throttle_scope = "account_management_passive"
  82. def post(self, request, *args, **kwargs):
  83. user = self.request.user
  84. token = Token.objects.create(
  85. user=user,
  86. perm_manage_tokens=True,
  87. max_age=timedelta(days=7),
  88. max_unused_period=timedelta(hours=1),
  89. mfa=False,
  90. )
  91. user_logged_in.send(sender=user.__class__, request=self.request, user=user)
  92. data = self.get_serializer(token, include_plain=True).data
  93. return Response(data)
  94. class AccountLogoutView(APIView, mixins.DestroyModelMixin):
  95. authentication_classes = (authentication.TokenAuthentication,)
  96. permission_classes = (IsAuthenticated,)
  97. throttle_classes = [] # always allow people to log out
  98. def get_object(self):
  99. # self.request.auth contains the hashed key as it is stored in the database
  100. return Token.objects.get(key=self.request.auth)
  101. def post(self, request, *args, **kwargs):
  102. return self.destroy(request, *args, **kwargs)
  103. class AccountChangeEmailView(generics.GenericAPIView):
  104. authentication_classes = (authentication.EmailPasswordPayloadAuthentication,)
  105. permission_classes = (IsAuthenticated,)
  106. serializer_class = serializers.ChangeEmailSerializer
  107. throttle_scope = "account_management_active"
  108. def post(self, request, *args, **kwargs):
  109. # Check password and extract `new_email` field
  110. serializer = self.get_serializer(data=request.data)
  111. serializer.is_valid(raise_exception=True)
  112. new_email = serializer.validated_data["new_email"]
  113. serializers.AuthenticatedChangeEmailUserActionSerializer.build_and_save(
  114. user=request.user, new_email=new_email
  115. )
  116. # At this point, we know that we are talking to the user, so we can tell that we sent an email.
  117. return Response(
  118. data={
  119. "detail": "Please check your mailbox to confirm email address change."
  120. },
  121. status=status.HTTP_202_ACCEPTED,
  122. )
  123. class AccountResetPasswordView(generics.GenericAPIView):
  124. serializer_class = serializers.ResetPasswordSerializer
  125. throttle_scope = "account_management_active"
  126. def post(self, request, *args, **kwargs):
  127. serializer = self.get_serializer(data=request.data)
  128. serializer.is_valid(raise_exception=True)
  129. try:
  130. email = serializer.validated_data["email"]
  131. user = User.objects.get(email=email, is_active=True)
  132. except User.DoesNotExist:
  133. pass
  134. else:
  135. serializers.AuthenticatedResetPasswordUserActionSerializer.build_and_save(
  136. user=user
  137. )
  138. # This request is unauthenticated, so don't expose whether we did anything.
  139. return Response(
  140. data={
  141. "detail": "Please check your mailbox for further password reset instructions. "
  142. "If you did not receive an email, please contact support."
  143. },
  144. status=status.HTTP_202_ACCEPTED,
  145. )