private static string GetTokenHash(SqlFedAuthToken token)
        {
            if (token == null)
            {
                return("null");
            }

            // Here we mimic how ADAL calculates hash for token. They use UTF8 instead of Unicode.
            var originalTokenString = SqlAuthenticationToken.AccessTokenStringFromBytes(token.accessToken);
            var bytesInUtf8         = Encoding.UTF8.GetBytes(originalTokenString);

            using (var sha256 = SHA256.Create()) {
                var hash = sha256.ComputeHash(bytesInUtf8);
                return(Convert.ToBase64String(hash));
            }
        }
        /// <summary>
        /// Get the Federated Authentication Token.
        /// </summary>
        /// <param name="fedAuthInfo">Information obtained from server as Federated Authentication Info.</param>
        /// <returns>SqlFedAuthToken</returns>
        internal SqlFedAuthToken GetFedAuthToken(SqlFedAuthInfo fedAuthInfo) {

            Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null.");

            // No:of milliseconds to sleep for the inital back off.
            int sleepInterval = 100;

            // No:of attempts, for tracing purposes, if we underwent retries.
            int numberOfAttempts = 0;

            // Object that will be returned to the caller, containing all required data about the token.
            SqlFedAuthToken fedAuthToken = new SqlFedAuthToken();

            // Username to use in error messages.
            string username = null;

            while (true) {
                numberOfAttempts++;
                try {
                    if (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated) {
                        username = TdsEnums.NTAUTHORITYANONYMOUSLOGON;
                        fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl,
                                                                                                                fedAuthInfo.spn,
                                                                                                                _clientConnectionId, ActiveDirectoryAuthentication.AdoClientId,
                                                                                                                ref fedAuthToken.expirationFileTime);
                    }
                    else if (_credential != null) {
                        username = _credential.UserId;
                        fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessToken(_credential.UserId,
                                                                                            _credential.Password,
                                                                                            fedAuthInfo.stsurl,
                                                                                            fedAuthInfo.spn,
                                                                                            _clientConnectionId,
                                                                                            ActiveDirectoryAuthentication.AdoClientId,
                                                                                            ref fedAuthToken.expirationFileTime);
                    }
                    else {
                        username = ConnectionOptions.UserID;
                        fedAuthToken.accessToken = ADALNativeWrapper.ADALGetAccessToken(ConnectionOptions.UserID,
                                                                                            ConnectionOptions.Password,
                                                                                            fedAuthInfo.stsurl,
                                                                                            fedAuthInfo.spn,
                                                                                            _clientConnectionId,
                                                                                            ActiveDirectoryAuthentication.AdoClientId,
                                                                                            ref fedAuthToken.expirationFileTime);
                    }

                    Debug.Assert(fedAuthToken.accessToken != null, "AccessToken should not be null.");
#if DEBUG
                    if (_forceAdalRetry) {
                        // 3399614468 is 0xCAA20004L just for testing.
                        throw new AdalException("Force retry in GetFedAuthToken", ActiveDirectoryAuthentication.GetAccessTokenTansisentError, 3399614468, 6);
                    }
#endif
                    // Break out of the retry loop in successful case.
                    break;
                }
                catch (AdalException adalException) {

                    uint errorCategory = adalException.GetCategory();

                    if (ActiveDirectoryAuthentication.GetAccessTokenTansisentError != errorCategory
                        || _timeout.IsExpired
                        || _timeout.MillisecondsRemaining <= sleepInterval) {

                        string errorStatus = adalException.GetStatus().ToString("X");

                        Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken.ADALException category:> %d#  <error:> %s#\n", (int)errorCategory, errorStatus);

                        // Error[0]
                        SqlErrorCollection sqlErs = new SqlErrorCollection();
                        sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, Res.GetString(Res.SQL_ADALFailure, username, ConnectionOptions.Authentication.ToString("G")), ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));

                        // Error[1]
                        string errorMessage1 = Res.GetString(Res.SQL_ADALInnerException, errorStatus, adalException.GetState());
                        sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, errorMessage1, ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));

                        // Error[2]
                        if (!string.IsNullOrEmpty(adalException.Message)) {
                            sqlErs.Add(new SqlError(0, (byte)0x00, (byte)TdsEnums.MIN_ERROR_CLASS, ConnectionOptions.DataSource, adalException.Message, ActiveDirectoryAuthentication.AdalGetAccessTokenFunctionName, 0));
                        }
                        SqlException exc = SqlException.CreateException(sqlErs, "", this);
                        throw exc;
                    }

                    Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> %d#, sleeping %d{Milliseconds}\n", ObjectID, sleepInterval);
                    Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken|ADV> %d#, remaining %d{Milliseconds}\n", ObjectID, _timeout.MillisecondsRemaining);

                    Thread.Sleep(sleepInterval);
                    sleepInterval *= 2;
                }
            }

            Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
            Debug.Assert(fedAuthToken.accessToken != null && fedAuthToken.accessToken.Length > 0, "fedAuthToken.accessToken should not be null or empty.");

            // Store the newly generated token in _newDbConnectionPoolAuthenticationContext, only if using pooling.
            if (_dbConnectionPool != null) {
                DateTime expirationTime = DateTime.FromFileTimeUtc(fedAuthToken.expirationFileTime);
                _newDbConnectionPoolAuthenticationContext = new DbConnectionPoolAuthenticationContext(fedAuthToken.accessToken, expirationTime);
            }

            Bid.Trace("<sc.SqlInternalConnectionTds.GetFedAuthToken> %d#, Finished generating federated authentication token.\n", ObjectID);

            return fedAuthToken;
        }
        /// <summary>
        /// Tries to acquire a lock on the authentication context. If successful in acquiring the lock, gets a new token and assigns it in the out parameter. Else returns false.
        /// </summary>
        /// <param name="fedAuthInfo">Federated Authentication Info</param>
        /// <param name="dbConnectionPoolAuthenticationContext">Authentication Context cached in the connection pool.</param>
        /// <param name="fedAuthToken">Out parameter, carrying the token if we acquired a lock and got the token.</param>
        /// <returns></returns>
        internal bool TryGetFedAuthTokenLocked(SqlFedAuthInfo fedAuthInfo, DbConnectionPoolAuthenticationContext dbConnectionPoolAuthenticationContext, out SqlFedAuthToken fedAuthToken) {

            Debug.Assert(fedAuthInfo != null, "fedAuthInfo should not be null.");
            Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");

            fedAuthToken = null;

            // Variable which indicates if we did indeed manage to acquire the lock on the authentication context, to try update it.
            bool authenticationContextLocked = false;

            // Prepare CER to ensure the lock on authentication context is released.
            RuntimeHelpers.PrepareConstrainedRegions();
            try {
                // Try to obtain a lock on the context. If acquired, this thread got the opportunity to update.
                // Else some other thread is already updating it, so just proceed forward with the existing token in the cache.
                if (dbConnectionPoolAuthenticationContext.LockToUpdate()) {
                    Bid.Trace("<sc.SqlInternalConnectionTds.TryGetFedAuthTokenLocked> %d#, Acquired the lock to update the authentication context.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());

                    authenticationContextLocked = true;
                }
                else {
                    Bid.Trace("<sc.SqlInternalConnectionTds.TryGetFedAuthTokenLocked> %d#, Refreshing the context is already in progress by another thread.\n", ObjectID);
                }

                if (authenticationContextLocked) {
                    // Get the Federated Authentication Token.
                    fedAuthToken = GetFedAuthToken(fedAuthInfo);
                    Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");
                }
            }
            finally {
                if (authenticationContextLocked) {
                    // Release the lock we took on the authentication context, even if we have not yet updated the cache with the new context. Login process can fail at several places after this step and so there is no guarantee that the new context will make it to the cache. So we shouldn't miss resetting the flag. With the reset, at-least another thread may have a chance to update it.
                    dbConnectionPoolAuthenticationContext.ReleaseLockToUpdate();
                }
            }

            return authenticationContextLocked;
        }
        /// <summary>
        /// Generates (if appropriate) and sends a Federated Authentication Access token to the server, using the Federated Authentication Info.
        /// </summary>
        /// <param name="fedAuthInfo">Federated Authentication Info.</param>
        internal void OnFedAuthInfo(SqlFedAuthInfo fedAuthInfo) {
            Debug.Assert((ConnectionOptions.HasUserIdKeyword && ConnectionOptions.HasPasswordKeyword)
                         || _credential != null
                         || (ConnectionOptions.Authentication == SqlAuthenticationMethod.ActiveDirectoryIntegrated && _fedAuthRequired),
                         "Credentials aren't provided for calling ADAL");
            Debug.Assert(fedAuthInfo != null, "info should not be null.");
            Debug.Assert(_dbConnectionPoolAuthenticationContextKey == null, "_dbConnectionPoolAuthenticationContextKey should be null.");

            Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, Generating federated authentication token\n", ObjectID);

            DbConnectionPoolAuthenticationContext dbConnectionPoolAuthenticationContext = null;

            // We want to refresh the token without taking a lock on the context, allowed when the access token is expiring within the next 10 mins.
            bool attemptRefreshTokenUnLocked = false;

            // We want to refresh the token, if taking the lock on the authentication context is successful.
            bool attemptRefreshTokenLocked = false;

            // The Federated Authentication returned by TryGetFedAuthTokenLocked or GetFedAuthToken.
            SqlFedAuthToken fedAuthToken = null;

            if (_dbConnectionPool != null) {
                Debug.Assert(_dbConnectionPool.AuthenticationContexts != null);

                // Construct the dbAuthenticationContextKey with information from FedAuthInfo and store for later use, when inserting in to the token cache.
                _dbConnectionPoolAuthenticationContextKey = new DbConnectionPoolAuthenticationContextKey(fedAuthInfo.stsurl, fedAuthInfo.spn);

                // Try to retrieve the authentication context from the pool, if one does exist for this key.
                if (_dbConnectionPool.AuthenticationContexts.TryGetValue(_dbConnectionPoolAuthenticationContextKey, out dbConnectionPoolAuthenticationContext)) {
                    Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");

                    // The timespan between UTCNow and the token expiry.
                    TimeSpan contextValidity = dbConnectionPoolAuthenticationContext.ExpirationTime.Subtract(DateTime.UtcNow);

                    // If the authentication context is expiring within next 10 minutes, lets just re-create a token for this connection attempt.
                    // And on successful login, try to update the cache with the new token.
                    if (contextValidity <= _dbAuthenticationContextUnLockedRefreshTimeSpan) {
                        Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The expiration time is less than 10 mins, so trying to get new access token regardless of if an other thread is also trying to update it.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());

                        attemptRefreshTokenUnLocked = true;
                    }

#if DEBUG
                    // Checking if any failpoints are enabled.
                    else if (_forceExpiryUnLocked) {
                        attemptRefreshTokenUnLocked = true;
                    }
                    else if (_forceExpiryLocked) {
                        attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken);
                    }
#endif

                    // If the token is expiring within the next 45 mins, try to fetch a new token, if there is no thread already doing it.
                    // If a thread is already doing the refresh, just use the existing token in the cache and proceed.
                    else if (contextValidity <= _dbAuthenticationContextLockedRefreshTimeSpan) {
                        if (Bid.AdvancedOn) {
                            Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The authentication context needs a refresh.The expiration time is %s. Current Time is %s.\n", ObjectID, dbConnectionPoolAuthenticationContext.ExpirationTime.ToLongTimeString(), DateTime.UtcNow.ToLongTimeString());
                        }

                        // Call the function which tries to acquire a lock over the authentication context before trying to update.
                        // If the lock could not be obtained, it will return false, without attempting to fetch a new token.
                        attemptRefreshTokenLocked = TryGetFedAuthTokenLocked(fedAuthInfo, dbConnectionPoolAuthenticationContext, out fedAuthToken);

                        // If TryGetFedAuthTokenLocked returns true, it means lock was obtained and fedAuthToken should not be null.
                        // If there was an exception in retrieving the new token, TryGetFedAuthTokenLocked should have thrown, so we won't be here.
                        Debug.Assert(!attemptRefreshTokenLocked || fedAuthToken != null, "Either Lock should not have been obtained or fedAuthToken should not be null.");
                        Debug.Assert(!attemptRefreshTokenLocked || _newDbConnectionPoolAuthenticationContext != null, "Either Lock should not have been obtained or _newDbConnectionPoolAuthenticationContext should not be null.");

                        // Indicate in Bid Trace that we are successful with the update.
                        if (attemptRefreshTokenLocked) {
                            Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, The attempt to get a new access token succeeded under the locked mode.");
                        }

                    }
                    else if (Bid.AdvancedOn) {
                        Bid.Trace("<sc.SqlInternalConnectionTds.OnFedAuthInfo> %d#, Found an authentication context in the cache that does not need a refresh at this time. Re-using the cached token.\n", ObjectID);
                    }
                }
            }

            // dbConnectionPoolAuthenticationContext will be null if either this is the first connection attempt in the pool or pooling is disabled.
            if (dbConnectionPoolAuthenticationContext == null || attemptRefreshTokenUnLocked) {
                // Get the Federated Authentication Token.
                fedAuthToken = GetFedAuthToken(fedAuthInfo);
                Debug.Assert(fedAuthToken != null, "fedAuthToken should not be null.");

                if (_dbConnectionPool != null) {
                    // GetFedAuthToken should have updated _newDbConnectionPoolAuthenticationContext.
                    Debug.Assert(_newDbConnectionPoolAuthenticationContext != null, "_newDbConnectionPoolAuthenticationContext should not be null.");
                }
            }
            else if (!attemptRefreshTokenLocked) {
                Debug.Assert(dbConnectionPoolAuthenticationContext != null, "dbConnectionPoolAuthenticationContext should not be null.");
                Debug.Assert(fedAuthToken == null, "fedAuthToken should be null in this case.");
                Debug.Assert(_newDbConnectionPoolAuthenticationContext == null, "_newDbConnectionPoolAuthenticationContext should be null.");

                fedAuthToken = new SqlFedAuthToken();

                // If the code flow is here, then we are re-using the context from the cache for this connection attempt and not
                // generating a new access token on this thread.
                fedAuthToken.accessToken = dbConnectionPoolAuthenticationContext.AccessToken;
            }

            Debug.Assert(fedAuthToken != null && fedAuthToken.accessToken != null, "fedAuthToken and fedAuthToken.accessToken cannot be null.");
            _parser.SendFedAuthToken(fedAuthToken);
        }
Esempio n. 5
0
        }// tdsLogin

        /// <summary>
        /// Send the access token to the server.
        /// </summary>
        /// <param name="fedAuthToken">Type encapuslating a Federated Authentication access token.</param>
        internal void SendFedAuthToken(SqlFedAuthToken fedAuthToken) {
            Debug.Assert(fedAuthToken != null, "fedAuthToken cannot be null");
            Debug.Assert(fedAuthToken.accessToken != null, "fedAuthToken.accessToken cannot be null");


            Bid.Trace("<sc.TdsParser.SendFedAuthToken|SEC> Sending federated authentication token\n");

            _physicalStateObj._outputMessageType = TdsEnums.MT_FEDAUTH;

            byte[] accessToken = fedAuthToken.accessToken;

            // Send total length (length of token plus 4 bytes for the token length field)
            // If we were sending a nonce, this would include that length as well
            WriteUnsignedInt((uint)accessToken.Length + sizeof(uint), _physicalStateObj);

            // Send length of token
            WriteUnsignedInt((uint)accessToken.Length, _physicalStateObj);

            // Send federated authentication access token
            _physicalStateObj.WriteByteArray(accessToken, accessToken.Length, 0);

            _physicalStateObj.WritePacket(TdsEnums.HARDFLUSH);
            _physicalStateObj._pendingData = true;
            _physicalStateObj._messageStatus = 0;

            _connHandler._federatedAuthenticationRequested = true;
        }