예제 #1
0
        private GetTokenOperation CreateOperation(
            VssTraceActivity traceActivity,
            IssuedToken failedToken,
            CancellationToken cancellationToken,
            out GetTokenOperation operationInProgress)
        {
            operationInProgress = null;
            GetTokenOperation operation = null;

            lock (m_thisLock)
            {
                if (m_operations == null)
                {
                    m_operations = new List <GetTokenOperation>();
                }

                // Grab the main operation which is doing the work (if any)
                if (m_operations.Count > 0)
                {
                    operationInProgress = m_operations[0];

                    // Use the existing completion source when creating the new operation
                    operation = new GetTokenOperation(traceActivity, this, failedToken, cancellationToken, operationInProgress.CompletionSource);
                }
                else
                {
                    operation = new GetTokenOperation(traceActivity, this, failedToken, cancellationToken);
                }

                m_operations.Add(operation);
            }

            return(operation);
        }
예제 #2
0
        private void ApplyToken(
            HttpRequestMessage request,
            IssuedToken token,
            bool applyICredentialsToWebProxy = false)
        {
            if (token == null)
            {
                return;
            }

            ICredentials credentialsToken = token as ICredentials;

            if (credentialsToken != null)
            {
                if (applyICredentialsToWebProxy)
                {
                    HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;

                    if (httpClientHandler != null &&
                        httpClientHandler.Proxy != null)
                    {
                        httpClientHandler.Proxy.Credentials = credentialsToken;
                    }
                }

                m_credentialWrapper.InnerCredentials = credentialsToken;
            }
            else
            {
                token.ApplyTo(new HttpRequestMessageWrapper(request));
            }
        }
예제 #3
0
 public GetTokenOperation(
     VssTraceActivity activity,
     IssuedTokenProvider provider,
     IssuedToken failedToken,
     CancellationToken cancellationToken)
     : this(activity, provider, failedToken, cancellationToken, new DisposableTaskCompletionSource <IssuedToken>(), true)
 {
 }
예제 #4
0
        protected virtual void OnTokenInvalidated(IssuedToken token)
        {
            if (Credential.Storage != null && TokenStorageUrl != null)
            {
                Credential.Storage.RemoveTokenValue(TokenStorageUrl, token);
            }

            VssHttpEventSource.Log.IssuedTokenInvalidated(VssTraceActivity.Current, this, token);
        }
예제 #5
0
        protected virtual void OnTokenValidated(IssuedToken token)
        {
            // Store the validated token to the token storage if it is not originally from there.
            if (!token.FromStorage && TokenStorageUrl != null)
            {
                Credential.Storage?.StoreToken(TokenStorageUrl, token);
            }

            VssHttpEventSource.Log.IssuedTokenValidated(VssTraceActivity.Current, this, token);
        }
예제 #6
0
            public async Task <IssuedToken> GetTokenAsync(VssTraceActivity traceActivity)
            {
                IssuedToken token = null;

                try
                {
                    VssHttpEventSource.Log.IssuedTokenAcquiring(traceActivity, this.Provider);
                    if (this.Provider.InvokeRequired)
                    {
                        // Post to the UI thread using the scheduler. This may return a new task object which needs
                        // to be awaited, since once we get to the UI thread there may be nothing to do if someone else
                        // preempts us.

                        // The cancellation token source is used to handle race conditions between scheduling and
                        // waiting for the UI task to begin execution. The callback is responsible for disposing of
                        // the token source, since the thought here is that the callback will run eventually as the
                        // typical reason for not starting execution within the timeout is due to a deadlock with
                        // the scheduler being used.
                        var timerTask          = new TaskCompletionSource <Object>();
                        var timeoutTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(3));
                        timeoutTokenSource.Token.Register(() => timerTask.SetResult(null), false);

                        var uiTask = Task.Factory.StartNew((state) => PostCallback(state, timeoutTokenSource),
                                                           this,
                                                           this.CancellationToken,
                                                           TaskCreationOptions.None,
                                                           this.Provider.Credential.Scheduler).Unwrap();

                        var completedTask = await Task.WhenAny(timerTask.Task, uiTask).ConfigureAwait(false);

                        if (completedTask == uiTask)
                        {
                            token = uiTask.Result;
                        }
                    }
                    else
                    {
                        token = await this.Provider.OnGetTokenAsync(this.FailedToken, this.CancellationToken).ConfigureAwait(false);
                    }

                    CompletionSource.TrySetResult(token);
                    return(token);
                }
                catch (Exception exception)
                {
                    // Mark our completion source as failed so other waiters will get notified in all cases
                    CompletionSource.TrySetException(exception);
                    throw;
                }
                finally
                {
                    this.Provider.CurrentToken = token ?? this.FailedToken;
                    VssHttpEventSource.Log.IssuedTokenAcquired(traceActivity, this.Provider, token);
                }
            }
예제 #7
0
 /// <summary>
 /// Retrieves a token for the credentials.
 /// </summary>
 /// <param name="failedToken">The token which previously failed authentication, if available</param>
 /// <param name="cancellationToken">The <c>CancellationToken</c>that will be assigned to the new task</param>
 /// <returns>A security token for the current credentials</returns>
 protected virtual Task <IssuedToken> OnGetTokenAsync(
     IssuedToken failedToken,
     CancellationToken cancellationToken)
 {
     if (this.Credential.Prompt != null)
     {
         return(this.Credential.Prompt.GetTokenAsync(this, failedToken));
     }
     else
     {
         return(Task.FromResult <IssuedToken>(null));
     }
 }
예제 #8
0
 public GetTokenOperation(
     VssTraceActivity activity,
     IssuedTokenProvider provider,
     IssuedToken failedToken,
     CancellationToken cancellationToken,
     DisposableTaskCompletionSource <IssuedToken> completionSource,
     Boolean ownsCompletionSource = false)
 {
     this.Provider             = provider;
     this.ActivityId           = activity?.Id ?? Guid.Empty;
     this.FailedToken          = failedToken;
     this.CancellationToken    = cancellationToken;
     this.CompletionSource     = completionSource;
     this.OwnsCompletionSource = ownsCompletionSource;
 }
예제 #9
0
        /// <summary>
        /// Invalidates the current token if the provided reference is the current token.
        /// </summary>
        /// <param name="token">The token reference which should be invalidated</param>
        internal void InvalidateToken(IssuedToken token)
        {
            bool invalidated = false;

            lock (m_thisLock)
            {
                if (token != null && object.ReferenceEquals(this.CurrentToken, token))
                {
                    this.CurrentToken = null;
                    invalidated       = true;
                }
            }

            if (invalidated)
            {
                OnTokenInvalidated(token);
            }
        }
예제 #10
0
            public async Task <IssuedToken> WaitForTokenAsync(
                VssTraceActivity traceActivity,
                CancellationToken cancellationToken)
            {
                IssuedToken token = null;

                try
                {
                    VssHttpEventSource.Log.IssuedTokenWaitStart(traceActivity, this.Provider, this.ActivityId);
                    token = await Task.Factory.ContinueWhenAll <IssuedToken>(new Task[] { CompletionSource.Task }, (x) => CompletionSource.Task.Result, cancellationToken).ConfigureAwait(false);
                }
                finally
                {
                    VssHttpEventSource.Log.IssuedTokenWaitStop(traceActivity, this.Provider, token);
                }

                return(token);
            }
예제 #11
0
        /// <summary>
        /// Validates the current token if the provided reference is the current token and it
        /// has not been validated before.
        /// </summary>
        /// <param name="token">The token which should be validated</param>
        /// <param name="webResponse">The web response which used the token</param>
        internal void ValidateToken(
            IssuedToken token,
            IHttpResponse webResponse)
        {
            if (token == null)
            {
                return;
            }

            lock (m_thisLock)
            {
                IssuedToken tokenToValidate = OnValidatingToken(token, webResponse);

                if (tokenToValidate.IsAuthenticated)
                {
                    return;
                }

                try
                {
                    // Perform validation which may include matching user information from the response
                    // with that from the stored connection. If user information mismatch, an exception
                    // will be thrown and the token will not be authenticated, which means if the same
                    // token is ever used again in a different request it will be revalidated and fail.
                    tokenToValidate.GetUserData(webResponse);
                    OnTokenValidated(tokenToValidate);

                    // Set the token to be authenticated.
                    tokenToValidate.Authenticated();
                }
                finally
                {
                    // When the token fails validation, we null its reference from the token provider so it
                    // would not be used again by the consumers of both. Note that we only update the current
                    // token of the provider if it is the original token being validated, because we do not
                    // want to overwrite a different token.
                    if (object.ReferenceEquals(this.CurrentToken, token))
                    {
                        this.CurrentToken = tokenToValidate.IsAuthenticated ? tokenToValidate : null;
                    }
                }
            }
        }
예제 #12
0
        /// <summary>
        /// Creates a token provider for the configured issued token credentials.
        /// </summary>
        /// <param name="serverUrl">The targeted server</param>
        /// <param name="webResponse">The failed web response</param>
        /// <param name="failedToken">The failed token</param>
        /// <returns>A provider for retrieving tokens for the configured credential</returns>
        internal IssuedTokenProvider CreateTokenProvider(
            Uri serverUrl,
            IHttpResponse webResponse,
            IssuedToken failedToken)
        {
            ArgumentUtility.CheckForNull(serverUrl, "serverUrl");

            IssuedTokenProvider tokenProvider = null;
            VssTraceActivity    traceActivity = VssTraceActivity.Current;

            lock (m_thisLock)
            {
                tokenProvider = m_currentProvider;
                if (tokenProvider == null || !tokenProvider.IsAuthenticationChallenge(webResponse))
                {
                    // Prefer federated authentication over Windows authentication.
                    if (m_federatedCredential != null && m_federatedCredential.IsAuthenticationChallenge(webResponse))
                    {
                        if (tokenProvider != null)
                        {
                            VssHttpEventSource.Log.IssuedTokenProviderRemoved(traceActivity, tokenProvider);
                        }

                        // TODO: This needs to be refactored or renamed to be more generic ...
                        this.TryGetValidAdalToken(m_federatedCredential.Prompt);

                        tokenProvider = m_federatedCredential.CreateTokenProvider(serverUrl, webResponse, failedToken);

                        if (tokenProvider != null)
                        {
                            VssHttpEventSource.Log.IssuedTokenProviderCreated(traceActivity, tokenProvider);
                        }
                    }

                    m_currentProvider = tokenProvider;
                }

                return(tokenProvider);
            }
        }
예제 #13
0
        /// <summary>
        /// Creates a token provider suitable for handling the challenge presented in the response.
        /// </summary>
        /// <param name="serverUrl">The targeted server</param>
        /// <param name="response">The challenge response</param>
        /// <param name="failedToken">The failed token</param>
        /// <returns>An issued token provider instance</returns>
        internal IssuedTokenProvider CreateTokenProvider(
            Uri serverUrl,
            IHttpResponse response,
            IssuedToken failedToken)
        {
            if (response != null && !IsAuthenticationChallenge(response))
            {
                throw new InvalidOperationException();
            }

            if (InitialToken == null && Storage != null)
            {
                if (TokenStorageUrl == null)
                {
                    throw new InvalidOperationException($"The {nameof(TokenStorageUrl)} property must have a value if the {nameof(Storage)} property is set on this instance of {GetType().Name}.");
                }
                InitialToken = Storage.RetrieveToken(TokenStorageUrl, CredentialType);
            }

            IssuedTokenProvider provider = OnCreateTokenProvider(serverUrl, response);

            if (provider != null)
            {
                provider.TokenStorageUrl = TokenStorageUrl;
            }

            // If the initial token is the one which failed to authenticate, don't
            // use it again and let the token provider get a new token.
            if (provider != null)
            {
                if (InitialToken != null && !Object.ReferenceEquals(InitialToken, failedToken))
                {
                    provider.CurrentToken = InitialToken;
                }
            }

            return(provider);
        }
예제 #14
0
 protected FederatedCredential(IssuedToken initialToken)
     : base(initialToken)
 {
 }
예제 #15
0
 protected IssuedTokenCredential(IssuedToken initialToken)
 {
     InitialToken = initialToken;
 }
예제 #16
0
        /// <summary>
        /// Retrieves a token for the credentials.
        /// </summary>
        /// <param name="failedToken">The token which previously failed authentication, if available</param>
        /// <param name="cancellationToken">The <c>CancellationToken</c>that will be assigned to the new task</param>
        /// <returns>A security token for the current credentials</returns>
        public async Task <IssuedToken> GetTokenAsync(
            IssuedToken failedToken,
            CancellationToken cancellationToken)
        {
            IssuedToken      currentToken      = this.CurrentToken;
            VssTraceActivity traceActivity     = VssTraceActivity.Current;
            Stopwatch        aadAuthTokenTimer = Stopwatch.StartNew();

            try
            {
                VssHttpEventSource.Log.AuthenticationStart(traceActivity);

                if (currentToken != null)
                {
                    VssHttpEventSource.Log.IssuedTokenRetrievedFromCache(traceActivity, this, currentToken);
                    return(currentToken);
                }
                else
                {
                    GetTokenOperation operation = null;
                    try
                    {
                        GetTokenOperation operationInProgress;
                        operation = CreateOperation(traceActivity, failedToken, cancellationToken, out operationInProgress);
                        if (operationInProgress == null)
                        {
                            return(await operation.GetTokenAsync(traceActivity).ConfigureAwait(false));
                        }
                        else
                        {
                            return(await operationInProgress.WaitForTokenAsync(traceActivity, cancellationToken).ConfigureAwait(false));
                        }
                    }
                    finally
                    {
                        lock (m_thisLock)
                        {
                            m_operations.Remove(operation);
                        }

                        operation?.Dispose();
                    }
                }
            }
            finally
            {
                VssHttpEventSource.Log.AuthenticationStop(traceActivity);

                aadAuthTokenTimer.Stop();
                TimeSpan getTokenTime = aadAuthTokenTimer.Elapsed;

                if (getTokenTime.TotalSeconds >= c_slowTokenAcquisitionTimeInSeconds)
                {
                    // It may seem strange to pass the string value of TotalSeconds into this method, but testing
                    // showed that ETW is persnickety when you register a method in an EventSource that doesn't
                    // use strings or integers as its parameters. It is easier to simply give the method a string
                    // than figure out to get ETW to reliably accept a double or TimeSpan.
                    VssHttpEventSource.Log.AuthorizationDelayed(getTokenTime.TotalSeconds.ToString());
                }
            }
        }
예제 #17
0
        /// <summary>
        /// Handles the authentication hand-shake for a Visual Studio service.
        /// </summary>
        /// <param name="request">The HTTP request message</param>
        /// <param name="cancellationToken">The cancellation token used for cooperative cancellation</param>
        /// <returns>A new <c>Task&lt;HttpResponseMessage&gt;</c> which wraps the response from the remote service</returns>
        protected override async Task <HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            VssTraceActivity traceActivity = VssTraceActivity.Current;

            var traceInfo = VssHttpMessageHandlerTraceInfo.GetTraceInfo(request);

            traceInfo?.TraceHandlerStartTime();

            if (!m_appliedClientCertificatesToTransportHandler &&
                request.RequestUri.Scheme == "https")
            {
                HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
                if (httpClientHandler != null &&
                    this.Settings.ClientCertificateManager != null &&
                    this.Settings.ClientCertificateManager.ClientCertificates != null &&
                    this.Settings.ClientCertificateManager.ClientCertificates.Count > 0)
                {
                    httpClientHandler.ClientCertificates.AddRange(this.Settings.ClientCertificateManager.ClientCertificates);
                }
                m_appliedClientCertificatesToTransportHandler = true;
            }

            if (!m_appliedServerCertificateValidationCallbackToTransportHandler &&
                request.RequestUri.Scheme == "https")
            {
                HttpClientHandler httpClientHandler = m_transportHandler as HttpClientHandler;
                if (httpClientHandler != null &&
                    this.Settings.ServerCertificateValidationCallback != null)
                {
                    httpClientHandler.ServerCertificateCustomValidationCallback = this.Settings.ServerCertificateValidationCallback;
                }
                m_appliedServerCertificateValidationCallbackToTransportHandler = true;
            }

            // The .NET Core 2.1 runtime switched its HTTP default from HTTP 1.1 to HTTP 2.
            // This causes problems with some versions of the Curl handler on Linux.
            // See GitHub issue https://github.com/dotnet/corefx/issues/32376
            if (Settings.UseHttp11)
            {
                request.Version = HttpVersion.Version11;
            }

            IssuedToken         token = null;
            IssuedTokenProvider provider;

            if (this.Credentials.TryGetTokenProvider(request.RequestUri, out provider))
            {
                token = provider.CurrentToken;
            }

            // Add ourselves to the message so the underlying token issuers may use it if necessary
            request.Options.Set(new HttpRequestOptionsKey <VssHttpMessageHandler>(VssHttpMessageHandler.PropertyName), this);

            Boolean                    succeeded = false;
            Boolean                    lastResponseDemandedProxyAuth = false;
            Int32                      retries  = m_maxAuthRetries;
            HttpResponseMessage        response = null;
            HttpResponseMessageWrapper responseWrapper;
            CancellationTokenSource    tokenSource = null;

            try
            {
                tokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);

                if (this.Settings.SendTimeout > TimeSpan.Zero)
                {
                    tokenSource.CancelAfter(this.Settings.SendTimeout);
                }

                do
                {
                    if (response != null)
                    {
                        response.Dispose();
                    }

                    ApplyHeaders(request);

                    // In the case of a Windows token, only apply it to the web proxy if it
                    // returned a 407 Proxy Authentication Required. If we didn't get this
                    // status code back, then the proxy (if there is one) is clearly working fine,
                    // so we shouldn't mess with its credentials.
                    ApplyToken(request, token, applyICredentialsToWebProxy: lastResponseDemandedProxyAuth);
                    lastResponseDemandedProxyAuth = false;

                    // The WinHttpHandler will chunk any content that does not have a computed length which is
                    // not what we want. By loading into a buffer up-front we bypass this behavior and there is
                    // no difference in the normal HttpClientHandler behavior here since this is what they were
                    // already doing.
                    await BufferRequestContentAsync(request, tokenSource.Token).ConfigureAwait(false);

                    traceInfo?.TraceBufferedRequestTime();

                    // ConfigureAwait(false) enables the continuation to be run outside any captured
                    // SyncronizationContext (such as ASP.NET's) which keeps things from deadlocking...
                    response = await m_messageInvoker.SendAsync(request, tokenSource.Token).ConfigureAwait(false);

                    traceInfo?.TraceRequestSendTime();

                    // Now buffer the response content if configured to do so. In general we will be buffering
                    // the response content in this location, except in the few cases where the caller has
                    // specified HttpCompletionOption.ResponseHeadersRead.
                    // Trace content type in case of error
                    await BufferResponseContentAsync(request, response, () => $"[ContentType: {response.Content.GetType().Name}]", tokenSource.Token).ConfigureAwait(false);

                    traceInfo?.TraceResponseContentTime();

                    responseWrapper = new HttpResponseMessageWrapper(response);

                    if (!this.Credentials.IsAuthenticationChallenge(responseWrapper))
                    {
                        // Validate the token after it has been successfully authenticated with the server.
                        if (provider != null)
                        {
                            provider.ValidateToken(token, responseWrapper);
                        }

                        // Make sure that once we can authenticate with the service that we turn off the
                        // Expect100Continue behavior to increase performance.
                        this.ExpectContinue = false;
                        succeeded           = true;
                        break;
                    }
                    else
                    {
                        // In the case of a Windows token, only apply it to the web proxy if it
                        // returned a 407 Proxy Authentication Required. If we didn't get this
                        // status code back, then the proxy (if there is one) is clearly working fine,
                        // so we shouldn't mess with its credentials.
                        lastResponseDemandedProxyAuth = responseWrapper.StatusCode == HttpStatusCode.ProxyAuthenticationRequired;

                        // Invalidate the token and ensure that we have the correct token provider for the challenge
                        // which we just received
                        VssHttpEventSource.Log.AuthenticationFailed(traceActivity, response);

                        if (provider != null)
                        {
                            provider.InvalidateToken(token);
                        }

                        // Ensure we have an appropriate token provider for the current challenge
                        provider = this.Credentials.CreateTokenProvider(request.RequestUri, responseWrapper, token);

                        // Make sure we don't invoke the provider in an invalid state
                        if (provider == null)
                        {
                            VssHttpEventSource.Log.IssuedTokenProviderNotFound(traceActivity);
                            break;
                        }
                        else if (provider.GetTokenIsInteractive && this.Credentials.PromptType == CredentialPromptType.DoNotPrompt)
                        {
                            VssHttpEventSource.Log.IssuedTokenProviderPromptRequired(traceActivity, provider);
                            break;
                        }

                        // If the user has already tried once but still unauthorized, stop retrying. The main scenario for this condition
                        // is a user typed in a valid credentials for a hosted account but the associated identity does not have
                        // access. We do not want to continually prompt 3 times without telling them the failure reason. In the
                        // next release we should rethink about presenting user the failure and options between retries.
                        IEnumerable <String> headerValues;
                        Boolean hasAuthenticateError =
                            response.Headers.TryGetValues(HttpHeaders.VssAuthenticateError, out headerValues) &&
                            !String.IsNullOrEmpty(headerValues.FirstOrDefault());

                        if (retries == 0 || (retries < m_maxAuthRetries && hasAuthenticateError))
                        {
                            break;
                        }

                        // Now invoke the provider and await the result
                        token = await provider.GetTokenAsync(token, tokenSource.Token).ConfigureAwait(false);

                        // I always see 0 here, but the method above could take more time so keep for now
                        traceInfo?.TraceGetTokenTime();

                        // If we just received a token, lets ask the server for the VSID
                        request.Headers.Add(HttpHeaders.VssUserData, String.Empty);

                        retries--;
                    }
                }while (retries >= 0);

                if (traceInfo != null)
                {
                    traceInfo.TokenRetries = m_maxAuthRetries - retries;
                }

                // We're out of retries and the response was an auth challenge -- then the request was unauthorized
                // and we will throw a strongly-typed exception with a friendly error message.
                if (!succeeded && response != null && this.Credentials.IsAuthenticationChallenge(responseWrapper))
                {
                    String message = null;
                    IEnumerable <String> serviceError;

                    if (response.Headers.TryGetValues(HttpHeaders.TfsServiceError, out serviceError))
                    {
                        message = UriUtility.UrlDecode(serviceError.FirstOrDefault());
                    }
                    else
                    {
                        message = CommonResources.VssUnauthorized(request.RequestUri.GetLeftPart(UriPartial.Authority));
                    }

                    // Make sure we do not leak the response object when raising an exception
                    if (response != null)
                    {
                        response.Dispose();
                    }

                    VssHttpEventSource.Log.HttpRequestUnauthorized(traceActivity, request, message);
                    VssUnauthorizedException unauthorizedException = new VssUnauthorizedException(message);

                    if (provider != null)
                    {
                        unauthorizedException.Data.Add(CredentialsType, provider.CredentialType);
                    }

                    throw unauthorizedException;
                }

                return(response);
            }
            catch (OperationCanceledException ex)
            {
                if (cancellationToken.IsCancellationRequested)
                {
                    VssHttpEventSource.Log.HttpRequestCancelled(traceActivity, request);
                    throw;
                }
                else
                {
                    VssHttpEventSource.Log.HttpRequestTimedOut(traceActivity, request, this.Settings.SendTimeout);
                    throw new TimeoutException(CommonResources.HttpRequestTimeout(this.Settings.SendTimeout), ex);
                }
            }
            finally
            {
                // We always dispose of the token source since otherwise we leak resources if there is a timer pending
                if (tokenSource != null)
                {
                    tokenSource.Dispose();
                }

                traceInfo?.TraceTrailingTime();
            }
        }
예제 #18
0
 /// <summary>
 /// Invoked when the current token is being validated. When overriden in a derived class,
 /// validate and return the validated token.
 /// </summary>
 /// <remarks>Is called inside a lock in <c>ValidateToken</c></remarks>
 /// <param name="token">The token to validate</param>
 /// <param name="webResponse">The web response which used the token</param>
 /// <returns>The validated token</returns>
 protected virtual IssuedToken OnValidatingToken(
     IssuedToken token,
     IHttpResponse webResponse)
 {
     return(token);
 }