users.py 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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(default=_limit_domains_default.__func__, null=True, blank=True)
  39. needs_captcha = models.BooleanField(default=True)
  40. outreach_preference = models.BooleanField(default=True)
  41. objects = MyUserManager()
  42. USERNAME_FIELD = 'email'
  43. REQUIRED_FIELDS = []
  44. def get_full_name(self):
  45. return self.email
  46. def get_short_name(self):
  47. return self.email
  48. def __str__(self):
  49. return self.email
  50. # noinspection PyMethodMayBeStatic
  51. def has_perm(self, *_):
  52. """Does the user have a specific permission?"""
  53. # Simplest possible answer: Yes, always
  54. return True
  55. # noinspection PyMethodMayBeStatic
  56. def has_module_perms(self, *_):
  57. """Does the user have permissions to view the app `app_label`?"""
  58. # Simplest possible answer: Yes, always
  59. return True
  60. @property
  61. def is_staff(self):
  62. """Is the user a member of staff?"""
  63. # Simplest possible answer: All admins are staff
  64. return self.is_admin
  65. def activate(self):
  66. self.is_active = True
  67. self.needs_captcha = False
  68. self.save()
  69. def change_email(self, email):
  70. old_email = self.email
  71. self.email = email
  72. self.credentials_changed = timezone.now()
  73. self.validate_unique()
  74. self.save()
  75. self.send_email('change-email-confirmation-old-email', recipient=old_email)
  76. def change_password(self, raw_password):
  77. self.set_password(raw_password)
  78. self.credentials_changed = timezone.now()
  79. self.save()
  80. self.send_email('password-change-confirmation')
  81. def delete(self):
  82. pk = self.pk
  83. ret = super().delete()
  84. logger.warning(f'User {pk} deleted')
  85. return ret
  86. def send_email(self, reason, context=None, recipient=None, subject=None, template=None):
  87. fast_lane = 'email_fast_lane'
  88. slow_lane = 'email_slow_lane'
  89. immediate_lane = 'email_immediate_lane'
  90. lanes = {
  91. 'activate-account': slow_lane,
  92. 'change-email': slow_lane,
  93. 'change-email-confirmation-old-email': fast_lane,
  94. 'change-outreach-preference': slow_lane,
  95. 'confirm-account': slow_lane,
  96. 'password-change-confirmation': fast_lane,
  97. 'reset-password': fast_lane,
  98. 'delete-account': fast_lane,
  99. 'domain-dyndns': fast_lane,
  100. 'renew-domain': immediate_lane,
  101. }
  102. if reason not in lanes:
  103. raise ValueError(f'Cannot send email to user {self.pk} without a good reason: {reason}')
  104. context = context or {}
  105. template = template or get_template(f'emails/{reason}/content.txt')
  106. content = template.render(context)
  107. content += f'\nSupport Reference: user_id = {self.pk}\n'
  108. logger.warning(f'Queuing email for user account {self.pk} (reason: {reason}, lane: {lanes[reason]})')
  109. num_queued = EmailMessage(
  110. subject=(subject or get_template(f'emails/{reason}/subject.txt').render(context)).strip(),
  111. body=content,
  112. from_email=get_template('emails/from.txt').render(),
  113. to=[recipient or self.email],
  114. connection=get_connection(lane=lanes[reason], debug={'user': self.pk, 'reason': reason})
  115. ).send()
  116. metrics.get('desecapi_messages_queued').labels(reason, self.pk, lanes[reason]).observe(num_queued)
  117. return num_queued