99
1010logger = logging .getLogger (__name__ )
1111
12- class Signer (object ):
13- def sign_assertion (
14- self , audience , issuer , subject , expires_at ,
12+ class AssertionCreator (object ):
13+ def create_normal_assertion (
14+ self , audience , issuer , subject , expires_at = None , expires_in = 600 ,
1515 issued_at = None , assertion_id = None , ** kwargs ):
16- # Names are defined in https://tools.ietf.org/html/rfc7521#section-5
16+ """Create an assertion in bytes, based on the provided claims.
17+
18+ All parameter names are defined in https://tools.ietf.org/html/rfc7521#section-5
19+ except the expires_in is defined here as lifetime-in-seconds,
20+ which will be automatically translated into expires_at in UTC.
21+ """
1722 raise NotImplementedError ("Will be implemented by sub-class" )
1823
24+ def create_regenerative_assertion (
25+ self , audience , issuer , subject = None , expires_in = 600 , ** kwargs ):
26+ """Create an assertion as a callable,
27+ which will then compute the assertion later when necessary.
28+
29+ This is a useful optimization to reuse the client assertion.
30+ """
31+ return AutoRefresher ( # Returns a callable
32+ lambda a = audience , i = issuer , s = subject , e = expires_in , kwargs = kwargs :
33+ self .create_normal_assertion (a , i , s , expires_in = e , ** kwargs ),
34+ expires_in = max (expires_in - 60 , 0 ))
35+
36+
37+ class AutoRefresher (object ):
38+ """Cache the output of a factory, and auto-refresh it when necessary. Usage::
1939
20- class JwtSigner (Signer ):
40+ r = AutoRefresher(time.time, expires_in=5)
41+ for i in range(15):
42+ print(r()) # the timestamp change only after every 5 seconds
43+ time.sleep(1)
44+ """
45+ def __init__ (self , factory , expires_in = 540 ):
46+ self ._factory = factory
47+ self ._expires_in = expires_in
48+ self ._buf = {}
49+ def __call__ (self ):
50+ EXPIRES_AT , VALUE = "expires_at" , "value"
51+ now = time .time ()
52+ if self ._buf .get (EXPIRES_AT , 0 ) <= now :
53+ logger .debug ("Regenerating new assertion" )
54+ self ._buf = {VALUE : self ._factory (), EXPIRES_AT : now + self ._expires_in }
55+ else :
56+ logger .debug ("Reusing still valid assertion" )
57+ return self ._buf .get (VALUE )
58+
59+
60+ class JwtAssertionCreator (AssertionCreator ):
2161 def __init__ (self , key , algorithm , sha1_thumbprint = None , headers = None ):
22- """Create a signer .
62+ """Construct a Jwt assertion creator .
2363
2464 Args:
2565
@@ -37,11 +77,11 @@ def __init__(self, key, algorithm, sha1_thumbprint=None, headers=None):
3777 self .headers ["x5t" ] = base64 .urlsafe_b64encode (
3878 binascii .a2b_hex (sha1_thumbprint )).decode ()
3979
40- def sign_assertion (
41- self , audience , issuer , subject = None , expires_at = None ,
80+ def create_normal_assertion (
81+ self , audience , issuer , subject = None , expires_at = None , expires_in = 600 ,
4282 issued_at = None , assertion_id = None , not_before = None ,
4383 additional_claims = None , ** kwargs ):
44- """Sign a JWT Assertion.
84+ """Create a JWT Assertion.
4585
4686 Parameters are defined in https://tools.ietf.org/html/rfc7523#section-3
4787 Key-value pairs in additional_claims will be added into payload as-is.
@@ -51,7 +91,7 @@ def sign_assertion(
5191 'aud' : audience ,
5292 'iss' : issuer ,
5393 'sub' : subject or issuer ,
54- 'exp' : expires_at or (now + 10 * 60 ), # 10 minutes
94+ 'exp' : expires_at or (now + expires_in ),
5595 'iat' : issued_at or now ,
5696 'jti' : assertion_id or str (uuid .uuid4 ()),
5797 }
@@ -68,3 +108,9 @@ def sign_assertion(
68108 'See https://pyjwt.readthedocs.io/en/latest/installation.html#cryptographic-dependencies-optional' )
69109 raise
70110
111+
112+ # Obsolete. For backward compatibility. They will be removed in future versions.
113+ Signer = AssertionCreator # For backward compatibility
114+ JwtSigner = JwtAssertionCreator # For backward compatibility
115+ JwtSigner .sign_assertion = JwtAssertionCreator .create_normal_assertion # For backward compatibility
116+
0 commit comments