from django.conf import settings from django.contrib.auth.tokens import PasswordResetTokenGenerator from django.urls import re_path from django.utils.crypto import constant_time_compare from django.utils.http import base36_to_int from organizations.backends.defaults import InvitationBackend as BaseInvitationBackend from .models import Organization from .tasks import send_email_invite REGISTRATION_TIMEOUT_DAYS = getattr(settings, "REGISTRATION_TIMEOUT_DAYS", 15) class InvitationTokenGenerator(PasswordResetTokenGenerator): def _make_hash_value(self, user, timestamp): return str(user.pk) + str(timestamp) def check_token(self, user, token): """ Check that a password reset 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, self.secret), token ): return False # Check the timestamp is within limit if (self._num_seconds(self._now()) - ts) > REGISTRATION_TIMEOUT_DAYS * 86400: return False return True class InvitationBackend(BaseInvitationBackend): """ Based on django-organizations InvitationBackend but for org user instead of user """ def __init__(self, org_model=None, namespace=None): self.user_model = None self.org_model = Organization self.namespace = namespace def get_urls(self): return [ re_path( r"^(?P[\d]+)/(?P[0-9A-Za-z]{1,90}-[0-9A-Za-z]{1,90})/$", view=self.activate_view, name="invitations_register", ), ] def get_token(self, org_user, **kwargs): return InvitationTokenGenerator().make_token(org_user) def send_invitation(self, user, sender=None, **kwargs): kwargs.update( { "token": self.get_token(user), "organization": user.organization, } ) send_email_invite.delay(user.pk, kwargs["token"]) return True