public async Task <IAuthenticationResult> GetAsync( AzureSqlConnectionStringBuilder connectionString, string clientCertificateData, ILogger logger = null) { AccessTokenCacheValue accessToken; if (TryGetValue(connectionString, out accessToken) && !IsExpired(accessToken)) { // Refresh access token in background, if near expiry or client certificate has changed. if (IsNearExpiry(accessToken) || ClientCertificateHasChanged(accessToken, clientCertificateData)) { TriggerBackgroundRefresh(connectionString, clientCertificateData, logger); } // Returned cached access token. return(accessToken.AuthenticationResult); } // Acquire token in foreground, first time or if already expired. if (await TryRefreshAccessTokenAsync(connectionString, clientCertificateData, logger, ForegroundRefreshTimeoutMilliseconds)) { if (TryGetValue(connectionString, out accessToken)) { return(accessToken.AuthenticationResult); } } throw new InvalidOperationException($"Failed to acquire access token for {connectionString.Sql.InitialCatalog}."); }
/// <summary> /// Refresh the access token in the token cache. /// </summary> private async Task <bool> RefreshAccessTokenAsync( AzureSqlConnectionStringBuilder connectionString, string clientCertificateData, ILogger logger) { try { var start = DateTimeOffset.Now; var accessToken = await AcquireAccessTokenAsync(connectionString, clientCertificateData); Debug.Assert(accessToken != null); logger?.LogInformation("Refreshed access token {Token} for catalog {Catalog} which expires in {ExpirationMinutes}m (Latency = {ElapsedMilliseconds}ms).", accessToken.AuthenticationResult.AccessToken.GetHashCode(), connectionString.Sql.InitialCatalog, (accessToken.AuthenticationResult.ExpiresOn - DateTimeOffset.Now).TotalMinutes.ToString("F2"), (DateTimeOffset.Now - start).TotalMilliseconds.ToString("F2")); _cache.AddOrUpdate(connectionString.ConnectionString, accessToken, (k, v) => accessToken); return(true); } catch (Exception ex) { logger?.LogError(0, ex, "Failed to refresh access token for {Catalog}.", connectionString.Sql.InitialCatalog); return(false); } }
/// <summary> /// Try to refresh the access token in the token cache. /// </summary> private async Task <bool> TryRefreshAccessTokenAsync( AzureSqlConnectionStringBuilder connectionString, string clientCertificateData, ILogger logger, int refreshTimeoutMilliseconds) { if (!await AcquireTokenLock.WaitAsync(refreshTimeoutMilliseconds)) { return(false); } try { TryGetValue(connectionString, out var accessToken); if (accessToken == null || IsNearExpiry(accessToken) || ClientCertificateHasChanged(accessToken, clientCertificateData)) { return(await RefreshAccessTokenAsync(connectionString, clientCertificateData, logger)); } } finally { AcquireTokenLock.Release(); } return(true); }
/// <summary> /// Refresh the access token in the token cache. /// </summary> private async Task <bool> RefreshAccessTokenAsync( AzureSqlConnectionStringBuilder connectionString, string clientCertificateData, ILogger logger) { try { var start = DateTimeOffset.Now; var accessToken = await AcquireAccessTokenAsync(connectionString, clientCertificateData); Debug.Assert(accessToken != null); logger?.LogInformation("Refreshed access token for {InitialCatalog} in {ElapsedMilliseconds}.", connectionString.Sql.InitialCatalog, (DateTimeOffset.Now - start).TotalMilliseconds); _cache.AddOrUpdate(connectionString.ConnectionString, accessToken, (k, v) => accessToken); return(true); } catch (Exception ex) { logger?.LogError(0, ex, "Failed to refresh access token for {InitialCatalog}.", connectionString.Sql.InitialCatalog); return(false); } }
public AzureSqlConnectionFactory( AzureSqlConnectionStringBuilder connectionString, ISecretInjector secretInjector, ILogger logger = null) { ConnectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString)); SecretInjector = secretInjector ?? throw new ArgumentNullException(nameof(secretInjector)); Logger = logger; }
public AzureSqlConnectionFactory(string connectionString, ISecretInjector secretInjector, ILogger logger = null) { if (string.IsNullOrEmpty(connectionString)) { throw new ArgumentException("Value cannot be null or empty", nameof(connectionString)); } ConnectionString = new AzureSqlConnectionStringBuilder(connectionString); SecretInjector = secretInjector ?? throw new ArgumentNullException(nameof(secretInjector)); Logger = logger; }
/// <summary> /// Attempt non-blocking refresh of access token in background with retries. /// </summary> private void TriggerBackgroundRefresh( AzureSqlConnectionStringBuilder connectionString, string clientCertificateData, ILogger logger) { Task.Run(async() => { await TryRefreshAccessTokenAsync(connectionString, clientCertificateData, logger, refreshTimeoutMilliseconds: BackgroundRefreshTimeoutMilliseconds); }); }
/// <summary> /// Request an access token from the token service. /// </summary> protected virtual async Task <AccessTokenCacheValue> AcquireAccessTokenAsync( AzureSqlConnectionStringBuilder connectionString, string clientCertificateData) { using (var certificate = new X509Certificate2(Convert.FromBase64String(clientCertificateData), string.Empty)) { var clientAssertion = new ClientAssertionCertificate(connectionString.AadClientId, certificate); var authContext = new AuthenticationContext(connectionString.AadAuthority, tokenCache: null); var authResult = await authContext.AcquireTokenAsync(AzureSqlResourceTokenUrl, clientAssertion, connectionString.AadSendX5c); return(new AccessTokenCacheValue(clientCertificateData, authResult)); } }
protected virtual bool TryGetValue( AzureSqlConnectionStringBuilder connectionString, out AccessTokenCacheValue accessToken) { return(_cache.TryGetValue(connectionString.ConnectionString, out accessToken)); }