tokens.py 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import json
  2. from django.core.signing import TimestampSigner, BadSignature
  3. from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
  4. from django.utils.encoding import DjangoUnicodeDecodeError
  5. from .models import Contrib
  6. class URLTokenManager(TimestampSigner):
  7. """ Handle signed json data as URL-safe strings
  8. This class has two responsibilities:
  9. - sign/unsign
  10. - pack/unpack JSON to base64
  11. """
  12. def sign(self, payload, *args, **kwargs):
  13. """
  14. :param payload: the data to be embeded into the token
  15. :type data: dict
  16. """
  17. return urlsafe_base64_encode(
  18. super().sign(
  19. json.dumps(payload), *args, **kwargs
  20. ).encode('utf-8')).decode('ascii')
  21. def unsign(self, encoded_payload, *args, **kwargs):
  22. decoded_payload = urlsafe_base64_decode(encoded_payload)
  23. unsigned = super().unsign(decoded_payload)
  24. return json.loads(unsigned)
  25. class TokenError(Exception):
  26. pass
  27. class ContribTokenManager:
  28. """Produce and use signed URL tokens for account-less contrib management
  29. """
  30. SCOPE = 'contrib/manage'
  31. def __init__(self):
  32. self.manager = URLTokenManager()
  33. def mk_token(self, contrib):
  34. """ Generate a signed contrib management token
  35. Valid for a given contrib, and for a limited time.
  36. :type contrib: Contrib
  37. :rtype str:
  38. """
  39. return self.manager.sign({'scope': self.SCOPE, 'pk': contrib.pk})
  40. def get_instance_if_allowed(self, encoded_token, pk=None):
  41. """Return a contrib if the token grants authz for that Contrib pk
  42. Raise a TokenError if not authorized.
  43. :param pk: the contrib pk (optional, if you want to check that the
  44. instance is the right one)
  45. :param encoded_token: the encoded token, from ``mk_token()``:
  46. :return: a Contrib instance or None, if the object does not exist
  47. :rtype Contrib:
  48. """
  49. try:
  50. data = self.manager.unsign(encoded_token)
  51. except BadSignature:
  52. raise TokenError('Invalid token signature')
  53. except (DjangoUnicodeDecodeError, UnicodeDecodeError):
  54. raise TokenError('This is not a well-formed token')
  55. if (pk is not None) and (data['pk'] != pk):
  56. raise TokenError('Token is not valid for id {}'.format(pk))
  57. else:
  58. try:
  59. return Contrib.objects.get(pk=data['pk'])
  60. except Contrib.DoesNotExist:
  61. return None