Jonas Grumann
Jonas Grumann

Reputation: 10776

Django get Model by token

on our Django site we need to create an admin action that will send a mail to the selected user to ask them to update their address. To do so I took a look at django's built in password reset tool, which works using tokens.

I copied and changed a bit their token thing:

class AddressEmailTokenGenerator(object):
    """
    Strategy object used to generate and check tokens for the password
    reset mechanism.
    """
    def make_token(self, address):
        """
        Returns a token that can be used once to do a password reset
        for the given user.
        """
        return self._make_token_with_timestamp(address, self._num_days(self._today()))

    def check_token(self, address, 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(address, ts), token):
            return False

        # Check the timestamp is within limit
        if (self._num_days(self._today()) - ts) > settings.PASSWORD_RESET_TIMEOUT_DAYS:
            return False

        return True

    def _make_token_with_timestamp(self, address, timestamp):
        # timestamp is number of days since 2001-1-1.  Converted to
        # base 36, this gives us a 3 digit string until about 2121
        ts_b36 = int_to_base36(timestamp)

        # By hashing on the internal state of the user and using state
        # that is sure to change (the password salt will change as soon as
        # the password is set, at least for current Django auth, and
        # last_login will also change), we produce a hash that will be
        # invalid as soon as it is used.
        # We limit the hash to 20 chars to keep URL short
        key_salt = "django.contrib.auth.tokens.PasswordResetTokenGenerator"

        # Ensure results are consistent across DB backends
        # login_timestamp = user.last_login.replace(microsecond=0, tzinfo=None)

        value = (six.text_type(address.pk) + six.text_type(timestamp))
        hash = salted_hmac(key_salt, value).hexdigest()[::2]
        return "%s-%s" % (ts_b36, hash)

    def _num_days(self, dt):
        return (dt - date(2001, 1, 1)).days

    def _today(self):
        # Used for mocking in tests
        return date.today()

I have my admin action:

def send_update_request(modeladmin, request, queryset):
    test_mail = "[email protected]"
    for address in queryset:
        token_generator = AddressEmailTokenGenerator()
        token = token_generator.make_token(address)
        print token
        form_url = reverse("update_address", kwargs={"token":token})

        send_mail("Address", form_url, "[email protected]", [test_mail], fail_silently=False)

And now to the question: How can I get the pk of the corresponding address from the token in the url?

My view:

class UpdateAddressView(CreateView):
    model = UpdateAddress

    def get_context_data(self, **kwargs):
        context = super(UpdateAddressView, self).get_context_data(**kwargs)
        token = self.kwargs['token']
        print token
        #The token here is something like "3zi-ebf1da92b5e8bd3aaf86"
        return context

Upvotes: 0

Views: 1014

Answers (1)

Daniel Roseman
Daniel Roseman

Reputation: 599470

You don't, that's not how it works. The Django reset password view has two components: a base-64 encoded string of the user ID, and the token to check validity. Decoding the ID is as simple as calling base64.urlsafe_b64decode, but you can't decode the token at all: it's hashed and salted, the only thing you can do is create a new token with the user object and check that they match.

Upvotes: 1

Related Questions