@@ -435,6 +435,41 @@ def acquire_token_silent(
435435 or by finding a valid refresh token from cache and then automatically
436436 use it to redeem a new access token.
437437
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+
438473 :param list[str] scopes: (Required)
439474 Scopes requested to access a protected API (a resource).
440475 :param account:
@@ -444,8 +479,11 @@ def acquire_token_silent(
444479 If True, it will skip Access Token look-up,
445480 and try to find a Refresh Token to obtain a new Access Token.
446481 :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.
449487 """
450488 assert isinstance (scopes , list ), "Invalid parameter type"
451489 self ._validate_ssh_cert_input_data (kwargs .get ("data" , {}))
@@ -460,8 +498,9 @@ def acquire_token_silent(
460498 scopes , account , self .authority , force_refresh = force_refresh ,
461499 correlation_id = correlation_id ,
462500 ** kwargs )
463- if result :
501+ if result and "error" not in result :
464502 return result
503+ final_result = result
465504 for alias in self ._get_authority_aliases (self .authority .instance ):
466505 the_authority = Authority (
467506 "https://" + alias + "/" + self .authority .tenant ,
@@ -472,7 +511,18 @@ def acquire_token_silent(
472511 correlation_id = correlation_id ,
473512 ** kwargs )
474513 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
476526
477527 def _acquire_token_silent_from_cache_and_possibly_refresh_it (
478528 self ,
@@ -533,13 +583,13 @@ def _acquire_token_silent_by_finding_rt_belongs_to_me_or_my_family(
533583 # https://msazure.visualstudio.com/One/_git/ESTS-Docs/pullrequest/1138595
534584 "client_mismatch" in response .get ("error_additional_info" , []),
535585 ** kwargs )
536- if at :
586+ if at and "error" not in at :
537587 return at
538588 if app_metadata .get ("family_id" ): # Meaning this app belongs to this family
539589 at = self ._acquire_token_silent_by_finding_specific_refresh_token (
540590 authority , scopes , dict (query , family_id = app_metadata ["family_id" ]),
541591 ** kwargs )
542- if at :
592+ if at and "error" not in at :
543593 return at
544594 # Either this app is an orphan, so we will naturally use its own RT;
545595 # 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(
562612 query = query )
563613 logger .debug ("Found %d RTs matching %s" , len (matches ), query )
564614 client = self ._build_client (self .client_credential , authority )
615+
616+ response = None # A distinguishable value to mean cache is empty
565617 for entry in matches :
566618 logger .debug ("Cache attempts an RT" )
567619 response = client .obtain_token_by_refresh_token (
@@ -582,6 +634,7 @@ def _acquire_token_silent_by_finding_specific_refresh_token(
582634 ))
583635 if break_condition (response ):
584636 break
637+ return response # Returns the latest error (if any), or just None
585638
586639 def _validate_ssh_cert_input_data (self , data ):
587640 if data .get ("token_type" ) == "ssh-cert" :
0 commit comments