/// <summary> /// Set the provided traceInfo as a property on a request message (if not already set) /// </summary> /// <param name="message"></param> /// <param name="traceInfo"></param> public static void SetTraceInfo(HttpRequestMessage message, VssHttpMessageHandlerTraceInfo traceInfo) { object existingTraceInfo; if (!message.Properties.TryGetValue(TfsTraceInfoKey, out existingTraceInfo)) { message.Properties.Add(TfsTraceInfoKey, traceInfo); } }
/// <summary> /// Set the provided traceInfo as a property on a request message (if not already set) /// </summary> /// <param name="message"></param> /// <param name="traceInfo"></param> public static void SetTraceInfo(HttpRequestMessage message, VssHttpMessageHandlerTraceInfo traceInfo) { object existingTraceInfo; if (!message.Options.TryGetValue(TfsTraceInfoKey, out existingTraceInfo)) { message.Options.Set(new HttpRequestOptionsKey <VssHttpMessageHandlerTraceInfo>(TfsTraceInfoKey), traceInfo); } }
/// <summary> /// Get VssHttpMessageHandlerTraceInfo from request message, or return null if none found /// </summary> /// <param name="message"></param> /// <returns></returns> public static VssHttpMessageHandlerTraceInfo GetTraceInfo(HttpRequestMessage message) { VssHttpMessageHandlerTraceInfo traceInfo = null; if (message.Properties.TryGetValue(TfsTraceInfoKey, out object traceInfoObject)) { traceInfo = traceInfoObject as VssHttpMessageHandlerTraceInfo; } return(traceInfo); }
/// <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<HttpResponseMessage></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(); } }