diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-11-24 19:44:57 -0800 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-11-24 19:44:57 -0800 |
commit | 143d80b2ce76ef6eee4bddda9039a0b0f9673356 (patch) | |
tree | bcbf7ee6e089d85a3e4ee834871b5edb1a8a43fa /src | |
parent | e81621138e95624e12db5667c5720cde1f0475d9 (diff) | |
download | DotNetOpenAuth-143d80b2ce76ef6eee4bddda9039a0b0f9673356.zip DotNetOpenAuth-143d80b2ce76ef6eee4bddda9039a0b0f9673356.tar.gz DotNetOpenAuth-143d80b2ce76ef6eee4bddda9039a0b0f9673356.tar.bz2 |
Added discovery and around 60 tests. The discovery and a few other tests still fail, but we're making progress.
Diffstat (limited to 'src')
45 files changed, 1391 insertions, 139 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index 6e294e5..a0f6549 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -60,6 +60,9 @@ </ItemGroup> <ItemGroup> <Compile Include="CoordinatorBase.cs" /> + <Compile Include="Hosting\AspNetHost.cs" /> + <Compile Include="Hosting\HttpHost.cs" /> + <Compile Include="Hosting\TestingWorkerRequest.cs" /> <Compile Include="Messaging\CollectionAssert.cs" /> <Compile Include="Messaging\ErrorUtilitiesTests.cs" /> <Compile Include="Messaging\MessageSerializerTests.cs" /> @@ -75,6 +78,8 @@ <Compile Include="Messaging\Reflection\ValueMappingTests.cs" /> <Compile Include="Mocks\CoordinatingChannel.cs" /> <Compile Include="Mocks\InMemoryTokenManager.cs" /> + <Compile Include="Mocks\MockHttpRequest.cs" /> + <Compile Include="Mocks\MockIdentifier.cs" /> <Compile Include="Mocks\MockTransformationBindingElement.cs" /> <Compile Include="Mocks\MockReplayProtectionBindingElement.cs" /> <Compile Include="Mocks\TestBaseMessage.cs" /> @@ -97,6 +102,7 @@ <Compile Include="OAuth\ProtocolTests.cs" /> <Compile Include="OAuth\ServiceProviderDescriptionTests.cs" /> <Compile Include="OpenId\ChannelElements\KeyValueFormEncodingTests.cs" /> + <Compile Include="OpenId\IdentifierTests.cs" /> <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateUnsuccessfulResponseTests.cs" /> @@ -109,6 +115,10 @@ <Compile Include="OpenId\AssociationHandshakeTests.cs" /> <Compile Include="OpenId\OpenIdTestBase.cs" /> <Compile Include="OpenId\RealmTests.cs" /> + <Compile Include="OpenId\TestSupport.cs" /> + <Compile Include="OpenId\UI\UITestSupport.cs" /> + <Compile Include="OpenId\UriIdentifierTests.cs" /> + <Compile Include="OpenId\XriIdentifierTests.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Messaging\ResponseTests.cs" /> <Compile Include="OAuth\AppendixScenarios.cs" /> @@ -130,6 +140,29 @@ <ItemGroup> <Shadow Include="Test References\DotNetOpenAuth.accessor" /> </ItemGroup> + <ItemGroup> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html1020.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html10both.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html10del.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html10prov.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html2010.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html2010combinedA.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html2010combinedB.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html2010combinedC.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html20both.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html20del.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html20prov.html" /> + <EmbeddedResource Include="OpenId\Discovery\htmldiscovery\html20relative.html" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\xrds-irrelevant.xml" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\xrds10.xml" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\xrds1020.xml" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\xrds11.xml" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\xrds20.xml" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\xrds2010a.xml" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\xrds2010b.xml" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\XrdsReferencedInHead.html" /> + <EmbeddedResource Include="OpenId\Discovery\xrdsdiscovery\XrdsReferencedInHttpHeader.html" /> + </ItemGroup> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> </Project>
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Test/Hosting/AspNetHost.cs b/src/DotNetOpenAuth.Test/Hosting/AspNetHost.cs new file mode 100644 index 0000000..0bbca5e --- /dev/null +++ b/src/DotNetOpenAuth.Test/Hosting/AspNetHost.cs @@ -0,0 +1,61 @@ +namespace DotNetOpenAuth.Test.Hosting { + using System; + using System.IO; + using System.Net; + using System.Threading; + using System.Web; + using System.Web.Hosting; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Test.OpenId; + + /// <summary> + /// Hosts a 'portable' version of the OpenIdProvider for testing itself and the + /// RelyingParty against it. + /// </summary> + class AspNetHost : MarshalByRefObject { + HttpHost httpHost; + + public AspNetHost() { + httpHost = HttpHost.CreateHost(this); + ////if (!UntrustedWebRequestHandler.WhitelistHosts.Contains("localhost")) + //// UntrustedWebRequestHandler.WhitelistHosts.Add("localhost"); + } + + public Uri BaseUri { + get { return httpHost.BaseUri; } + } + + public static AspNetHost CreateHost(string webDirectory) { + AspNetHost host = (AspNetHost)ApplicationHost. + CreateApplicationHost(typeof(AspNetHost), "/", webDirectory); + return host; + } + + public string ProcessRequest(string url) { + return httpHost.ProcessRequest(url); + } + + public string ProcessRequest(string url, string body) { + return httpHost.ProcessRequest(url, body); + } + + public void BeginProcessRequest(HttpListenerContext context) { + ThreadPool.QueueUserWorkItem(state => { ProcessRequest(context); }); + } + + public void ProcessRequest(HttpListenerContext context) { + try { + using (TextWriter tw = new StreamWriter(context.Response.OutputStream)) { + HttpRuntime.ProcessRequest(new TestingWorkerRequest(context, tw)); + } + } catch (Exception ex) { + Logger.Error("Exception in AspNetHost", ex); + throw; + } + } + + public void CloseHttp() { + httpHost.Dispose(); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Hosting/HttpHost.cs b/src/DotNetOpenAuth.Test/Hosting/HttpHost.cs new file mode 100644 index 0000000..9606cb7 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Hosting/HttpHost.cs @@ -0,0 +1,93 @@ +namespace DotNetOpenAuth.Test.Hosting { + using System; + using System.Globalization; + using System.IO; + using System.Net; + using System.Threading; + + class HttpHost : IDisposable { + HttpListener listener; + public int Port { get; private set; } + Thread listenerThread; + AspNetHost aspNetHost; + + HttpHost(AspNetHost aspNetHost) { + this.aspNetHost = aspNetHost; + + Port = 59687; + Random r = new Random(); + tryAgain: + try { + listener = new HttpListener(); + listener.Prefixes.Add(string.Format(CultureInfo.InvariantCulture, + "http://localhost:{0}/", Port)); + listener.Start(); + } catch (HttpListenerException ex) { + if (ex.Message.Contains("conflicts")) { + Port += r.Next(1, 20); + goto tryAgain; + } + throw; + } + listenerThread = new Thread(processRequests); + listenerThread.Start(); + } + + public static HttpHost CreateHost(AspNetHost aspNetHost) { + return new HttpHost(aspNetHost); + } + + public static HttpHost CreateHost(string webDirectory) { + return new HttpHost(AspNetHost.CreateHost(webDirectory)); + } + void processRequests() { + try { + while (true) { + var context = listener.GetContext(); + aspNetHost.BeginProcessRequest(context); + } + } catch (HttpListenerException) { + // the listener is probably being shut down + } + } + + public Uri BaseUri { + get { return new Uri("http://localhost:" + Port.ToString() + "/"); } + } + public string ProcessRequest(string url) { + return ProcessRequest(url, null); + } + public string ProcessRequest(string url, string body) { + WebRequest request = WebRequest.Create(new Uri(BaseUri, url)); + if (body != null) { + request.Method = "POST"; + request.ContentLength = body.Length; + using (StreamWriter sw = new StreamWriter(request.GetRequestStream())) + sw.Write(body); + } + try { + using (WebResponse response = request.GetResponse()) { + using (StreamReader sr = new StreamReader(response.GetResponseStream())) + return sr.ReadToEnd(); + } + } catch (WebException ex) { + Logger.Error("Exception in HttpHost", ex); + using (StreamReader sr = new StreamReader(ex.Response.GetResponseStream())) { + string streamContent = sr.ReadToEnd(); + Logger.ErrorFormat("Error content stream follows: {0}", streamContent); + } + throw; + } + } + + #region IDisposable Members + + public void Dispose() { + listener.Close(); + listenerThread.Join(1000); + listenerThread.Abort(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth.Test/Hosting/TestingWorkerRequest.cs b/src/DotNetOpenAuth.Test/Hosting/TestingWorkerRequest.cs new file mode 100644 index 0000000..29f1c4c --- /dev/null +++ b/src/DotNetOpenAuth.Test/Hosting/TestingWorkerRequest.cs @@ -0,0 +1,91 @@ +namespace DotNetOpenAuth.Test.Hosting { + using System; + using System.IO; + using System.Net; + using System.Web.Hosting; + + class TestingWorkerRequest : SimpleWorkerRequest { + public TestingWorkerRequest(string page, string query, Stream entityStream, TextWriter writer) + : base(page, query, writer) { + this.entityStream = entityStream; + this.writer = writer; + } + public TestingWorkerRequest(HttpListenerContext context, TextWriter output) + : base(context.Request.Url.LocalPath.TrimStart('/'), context.Request.Url.Query, output) { + this.entityStream = context.Request.InputStream; + this.context = context; + this.writer = output; + } + + Stream entityStream; + HttpListenerContext context; + TextWriter writer; + public override string GetFilePath() { + string filePath = context.Request.Url.LocalPath.Replace("/", "\\"); + if (filePath.EndsWith("\\", StringComparison.Ordinal)) + filePath += "default.aspx"; + return filePath; + } + public override int GetLocalPort() { + return context.Request.Url.Port; + } + public override string GetServerName() { + return context.Request.Url.Host; + } + public override string GetQueryString() { + return context.Request.Url.Query.TrimStart('?'); + } + public override string GetHttpVerbName() { + return context.Request.HttpMethod; + } + public override string GetLocalAddress() { + return context.Request.LocalEndPoint.Address.ToString(); + } + public override string GetHttpVersion() { + return "HTTP/1.1"; + } + public override string GetProtocol() { + return context.Request.Url.Scheme; + } + public override string GetRawUrl() { + return context.Request.RawUrl; + } + public override int GetTotalEntityBodyLength() { + return (int)context.Request.ContentLength64; + } + public override string GetKnownRequestHeader(int index) { + return context.Request.Headers[GetKnownRequestHeaderName(index)]; + } + public override string GetUnknownRequestHeader(string name) { + return context.Request.Headers[name]; + } + public override bool IsEntireEntityBodyIsPreloaded() { + return false; + } + public override int ReadEntityBody(byte[] buffer, int size) { + return entityStream.Read(buffer, 0, size); + } + public override int ReadEntityBody(byte[] buffer, int offset, int size) { + return entityStream.Read(buffer, offset, size); + } + public override void SendCalculatedContentLength(int contentLength) { + context.Response.ContentLength64 = contentLength; + } + public override void SendStatus(int statusCode, string statusDescription) { + if (context != null) { + context.Response.StatusCode = statusCode; + context.Response.StatusDescription = statusDescription; + } + } + public override void SendKnownResponseHeader(int index, string value) { + if (context != null) { + context.Response.Headers[(HttpResponseHeader)index] = value; + } + } + public override void SendUnknownResponseHeader(string name, string value) { + if (context != null) { + context.Response.Headers[name] = value; + } + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs b/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs new file mode 100644 index 0000000..a9c6ad4 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/MockHttpRequest.cs @@ -0,0 +1,179 @@ +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using System.Globalization; + using System.IO; + using System.Net; + using System.Text; + using System.Web; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Yadis; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.Test.OpenId; + + internal class MockHttpRequest { + private readonly Dictionary<Uri, DirectWebResponse> registeredMockResponses = new Dictionary<Uri, DirectWebResponse>(); + private readonly TestWebRequestHandler mockHandler; + + internal MockHttpRequest(TestWebRequestHandler mockHandler) { + ErrorUtilities.VerifyArgumentNotNull(mockHandler, "mockHandler"); + this.mockHandler = mockHandler; + this.mockHandler.Callback = this.GetMockResponse; + } + + private DirectWebResponse GetMockResponse(HttpWebRequest request) { + DirectWebResponse response; + if (this.registeredMockResponses.TryGetValue(request.RequestUri, out response)) { + // reset response stream position so this response can be reused on a subsequent request. + response.ResponseStream.Seek(0, SeekOrigin.Begin); + return response; + } else { + ////Assert.Fail("Unexpected HTTP request: {0}", uri); + Logger.WarnFormat("Unexpected HTTP request: {0}", request.RequestUri); + return new DirectWebResponse(request.RequestUri, request.RequestUri, new WebHeaderCollection(), HttpStatusCode.NotFound, + "text/html", null, new MemoryStream()); + } + } + + /// <summary> + /// Clears all all mock HTTP responses and deactivates HTTP mocking. + /// </summary> + internal void Reset() { + this.registeredMockResponses.Clear(); + } + + internal void RegisterMockResponse(DirectWebResponse response) { + if (response == null) throw new ArgumentNullException("response"); + if (registeredMockResponses.ContainsKey(response.RequestUri)) { + Logger.WarnFormat("Mock HTTP response already registered for {0}.", response.RequestUri); + } else { + registeredMockResponses.Add(response.RequestUri, response); + } + } + + internal void RegisterMockResponse(Uri requestUri, string contentType, string responseBody) { + RegisterMockResponse(requestUri, requestUri, contentType, responseBody); + } + + internal void RegisterMockResponse(Uri requestUri, Uri responseUri, string contentType, string responseBody) { + RegisterMockResponse(requestUri, responseUri, contentType, new WebHeaderCollection(), responseBody); + } + + internal void RegisterMockResponse(Uri requestUri, Uri responseUri, string contentType, WebHeaderCollection headers, string responseBody) { + if (requestUri == null) throw new ArgumentNullException("requestUri"); + if (responseUri == null) throw new ArgumentNullException("responseUri"); + if (String.IsNullOrEmpty(contentType)) throw new ArgumentNullException("contentType"); + + // Set up the redirect if appropriate + if (requestUri != responseUri) { + RegisterMockRedirect(requestUri, responseUri); + } + + string contentEncoding = null; + MemoryStream stream = new MemoryStream(); + StreamWriter sw = new StreamWriter(stream); + sw.Write(responseBody); + sw.Flush(); + stream.Seek(0, SeekOrigin.Begin); + RegisterMockResponse(new DirectWebResponse(responseUri, responseUri, headers ?? new WebHeaderCollection(), + HttpStatusCode.OK, contentType, contentEncoding, stream)); + } + + internal void RegisterMockXrdsResponses(IDictionary<string, string> requestUriAndResponseBody) { + foreach (var pair in requestUriAndResponseBody) { + RegisterMockResponse(new Uri(pair.Key), "text/xml; saml=false; https=false; charset=UTF-8", pair.Value); + } + } + + internal void RegisterMockXrdsResponse(ServiceEndpoint endpoint) { + if (endpoint == null) throw new ArgumentNullException("endpoint"); + + string identityUri; + if (endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier) { + identityUri = endpoint.UserSuppliedIdentifier; + } else { + identityUri = endpoint.UserSuppliedIdentifier ?? endpoint.ClaimedIdentifier; + } + RegisterMockXrdsResponse(new Uri(identityUri), new ServiceEndpoint[] { endpoint }); + } + + internal void RegisterMockXrdsResponse(Uri respondingUri, IEnumerable<ServiceEndpoint> endpoints) { + if (endpoints == null) throw new ArgumentNullException("endpoints"); + + StringBuilder xrds = new StringBuilder(); + xrds.AppendLine(@"<xrds:XRDS xmlns:xrds='xri://$xrds' xmlns:openid='http://openid.net/xmlns/1.0' xmlns='xri://$xrd*($v*2.0)'> + <XRD>"); + foreach (var endpoint in endpoints) { + string template = @" + <Service priority='10'> + <Type>{0}</Type> + <URI>{1}</URI> + <LocalID>{2}</LocalID> + <openid:Delegate xmlns:openid='http://openid.net/xmlns/1.0'>{2}</openid:Delegate> + </Service>"; + string serviceTypeUri; + if (endpoint.ClaimedIdentifier == endpoint.Protocol.ClaimedIdentifierForOPIdentifier) { + serviceTypeUri = endpoint.Protocol.OPIdentifierServiceTypeURI; + } else { + serviceTypeUri = endpoint.Protocol.ClaimedIdentifierServiceTypeURI; + } + string xrd = string.Format(CultureInfo.InvariantCulture, template, + HttpUtility.HtmlEncode(serviceTypeUri), + HttpUtility.HtmlEncode(endpoint.ProviderEndpoint.AbsoluteUri), + HttpUtility.HtmlEncode(endpoint.ProviderLocalIdentifier)); + xrds.Append(xrd); + } + xrds.Append(@" + </XRD> +</xrds:XRDS>"); + + RegisterMockResponse(respondingUri, ContentTypes.Xrds, xrds.ToString()); + } + internal void RegisterMockXrdsResponse(UriIdentifier directedIdentityAssignedIdentifier, ServiceEndpoint providerEndpoint) { + ServiceEndpoint identityEndpoint = ServiceEndpoint.CreateForClaimedIdentifier( + directedIdentityAssignedIdentifier, + directedIdentityAssignedIdentifier, + providerEndpoint.ProviderEndpoint, + new string[] { providerEndpoint.Protocol.ClaimedIdentifierServiceTypeURI }, + 10, + 10); + RegisterMockXrdsResponse(identityEndpoint); + } + internal Identifier RegisterMockXrdsResponse(string embeddedResourcePath) { + UriIdentifier id = TestSupport.GetFullUrl(embeddedResourcePath); + RegisterMockResponse(id, "application/xrds+xml", TestSupport.LoadEmbeddedFile(embeddedResourcePath)); + return id; + } + internal void RegisterMockRPDiscovery() { + Uri rpRealmUri = TestSupport.Realm.UriWithWildcardChangedToWww; + + string template = @"<xrds:XRDS xmlns:xrds='xri://$xrds' xmlns:openid='http://openid.net/xmlns/1.0' xmlns='xri://$xrd*($v*2.0)'> + <XRD> + <Service priority='10'> + <Type>{0}</Type> + <URI>{1}</URI> + </Service> + </XRD> +</xrds:XRDS>"; + string xrds = string.Format(CultureInfo.InvariantCulture, template, + HttpUtility.HtmlEncode(Protocol.V20.RPReturnToTypeURI), + HttpUtility.HtmlEncode(rpRealmUri.AbsoluteUri)); + + RegisterMockResponse(rpRealmUri, ContentTypes.Xrds, xrds); + } + + internal void DeleteResponse(Uri requestUri) { + this.registeredMockResponses.Remove(requestUri); + } + + internal void RegisterMockRedirect(Uri origin, Uri redirectLocation) { + var redirectionHeaders = new WebHeaderCollection { + { HttpResponseHeader.Location, redirectLocation.AbsoluteUri }, + }; + DirectWebResponse response = new DirectWebResponse(origin, origin, + redirectionHeaders, HttpStatusCode.Redirect, null, null, new MemoryStream()); + RegisterMockResponse(response); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs b/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs new file mode 100644 index 0000000..2b7a7a9 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/MockIdentifier.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="MockIdentifier.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System; + using System.Collections.Generic; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.OpenId; +using DotNetOpenAuth.Messaging; + + /// <summary> + /// Performs similar to an ordinary <see cref="Identifier"/>, but when called upon + /// to perform discovery, it returns a preset list of sevice endpoints to avoid + /// having a dependency on a hosted web site to actually perform discovery on. + /// </summary> + class MockIdentifier : Identifier { + IEnumerable<ServiceEndpoint> endpoints; + MockHttpRequest mockHttpRequest; + Identifier wrappedIdentifier; + + public MockIdentifier(Identifier wrappedIdentifier, MockHttpRequest mockHttpRequest, IEnumerable<ServiceEndpoint> endpoints) + : base(false) { + ErrorUtilities.VerifyArgumentNotNull(wrappedIdentifier, "wrappedIdentifier"); + ErrorUtilities.VerifyArgumentNotNull(mockHttpRequest, "mockHttpRequest"); + ErrorUtilities.VerifyArgumentNotNull(endpoints, "endpoints"); + + this.wrappedIdentifier = wrappedIdentifier; + this.endpoints = endpoints; + this.mockHttpRequest = mockHttpRequest; + + // Register a mock HTTP response to enable discovery of this identifier within the RP + // without having to host an ASP.NET site within the test. + mockHttpRequest.RegisterMockXrdsResponse(new Uri(wrappedIdentifier.ToString()), endpoints); + } + + internal override IEnumerable<ServiceEndpoint> Discover(IDirectSslWebRequestHandler requestHandler) { + return endpoints; + } + + internal override Identifier TrimFragment() { + return this; + } + + internal override bool TryRequireSsl(out Identifier secureIdentifier) { + // We take special care to make our wrapped identifier secure, but still + // return a mocked (secure) identifier. + Identifier secureWrappedIdentifier; + bool result = wrappedIdentifier.TryRequireSsl(out secureWrappedIdentifier); + secureIdentifier = new MockIdentifier(secureWrappedIdentifier, this.mockHttpRequest, this.endpoints); + return result; + } + + public override string ToString() { + return wrappedIdentifier.ToString(); + } + + public override bool Equals(object obj) { + return wrappedIdentifier.Equals(obj); + } + + public override int GetHashCode() { + return wrappedIdentifier.GetHashCode(); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs index af971a0..e655238 100644 --- a/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs +++ b/src/DotNetOpenAuth.Test/Mocks/TestWebRequestHandler.cs @@ -12,7 +12,7 @@ namespace DotNetOpenAuth.Test.Mocks { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.ChannelElements; - internal class TestWebRequestHandler : IDirectWebRequestHandler { + internal class TestWebRequestHandler : IDirectSslWebRequestHandler { private StringBuilder postEntity; /// <summary> @@ -72,5 +72,21 @@ namespace DotNetOpenAuth.Test.Mocks { } #endregion + + #region IDirectSslWebRequestHandler Members + + public TextWriter GetRequestStream(HttpWebRequest request, bool requireSsl) { + ErrorUtilities.VerifyProtocol(!requireSsl || request.RequestUri.Scheme == Uri.UriSchemeHttps, "disallowed request"); + return this.GetRequestStream(request); + } + + public DirectWebResponse GetResponse(HttpWebRequest request, bool requireSsl) { + ErrorUtilities.VerifyProtocol(!requireSsl || request.RequestUri.Scheme == Uri.UriSchemeHttps, "disallowed request"); + var result = this.GetResponse(request); + ErrorUtilities.VerifyProtocol(!requireSsl || result.FinalUri.Scheme == Uri.UriSchemeHttps, "disallowed request"); + return result; + } + + #endregion } } diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html1020.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html1020.html new file mode 100644 index 0000000..5fc841f --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html1020.html @@ -0,0 +1,12 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid.server" href="http://e/f" /> + <link rel="openid.delegate" href="http://g/h" /> + <link rel="openid2.provider" href="http://a/b" /> + <link rel="openid2.local_id" href="http://c/d" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10both.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10both.html new file mode 100644 index 0000000..d350abe --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10both.html @@ -0,0 +1,10 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid.server" href="http://a/b" /> + <link rel="openid.delegate" href="http://c/d" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10del.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10del.html new file mode 100644 index 0000000..c19028b --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10del.html @@ -0,0 +1,9 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid.delegate" href="http://c/d" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10prov.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10prov.html new file mode 100644 index 0000000..f787dce --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html10prov.html @@ -0,0 +1,9 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid.server" href="http://a/b" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010.html new file mode 100644 index 0000000..61d1305 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010.html @@ -0,0 +1,12 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid2.provider" href="http://a/b" /> + <link rel="openid2.local_id" href="http://c/d" /> + <link rel="openid.server" href="http://e/f" /> + <link rel="openid.delegate" href="http://g/h" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedA.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedA.html new file mode 100644 index 0000000..883344b --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedA.html @@ -0,0 +1,10 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid2.provider B openid.server" href="http://a/b" /> + <link rel="openid2.local_id B openid.delegate" href="http://c/d" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedB.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedB.html new file mode 100644 index 0000000..3da5a73 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedB.html @@ -0,0 +1,10 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="A openid2.provider openid.server" href="http://a/b" /> + <link rel="A openid2.local_id openid.delegate" href="http://c/d" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedC.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedC.html new file mode 100644 index 0000000..2950500 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html2010combinedC.html @@ -0,0 +1,10 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid.server B openid2.provider" href="http://a/b" /> + <link rel="openid.delegate B openid2.local_id" href="http://c/d" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20both.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20both.html new file mode 100644 index 0000000..77d9346 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20both.html @@ -0,0 +1,10 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid2.provider" href="http://a/b" /> + <link rel="openid2.local_id" href="http://c/d" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20del.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20del.html new file mode 100644 index 0000000..b8d65c5 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20del.html @@ -0,0 +1,9 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid2.local_id" href="http://c/d" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20prov.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20prov.html new file mode 100644 index 0000000..de51e42 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20prov.html @@ -0,0 +1,9 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid2.provider" href="http://a/b" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20relative.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20relative.html new file mode 100644 index 0000000..0e9f341 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/htmldiscovery/html20relative.html @@ -0,0 +1,9 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head> + <title>Untitled Page</title> + <link rel="openid2.provider" href="../a/b" /> +</head> +<body> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/XrdsReferencedInHead.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/XrdsReferencedInHead.html new file mode 100644 index 0000000..142ab4d --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/XrdsReferencedInHead.html @@ -0,0 +1,12 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml"> +<head runat="server"> + <title>Untitled Page</title> + <meta http-equiv="X-XRDS-Location" content="http://localhost/xrds1020.xml"/> +</head> +<body> + <form id="form1" runat="server"> + </form> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/XrdsReferencedInHttpHeader.html b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/XrdsReferencedInHttpHeader.html new file mode 100644 index 0000000..a394aff --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/XrdsReferencedInHttpHeader.html @@ -0,0 +1,9 @@ +<html xmlns="http://www.w3.org/1999/xhtml"> +<head runat="server"> + <title>Untitled Page</title> +</head> +<body> + <form id="form1" runat="server"> + </form> +</body> +</html> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds-irrelevant.xml b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds-irrelevant.xml new file mode 100644 index 0000000..5116c0b --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds-irrelevant.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>junk</Type> + <URI>http://a/b</URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds10.xml b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds10.xml new file mode 100644 index 0000000..c67b36e --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds10.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>http://openid.net/signon/1.0</Type> + <!-- this next sreg one is deliberately an unofficial (but supported) sreg/1.0 typeUri, so we test it. --> + <Type>http://openid.net/sreg/1.0</Type> + <URI>http://a/b</URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds1020.xml b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds1020.xml new file mode 100644 index 0000000..1b56f0f --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds1020.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>http://openid.net/signon/1.0</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://a/b</URI> + </Service> + <Service priority="20"> + <Type>http://specs.openid.net/auth/2.0/signon</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://c/d</URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds11.xml b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds11.xml new file mode 100644 index 0000000..2d6a7aa --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds11.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>http://openid.net/signon/1.1</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://a/b</URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds20.xml b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds20.xml new file mode 100644 index 0000000..870b540 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds20.xml @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/signon</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://a/b</URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds2010a.xml b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds2010a.xml new file mode 100644 index 0000000..4f72ac2 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds2010a.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/signon</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://a/b</URI> + </Service> + <Service priority="20"> + <Type>http://openid.net/signon/1.0</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://c/d</URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds2010b.xml b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds2010b.xml new file mode 100644 index 0000000..8ad468f --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Discovery/xrdsdiscovery/xrds2010b.xml @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<xrds:XRDS + xmlns:xrds="xri://$xrds" + xmlns:openid="http://openid.net/xmlns/1.0" + xmlns="xri://$xrd*($v*2.0)"> + <XRD> + <Service priority="20"> + <Type>http://openid.net/signon/1.0</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://c/d</URI> + </Service> + <Service priority="10"> + <Type>http://specs.openid.net/auth/2.0/signon</Type> + <Type>http://openid.net/extensions/sreg/1.1</Type> + <URI>http://a/b</URI> + </Service> + </XRD> +</xrds:XRDS> diff --git a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs index 8c3bc3b..941aa70 100644 --- a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs @@ -14,8 +14,8 @@ namespace DotNetOpenAuth.Test.OpenId { [TestMethod] public void Parse() { - Assert.IsInstanceOfType(typeof(UriIdentifier), Identifier.Parse(uri)); - Assert.IsInstanceOfType(typeof(XriIdentifier), Identifier.Parse(xri)); + Assert.IsInstanceOfType(Identifier.Parse(uri), typeof(UriIdentifier)); + Assert.IsInstanceOfType(Identifier.Parse(xri), typeof(XriIdentifier)); } /// <summary> @@ -30,7 +30,7 @@ namespace DotNetOpenAuth.Test.OpenId { prefixes.AddRange(symbols.Select(s => "xri://" + s.ToString())); foreach (string prefix in prefixes) { var id = Identifier.Parse(prefix + "andrew"); - Assert.IsInstanceOfType(typeof(XriIdentifier), id); + Assert.IsInstanceOfType(id, typeof(XriIdentifier)); } } @@ -41,15 +41,15 @@ namespace DotNetOpenAuth.Test.OpenId { public void ParseEndUserSuppliedUriIdentifier() { // verify a fully-qualified Uri var id = Identifier.Parse(uri); - Assert.IsInstanceOfType(typeof(UriIdentifier), id); + Assert.IsInstanceOfType(id, typeof(UriIdentifier)); Assert.AreEqual(uri, ((UriIdentifier)id).Uri.AbsoluteUri); // verify an HTTPS Uri id = Identifier.Parse(uriHttps); - Assert.IsInstanceOfType(typeof(UriIdentifier), id); + Assert.IsInstanceOfType(id, typeof(UriIdentifier)); Assert.AreEqual(uriHttps, ((UriIdentifier)id).Uri.AbsoluteUri); // verify that if the scheme is missing it is added automatically id = Identifier.Parse(uriNoScheme); - Assert.IsInstanceOfType(typeof(UriIdentifier), id); + Assert.IsInstanceOfType(id, typeof(UriIdentifier)); Assert.AreEqual(uri, ((UriIdentifier)id).Uri.AbsoluteUri); } @@ -58,7 +58,7 @@ namespace DotNetOpenAuth.Test.OpenId { Identifier.Parse(null); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void ParseEmpty() { Identifier.Parse(string.Empty); } diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs index 4b52634..4eff09f 100644 --- a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs +++ b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs @@ -8,17 +8,26 @@ namespace DotNetOpenAuth.Test.OpenId { using DotNetOpenAuth.Configuration; using DotNetOpenAuth.OpenId.Provider; using DotNetOpenAuth.OpenId.RelyingParty; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using DotNetOpenAuth.Test.Mocks; public class OpenIdTestBase : TestBase { protected RelyingPartySecuritySettings RelyingPartySecuritySettings { get; private set; } protected ProviderSecuritySettings ProviderSecuritySettings { get; private set; } + internal TestWebRequestHandler requestHandler; + internal MockHttpRequest mockResponder; + + [TestInitialize] public override void SetUp() { base.SetUp(); - RelyingPartySecuritySettings = RelyingPartySection.Configuration.SecuritySettings.CreateSecuritySettings(); - ProviderSecuritySettings = ProviderSection.Configuration.SecuritySettings.CreateSecuritySettings(); + this.RelyingPartySecuritySettings = RelyingPartySection.Configuration.SecuritySettings.CreateSecuritySettings(); + this.ProviderSecuritySettings = ProviderSection.Configuration.SecuritySettings.CreateSecuritySettings(); + + this.requestHandler = new TestWebRequestHandler(); + this.mockResponder = new MockHttpRequest(requestHandler); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/TestSupport.cs b/src/DotNetOpenAuth.Test/OpenId/TestSupport.cs new file mode 100644 index 0000000..4e742dc --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/TestSupport.cs @@ -0,0 +1,364 @@ +//----------------------------------------------------------------------- +// <copyright file="TestSupport.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId { + using System; + using System.Collections.Generic; + using System.Collections.Specialized; + using System.Diagnostics; + using System.IO; + using System.Reflection; + using System.Web; + using DotNetOpenAuth; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Test.Mocks; + //using DotNetOpenAuth.Test.UI; + using log4net; + using IProviderAssociationStore = DotNetOpenAuth.OpenId.IAssociationStore<DotNetOpenAuth.OpenId.AssociationRelyingPartyType>; + using ProviderMemoryStore = DotNetOpenAuth.OpenId.AssociationMemoryStore<DotNetOpenAuth.OpenId.AssociationRelyingPartyType>; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using DotNetOAuth.Test.OpenId.UI; + using DotNetOpenAuth.Messaging; + + public class TestSupport { + public static readonly string TestWebDirectory = Path.GetFullPath(Path.Combine(Directory.GetCurrentDirectory(), @"..\..\src\DotNetOpenId.TestWeb")); + public const string HostTestPage = "HostTest.aspx"; + const string identityPage = "IdentityEndpoint.aspx"; + const string directedIdentityPage = "DirectedIdentityEndpoint.aspx"; + public const string ProviderPage = "ProviderEndpoint.aspx"; + public const string DirectedProviderEndpoint = "DirectedProviderEndpoint.aspx"; + public const string MobileConsumerPage = "RelyingPartyMobile.aspx"; + public const string ConsumerPage = "RelyingParty.aspx"; + public const string OPDefaultPage = "OPDefault.aspx"; + public static Uri ReturnTo { + get { return TestSupport.GetFullUrl(TestSupport.ConsumerPage); } + } + public static Realm Realm { + get { return new Realm(TestSupport.GetFullUrl(TestSupport.ConsumerPage).AbsoluteUri); } + } + public readonly static ILog Logger = LogManager.GetLogger("DotNetOpenId.Test"); + + public enum Scenarios { + // Authentication test scenarios + AutoApproval, + AutoApprovalAddFragment, + ApproveOnSetup, + AlwaysDeny, + + // Extension test scenarios + /// <summary> + /// Provides all required and requested fields. + /// </summary> + ExtensionFullCooperation, + /// <summary> + /// Provides only those fields marked as required. + /// </summary> + ExtensionPartialCooperation, + } + + ////internal static UriIdentifier GetOPIdentityUrl(Scenarios scenario, bool useSsl) { + //// var args = new Dictionary<string, string> { + //// { "user", scenario.ToString() }, + ////}; + //// return new UriIdentifier(GetFullUrl("/" + OPDefaultPage, args, useSsl)); + ////} + internal static UriIdentifier GetIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion) { + return GetIdentityUrl(scenario, providerVersion, false); + } + internal static UriIdentifier GetIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion, bool useSsl) { + return new UriIdentifier(GetFullUrl("/" + identityPage, new Dictionary<string, string> { + { "user", scenario.ToString() }, + { "version", providerVersion.ToString() }, + }, useSsl)); + } + ////internal static UriIdentifier GetDirectedIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion) { + //// return GetDirectedIdentityUrl(scenario, providerVersion, false); + ////} + ////internal static UriIdentifier GetDirectedIdentityUrl(Scenarios scenario, ProtocolVersion providerVersion, bool useSsl) { + //// return new UriIdentifier(GetFullUrl("/" + directedIdentityPage, new Dictionary<string, string> { + //// { "user", scenario.ToString() }, + //// { "version", providerVersion.ToString() }, + ////}, useSsl)); + ////} + public static Identifier GetDelegateUrl(Scenarios scenario) { + return GetDelegateUrl(scenario, false); + } + public static Identifier GetDelegateUrl(Scenarios scenario, bool useSsl) { + return new UriIdentifier(GetFullUrl("/" + scenario, null, useSsl)); + } + internal static MockIdentifier GetMockIdentifier(Scenarios scenario, MockHttpRequest mockRequest, ProtocolVersion providerVersion) { + return GetMockIdentifier(scenario, mockRequest, providerVersion, false); + } + internal static MockIdentifier GetMockIdentifier(Scenarios scenario, MockHttpRequest mockRequest, ProtocolVersion providerVersion, bool useSsl) { + ServiceEndpoint se = GetServiceEndpoint(scenario, providerVersion, 10, useSsl); + return new MockIdentifier(GetIdentityUrl(scenario, providerVersion, useSsl), mockRequest, new ServiceEndpoint[] { se }); + } + internal static ServiceEndpoint GetServiceEndpoint(Scenarios scenario, ProtocolVersion providerVersion, int servicePriority, bool useSsl) { + return ServiceEndpoint.CreateForClaimedIdentifier( + GetIdentityUrl(scenario, providerVersion, useSsl), + GetDelegateUrl(scenario, useSsl), + GetFullUrl("/" + ProviderPage, null, useSsl), + new string[] { Protocol.Lookup(providerVersion).ClaimedIdentifierServiceTypeURI }, + servicePriority, + 10); + } + ////internal static MockIdentifier GetMockOPIdentifier(Scenarios scenario, UriIdentifier expectedClaimedId) { + //// return GetMockOPIdentifier(scenario, expectedClaimedId, false, false); + ////} + ////internal static MockIdentifier GetMockOPIdentifier(Scenarios scenario, UriIdentifier expectedClaimedId, bool useSslOpIdentifier, bool useSslProviderEndpoint) { + //// var fields = new Dictionary<string, string> { + //// { "user", scenario.ToString() }, + ////}; + //// Uri opEndpoint = GetFullUrl(DirectedProviderEndpoint, fields, useSslProviderEndpoint); + //// Uri opIdentifier = GetOPIdentityUrl(scenario, useSslOpIdentifier); + //// ServiceEndpoint se = ServiceEndpoint.CreateForProviderIdentifier( + //// opIdentifier, + //// opEndpoint, + //// new string[] { Protocol.V20.OPIdentifierServiceTypeURI }, + //// 10, + //// 10); + + //// // Register the Claimed Identifier that directed identity will choose so that RP + //// // discovery on that identifier can be mocked up. + //// MockHttpRequest.RegisterMockXrdsResponse(expectedClaimedId, se); + + //// return new MockIdentifier(opIdentifier, new ServiceEndpoint[] { se }); + ////} + public static Uri GetFullUrl(string url) { + return GetFullUrl(url, null, false); + } + public static Uri GetFullUrl(string url, string key, object value) { + return GetFullUrl(url, new Dictionary<string, string> { + { key, value.ToString() }, + }, false); + } + public static Uri GetFullUrl(string url, IDictionary<string, string> args, bool useSsl) { + Uri defaultUriBase = new Uri(useSsl ? "https://localhost/" : "http://localhost/"); + Uri baseUri = UITestSupport.Host != null ? UITestSupport.Host.BaseUri : defaultUriBase; + UriBuilder builder = new UriBuilder(new Uri(baseUri, url)); + MessagingUtilities.AppendQueryArgs(builder, args); + return builder.Uri; + } + + /// <summary> + /// Returns the content of a given embedded resource. + /// </summary> + /// <param name="path">The path of the file as it appears within the project, + /// where the leading / marks the root directory of the project.</param> + internal static string LoadEmbeddedFile(string path) { + if (!path.StartsWith("/")) path = "/" + path; + path = "DotNetOpenAuth.Test.OpenId" + path.Replace('/', '.'); + Stream resource = Assembly.GetExecutingAssembly().GetManifestResourceStream(path); + if (resource == null) throw new ArgumentException(); + using (StreamReader sr = new StreamReader(resource)) { + return sr.ReadToEnd(); + } + } + + ////internal static IRelyingPartyApplicationStore RelyingPartyStore; + ////internal static IProviderAssociationStore ProviderStore; + /////// <summary> + /////// Generates a new, stateful <see cref="OpenIdRelyingParty"/> whose direct messages + /////// will be automatically handled by an internal <see cref="OpenIdProvider"/> + /////// that uses the shared <see cref="ProviderStore"/>. + /////// </summary> + ////internal static OpenIdRelyingParty CreateRelyingParty(NameValueCollection fields) { + //// return CreateRelyingParty(RelyingPartyStore, null, fields); + ////} + ////internal static OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store, NameValueCollection fields) { + //// return CreateRelyingParty(store, null, fields); + ////} + /////// <summary> + /////// Generates a new <see cref="OpenIdRelyingParty"/> whose direct messages + /////// will be automatically handled by an internal <see cref="OpenIdProvider"/> + /////// that uses the shared <see cref="ProviderStore"/>. + /////// </summary> + ////internal static OpenIdRelyingParty CreateRelyingParty(IRelyingPartyApplicationStore store, Uri requestUrl, NameValueCollection fields) { + //// var rp = new OpenIdRelyingParty(store, requestUrl ?? GetFullUrl(ConsumerPage), fields ?? new NameValueCollection()); + //// if (fields == null || fields.Count == 0) { + //// Assert.IsNull(rp.Response); + //// } + //// rp.DirectMessageChannel = new DirectMessageTestRedirector(ProviderStore); + //// return rp; + ////} + ////internal static DotNetOpenId.RelyingParty.IAuthenticationRequest CreateRelyingPartyRequest(bool stateless, Scenarios scenario, ProtocolVersion version, bool useSsl) { + //// // Publish RP discovery information + //// MockHttpRequest.RegisterMockRPDiscovery(); + + //// var rp = TestSupport.CreateRelyingParty(stateless ? null : RelyingPartyStore, null); + //// var rpReq = rp.CreateRequest(TestSupport.GetMockIdentifier(scenario, version, useSsl), Realm, ReturnTo); + + //// // Sidetrack: verify URLs and other default properties + //// { + //// Assert.AreEqual(AuthenticationRequestMode.Setup, rpReq.Mode); + //// Assert.AreEqual(Realm, rpReq.Realm); + //// Assert.AreEqual(ReturnTo, rpReq.ReturnToUrl); + //// } + + //// return rpReq; + ////} + /////// <summary> + /////// Generates a new <see cref="OpenIdRelyingParty"/> ready to process a + /////// response from an <see cref="OpenIdProvider"/>. + /////// </summary> + ////internal static IAuthenticationResponse CreateRelyingPartyResponse(IRelyingPartyApplicationStore store, IResponse providerResponse) { + //// return CreateRelyingPartyResponse(store, providerResponse, false); + ////} + ////internal static IAuthenticationResponse CreateRelyingPartyResponse(IRelyingPartyApplicationStore store, IResponse providerResponse, bool requireSsl) { + //// if (providerResponse == null) throw new ArgumentNullException("providerResponse"); + + //// var opAuthWebResponse = (Response)providerResponse; + //// var opAuthResponse = (EncodableResponse)opAuthWebResponse.EncodableMessage; + //// var rp = CreateRelyingParty(store, opAuthResponse.RedirectUrl, + //// opAuthResponse.EncodedFields.ToNameValueCollection()); + //// rp.Settings.RequireSsl = requireSsl; + //// // Get the response now, before trying the replay attack. The Response + //// // property is lazily-evaluated, so the replay attack can be evaluated first + //// // and pass, while this one that SUPPOSED to pass fails, if we don't force it now. + //// var response = rp.Response; + + //// // Side-track to test for replay attack while we're at it. + //// // This simulates a network sniffing user who caught the + //// // authenticating query en route to either the user agent or + //// // the consumer, and tries the same query to the consumer in an + //// // attempt to spoof the identity of the authenticating user. + //// try { + //// Logger.Info("Attempting replay attack..."); + //// var replayRP = CreateRelyingParty(store, opAuthResponse.RedirectUrl, + //// opAuthResponse.EncodedFields.ToNameValueCollection()); + //// replayRP.Settings.RequireSsl = requireSsl; + //// Assert.AreNotEqual(AuthenticationStatus.Authenticated, replayRP.Response.Status, "Replay attack succeeded!"); + //// } catch (OpenIdException) { // nonce already used + //// // another way to pass + //// } + + //// // Return the result of the initial response (not the replay attack one). + //// return response; + ////} + /////// <summary> + /////// Generates a new <see cref="OpenIdProvider"/> that uses the shared + /////// store in <see cref="ProviderStore"/>. + /////// </summary> + ////internal static OpenIdProvider CreateProvider(NameValueCollection fields) { + //// return CreateProvider(fields, false); + ////} + ////internal static OpenIdProvider CreateProvider(NameValueCollection fields, bool useSsl) { + //// Protocol protocol = fields != null ? Protocol.Detect(fields.ToDictionary()) : Protocol.V20; + //// Uri opEndpoint = GetFullUrl(ProviderPage, null, useSsl); + //// var provider = new OpenIdProvider(ProviderStore, opEndpoint, opEndpoint, fields ?? new NameValueCollection()); + //// return provider; + ////} + ////internal static OpenIdProvider CreateProviderForRequest(DotNetOpenId.RelyingParty.IAuthenticationRequest request) { + //// IResponse relyingPartyAuthenticationRequest = request.RedirectingResponse; + //// var rpWebMessageToOP = (Response)relyingPartyAuthenticationRequest; + //// var rpMessageToOP = (IndirectMessageRequest)rpWebMessageToOP.EncodableMessage; + //// var opEndpoint = (ServiceEndpoint)request.Provider; + //// var provider = new OpenIdProvider(ProviderStore, opEndpoint.ProviderEndpoint, + //// opEndpoint.ProviderEndpoint, rpMessageToOP.EncodedFields.ToNameValueCollection()); + //// return provider; + ////} + ////internal static IResponse CreateProviderResponseToRequest( + //// DotNetOpenId.RelyingParty.IAuthenticationRequest request, + //// Action<DotNetOpenId.Provider.IAuthenticationRequest> prepareProviderResponse) { + //// { + //// // Sidetrack: Verify the return_to and realm URLs + //// var consumerToProviderQuery = HttpUtility.ParseQueryString(request.RedirectingResponse.ExtractUrl().Query); + //// Protocol protocol = Protocol.Detect(consumerToProviderQuery.ToDictionary()); + //// Assert.IsTrue(consumerToProviderQuery[protocol.openid.return_to].StartsWith(request.ReturnToUrl.AbsoluteUri, StringComparison.Ordinal)); + //// Assert.AreEqual(request.Realm.ToString(), consumerToProviderQuery[protocol.openid.Realm]); + //// } + + //// var op = TestSupport.CreateProviderForRequest(request); + //// var opReq = (DotNetOpenId.Provider.IAuthenticationRequest)op.Request; + //// prepareProviderResponse(opReq); + //// Assert.IsTrue(opReq.IsResponseReady); + //// return opReq.Response; + ////} + ////internal static IAuthenticationResponse CreateRelyingPartyResponseThroughProvider( + //// DotNetOpenId.RelyingParty.IAuthenticationRequest request, + //// Action<DotNetOpenId.Provider.IAuthenticationRequest> providerAction) { + //// var rpReq = (AuthenticationRequest)request; + //// var opResponse = CreateProviderResponseToRequest(rpReq, providerAction); + //// // Be careful to use whatever store the original RP was using. + //// var rp = CreateRelyingPartyResponse(rpReq.RelyingParty.Store, opResponse, + //// ((AuthenticationRequest)request).RelyingParty.Settings.RequireSsl); + //// Assert.IsNotNull(rp); + //// return rp; + ////} + + ////[SetUp] + ////public void SetUp() { + //// log4net.Config.XmlConfigurator.Configure(Assembly.GetExecutingAssembly().GetManifestResourceStream("DotNetOpenId.Test.Logging.config")); + + //// ResetStores(); + ////} + + ////[TearDown] + ////public void TearDown() { + //// log4net.LogManager.Shutdown(); + ////} + + ////internal static void ResetStores() { + //// RelyingPartyStore = new ApplicationMemoryStore(); + //// ProviderStore = new ProviderMemoryStore(); + ////} + + ////internal static void SetAuthenticationFromScenario(Scenarios scenario, DotNetOpenId.Provider.IAuthenticationRequest request) { + //// Assert.IsTrue(request.IsReturnUrlDiscoverable); + //// switch (scenario) { + //// case TestSupport.Scenarios.ExtensionFullCooperation: + //// case TestSupport.Scenarios.ExtensionPartialCooperation: + //// case TestSupport.Scenarios.AutoApproval: + //// // immediately approve + //// request.IsAuthenticated = true; + //// break; + //// case TestSupport.Scenarios.AutoApprovalAddFragment: + //// request.SetClaimedIdentifierFragment("frag"); + //// request.IsAuthenticated = true; + //// break; + //// case TestSupport.Scenarios.ApproveOnSetup: + //// request.IsAuthenticated = !request.Immediate; + //// break; + //// case TestSupport.Scenarios.AlwaysDeny: + //// request.IsAuthenticated = false; + //// break; + //// default: + //// throw new InvalidOperationException("Unrecognized scenario"); + //// } + ////} + + /////// <summary> + /////// Uses an RPs stored association to resign an altered message from a Provider, + /////// to simulate a Provider that deliberately sent a bad message in an attempt + /////// to thwart RP security. + /////// </summary> + ////internal static void Resign(NameValueCollection nvc, IRelyingPartyApplicationStore store) { + //// Debug.Assert(nvc != null); + //// Debug.Assert(store != null); + //// var dict = Util.NameValueCollectionToDictionary(nvc); + //// Protocol protocol = Protocol.Detect(dict); + //// Uri providerEndpoint = new Uri(nvc[protocol.openid.op_endpoint]); + //// string assoc_handle = nvc[protocol.openid.assoc_handle]; + //// Association assoc = store.GetAssociation(providerEndpoint, assoc_handle); + //// Debug.Assert(assoc != null, "Association not found in RP's store. Maybe you're communicating with a hosted OP instead of the TestSupport one?"); + //// IList<string> signed = nvc[protocol.openid.signed].Split(','); + //// var subsetDictionary = new Dictionary<string, string>(); + //// foreach (string signedKey in signed) { + //// string keyName = protocol.openid.Prefix + signedKey; + //// subsetDictionary.Add(signedKey, dict[keyName]); + //// } + //// nvc[protocol.openid.sig] = Convert.ToBase64String(assoc.Sign(subsetDictionary, signed)); + ////} + + ////public static IAssociationStore<AssociationRelyingPartyType> ProviderStoreContext { + //// get { + //// return DotNetOpenId.Provider.OpenIdProvider.HttpApplicationStore; + //// } + ////} + } +}
\ No newline at end of file diff --git a/src/DotNetOpenAuth.Test/OpenId/UI/UITestSupport.cs b/src/DotNetOpenAuth.Test/OpenId/UI/UITestSupport.cs new file mode 100644 index 0000000..da18fa9 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/UI/UITestSupport.cs @@ -0,0 +1,21 @@ +namespace DotNetOAuth.Test.OpenId.UI { + using DotNetOpenAuth.Test.Hosting; + + ////[SetUpFixture] + public class UITestSupport { + internal static AspNetHost Host { get; private set; } + + ////[SetUp] + ////public void SetUp() { + //// Host = AspNetHost.CreateHost(TestSupport.TestWebDirectory); + ////} + + ////[TearDown] + ////public void TearDown() { + //// if (Host != null) { + //// Host.CloseHttp(); + //// Host = null; + //// } + ////} + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs index e6e9149..da2c6cc 100644 --- a/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/UriIdentifierTests.cs @@ -1,13 +1,20 @@ -using System; -using System.Linq; -using System.Net; -using System.Web; -using DotNetOpenAuth.OpenId.RelyingParty; -using DotNetOpenAuth.Test.Mocks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using DotNetOpenAuth.OpenId; +//----------------------------------------------------------------------- +// <copyright file="UriIdentifierTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- namespace DotNetOpenAuth.Test.OpenId { + using System; + using System.Linq; + using System.Net; + using System.Web; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Test.Mocks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using DotNetOpenAuth.OpenId; +using DotNetOpenAuth.Messaging; + [TestClass] public class UriIdentifierTests : OpenIdTestBase { string goodUri = "http://blog.nerdbank.net/"; @@ -16,13 +23,7 @@ namespace DotNetOpenAuth.Test.OpenId { [TestInitialize] public override void SetUp() { - if (!UntrustedWebRequest.WhitelistHosts.Contains("localhost")) - UntrustedWebRequest.WhitelistHosts.Add("localhost"); - } - - [TestCleanup] - public override void Cleanup() { - Mocks.MockHttpRequest.Reset(); + base.SetUp(); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] @@ -35,7 +36,7 @@ namespace DotNetOpenAuth.Test.OpenId { new UriIdentifier((string)null); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void CtorBlank() { new UriIdentifier(string.Empty); } @@ -126,7 +127,7 @@ namespace DotNetOpenAuth.Test.OpenId { Assert.AreEqual(new UriIdentifier(goodUri), new UriIdentifier(goodUri + "#frag")); Assert.AreNotEqual(new UriIdentifier(goodUri), new UriIdentifier(goodUri + "a")); Assert.AreNotEqual(null, new UriIdentifier(goodUri)); - Assert.AreNotEqual(goodUri, new UriIdentifier(goodUri)); + Assert.AreEqual(goodUri, new UriIdentifier(goodUri)); } [TestMethod] @@ -158,17 +159,19 @@ namespace DotNetOpenAuth.Test.OpenId { } else { throw new InvalidOperationException(); } - MockHttpRequest.RegisterMockResponse(new Uri(idToDiscover), claimedId, contentType, + this.mockResponder.RegisterMockResponse(new Uri(idToDiscover), claimedId, contentType, headers ?? new WebHeaderCollection(), TestSupport.LoadEmbeddedFile(url)); - ServiceEndpoint se = idToDiscover.Discover().FirstOrDefault(); + ServiceEndpoint se = idToDiscover.Discover(this.requestHandler).FirstOrDefault(); Assert.IsNotNull(se, url + " failed to be discovered."); Assert.AreSame(protocol, se.Protocol); Assert.AreEqual(claimedId, se.ClaimedIdentifier); Assert.AreEqual(expectedLocalId, se.ProviderLocalIdentifier); Assert.AreEqual(expectSreg ? 2 : 1, se.ProviderSupportedServiceTypeUris.Length); Assert.IsTrue(Array.IndexOf(se.ProviderSupportedServiceTypeUris, protocol.ClaimedIdentifierServiceTypeURI) >= 0); - Assert.AreEqual(expectSreg, se.IsExtensionSupported(new ClaimsRequest())); + + // TODO: re-enable this line once extensions support is added back in. + ////Assert.AreEqual(expectSreg, se.IsExtensionSupported(new ClaimsRequest())); } void discoverXrds(string page, ProtocolVersion version, Identifier expectedLocalId) { discoverXrds(page, version, expectedLocalId, null); @@ -189,10 +192,10 @@ namespace DotNetOpenAuth.Test.OpenId { void failDiscover(string url) { UriIdentifier userSuppliedId = TestSupport.GetFullUrl(url); - Mocks.MockHttpRequest.RegisterMockResponse(new Uri(userSuppliedId), userSuppliedId, "text/html", + this.mockResponder.RegisterMockResponse(new Uri(userSuppliedId), userSuppliedId, "text/html", TestSupport.LoadEmbeddedFile(url)); - Assert.AreEqual(0, userSuppliedId.Discover().Count()); // ... but that no endpoint info is discoverable + Assert.AreEqual(0, userSuppliedId.Discover(this.requestHandler).Count()); // ... but that no endpoint info is discoverable } void failDiscoverHtml(string scenario) { failDiscover("/Discovery/htmldiscovery/" + scenario + ".html"); @@ -220,7 +223,7 @@ namespace DotNetOpenAuth.Test.OpenId { } [TestMethod] public void XrdsDiscoveryFromHead() { - Mocks.MockHttpRequest.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"), + this.mockResponder.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"), "application/xrds+xml", TestSupport.LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds1020.xml")); discoverXrds("XrdsReferencedInHead.html", ProtocolVersion.V10, null); } @@ -228,7 +231,7 @@ namespace DotNetOpenAuth.Test.OpenId { public void XrdsDiscoveryFromHttpHeader() { WebHeaderCollection headers = new WebHeaderCollection(); headers.Add("X-XRDS-Location", TestSupport.GetFullUrl("http://localhost/xrds1020.xml").AbsoluteUri); - Mocks.MockHttpRequest.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"), + this.mockResponder.RegisterMockResponse(new Uri("http://localhost/xrds1020.xml"), "application/xrds+xml", TestSupport.LoadEmbeddedFile("/Discovery/xrdsdiscovery/xrds1020.xml")); discoverXrds("XrdsReferencedInHttpHeader.html", ProtocolVersion.V10, null, headers); } @@ -272,18 +275,17 @@ namespace DotNetOpenAuth.Test.OpenId { [TestMethod] public void DiscoveryWithRedirects() { - MockHttpRequest.Reset(); - Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20); + Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, this.mockResponder, ProtocolVersion.V20); // Add a couple of chained redirect pages that lead to the claimedId. Uri userSuppliedUri = TestSupport.GetFullUrl("/someSecurePage", null, true); Uri insecureMidpointUri = TestSupport.GetFullUrl("/insecureStop"); - MockHttpRequest.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); - MockHttpRequest.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); + this.mockResponder.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); + this.mockResponder.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); // don't require secure SSL discovery for this test. Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, false); - Assert.AreEqual(1, userSuppliedIdentifier.Discover().Count()); + Assert.AreEqual(1, userSuppliedIdentifier.Discover(this.requestHandler).Count()); } [TestMethod] @@ -307,79 +309,78 @@ namespace DotNetOpenAuth.Test.OpenId { Assert.IsFalse(id.TryRequireSsl(out secureId)); Assert.IsFalse(secureId.IsDiscoverySecureEndToEnd); Assert.AreEqual("http://www.yahoo.com/", secureId.ToString()); - Assert.AreEqual(0, secureId.Discover().Count()); + Assert.AreEqual(0, secureId.Discover(this.requestHandler).Count()); id = new UriIdentifier("http://www.yahoo.com"); Assert.IsFalse(id.TryRequireSsl(out secureId)); Assert.IsFalse(secureId.IsDiscoverySecureEndToEnd); Assert.AreEqual("http://www.yahoo.com/", secureId.ToString()); - Assert.AreEqual(0, secureId.Discover().Count()); + Assert.AreEqual(0, secureId.Discover(this.requestHandler).Count()); } [TestMethod] public void DiscoverRequireSslWithSecureRedirects() { - MockHttpRequest.Reset(); - Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, true); + this.mockResponder.Reset(); + Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, this.mockResponder, ProtocolVersion.V20, true); // Add a couple of chained redirect pages that lead to the claimedId. // All redirects should be secure. Uri userSuppliedUri = TestSupport.GetFullUrl("/someSecurePage", null, true); Uri secureMidpointUri = TestSupport.GetFullUrl("/secureStop", null, true); - MockHttpRequest.RegisterMockRedirect(userSuppliedUri, secureMidpointUri); - MockHttpRequest.RegisterMockRedirect(secureMidpointUri, new Uri(claimedId.ToString())); + this.mockResponder.RegisterMockRedirect(userSuppliedUri, secureMidpointUri); + this.mockResponder.RegisterMockRedirect(secureMidpointUri, new Uri(claimedId.ToString())); Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true); - Assert.AreEqual(1, userSuppliedIdentifier.Discover().Count()); + Assert.AreEqual(1, userSuppliedIdentifier.Discover(this.requestHandler).Count()); } - [TestMethod, ExpectedException(typeof(OpenIdException))] + [TestMethod, ExpectedException(typeof(ProtocolException))] public void DiscoverRequireSslWithInsecureRedirect() { - MockHttpRequest.Reset(); - Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, true); + Identifier claimedId = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, this.mockResponder, ProtocolVersion.V20, true); // Add a couple of chained redirect pages that lead to the claimedId. // Include an insecure HTTP jump in those redirects to verify that // the ultimate endpoint is never found as a result of high security profile. Uri userSuppliedUri = TestSupport.GetFullUrl("/someSecurePage", null, true); Uri insecureMidpointUri = TestSupport.GetFullUrl("/insecureStop"); - MockHttpRequest.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); - MockHttpRequest.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); + this.mockResponder.RegisterMockRedirect(userSuppliedUri, insecureMidpointUri); + this.mockResponder.RegisterMockRedirect(insecureMidpointUri, new Uri(claimedId.ToString())); Identifier userSuppliedIdentifier = new UriIdentifier(userSuppliedUri, true); - userSuppliedIdentifier.Discover(); + userSuppliedIdentifier.Discover(this.requestHandler); } [TestMethod] public void DiscoveryRequireSslWithInsecureXrdsInSecureHtmlHead() { - var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false); + var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, this.mockResponder, ProtocolVersion.V20, false); Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true); string html = string.Format("<html><head><meta http-equiv='X-XRDS-Location' content='{0}'/></head><body></body></html>", insecureXrdsSource); - MockHttpRequest.RegisterMockResponse(secureClaimedUri, "text/html", html); + this.mockResponder.RegisterMockResponse(secureClaimedUri, "text/html", html); Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true); - Assert.AreEqual(0, userSuppliedIdentifier.Discover().Count()); + Assert.AreEqual(0, userSuppliedIdentifier.Discover(this.requestHandler).Count()); } [TestMethod] public void DiscoveryRequireSslWithInsecureXrdsInSecureHttpHeader() { - var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false); + var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, this.mockResponder, ProtocolVersion.V20, false); Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true); string html = "<html><head></head><body></body></html>"; WebHeaderCollection headers = new WebHeaderCollection { { "X-XRDS-Location", insecureXrdsSource } }; - MockHttpRequest.RegisterMockResponse(secureClaimedUri, secureClaimedUri, "text/html", headers, html); + this.mockResponder.RegisterMockResponse(secureClaimedUri, secureClaimedUri, "text/html", headers, html); Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true); - Assert.AreEqual(0, userSuppliedIdentifier.Discover().Count()); + Assert.AreEqual(0, userSuppliedIdentifier.Discover(this.requestHandler).Count()); } [TestMethod] public void DiscoveryRequireSslWithInsecureXrdsButSecureLinkTags() { - var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, false); + var insecureXrdsSource = TestSupport.GetMockIdentifier(TestSupport.Scenarios.AutoApproval, this.mockResponder, ProtocolVersion.V20, false); Uri secureClaimedUri = TestSupport.GetFullUrl("/secureId", null, true); Identifier localIdForLinkTag = TestSupport.GetDelegateUrl(TestSupport.Scenarios.AlwaysDeny, true); @@ -392,10 +393,10 @@ namespace DotNetOpenAuth.Test.OpenId { HttpUtility.HtmlEncode(insecureXrdsSource), HttpUtility.HtmlEncode(TestSupport.GetFullUrl("/" + TestSupport.ProviderPage, null, true).AbsoluteUri), HttpUtility.HtmlEncode(localIdForLinkTag.ToString())); - MockHttpRequest.RegisterMockResponse(secureClaimedUri, "text/html", html); + this.mockResponder.RegisterMockResponse(secureClaimedUri, "text/html", html); Identifier userSuppliedIdentifier = new UriIdentifier(secureClaimedUri, true); - Assert.AreEqual(localIdForLinkTag, userSuppliedIdentifier.Discover().Single().ProviderLocalIdentifier); + Assert.AreEqual(localIdForLinkTag, userSuppliedIdentifier.Discover(this.requestHandler).Single().ProviderLocalIdentifier); } [TestMethod] @@ -403,8 +404,8 @@ namespace DotNetOpenAuth.Test.OpenId { var insecureEndpoint = TestSupport.GetServiceEndpoint(TestSupport.Scenarios.AutoApproval, ProtocolVersion.V20, 10, false); var secureEndpoint = TestSupport.GetServiceEndpoint(TestSupport.Scenarios.ApproveOnSetup, ProtocolVersion.V20, 20, true); UriIdentifier secureClaimedId = new UriIdentifier(TestSupport.GetFullUrl("/claimedId", null, true), true); - MockHttpRequest.RegisterMockXrdsResponse(secureClaimedId, new ServiceEndpoint[] { insecureEndpoint, secureEndpoint }); - Assert.AreEqual(secureEndpoint.ProviderLocalIdentifier, secureClaimedId.Discover().Single().ProviderLocalIdentifier); + this.mockResponder.RegisterMockXrdsResponse(secureClaimedId, new ServiceEndpoint[] { insecureEndpoint, secureEndpoint }); + Assert.AreEqual(secureEndpoint.ProviderLocalIdentifier, secureClaimedId.Discover(this.requestHandler).Single().ProviderLocalIdentifier); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs index 1edc086..5f85f28 100644 --- a/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/XriIdentifierTests.cs @@ -1,20 +1,21 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using DotNetOpenAuth.OpenId.RelyingParty; -using DotNetOpenAuth.Test.Mocks; -using Microsoft.VisualStudio.TestTools.UnitTesting; -using DotNetOpenAuth.OpenId; +namespace DotNetOpenAuth.Test.OpenId { + using System; + using System.Collections.Generic; + using System.Linq; + using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Test.Mocks; + using Microsoft.VisualStudio.TestTools.UnitTesting; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.Messaging; -namespace DotNetOpenAuth.Test.OpenId { [TestClass] public class XriIdentifierTests : OpenIdTestBase { string goodXri = "=Andrew*Arnott"; string badXri = "some\\wacky%^&*()non-XRI"; - [TestCleanup] - public void TearDown() { - MockHttpRequest.Reset(); + [TestInitialize] + public override void SetUp() { + base.SetUp(); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] @@ -22,7 +23,7 @@ namespace DotNetOpenAuth.Test.OpenId { new XriIdentifier(null); } - [TestMethod, ExpectedException(typeof(ArgumentNullException))] + [TestMethod, ExpectedException(typeof(ArgumentException))] public void CtorBlank() { new XriIdentifier(string.Empty); } @@ -80,12 +81,11 @@ namespace DotNetOpenAuth.Test.OpenId { Assert.AreEqual(new XriIdentifier(goodXri), new XriIdentifier(goodXri)); Assert.AreNotEqual(new XriIdentifier(goodXri), new XriIdentifier(goodXri + "a")); Assert.AreNotEqual(null, new XriIdentifier(goodXri)); - Assert.AreNotEqual(goodXri, new XriIdentifier(goodXri)); + Assert.AreEqual(goodXri, new XriIdentifier(goodXri)); } -#if DISCOVERY // TODO: Add discovery and then re-enable this code block private ServiceEndpoint verifyCanonicalId(Identifier iname, string expectedClaimedIdentifier) { - ServiceEndpoint se = iname.Discover().FirstOrDefault(); + ServiceEndpoint se = iname.Discover(this.requestHandler).FirstOrDefault(); if (expectedClaimedIdentifier != null) { Assert.IsNotNull(se); Assert.AreEqual(expectedClaimedIdentifier, se.ClaimedIdentifier.ToString(), "i-name {0} discovery resulted in unexpected CanonicalId", iname); @@ -95,7 +95,6 @@ namespace DotNetOpenAuth.Test.OpenId { } return se; } -#endif [TestMethod] public void Discover() { @@ -134,7 +133,7 @@ namespace DotNetOpenAuth.Test.OpenId { { "https://xri.net/=Arnott?_xrd_r=application/xrd%2Bxml;sep=false", xrds }, { "https://xri.net/=!9B72.7DD1.50A9.5CCD?_xrd_r=application/xrd%2Bxml;sep=false", xrds }, }; - MockHttpRequest.RegisterMockXrdsResponses(mocks); + this.mockResponder.RegisterMockXrdsResponses(mocks); string expectedCanonicalId = "=!9B72.7DD1.50A9.5CCD"; ServiceEndpoint se = verifyCanonicalId("=Arnott", expectedCanonicalId); @@ -360,7 +359,7 @@ uEyb50RJ7DWmXctSC0b3eymZ2lSXxAWNOsNy </X509Data> </KeyInfo> </XRD>"; - MockHttpRequest.RegisterMockXrdsResponses(new Dictionary<string, string> { + this.mockResponder.RegisterMockXrdsResponses(new Dictionary<string, string> { { "https://xri.net/@llli?_xrd_r=application/xrd%2Bxml;sep=false", llliResponse }, { "https://xri.net/@llli*area?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaResponse }, { "https://xri.net/@llli*area*canada.unattached?_xrd_r=application/xrd%2Bxml;sep=false", llliAreaCanadaUnattachedResponse }, @@ -376,7 +375,7 @@ uEyb50RJ7DWmXctSC0b3eymZ2lSXxAWNOsNy [TestMethod] public void DiscoveryCommunityInameDelegateWithoutCanonicalID() { - MockHttpRequest.RegisterMockXrdsResponses(new Dictionary<string, string> { + this.mockResponder.RegisterMockXrdsResponses(new Dictionary<string, string> { { "https://xri.net/=Web*andrew.arnott?_xrd_r=application/xrd%2Bxml;sep=false", @"<?xml version='1.0' encoding='UTF-8'?> <XRD xmlns='xri://$xrd*($v*2.0)'> <Query>*andrew.arnott</Query> diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index d6ccb7b..9ee0b25 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -78,6 +78,7 @@ <Compile Include="Messaging\EmptyEnumerator.cs" /> <Compile Include="Messaging\EmptyList.cs" /> <Compile Include="Messaging\ErrorUtilities.cs" /> + <Compile Include="Messaging\IDirectSslWebRequestHandler.cs" /> <Compile Include="Messaging\InternalErrorException.cs" /> <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> <Compile Include="OAuth\ChannelElements\OAuthConsumerMessageFactory.cs" /> diff --git a/src/DotNetOpenAuth/Messaging/IDirectSslWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/IDirectSslWebRequestHandler.cs new file mode 100644 index 0000000..084d6d2 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/IDirectSslWebRequestHandler.cs @@ -0,0 +1,39 @@ +//----------------------------------------------------------------------- +// <copyright file="IDirectSslWebRequestHandler.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using System.IO; + using System.Net; + + /// <summary> + /// A contract for <see cref="HttpWebRequest"/> handling, + /// with added support for SSL-only requests. + /// </summary> + public interface IDirectSslWebRequestHandler : IDirectWebRequestHandler { + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <param name="requireSsl">if set to <c>true</c> all requests made with this instance must be completed using SSL.</param> + /// <returns> + /// The writer the caller should write out the entity data to. + /// </returns> + TextWriter GetRequestStream(HttpWebRequest request, bool requireSsl); + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="DirectWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <param name="requireSsl">if set to <c>true</c> all requests made with this instance must be completed using SSL.</param> + /// <returns>An instance of <see cref="DirectWebResponse"/> describing the response.</returns> + DirectWebResponse GetResponse(HttpWebRequest request, bool requireSsl); + } +} diff --git a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs index bf2acfb..51513db 100644 --- a/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/IDirectWebRequestHandler.cs @@ -12,12 +12,17 @@ namespace DotNetOpenAuth.Messaging { /// <summary> /// A contract for <see cref="HttpWebRequest"/> handling. /// </summary> + /// <remarks> + /// Implementations of this interface must be thread safe. + /// </remarks> public interface IDirectWebRequestHandler { /// <summary> /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. /// </summary> /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> - /// <returns>The writer the caller should write out the entity data to.</returns> + /// <returns> + /// The writer the caller should write out the entity data to. + /// </returns> TextWriter GetRequestStream(HttpWebRequest request); /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs index d173541..4cc8844 100644 --- a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs @@ -34,7 +34,7 @@ namespace DotNetOpenAuth.Messaging { /// 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 : IDirectWebRequestHandler { + public class UntrustedWebRequestHandler : IDirectSslWebRequestHandler { /// <summary> /// Gets or sets the default cache policy to use for HTTP requests. /// </summary> @@ -63,17 +63,9 @@ namespace DotNetOpenAuth.Messaging { private int maximumRedirections = Configuration.MaximumRedirections; /// <summary> - /// Backing store for the <see cref="RequireSsl"/> property. - /// </summary> - private readonly bool requireSsl; - - /// <summary> /// Initializes a new instance of the <see cref="UntrustedWebRequestHandler"/> class. /// </summary> - /// <param name="requireSsl">if set to <c>true</c> all requests made with this instance must be completed using SSL.</param> - public UntrustedWebRequestHandler(bool requireSsl) { - this.requireSsl = requireSsl; - + public UntrustedWebRequestHandler() { this.ReadWriteTimeout = Configuration.ReadWriteTimeout; this.Timeout = Configuration.Timeout; #if LONGTIMEOUT @@ -83,18 +75,6 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Gets a value indicating whether all requests (and redirects) will be required - /// to use SSL encryption for the request to be completed successfully. - /// </summary> - /// <remarks> - /// Many policies in this class can be configured after the class is instantiated. - /// But requiring SSL is an immutable setting and can only be set in the constructor. - /// </remarks> - public bool RequireSsl { - get { return this.requireSsl; } - } - - /// <summary> /// Gets or sets the total number of redirections to allow on any one request. /// Default is 10. /// </summary> @@ -144,18 +124,19 @@ namespace DotNetOpenAuth.Messaging { /// </summary> public ICollection<Regex> BlacklistHostsRegex { get { return blacklistHostsRegex; } } - #region IWebRequestHandler Members + #region IDirectUntrustedWebRequestHandler Members /// <summary> /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. /// </summary> /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <param name="requireSsl">if set to <c>true</c> all requests made with this instance must be completed using SSL.</param> /// <returns> /// The writer the caller should write out the entity data to. /// </returns> - public TextWriter GetRequestStream(HttpWebRequest request) { + public TextWriter GetRequestStream(HttpWebRequest request, bool requireSsl) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); - this.EnsureAllowableRequestUri(request.RequestUri); + this.EnsureAllowableRequestUri(request.RequestUri, requireSsl); this.PrepareRequest(request); @@ -172,9 +153,18 @@ namespace DotNetOpenAuth.Messaging { } } - public DirectWebResponse GetResponse(HttpWebRequest request) { + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="DirectWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <param name="requireSsl">if set to <c>true</c> all requests made with this instance must be completed using SSL.</param> + /// <returns> + /// An instance of <see cref="DirectWebResponse"/> describing the response. + /// </returns> + public DirectWebResponse GetResponse(HttpWebRequest request, bool requireSsl) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); - this.EnsureAllowableRequestUri(request.RequestUri); + this.EnsureAllowableRequestUri(request.RequestUri, requireSsl); // This request MAY have already been prepared by GetRequestStream, but // we have no guarantee, so do it just to be safe. @@ -186,7 +176,32 @@ namespace DotNetOpenAuth.Messaging { #endregion - internal DirectWebResponse RequestWithManagedRedirects(HttpWebRequest request) { + #region IDirectWebRequestHandler Members + + /// <summary> + /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param> + /// <returns> + /// The writer the caller should write out the entity data to. + /// </returns> + TextWriter IDirectWebRequestHandler.GetRequestStream(HttpWebRequest request) { + return this.GetRequestStream(request, false); + } + + /// <summary> + /// Processes an <see cref="HttpWebRequest"/> and converts the + /// <see cref="HttpWebResponse"/> to a <see cref="DirectWebResponse"/> instance. + /// </summary> + /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param> + /// <returns>An instance of <see cref="DirectWebResponse"/> describing the response.</returns> + DirectWebResponse IDirectWebRequestHandler.GetResponse(HttpWebRequest request) { + return this.GetResponse(request, false); + } + + #endregion + + internal DirectWebResponse RequestWithManagedRedirects(HttpWebRequest request, bool requireSsl) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); // Since we may require SSL for every redirect, we handle each redirect manually @@ -196,7 +211,7 @@ namespace DotNetOpenAuth.Messaging { Uri originalRequestUri = request.RequestUri; int i; for (i = 0; i < MaximumRedirections; i++) { - DirectWebResponse response = this.RequestCore(request, null, originalRequestUri); + DirectWebResponse response = this.RequestCore(request, null, originalRequestUri, requireSsl); if (response.Status == HttpStatusCode.MovedPermanently || response.Status == HttpStatusCode.Redirect || response.Status == HttpStatusCode.RedirectMethod || @@ -281,9 +296,10 @@ namespace DotNetOpenAuth.Messaging { /// Verify that the request qualifies under our security policies /// </summary> /// <param name="requestUri">The request URI.</param> - private void EnsureAllowableRequestUri(Uri requestUri) { + private void EnsureAllowableRequestUri(Uri requestUri, bool requireSsl) { ErrorUtilities.VerifyArgument(this.isUriAllowable(requestUri), MessagingStrings.UnsafeWebRequestDetected, requestUri); - ErrorUtilities.VerifyProtocol(!this.RequireSsl || String.Equals(requestUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase), MessagingStrings.InsecureWebRequestWithSslRequired, requestUri); + + ErrorUtilities.VerifyProtocol(!requireSsl || String.Equals(requestUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase), MessagingStrings.InsecureWebRequestWithSslRequired, requestUri); } private bool isUriAllowable(Uri uri) { @@ -359,10 +375,10 @@ namespace DotNetOpenAuth.Messaging { return request; } - private DirectWebResponse RequestCore(HttpWebRequest request, Stream postEntity, Uri originalRequestUri) { + private DirectWebResponse RequestCore(HttpWebRequest request, Stream postEntity, Uri originalRequestUri, bool requireSsl) { ErrorUtilities.VerifyArgumentNotNull(request, "request"); ErrorUtilities.VerifyArgumentNotNull(originalRequestUri, "originalRequestUri"); - EnsureAllowableRequestUri(request.RequestUri); + EnsureAllowableRequestUri(request.RequestUri, requireSsl); int postEntityLength = 0; try { @@ -391,7 +407,7 @@ namespace DotNetOpenAuth.Messaging { request.ServicePoint.Expect100Continue = false; // TODO: investigate that CAS may throw here, and we can use request.Expect instead. postEntity.Seek(-postEntityLength, SeekOrigin.Current); request = CloneRequestWithNewUrl(request, request.RequestUri); - return RequestCore(request, postEntity, originalRequestUri); + return RequestCore(request, postEntity, originalRequestUri, requireSsl); } } return new DirectWebResponse(originalRequestUri, response, MaximumBytesToRead); diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs index 26108af..de20e6e 100644 --- a/src/DotNetOpenAuth/OpenId/Identifier.cs +++ b/src/DotNetOpenAuth/OpenId/Identifier.cs @@ -127,10 +127,11 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// Performs discovery on the Identifier. /// </summary> + /// <param name="requestHandler">The web request handler to use for discovery.</param> /// <returns> /// An initialized structure containing the discovered provider endpoint information. /// </returns> - internal abstract IEnumerable<ServiceEndpoint> Discover(); + internal abstract IEnumerable<ServiceEndpoint> Discover(IDirectSslWebRequestHandler requestHandler); /// <summary> /// Tests equality between two <see cref="Identifier"/>s. diff --git a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs index b563324..126a59c 100644 --- a/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/NoDiscoveryIdentifier.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId { using System.Collections.Generic; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; + using System.Linq; /// <summary> /// Wraps an existing Identifier and prevents it from performing discovery. @@ -30,8 +31,8 @@ namespace DotNetOpenAuth.OpenId { this.wrappedIdentifier = wrappedIdentifier; } - internal override IEnumerable<ServiceEndpoint> Discover() { - return new ServiceEndpoint[0]; + internal override IEnumerable<ServiceEndpoint> Discover(IDirectSslWebRequestHandler requestHandler) { + return Enumerable.Empty<ServiceEndpoint>(); } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs index 0bdaa33..5b1b10a 100644 --- a/src/DotNetOpenAuth/OpenId/Realm.cs +++ b/src/DotNetOpenAuth/OpenId/Realm.cs @@ -358,9 +358,9 @@ namespace DotNetOpenAuth.OpenId { /// false when performing return URL verification per 2.0 spec section 9.2.1. /// </param> /// <returns>The details of the endpoints if found, otherwise null.</returns> - internal IEnumerable<RelyingPartyEndpointDescription> Discover(bool allowRedirects) { + internal IEnumerable<RelyingPartyEndpointDescription> Discover(IDirectSslWebRequestHandler requestHandler, bool allowRedirects) { // Attempt YADIS discovery - DiscoveryResult yadisResult = Yadis.Discover(UriWithWildcardChangedToWww, false); + DiscoveryResult yadisResult = Yadis.Discover(requestHandler, UriWithWildcardChangedToWww, false); if (yadisResult != null) { // Detect disallowed redirects, since realm discovery never allows them for security. ErrorUtilities.VerifyProtocol(allowRedirects || yadisResult.NormalizedUri == yadisResult.RequestUri, OpenIdStrings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index e00828a..2f0fb16 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -21,6 +21,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private RelyingPartySecuritySettings securitySettings; /// <summary> + /// The untrusted web request handler we use (and share) by default across all RP instances. + /// </summary> + private static IDirectSslWebRequestHandler DefaultUntrustedWebRequestHandler = new UntrustedWebRequestHandler(); + + /// <summary> /// Initializes a new instance of the <see cref="OpenIdRelyingParty"/> class. /// </summary> /// <param name="associationStore">The association store. If null, the relying party will always operate in "dumb mode".</param> @@ -30,6 +35,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { this.Channel = new OpenIdChannel(); this.AssociationStore = associationStore; this.SecuritySettings = RelyingPartySection.Configuration.SecuritySettings.CreateSecuritySettings(); + this.WebRequestHandler = DefaultUntrustedWebRequestHandler; } /// <summary> @@ -60,6 +66,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { internal IAssociationStore<Uri> AssociationStore { get; private set; } /// <summary> + /// Gets the web request handler to use for discovery and the part of + /// authentication where direct messages are sent to an untrusted remote party. + /// </summary> + internal IDirectSslWebRequestHandler WebRequestHandler { get; private set; } + + /// <summary> /// Gets an association between this Relying Party and a given Provider. /// A new association is created if necessary and possible. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs index 1618c95..6885fa5 100644 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs @@ -252,10 +252,10 @@ namespace DotNetOpenAuth.OpenId { providerEndpoint, typeURIs, (int?)null, (int?)null); } - internal override IEnumerable<ServiceEndpoint> Discover() { + internal override IEnumerable<ServiceEndpoint> Discover(IDirectSslWebRequestHandler requestHandler) { List<ServiceEndpoint> endpoints = new List<ServiceEndpoint>(); // Attempt YADIS discovery - DiscoveryResult yadisResult = Yadis.Discover(this, IsDiscoverySecureEndToEnd); + DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this, IsDiscoverySecureEndToEnd); if (yadisResult != null) { if (yadisResult.IsXrds) { XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs index 13c8cdf..a6a7fe8 100644 --- a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs @@ -149,23 +149,23 @@ namespace DotNetOpenAuth.OpenId { || xri.StartsWith(XriScheme, StringComparison.OrdinalIgnoreCase); } - private XrdsDocument downloadXrds() { - var xrdsResponse = Yadis.Request(this.XrdsUrl, this.IsDiscoverySecureEndToEnd); + private XrdsDocument downloadXrds(IDirectSslWebRequestHandler requestHandler) { + var xrdsResponse = Yadis.Request(requestHandler, this.XrdsUrl, this.IsDiscoverySecureEndToEnd); XrdsDocument doc = new XrdsDocument(XmlReader.Create(xrdsResponse.ResponseStream)); ErrorUtilities.VerifyProtocol(doc.IsXrdResolutionSuccessful, OpenIdStrings.XriResolutionFailed); return doc; } - internal override IEnumerable<ServiceEndpoint> Discover() { - return downloadXrds().CreateServiceEndpoints(this); + internal override IEnumerable<ServiceEndpoint> Discover(IDirectSslWebRequestHandler requestHandler) { + return downloadXrds(requestHandler).CreateServiceEndpoints(this); } /// <summary> /// Performs discovery on THIS identifier, but generates <see cref="ServiceEndpoint"/> /// instances that treat another given identifier as the user-supplied identifier. /// </summary> - internal IEnumerable<ServiceEndpoint> Discover(XriIdentifier userSuppliedIdentifier) { - return downloadXrds().CreateServiceEndpoints(userSuppliedIdentifier); + internal IEnumerable<ServiceEndpoint> Discover(IDirectSslWebRequestHandler requestHandler, XriIdentifier userSuppliedIdentifier) { + return downloadXrds(requestHandler).CreateServiceEndpoints(userSuppliedIdentifier); } /// <summary> diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs index 7765575..1bbbc73 100644 --- a/src/DotNetOpenAuth/Yadis/Yadis.cs +++ b/src/DotNetOpenAuth/Yadis/Yadis.cs @@ -19,15 +19,12 @@ namespace DotNetOpenAuth.Yadis { internal class Yadis { internal const string HeaderName = "X-XRDS-Location"; - private static readonly IDirectWebRequestHandler discoveryRequestHandlerSsl = new UntrustedWebRequestHandler(true); - private static readonly IDirectWebRequestHandler discoveryRequestHandler = new UntrustedWebRequestHandler(false); - /// <summary> /// Gets or sets the cache that can be used for HTTP requests made during identifier discovery. /// </summary> internal readonly static RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheIfAvailable); - internal static DirectWebResponse Request(Uri uri, bool requireSsl, params string[] acceptTypes) { + internal static DirectWebResponse Request(IDirectSslWebRequestHandler requestHandler, Uri uri, bool requireSsl, params string[] acceptTypes) { ErrorUtilities.VerifyArgumentNotNull(uri, "uri"); HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri); @@ -36,8 +33,7 @@ namespace DotNetOpenAuth.Yadis { request.Accept = string.Join(",", acceptTypes); } - IDirectWebRequestHandler handler = requireSsl ? discoveryRequestHandlerSsl : discoveryRequestHandler; - return handler.GetResponse(request); + return requestHandler.GetResponse(request, requireSsl); } /// <summary> @@ -51,14 +47,14 @@ namespace DotNetOpenAuth.Yadis { /// or if <paramref name="requireSsl"/> is true but part of discovery /// is not protected by SSL. /// </returns> - public static DiscoveryResult Discover(UriIdentifier uri, bool requireSsl) { + public static DiscoveryResult Discover(IDirectSslWebRequestHandler requestHandler, UriIdentifier uri, bool requireSsl) { DirectWebResponse response; try { if (requireSsl && !string.Equals(uri.Uri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { Logger.WarnFormat("Discovery on insecure identifier '{0}' aborted.", uri); return null; } - response = Request(uri, requireSsl, ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds); + response = Request(requestHandler, uri, requireSsl, ContentTypes.Html, ContentTypes.XHtml, ContentTypes.Xrds); if (response.Status != System.Net.HttpStatusCode.OK) { return null; } @@ -87,7 +83,7 @@ namespace DotNetOpenAuth.Yadis { } if (url != null) { if (!requireSsl || string.Equals(url.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase)) { - response2 = Request(url, requireSsl); + response2 = Request(requestHandler, url, requireSsl); if (response2.Status != System.Net.HttpStatusCode.OK) { return null; } |