diff options
5 files changed, 117 insertions, 98 deletions
diff --git a/src/DotNetOpenAuth.OpenId/DefaultOpenIdHostFactories.cs b/src/DotNetOpenAuth.OpenId/DefaultOpenIdHostFactories.cs index d52342c..cd41c72 100644 --- a/src/DotNetOpenAuth.OpenId/DefaultOpenIdHostFactories.cs +++ b/src/DotNetOpenAuth.OpenId/DefaultOpenIdHostFactories.cs @@ -28,7 +28,7 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> public virtual HttpMessageHandler CreateHttpMessageHandler() { var handler = new UntrustedWebRequestHandler(); - handler.CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); + ((WebRequestHandler)handler.InnerHandler).CachePolicy = new RequestCachePolicy(RequestCacheLevel.NoCacheNoStore); return handler; } diff --git a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs index 5073897..b797f3a 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/OpenIdUtilities.cs @@ -217,6 +217,7 @@ namespace DotNetOpenAuth.OpenId { var rootHandler = hostFactories.CreateHttpMessageHandler(); var handler = rootHandler; + bool sslRequiredSet = false, cachePolicySet = false; do { var webRequestHandler = handler as WebRequestHandler; var untrustedHandler = handler as UntrustedWebRequestHandler; @@ -224,25 +225,28 @@ namespace DotNetOpenAuth.OpenId { if (webRequestHandler != null) { if (cachePolicy != null) { webRequestHandler.CachePolicy = cachePolicy; + cachePolicySet = true; } - - break; } else if (untrustedHandler != null) { - if (cachePolicy != null) { - untrustedHandler.CachePolicy = cachePolicy; - } - untrustedHandler.IsSslRequired = requireSsl; - break; - } else if (delegatingHandler != null) { + sslRequiredSet = true; + } + + if (delegatingHandler != null) { handler = delegatingHandler.InnerHandler; } else { - Logger.Http.DebugFormat("Unable to set cache policy on unsupported {0}.", handler.GetType().FullName); break; } } while (true); + if (cachePolicy != null && !cachePolicySet) { + Logger.OpenId.Warn( + "Unable to set cache policy due to HttpMessageHandler instances not being of type WebRequestHandler."); + } + + ErrorUtilities.VerifyProtocol(!requireSsl || sslRequiredSet, "Unable to set RequireSsl on message handler because no HttpMessageHandler was of type {0}.", typeof(UntrustedWebRequestHandler).FullName); + return hostFactories.CreateHttpClient(rootHandler); } diff --git a/src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs index f1b7e81..94d92e5 100644 --- a/src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs +++ b/src/DotNetOpenAuth.OpenId/OpenId/UntrustedWebRequestHandler.cs @@ -36,12 +36,7 @@ namespace DotNetOpenAuth.OpenId { /// If a particular host would be permitted but is in the blacklist, it is not allowed. /// If a particular host would not be permitted but is in the whitelist, it is allowed. /// </remarks> - public class UntrustedWebRequestHandler : HttpMessageHandler { - /// <summary> - /// The inner handler. - /// </summary> - private readonly InternalWebRequestHandler innerHandler; - + public class UntrustedWebRequestHandler : DelegatingHandler { /// <summary> /// The set of URI schemes allowed in untrusted web requests. /// </summary> @@ -71,7 +66,13 @@ namespace DotNetOpenAuth.OpenId { /// The maximum redirections to follow in the course of a single request. /// </summary> [DebuggerBrowsable(DebuggerBrowsableState.Never)] - private int maximumRedirections = Configuration.MaximumRedirections; + private int maxAutomaticRedirections = Configuration.MaximumRedirections; + + /// <summary> + /// A value indicating whether to automatically follow redirects. + /// </summary> + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private bool allowAutoRedirect = true; /// <summary> /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler" /> class. @@ -80,9 +81,8 @@ namespace DotNetOpenAuth.OpenId { /// The inner handler. This handler will be modified to suit the purposes of this wrapping handler, /// and should not be used independently of this wrapper after construction of this object. /// </param> - public UntrustedWebRequestHandler(WebRequestHandler innerHandler = null) { - this.innerHandler = new InternalWebRequestHandler(innerHandler ?? new WebRequestHandler()); - + public UntrustedWebRequestHandler(WebRequestHandler innerHandler = null) + : base(innerHandler ?? new WebRequestHandler()) { // If SSL is required throughout, we cannot allow auto redirects because // it may include a pass through an unprotected HTTP request. // We have to follow redirects manually. @@ -101,6 +101,18 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class + /// for use in unit testing. + /// </summary> + /// <param name="innerHandler"> + /// The inner handler which is responsible for processing the HTTP response messages. + /// This handler should NOT automatically follow redirects. + /// </param> + internal UntrustedWebRequestHandler(HttpMessageHandler innerHandler) + : base(innerHandler) { + } + + /// <summary> /// Gets or sets a value indicating whether all requests must use SSL. /// </summary> /// <value> @@ -109,25 +121,36 @@ namespace DotNetOpenAuth.OpenId { public bool IsSslRequired { get; set; } /// <summary> - /// Gets or sets the cache policy. - /// </summary> - public RequestCachePolicy CachePolicy { - get { return this.InnerWebRequestHandler.CachePolicy; } - set { this.InnerWebRequestHandler.CachePolicy = value; } - } - - /// <summary> /// Gets or sets the total number of redirections to allow on any one request. /// Default is 10. /// </summary> public int MaxAutomaticRedirections { get { - return this.maximumRedirections; + return base.InnerHandler is WebRequestHandler ? this.InnerWebRequestHandler.MaxAutomaticRedirections : this.maxAutomaticRedirections; } set { Requires.Range(value >= 0, "value"); - this.maximumRedirections = value; + this.maxAutomaticRedirections = value; + if (base.InnerHandler is WebRequestHandler) { + this.InnerWebRequestHandler.MaxAutomaticRedirections = value; + } + } + } + + /// <summary> + /// Gets or sets a value indicating whether to automatically follow redirects. + /// </summary> + public bool AllowAutoRedirect { + get { + return base.InnerHandler is WebRequestHandler ? this.InnerWebRequestHandler.AllowAutoRedirect : this.allowAutoRedirect; + } + + set { + this.allowAutoRedirect = value; + if (base.InnerHandler is WebRequestHandler) { + this.InnerWebRequestHandler.AllowAutoRedirect = value; + } } } @@ -190,8 +213,8 @@ namespace DotNetOpenAuth.OpenId { /// <value> /// The inner web request handler. /// </value> - protected WebRequestHandler InnerWebRequestHandler { - get { return (WebRequestHandler)this.innerHandler.InnerHandler; } + public WebRequestHandler InnerWebRequestHandler { + get { return (WebRequestHandler)this.InnerHandler; } } /// <summary> @@ -253,7 +276,8 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// Returns <see cref="T:System.Threading.Tasks.Task`1" />.The task object representing the asynchronous operation. /// </returns> - protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { + protected override async Task<HttpResponseMessage> SendAsync( + HttpRequestMessage request, CancellationToken cancellationToken) { this.EnsureAllowableRequestUri(request.RequestUri); // Since we may require SSL for every redirect, we handle each redirect manually @@ -264,16 +288,20 @@ namespace DotNetOpenAuth.OpenId { int i; for (i = 0; i < this.MaxAutomaticRedirections; i++) { this.EnsureAllowableRequestUri(request.RequestUri); - var response = await this.innerHandler.SendAsync(request, cancellationToken); - if (response.StatusCode == HttpStatusCode.MovedPermanently || response.StatusCode == HttpStatusCode.Redirect - || response.StatusCode == HttpStatusCode.RedirectMethod || response.StatusCode == HttpStatusCode.RedirectKeepVerb) { - // We have no copy of the post entity stream to repeat on our manually - // cloned HttpWebRequest, so we have to bail. - ErrorUtilities.VerifyProtocol(request.Method != HttpMethod.Post, MessagingStrings.UntrustedRedirectsOnPOSTNotSupported); - Uri redirectUri = new Uri(request.RequestUri, response.Headers.Location); - request = request.Clone(); - request.RequestUri = redirectUri; - continue; + var response = await base.SendAsync(request, cancellationToken); + if (this.AllowAutoRedirect) { + if (response.StatusCode == HttpStatusCode.MovedPermanently || response.StatusCode == HttpStatusCode.Redirect + || response.StatusCode == HttpStatusCode.RedirectMethod + || response.StatusCode == HttpStatusCode.RedirectKeepVerb) { + // We have no copy of the post entity stream to repeat on our manually + // cloned HttpWebRequest, so we have to bail. + ErrorUtilities.VerifyProtocol( + request.Method != HttpMethod.Post, MessagingStrings.UntrustedRedirectsOnPOSTNotSupported); + Uri redirectUri = new Uri(request.RequestUri, response.Headers.Location); + request = request.Clone(); + request.RequestUri = redirectUri; + continue; + } } if (response.StatusCode == HttpStatusCode.ExpectationFailed) { @@ -286,7 +314,9 @@ namespace DotNetOpenAuth.OpenId { // the web site's global behavior when calling that host. // TODO: verify that this still works in DNOA 5.0 var servicePoint = ServicePointManager.FindServicePoint(request.RequestUri); - Logger.Http.InfoFormat("HTTP POST to {0} resulted in 417 Expectation Failed. Changing ServicePoint to not use Expect: Continue next time.", request.RequestUri); + Logger.Http.InfoFormat( + "HTTP POST to {0} resulted in 417 Expectation Failed. Changing ServicePoint to not use Expect: Continue next time.", + request.RequestUri); servicePoint.Expect100Continue = false; } @@ -441,30 +471,5 @@ namespace DotNetOpenAuth.OpenId { } return true; } - - /// <summary> - /// A <see cref="DelegatingHandler" /> derived type that makes its SendAsync method available internally. - /// </summary> - private class InternalWebRequestHandler : DelegatingHandler { - /// <summary> - /// Initializes a new instance of the <see cref="InternalWebRequestHandler"/> class. - /// </summary> - /// <param name="innerHandler">The inner handler which is responsible for processing the HTTP response messages.</param> - internal InternalWebRequestHandler(HttpMessageHandler innerHandler) - : base(innerHandler) { - } - - /// <summary> - /// Creates an instance of <see cref="T:System.Net.Http.HttpResponseMessage" /> based on the information provided in the <see cref="T:System.Net.Http.HttpRequestMessage" /> as an operation that will not block. - /// </summary> - /// <param name="request">The HTTP request message.</param> - /// <param name="cancellationToken">A cancellation token to cancel the operation.</param> - /// <returns> - /// Returns <see cref="T:System.Threading.Tasks.Task`1" />.The task object representing the asynchronous operation. - /// </returns> - protected internal new Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { - return base.SendAsync(request, cancellationToken); - } - } } } diff --git a/src/DotNetOpenAuth.Test/MockingHostFactories.cs b/src/DotNetOpenAuth.Test/MockingHostFactories.cs index d9f6b02..ae10435 100644 --- a/src/DotNetOpenAuth.Test/MockingHostFactories.cs +++ b/src/DotNetOpenAuth.Test/MockingHostFactories.cs @@ -11,6 +11,9 @@ namespace DotNetOpenAuth.Test { using System.Threading; using System.Threading.Tasks; using System.Linq; + + using DotNetOpenAuth.OpenId; + using Validation; internal class MockingHostFactories : IHostFactories { @@ -30,10 +33,16 @@ namespace DotNetOpenAuth.Test { public bool AllowAutoRedirects { get; set; } + public bool InstallUntrustedWebReqestHandler { get; set; } + public HttpMessageHandler CreateHttpMessageHandler() { var forwardingMessageHandler = new ForwardingMessageHandler(this.handlers, this); var cookieDelegatingHandler = new CookieDelegatingHandler(forwardingMessageHandler, this.CookieContainer); - if (this.AllowAutoRedirects) { + if (this.InstallUntrustedWebReqestHandler) { + var untrustedHandler = new UntrustedWebRequestHandler(cookieDelegatingHandler); + untrustedHandler.AllowAutoRedirect = this.AllowAutoRedirects; + return untrustedHandler; + } else if (this.AllowAutoRedirects) { return new AutoRedirectHandler(cookieDelegatingHandler); } else { return cookieDelegatingHandler; diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs index fed2402..e6b878a 100644 --- a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs +++ b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs @@ -79,6 +79,7 @@ namespace DotNetOpenAuth.Test.OpenId { this.AutoProviderScenario = Scenarios.AutoApproval; Identifier.EqualityOnStrings = true; + this.HostFactories.InstallUntrustedWebReqestHandler = true; } [TearDown] @@ -302,37 +303,37 @@ namespace DotNetOpenAuth.Test.OpenId { return await op.Channel.PrepareResponseAsync(response); }); - { - var rp = this.CreateRelyingParty(); - ExtensionTestUtilities.RegisterExtension(rp.Channel, Mocks.MockOpenIdExtension.Factory); - var requestBase = new CheckIdRequest(protocol.Version, OpenIdTestBase.OPUri, AuthenticationRequestMode.Immediate); - OpenIdTestBase.StoreAssociation(rp, OpenIdTestBase.OPUri, association); - requestBase.AssociationHandle = association.Handle; - requestBase.ClaimedIdentifier = "http://claimedid"; - requestBase.LocalIdentifier = "http://localid"; - requestBase.ReturnTo = OpenIdTestBase.RPUri; - - foreach (IOpenIdMessageExtension extension in requests) { - requestBase.Extensions.Add(extension); - } + { + var rp = this.CreateRelyingParty(); + ExtensionTestUtilities.RegisterExtension(rp.Channel, Mocks.MockOpenIdExtension.Factory); + var requestBase = new CheckIdRequest(protocol.Version, OpenIdTestBase.OPUri, AuthenticationRequestMode.Immediate); + OpenIdTestBase.StoreAssociation(rp, OpenIdTestBase.OPUri, association); + requestBase.AssociationHandle = association.Handle; + requestBase.ClaimedIdentifier = "http://claimedid"; + requestBase.LocalIdentifier = "http://localid"; + requestBase.ReturnTo = OpenIdTestBase.RPUri; + + foreach (IOpenIdMessageExtension extension in requests) { + requestBase.Extensions.Add(extension); + } - var redirectingRequest = await rp.Channel.PrepareResponseAsync(requestBase); - Uri redirectingResponseUri; - this.HostFactories.AllowAutoRedirects = false; - using (var httpClient = rp.Channel.HostFactories.CreateHttpClient()) { - using (var redirectingResponse = await httpClient.GetAsync(redirectingRequest.Headers.Location)) { - Assert.AreEqual(HttpStatusCode.Found, redirectingResponse.StatusCode); - redirectingResponseUri = redirectingResponse.Headers.Location; - } + var redirectingRequest = await rp.Channel.PrepareResponseAsync(requestBase); + Uri redirectingResponseUri; + this.HostFactories.AllowAutoRedirects = false; + using (var httpClient = rp.Channel.HostFactories.CreateHttpClient()) { + using (var redirectingResponse = await httpClient.GetAsync(redirectingRequest.Headers.Location)) { + Assert.AreEqual(HttpStatusCode.Found, redirectingResponse.StatusCode); + redirectingResponseUri = redirectingResponse.Headers.Location; } - - var response = - await - rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>( - new HttpRequestMessage(HttpMethod.Get, redirectingResponseUri), CancellationToken.None); - var receivedResponses = response.Extensions.Cast<IOpenIdMessageExtension>(); - CollectionAssert<IOpenIdMessageExtension>.AreEquivalentByEquality(responses.ToArray(), receivedResponses.ToArray()); } + + var response = + await + rp.Channel.ReadFromRequestAsync<PositiveAssertionResponse>( + new HttpRequestMessage(HttpMethod.Get, redirectingResponseUri), CancellationToken.None); + var receivedResponses = response.Extensions.Cast<IOpenIdMessageExtension>(); + CollectionAssert<IOpenIdMessageExtension>.AreEquivalentByEquality(responses.ToArray(), receivedResponses.ToArray()); + } } } } |