< Summary

Class:Azure.Core.Pipeline.BearerTokenAuthenticationPolicy
Assembly:Azure.Core
File(s):C:\Git\azure-sdk-for-net\sdk\core\Azure.Core\src\Pipeline\BearerTokenAuthenticationPolicy.cs
Covered lines:88
Uncovered lines:7
Coverable lines:95
Total lines:239
Line coverage:92.6% (88 of 95)
Covered branches:38
Total branches:40
Branch coverage:95% (38 of 40)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
.ctor(...)-100%100%
.ctor(...)-100%100%
.ctor(...)-100%100%
ProcessAsync(...)-100%100%
Process(...)-100%100%
ProcessAsync()-100%100%
.ctor(...)-100%100%
GetHeaderValueAsync()-87.5%91.67%
GetTaskCompletionSources()-100%100%
GetHeaderValueFromCredentialInBackgroundAsync()-71.43%50%
GetHeaderValueFromCredentialAsync()-100%100%
get_HeaderValue()-100%100%
get_ExpiresOn()-100%100%
get_RefreshOn()-100%100%
.ctor(...)-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\core\Azure.Core\src\Pipeline\BearerTokenAuthenticationPolicy.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4using System;
 5using System.Collections.Generic;
 6using System.Linq;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using Azure.Core.Diagnostics;
 10
 11namespace Azure.Core.Pipeline
 12{
 13    /// <summary>
 14    /// A policy that sends an <see cref="AccessToken"/> provided by a <see cref="TokenCredential"/> as an Authenticatio
 15    /// </summary>
 16    public class BearerTokenAuthenticationPolicy : HttpPipelinePolicy
 17    {
 18        private readonly AccessTokenCache _accessTokenCache;
 19
 20        /// <summary>
 21        /// Creates a new instance of <see cref="BearerTokenAuthenticationPolicy"/> using provided token credential and 
 22        /// </summary>
 23        /// <param name="credential">The token credential to use for authentication.</param>
 24        /// <param name="scope">The scope to authenticate for.</param>
 8825        public BearerTokenAuthenticationPolicy(TokenCredential credential, string scope) : this(credential, new[] { scop
 26
 27        /// <summary>
 28        /// Creates a new instance of <see cref="BearerTokenAuthenticationPolicy"/> using provided token credential and 
 29        /// </summary>
 30        /// <param name="credential">The token credential to use for authentication.</param>
 31        /// <param name="scopes">Scopes to authenticate for.</param>
 32        public BearerTokenAuthenticationPolicy(TokenCredential credential, IEnumerable<string> scopes)
 10433            : this(credential, scopes, TimeSpan.FromMinutes(5), TimeSpan.FromSeconds(30)) { }
 34
 6835        internal BearerTokenAuthenticationPolicy(TokenCredential credential, IEnumerable<string> scopes, TimeSpan tokenR
 6836            Argument.AssertNotNull(credential, nameof(credential));
 6837            Argument.AssertNotNull(scopes, nameof(scopes));
 38
 6839            _accessTokenCache = new AccessTokenCache(credential, scopes.ToArray(), tokenRefreshOffset, tokenRefreshRetry
 6840        }
 41
 42        /// <inheritdoc />
 43        public override ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
 44        {
 47845            return ProcessAsync(message, pipeline, true);
 46        }
 47
 48        /// <inheritdoc />
 49        public override void Process(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline)
 50        {
 47851            ProcessAsync(message, pipeline, false).EnsureCompleted();
 26052        }
 53
 54        private async ValueTask ProcessAsync(HttpMessage message, ReadOnlyMemory<HttpPipelinePolicy> pipeline, bool asyn
 55        {
 95656            if (message.Request.Uri.Scheme != Uri.UriSchemeHttps)
 57            {
 858                throw new InvalidOperationException("Bearer token authentication is not permitted for non TLS protected 
 59            }
 60
 94861            string headerValue = await _accessTokenCache.GetHeaderValueAsync(message, async);
 52062            message.Request.SetHeader(HttpHeader.Names.Authorization, headerValue);
 63
 52064            if (async)
 65            {
 26066                await ProcessNextAsync(message, pipeline).ConfigureAwait(false);
 67            }
 68            else
 69            {
 26070                ProcessNext(message, pipeline);
 71            }
 52072        }
 73
 74        private class AccessTokenCache
 75        {
 6876            private readonly object _syncObj = new object();
 77            private readonly TokenCredential _credential;
 78            private readonly string[] _scopes;
 79            private readonly TimeSpan _tokenRefreshOffset;
 80            private readonly TimeSpan _tokenRefreshRetryDelay;
 81
 82            private TaskCompletionSource<HeaderValueInfo>? _infoTcs;
 83            private TaskCompletionSource<HeaderValueInfo>? _backgroundUpdateTcs;
 6884            public AccessTokenCache(TokenCredential credential, string[] scopes, TimeSpan tokenRefreshOffset, TimeSpan t
 85            {
 6886                _credential = credential;
 6887                _scopes = scopes;
 6888                _tokenRefreshOffset = tokenRefreshOffset;
 6889                _tokenRefreshRetryDelay = tokenRefreshRetryDelay;
 6890            }
 91
 92            public async ValueTask<string> GetHeaderValueAsync(HttpMessage message, bool async)
 93            {
 94                bool getTokenFromCredential;
 95                TaskCompletionSource<HeaderValueInfo> headerValueTcs;
 96                TaskCompletionSource<HeaderValueInfo>? backgroundUpdateTcs;
 94897                (headerValueTcs, backgroundUpdateTcs, getTokenFromCredential) = GetTaskCompletionSources();
 98
 94899                if (getTokenFromCredential)
 100                {
 124101                    if (backgroundUpdateTcs != null)
 102                    {
 20103                        HeaderValueInfo info = headerValueTcs.Task.EnsureCompleted();
 40104                        _ = Task.Run(() => GetHeaderValueFromCredentialInBackgroundAsync(backgroundUpdateTcs, info, mess
 20105                        return info.HeaderValue;
 106                    }
 107
 108                    try
 109                    {
 104110                        HeaderValueInfo info = await GetHeaderValueFromCredentialAsync(message, async, message.Cancellat
 60111                        headerValueTcs.SetResult(info);
 60112                    }
 0113                    catch (OperationCanceledException)
 114                    {
 0115                        headerValueTcs.SetCanceled();
 0116                        throw;
 117                    }
 44118                    catch (Exception exception)
 119                    {
 44120                        headerValueTcs.SetException(exception);
 44121                        throw;
 122                    }
 123                }
 124
 884125                var headerValueTask = headerValueTcs.Task;
 884126                if (!headerValueTask.IsCompleted)
 127                {
 602128                    if (async)
 129                    {
 404130                        await headerValueTask.AwaitWithCancellation(message.CancellationToken);
 131                    }
 132                    else
 133                    {
 134                        try
 135                        {
 198136                            headerValueTask.Wait(message.CancellationToken);
 18137                        }
 356138                        catch (AggregateException) { } // ignore exception here to rethrow it with EnsureCompleted
 139                    }
 140                }
 141
 678142                return headerValueTcs.Task.EnsureCompleted().HeaderValue;
 520143            }
 144
 145            private (TaskCompletionSource<HeaderValueInfo> tcs, TaskCompletionSource<HeaderValueInfo>? backgroundUpdateT
 146            {
 948147                lock (_syncObj)
 148                {
 149                    // Initial state. GetTaskCompletionSources has been called for the first time
 948150                    if (_infoTcs == null)
 151                    {
 60152                        _infoTcs = new TaskCompletionSource<HeaderValueInfo>(TaskCreationOptions.RunContinuationsAsynchr
 60153                        return (_infoTcs, default, true);
 154                    }
 155
 156                    // Getting new access token is in progress, wait for it
 888157                    if (!_infoTcs.Task.IsCompleted)
 158                    {
 602159                        _backgroundUpdateTcs = default;
 602160                        return (_infoTcs, _backgroundUpdateTcs, false);
 161                    }
 162
 286163                    DateTimeOffset now = DateTimeOffset.UtcNow;
 164                    // Access token has been successfully acquired in background and it is not expired yet, use it inste
 286165                    if (_backgroundUpdateTcs != null && _backgroundUpdateTcs.Task.Status == TaskStatus.RanToCompletion &
 166                    {
 8167                        _infoTcs = _backgroundUpdateTcs;
 8168                        _backgroundUpdateTcs = default;
 169                    }
 170
 171                    // Attempt to get access token has failed or it has already expired. Need to get a new one
 286172                    if (_infoTcs.Task.Status != TaskStatus.RanToCompletion || now >= _infoTcs.Task.Result.ExpiresOn)
 173                    {
 44174                        _infoTcs = new TaskCompletionSource<HeaderValueInfo>(TaskCreationOptions.RunContinuationsAsynchr
 44175                        return (_infoTcs, default, true);
 176                    }
 177
 178                    // Access token is still valid but is about to expire, try to get it in background
 242179                    if (now >= _infoTcs.Task.Result.RefreshOn && _backgroundUpdateTcs == null)
 180                    {
 20181                        _backgroundUpdateTcs = new TaskCompletionSource<HeaderValueInfo>(TaskCreationOptions.RunContinua
 20182                        return (_infoTcs, _backgroundUpdateTcs, true);
 183                    }
 184
 185                    // Access token is valid, use it
 222186                    return (_infoTcs, default, false);
 187                }
 948188            }
 189
 190            private async ValueTask GetHeaderValueFromCredentialInBackgroundAsync(TaskCompletionSource<HeaderValueInfo> 
 191            {
 20192                var cts = new CancellationTokenSource(_tokenRefreshRetryDelay);
 193                try
 194                {
 20195                    HeaderValueInfo newInfo = await GetHeaderValueFromCredentialAsync(httpMessage, async, cts.Token);
 8196                    backgroundUpdateTcs.SetResult(newInfo);
 8197                }
 0198                catch (OperationCanceledException oce) when (cts.IsCancellationRequested)
 199                {
 0200                    backgroundUpdateTcs.SetResult(new HeaderValueInfo(info.HeaderValue, info.ExpiresOn, DateTimeOffset.U
 0201                    AzureCoreEventSource.Singleton.BackgroundRefreshFailed(httpMessage.Request.ClientRequestId, oce.ToSt
 0202                }
 12203                catch (Exception e)
 204                {
 12205                    backgroundUpdateTcs.SetResult(new HeaderValueInfo(info.HeaderValue, info.ExpiresOn, DateTimeOffset.U
 12206                    AzureCoreEventSource.Singleton.BackgroundRefreshFailed(httpMessage.Request.ClientRequestId, e.ToStri
 12207                }
 208                finally
 209                {
 20210                    cts.Dispose();
 211                }
 20212            }
 213
 214            private async ValueTask<HeaderValueInfo> GetHeaderValueFromCredentialAsync(HttpMessage message, bool async, 
 215            {
 124216                var requestContext = new TokenRequestContext(_scopes, message.Request.ClientRequestId);
 124217                AccessToken token = async
 124218                    ? await _credential.GetTokenAsync(requestContext, cancellationToken).ConfigureAwait(false)
 124219                    : _credential.GetToken(requestContext, cancellationToken);
 220
 68221                return new HeaderValueInfo("Bearer " + token.Token, token.ExpiresOn, token.ExpiresOn - _tokenRefreshOffs
 68222            }
 223
 224            private readonly struct HeaderValueInfo
 225            {
 532226                public string HeaderValue { get; }
 278227                public DateTimeOffset ExpiresOn { get; }
 242228                public DateTimeOffset RefreshOn { get; }
 229
 230                public HeaderValueInfo(string headerValue, DateTimeOffset expiresOn, DateTimeOffset refreshOn)
 231                {
 80232                    HeaderValue = headerValue;
 80233                    ExpiresOn = expiresOn;
 80234                    RefreshOn = refreshOn;
 80235                }
 236            }
 237        }
 238    }
 239}