@@ -10,7 +10,8 @@ namespace Microsoft.Azure.Cosmos
1010 using System . Threading ;
1111 using System . Threading . Tasks ;
1212 using global ::Azure ;
13- using global ::Azure . Core ;
13+ using global ::Azure . Core ;
14+ using Microsoft . Azure . Cosmos . Authorization ;
1415 using Microsoft . Azure . Cosmos . Core . Trace ;
1516 using Microsoft . Azure . Cosmos . Resource . CosmosExceptions ;
1617 using Microsoft . Azure . Cosmos . Tracing ;
@@ -36,9 +37,7 @@ internal sealed class TokenCredentialCache : IDisposable
3637 // If the background refresh fails with less than a minute then just allow the request to hit the exception.
3738 public static readonly TimeSpan MinimumTimeBetweenBackgroundRefreshInterval = TimeSpan . FromMinutes ( 1 ) ;
3839
39- private const string ScopeFormat = "https://{0}/.default" ;
40-
41- private readonly TokenRequestContext tokenRequestContext ;
40+ private readonly IScopeProvider scopeProvider ;
4241 private readonly TokenCredential tokenCredential ;
4342 private readonly CancellationTokenSource cancellationTokenSource ;
4443 private readonly CancellationToken cancellationToken ;
@@ -51,7 +50,7 @@ internal sealed class TokenCredentialCache : IDisposable
5150 private Task < AccessToken > ? currentRefreshOperation = null ;
5251 private AccessToken ? cachedAccessToken = null ;
5352 private bool isBackgroundTaskRunning = false ;
54- private bool isDisposed = false ;
53+ private bool isDisposed = false ;
5554
5655 internal TokenCredentialCache (
5756 TokenCredential tokenCredential ,
@@ -65,14 +64,7 @@ internal TokenCredentialCache(
6564 throw new ArgumentNullException ( nameof ( accountEndpoint ) ) ;
6665 }
6766
68- string ? scopeOverride = ConfigurationManager . AADScopeOverrideValue ( defaultValue : null ) ;
69-
70- this . tokenRequestContext = new TokenRequestContext ( new string [ ]
71- {
72- ! string . IsNullOrEmpty ( scopeOverride )
73- ? scopeOverride
74- : string . Format ( TokenCredentialCache . ScopeFormat , accountEndpoint . Host )
75- } ) ;
67+ this . scopeProvider = new Microsoft . Azure . Cosmos . Authorization . CosmosScopeProvider ( accountEndpoint ) ;
7668
7769 if ( backgroundTokenCredentialRefreshInterval . HasValue )
7870 {
@@ -129,7 +121,7 @@ public void Dispose()
129121 }
130122
131123 this . cancellationTokenSource . Cancel ( ) ;
132- this . cancellationTokenSource . Dispose ( ) ;
124+ this . cancellationTokenSource . Dispose ( ) ;
133125 this . isDisposed = true ;
134126 }
135127
@@ -171,11 +163,13 @@ private async Task<AccessToken> GetNewTokenAsync(
171163
172164 private async ValueTask < AccessToken > RefreshCachedTokenWithRetryHelperAsync (
173165 ITrace trace )
174- {
166+ {
167+ Exception ? lastException = null ;
168+ const int totalRetryCount = 2 ;
169+ TokenRequestContext tokenRequestContext = default ;
170+
175171 try
176172 {
177- Exception ? lastException = null ;
178- const int totalRetryCount = 2 ;
179173 for ( int retry = 0 ; retry < totalRetryCount ; retry ++ )
180174 {
181175 if ( this . cancellationToken . IsCancellationRequested )
@@ -190,11 +184,13 @@ private async ValueTask<AccessToken> RefreshCachedTokenWithRetryHelperAsync(
190184 name : nameof ( this . RefreshCachedTokenWithRetryHelperAsync ) ,
191185 component : TraceComponent . Authorization ,
192186 level : Tracing . TraceLevel . Info ) )
193- {
187+ {
194188 try
195- {
189+ {
190+ tokenRequestContext = this . scopeProvider . GetTokenRequestContext ( ) ;
191+
196192 this . cachedAccessToken = await this . tokenCredential . GetTokenAsync (
197- requestContext : this . tokenRequestContext ,
193+ requestContext : tokenRequestContext ,
198194 cancellationToken : this . cancellationToken ) ;
199195
200196 if ( ! this . cachedAccessToken . HasValue )
@@ -219,32 +215,15 @@ private async ValueTask<AccessToken> RefreshCachedTokenWithRetryHelperAsync(
219215
220216 return this . cachedAccessToken . Value ;
221217 }
222- catch ( RequestFailedException requestFailedException )
223- {
224- lastException = requestFailedException ;
225- getTokenTrace . AddDatum (
226- $ "RequestFailedException at { DateTime . UtcNow . ToString ( CultureInfo . InvariantCulture ) } ",
227- requestFailedException . Message ) ;
228-
229- DefaultTrace . TraceError ( $ "TokenCredential.GetToken() failed with RequestFailedException. scope = { string . Join ( ";" , this . tokenRequestContext . Scopes ) } , retry = { retry } , Exception = { lastException . Message } ") ;
230-
231- // Don't retry on auth failures
232- if ( requestFailedException . Status == ( int ) HttpStatusCode . Unauthorized ||
233- requestFailedException . Status == ( int ) HttpStatusCode . Forbidden )
234- {
235- this . cachedAccessToken = default ;
236- throw ;
237- }
238- }
239218 catch ( OperationCanceledException operationCancelled )
240219 {
241220 lastException = operationCancelled ;
242221 getTokenTrace . AddDatum (
243222 $ "OperationCanceledException at { DateTime . UtcNow . ToString ( CultureInfo . InvariantCulture ) } ",
244- operationCancelled . Message ) ;
245-
246- DefaultTrace . TraceError (
247- $ "TokenCredential.GetTokenAsync() failed. scope = { string . Join ( ";" , this . tokenRequestContext . Scopes ) } , retry = { retry } , Exception = { lastException . Message } ") ;
223+ operationCancelled . Message ) ;
224+
225+ DefaultTrace . TraceError (
226+ $ "TokenCredential.GetTokenAsync() failed. scope = { string . Join ( ";" , tokenRequestContext . Scopes ) } , retry = { retry } , Exception = { lastException . Message } ") ;
248227
249228 throw CosmosExceptionFactory . CreateRequestTimeoutException (
250229 message : ClientResources . FailedToGetAadToken ,
@@ -255,15 +234,29 @@ private async ValueTask<AccessToken> RefreshCachedTokenWithRetryHelperAsync(
255234 innerException : lastException ,
256235 trace : getTokenTrace ) ;
257236 }
258- catch ( Exception exception )
259- {
260- lastException = exception ;
261- getTokenTrace . AddDatum (
262- $ "Exception at { DateTime . UtcNow . ToString ( CultureInfo . InvariantCulture ) } ",
263- exception . Message ) ;
264-
265- DefaultTrace . TraceError (
266- $ "TokenCredential.GetTokenAsync() failed. scope = { string . Join ( ";" , this . tokenRequestContext . Scopes ) } , retry = { retry } , Exception = { lastException . Message } ") ;
237+ catch ( Exception exception )
238+ {
239+ lastException = exception ;
240+ getTokenTrace . AddDatum (
241+ $ "Exception at { DateTime . UtcNow . ToString ( CultureInfo . InvariantCulture ) } ",
242+ exception . Message ) ;
243+
244+ DefaultTrace . TraceError ( $ "TokenCredential.GetToken() failed with RequestFailedException. scope = { string . Join ( ";" , tokenRequestContext . Scopes ) } , retry = { retry } , Exception = { lastException . Message } ") ;
245+
246+ // Don't retry on auth failures
247+ if ( exception is RequestFailedException requestFailedException &&
248+ ( requestFailedException . Status == ( int ) HttpStatusCode . Unauthorized ||
249+ requestFailedException . Status == ( int ) HttpStatusCode . Forbidden ) )
250+ {
251+ this . cachedAccessToken = default ;
252+ throw ;
253+ }
254+ bool didFallback = this . scopeProvider . TryFallback ( exception ) ;
255+
256+ if ( didFallback )
257+ {
258+ DefaultTrace . TraceInformation ( $ "TokenCredential.GetTokenAsync() failed. scope = { string . Join ( ";" , tokenRequestContext . Scopes ) } , retry = { retry } , Exception = { lastException . Message } . Fallback attempted: { didFallback } ") ;
259+ }
267260 }
268261 }
269262 }
0 commit comments