tokens.py 3.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586
  1. # -*- coding: utf-8 -*-
  2. """ This file contains functions to generate and verify tokens for Flask-User.
  3. Tokens contain an encoded user ID and a signature. The signature is managed by the itsdangerous module.
  4. :copyright: (c) 2013 by Ling Thio
  5. :author: Ling Thio (ling.thio@gmail.com)
  6. :license: Simplified BSD License, see LICENSE.txt for more details."""
  7. import base64
  8. from Crypto.Cipher import AES
  9. from itsdangerous import BadSignature, SignatureExpired, TimestampSigner
  10. class TokenManager(object):
  11. def __init__(self):
  12. """ Create a cypher to encrypt IDs and a signer to sign tokens."""
  13. # Create cypher to encrypt IDs
  14. # and ensure >=16 characters
  15. # secret = app.config.get('SECRET_KEY')
  16. secret = 'SECRET_KEY'
  17. precursor = b'0123456789abcdef'
  18. if isinstance(secret, bytes):
  19. key = secret + precursor
  20. else:
  21. key = secret.encode("utf-8") + precursor
  22. self.cipher = AES.new(key[0:16], AES.MODE_ECB)
  23. # Create signer to sign tokens
  24. self.signer = TimestampSigner(secret)
  25. def encrypt_id(self, id):
  26. """ Encrypts integer ID to url-safe base64 string."""
  27. # 16 byte integer
  28. str1 = '%016d' % id
  29. # encrypted data
  30. str2 = self.cipher.encrypt(str1.encode())
  31. # URL safe base64 string with '=='
  32. str3 = base64.urlsafe_b64encode(str2)
  33. # return base64 string without '=='
  34. return str3[0:-2]
  35. def decrypt_id(self, encrypted_id):
  36. """ Decrypts url-safe base64 string to integer ID"""
  37. # Convert strings and unicode strings to bytes if needed
  38. if hasattr(encrypted_id, 'encode'):
  39. encrypted_id = encrypted_id.encode('ascii', 'ignore')
  40. try:
  41. str3 = encrypted_id + b'==' # --> base64 string with '=='
  42. # print('str3=', str3)
  43. str2 = base64.urlsafe_b64decode(str3) # --> encrypted data
  44. # print('str2=', str2)
  45. str1 = self.cipher.decrypt(str2) # --> 16 byte integer string
  46. # print('str1=', str1)
  47. return int(str1) # --> integer id
  48. except Exception as e: # pragma: no cover
  49. print('!!!Exception in decrypt_id!!!:', e)
  50. return 0
  51. def generate_token(self, id):
  52. """ Return token with id, timestamp and signature"""
  53. # In Python3 we must make sure that bytes are converted to strings.
  54. # Hence the addition of '.decode()'
  55. return self.signer.sign(self.encrypt_id(id)).decode()
  56. def verify_token(self, token, expiration_in_seconds):
  57. """ Verify token and return (is_valid, has_expired, id).
  58. Returns (True, False, id) on success.
  59. Returns (False, True, None) on expired tokens.
  60. Returns (False, False, None) on invalid tokens."""
  61. try:
  62. data = self.signer.unsign(token, max_age=expiration_in_seconds)
  63. is_valid = True
  64. has_expired = False
  65. id = self.decrypt_id(data)
  66. except SignatureExpired:
  67. is_valid = False
  68. has_expired = True
  69. id = None
  70. except BadSignature:
  71. is_valid = False
  72. has_expired = False
  73. id = None
  74. return (is_valid, has_expired, id)