/// <summary>
        /// Get token.
        /// </summary>
        public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(() =>
        {
            IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId)
                                           .WithAuthority(parameters.Authority)
                                           .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                                           .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                                           .Build();
            AuthenticationResult result;
            string scope    = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix;
            string[] scopes = new string[] { scope };

            // Note: CorrelationId, which existed in ADAL, can not be set in MSAL (yet?).
            // parameter.ConnectionId was passed as the CorrelationId in ADAL to aid support in troubleshooting.
            // If/When MSAL adds CorrelationId support, it should be passed from parameters here, too.

            SecureString password = new SecureString();
            foreach (char c in parameters.Password)
            {
                password.AppendChar(c);
            }
            password.MakeReadOnly();
            result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password).ExecuteAsync().Result;

            return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
        });
        /// <summary>
        /// Get token.
        /// </summary>
        public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() =>
        {
            IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId)
                                           .WithAuthority(parameters.Authority)
                                           .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                                           .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                                           .Build();
            AuthenticationResult result;
            string[] scopes = parameters.Scopes;

            // Note: CorrelationId, which existed in ADAL, can not be set in MSAL (yet?).
            // parameter.ConnectionId was passed as the CorrelationId in ADAL to aid support in troubleshooting.
            // If/When MSAL adds CorrelationId support, it should be passed from parameters here, too.

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
            {
                result = app.AcquireTokenByIntegratedWindowsAuth(scopes).ExecuteAsync().Result;
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword)
            {
                SecureString password = new SecureString();
                foreach (char c in parameters.Password)
                {
                    password.AppendChar(c);
                }
                password.MakeReadOnly();
                result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password).ExecuteAsync().Result;
            }
            else
            {
                result = await app.AcquireTokenInteractive(scopes)
                         .WithUseEmbeddedWebView(true)
                         .ExecuteAsync();
            }

            return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
        });
 /// <include file='../../../../../../../doc/snippets/Microsoft.Data.SqlClient/SqlAuthenticationProvider.xml' path='docs/members[@name="SqlAuthenticationProvider"]/AcquireTokenAsync/*'/>
 public abstract Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters);
Example #4
0
        /// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/>
        public override async Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters)
        {
            CancellationTokenSource cts = new CancellationTokenSource();

            // Use Connection timeout value to cancel token acquire request after certain period of time.
            cts.CancelAfter(parameters.ConnectionTimeout * 1000); // Convert to milliseconds

            string scope = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix;

            string[] scopes = new string[] { scope };

            int    seperatorIndex = parameters.Authority.LastIndexOf('/');
            string tenantId       = parameters.Authority.Substring(seperatorIndex + 1);
            string authority      = parameters.Authority.Remove(seperatorIndex + 1);

            TokenRequestContext tokenRequestContext = new TokenRequestContext(scopes);
            string clientId = string.IsNullOrWhiteSpace(parameters.UserId) ? null : parameters.UserId;

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDefault)
            {
                DefaultAzureCredentialOptions defaultAzureCredentialOptions = new DefaultAzureCredentialOptions()
                {
                    AuthorityHost                       = new Uri(authority),
                    ManagedIdentityClientId             = clientId,
                    InteractiveBrowserTenantId          = tenantId,
                    SharedTokenCacheTenantId            = tenantId,
                    SharedTokenCacheUsername            = clientId,
                    VisualStudioCodeTenantId            = tenantId,
                    VisualStudioTenantId                = tenantId,
                    ExcludeInteractiveBrowserCredential = true // Force disabled, even though it's disabled by default to respect driver specifications.
                };
                AccessToken accessToken = await new DefaultAzureCredential(defaultAzureCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token);
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Default auth mode. Expiry Time: {0}", accessToken.ExpiresOn);
                return(new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn));
            }

            TokenCredentialOptions tokenCredentialOptions = new TokenCredentialOptions()
            {
                AuthorityHost = new Uri(authority)
            };

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryManagedIdentity || parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryMSI)
            {
                AccessToken accessToken = await new ManagedIdentityCredential(clientId, tokenCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token);
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Managed Identity auth mode. Expiry Time: {0}", accessToken.ExpiresOn);
                return(new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn));
            }

            AuthenticationResult result;

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal)
            {
                AccessToken accessToken = await new ClientSecretCredential(tenantId, parameters.UserId, parameters.Password, tokenCredentialOptions).GetTokenAsync(tokenRequestContext, cts.Token);
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", accessToken.ExpiresOn);
                return(new SqlAuthenticationToken(accessToken.Token, accessToken.ExpiresOn));
            }

            /*
             * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows
             * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend
             * that you use https://login.microsoftonline.com/common/oauth2/nativeclient.
             *
             * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris
             */
            string redirectUri = s_nativeClientRedirectUri;

#if NETCOREAPP
            if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)
            {
                redirectUri = "http://localhost";
            }
#endif
            PublicClientAppKey pcaKey = new PublicClientAppKey(parameters.Authority, redirectUri, _applicationClientId
#if NETFRAMEWORK
                                                               , _iWin32WindowFunc
#endif
#if NETSTANDARD
                                                               , _parentActivityOrWindowFunc
#endif
                                                               );

            IPublicClientApplication app = GetPublicClientAppInstance(pcaKey);

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
            {
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    result = await app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .WithUsername(parameters.UserId)
                             .ExecuteAsync(cancellationToken: cts.Token);
                }
                else
                {
                    result = await app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .ExecuteAsync(cancellationToken: cts.Token);
                }
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result?.ExpiresOn);
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword)
            {
                SecureString password = new SecureString();
                foreach (char c in parameters.Password)
                {
                    password.AppendChar(c);
                }
                password.MakeReadOnly();

                result = await app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password)
                         .WithCorrelationId(parameters.ConnectionId)
                         .ExecuteAsync(cancellationToken: cts.Token);

                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result?.ExpiresOn);
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive ||
                     parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)
            {
                // Fetch available accounts from 'app' instance
                System.Collections.Generic.IEnumerator <IAccount> accounts = (await app.GetAccountsAsync()).GetEnumerator();

                IAccount account = default;
                if (accounts.MoveNext())
                {
                    if (!string.IsNullOrEmpty(parameters.UserId))
                    {
                        do
                        {
                            IAccount currentVal = accounts.Current;
                            if (string.Compare(parameters.UserId, currentVal.Username, StringComparison.InvariantCultureIgnoreCase) == 0)
                            {
                                account = currentVal;
                                break;
                            }
                        }while (accounts.MoveNext());
                    }
                    else
                    {
                        account = accounts.Current;
                    }
                }

                if (null != account)
                {
                    try
                    {
                        // If 'account' is available in 'app', we use the same to acquire token silently.
                        // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent
                        result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync(cancellationToken: cts.Token);

                        SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn);
                    }
                    catch (MsalUiRequiredException)
                    {
                        // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application,
                        // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired),
                        // or the user needs to perform two factor authentication.
                        result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts);

                        SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn);
                    }
                }
                else
                {
                    // If no existing 'account' is found, we request user to sign in interactively.
                    result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod, cts);

                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result?.ExpiresOn);
                }
            }
            else
            {
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod);
                throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod);
            }

            return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
        }
        /// <summary>
        /// Get token.
        /// </summary>
        public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() =>
        {
            AuthenticationResult result;
            string scope    = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix;
            string[] scopes = new string[] { scope };

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal)
            {
                IConfidentialClientApplication ccApp = ConfidentialClientApplicationBuilder.Create(parameters.UserId)
                                                       .WithAuthority(parameters.Authority)
                                                       .WithClientSecret(parameters.Password)
                                                       .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                                                       .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                                                       .Build();

                result = ccApp.AcquireTokenForClient(scopes).ExecuteAsync().Result;
                return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
            }

            IPublicClientApplication app = PublicClientApplicationBuilder.Create(ActiveDirectoryAuthentication.AdoClientId)
                                           .WithAuthority(parameters.Authority)
                                           .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                                           .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
#if netcoreapp
                                           .WithRedirectUri("http://localhost")
#else
                                           /*
                                            * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows
                                            * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend
                                            * that you use https://login.microsoftonline.com/common/oauth2/nativeclient.
                                            *
                                            * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris
                                            */
                                           .WithRedirectUri("https://login.microsoftonline.com/oauth2/nativeclient")
#endif
                                           .Build();

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
            {
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    result = app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .WithUsername(parameters.UserId)
                             .ExecuteAsync().Result;
                }
                else
                {
                    result = app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .ExecuteAsync().Result;
                }
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword)
            {
                SecureString password = new SecureString();
                foreach (char c in parameters.Password)
                {
                    password.AppendChar(c);
                }
                password.MakeReadOnly();
                result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password)
                         .WithCorrelationId(parameters.ConnectionId)
                         .ExecuteAsync().Result;
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive)
            {
                var accounts = await app.GetAccountsAsync();
                IAccount account;
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    account = accounts.FirstOrDefault(a => parameters.UserId.Equals(a.Username, System.StringComparison.InvariantCultureIgnoreCase));
                }
                else
                {
                    account = accounts.FirstOrDefault();
                }

                if (null != account)
                {
                    try
                    {
                        result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
                    }
                    catch (MsalUiRequiredException)
                    {
                        result = await AcquireTokenInteractive(app, scopes, parameters.ConnectionId, parameters.UserId);
                    }
                }
                else
                {
                    result = await AcquireTokenInteractive(app, scopes, parameters.ConnectionId, parameters.UserId);
                }
            }
            else
            {
                throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod);
            }

            return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
        });
Example #6
0
        /// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/>
        public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() =>
        {
            AuthenticationResult result;
            string scope    = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix;
            string[] scopes = new string[] { scope };

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal)
            {
                IConfidentialClientApplication ccApp = ConfidentialClientApplicationBuilder.Create(parameters.UserId)
                                                       .WithAuthority(parameters.Authority)
                                                       .WithClientSecret(parameters.Password)
                                                       .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                                                       .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                                                       .Build();

                result = ccApp.AcquireTokenForClient(scopes).ExecuteAsync().Result;
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", result.ExpiresOn);
                return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
            }

            /*
             * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows
             * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend
             * that you use https://login.microsoftonline.com/common/oauth2/nativeclient.
             *
             * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris
             */
            string redirectUri = s_nativeClientRedirectUri;

#if NETCOREAPP
            if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)
            {
                redirectUri = "http://localhost";
            }
#endif
            PublicClientAppKey pcaKey = new PublicClientAppKey(parameters.Authority, redirectUri, _applicationClientId
#if NETFRAMEWORK
                                                               , _iWin32WindowFunc
#endif
#if NETSTANDARD
                                                               , _parentActivityOrWindowFunc
#endif
                                                               );

            IPublicClientApplication app = GetPublicClientAppInstance(pcaKey);

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
            {
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    result = app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .WithUsername(parameters.UserId)
                             .ExecuteAsync().Result;
                }
                else
                {
                    result = app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .ExecuteAsync().Result;
                }
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result.ExpiresOn);
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword)
            {
                SecureString password = new SecureString();
                foreach (char c in parameters.Password)
                {
                    password.AppendChar(c);
                }
                password.MakeReadOnly();
                result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password)
                         .WithCorrelationId(parameters.ConnectionId)
                         .ExecuteAsync().Result;
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result.ExpiresOn);
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive ||
                     parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)
            {
                // Fetch available accounts from 'app' instance
                System.Collections.Generic.IEnumerable <IAccount> accounts = await app.GetAccountsAsync();
                IAccount account;
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    account = accounts.FirstOrDefault(a => parameters.UserId.Equals(a.Username, System.StringComparison.InvariantCultureIgnoreCase));
                }
                else
                {
                    account = accounts.FirstOrDefault();
                }

                if (null != account)
                {
                    try
                    {
                        // If 'account' is available in 'app', we use the same to acquire token silently.
                        // Read More on API docs: https://docs.microsoft.com/dotnet/api/microsoft.identity.client.clientapplicationbase.acquiretokensilent
                        result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
                        SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                    }
                    catch (MsalUiRequiredException)
                    {
                        // An 'MsalUiRequiredException' is thrown in the case where an interaction is required with the end user of the application,
                        // for instance, if no refresh token was in the cache, or the user needs to consent, or re-sign-in (for instance if the password expired),
                        // or the user needs to perform two factor authentication.
                        result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod);
                        SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                    }
                }
                else
                {
                    // If no existing 'account' is found, we request user to sign in interactively.
                    result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod);
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                }
            }
            else
            {
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod);
                throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod);
            }

            return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
        });
Example #7
0
        /// <include file='../../../../../../doc/snippets/Microsoft.Data.SqlClient/ActiveDirectoryAuthenticationProvider.xml' path='docs/members[@name="ActiveDirectoryAuthenticationProvider"]/AcquireTokenAsync/*'/>
        public override Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters) => Task.Run(async() =>
        {
            AuthenticationResult result;
            string scope    = parameters.Resource.EndsWith(s_defaultScopeSuffix) ? parameters.Resource : parameters.Resource + s_defaultScopeSuffix;
            string[] scopes = new string[] { scope };

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryServicePrincipal)
            {
                IConfidentialClientApplication ccApp = ConfidentialClientApplicationBuilder.Create(parameters.UserId)
                                                       .WithAuthority(parameters.Authority)
                                                       .WithClientSecret(parameters.Password)
                                                       .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                                                       .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                                                       .Build();

                result = ccApp.AcquireTokenForClient(scopes).ExecuteAsync().Result;
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Service Principal auth mode. Expiry Time: {0}", result.ExpiresOn);
                return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
            }

            /*
             * Today, MSAL.NET uses another redirect URI by default in desktop applications that run on Windows
             * (urn:ietf:wg:oauth:2.0:oob). In the future, we'll want to change this default, so we recommend
             * that you use https://login.microsoftonline.com/common/oauth2/nativeclient.
             *
             * https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-app-registration#redirect-uris
             */
            string redirectURI = "https://login.microsoftonline.com/common/oauth2/nativeclient";

#if NETCOREAPP
            if (parameters.AuthenticationMethod != SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)
            {
                redirectURI = "http://localhost";
            }
#endif
            IPublicClientApplication app;

#if NETSTANDARD
            if (parentActivityOrWindowFunc != null)
            {
                app = PublicClientApplicationBuilder.Create(_applicationClientId)
                      .WithAuthority(parameters.Authority)
                      .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                      .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                      .WithRedirectUri(redirectURI)
                      .WithParentActivityOrWindow(parentActivityOrWindowFunc)
                      .Build();
            }
#endif
#if NETFRAMEWORK
            if (_iWin32WindowFunc != null)
            {
                app = PublicClientApplicationBuilder.Create(_applicationClientId)
                      .WithAuthority(parameters.Authority)
                      .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                      .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                      .WithRedirectUri(redirectURI)
                      .WithParentActivityOrWindow(_iWin32WindowFunc)
                      .Build();
            }
#endif
#if !NETCOREAPP
            else
#endif
            {
                app = PublicClientApplicationBuilder.Create(_applicationClientId)
                      .WithAuthority(parameters.Authority)
                      .WithClientName(Common.DbConnectionStringDefaults.ApplicationName)
                      .WithClientVersion(Common.ADP.GetAssemblyVersion().ToString())
                      .WithRedirectUri(redirectURI)
                      .Build();
            }

            if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryIntegrated)
            {
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    result = app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .WithUsername(parameters.UserId)
                             .ExecuteAsync().Result;
                }
                else
                {
                    result = app.AcquireTokenByIntegratedWindowsAuth(scopes)
                             .WithCorrelationId(parameters.ConnectionId)
                             .ExecuteAsync().Result;
                }
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Integrated auth mode. Expiry Time: {0}", result.ExpiresOn);
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryPassword)
            {
                SecureString password = new SecureString();
                foreach (char c in parameters.Password)
                {
                    password.AppendChar(c);
                }
                password.MakeReadOnly();
                result = app.AcquireTokenByUsernamePassword(scopes, parameters.UserId, password)
                         .WithCorrelationId(parameters.ConnectionId)
                         .ExecuteAsync().Result;
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token for Active Directory Password auth mode. Expiry Time: {0}", result.ExpiresOn);
            }
            else if (parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryInteractive ||
                     parameters.AuthenticationMethod == SqlAuthenticationMethod.ActiveDirectoryDeviceCodeFlow)
            {
                System.Collections.Generic.IEnumerable <IAccount> accounts = await app.GetAccountsAsync();
                IAccount account;
                if (!string.IsNullOrEmpty(parameters.UserId))
                {
                    account = accounts.FirstOrDefault(a => parameters.UserId.Equals(a.Username, System.StringComparison.InvariantCultureIgnoreCase));
                }
                else
                {
                    account = accounts.FirstOrDefault();
                }

                if (null != account)
                {
                    try
                    {
                        result = await app.AcquireTokenSilent(scopes, account).ExecuteAsync();
                        SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (silent) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                    }
                    catch (MsalUiRequiredException)
                    {
                        result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod);
                        SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                    }
                }
                else
                {
                    result = await AcquireTokenInteractiveDeviceFlowAsync(app, scopes, parameters.ConnectionId, parameters.UserId, parameters.AuthenticationMethod);
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Acquired access token (interactive) for {0} auth mode. Expiry Time: {1}", parameters.AuthenticationMethod, result.ExpiresOn);
                }
            }
            else
            {
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | {0} authentication mode not supported by ActiveDirectoryAuthenticationProvider class.", parameters.AuthenticationMethod);
                throw SQL.UnsupportedAuthenticationSpecified(parameters.AuthenticationMethod);
            }

            return(new SqlAuthenticationToken(result.AccessToken, result.ExpiresOn));
        });
Example #8
0
        // Reference: https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/how-to-use-vm-token#get-a-token-using-http
        public override async Task <SqlAuthenticationToken> AcquireTokenAsync(SqlAuthenticationParameters parameters)
        {
            HttpClient httpClient = s_httpClient;

            try
            {
                // Check if App Services MSI is available. If both these environment variables are set, then it is.
                string msiEndpoint = Environment.GetEnvironmentVariable("IDENTITY_ENDPOINT");
                string msiHeader   = Environment.GetEnvironmentVariable("IDENTITY_HEADER");

                var isAppServicesMsiAvailable = !string.IsNullOrWhiteSpace(msiEndpoint) && !string.IsNullOrWhiteSpace(msiHeader);

                // if App Service MSI is not available then Azure VM IMDS may be available, test with a probe request
                if (!isAppServicesMsiAvailable)
                {
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | This environment is not identified as an Azure App Service environment. Proceeding to validate Azure VM IMDS endpoint availability.");
                    using (var internalTokenSource = new CancellationTokenSource())
                        using (var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(internalTokenSource.Token, default))
                        {
                            HttpRequestMessage imdsProbeRequest = new HttpRequestMessage(HttpMethod.Get, AzureVmImdsEndpoint);

                            try
                            {
                                internalTokenSource.CancelAfter(_azureVmImdsProbeTimeout);
                                await httpClient.SendAsync(imdsProbeRequest, linkedTokenSource.Token).ConfigureAwait(false);

                                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | The Instance Metadata Service (IMDS) service endpoint is accessible. Proceeding to acquire access token.");
                            }
                            catch (OperationCanceledException)
                            {
                                // request to IMDS timed out (internal cancellation token canceled), neither Azure VM IMDS nor App Services MSI are available
                                if (internalTokenSource.Token.IsCancellationRequested)
                                {
                                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | The Instance Metadata Service (IMDS) service endpoint is not accessible.");
                                    // Throw error: Tried to get token using Managed Identity. Unable to connect to the Instance Metadata Service (IMDS). Skipping request to the Managed Service Identity (MSI) token endpoint.
                                    throw SQL.Azure_ManagedIdentityException($"{Strings.Azure_ManagedIdentityUsed} {Strings.Azure_MetadataEndpointNotListening}");
                                }

                                throw;
                            }
                        }
                }
                else
                {
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | This environment is identified as an Azure App Service environment. Proceeding to acquire access token from Endpoint URL: {0}", msiEndpoint);
                }

                string objectIdParameter = string.Empty;

                // If user assigned managed identity is specified, include object ID parameter in request
                if (parameters.UserId != default)
                {
                    objectIdParameter = $"&object_id={Uri.EscapeDataString(parameters.UserId)}";
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Identity Object id received and will be used for acquiring access token {0}", parameters.UserId);
                }

                // Craft request as per the MSI protocol
                var requestUrl = isAppServicesMsiAvailable
                    ? $"{msiEndpoint}?resource={parameters.Resource}{objectIdParameter}{AzureSystemApiVersion}"
                    : $"{AzureVmImdsEndpoint}?resource={parameters.Resource}{objectIdParameter}{AzureVmImdsApiVersion}";

                HttpResponseMessage response = null;

                try
                {
                    response = await httpClient.SendAsyncWithRetry(getRequestMessage, _retryTimeoutInSeconds, _maxRetryCount, default).ConfigureAwait(false);

                    HttpRequestMessage getRequestMessage()
                    {
                        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUrl);

                        if (isAppServicesMsiAvailable)
                        {
                            request.Headers.Add("X-IDENTITY-HEADER", msiHeader);
                        }
                        else
                        {
                            request.Headers.Add("Metadata", "true");
                        }

                        return(request);
                    }
                }
                catch (HttpRequestException)
                {
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Failed after 5 retries. Unable to connect to the Managed Service Identity (MSI) endpoint.");
                    // Throw error: Tried to get token using Managed Service Identity. Failed after 5 retries. Unable to connect to the Managed Service Identity (MSI) endpoint. Please check that you are running on an Azure resource that has MSI setup.
                    throw SQL.Azure_ManagedIdentityException($"{Strings.Azure_ManagedIdentityUsed} {Strings.Azure_RetryFailure} {Strings.Azure_IdentityEndpointNotListening}");
                }

                // If the response is successful, it should have JSON response with an access_token field
                if (response.IsSuccessStatusCode)
                {
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Successful response received. Status Code {0}", response.StatusCode);
                    string jsonResponse = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                    int      accessTokenStartIndex = jsonResponse.IndexOf(AccessToken) + AccessToken.Length + 3;
                    var      imdsAccessToken       = jsonResponse.Substring(accessTokenStartIndex, jsonResponse.IndexOf('"', accessTokenStartIndex) - accessTokenStartIndex);
                    var      expiresin             = jsonResponse.Substring(jsonResponse.IndexOf(Expiry) + Expiry.Length + 3, FileTimeLength);
                    DateTime expiryTime            = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc).AddSeconds(long.Parse(expiresin));
                    SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Access Token received. Expiry Time: {0}", expiryTime);
                    return(new SqlAuthenticationToken(imdsAccessToken, expiryTime));
                }

                // RetryFailure : Failed after 5 retries.
                // NonRetryableError : Received a non-retryable error.
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Request to acquire access token failed with status code {0}", response.StatusCode);
                string errorStatusDetail = response.IsRetryableStatusCode()
                    ? Strings.Azure_RetryFailure
                    : Strings.Azure_NonRetryableError;

                string errorText = await response.Content.ReadAsStringAsync().ConfigureAwait(false);

                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Error occurred while acquiring access token: {0} Identity Response Code: {1}, Response: {2}", errorStatusDetail, response.StatusCode, errorText);
                throw SQL.Azure_ManagedIdentityException($"{errorStatusDetail} Identity Response Code: {response.StatusCode}, Response: {errorText}");
            }
            catch (Exception exp)
            {
                SqlClientEventSource.Log.TryTraceEvent("AcquireTokenAsync | Error occurred while acquiring access token: {0}", exp.Message);
                if (exp is SqlException)
                {
                    throw;
                }
                // Throw error: Access token could not be acquired. {exp.Message}
                throw SQL.Azure_ManagedIdentityException($"{Strings.Azure_ManagedIdentityUsed} {Strings.Azure_GenericErrorMessage} {exp.Message}");
            }
        }