@@ -16,24 +16,22 @@ def base64decode(raw): # This can handle a padding-less raw input
1616
1717
1818class TokenCache (object ):
19+ """This is considered as a base class containing minimal cache behavior.
20+
21+ Although this class already maintains tokens using unified schema,
22+ it does not serialize/persist them. See subclass SerializableTokenCache
23+ for more details.
24+ """
1925
2026 class CredentialType :
2127 ACCESS_TOKEN = "AccessToken"
2228 REFRESH_TOKEN = "RefreshToken"
2329 ACCOUNT = "Account" # Not exactly a credential type, but we put it here
2430 ID_TOKEN = "IdToken"
2531
26- def __init__ (self , state = None ):
27- """Initialize a token_cache instance, with an optional initial state,
28- which can come from a previous run of token cache instance.
29-
30- Although this class already maintains cached tokens using unified schema,
31- it does not actually persist them.
32- The persistence layer would be implemented in a subclass which provides
33- a serialize() and deserialize() wrapping the self._cache internal structure.
34- """
35- self ._cache = state or {}
32+ def __init__ (self ):
3633 self ._lock = threading .RLock ()
34+ self ._cache = {}
3735
3836 def find (self , credential_type , target = None , query = None ):
3937 target = target or []
@@ -166,3 +164,47 @@ def update_rt(self, rt_item, new_rt):
166164 rt = self ._cache .setdefault (self .CredentialType .REFRESH_TOKEN , {})[key ]
167165 rt ["secret" ] = new_rt
168166
167+
168+ class SerializableTokenCache (TokenCache ):
169+ """This serialization can be a starting point to implement your own persistence.
170+
171+ This class does NOT actually persist the cache on disk/db/etc..
172+ Depends on your need, the following file-based persistence may be sufficient:
173+
174+ import atexit
175+ cache = SerializableTokenCache()
176+ cache.deserialize(open("my_cache.bin", "rb").read())
177+ atexit.register(lambda:
178+ open("my_cache.bin", "wb").write(cache.serialize())
179+ # Hint: The following optional line persists only when state changed
180+ if cache.has_state_changed else None
181+ )
182+ app = ClientApplication(..., token_cache=cache)
183+ ...
184+ """
185+ def add (self , event ):
186+ super (SerializableTokenCache , self ).add (event )
187+ self .has_state_changed = True
188+
189+ def remove_rt (self , rt_item ):
190+ super (SerializableTokenCache , self ).remove_rt (rt_item )
191+ self .has_state_changed = True
192+
193+ def update_rt (self , rt_item , new_rt ):
194+ super (SerializableTokenCache , self ).update_rt (rt_item , new_rt )
195+ self .has_state_changed = True
196+
197+ def deserialize (self , state ):
198+ # type: (Optional[str]) -> None
199+ """Deserialize the cache from a state previously obtained by serialize()"""
200+ with self ._lock :
201+ self ._cache = json .loads (state ) if state else {}
202+ self .has_state_changed = False # reset
203+
204+ def serialize (self ):
205+ # type: () -> str
206+ """Serialize the current cache state into a string."""
207+ with self ._lock :
208+ self .has_state_changed = False
209+ return json .dumps (self ._cache )
210+
0 commit comments