23
23
24
24
25
25
# The __init__.py will import this. Not the other way around.
26
- __version__ = "1.12 .0"
26
+ __version__ = "1.13 .0"
27
27
28
28
logger = logging .getLogger (__name__ )
29
29
@@ -131,6 +131,14 @@ def __init__(
131
131
"The provided signature value did not match the expected signature value",
132
132
you may try use only the leaf cert (in PEM/str format) instead.
133
133
134
+ *Added in version 1.13.0*:
135
+ It can also be a completly pre-signed assertion that you've assembled yourself.
136
+ Simply pass a container containing only the key "client_assertion", like this::
137
+
138
+ {
139
+ "client_assertion": "...a JWT with claims aud, exp, iss, jti, nbf, and sub..."
140
+ }
141
+
134
142
:param dict client_claims:
135
143
*Added in version 0.5.0*:
136
144
It is a dictionary of extra claims that would be signed by
@@ -377,7 +385,7 @@ def _get_regional_authority(self, central_authority):
377
385
validate_authority = False ) # The central_authority has already been validated
378
386
return None
379
387
380
- def _build_client (self , client_credential , authority ):
388
+ def _build_client (self , client_credential , authority , skip_regional_client = False ):
381
389
client_assertion = None
382
390
client_assertion_type = None
383
391
default_headers = {
@@ -391,28 +399,32 @@ def _build_client(self, client_credential, authority):
391
399
default_headers ['x-app-ver' ] = self .app_version
392
400
default_body = {"client_info" : 1 }
393
401
if isinstance (client_credential , dict ):
394
- assert ("private_key" in client_credential
395
- and "thumbprint" in client_credential )
396
- headers = {}
397
- if 'public_certificate' in client_credential :
398
- headers ["x5c" ] = extract_certs (client_credential ['public_certificate' ])
399
- if not client_credential .get ("passphrase" ):
400
- unencrypted_private_key = client_credential ['private_key' ]
401
- else :
402
- from cryptography .hazmat .primitives import serialization
403
- from cryptography .hazmat .backends import default_backend
404
- unencrypted_private_key = serialization .load_pem_private_key (
405
- _str2bytes (client_credential ["private_key" ]),
406
- _str2bytes (client_credential ["passphrase" ]),
407
- backend = default_backend (), # It was a required param until 2020
408
- )
409
- assertion = JwtAssertionCreator (
410
- unencrypted_private_key , algorithm = "RS256" ,
411
- sha1_thumbprint = client_credential .get ("thumbprint" ), headers = headers )
412
- client_assertion = assertion .create_regenerative_assertion (
413
- audience = authority .token_endpoint , issuer = self .client_id ,
414
- additional_claims = self .client_claims or {})
402
+ assert (("private_key" in client_credential
403
+ and "thumbprint" in client_credential ) or
404
+ "client_assertion" in client_credential )
415
405
client_assertion_type = Client .CLIENT_ASSERTION_TYPE_JWT
406
+ if 'client_assertion' in client_credential :
407
+ client_assertion = client_credential ['client_assertion' ]
408
+ else :
409
+ headers = {}
410
+ if 'public_certificate' in client_credential :
411
+ headers ["x5c" ] = extract_certs (client_credential ['public_certificate' ])
412
+ if not client_credential .get ("passphrase" ):
413
+ unencrypted_private_key = client_credential ['private_key' ]
414
+ else :
415
+ from cryptography .hazmat .primitives import serialization
416
+ from cryptography .hazmat .backends import default_backend
417
+ unencrypted_private_key = serialization .load_pem_private_key (
418
+ _str2bytes (client_credential ["private_key" ]),
419
+ _str2bytes (client_credential ["passphrase" ]),
420
+ backend = default_backend (), # It was a required param until 2020
421
+ )
422
+ assertion = JwtAssertionCreator (
423
+ unencrypted_private_key , algorithm = "RS256" ,
424
+ sha1_thumbprint = client_credential .get ("thumbprint" ), headers = headers )
425
+ client_assertion = assertion .create_regenerative_assertion (
426
+ audience = authority .token_endpoint , issuer = self .client_id ,
427
+ additional_claims = self .client_claims or {})
416
428
else :
417
429
default_body ['client_secret' ] = client_credential
418
430
central_configuration = {
@@ -436,7 +448,8 @@ def _build_client(self, client_credential, authority):
436
448
on_updating_rt = self .token_cache .update_rt )
437
449
438
450
regional_client = None
439
- if client_credential : # Currently regional endpoint only serves some CCA flows
451
+ if (client_credential # Currently regional endpoint only serves some CCA flows
452
+ and not skip_regional_client ):
440
453
regional_authority = self ._get_regional_authority (authority )
441
454
if regional_authority :
442
455
regional_configuration = {
@@ -777,7 +790,7 @@ def get_accounts(self, username=None):
777
790
accounts = [a for a in accounts
778
791
if a ["username" ].lower () == lowercase_username ]
779
792
if not accounts :
780
- logger .warning ((
793
+ logger .debug (( # This would also happen when the cache is empty
781
794
"get_accounts(username='{}') finds no account. "
782
795
"If tokens were acquired without 'profile' scope, "
783
796
"they would contain no username for filtering. "
@@ -1102,9 +1115,13 @@ def _acquire_token_silent_by_finding_specific_refresh_token(
1102
1115
# target=scopes, # AAD RTs are scope-independent
1103
1116
query = query )
1104
1117
logger .debug ("Found %d RTs matching %s" , len (matches ), query )
1105
- client , _ = self ._build_client (self .client_credential , authority )
1106
1118
1107
1119
response = None # A distinguishable value to mean cache is empty
1120
+ if not matches : # Then exit early to avoid expensive operations
1121
+ return response
1122
+ client , _ = self ._build_client (
1123
+ # Potentially expensive if building regional client
1124
+ self .client_credential , authority , skip_regional_client = True )
1108
1125
telemetry_context = self ._build_telemetry_context (
1109
1126
self .ACQUIRE_TOKEN_SILENT_ID ,
1110
1127
correlation_id = correlation_id , refresh_reason = refresh_reason )
0 commit comments