11# -*- coding: utf-8 -*-
22
3+ import base64
4+ import hashlib
5+ import hmac
36import json
47import logging
58import os
69import threading
710import time
811import uuid
9- from base64 import b64encode
1012
1113import cherrypy
12- import jwt
1314
1415from .. import mgr
16+ from ..exceptions import ExpiredSignatureError , InvalidAlgorithmError , InvalidTokenError
1517from .access_control import LocalAuthenticator , UserDoesNotExist
1618
1719cherrypy .config .update ({
@@ -33,7 +35,7 @@ class JwtManager(object):
3335 @staticmethod
3436 def _gen_secret ():
3537 secret = os .urandom (16 )
36- return b64encode (secret ).decode ('utf-8' )
38+ return base64 . b64encode (secret ).decode ('utf-8' )
3739
3840 @classmethod
3941 def init (cls ):
@@ -45,6 +47,54 @@ def init(cls):
4547 mgr .set_store ('jwt_secret' , secret )
4648 cls ._secret = secret
4749
50+ @classmethod
51+ def array_to_base64_string (cls , message ):
52+ jsonstr = json .dumps (message , sort_keys = True ).replace (" " , "" )
53+ string_bytes = base64 .urlsafe_b64encode (bytes (jsonstr , 'UTF-8' ))
54+ return string_bytes .decode ('UTF-8' ).replace ("=" , "" )
55+
56+ @classmethod
57+ def encode (cls , message , secret ):
58+ header = {"alg" : cls .JWT_ALGORITHM , "typ" : "JWT" }
59+ base64_header = cls .array_to_base64_string (header )
60+ base64_message = cls .array_to_base64_string (message )
61+ base64_secret = base64 .urlsafe_b64encode (hmac .new (
62+ bytes (secret , 'UTF-8' ),
63+ msg = bytes (base64_header + "." + base64_message , 'UTF-8' ),
64+ digestmod = hashlib .sha256
65+ ).digest ()).decode ('UTF-8' ).replace ("=" , "" )
66+ return base64_header + "." + base64_message + "." + base64_secret
67+
68+ @classmethod
69+ def decode (cls , message , secret ):
70+ split_message = message .split ("." )
71+ base64_header = split_message [0 ]
72+ base64_message = split_message [1 ]
73+ base64_secret = split_message [2 ]
74+
75+ decoded_header = json .loads (base64 .urlsafe_b64decode (base64_header ))
76+
77+ if decoded_header ['alg' ] != cls .JWT_ALGORITHM :
78+ raise InvalidAlgorithmError ()
79+
80+ incoming_secret = base64 .urlsafe_b64encode (hmac .new (
81+ bytes (secret , 'UTF-8' ),
82+ msg = bytes (base64_header + "." + base64_message , 'UTF-8' ),
83+ digestmod = hashlib .sha256
84+ ).digest ()).decode ('UTF-8' ).replace ("=" , "" )
85+
86+ if base64_secret != incoming_secret :
87+ raise InvalidTokenError ()
88+
89+ # We add ==== as padding to ignore the requirement to have correct padding in
90+ # the urlsafe_b64decode method.
91+ decoded_message = json .loads (base64 .urlsafe_b64decode (base64_message + "====" ))
92+ now = int (time .time ())
93+ if decoded_message ['exp' ] < now :
94+ raise ExpiredSignatureError ()
95+
96+ return decoded_message
97+
4898 @classmethod
4999 def gen_token (cls , username ):
50100 if not cls ._secret :
@@ -59,13 +109,13 @@ def gen_token(cls, username):
59109 'iat' : now ,
60110 'username' : username
61111 }
62- return jwt .encode (payload , cls ._secret , algorithm = cls . JWT_ALGORITHM ) # type: ignore
112+ return cls .encode (payload , cls ._secret ) # type: ignore
63113
64114 @classmethod
65115 def decode_token (cls , token ):
66116 if not cls ._secret :
67117 cls .init ()
68- return jwt .decode (token , cls ._secret , algorithms = cls . JWT_ALGORITHM ) # type: ignore
118+ return cls .decode (token , cls ._secret ) # type: ignore
69119
70120 @classmethod
71121 def get_token_from_header (cls ):
@@ -99,8 +149,8 @@ def get_username(cls):
99149 @classmethod
100150 def get_user (cls , token ):
101151 try :
102- dtoken = JwtManager .decode_token (token )
103- if not JwtManager .is_blocklisted (dtoken ['jti' ]):
152+ dtoken = cls .decode_token (token )
153+ if not cls .is_blocklisted (dtoken ['jti' ]):
104154 user = AuthManager .get_user (dtoken ['username' ])
105155 if user .last_update <= dtoken ['iat' ]:
106156 return user
@@ -110,10 +160,12 @@ def get_user(cls, token):
110160 )
111161 else :
112162 cls .logger .debug ('Token is block-listed' ) # type: ignore
113- except jwt . ExpiredSignatureError :
163+ except ExpiredSignatureError :
114164 cls .logger .debug ("Token has expired" ) # type: ignore
115- except jwt . InvalidTokenError :
165+ except InvalidTokenError :
116166 cls .logger .debug ("Failed to decode token" ) # type: ignore
167+ except InvalidAlgorithmError :
168+ cls .logger .debug ("Only the HS256 algorithm is supported." ) # type: ignore
117169 except UserDoesNotExist :
118170 cls .logger .debug ( # type: ignore
119171 "Invalid token: user %s does not exist" , dtoken ['username' ]
0 commit comments