from datetime import date from django.contrib.auth import get_user_model from django.core.mail import EmailMultiAlternatives from django.template.loader import render_to_string from django.utils import six from django.utils.crypto import constant_time_compare, salted_hmac from django.utils.encoding import force_bytes from django.utils.http import base36_to_int, int_to_base36 from .compat import urlsafe_base64_encode from .conf import settings try: from django.contrib.sites.shortcuts import get_current_site except ImportError: # pragma: no cover from django.contrib.sites.models import get_current_site try: from django.db.models.signals import post_migrate except ImportError: # pragma: no cover from django.db.models.signals import post_syncdb as post_migrate if settings.USERS_CREATE_SUPERUSER: try: # create_superuser is removed in django 1.7 from django.contrib.auth.management import create_superuser except ImportError: # pragma: no cover pass else: # Prevent interactive question about wanting a superuser created. from django.contrib.auth import models as auth_app post_migrate.disconnect( create_superuser, sender=auth_app, dispatch_uid='django.contrib.auth.management.create_superuser') def auto_create_superuser(sender, **kwargs): if not settings.USERS_CREATE_SUPERUSER: return email = settings.USERS_SUPERUSER_EMAIL password = settings.USERS_SUPERUSER_PASSWORD User = get_user_model() try: User.base_objects.get(email=email) except User.DoesNotExist: print('Creating superuser ({0}:{1})'.format(email, password)) User.objects.create_superuser(email, password) post_migrate.connect(auto_create_superuser, sender=None) class EmailActivationTokenGenerator(object): def make_token(self, user): return self._make_token_with_timestamp(user, self._num_days(self._today())) def check_token(self, user, token): """ Check that a activation token is correct for a given user. """ # Parse the token try: ts_b36, hash = token.split('-') except ValueError: return False try: ts = base36_to_int(ts_b36) except ValueError: return False # Check that the timestamp/uid has not been tampered with if not constant_time_compare(self._make_token_with_timestamp(user, ts), token): return False # Check the timestamp is within limit if (self._num_days(self._today()) - ts) > settings.USERS_EMAIL_CONFIRMATION_TIMEOUT_DAYS: return False return True def _make_token_with_timestamp(self, user, timestamp): ts_b36 = int_to_base36(timestamp) key_salt = 'users.utils.EmailActivationTokenGenerator' login_timestamp = '' if user.last_login is None else \ user.last_login.replace(microsecond=0, tzinfo=None) value = (six.text_type(user.pk) + six.text_type(user.email) + six.text_type(login_timestamp) + six.text_type(timestamp)) hash = salted_hmac(key_salt, value).hexdigest()[::2] return '%s-%s' % (ts_b36, hash) @staticmethod def _num_days(dt): return (dt - date(2001, 1, 1)).days @staticmethod def _today(): # Used for mocking in tests return date.today() def send_activation_email( user=None, request=None, from_email=None, subject_template='users/activation_email_subject.html', email_template='users/activation_email.html', html_email_template=None): if not user.is_active and settings.USERS_VERIFY_EMAIL: token_generator = EmailActivationTokenGenerator() current_site = get_current_site(request) context = { 'email': user.email, 'site': current_site, 'expiration_days': settings.USERS_EMAIL_CONFIRMATION_TIMEOUT_DAYS, 'user': user, 'uid': urlsafe_base64_encode(force_bytes(user.pk)), 'token': token_generator.make_token(user=user), 'protocol': 'https' if request.is_secure() else 'http', } subject = render_to_string(subject_template, context) # email subject *must not* contain newlines subject = ''.join(subject.splitlines()) body = render_to_string(email_template, context) email_message = EmailMultiAlternatives(subject, body, from_email, [user.email]) if html_email_template is not None: html_email = render_to_string(html_email_template, context) email_message.attach_alternative(html_email, 'text/html') email_message.send()