users.py 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. from __future__ import annotations
  2. import uuid
  3. from django.conf import settings
  4. from django.contrib.auth.models import AbstractBaseUser, BaseUserManager
  5. from django.contrib.postgres.fields import CIEmailField
  6. from django.core.mail import EmailMessage, get_connection
  7. from django.db import models
  8. from django.template.loader import get_template
  9. from django.utils import timezone
  10. from django_prometheus.models import ExportModelOperationsMixin
  11. from desecapi import logger, metrics
  12. class MyUserManager(BaseUserManager):
  13. def create_user(self, email, password, **extra_fields):
  14. """
  15. Creates and saves a User with the given email and password.
  16. """
  17. if not email:
  18. raise ValueError("Users must have an email address")
  19. email = self.normalize_email(email)
  20. user = self.model(email=email, **extra_fields)
  21. user.set_password(password)
  22. user.save(using=self._db)
  23. return user
  24. class User(ExportModelOperationsMixin("User"), AbstractBaseUser):
  25. @staticmethod
  26. def _limit_domains_default():
  27. return settings.LIMIT_USER_DOMAIN_COUNT_DEFAULT
  28. id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
  29. email = CIEmailField(
  30. verbose_name="email address",
  31. unique=True,
  32. )
  33. email_verified = models.DateTimeField(null=True, blank=True)
  34. is_active = models.BooleanField(default=True, null=True)
  35. is_admin = models.BooleanField(default=False)
  36. created = models.DateTimeField(auto_now_add=True)
  37. credentials_changed = models.DateTimeField(auto_now_add=True)
  38. limit_domains = models.PositiveIntegerField(
  39. default=_limit_domains_default.__func__, null=True, blank=True
  40. )
  41. needs_captcha = models.BooleanField(default=True)
  42. outreach_preference = models.BooleanField(default=True)
  43. objects = MyUserManager()
  44. USERNAME_FIELD = "email"
  45. REQUIRED_FIELDS = []
  46. def get_full_name(self):
  47. return self.email
  48. def get_short_name(self):
  49. return self.email
  50. def __str__(self):
  51. return self.email
  52. # noinspection PyMethodMayBeStatic
  53. def has_perm(self, *_):
  54. """Does the user have a specific permission?"""
  55. # Simplest possible answer: Yes, always
  56. return True
  57. # noinspection PyMethodMayBeStatic
  58. def has_module_perms(self, *_):
  59. """Does the user have permissions to view the app `app_label`?"""
  60. # Simplest possible answer: Yes, always
  61. return True
  62. @property
  63. def is_staff(self):
  64. """Is the user a member of staff?"""
  65. # Simplest possible answer: All admins are staff
  66. return self.is_admin
  67. def activate(self):
  68. self.is_active = True
  69. self.needs_captcha = False
  70. self.save()
  71. def change_email(self, email):
  72. old_email = self.email
  73. self.email = email
  74. self.credentials_changed = timezone.now()
  75. self.validate_unique()
  76. self.save()
  77. self.send_email("change-email-confirmation-old-email", recipient=old_email)
  78. def change_password(self, raw_password):
  79. self.set_password(raw_password)
  80. self.credentials_changed = timezone.now()
  81. self.save()
  82. self.send_email("password-change-confirmation")
  83. def delete(self):
  84. pk = self.pk
  85. ret = super().delete()
  86. logger.warning(f"User {pk} deleted")
  87. return ret
  88. def send_email(
  89. self, reason, context=None, recipient=None, subject=None, template=None
  90. ):
  91. fast_lane = "email_fast_lane"
  92. slow_lane = "email_slow_lane"
  93. immediate_lane = "email_immediate_lane"
  94. lanes = {
  95. "activate-account": slow_lane,
  96. "change-email": slow_lane,
  97. "change-email-confirmation-old-email": fast_lane,
  98. "change-outreach-preference": slow_lane,
  99. "confirm-account": slow_lane,
  100. "password-change-confirmation": fast_lane,
  101. "reset-password": fast_lane,
  102. "delete-account": fast_lane,
  103. "domain-dyndns": fast_lane,
  104. "renew-domain": immediate_lane,
  105. }
  106. if reason not in lanes:
  107. raise ValueError(
  108. f"Cannot send email to user {self.pk} without a good reason: {reason}"
  109. )
  110. context = context or {}
  111. template = template or get_template(f"emails/{reason}/content.txt")
  112. content = template.render(context)
  113. content += f"\nSupport Reference: user_id = {self.pk}\n"
  114. logger.warning(
  115. f"Queuing email for user account {self.pk} (reason: {reason}, lane: {lanes[reason]})"
  116. )
  117. num_queued = EmailMessage(
  118. subject=(
  119. subject or get_template(f"emails/{reason}/subject.txt").render(context)
  120. ).strip(),
  121. body=content,
  122. from_email=get_template("emails/from.txt").render(),
  123. to=[recipient or self.email],
  124. connection=get_connection(
  125. lane=lanes[reason], debug={"user": self.pk, "reason": reason}
  126. ),
  127. ).send()
  128. metrics.get("desecapi_messages_queued").labels(
  129. reason, self.pk, lanes[reason]
  130. ).observe(num_queued)
  131. return num_queued