Reputation: 10776
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
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