Returns a generator of matching entries. It is O(1) for AT hits, and O(n) for other types. Note that it holds a lock during the entire search.
(self, credential_type, target=None, query=None, *, now=None)
| 234 | if target_set else True) |
| 235 | |
| 236 | def search(self, credential_type, target=None, query=None, *, now=None): # O(n) generator |
| 237 | """Returns a generator of matching entries. |
| 238 | |
| 239 | It is O(1) for AT hits, and O(n) for other types. |
| 240 | Note that it holds a lock during the entire search. |
| 241 | """ |
| 242 | target = sorted(target or []) # Match the order sorted by add() |
| 243 | assert isinstance(target, list), "Invalid parameter type" |
| 244 | |
| 245 | preferred_result = None |
| 246 | if (credential_type == self.CredentialType.ACCESS_TOKEN |
| 247 | and isinstance(query, dict) |
| 248 | and "home_account_id" in query and "environment" in query |
| 249 | and "client_id" in query and "realm" in query and target |
| 250 | ): # Special case for O(1) AT lookup |
| 251 | preferred_result = self._get_access_token( |
| 252 | query["home_account_id"], query["environment"], |
| 253 | query["client_id"], query["realm"], target, |
| 254 | ext_cache_key=query.get("ext_cache_key")) |
| 255 | if preferred_result and self._is_matching( |
| 256 | preferred_result, query, |
| 257 | # Needs no target_set here because it is satisfied by dict key |
| 258 | ): |
| 259 | yield preferred_result |
| 260 | |
| 261 | target_set = set(target) |
| 262 | with self._lock: |
| 263 | # O(n) search. The key is NOT used in search. |
| 264 | now = int(time.time() if now is None else now) |
| 265 | expired_access_tokens = [ |
| 266 | # Especially when/if we key ATs by ephemeral fields such as key_id, |
| 267 | # stale ATs keyed by an old key_id would stay forever. |
| 268 | # Here we collect them for their removal. |
| 269 | ] |
| 270 | for entry in self._cache.get(credential_type, {}).values(): |
| 271 | if ( # Automatically delete expired access tokens |
| 272 | credential_type == self.CredentialType.ACCESS_TOKEN |
| 273 | and int(entry["expires_on"]) < now |
| 274 | ): |
| 275 | expired_access_tokens.append(entry) # Can't delete them within current for-loop |
| 276 | continue |
| 277 | if (entry != preferred_result # Avoid yielding the same entry twice |
| 278 | and self._is_matching(entry, query, target_set=target_set) |
| 279 | ): |
| 280 | # Cache isolation for extended cache keys (e.g., FMI path). |
| 281 | # Entries with ext_cache_key must not match queries without one. |
| 282 | if (credential_type == self.CredentialType.ACCESS_TOKEN |
| 283 | and "ext_cache_key" in entry |
| 284 | and "ext_cache_key" not in (query or {}) |
| 285 | ): |
| 286 | continue |
| 287 | yield entry |
| 288 | for at in expired_access_tokens: |
| 289 | self.remove_at(at) |
| 290 | |
| 291 | def find(self, credential_type, target=None, query=None, *, now=None): |
| 292 | """Equivalent to list(search(...)).""" |