@@ -435,6 +435,41 @@ def acquire_token_silent(
435
435
or by finding a valid refresh token from cache and then automatically
436
436
use it to redeem a new access token.
437
437
438
+ This method will combine the cache empty and refresh error
439
+ into one return value, `None`.
440
+ If your app does not care about the exact token refresh error during
441
+ token cache look-up, then this method is easier and recommended.
442
+
443
+ Internally, this method calls :func:`~acquire_token_silent_with_error`.
444
+
445
+ :return:
446
+ - A dict containing no "error" key,
447
+ and typically contains an "access_token" key,
448
+ if cache lookup succeeded.
449
+ - None when cache lookup does not yield a token.
450
+ """
451
+ result = self .acquire_token_silent_with_error (
452
+ scopes , account , authority , force_refresh , ** kwargs )
453
+ return result if result and "error" not in result else None
454
+
455
+ def acquire_token_silent_with_error (
456
+ self ,
457
+ scopes , # type: List[str]
458
+ account , # type: Optional[Account]
459
+ authority = None , # See get_authorization_request_url()
460
+ force_refresh = False , # type: Optional[boolean]
461
+ ** kwargs ):
462
+ """Acquire an access token for given account, without user interaction.
463
+
464
+ It is done either by finding a valid access token from cache,
465
+ or by finding a valid refresh token from cache and then automatically
466
+ use it to redeem a new access token.
467
+
468
+ This method will differentiate cache empty from token refresh error.
469
+ If your app cares the exact token refresh error during
470
+ token cache look-up, then this method is suitable.
471
+ Otherwise, the other method :func:`~acquire_token_silent` is recommended.
472
+
438
473
:param list[str] scopes: (Required)
439
474
Scopes requested to access a protected API (a resource).
440
475
:param account:
@@ -444,8 +479,11 @@ def acquire_token_silent(
444
479
If True, it will skip Access Token look-up,
445
480
and try to find a Refresh Token to obtain a new Access Token.
446
481
:return:
447
- - A dict containing "access_token" key, when cache lookup succeeds.
448
- - None when cache lookup does not yield anything.
482
+ - A dict containing no "error" key,
483
+ and typically contains an "access_token" key,
484
+ if cache lookup succeeded.
485
+ - None when there is simply no token in the cache.
486
+ - A dict containing an "error" key, when token refresh failed.
449
487
"""
450
488
assert isinstance (scopes , list ), "Invalid parameter type"
451
489
self ._validate_ssh_cert_input_data (kwargs .get ("data" , {}))
@@ -460,8 +498,9 @@ def acquire_token_silent(
460
498
scopes , account , self .authority , force_refresh = force_refresh ,
461
499
correlation_id = correlation_id ,
462
500
** kwargs )
463
- if result :
501
+ if result and "error" not in result :
464
502
return result
503
+ final_result = result
465
504
for alias in self ._get_authority_aliases (self .authority .instance ):
466
505
the_authority = Authority (
467
506
"https://" + alias + "/" + self .authority .tenant ,
@@ -472,7 +511,18 @@ def acquire_token_silent(
472
511
correlation_id = correlation_id ,
473
512
** kwargs )
474
513
if result :
475
- return result
514
+ if "error" not in result :
515
+ return result
516
+ final_result = result
517
+ if final_result and final_result .get ("suberror" ):
518
+ final_result ["classification" ] = { # Suppress these suberrors, per #57
519
+ "bad_token" : "" ,
520
+ "token_expired" : "" ,
521
+ "protection_policy_required" : "" ,
522
+ "client_mismatch" : "" ,
523
+ "device_authentication_failed" : "" ,
524
+ }.get (final_result ["suberror" ], final_result ["suberror" ])
525
+ return final_result
476
526
477
527
def _acquire_token_silent_from_cache_and_possibly_refresh_it (
478
528
self ,
@@ -533,13 +583,13 @@ def _acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family(
533
583
# https://msazure.visualstudio.com/One/_git/ESTS-Docs/pullrequest/1138595
534
584
"client_mismatch" in response .get ("error_additional_info" , []),
535
585
** kwargs )
536
- if at :
586
+ if at and "error" not in at :
537
587
return at
538
588
if app_metadata .get ("family_id" ): # Meaning this app belongs to this family
539
589
at = self ._acquire_token_silent_by_finding_specific_refresh_token (
540
590
authority , scopes , dict (query , family_id = app_metadata ["family_id" ]),
541
591
** kwargs )
542
- if at :
592
+ if at and "error" not in at :
543
593
return at
544
594
# Either this app is an orphan, so we will naturally use its own RT;
545
595
# or all attempts above have failed, so we fall back to non-foci behavior.
@@ -562,6 +612,8 @@ def _acquire_token_silent_by_finding_specific_refresh_token(
562
612
query = query )
563
613
logger .debug ("Found %d RTs matching %s" , len (matches ), query )
564
614
client = self ._build_client (self .client_credential , authority )
615
+
616
+ response = None # A distinguishable value to mean cache is empty
565
617
for entry in matches :
566
618
logger .debug ("Cache attempts an RT" )
567
619
response = client .obtain_token_by_refresh_token (
@@ -582,6 +634,7 @@ def _acquire_token_silent_by_finding_specific_refresh_token(
582
634
))
583
635
if break_condition (response ):
584
636
break
637
+ return response # Returns the latest error (if any), or just None
585
638
586
639
def _validate_ssh_cert_input_data (self , data ):
587
640
if data .get ("token_type" ) == "ssh-cert" :
0 commit comments