diff options
Diffstat (limited to 'src')
162 files changed, 5251 insertions, 558 deletions
diff --git a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj index 1a7a406..7212008 100644 --- a/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj +++ b/src/DotNetOpenAuth.Test/DotNetOpenAuth.Test.csproj @@ -98,6 +98,10 @@ <HintPath>..\..\lib\Microsoft.Contracts.dll</HintPath> </Reference> <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" /> + <Reference Include="Moq, Version=3.1.416.3, Culture=neutral, PublicKeyToken=69f491c39445e920, processorArchitecture=MSIL"> + <SpecificVersion>False</SpecificVersion> + <HintPath>..\..\lib\Moq.dll</HintPath> + </Reference> <Reference Include="System" /> <Reference Include="System.configuration" /> <Reference Include="System.Core"> @@ -131,6 +135,7 @@ <Compile Include="Messaging\EnumerableCacheTests.cs" /> <Compile Include="Messaging\ErrorUtilitiesTests.cs" /> <Compile Include="Messaging\MessageSerializerTests.cs" /> + <Compile Include="Messaging\OutgoingWebResponseTests.cs" /> <Compile Include="Messaging\Reflection\MessageDescriptionTests.cs" /> <Compile Include="Messaging\Reflection\MessageDictionaryTests.cs" /> <Compile Include="Messaging\MessagingTestBase.cs" /> @@ -149,6 +154,7 @@ <Compile Include="Mocks\MockHttpRequest.cs" /> <Compile Include="Mocks\MockIdentifier.cs" /> <Compile Include="Mocks\MockOpenIdExtension.cs" /> + <Compile Include="Mocks\MockRealm.cs" /> <Compile Include="Mocks\MockTransformationBindingElement.cs" /> <Compile Include="Mocks\MockReplayProtectionBindingElement.cs" /> <Compile Include="Mocks\TestBaseMessage.cs" /> @@ -168,9 +174,11 @@ <Compile Include="OAuth\ChannelElements\OAuthChannelTests.cs" /> <Compile Include="OAuth\ChannelElements\PlaintextSigningBindingElementTest.cs" /> <Compile Include="OAuth\ChannelElements\SigningBindingElementBaseTests.cs" /> + <Compile Include="OAuth\ChannelElements\UriOrOobEncodingTests.cs" /> <Compile Include="OAuth\ConsumerDescription.cs" /> <Compile Include="OAuth\ProtocolTests.cs" /> <Compile Include="OAuth\ServiceProviderDescriptionTests.cs" /> + <Compile Include="OAuth\ServiceProviderTests.cs" /> <Compile Include="OpenId\AssociationsTests.cs" /> <Compile Include="OpenId\AssociationTests.cs" /> <Compile Include="OpenId\AuthenticationTests.cs" /> @@ -186,6 +194,8 @@ <Compile Include="OpenId\Extensions\AttributeExchange\AttributeValuesTests.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\StoreRequestTests.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\StoreResponseTests.cs" /> + <Compile Include="OpenId\Extensions\ExtensionsInteropHelperOPTests.cs" /> + <Compile Include="OpenId\Extensions\ExtensionsInteropHelperRPResponseTests.cs" /> <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PapeRoundTripTests.cs" /> <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyRequestTests.cs" /> <Compile Include="OpenId\Extensions\ProviderAuthenticationPolicy\PolicyResponseTests.cs" /> @@ -194,6 +204,7 @@ <Compile Include="OpenId\Extensions\SimpleRegistration\ClaimsRequestTests.cs" /> <Compile Include="OpenId\Extensions\UI\UIRequestTests.cs" /> <Compile Include="OpenId\IdentifierTests.cs" /> + <Compile Include="OpenId\Extensions\ExtensionsInteropHelperRPRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateDiffieHellmanRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateRequestTests.cs" /> <Compile Include="OpenId\Messages\AssociateUnsuccessfulResponseTests.cs" /> @@ -212,6 +223,7 @@ <Compile Include="OpenId\OpenIdCoordinator.cs" /> <Compile Include="OpenId\AssociationHandshakeTests.cs" /> <Compile Include="OpenId\OpenIdTestBase.cs" /> + <Compile Include="OpenId\Provider\PerformanceTests.cs" /> <Compile Include="OpenId\ProviderEndpointDescriptionTests.cs" /> <Compile Include="OpenId\Provider\AnonymousRequestTests.cs" /> <Compile Include="OpenId\Provider\AuthenticationRequestTest.cs" /> diff --git a/src/DotNetOpenAuth.Test/Hosting/HostingTests.cs b/src/DotNetOpenAuth.Test/Hosting/HostingTests.cs index ff72c66..d7de7a1 100644 --- a/src/DotNetOpenAuth.Test/Hosting/HostingTests.cs +++ b/src/DotNetOpenAuth.Test/Hosting/HostingTests.cs @@ -18,15 +18,19 @@ namespace DotNetOpenAuth.Test.Hosting { public class HostingTests : TestBase { [TestMethod] public void AspHostBasicTest() { - using (AspNetHost host = AspNetHost.CreateHost(TestWebDirectory)) { - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(host.BaseUri); - using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { - Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); - using (StreamReader sr = new StreamReader(response.GetResponseStream())) { - string content = sr.ReadToEnd(); - StringAssert.Contains(content, "Test home page"); + try { + using (AspNetHost host = AspNetHost.CreateHost(TestWebDirectory)) { + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(host.BaseUri); + using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { + Assert.AreEqual(HttpStatusCode.OK, response.StatusCode); + using (StreamReader sr = new StreamReader(response.GetResponseStream())) { + string content = sr.ReadToEnd(); + StringAssert.Contains(content, "Test home page"); + } } } + } catch (FileNotFoundException ex) { + Assert.Inconclusive("Unable to execute hosted ASP.NET tests because {0} could not be found. {1}", ex.FileName, ex.FusionLog); } } } diff --git a/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs b/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs index 4cdaa39..05ac306 100644 --- a/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/HttpRequestInfoTests.cs @@ -30,6 +30,50 @@ namespace DotNetOpenAuth.Test.Messaging { Assert.AreEqual(request.HttpMethod, info.HttpMethod); } + // All these tests are ineffective because ServerVariables[] cannot be set. + ////[TestMethod] + ////public void CtorRequestWithDifferentPublicHttpHost() { + //// HttpRequest request = new HttpRequest("file", "http://someserver?a=b", "a=b"); + //// request.ServerVariables["HTTP_HOST"] = "publichost"; + //// HttpRequestInfo info = new HttpRequestInfo(request); + //// Assert.AreEqual("publichost", info.UrlBeforeRewriting.Host); + //// Assert.AreEqual(80, info.UrlBeforeRewriting.Port); + //// Assert.AreEqual(request.Url.Query, info.Query); + //// Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]); + ////} + + ////[TestMethod] + ////public void CtorRequestWithDifferentPublicHttpsHost() { + //// HttpRequest request = new HttpRequest("file", "https://someserver?a=b", "a=b"); + //// request.ServerVariables["HTTP_HOST"] = "publichost"; + //// HttpRequestInfo info = new HttpRequestInfo(request); + //// Assert.AreEqual("publichost", info.UrlBeforeRewriting.Host); + //// Assert.AreEqual(443, info.UrlBeforeRewriting.Port); + //// Assert.AreEqual(request.Url.Query, info.Query); + //// Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]); + ////} + + ////[TestMethod] + ////public void CtorRequestWithDifferentPublicHostNonstandardPort() { + //// HttpRequest request = new HttpRequest("file", "http://someserver?a=b", "a=b"); + //// request.ServerVariables["HTTP_HOST"] = "publichost:550"; + //// HttpRequestInfo info = new HttpRequestInfo(request); + //// Assert.AreEqual("publichost", info.UrlBeforeRewriting.Host); + //// Assert.AreEqual(550, info.UrlBeforeRewriting.Port); + //// Assert.AreEqual(request.Url.Query, info.Query); + //// Assert.AreEqual(request.QueryString["a"], info.QueryString["a"]); + ////} + + ////[TestMethod] + ////public void CtorRequestWithDifferentPublicIPv6Host() { + //// HttpRequest request = new HttpRequest("file", "http://[fe80::587e:c6e5:d3aa:657a]:8089/v3.1/", ""); + //// request.ServerVariables["HTTP_HOST"] = "[fe80::587e:c6e5:d3aa:657b]:8089"; + //// HttpRequestInfo info = new HttpRequestInfo(request); + //// Assert.AreEqual("[fe80::587e:c6e5:d3aa:657b]", info.UrlBeforeRewriting.Host); + //// Assert.AreEqual(8089, info.UrlBeforeRewriting.Port); + //// Assert.AreEqual(request.Url.Query, info.Query); + ////} + /// <summary> /// Checks that a property dependent on another null property /// doesn't generate a NullReferenceException. diff --git a/src/DotNetOpenAuth.Test/Messaging/OutgoingWebResponseTests.cs b/src/DotNetOpenAuth.Test/Messaging/OutgoingWebResponseTests.cs new file mode 100644 index 0000000..35f9259 --- /dev/null +++ b/src/DotNetOpenAuth.Test/Messaging/OutgoingWebResponseTests.cs @@ -0,0 +1,36 @@ +//----------------------------------------------------------------------- +// <copyright file="OutgoingWebResponseTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Messaging { + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class OutgoingWebResponseTests { + /// <summary> + /// Verifies that setting the Body property correctly converts to a byte stream. + /// </summary> + [TestMethod] + public void SetBodyToByteStream() { + var response = new OutgoingWebResponse(); + string stringValue = "abc"; + response.Body = stringValue; + Assert.AreEqual(stringValue.Length, response.ResponseStream.Length); + + // Verify that the actual bytes are correct. + Encoding encoding = new UTF8Encoding(false); // avoid emitting a byte-order mark + var expectedBuffer = encoding.GetBytes(stringValue); + var actualBuffer = new byte[stringValue.Length]; + Assert.AreEqual(stringValue.Length, response.ResponseStream.Read(actualBuffer, 0, stringValue.Length)); + CollectionAssert.AreEqual(expectedBuffer, actualBuffer); + + // Verify that the header was set correctly. + Assert.AreEqual(encoding.HeaderName, response.Headers[HttpResponseHeader.ContentEncoding]); + } + } +} diff --git a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs index 0215801..19e6a82 100644 --- a/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs +++ b/src/DotNetOpenAuth.Test/Messaging/Reflection/MessagePartTests.cs @@ -82,7 +82,7 @@ namespace DotNetOpenAuth.Test.Messaging.Reflection { Assert.AreEqual("abc", part.GetValue(message)); } - [TestMethod, ExpectedException(typeof(ArgumentException))] + [TestMethod, ExpectedException(typeof(ProtocolException))] public void ConstantFieldMemberInvalidValues() { var message = new MessageWithConstantField(); MessagePart part = GetMessagePart(message.GetType(), "ConstantField"); diff --git a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs index be3c563..48547b7 100644 --- a/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs +++ b/src/DotNetOpenAuth.Test/Mocks/InMemoryTokenManager.cs @@ -9,12 +9,13 @@ namespace DotNetOpenAuth.Test.Mocks { using System.Collections.Generic; using System.Diagnostics; using System.Linq; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; internal class InMemoryTokenManager : IConsumerTokenManager, IServiceProviderTokenManager { - private Dictionary<string, string> consumersAndSecrets = new Dictionary<string, string>(); - private Dictionary<string, string> tokensAndSecrets = new Dictionary<string, string>(); + private KeyedCollectionDelegate<string, ConsumerInfo> consumers = new KeyedCollectionDelegate<string, ConsumerInfo>(c => c.Key); + private KeyedCollectionDelegate<string, TokenInfo> tokens = new KeyedCollectionDelegate<string, TokenInfo>(t => t.Token); /// <summary> /// Request tokens that have been issued, and whether they have been authorized yet. @@ -29,11 +30,11 @@ namespace DotNetOpenAuth.Test.Mocks { #region IConsumerTokenManager Members public string ConsumerKey { - get { return this.consumersAndSecrets.Keys.Single(); } + get { return this.consumers.Single().Key; } } public string ConsumerSecret { - get { return this.consumersAndSecrets.Values.Single(); } + get { return this.consumers.Single().Secret; } } #endregion @@ -41,11 +42,11 @@ namespace DotNetOpenAuth.Test.Mocks { #region ITokenManager Members public string GetTokenSecret(string token) { - return this.tokensAndSecrets[token]; + return this.tokens[token].Secret; } public void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) { - this.tokensAndSecrets[response.Token] = response.TokenSecret; + this.tokens.Add(new TokenInfo { ConsumerKey = request.ConsumerKey, Token = response.Token, Secret = response.TokenSecret }); this.requestTokens.Add(response.Token, false); } @@ -70,8 +71,8 @@ namespace DotNetOpenAuth.Test.Mocks { ////Debug.Assert(this.requestTokens[requestToken], "Unauthorized token should not be exchanged for access token."); this.requestTokens.Remove(requestToken); this.accessTokens.Add(accessToken); - this.tokensAndSecrets.Remove(requestToken); - this.tokensAndSecrets[accessToken] = accessTokenSecret; + this.tokens.Remove(requestToken); + this.tokens.Add(new TokenInfo { Token = accessToken, Secret = accessTokenSecret }); } /// <summary> @@ -93,8 +94,16 @@ namespace DotNetOpenAuth.Test.Mocks { #region IServiceProviderTokenManager Members - public string GetConsumerSecret(string consumerKey) { - return this.consumersAndSecrets[consumerKey]; + public IConsumerDescription GetConsumer(string consumerKey) { + return this.consumers[consumerKey]; + } + + public IServiceProviderRequestToken GetRequestToken(string token) { + return this.tokens[token]; + } + + public IServiceProviderAccessToken GetAccessToken(string token) { + return this.tokens[token]; } #endregion @@ -105,7 +114,7 @@ namespace DotNetOpenAuth.Test.Mocks { /// </summary> /// <param name="consumerDescription">The consumer description.</param> internal void AddConsumer(ConsumerDescription consumerDescription) { - this.consumersAndSecrets.Add(consumerDescription.ConsumerKey, consumerDescription.ConsumerSecret); + this.consumers.Add(new ConsumerInfo { Key = consumerDescription.ConsumerKey, Secret = consumerDescription.ConsumerSecret }); } /// <summary> @@ -119,5 +128,49 @@ namespace DotNetOpenAuth.Test.Mocks { this.requestTokens[requestToken] = true; } + + private class TokenInfo : IServiceProviderRequestToken, IServiceProviderAccessToken { + internal TokenInfo() { + this.CreatedOn = DateTime.Now; + } + + public string ConsumerKey { get; set; } + + public DateTime CreatedOn { get; set; } + + public string Token { get; set; } + + public string VerificationCode { get; set; } + + public Uri Callback { get; set; } + + public Version ConsumerVersion { get; set; } + + public string Username { get; set; } + + public string[] Roles { get; set; } + + public DateTime? ExpirationDate { get; set; } + + internal string Secret { get; set; } + } + + private class ConsumerInfo : IConsumerDescription { + #region IConsumerDescription Members + + public string Key { get; set; } + + public string Secret { get; set; } + + public System.Security.Cryptography.X509Certificates.X509Certificate2 Certificate { get; set; } + + public Uri Callback { get; set; } + + public DotNetOpenAuth.OAuth.VerificationCodeFormat VerificationCodeFormat { get; set; } + + public int VerificationCodeLength { get; set; } + + #endregion + } } } diff --git a/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs b/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs new file mode 100644 index 0000000..ae39ebb --- /dev/null +++ b/src/DotNetOpenAuth.Test/Mocks/MockRealm.cs @@ -0,0 +1,42 @@ +//----------------------------------------------------------------------- +// <copyright file="MockRealm.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.Mocks { + using System.Collections.Generic; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + + internal class MockRealm : Realm { + private RelyingPartyEndpointDescription[] relyingPartyDescriptions; + + /// <summary> + /// Initializes a new instance of the <see cref="MockRealm"/> class. + /// </summary> + /// <param name="wrappedRealm">The wrapped realm.</param> + /// <param name="relyingPartyDescriptions">The relying party descriptions.</param> + internal MockRealm(Realm wrappedRealm, params RelyingPartyEndpointDescription[] relyingPartyDescriptions) + : base(wrappedRealm) { + ErrorUtilities.VerifyArgumentNotNull(relyingPartyDescriptions, "relyingPartyDescriptions"); + + this.relyingPartyDescriptions = relyingPartyDescriptions; + } + + /// <summary> + /// Searches for an XRDS document at the realm URL, and if found, searches + /// for a description of a relying party endpoints (OpenId login pages). + /// </summary> + /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> + /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. + /// This may be true when creating an unsolicited assertion, but must be + /// 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 override IEnumerable<RelyingPartyEndpointDescription> DiscoverReturnToEndpoints(IDirectWebRequestHandler requestHandler, bool allowRedirects) { + return this.relyingPartyDescriptions; + } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs index bc3a94c..856f164 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/OAuthChannelTests.cs @@ -33,7 +33,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { base.SetUp(); this.webRequestHandler = new TestWebRequestHandler(); - this.signingElement = new RsaSha1SigningBindingElement(); + this.signingElement = new RsaSha1SigningBindingElement(new InMemoryTokenManager()); this.nonceStore = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new InMemoryTokenManager(), new TestMessageFactory()); this.accessor = OAuthChannel_Accessor.AttachShadow(this.channel); @@ -47,22 +47,22 @@ namespace DotNetOpenAuth.Test.ChannelElements { [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void CtorNullStore() { - new OAuthChannel(new RsaSha1SigningBindingElement(), null, new InMemoryTokenManager(), new TestMessageFactory()); + new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), null, new InMemoryTokenManager(), new TestMessageFactory()); } [TestMethod, ExpectedException(typeof(ArgumentNullException))] public void CtorNullTokenManager() { - new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, null, new TestMessageFactory()); + new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, null, new TestMessageFactory()); } [TestMethod] public void CtorSimpleConsumer() { - new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, (IConsumerTokenManager)new InMemoryTokenManager()); + new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IConsumerTokenManager)new InMemoryTokenManager()); } [TestMethod] public void CtorSimpleServiceProvider() { - new OAuthChannel(new RsaSha1SigningBindingElement(), this.nonceStore, (IServiceProviderTokenManager)new InMemoryTokenManager()); + new OAuthChannel(new RsaSha1SigningBindingElement(new InMemoryTokenManager()), this.nonceStore, (IServiceProviderTokenManager)new InMemoryTokenManager()); } [TestMethod] diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs index ca63b50..627db8f 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/PlaintextSigningBindingElementTest.cs @@ -4,9 +4,9 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenAuth.Test.ChannelElements -{ +namespace DotNetOpenAuth.Test.ChannelElements { using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -17,7 +17,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpsSignatureGeneration() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; Assert.IsNotNull(target.ProcessOutgoingMessage(message)); @@ -29,7 +29,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpsSignatureVerification() { MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); ITamperProtectionChannelBindingElement target = new PlaintextSigningBindingElement(); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; message.SignatureMethod = "PLAINTEXT"; @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpsSignatureVerificationNotApplicable() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://localtest", HttpDeliveryMethods.GetRequest); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; message.SignatureMethod = "ANOTHERALGORITHM"; @@ -53,7 +53,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpSignatureGeneration() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; @@ -67,7 +67,7 @@ namespace DotNetOpenAuth.Test.ChannelElements public void HttpSignatureVerification() { SigningBindingElementBase target = new PlaintextSigningBindingElement(); MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("http://localtest", HttpDeliveryMethods.GetRequest); - ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint); + ITamperResistantOAuthMessage message = new UnauthorizedTokenRequest(endpoint, Protocol.Default.Version); message.ConsumerSecret = "cs"; message.TokenSecret = "ts"; message.SignatureMethod = "PLAINTEXT"; diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs index 93c0b3f..6e566c8 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.OAuth.Messages; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -63,7 +64,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { internal static UnauthorizedTokenRequest CreateTestRequestTokenMessage(MessageDescriptionCollection messageDescriptions, MessageReceivingEndpoint endpoint) { endpoint = endpoint ?? new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest); - UnauthorizedTokenRequest message = new UnauthorizedTokenRequest(endpoint); + UnauthorizedTokenRequest message = new UnauthorizedTokenRequest(endpoint, Protocol.V10.Version); message.ConsumerKey = "nerdbank.org"; ((ITamperResistantOAuthMessage)message).ConsumerSecret = "nerdbanksecret"; var signedMessage = (ITamperResistantOAuthMessage)message; diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/UriOrOobEncodingTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/UriOrOobEncodingTests.cs new file mode 100644 index 0000000..40fc93e --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/UriOrOobEncodingTests.cs @@ -0,0 +1,68 @@ +//----------------------------------------------------------------------- +// <copyright file="UriOrOobEncodingTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth.ChannelElements; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class UriOrOobEncodingTests : TestBase { + private UriOrOobEncoding encoding; + + [TestInitialize] + public void Setup() { + this.encoding = new UriOrOobEncoding(); + } + + /// <summary> + /// Verifies null value encoding + /// </summary> + [TestMethod] + public void NullValueEncoding() { + Assert.AreEqual("oob", this.encoding.EncodedNullValue); + } + + /// <summary> + /// Verifies decoding "oob" results in a null uri. + /// </summary> + [TestMethod] + public void DecodeOobToNullUri() { + Assert.IsNull(this.encoding.Decode("oob")); + } + + /// <summary> + /// Verifies that decoding an empty string generates an exception. + /// </summary> + [TestMethod, ExpectedException(typeof(UriFormatException))] + public void DecodeEmptyStringFails() { + this.encoding.Decode(string.Empty); + } + + /// <summary> + /// Verifies proper decoding/encoding of a Uri + /// </summary> + [TestMethod] + public void UriEncodeDecode() { + Uri original = new Uri("http://somehost/p?q=a#frag"); + string encodedValue = this.encoding.Encode(original); + Assert.AreEqual(original.AbsoluteUri, encodedValue); + Uri decoded = (Uri)this.encoding.Decode(encodedValue); + Assert.AreEqual(original, decoded); + } + + /// <summary> + /// Verifies failure to decode a relative Uri + /// </summary> + [TestMethod, ExpectedException(typeof(UriFormatException))] + public void RelativeUriDecodeFails() { + this.encoding.Decode("../a/b"); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs index e04edeb..ce548a9 100644 --- a/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs +++ b/src/DotNetOpenAuth.Test/OAuth/OAuthCoordinator.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Test { using System; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth; using DotNetOpenAuth.OAuth.ChannelElements; using DotNetOpenAuth.Test.Mocks; @@ -57,7 +58,7 @@ namespace DotNetOpenAuth.Test { WebConsumer consumer = new WebConsumer(this.serviceDescription, consumerTokenManager) { OAuthChannel = consumerChannel, }; - ServiceProvider serviceProvider = new ServiceProvider(this.serviceDescription, serviceTokenManager) { + ServiceProvider serviceProvider = new ServiceProvider(this.serviceDescription, serviceTokenManager, new NonceMemoryStore()) { OAuthChannel = serviceProviderChannel, }; diff --git a/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs b/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs index 6a2551a..ce8070b 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ProtocolTests.cs @@ -12,7 +12,7 @@ namespace DotNetOpenAuth.Test { public class ProtocolTests { [TestMethod] public void Default() { - Assert.AreSame(Protocol.V10, Protocol.Default); + Assert.AreSame(Protocol.V10a, Protocol.Default); } [TestMethod] @@ -23,12 +23,12 @@ namespace DotNetOpenAuth.Test { [TestMethod] public void AuthorizationHeaderScheme() { - Assert.AreEqual("OAuth", Protocol.V10.AuthorizationHeaderScheme); + Assert.AreEqual("OAuth", Protocol.AuthorizationHeaderScheme); } [TestMethod] public void ParameterPrefix() { - Assert.AreEqual("oauth_", Protocol.V10.ParameterPrefix); + Assert.AreEqual("oauth_", Protocol.ParameterPrefix); } } } diff --git a/src/DotNetOpenAuth.Test/OAuth/ServiceProviderTests.cs b/src/DotNetOpenAuth.Test/OAuth/ServiceProviderTests.cs new file mode 100644 index 0000000..2a443ce --- /dev/null +++ b/src/DotNetOpenAuth.Test/OAuth/ServiceProviderTests.cs @@ -0,0 +1,38 @@ +//----------------------------------------------------------------------- +// <copyright file="ServiceProviderTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OAuth { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ServiceProviderTests : TestBase { + /// <summary> + /// Verifies the CreateVerificationCode method. + /// </summary> + [TestMethod] + public void CreateVerificationCode() { + this.TestCode(VerificationCodeFormat.Numeric, 3, MessagingUtilities.Digits); + this.TestCode(VerificationCodeFormat.AlphaLower, 5, MessagingUtilities.LowercaseLetters); + this.TestCode(VerificationCodeFormat.AlphaUpper, 5, MessagingUtilities.UppercaseLetters); + this.TestCode(VerificationCodeFormat.AlphaNumericNoLookAlikes, 8, MessagingUtilities.AlphaNumericNoLookAlikes); + } + + private void TestCode(VerificationCodeFormat format, int length, string allowableCharacters) { + string code = ServiceProvider.CreateVerificationCode(format, length); + TestContext.WriteLine("{0} of length {2}: {1}", format, code, length); + Assert.AreEqual(length, code.Length); + foreach (char ch in code) { + Assert.IsTrue(allowableCharacters.Contains(ch)); + } + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs index 6bd2c00..3ab6559 100644 --- a/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/ChannelElements/SigningBindingElementTests.cs @@ -36,7 +36,7 @@ namespace DotNetOpenAuth.Test.OpenId.ChannelElements { signedMessage.UtcCreationDate = DateTime.Parse("1/1/2009"); signedMessage.AssociationHandle = association.Handle; Assert.IsNotNull(signer.ProcessOutgoingMessage(message)); - Assert.AreEqual("0wOdvNgzCZ5I5AzbU58Nq2Tg8EJZ7QoNz4gpx2r7jII=", signedMessage.Signature); + Assert.AreEqual("o9+uN7qTaUS9v0otbHTuNAtbkpBm14+es9QnNo6IHD4=", signedMessage.Signature); } /// <summary> diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs new file mode 100644 index 0000000..9f849ea --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperOPTests.cs @@ -0,0 +1,147 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsInteropHelperOPTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId.Extensions { + using System.Collections.Generic; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ExtensionsInteropHelperOPTests : OpenIdTestBase { + private AuthenticationRequest request; + private IList<IExtensionMessage> extensions; + + [TestInitialize] + public override void SetUp() { + base.SetUp(); + + var op = this.CreateProvider(); + var rpRequest = new CheckIdRequest(Protocol.Default.Version, OPUri, DotNetOpenAuth.OpenId.RelyingParty.AuthenticationRequestMode.Setup); + rpRequest.ReturnTo = RPUri; + this.extensions = rpRequest.Extensions; + this.request = new AuthenticationRequest(op, rpRequest); + this.request.IsAuthenticated = true; + } + + /// <summary> + /// Verifies no extensions appear as no extensions + /// </summary> + [TestMethod] + public void NoRequestedExtensions() { + var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.request); + Assert.IsNull(sreg); + + // Make sure we're still able to send an sreg response. + var sregResponse = new ClaimsResponse(); + this.request.AddResponseExtension(sregResponse); + ExtensionsInteropHelper.ConvertSregToMatchRequest(this.request); + var extensions = this.GetResponseExtensions(); + Assert.AreSame(sregResponse, extensions.Single()); + } + + /// <summary> + /// Verifies sreg coming in is seen as sreg. + /// </summary> + [TestMethod] + public void UnifyExtensionsAsSregWithSreg() { + var sregInjected = new ClaimsRequest { + Nickname = DemandLevel.Request, + }; + this.extensions.Add(sregInjected); + var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.request); + Assert.AreSame(sregInjected, sreg); + Assert.AreEqual(DemandLevel.Request, sreg.Nickname); + Assert.AreEqual(DemandLevel.NoRequest, sreg.FullName); + + var sregResponse = new ClaimsResponse(); + this.request.AddResponseExtension(sregResponse); + ExtensionsInteropHelper.ConvertSregToMatchRequest(this.request); + var extensions = this.GetResponseExtensions(); + Assert.AreSame(sregResponse, extensions.Single()); + } + + /// <summary> + /// Verifies AX coming in looks like sreg. + /// </summary> + [TestMethod] + public void UnifyExtensionsAsSregWithAX() { + this.ParameterizedAXTest(AXAttributeFormats.AXSchemaOrg); + } + + /// <summary> + /// Verifies AX coming in looks like sreg. + /// </summary> + [TestMethod] + public void UnifyExtensionsAsSregWithAXSchemaOpenIdNet() { + this.ParameterizedAXTest(AXAttributeFormats.SchemaOpenIdNet); + } + + /// <summary> + /// Verifies sreg and AX in one request has a preserved sreg request. + /// </summary> + [TestMethod] + public void UnifyExtensionsAsSregWithBothSregAndAX() { + var sregInjected = new ClaimsRequest { + Nickname = DemandLevel.Request, + }; + this.extensions.Add(sregInjected); + var axInjected = new FetchRequest(); + axInjected.Attributes.AddOptional(WellKnownAttributes.Contact.Email); + this.extensions.Add(axInjected); + var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.request); + Assert.AreSame(sregInjected, sreg); + Assert.AreEqual(DemandLevel.Request, sreg.Nickname); + Assert.AreEqual(DemandLevel.NoRequest, sreg.Email); + + var sregResponseInjected = new ClaimsResponse { + Nickname = "andy", + }; + this.request.AddResponseExtension(sregResponseInjected); + var axResponseInjected = new FetchResponse(); + axResponseInjected.Attributes.Add(WellKnownAttributes.Contact.Email, "a@b.com"); + this.request.AddResponseExtension(axResponseInjected); + ExtensionsInteropHelper.ConvertSregToMatchRequest(this.request); + var extensions = this.GetResponseExtensions(); + var sregResponse = extensions.OfType<ClaimsResponse>().Single(); + Assert.AreEqual("andy", sregResponse.Nickname); + var axResponse = extensions.OfType<FetchResponse>().Single(); + Assert.AreEqual("a@b.com", axResponse.GetAttributeValue(WellKnownAttributes.Contact.Email)); + } + + private IList<IExtensionMessage> GetResponseExtensions() { + IProtocolMessageWithExtensions response = (IProtocolMessageWithExtensions)this.request.Response; + return response.Extensions; + } + + private void ParameterizedAXTest(AXAttributeFormats format) { + var axInjected = new FetchRequest(); + axInjected.Attributes.AddOptional(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, format)); + axInjected.Attributes.AddRequired(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.FullName, format)); + this.extensions.Add(axInjected); + var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.request); + Assert.AreSame(sreg, this.request.GetExtension<ClaimsRequest>()); + Assert.AreEqual(DemandLevel.Request, sreg.Nickname); + Assert.AreEqual(DemandLevel.Require, sreg.FullName); + Assert.AreEqual(DemandLevel.NoRequest, sreg.Language); + + var sregResponse = new ClaimsResponse { + Nickname = "andy", + }; + this.request.AddResponseExtension(sregResponse); + ExtensionsInteropHelper.ConvertSregToMatchRequest(this.request); + var extensions = this.GetResponseExtensions(); + var axResponse = extensions.OfType<FetchResponse>().Single(); + Assert.AreEqual("andy", axResponse.GetAttributeValue(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, format))); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs new file mode 100644 index 0000000..ba5e335 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPRequestTests.cs @@ -0,0 +1,128 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsInteropHelperRPRequestTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId { + using System.Linq; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.RelyingParty; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ExtensionsInteropHelperRPRequestTests : OpenIdTestBase { + private AuthenticationRequest authReq; + private ClaimsRequest sreg; + + [TestInitialize] + public override void SetUp() { + base.SetUp(); + + var rp = CreateRelyingParty(true); + Identifier identifier = this.GetMockIdentifier(ProtocolVersion.V20); + this.authReq = (AuthenticationRequest)rp.CreateRequest(identifier, RPRealmUri, RPUri); + this.sreg = new ClaimsRequest { + Nickname = DemandLevel.Request, + FullName = DemandLevel.Request, + BirthDate = DemandLevel.Request, + Email = DemandLevel.Require, + Country = DemandLevel.Request, + PostalCode = DemandLevel.Request, + Gender = DemandLevel.Request, + Language = DemandLevel.Request, + TimeZone = DemandLevel.Request, + }; + } + + /// <summary> + /// Verifies that without an Sreg extension to copy from, no AX extension request is added. + /// </summary> + [TestMethod] + public void SpreadSregToAXNoExtensions() { + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.AXSchemaOrg); + Assert.AreEqual(0, this.authReq.AppliedExtensions.Count()); + } + + /// <summary> + /// Verifies that Sreg requests are correctly copied to axschema.org AX requests. + /// </summary> + [TestMethod] + public void SpreadSregToAXBasic() { + this.authReq.AddExtension(this.sreg); + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.AXSchemaOrg); + var ax = this.authReq.AppliedExtensions.OfType<FetchRequest>().Single(); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Name.Alias].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Name.FullName].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.BirthDate.WholeBirthDate].IsRequired); + Assert.IsTrue(ax.Attributes[WellKnownAttributes.Contact.Email].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Contact.HomeAddress.Country].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Contact.HomeAddress.PostalCode].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Person.Gender].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Preferences.Language].IsRequired); + Assert.IsFalse(ax.Attributes[WellKnownAttributes.Preferences.TimeZone].IsRequired); + } + + /// <summary> + /// Verifies that sreg can spread to multiple AX schemas. + /// </summary> + [TestMethod] + public void SpreadSregToAxMultipleSchemas() { + this.authReq.AddExtension(this.sreg); + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.AXSchemaOrg | AXAttributeFormats.SchemaOpenIdNet); + var ax = this.authReq.AppliedExtensions.OfType<FetchRequest>().Single(); + Assert.IsTrue(ax.Attributes.Contains(WellKnownAttributes.Name.Alias)); + Assert.IsTrue(ax.Attributes.Contains(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet))); + Assert.IsFalse(ax.Attributes.Contains(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.OpenIdNetSchema))); + } + + /// <summary> + /// Verifies no spread if the OP advertises sreg support. + /// </summary> + [TestMethod] + public void SpreadSregToAxNoOpIfOPSupportsSreg() { + this.authReq.AddExtension(this.sreg); + this.InjectAdvertisedTypeUri(DotNetOpenAuth.OpenId.Extensions.SimpleRegistration.Constants.sreg_ns); + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.All); + Assert.IsFalse(this.authReq.AppliedExtensions.OfType<FetchRequest>().Any()); + } + + /// <summary> + /// Verifies a targeted AX request if the OP advertises a recognized type URI format. + /// </summary> + [TestMethod] + public void SpreadSregToAxTargetedAtOPFormat() { + this.authReq.AddExtension(this.sreg); + this.InjectAdvertisedTypeUri(WellKnownAttributes.Name.FullName); + ExtensionsInteropHelper.SpreadSregToAX(this.authReq, AXAttributeFormats.OpenIdNetSchema); + var ax = this.authReq.AppliedExtensions.OfType<FetchRequest>().Single(); + Assert.IsFalse(ax.Attributes.Contains(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Contact.Email, AXAttributeFormats.OpenIdNetSchema))); + Assert.IsTrue(ax.Attributes.Contains(WellKnownAttributes.Contact.Email)); + } + + /// <summary> + /// Verifies that TransformAXFormat correctly translates AX schema Type URIs. + /// </summary> + [TestMethod] + public void TransformAXFormatTest() { + Assert.AreEqual(WellKnownAttributes.Name.Alias, ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.AXSchemaOrg)); + Assert.AreEqual("http://schema.openid.net/namePerson/friendly", ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet)); + Assert.AreEqual("http://openid.net/schema/namePerson/friendly", ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.OpenIdNetSchema)); + } + + /// <summary> + /// Injects the advertised type URI into the list of advertised services for the authentication request. + /// </summary> + /// <param name="typeUri">The type URI.</param> + private void InjectAdvertisedTypeUri(string typeUri) { + var serviceEndpoint = ServiceEndpoint_Accessor.AttachShadow(((ServiceEndpoint)this.authReq.Provider)); + serviceEndpoint.ProviderDescription = ProviderEndpointDescription_Accessor.AttachShadow( + new ProviderEndpointDescription( + serviceEndpoint.ProviderDescription.Endpoint, + serviceEndpoint.ProviderDescription.Capabilities.Concat(new[] { typeUri }))); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs new file mode 100644 index 0000000..5fe05c1 --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/ExtensionsInteropHelperRPResponseTests.cs @@ -0,0 +1,83 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsInteropHelperRPResponseTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId { + using System.Collections.Generic; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.RelyingParty; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class ExtensionsInteropHelperRPResponseTests : OpenIdTestBase { + private IAuthenticationResponse response; + private IList<IExtensionMessage> extensions; + + [TestInitialize] + public override void SetUp() { + base.SetUp(); + + IndirectSignedResponse responseMessage = new IndirectSignedResponse(Protocol.Default.Version, RPUri); + this.extensions = responseMessage.Extensions; + this.response = new DotNetOpenAuth.OpenId.RelyingParty.PositiveAnonymousResponse(responseMessage); + } + + /// <summary> + /// Verifies that with no extensions present, UnifyExtensionsAsSreg returns an empty ClaimsResponse. + /// </summary> + [TestMethod] + public void UnifyExtensionsAsSregNoExtensions() { + var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.response, true); + Assert.IsNotNull(sreg); + Assert.IsNull(sreg.Nickname); + } + + /// <summary> + /// Verifies that with sreg and AX extensions present, the sreg extension is returned. + /// </summary> + [TestMethod] + public void UnifyExtensionsAsSregWithSreg() { + var sregInjected = new ClaimsResponse { + Nickname = "andy", + }; + var axInjected = new FetchResponse(); + axInjected.Attributes.Add(WellKnownAttributes.Name.Alias, "nate"); + this.extensions.Add(sregInjected); + this.extensions.Add(axInjected); + var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.response, true); + Assert.AreSame(sregInjected, sreg); + Assert.AreEqual("andy", sreg.Nickname); + } + + /// <summary> + /// Verifies UnifyExtensionsAsSreg correctly converts AX to sreg. + /// </summary> + [TestMethod] + public void UnifyExtensionsAsSregFromAXSchemaOrg() { + var axInjected = new FetchResponse(); + axInjected.Attributes.Add(WellKnownAttributes.Name.Alias, "nate"); + this.extensions.Add(axInjected); + var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.response, true); + Assert.AreEqual("nate", sreg.Nickname); + } + + /// <summary> + /// Verifies UnifyExtensionsAsSreg correctly converts AX in a non-standard format to sreg. + /// </summary> + [TestMethod] + public void UnifyExtensionsasSregFromSchemaOpenIdNet() { + var axInjected = new FetchResponse(); + axInjected.Attributes.Add(ExtensionsInteropHelper_Accessor.TransformAXFormat(WellKnownAttributes.Name.Alias, AXAttributeFormats.SchemaOpenIdNet), "nate"); + this.extensions.Add(axInjected); + var sreg = ExtensionsInteropHelper.UnifyExtensionsAsSreg(this.response, true); + Assert.AreEqual("nate", sreg.Nickname); + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/UI/UIRequestTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/UI/UIRequestTests.cs index f69fc8b..7a60a32 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/UI/UIRequestTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/UI/UIRequestTests.cs @@ -16,15 +16,30 @@ namespace DotNetOpenAuth.Test.OpenId.Extensions.UI { public void Defaults() { UIRequest request = new UIRequest(); Assert.AreEqual("popup", request.Mode); - Assert.AreEqual(CultureInfo.CurrentUICulture, request.LanguagePreference); + Assert.AreEqual(1, request.LanguagePreference.Length); + Assert.AreEqual(CultureInfo.CurrentUICulture, request.LanguagePreference[0]); } [TestMethod] - public void LanguagePreferenceEncoding() { + public void LanguagePreferenceEncodingDecoding() { var request = new UIRequest(); - request.LanguagePreference = new CultureInfo("en-US"); MessageDictionary dictionary = this.MessageDescriptions.GetAccessor(request); + + request.LanguagePreference = new[] { new CultureInfo("en-US") }; Assert.AreEqual("en-US", dictionary["lang"]); + + request.LanguagePreference = new[] { new CultureInfo("en-US"), new CultureInfo("es-ES") }; + Assert.AreEqual("en-US,es-ES", dictionary["lang"]); + + // Now test decoding + dictionary["lang"] = "en-US"; + Assert.AreEqual(1, request.LanguagePreference.Length); + Assert.AreEqual(new CultureInfo("en-US"), request.LanguagePreference[0]); + + dictionary["lang"] = "en-US,es-ES"; + Assert.AreEqual(2, request.LanguagePreference.Length); + Assert.AreEqual(new CultureInfo("en-US"), request.LanguagePreference[0]); + Assert.AreEqual(new CultureInfo("es-ES"), request.LanguagePreference[1]); } [TestMethod] diff --git a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs index 8fa5580..5034b7e 100644 --- a/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs +++ b/src/DotNetOpenAuth.Test/OpenId/OpenIdTestBase.cs @@ -71,6 +71,14 @@ namespace DotNetOpenAuth.Test.OpenId { this.MockResponder = MockHttpRequest.CreateUntrustedMockHttpHandler(); this.RequestHandler = this.MockResponder.MockWebRequestHandler; this.AutoProviderScenario = Scenarios.AutoApproval; + Identifier.EqualityOnStrings = true; + } + + [TestCleanup] + public override void Cleanup() { + base.Cleanup(); + + Identifier.EqualityOnStrings = false; } /// <summary> @@ -168,6 +176,11 @@ namespace DotNetOpenAuth.Test.OpenId { } } + protected Realm GetMockRealm(bool useSsl) { + var rpDescription = new RelyingPartyEndpointDescription(useSsl ? RPUriSsl : RPUri, new string[] { Protocol.V20.RPReturnToTypeURI }); + return new MockRealm(useSsl ? RPRealmUriSsl : RPRealmUri, rpDescription); + } + protected Identifier GetMockIdentifier(ProtocolVersion providerVersion) { return this.GetMockIdentifier(providerVersion, false); } @@ -187,7 +200,16 @@ namespace DotNetOpenAuth.Test.OpenId { /// </summary> /// <returns>The new instance.</returns> protected OpenIdRelyingParty CreateRelyingParty() { - var rp = new OpenIdRelyingParty(new StandardRelyingPartyApplicationStore()); + return this.CreateRelyingParty(false); + } + + /// <summary> + /// Creates a standard <see cref="OpenIdRelyingParty"/> instance for general testing. + /// </summary> + /// <param name="stateless">if set to <c>true</c> a stateless RP is created.</param> + /// <returns>The new instance.</returns> + protected OpenIdRelyingParty CreateRelyingParty(bool stateless) { + var rp = new OpenIdRelyingParty(stateless ? null : new StandardRelyingPartyApplicationStore()); rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; return rp; } diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs index 28b2b55..0a6cdcc 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/OpenIdProviderTests.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { using System; + using System.IO; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.OpenId.Extensions; @@ -126,15 +127,19 @@ namespace DotNetOpenAuth.Test.OpenId.Provider { [TestMethod] public void BadRequestsGenerateValidErrorResponsesHosted() { - using (AspNetHost host = AspNetHost.CreateHost(TestWebDirectory)) { - Uri opEndpoint = new Uri(host.BaseUri, "/OpenIdProviderEndpoint.ashx"); - var rp = new OpenIdRelyingParty(null); - var nonOpenIdMessage = new Mocks.TestDirectedMessage(); - nonOpenIdMessage.Recipient = opEndpoint; - nonOpenIdMessage.HttpMethods = HttpDeliveryMethods.PostRequest; - MessagingTestBase.GetStandardTestMessage(MessagingTestBase.FieldFill.AllRequired, nonOpenIdMessage); - var response = rp.Channel.Request<DirectErrorResponse>(nonOpenIdMessage); - Assert.IsNotNull(response.ErrorMessage); + try { + using (AspNetHost host = AspNetHost.CreateHost(TestWebDirectory)) { + Uri opEndpoint = new Uri(host.BaseUri, "/OpenIdProviderEndpoint.ashx"); + var rp = new OpenIdRelyingParty(null); + var nonOpenIdMessage = new Mocks.TestDirectedMessage(); + nonOpenIdMessage.Recipient = opEndpoint; + nonOpenIdMessage.HttpMethods = HttpDeliveryMethods.PostRequest; + MessagingTestBase.GetStandardTestMessage(MessagingTestBase.FieldFill.AllRequired, nonOpenIdMessage); + var response = rp.Channel.Request<DirectErrorResponse>(nonOpenIdMessage); + Assert.IsNotNull(response.ErrorMessage); + } + } catch (FileNotFoundException ex) { + Assert.Inconclusive("Unable to execute hosted ASP.NET tests because {0} could not be found. {1}", ex.FileName, ex.FusionLog); } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs new file mode 100644 index 0000000..9f4727d --- /dev/null +++ b/src/DotNetOpenAuth.Test/OpenId/Provider/PerformanceTests.cs @@ -0,0 +1,157 @@ +//----------------------------------------------------------------------- +// <copyright file="PerformanceTests.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Test.OpenId.Provider { + using System; + using System.Collections.Generic; + using System.Diagnostics; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + using DotNetOpenAuth.OpenId; + using DotNetOpenAuth.OpenId.ChannelElements; + using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; + using Microsoft.VisualStudio.TestTools.UnitTesting; + + [TestClass] + public class PerformanceTests : OpenIdTestBase { + private const string SharedAssociationHandle = "handle"; + private static readonly TimeSpan TestRunTime = TimeSpan.FromSeconds(3); + private OpenIdProvider provider; + + [TestInitialize] + public override void SetUp() { + base.SetUp(); + SuspendLogging(); + this.provider = CreateProvider(); + } + + [TestCleanup] + public override void Cleanup() { + ResumeLogging(); + base.Cleanup(); + } + + [TestMethod] + public void AssociateDH() { + var associateRequest = this.CreateAssociateRequest(OPUri); + Stopwatch timer = new Stopwatch(); + timer.Start(); + int iterations; + for (iterations = 0; timer.ElapsedMilliseconds < TestRunTime.TotalMilliseconds; iterations++) { + IRequest request = this.provider.GetRequest(associateRequest); + var response = this.provider.PrepareResponse(request); + Assert.IsInstanceOfType(response.OriginalMessage, typeof(AssociateSuccessfulResponse)); + } + timer.Stop(); + double executionsPerSecond = GetExecutionsPerSecond(iterations, timer); + TestContext.WriteLine("Created {0} associations in {1}, or {2} per second.", iterations, timer.Elapsed, executionsPerSecond); + Assert.IsTrue(executionsPerSecond >= 2, "Too slow ({0} >= 2 executions per second required.)", executionsPerSecond); + } + + [TestMethod] + public void AssociateClearText() { + var associateRequest = this.CreateAssociateRequest(OPUriSsl); // SSL will cause a plaintext association + Stopwatch timer = new Stopwatch(); + timer.Start(); + int iterations; + for (iterations = 0; timer.ElapsedMilliseconds < TestRunTime.TotalMilliseconds; iterations++) { + IRequest request = this.provider.GetRequest(associateRequest); + var response = this.provider.PrepareResponse(request); + Assert.IsInstanceOfType(response.OriginalMessage, typeof(AssociateSuccessfulResponse)); + } + timer.Stop(); + double executionsPerSecond = GetExecutionsPerSecond(iterations, timer); + TestContext.WriteLine("Created {0} associations in {1}, or {2} per second.", iterations, timer.Elapsed, executionsPerSecond); + Assert.IsTrue(executionsPerSecond > 1000, "Too slow ({0} > 1000 executions per second required.)", executionsPerSecond); + } + + [TestMethod] + public void CheckIdSharedHmacSha1Association() { + Protocol protocol = Protocol.Default; + string assocType = protocol.Args.SignatureAlgorithm.HMAC_SHA1; + double executionsPerSecond = this.ParameterizedCheckIdTest(protocol, assocType); + TestContext.WriteLine("{0} executions per second.", executionsPerSecond); + Assert.IsTrue(executionsPerSecond > 500, "Too slow ({0} > 500 executions per second required.)", executionsPerSecond); + } + + [TestMethod] + public void CheckIdSharedHmacSha256Association() { + Protocol protocol = Protocol.Default; + string assocType = protocol.Args.SignatureAlgorithm.HMAC_SHA256; + double executionsPerSecond = this.ParameterizedCheckIdTest(protocol, assocType); + TestContext.WriteLine("{0} executions per second.", executionsPerSecond); + Assert.IsTrue(executionsPerSecond > 400, "Too slow ({0} > 400 executions per second required.)", executionsPerSecond); + } + + private static double GetExecutionsPerSecond(int iterations, Stopwatch timer) { + return (double)iterations / (timer.ElapsedMilliseconds / 1000); + } + + private double ParameterizedCheckIdTest(Protocol protocol, string assocType) { + Association assoc = HmacShaAssociation.Create( + protocol, + assocType, + AssociationRelyingPartyType.Smart, + this.provider.SecuritySettings); + this.provider.AssociationStore.StoreAssociation(AssociationRelyingPartyType.Smart, assoc); + var checkidRequest = this.CreateCheckIdRequest(true); + Stopwatch timer = new Stopwatch(); + timer.Start(); + int iterations; + for (iterations = 0; timer.ElapsedMilliseconds < TestRunTime.TotalMilliseconds; iterations++) { + var request = (IAuthenticationRequest)this.provider.GetRequest(checkidRequest); + request.IsAuthenticated = true; + var response = this.provider.PrepareResponse(request); + Assert.IsInstanceOfType(response.OriginalMessage, typeof(PositiveAssertionResponse)); + } + timer.Stop(); + double executionsPerSecond = GetExecutionsPerSecond(iterations, timer); + TestContext.WriteLine("Responded to {0} checkid messages in {1}; or {2} authentications per second.", iterations, timer.Elapsed, executionsPerSecond); + return executionsPerSecond; + } + + private HttpRequestInfo CreateAssociateRequest(Uri opEndpoint) { + var rp = CreateRelyingParty(true); + AssociateRequest associateMessage = AssociateRequest.Create(rp.SecuritySettings, new ProviderEndpointDescription(opEndpoint, Protocol.Default.Version)); + Channel rpChannel = rp.Channel; + MemoryStream ms = new MemoryStream(); + StreamWriter mswriter = new StreamWriter(ms); + mswriter.Write(MessagingUtilities.CreateQueryString(rpChannel.MessageDescriptions.GetAccessor(associateMessage))); + mswriter.Flush(); + ms.Position = 0; + var headers = new WebHeaderCollection(); + headers.Add(HttpRequestHeader.ContentType, Channel.HttpFormUrlEncoded); + var httpRequest = new HttpRequestInfo("POST", opEndpoint, opEndpoint.PathAndQuery, headers, ms); + return httpRequest; + } + + private HttpRequestInfo CreateCheckIdRequest(bool sharedAssociation) { + var rp = CreateRelyingParty(true); + CheckIdRequest checkidMessage = new CheckIdRequest( + Protocol.Default.Version, + OPUri, + DotNetOpenAuth.OpenId.RelyingParty.AuthenticationRequestMode.Setup); + if (sharedAssociation) { + checkidMessage.AssociationHandle = SharedAssociationHandle; + } + checkidMessage.ClaimedIdentifier = OPLocalIdentifiers[0]; + checkidMessage.LocalIdentifier = OPLocalIdentifiers[0]; + checkidMessage.Realm = RPRealmUri; + checkidMessage.ReturnTo = RPUri; + Channel rpChannel = rp.Channel; + UriBuilder receiver = new UriBuilder(OPUri); + receiver.Query = MessagingUtilities.CreateQueryString(rpChannel.MessageDescriptions.GetAccessor(checkidMessage)); + var headers = new WebHeaderCollection(); + var httpRequest = new HttpRequestInfo("GET", receiver.Uri, receiver.Uri.PathAndQuery, headers, null); + return httpRequest; + } + } +} diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/OpenIdRelyingPartyTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/OpenIdRelyingPartyTests.cs index 68bbff3..f6a57e7 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/OpenIdRelyingPartyTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/OpenIdRelyingPartyTests.cs @@ -63,6 +63,21 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { Assert.AreEqual(1, requests.Count()); } + [TestMethod] + public void CreateRequestsWithEndpointFilter() { + var rp = this.CreateRelyingParty(); + StoreAssociation(rp, OPUri, HmacShaAssociation.Create("somehandle", new byte[20], TimeSpan.FromDays(1))); + Identifier id = Identifier.Parse(GetMockIdentifier(ProtocolVersion.V20)); + + rp.EndpointFilter = opendpoint => true; + var requests = rp.CreateRequests(id, RPRealmUri, RPUri); + Assert.AreEqual(1, requests.Count()); + + rp.EndpointFilter = opendpoint => false; + requests = rp.CreateRequests(id, RPRealmUri, RPUri); + Assert.AreEqual(0, requests.Count()); + } + [TestMethod, ExpectedException(typeof(ProtocolException))] public void CreateRequestOnNonOpenID() { Uri nonOpenId = new Uri("http://www.microsoft.com/"); @@ -79,5 +94,31 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { var requests = rp.CreateRequests(nonOpenId, RPRealmUri, RPUri); Assert.AreEqual(0, requests.Count()); } + + /// <summary> + /// Verifies that incoming positive assertions throw errors if they come from + /// OPs that are not approved by <see cref="OpenIdRelyingParty.EndpointFilter"/>. + /// </summary> + [TestMethod] + public void AssertionWithEndpointFilter() { + var coordinator = new OpenIdCoordinator( + rp => { + // register with RP so that id discovery passes + rp.Channel.WebRequestHandler = this.MockResponder.MockWebRequestHandler; + + // Rig it to always deny the incoming OP + rp.EndpointFilter = op => false; + + // Receive the unsolicited assertion + var response = rp.GetResponse(); + Assert.AreEqual(AuthenticationStatus.Failed, response.Status); + }, + op => { + Identifier id = GetMockIdentifier(ProtocolVersion.V20); + op.SendUnsolicitedAssertion(OPUri, GetMockRealm(false), id, id); + AutoProvider(op); + }); + coordinator.Run(); + } } } diff --git a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs index 7701090..083b988 100644 --- a/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/RelyingParty/PositiveAuthenticationResponseTests.cs @@ -59,6 +59,18 @@ namespace DotNetOpenAuth.Test.OpenId.RelyingParty { Assert.AreEqual(AuthenticationStatus.Failed, authResponse.Status); } + /// <summary> + /// Verifies that the RP rejects positive assertions with HTTP Claimed + /// Cdentifiers when RequireSsl is set to true. + /// </summary> + [TestMethod, ExpectedException(typeof(ProtocolException))] + public void InsecureIdentifiersRejectedWithRequireSsl() { + PositiveAssertionResponse assertion = this.GetPositiveAssertion(); + var rp = CreateRelyingParty(); + rp.SecuritySettings.RequireSsl = true; + var authResponse = new PositiveAuthenticationResponse(assertion, rp); + } + [TestMethod] public void GetCallbackArguments() { PositiveAssertionResponse assertion = this.GetPositiveAssertion(); diff --git a/src/DotNetOpenAuth.Test/TestBase.cs b/src/DotNetOpenAuth.Test/TestBase.cs index d21691b..f9db40c 100644 --- a/src/DotNetOpenAuth.Test/TestBase.cs +++ b/src/DotNetOpenAuth.Test/TestBase.cs @@ -57,5 +57,13 @@ namespace DotNetOpenAuth.Test { public virtual void Cleanup() { log4net.LogManager.Shutdown(); } + + protected internal static void SuspendLogging() { + LogManager.GetLoggerRepository().Threshold = LogManager.GetLoggerRepository().LevelMap["OFF"]; + } + + protected internal static void ResumeLogging() { + LogManager.GetLoggerRepository().Threshold = LogManager.GetLoggerRepository().LevelMap["ALL"]; + } } } diff --git a/src/DotNetOpenAuth.sln b/src/DotNetOpenAuth.sln index 24448b3..e6aff81 100644 --- a/src/DotNetOpenAuth.sln +++ b/src/DotNetOpenAuth.sln @@ -8,6 +8,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{20B5E173-C3C4-49F8-BD25-E69044075B4D}" ProjectSection(SolutionItems) = preProject DotNetOpenAuth.vsmdi = DotNetOpenAuth.vsmdi + ..\LICENSE.txt = ..\LICENSE.txt LocalTestRun.testrunconfig = LocalTestRun.testrunconfig ..\doc\README.Bin.html = ..\doc\README.Bin.html ..\doc\README.html = ..\doc\README.html @@ -16,7 +17,9 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Specs", "Specs", "{CD57219F-24F4-4136-8741-6063D0D7A031}" ProjectSection(SolutionItems) = preProject + ..\doc\specs\ICAM_OpenID20Profile.pdf = ..\doc\specs\ICAM_OpenID20Profile.pdf ..\doc\specs\OAuth Core 1.0.htm = ..\doc\specs\OAuth Core 1.0.htm + ..\doc\specs\OAuth Core 1.0a (Draft 3).htm = ..\doc\specs\OAuth Core 1.0a (Draft 3).htm ..\doc\specs\OpenID OAuth Extension.htm = ..\doc\specs\OpenID OAuth Extension.htm ..\doc\specs\openid-attribute-exchange-1_0.html = ..\doc\specs\openid-attribute-exchange-1_0.html ..\doc\specs\openid-authentication-1_1.html = ..\doc\specs\openid-authentication-1_1.html @@ -104,6 +107,9 @@ Project("{E24C65DC-7377-472B-9ABA-BC803B73C61A}") = "OpenIdRelyingPartyClassicAs EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OAuthConsumerWpf", "..\samples\OAuthConsumerWpf\OAuthConsumerWpf.csproj", "{6EC36418-DBC5-4AD1-A402-413604AA7A08}" + ProjectSection(ProjectDependencies) = postProject + {7ADCCD5C-AC2B-4340-9410-FE3A31A48191} = {7ADCCD5C-AC2B-4340-9410-FE3A31A48191} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "OpenID", "OpenID", "{034D5B5B-7D00-4A9D-8AFE-4A476E0575B1}" EndProject diff --git a/src/DotNetOpenAuth.vsmdi b/src/DotNetOpenAuth.vsmdi index 69bdc0f..a404b8e 100644 --- a/src/DotNetOpenAuth.vsmdi +++ b/src/DotNetOpenAuth.vsmdi @@ -17,12 +17,14 @@ <TestLink id="70c08ce3-cbd0-d553-61c0-a6d2ca203dc4" name="IsExtensionSupportedNullExtension" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1d5fb5a9-e15c-d99c-7a7e-95a4c4d123c2" name="DirectRequestsUsePost" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="6b218bf7-a4e9-8dac-d2c2-9bc3ee3ffc3e" name="EqualityTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="d6088ffe-ccf5-9738-131b-0fc1bc7e3707" name="TrimFragment" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1c531011-403a-0821-d630-d5433d968f31" name="CtorFromRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="73c6c979-205d-2216-d98d-2dd136b352c6" name="UtcCreationDateConvertsToUniversal" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e7aacb49-62ef-637d-ada2-0a12d836414d" name="ExtensionFactory" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="49a266cf-4ab6-3fdc-f4fd-21533f42c7cb" name="CtorWithProtocolMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="8375c7bb-b539-3396-885a-a3ca220078ec" name="InsufficientlyProtectedMessageSent" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="d12e8df0-1195-ab75-2275-7c8f854ddf98" name="UserSetupUrl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="21cf1f9a-063f-395a-f8aa-92c190c69146" name="SignaturesMatchKnownGood" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="d570770a-74e4-50ec-8eb9-91bd81c093ad" name="ParseNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a9f7897c-b299-807b-0612-384732cd10c9" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f4bec8d2-0531-34ab-8d50-bca260b58c61" name="ReadFromRequestWithContext" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a314e3b9-36a5-bfbb-3e15-e5003f22cf87" name="Serialize" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -30,15 +32,17 @@ <TestLink id="1bfbe1e1-3827-824f-27ad-4c990b0e22ab" name="Defaults" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ca9f3da7-e19f-b58b-54fe-54fa56ab9556" name="AddByKeyAndValue" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d766edce-59de-a03d-830a-0f0477521cff" name="ApplyHeadersToResponseNullAspNetResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="9fae8ab4-8436-eba1-3e4b-51511998fa8e" name="UnsolicitedAssertionRejected" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="84e718d7-bb82-e7d1-31be-471e2c154053" name="Item" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="889ba616-43dc-8a7f-ee13-46288969d617" name="ParameterNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fdf5b3df-239b-26fd-c1a2-152057195b7e" name="ReadFromRequestForm" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="58df167c-cf19-351c-cb09-5c52ae9f97be" name="DeserializeNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="555edc3b-5abf-7e46-b4f6-ddf44800b5df" name="SpreadSregToAXBasic" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="149a95cf-a538-f853-e11b-3133c15579c5" name="RequestTokenUriTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f3cbbcda-49ff-fc43-140b-f362081654c3" name="CtorNullTypeUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fa52f2db-fc1e-ba31-cc5e-0bcc05998187" name="NoValue" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="65752c29-fa1f-7b88-bbec-5329af8db4d8" name="IsValid" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="cd1142a5-f77a-5626-a739-65eb0228bf7d" name="ProtocolDetection" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="68532d6d-a0cf-5883-17e2-6060707ba9ae" name="DecodeOobToNullUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="188ce83b-3117-adb5-4b89-12f2b09be1de" name="CtorSimpleConsumer" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="93c157e8-1293-3aff-f616-66502872b37d" name="DiscoveryRequiresSslIgnoresInsecureEndpointsInXrds" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f6ecb459-cc64-36ee-438c-4514e9413586" name="AddAttributeByPrimitives" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -62,16 +66,15 @@ <TestLink id="c11e5541-0a92-85ab-4f90-0db7766ebdcb" name="CtorUnsolicited" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="8aecb3a5-2cb5-143d-aa99-9514fa8dfacb" name="AddAttributeByValue" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ef6cebca-f8da-edf6-0217-8bb854710090" name="DiscoveryCommunityInameDelegateWithoutCanonicalID" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="64b41c6c-2b67-af35-0c93-df41bd6f2dbb" name="Store" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="b09311d4-4dea-6786-3e59-9c62fe16e301" name="ParameterNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ad56539c-6156-5f62-a98a-b24ae0159cc6" name="XmlSerialization" storage="..\bin\debug\dotnetopenauth.test.dll" enabled="false" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="54eae9ed-bed1-eeda-b6ea-045c8f7e2ba5" name="SendIndirectMessage301GetNullFields" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2d0ee03a-f082-768c-a0db-574ac8efeffb" name="Valid" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="6c20a52a-bab7-e84e-faca-fd79ec5303d9" name="CtorCountZero" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7fb8d29c-c8ea-7f88-ed42-ae7368d6a429" name="CtorNullStore" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="50986611-9de6-a112-2fe8-691210989f45" name="IsTypeUriPresent" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f17424d2-ed4b-1ea0-a339-733f5092d9d0" name="MaximumAuthenticationAgeTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c63c9935-54a0-398a-f44b-214e17faf1f1" name="SendDirectMessageResponseHonorsHttpStatusCodes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="b4b00582-dcc9-7672-0c02-52432b074a92" name="GetNullType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="163c8ba8-f829-c21e-a5a1-3c4565ec4425" name="UnifyExtensionsAsSregNoExtensions" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="054484ce-12c5-83ad-49a4-b241cd81557d" name="ClaimedIdentifier" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e8337858-a320-8aad-51aa-402e65a90b75" name="ReplayDetectionTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="507cd1b6-1010-0bca-cf7f-f96e3f4f6c6c" name="QueryBeforeSettingUrl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -80,15 +83,17 @@ <TestLink id="715dcbdd-28f5-3c33-7d88-e0a1b648d89a" name="CreateRequestDumbMode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="85a71d28-5f2f-75ce-9008-94982438bb5f" name="EqualityTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f334cc44-b2d0-2d67-358a-532def3bee80" name="ContainsKeyValuePair" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="fa05cc5f-2aaf-da22-ff52-caf1c3c6bb08" name="InsecureIdentifiersRejectedWithRequireSsl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7debb527-142a-6ca6-3b9b-1e131c18e801" name="AccessTokenUriTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="98e7a0f9-ab6c-7ff1-3a2c-00d8244e1bec" name="CommonMethods" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="44afc59c-60fc-3179-b5a6-1e58e7752d54" name="ApplyHeadersToResponseNullHeaders" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f583b298-139a-e733-dde6-f9dc4b73d4bf" name="SendDirectMessageResponseHonorsHttpStatusCodes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="af7cb01c-950e-23d7-0f32-082b7af8b382" name="CtorNullToObject" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a260d196-066f-b0ae-a40e-fb9d962b28a4" name="XrdsDirectDiscovery_20" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="13acd546-c82e-324c-220d-34f42a6d705e" name="DeserializeSimple" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b56cdf04-0d29-8b13-468c-fb4b4258c619" name="CtorNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2c2b48d0-8009-e7e0-9ff4-34f9973f59da" name="EqualsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="63e5025b-5ccf-5f13-6e05-d1e44502a6e9" name="RequestBadPreferredScheme" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f44fb549-fc8a-7469-6eed-09d9f86cebff" name="SendDirectMessageResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="8fc08a6d-6dcf-6256-42ff-073d4e4b6859" name="RequireDirectedIdentity" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f20bd439-e277-dc27-4ec4-5d5949d1c6bf" name="RequestUsingAuthorizationHeaderScattered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3fc3ac8d-7772-b620-0927-f4bd3a24ce2f" name="SendNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5f3758b3-1410-c742-e623-b964c01b0633" name="AuthenticationTimeUtcConvertsToUtc" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -97,10 +102,9 @@ <TestLink id="7b1fb2c4-39c0-0d39-700c-96d992f5a01f" name="AuthenticationTimeUtcSetUnspecified" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="00089858-d849-1e5f-4fb5-31d8d0590233" name="VerifyArgumentNotNullThrows" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="54a65e0b-1857-72b9-797b-fe3d9a082131" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="352d9fd6-cf38-4b72-478f-e3e17ace55f5" name="NoValueLoose" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f17424d2-ed4b-1ea0-a339-733f5092d9d0" name="MaximumAuthenticationAgeTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d067c55c-3715-ed87-14a2-c07349813c94" name="IsDirectedIdentity" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="8fc08a6d-6dcf-6256-42ff-073d4e4b6859" name="RequireDirectedIdentity" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="3aa4e498-fd14-8274-22da-895436c1659e" name="AssociateUnencrypted" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="2a7b77c3-27d5-7788-e664-5d20118d223b" name="OPRejectsHttpNoEncryptionAssociateRequests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1f46ce86-bc66-3f5c-4061-3f851cf6dd7f" name="HtmlDiscover_20" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2f9d176e-4137-63bd-ee2a-6b79fde70d0d" name="Clear" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fc7af2d7-6262-d761-335b-ef3ec029484d" name="DeserializeVerifyElementOrdering" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -111,8 +115,12 @@ <TestLink id="8e86c2fd-24b9-44c5-7cda-d66aa7cd4418" name="Serializable" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="06ec5bce-5a78-89c3-0cda-fa8bddfea27d" name="SetCountZero" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e2287de6-cbd2-4298-3fb8-297013749e70" name="SendIndirectMessageFormPostNullFields" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="db8d66cc-8206-57cc-0ce5-c8117813d77c" name="UnifyExtensionsasSregFromSchemaOpenIdNet" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ef8a2274-4e58-0dde-4c5c-7f286865fc3a" name="SendReplayProtectedMessageSetsNonce" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="63e5025b-5ccf-5f13-6e05-d1e44502a6e9" name="RequestBadPreferredScheme" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="decb3fef-ef61-6794-5bc6-f7ff722a146e" name="EqualsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0d99e0a9-295e-08a6-bc31-2abb79c00ff8" name="IsReturnUrlDiscoverableRequireSsl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f41ce7ab-5500-7eea-ab4d-8c646bffff23" name="HttpSchemePrepended" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="55b078e4-3933-d4e0-1151-a0a61321638e" name="ReadFromRequestAuthorization" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="38239ff0-1dfd-1116-55df-2790243dc768" name="IsValid" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="04be6602-31a2-f4ae-8fdb-b9ad2ac370be" name="PrepareMessageForReceiving" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -123,9 +131,9 @@ <TestLink id="385c302d-b546-c164-2a59-2e35f75d7d60" name="RemoveStructDeclaredProperty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="870cce9d-5b17-953c-028f-827ec8b56da2" name="GetInvalidMessageType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="97f0277a-86e6-5b5a-8419-c5253cabf2e0" name="UserAuthorizationUriTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="643add47-e9f3-20b8-d8e0-69e3f8926d33" name="CreateRequestsWithEndpointFilter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d66a3b7a-1738-f6b3-aed1-e9bc80734ae9" name="CtorNullString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f3f84a10-317f-817a-1988-fddc10b75c20" name="AddTwoAttributes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="643c9887-3f12-300e-fdac-17ae59652712" name="Mode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e03f0038-5bb7-92f2-87a7-00a7d2c31a77" name="MessageExpirationWithoutTamperResistance" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1f3ea08b-9880-635f-368f-9fcd3e25f3cd" name="ReadFromRequestNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9104f36f-6652-dcbb-a8ae-0d6fc34d76ed" name="AddCallbackArgumentClearsPreviousArgument" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -133,12 +141,12 @@ <TestLink id="f4b313bb-cebc-a854-ffbd-6c955d850a05" name="VerifyGoodTimestampIsAccepted" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="77047207-0571-72d5-71bd-586b878bcc0c" name="Base64Member" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f919a731-cc5c-88c6-5582-639b272d64fc" name="IsReturnUrlDiscoverableValidResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="3c438474-63f3-b56c-dcba-1ed923fcdbdd" name="CreateResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="a8bd3730-1660-dca9-87ec-23bc9dc39ab9" name="CtorGoodXriSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="4a00f3ab-f405-95a7-d745-2fcf7787eb56" name="GetNonexistentHandle" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f4537b23-bb5e-5c6f-da53-64b34472f0dc" name="ChannelGetter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="75fa4664-bb0e-3a54-de29-c18ac712b231" name="Mode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="94ba9fd3-851d-13b2-e273-6294b167c13e" name="HttpsSignatureVerification" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="ff78d828-b437-aaeb-e48a-85a5ad1fe396" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="cbfeb75b-d031-7df3-c281-3c9e1c450042" name="CtorFromRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5460f9c6-ec9d-969d-5aff-b946d6776e25" name="CtorWithNullProtocolMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d5f4e610-eabe-1dc0-ab3f-7c9dcb17fcc3" name="CtorImpliedLocalIdentifier" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="64d8c630-76c6-e420-937b-19c889dd7f59" name="CtorNonMessageType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -148,6 +156,7 @@ <TestLink id="a79e43c9-ad5a-5543-51ff-22271ec87ab0" name="PrepareMessageForSendingNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7ca16e07-126d-58ac-2ac5-a09a8bf77592" name="InvalidRealmBadWildcard1" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="832dbf28-5bf2-bd95-9029-bf798349d917" name="GetCallbackArguments" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="11108b79-f360-9f7c-aebc-2d11bebff96a" name="ReadFromRequestForm" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="53cbbf4a-89d3-122b-0d88-662f3022ce26" name="OpenIdMaxAuthenticationTime" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="20646985-c84a-db8e-f982-ec55d61eaacd" name="ResponseNonceSetter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c2c78c43-7f50-ffc3-affb-e60de2b76c94" name="CreateQueryStringNullDictionary" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -157,7 +166,9 @@ <TestLink id="b2e1bba0-ab24-cdd5-906c-a3655814ab2d" name="SendSetsTimestamp" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2f1a3fc4-77ec-2ae3-668c-9e18f9ab0ebe" name="SendIndirectMessage301Get" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e4403d9e-73c1-967d-345c-4a2c83880d4e" name="EqualsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="6ce37652-2f47-6952-fb6d-568c2ca85224" name="TransformAXFormatTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a145f430-8062-5ad7-0cf5-b51eba0f8de7" name="HttpsSignatureGeneration" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="a6ea74e5-8681-4eb4-a51b-5051e5f7603c" name="NonFieldOrPropertyMember" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="066ce22f-103c-56ee-0250-d9e28d43ffcd" name="Values" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="bb542259-4c10-4b88-1b3c-f842b0bb49a9" name="ImmediateVsSetupModes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="71564ca7-7845-92b3-7433-2f2beeb6b9f7" name="VerifyNonZeroLengthOnNonEmpty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -167,10 +178,12 @@ <TestLink id="c7f6459d-9e6e-b4bc-cae8-65f5a3785403" name="SendIndirectMessageNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="aa79cdf5-e0bc-194e-fdbb-78369c19c30f" name="ConstantFieldMemberInvalidValues" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="dd9e3279-2d7e-e88e-ccfa-ef213055fc3d" name="SendDirectedNoRecipientMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="b384002f-26a9-7dde-c3f6-9ceff34dd8e2" name="GetRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="5a77a48f-00d6-da6f-5ef7-c897ebf8fe6b" name="EscapeUriDataStringRfc3986Tests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="81f670d0-d314-c53c-9d91-c0765dfc30c1" name="MessagePartsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="efd570c9-5e74-17e4-f332-ac257c8e8aff" name="RealmReturnToMismatchV1" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fda58c48-e03a-73a3-4294-9a49e776ffb6" name="CtorWithTextMessageAndInnerException" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="660ad25a-b02b-1b17-7d6e-3af3303fa7bc" name="ModeEncoding" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="e7a41771-7dda-be44-0755-e06300f3cd92" name="IsSaneTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ae384709-e9a4-0142-20ba-6adb6b40b3e2" name="CtorStringHttpsSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3b70dd09-384d-5b99-222b-dc8ce8e791f2" name="SecuritySettingsSetNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f787ae5d-b8fc-0862-a527-9157d11bbed7" name="UntrustedWebRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -181,12 +194,12 @@ <TestLink id="be6a14aa-c0d9-cf61-286a-236b92239597" name="EnumerableCache" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5a4df395-962e-0b7c-de71-abcb7e8930db" name="CreateFiltersDelegatingIdentifiers" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="bf73c7f2-33b1-8e18-c4f6-cb8609388754" name="DiscoveryRequireSslWithInsecureXrdsInSecureHttpHeader" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f17128c1-5953-5391-ed75-c33774eacbfc" name="LastLineNotTerminated" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="feb0a53e-1592-b878-b70c-1a272d9c6908" name="SpreadSregToAxTargetedAtOPFormat" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e071a119-c7e9-1a55-b132-72e161fea598" name="CtorAndProperties" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a6e464af-42df-1ba4-17e5-b955352664b5" name="RPOnlyRenegotiatesOnce" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0290975f-02ce-d8a7-d723-5dae623cab46" name="CtorNullTokenManager" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3b535521-90c8-7f49-545f-bcfc4ad16d40" name="UnresponsiveProvidersComeLast" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="77934ac4-bd65-7ad8-9c53-9c9447f9e175" name="GetReturnToArgumentAndNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="4bd86299-18d7-abbe-e5d2-1afad17279e9" name="Parse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0443f5f8-aa08-80d5-dcc6-261802debe5a" name="XrdsDirectDiscovery_10" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="6c95f443-463e-2856-f500-b9029645e44c" name="RequestNullRecipient" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="afafb5ef-662e-2da3-35b8-1d67bb0d79ce" name="AddPolicies" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -195,6 +208,7 @@ <TestLink id="cab73921-470b-331f-e601-b44805b67c81" name="GetAttributeValue" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="14ce54ee-5507-ac70-5514-99b7b83ba3d6" name="ExtensionFactories" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ebd84587-bbc2-9889-c500-b6fbdf2bf209" name="GetRequestNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="565140c9-c9fe-9466-1e39-740d7e368cb5" name="TryParse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="782d64c8-46af-a624-b3f6-a65aeaa57bfe" name="LastLineNotTerminatedLoose" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="30a8eab6-6423-26af-da1a-ec304935fe43" name="RemoveNonexistentHandle" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e9bc3f63-aeb1-d84d-8abc-fc6ed77955e6" name="SignedResponsesIncludeExtraDataInSignature" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -221,18 +235,19 @@ <TestLink id="5435ab79-de25-e2fc-0b2d-b05d5686d27d" name="IsUrlWithinRealmTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="501fa941-c1ac-d4ef-56e7-46827788b571" name="GetRequestNoContext" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="01e33554-07cc-ff90-46f8-7d0ca036c9f6" name="ToDictionaryNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="3aa4e498-fd14-8274-22da-895436c1659e" name="AssociateUnencrypted" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0215f125-3936-484e-a8d0-d940d85bbc27" name="AppendQueryArgsNullDictionary" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7c048c58-c456-3406-995f-adb742cc2501" name="DeserializeInvalidMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="704a32d0-3f50-d462-f767-fd9cf1981b7f" name="ProviderVersion" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f362baf3-da5b-1b8c-39ae-7c9b2051270a" name="AuthenticationTimeUtcSetUtc" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3bb818b4-5423-ad91-8cd9-8606ec85d2cb" name="ReadFromRequestAuthorizationScattered" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="cbfeb75b-d031-7df3-c281-3c9e1c450042" name="CtorFromRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="02333934-cfea-2fb6-5e08-7a24be050f44" name="CreateRequestsOnNonOpenID" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="30f3c12b-e510-de63-5acd-ae8e32866592" name="CreateQueryString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="9302541c-9c30-9ce7-66db-348ee4e9f6ee" name="UnifyExtensionsAsSregWithSreg" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="997253fb-7591-c151-1705-02976b400f27" name="AddAttributeTwice" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9bf0528f-c3ab-9a38-fd8a-fd14bade0d0b" name="EnumerableCacheCurrentThrowsAfter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="8346368c-9c8a-de76-18dd-5faeeac3917d" name="OPRejectsMismatchingAssociationAndSessionTypes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3d0effa3-894a-630c-02b0-ada4b5cef795" name="CtorNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="d12e8df0-1195-ab75-2275-7c8f854ddf98" name="UserSetupUrl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="3c438474-63f3-b56c-dcba-1ed923fcdbdd" name="CreateResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="47706bc6-7bee-0385-62b4-4f9cec6cc702" name="CtorWithTextMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2495fc9b-d766-5ae7-7324-f044c4ce1242" name="AddNullValue" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fb6c270f-ff72-73f4-b8b3-82851537427c" name="MultiVersionedMessageTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -246,10 +261,9 @@ <TestLink id="93041654-1050-3878-6b90-656a7e2e3cfd" name="CtorDefault" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e2ab77b2-a6dc-f165-1485-140b9b3d916f" name="EqualityTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="035cd43a-23d5-af91-12ee-0a0ce78b3548" name="XrdsDiscoveryFromHttpHeader" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f70b368e-da33-bc64-6096-1b467d49a9d4" name="NonIdentityRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="dbf7855c-0cc6-309f-b5f5-022e0b95fe3b" name="QueryStringLookupWithoutQuery" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="abb0610a-c06f-0767-ac99-f37a2b573d1b" name="ParameterPrefix" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="b2b54c72-1d26-8c28-ebf5-7a5a4beeec43" name="VerifyNonZeroLengthOnNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="32532d1f-d817-258d-ca72-021772bfc185" name="UriEncodeDecode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f3345901-2e76-34dd-32f1-0b312d6e1c1e" name="IsReturnUrlDiscoverableNotSsl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1deb0ca9-923a-8ef7-7a24-d5d5af04acdf" name="SpecAppendixAExample" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b32b6295-d4a9-3369-f072-28a71e84d4e8" name="SerializationWithUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -268,6 +282,7 @@ <TestLink id="f4893153-bb84-bf45-7889-8350a7e1db66" name="DiscoveryRequireSslWithInsecureXrdsInSecureHtmlHead" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5218fba2-d1af-e1f4-7641-9ae1d4975430" name="DirectResponsesSentUsingKeyValueForm" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a1a0178c-cd4a-1651-8535-3c9ee3d40821" name="ToDictionaryWithNullKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="0f36556d-ece7-eb70-8597-a9d085165c2c" name="Sign" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="19d2219e-c04d-fa3a-5e26-92448f35f21d" name="RespondNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="eb932fc7-76c7-b63f-e1e6-a59dea8e4da1" name="AddAttribute" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a59c5dc0-de4d-8136-8545-2e2e9616de46" name="SerializationWithXri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -291,6 +306,7 @@ <TestLink id="87593646-8db5-fb47-3a5b-bf84d7d828c2" name="InvalidMessageTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="65f16786-7296-ee46-8a8f-82f18b211234" name="AddByKeyValuePair" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c891c6bc-da47-d4ab-b450-f3e3a0d6cba8" name="NoAssociationNegative" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="47e8fae9-542d-1ebb-e17c-568cf9594539" name="RelativeUriDecodeFails" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f3af5fd8-f661-dc4f-4539-947b081a8b54" name="ReceivedReplayProtectedMessageJustOnce" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a14ddf08-796b-6cf1-a9bf-856dd50520fa" name="RequiredProtection" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5b4fee50-7c15-8c6b-3398-c82279646e5f" name="RequiredOptionalLists" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -302,6 +318,7 @@ <TestLink id="ba35acc7-78d2-6710-57ac-6843210d4202" name="UserSetupUrlRequiredInV1Immediate" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f18b514c-4f78-5421-8bdf-8b0f1fdf2282" name="HandleLifecycle" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1e2ae78c-d2f3-a808-2b82-eca9f9f2e458" name="Keys" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="9173c754-a358-91cc-a8f0-2c2703a55da8" name="AssertionWithEndpointFilter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="6badbaa8-33d1-13c4-c1f9-aef73a9ac5bf" name="InvalidRawBirthdate" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="0435e38a-71f2-d58d-9c07-d97d830a1578" name="ExtensionResponsesAreSigned" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2f2ea001-a4f8-ff0d-5d12-74180e0bf610" name="HttpsSignatureVerificationNotApplicable" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -316,23 +333,29 @@ <TestLink id="72d3f240-67f2-0b04-bd31-a99c3f7a8e12" name="SharedAssociationPositive" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3cd9447e-9ffd-f706-37bb-e7eb5828e430" name="InvalidRealmEmpty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="28fe030c-d36e-13cf-475c-7813210bf886" name="AddAttributeRequestAgain" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="e7a41771-7dda-be44-0755-e06300f3cd92" name="IsSaneTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="e344ba35-96b7-d441-c174-8c8b295fd157" name="AddCallbackArgument" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2e23dc5a-93ea-11a5-d00d-02d294794e5f" name="AssociateDiffieHellmanOverHttps" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f41ce7ab-5500-7eea-ab4d-8c646bffff23" name="HttpSchemePrepended" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="8d0df47c-c381-0487-6c19-77548ad7fc13" name="UnifyExtensionsAsSregWithBothSregAndAX" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="2237b8ce-94ce-28c1-7eb2-14e59f47e926" name="UnifyExtensionsAsSregFromAXSchemaOrg" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="069995aa-4136-610b-3f41-df80a138c244" name="AppendQueryArgsNullUriBuilder" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="457d6b32-d224-8a06-5e34-dbef3e935655" name="HttpSignatureVerification" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="309fdc0f-150c-5992-9a79-63be5f479d89" name="RequiredProtection" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="2e1b27e8-2e3e-0290-2bee-d88e2914efd9" name="SpreadSregToAXNoExtensions" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c15c3ab5-e969-efc9-366d-78ebc43ce08f" name="Fetch" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7bf8e806-68a1-86bc-8d91-9a99d237d35c" name="CreateRequestMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a883dc73-d6be-e59a-6da2-0db1d4452679" name="BindingElementsOrdering" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5dcd69c3-e979-7316-4551-a73fe4645dcd" name="SecuritySettings" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f533bf9e-daa1-b26a-4789-372f3a9291d6" name="TryRequireSslAdjustsIdentifier" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f44fb549-fc8a-7469-6eed-09d9f86cebff" name="SendDirectMessageResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="11108b79-f360-9f7c-aebc-2d11bebff96a" name="ReadFromRequestForm" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="6ef9df5a-d069-0103-5260-593808f232da" name="XrdsDiscoveryFromHead" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="313faac6-6357-5468-2d4d-4c9fba001678" name="TryParseNoThrow" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7cbe4350-38d0-db7e-335c-93d9398fc95b" name="ExtensionOnlyFacadeLevel" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2f5cfa57-bcb4-39af-e769-2d7c34e2598e" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="ff78d828-b437-aaeb-e48a-85a5ad1fe396" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="29e45877-ca7a-85de-5c39-6d43befe1a1e" name="DiscoveryRequireSslWithInsecureXrdsButSecureLinkTags" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c351c660-d583-d869-0129-2e312665d815" name="CtorBlank" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f063a3c6-5a36-2801-53d7-5142416199a9" name="ImplicitConversionFromStringTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="809afd59-8f10-ce37-6630-06b59351a05a" name="CommonProperties" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="02333934-cfea-2fb6-5e08-7a24be050f44" name="CreateRequestsOnNonOpenID" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5298ecb0-bcad-9022-8b93-87793eb2c669" name="UnsolicitedDelegatingIdentifierRejection" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="068dcefa-8f2b-52c3-fe79-576c84c5648b" name="CtorBlank" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="315b5857-7697-8222-f94c-f6f10d539491" name="BaseSignatureStringTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -355,21 +378,23 @@ <TestLink id="b71e8878-b20e-5d96-bce4-7f10831ceaf8" name="AddPolicies" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ddf4f3ec-07bb-09e8-b5e8-0837cb8cb684" name="IsReturnUrlDiscoverableNoResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a18ae750-318b-bb1f-c2b3-c31da845c085" name="Count" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="a8bd3730-1660-dca9-87ec-23bc9dc39ab9" name="CtorGoodXriSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="5b451648-5ca1-4395-333d-bbcb098f4a45" name="NoRequestedExtensions" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="cb9a8325-abf5-5d97-a94e-a6d34f2b51e1" name="AssociateRenegotiateLimitedByRPSecuritySettings" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="9fae8ab4-8436-eba1-3e4b-51511998fa8e" name="UnsolicitedAssertionRejected" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="5271f941-565f-5977-6884-82cef09161db" name="ParseEndUserSuppliedXriIdentifer" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="7cf52613-a212-8a0f-843f-37f08740c125" name="SpreadSregToAxNoOpIfOPSupportsSreg" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7c8eac5a-0455-e038-0e9a-10e59d459452" name="CtorUriHttpSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f69f1c0c-e258-95fb-4fcb-ad14bfc40e3c" name="Discover" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ccfda025-cb1a-a2ff-78bd-5e9af885ae0b" name="ToDictionary" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="64142858-d52e-be06-d11f-6be326c6176b" name="RespondTwoValues" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="03b47440-3d09-ab28-97f1-39809f5703b6" name="NormalizeCase" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="34357633-4745-6fba-9316-493d3c6c5b90" name="ParseEmpty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="85a0dec0-983c-8f21-b093-a2179624cc88" name="UnifyExtensionsAsSregWithSreg" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5c66a1b8-5b20-2e3b-8427-d6ff4640ac53" name="BadRequestsGenerateValidErrorResponses" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="4ba7ca33-72f1-3fc6-d37c-65134eda904d" name="AddDeclaredValueThatAlreadyExists" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="a6ea74e5-8681-4eb4-a51b-5051e5f7603c" name="NonFieldOrPropertyMember" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="06b350b0-79d1-9393-7620-cd919061898c" name="ParseEndUserSuppliedUriIdentifier" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="46ec24da-deb7-27c7-6dc6-52090e4fd1fb" name="Serialize" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e9a5efc6-fde8-8fa4-0bda-2675a4a7e06b" name="DefaultReferenceTypeDeclaredPropertyHasNoKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b70b4bd5-6dae-b4ad-349c-c3ad70603773" name="ReadFromRequestQueryString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="2a7b77c3-27d5-7788-e664-5d20118d223b" name="OPRejectsHttpNoEncryptionAssociateRequests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5f02e24c-2972-c598-ca71-ea362b2fe7d8" name="SecuritySettingsSetNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="98a2ece8-c9e6-e6f3-c65e-f915b22077fa" name="RequestUsingGet" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2cfefc4a-918a-3e16-0670-53eb33634525" name="GeneratesOnlyRequiredElements" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -379,9 +404,11 @@ <TestLink id="7650ec62-b144-f36f-8b56-31ad20521d0e" name="DoesNotStripFragment" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e2b1ae2a-8f30-b6b3-bca6-ef28fc5a0175" name="ClaimedIdAndLocalIdSpecifiedIsValid" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="58d69d1e-3bd2-3379-0af1-188f9cff2dd0" name="IsTypeUriPresentEmpty" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="495dd486-08dd-d365-7a84-67d96fef8460" name="SendIndirectedUndirectedMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="7cdabb8a-aefa-e90e-c32e-047404b64c2d" name="SerializeTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="b4b00582-dcc9-7672-0c02-52432b074a92" name="GetNullType" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f6979feb-7016-4e2b-14e2-e6c2c392419f" name="RemoveByKeyValuePair" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="e344ba35-96b7-d441-c174-8c8b295fd157" name="AddCallbackArgument" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="58df167c-cf19-351c-cb09-5c52ae9f97be" name="DeserializeNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a778f331-f14e-9d6e-f942-a023423540f6" name="Ctor" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a1ff4ada-fe5d-d2f3-b7fb-8e72db02b3c3" name="Full" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="be00d3ef-f24d-eb8a-d251-4d691736ee6f" name="AddAttributeRequestNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -396,28 +423,29 @@ <TestLink id="c79dd056-8fff-3393-f125-4b83cf02cb3b" name="RequireSsl" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5a0d31d9-9c70-2a28-3e8c-46e8e047ac2d" name="ReceiveNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="4b44b825-36cc-77f8-3a4a-5892c540f577" name="GetValue" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="495dd486-08dd-d365-7a84-67d96fef8460" name="SendIndirectedUndirectedMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f17128c1-5953-5391-ed75-c33774eacbfc" name="LastLineNotTerminated" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="671ddaf5-238d-a517-b0f3-d79bd591a396" name="EmptyMailAddress" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d8118997-ecf7-7130-f068-5e2bc867786d" name="SerializeNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e9c2087b-1c52-5bb9-bf4e-9046cf281e36" name="DiscoverRequireSslWithInsecureRedirect" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="25e2c593-2e69-6215-90c0-67f269939865" name="CtorEmptyTypeUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="e1e9dde8-30e6-6ce0-d5a6-4e22e0347ac4" name="UnifyExtensionsAsSregWithAX" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f50a0bdb-380e-30f6-492a-a6dd9664d0f0" name="ExtensionOnlyChannelLevel" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="6ef9df5a-d069-0103-5260-593808f232da" name="XrdsDiscoveryFromHead" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="b384002f-26a9-7dde-c3f6-9ceff34dd8e2" name="GetRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="64b41c6c-2b67-af35-0c93-df41bd6f2dbb" name="Store" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="44afc59c-60fc-3179-b5a6-1e58e7752d54" name="ApplyHeadersToResponseNullHeaders" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="2d82ac4b-99b4-a132-eb62-d943e02d1498" name="ApplyHeadersToResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="599add9e-e9eb-5e8a-ce6b-6dc73c2bb408" name="DataContractNamespace" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="f583b298-139a-e733-dde6-f9dc4b73d4bf" name="SendDirectMessageResponseHonorsHttpStatusCodes" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="643c9887-3f12-300e-fdac-17ae59652712" name="Mode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="ed7efca3-c3c1-bc4a-cef7-eaf984749355" name="ValidMessageReceivedTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="44ced969-83dd-201d-a660-e3744ee81cf8" name="ConstructorTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="1c5d54e2-d96a-d3a6-aeac-95f137b96421" name="CommonMethods" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e9cceef5-383d-92f0-a8bb-f3e207582836" name="RealmReturnToMismatchV2" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="e6b412e5-3a53-e717-6393-254e1c93e239" name="PassThruDoubleCache" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="0f36556d-ece7-eb70-8597-a9d085165c2c" name="Sign" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="8375c7bb-b539-3396-885a-a3ca220078ec" name="InsufficientlyProtectedMessageSent" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="924b5295-0d39-5c89-8794-22518091e05a" name="CtorNullToString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="a63c169c-4e9a-bcba-b7cd-c4c5280cd652" name="PrepareMessageForSendingNonExtendableMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="63944cb8-4c61-c42c-906f-986fa793370b" name="SignatureTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="5a77a48f-00d6-da6f-5ef7-c897ebf8fe6b" name="EscapeUriDataStringRfc3986Tests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="309fdc0f-150c-5992-9a79-63be5f479d89" name="RequiredProtection" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="77934ac4-bd65-7ad8-9c53-9c9447f9e175" name="GetReturnToArgumentAndNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="735b7a56-0f6f-77d8-8968-6708792a7ce8" name="UnifyExtensionsAsSregWithAXSchemaOpenIdNet" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="4a5b601d-475d-e6cc-1fec-19a2850681ad" name="Serializable" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9bcc2d64-870f-7675-a314-fbb975446817" name="IsApprovedDeterminesReturnedMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3e676e31-3b6d-9d12-febd-d632ece804ec" name="RPRejectsMismatchingAssociationAndSessionBitLengths" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -428,16 +456,19 @@ <TestLink id="d647fd93-40b3-24d5-25fc-661c0d58335c" name="SendIndirectMessageFormPostNullMessage" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="62c6ee5b-ac29-461c-2373-bf620e948825" name="InvalidRealmNoScheme" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="115283b9-d95c-9a92-2197-96685ee8e96a" name="TwoExtensionsSameTypeUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="80719076-10fd-20a7-7ff3-a0aa2bc661cb" name="CtorNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="352d9fd6-cf38-4b72-478f-e3e17ace55f5" name="NoValueLoose" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="b2b54c72-1d26-8c28-ebf5-7a5a4beeec43" name="VerifyNonZeroLengthOnNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="9684f7bf-cdda-a2c5-0822-29cb0add3835" name="ResponseNonceGetter" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c4001e1c-75ad-236b-284f-318905d2bc3a" name="CreateRequestOnNonOpenID" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b58e4791-68c0-1bc0-2e48-e1351459ee46" name="UserSetupUrlSetForV1Immediate" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="30f3c12b-e510-de63-5acd-ae8e32866592" name="CreateQueryString" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="c0d7cfcc-4f7e-e7df-3de2-b578c4c3d6ee" name="SpreadSregToAxMultipleSchemas" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="32e95494-d0bb-cfc7-a8d6-652f8816c6b4" name="ReadFromResponse" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f967c0af-c04c-d156-4faf-8978bfcab5d7" name="RequiredNullableStruct" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="248f0afc-979f-a86f-e7de-fdeb4f9dd3ea" name="CtorBadUri" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="536ecd26-4bda-a35e-5af8-666eb9b44940" name="NullValueEncoding" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="fa2e5bbd-4c41-f2b1-e875-38c6ef011fa1" name="RandomCharactersTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3e2f1dad-3684-587c-9039-8d116582be10" name="GetReturnToArgumentEmptyKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="00ed61cd-46cd-9c0e-f044-38d784c8bcfb" name="DecodeEmptyStringFails" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="09b892f2-96e9-45b7-d082-b0bb512c1dd4" name="RequiredNonNullableStruct" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="d5912d3e-441c-a20e-20a2-0b9f0220a762" name="ParameterNames" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="439c8c16-2ba5-eb3b-b631-ce50ec48eba0" name="CtorNullMember" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -446,18 +477,19 @@ <TestLink id="bdba0004-be80-f5c1-1aae-487db09bdf04" name="GetReturnToArgumentDoesNotReturnExtraArgs" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="f1e1aa37-c712-6096-22fa-394008f0820a" name="CtorNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="cb48421f-f4ff-3994-3abc-4be35f8bfd99" name="AssociateQuietlyFailsAfterHttpError" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="decb3fef-ef61-6794-5bc6-f7ff722a146e" name="EqualsTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="660ad25a-b02b-1b17-7d6e-3af3303fa7bc" name="ModeEncoding" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="736a09b4-f56e-0176-6c1c-81db0fbe3412" name="CtorUriHttpsSchemeSecure" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="9f880280-aa8f-91bb-4a5f-3fe044b6815a" name="CreateVerificationCode" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="10a8b8e5-e147-838c-0708-be98d5e4490e" name="CtorFull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="6daa360b-71e4-a972-143f-01b801fada84" name="DeserializeWithExtraFields" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="8bbc6a02-b5a4-ea8e-2a77-8d1b6671ceb5" name="ImplicitConverstionFromUriTests" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="d6088ffe-ccf5-9738-131b-0fc1bc7e3707" name="TrimFragment" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="80719076-10fd-20a7-7ff3-a0aa2bc661cb" name="CtorNull" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="90d3c411-8895-a07f-7a21-258b9d43c5b2" name="InvalidMessageNoNonceReceivedTest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="121983e3-1336-70cb-8d2a-498629e92bec" name="GetReturnToArgumentNullKey" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="5e0c892d-7ad8-6d56-1f1d-2fb6236670d6" name="CtorDefault" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="643d722c-2c2b-fbd8-a499-5a852ef14dc7" name="PrepareMessageForSending" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="c23e762d-4162-cb9e-47b3-455a568b5072" name="SendIndirectMessageFormPostEmptyRecipient" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> - <TestLink id="809afd59-8f10-ce37-6630-06b59351a05a" name="CommonProperties" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="f70b368e-da33-bc64-6096-1b467d49a9d4" name="NonIdentityRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="46877579-ba4c-c30c-38c4-9c6ad3922390" name="InsufficientlyProtectedMessageReceived" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="b191e585-49d9-df8e-c156-307f798db169" name="AddAttributeRequest" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> <TestLink id="3772f97f-3fe6-3fc0-350d-4085e7c4329e" name="Test" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> @@ -480,6 +512,10 @@ <Description>All tests</Description> <TestLinks> <TestLink id="b350ddb1-f4e5-e79c-af5e-f4195767f294" name="TestPublic" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="7c3603c8-3686-807b-7840-1f04f8f307f5" name="AssociateDH" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="7fa99410-3aa3-10c3-10a0-27bb9288c900" name="CheckIdSharedHmacSha1Association" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="85469029-ffe0-f6f7-b56a-9ffd48fa137b" name="AssociateClearText" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> + <TestLink id="e6dadcc3-60ff-f60c-0c9a-2ebd5cf91df0" name="CheckIdSharedHmacSha256Association" storage="..\bin\debug\dotnetopenauth.test.dll" type="Microsoft.VisualStudio.TestTools.TestTypes.Unit.UnitTestElement, Microsoft.VisualStudio.QualityTools.Tips.UnitTest.ObjectModel, PublicKeyToken=b03f5f7f11d50a3a" /> </TestLinks> </TestList> <TestList name="Unit tests" id="f0eeb325-0558-48a3-9a99-952133d8148e" parentListId="8c43106b-9dc1-4907-a29f-aa66a61bf5b6" /> diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd new file mode 100644 index 0000000..a637d1f --- /dev/null +++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuth.xsd @@ -0,0 +1,319 @@ +<?xml version="1.0" encoding="utf-8"?> +<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:vs="http://schemas.microsoft.com/Visual-Studio-Intellisense" + elementFormDefault="qualified" + attributeFormDefault="unqualified"> + <xs:element name="dotNetOpenAuth"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="messaging"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="untrustedWebRequest"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="whitelistHosts"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="whitelistHostsRegex"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="blacklistHosts"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="blacklistHostsRegex"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="name" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="timeout" type="xs:string" /> + <xs:attribute name="readWriteTimeout" type="xs:string" /> + <xs:attribute name="maximumBytesToRead" type="xs:int" /> + <xs:attribute name="maximumRedirections" type="xs:int" /> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="lifetime" type="xs:string" /> + <xs:attribute name="clockSkew" type="xs:string" /> + </xs:complexType> + </xs:element> + <xs:element name="openid"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="relyingParty"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:complexType> + <xs:attribute name="requireSsl" type="xs:boolean" default="false" /> + <xs:attribute name="minimumRequiredOpenIdVersion"> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="V10" /> + <xs:enumeration value="V11" /> + <xs:enumeration value="V20" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="minimumHashBitLength" type="xs:int" /> + <xs:attribute name="maximumHashBitLength" type="xs:int" /> + <xs:attribute name="privateSecretMaximumAge" type="xs:string" /> + <xs:attribute name="requireDirectedIdentity" type="xs:boolean" /> + <xs:attribute name="requireAssociation" type="xs:boolean" /> + <xs:attribute name="rejectUnsolicitedAssertions" type="xs:boolean" /> + <xs:attribute name="rejectDelegatingIdentifiers" type="xs:boolean" /> + <xs:attribute name="ignoreUnsignedExtensions" type="xs:boolean" /> + </xs:complexType> + </xs:element> + <xs:element name="behaviors"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="optional" /> + <xs:attribute name="xaml" type="xs:string" use="optional" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="store"> + <xs:complexType> + <xs:attribute name="type" type="xs:string"/> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="provider"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="associations"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required" /> + <xs:attribute name="lifetime" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="requireSsl" type="xs:boolean" default="false" /> + <xs:attribute name="protectDownlevelReplayAttacks" type="xs:boolean" /> + <xs:attribute name="unsolicitedAssertionVerification"> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="RequireSuccess" /> + <xs:enumeration value="LogWarningOnFailure" /> + <xs:enumeration value="NeverVerify" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="minimumHashBitLength" type="xs:int" /> + <xs:attribute name="maximumHashBitLength" type="xs:int" /> + </xs:complexType> + </xs:element> + <xs:element name="behaviors"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="optional" /> + <xs:attribute name="xaml" type="xs:string" use="optional" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="store"> + <xs:complexType> + <xs:attribute name="type" type="xs:string"/> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="extensionFactories"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="add"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="optional" /> + <xs:attribute name="xaml" type="xs:string" use="optional" /> + </xs:complexType> + </xs:element> + <xs:element name="remove"> + <xs:complexType> + <xs:attribute name="type" type="xs:string" use="required" /> + </xs:complexType> + </xs:element> + <xs:element name="clear"> + <xs:complexType> + <!--tag is empty--> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="xriResolver"> + <xs:complexType> + <xs:attribute name="enabled" type="xs:boolean" /> + <xs:attribute name="proxy" type="xs:string" /> + </xs:complexType> + </xs:element> + </xs:choice> + <xs:attribute name="maxAuthenticationTime" type="xs:string" /> + </xs:complexType> + </xs:element> + <xs:element name="oauth"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="consumer"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:complexType> + + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + <xs:element name="serviceProvider"> + <xs:complexType> + <xs:choice minOccurs="0" maxOccurs="unbounded"> + <xs:element name="security"> + <xs:complexType> + <xs:attribute name="minimumRequiredOAuthVersion" default="V10"> + <xs:simpleType> + <xs:restriction base="xs:NMTOKEN"> + <xs:enumeration value="V10" /> + <xs:enumeration value="V10a" /> + </xs:restriction> + </xs:simpleType> + </xs:attribute> + <xs:attribute name="maxAuthorizationTime" type="xs:string" default="0:05" /> + </xs:complexType> + </xs:element> + <xs:element name="store"> + <xs:complexType> + <xs:attribute name="type" type="xs:string"/> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> + </xs:choice> + </xs:complexType> + </xs:element> +</xs:schema> diff --git a/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs b/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs index f535c38..7bd84d9 100644 --- a/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs +++ b/src/DotNetOpenAuth/Configuration/DotNetOpenAuthSection.cs @@ -30,6 +30,11 @@ namespace DotNetOpenAuth.Configuration { private const string OpenIdElementName = "openid"; /// <summary> + /// The name of the <oauth> sub-element. + /// </summary> + private const string OAuthElementName = "oauth"; + + /// <summary> /// Initializes a new instance of the <see cref="DotNetOpenAuthSection"/> class. /// </summary> internal DotNetOpenAuthSection() { @@ -61,5 +66,14 @@ namespace DotNetOpenAuth.Configuration { get { return (OpenIdElement)this[OpenIdElementName] ?? new OpenIdElement(); } set { this[OpenIdElementName] = value; } } + + /// <summary> + /// Gets or sets the configuration for OAuth. + /// </summary> + [ConfigurationProperty(OAuthElementName)] + internal OAuthElement OAuth { + get { return (OAuthElement)this[OAuthElementName] ?? new OAuthElement(); } + set { this[OAuthElementName] = value; } + } } } diff --git a/src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs b/src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs new file mode 100644 index 0000000..b15c3e3 --- /dev/null +++ b/src/DotNetOpenAuth/Configuration/OAuthConsumerElement.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthConsumerElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + + /// <summary> + /// Represents the <oauth/consumer> element in the host's .config file. + /// </summary> + internal class OAuthConsumerElement : ConfigurationElement { + /// <summary> + /// Gets the name of the security sub-element. + /// </summary> + private const string SecuritySettingsConfigName = "security"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthConsumerElement"/> class. + /// </summary> + internal OAuthConsumerElement() { + } + + /// <summary> + /// Gets or sets the security settings. + /// </summary> + [ConfigurationProperty(SecuritySettingsConfigName)] + public OAuthConsumerSecuritySettingsElement SecuritySettings { + get { return (OAuthConsumerSecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OAuthConsumerSecuritySettingsElement(); } + set { this[SecuritySettingsConfigName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs new file mode 100644 index 0000000..38a183a --- /dev/null +++ b/src/DotNetOpenAuth/Configuration/OAuthConsumerSecuritySettingsElement.cs @@ -0,0 +1,35 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthConsumerSecuritySettingsElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth; + + /// <summary> + /// Security settings that are applicable to consumers. + /// </summary> + internal class OAuthConsumerSecuritySettingsElement : ConfigurationElement { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthConsumerSecuritySettingsElement"/> class. + /// </summary> + internal OAuthConsumerSecuritySettingsElement() { + } + + /// <summary> + /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file. + /// </summary> + /// <returns>The newly created security settings object.</returns> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "By design")] + internal ConsumerSecuritySettings CreateSecuritySettings() { + return new ConsumerSecuritySettings(); + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/OAuthElement.cs b/src/DotNetOpenAuth/Configuration/OAuthElement.cs new file mode 100644 index 0000000..282bdba --- /dev/null +++ b/src/DotNetOpenAuth/Configuration/OAuthElement.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + + /// <summary> + /// Represents the <oauth> element in the host's .config file. + /// </summary> + internal class OAuthElement : ConfigurationElement { + /// <summary> + /// The name of the <consumer> sub-element. + /// </summary> + private const string ConsumerElementName = "consumer"; + + /// <summary> + /// The name of the <serviceProvider> sub-element. + /// </summary> + private const string ServiceProviderElementName = "serviceProvider"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthElement"/> class. + /// </summary> + internal OAuthElement() { + } + + /// <summary> + /// Gets or sets the configuration specific for Consumers. + /// </summary> + [ConfigurationProperty(ConsumerElementName)] + internal OAuthConsumerElement Consumer { + get { return (OAuthConsumerElement)this[ConsumerElementName] ?? new OAuthConsumerElement(); } + set { this[ConsumerElementName] = value; } + } + + /// <summary> + /// Gets or sets the configuration specific for Service Providers. + /// </summary> + [ConfigurationProperty(ServiceProviderElementName)] + internal OAuthServiceProviderElement ServiceProvider { + get { return (OAuthServiceProviderElement)this[ServiceProviderElementName] ?? new OAuthServiceProviderElement(); } + set { this[ServiceProviderElementName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs new file mode 100644 index 0000000..8e910a0 --- /dev/null +++ b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderElement.cs @@ -0,0 +1,49 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthServiceProviderElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System.Configuration; + using DotNetOpenAuth.Messaging.Bindings; + + /// <summary> + /// Represents the <oauth/serviceProvider> element in the host's .config file. + /// </summary> + internal class OAuthServiceProviderElement : ConfigurationElement { + /// <summary> + /// The name of the custom store sub-element. + /// </summary> + private const string StoreConfigName = "store"; + + /// <summary> + /// Gets the name of the security sub-element. + /// </summary> + private const string SecuritySettingsConfigName = "security"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthServiceProviderElement"/> class. + /// </summary> + internal OAuthServiceProviderElement() { + } + + /// <summary> + /// Gets or sets the type to use for storing application state. + /// </summary> + [ConfigurationProperty(StoreConfigName)] + public TypeConfigurationElement<INonceStore> ApplicationStore { + get { return (TypeConfigurationElement<INonceStore>)this[StoreConfigName] ?? new TypeConfigurationElement<INonceStore>(); } + set { this[StoreConfigName] = value; } + } + + /// <summary> + /// Gets or sets the security settings. + /// </summary> + [ConfigurationProperty(SecuritySettingsConfigName)] + public OAuthServiceProviderSecuritySettingsElement SecuritySettings { + get { return (OAuthServiceProviderSecuritySettingsElement)this[SecuritySettingsConfigName] ?? new OAuthServiceProviderSecuritySettingsElement(); } + set { this[SecuritySettingsConfigName] = value; } + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs new file mode 100644 index 0000000..c58c023 --- /dev/null +++ b/src/DotNetOpenAuth/Configuration/OAuthServiceProviderSecuritySettingsElement.cs @@ -0,0 +1,74 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthServiceProviderSecuritySettingsElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Configuration { + using System; + using System.Collections.Generic; + using System.Configuration; + using System.Linq; + using System.Text; + using DotNetOpenAuth.OAuth; + + /// <summary> + /// Security settings that are applicable to service providers. + /// </summary> + internal class OAuthServiceProviderSecuritySettingsElement : ConfigurationElement { + /// <summary> + /// Gets the name of the @minimumRequiredOAuthVersion attribute. + /// </summary> + private const string MinimumRequiredOAuthVersionConfigName = "minimumRequiredOAuthVersion"; + + /// <summary> + /// Gets the name of the @maxAuthorizationTime attribute. + /// </summary> + private const string MaximumRequestTokenTimeToLiveConfigName = "maxAuthorizationTime"; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthServiceProviderSecuritySettingsElement"/> class. + /// </summary> + internal OAuthServiceProviderSecuritySettingsElement() { + } + + /// <summary> + /// Gets or sets the minimum OAuth version a Consumer is required to support in order for this library to interoperate with it. + /// </summary> + /// <remarks> + /// Although the earliest versions of OAuth are supported, for security reasons it may be desirable to require the + /// remote party to support a later version of OAuth. + /// </remarks> + [ConfigurationProperty(MinimumRequiredOAuthVersionConfigName, DefaultValue = "V10")] + public ProtocolVersion MinimumRequiredOAuthVersion { + get { return (ProtocolVersion)this[MinimumRequiredOAuthVersionConfigName]; } + set { this[MinimumRequiredOAuthVersionConfigName] = value; } + } + + /// <summary> + /// Gets or sets the maximum time a user can take to complete authorization. + /// </summary> + /// <remarks> + /// This time limit serves as a security mitigation against brute force attacks to + /// compromise (unauthorized or authorized) request tokens. + /// Longer time limits is more friendly to slow users or consumers, while shorter + /// time limits provide better security. + /// </remarks> + [ConfigurationProperty(MaximumRequestTokenTimeToLiveConfigName, DefaultValue = "0:05")] // 5 minutes + [PositiveTimeSpanValidator] + public TimeSpan MaximumRequestTokenTimeToLive { + get { return (TimeSpan)this[MaximumRequestTokenTimeToLiveConfigName]; } + set { this[MaximumRequestTokenTimeToLiveConfigName] = value; } + } + + /// <summary> + /// Initializes a programmatically manipulatable bag of these security settings with the settings from the config file. + /// </summary> + /// <returns>The newly created security settings object.</returns> + internal ServiceProviderSecuritySettings CreateSecuritySettings() { + return new ServiceProviderSecuritySettings { + MinimumRequiredOAuthVersion = this.MinimumRequiredOAuthVersion, + }; + } + } +} diff --git a/src/DotNetOpenAuth/Configuration/OpenIdElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdElement.cs index 3a58da1..58a8276 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdElement.cs @@ -37,11 +37,16 @@ namespace DotNetOpenAuth.Configuration { private const string XriResolverElementName = "xriResolver"; /// <summary> - /// Gets the name of the @maxAuthenticationTime attribute. + /// The name of the @maxAuthenticationTime attribute. /// </summary> private const string MaxAuthenticationTimePropertyName = "maxAuthenticationTime"; /// <summary> + /// The name of the @cacheDiscovery attribute. + /// </summary> + private const string CacheDiscoveryPropertyName = "cacheDiscovery"; + + /// <summary> /// Initializes a new instance of the <see cref="OpenIdElement"/> class. /// </summary> internal OpenIdElement() { @@ -63,6 +68,24 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets a value indicating whether the results of Identifier discovery + /// should be cached. + /// </summary> + /// <value> + /// Use <c>true</c> to allow identifier discovery to immediately return cached results when available; + /// otherwise, use <c>false</c>.to force fresh results every time at the cost of slightly slower logins. + /// The default value is <c>true</c>. + /// </value> + /// <remarks> + /// When enabled, caching is done according to HTTP standards. + /// </remarks> + [ConfigurationProperty(CacheDiscoveryPropertyName, DefaultValue = true)] + internal bool CacheDiscovery { + get { return (bool)this[CacheDiscoveryPropertyName]; } + set { this[CacheDiscoveryPropertyName] = value; } + } + + /// <summary> /// Gets or sets the configuration specific for Relying Parties. /// </summary> [ConfigurationProperty(RelyingPartyElementName)] diff --git a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs index 457955c..3545fc5 100644 --- a/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs +++ b/src/DotNetOpenAuth/Configuration/OpenIdProviderSecuritySettingsElement.cs @@ -41,6 +41,11 @@ namespace DotNetOpenAuth.Configuration { private const string RequireSslConfigName = "requireSsl"; /// <summary> + /// Gets the name of the @unsolicitedAssertionVerification attribute. + /// </summary> + private const string UnsolicitedAssertionVerificationConfigName = "unsolicitedAssertionVerification"; + + /// <summary> /// Initializes a new instance of the <see cref="OpenIdProviderSecuritySettingsElement"/> class. /// </summary> public OpenIdProviderSecuritySettingsElement() { @@ -84,6 +89,17 @@ namespace DotNetOpenAuth.Configuration { } /// <summary> + /// Gets or sets the level of verification a Provider performs on an identifier before + /// sending an unsolicited assertion for it. + /// </summary> + /// <value>The default value is <see cref="ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.RequireSuccess"/>.</value> + [ConfigurationProperty(UnsolicitedAssertionVerificationConfigName, DefaultValue = ProviderSecuritySettings.UnsolicitedAssertionVerificationDefault)] + public ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerification { + get { return (ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel)this[UnsolicitedAssertionVerificationConfigName]; } + set { this[UnsolicitedAssertionVerificationConfigName] = value; } + } + + /// <summary> /// Gets or sets the configured lifetimes of the various association types. /// </summary> [ConfigurationProperty(AssociationsConfigName, IsDefaultCollection = false)] @@ -109,6 +125,7 @@ namespace DotNetOpenAuth.Configuration { settings.MinimumHashBitLength = this.MinimumHashBitLength; settings.MaximumHashBitLength = this.MaximumHashBitLength; settings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks; + settings.UnsolicitedAssertionVerification = this.UnsolicitedAssertionVerification; foreach (AssociationTypeElement element in this.AssociationLifetimes) { Contract.Assume(element != null); settings.AssociationLifetimes.Add(element.AssociationType, element.MaximumLifetime); diff --git a/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs b/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs index 24113ac..c8cf2aa 100644 --- a/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs +++ b/src/DotNetOpenAuth/Configuration/TypeConfigurationElement.cs @@ -100,11 +100,27 @@ namespace DotNetOpenAuth.Configuration { source = HttpContext.Current.Server.MapPath(source); } using (Stream xamlFile = File.OpenRead(source)) { - return (T)XamlReader.Load(xamlFile); + return CreateInstanceFromXaml(xamlFile); } } else { return defaultValue; } } + + /// <summary> + /// Creates the instance from xaml. + /// </summary> + /// <param name="xaml">The stream of xaml to deserialize.</param> + /// <returns>The deserialized object.</returns> + /// <remarks> + /// This exists as its own method to prevent the CLR's JIT compiler from failing + /// to compile the CreateInstance method just because the PresentationFramework.dll + /// may be missing (which it is on some shared web hosts). This way, if the + /// XamlSource attribute is never used, the PresentationFramework.dll never need + /// be present. + /// </remarks> + private static T CreateInstanceFromXaml(Stream xaml) { + return (T)XamlReader.Load(xaml); + } } } diff --git a/src/DotNetOpenAuth/DotNetOpenAuth.csproj b/src/DotNetOpenAuth/DotNetOpenAuth.csproj index e0894b1..01c4dd4 100644 --- a/src/DotNetOpenAuth/DotNetOpenAuth.csproj +++ b/src/DotNetOpenAuth/DotNetOpenAuth.csproj @@ -184,6 +184,11 @@ <Compile Include="Configuration\AssociationTypeElement.cs" /> <Compile Include="Configuration\DotNetOpenAuthSection.cs" /> <Compile Include="Configuration\MessagingElement.cs" /> + <Compile Include="Configuration\OAuthConsumerElement.cs" /> + <Compile Include="Configuration\OAuthConsumerSecuritySettingsElement.cs" /> + <Compile Include="Configuration\OAuthElement.cs" /> + <Compile Include="Configuration\OAuthServiceProviderElement.cs" /> + <Compile Include="Configuration\OAuthServiceProviderSecuritySettingsElement.cs" /> <Compile Include="Configuration\OpenIdElement.cs" /> <Compile Include="Configuration\OpenIdProviderElement.cs" /> <Compile Include="Configuration\OpenIdProviderSecuritySettingsElement.cs" /> @@ -232,23 +237,31 @@ <Compile Include="Messaging\NetworkDirectWebResponse.cs" /> <Compile Include="Messaging\OutgoingWebResponseActionResult.cs" /> <Compile Include="Messaging\Reflection\IMessagePartEncoder.cs" /> + <Compile Include="Messaging\Reflection\IMessagePartNullEncoder.cs" /> <Compile Include="Messaging\Reflection\MessageDescriptionCollection.cs" /> <Compile Include="OAuth\ChannelElements\ICombinedOpenIdProviderTokenManager.cs" /> - <Compile Include="OAuth\ChannelElements\IConsumerCertificateProvider.cs" /> + <Compile Include="OAuth\ChannelElements\IConsumerDescription.cs" /> <Compile Include="OAuth\ChannelElements\IConsumerTokenManager.cs" /> <Compile Include="OAuth\ChannelElements\IOpenIdOAuthTokenManager.cs" /> + <Compile Include="OAuth\ChannelElements\IServiceProviderAccessToken.cs" /> <Compile Include="OAuth\ChannelElements\IServiceProviderTokenManager.cs" /> <Compile Include="OAuth\ChannelElements\OAuthConsumerMessageFactory.cs" /> <Compile Include="OAuth\ChannelElements\ITokenGenerator.cs" /> <Compile Include="OAuth\ChannelElements\ITokenManager.cs" /> <Compile Include="OAuth\ChannelElements\OAuthHttpMethodBindingElement.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthIdentity.cs" /> + <Compile Include="OAuth\ChannelElements\OAuthPrincipal.cs" /> <Compile Include="OAuth\ChannelElements\PlaintextSigningBindingElement.cs" /> <Compile Include="OAuth\ChannelElements\HmacSha1SigningBindingElement.cs" /> + <Compile Include="OAuth\ChannelElements\IServiceProviderRequestToken.cs" /> <Compile Include="OAuth\ChannelElements\SigningBindingElementBaseContract.cs" /> <Compile Include="OAuth\ChannelElements\SigningBindingElementChain.cs" /> <Compile Include="OAuth\ChannelElements\StandardTokenGenerator.cs" /> <Compile Include="OAuth\ChannelElements\TokenType.cs" /> + <Compile Include="OAuth\ChannelElements\UriOrOobEncoding.cs" /> + <Compile Include="OAuth\ChannelElements\TokenHandlingBindingElement.cs" /> <Compile Include="OAuth\ConsumerBase.cs" /> + <Compile Include="OAuth\ConsumerSecuritySettings.cs" /> <Compile Include="OAuth\DesktopConsumer.cs" /> <Compile Include="GlobalSuppressions.cs" /> <Compile Include="OAuth\Messages\ITokenSecretContainingMessage.cs" /> @@ -259,11 +272,14 @@ <DesignTime>True</DesignTime> <DependentUpon>OAuthStrings.resx</DependentUpon> </Compile> + <Compile Include="OAuth\SecuritySettings.cs" /> <Compile Include="OAuth\ServiceProviderDescription.cs" /> <Compile Include="OAuth\Messages\ITokenContainingMessage.cs" /> <Compile Include="OAuth\Messages\SignedMessageBase.cs" /> <Compile Include="Messaging\Bindings\NonceMemoryStore.cs" /> <Compile Include="OAuth\ChannelElements\SigningBindingElementBase.cs" /> + <Compile Include="OAuth\ServiceProviderSecuritySettings.cs" /> + <Compile Include="OAuth\VerificationCodeFormat.cs" /> <Compile Include="OAuth\WebConsumer.cs" /> <Compile Include="Messaging\IDirectWebRequestHandler.cs" /> <Compile Include="OAuth\ChannelElements\ITamperResistantOAuthMessage.cs" /> @@ -317,6 +333,7 @@ <Compile Include="OpenId\Association.cs" /> <Compile Include="OpenId\AssociationMemoryStore.cs" /> <Compile Include="OpenId\Associations.cs" /> + <Compile Include="OpenId\Behaviors\AXFetchAsSregTransform.cs" /> <Compile Include="OpenId\Behaviors\BehaviorStrings.Designer.cs"> <AutoGen>True</AutoGen> <DesignTime>True</DesignTime> @@ -339,6 +356,7 @@ <Compile Include="OpenId\Extensions\AliasManager.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AttributeRequest.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AttributeValues.cs" /> + <Compile Include="OpenId\Extensions\AttributeExchange\AXAttributeFormats.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\AXUtilities.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\Constants.cs" /> <Compile Include="OpenId\Extensions\AttributeExchange\FetchRequest.cs" /> @@ -368,11 +386,13 @@ <Compile Include="OpenId\Extensions\SimpleRegistration\Constants.cs" /> <Compile Include="OpenId\Extensions\SimpleRegistration\DemandLevel.cs" /> <Compile Include="OpenId\Extensions\SimpleRegistration\Gender.cs" /> + <Compile Include="OpenId\Extensions\UI\UIConstants.cs" /> <Compile Include="OpenId\Extensions\UI\UIUtilities.cs" /> <Compile Include="OpenId\Extensions\UI\UIModes.cs" /> <Compile Include="OpenId\Extensions\UI\UIRequest.cs" /> <Compile Include="OpenId\Identifier.cs" /> <Compile Include="OpenId\IdentifierContract.cs" /> + <Compile Include="OpenId\Extensions\ExtensionsInteropHelper.cs" /> <Compile Include="OpenId\Interop\AuthenticationResponseShim.cs" /> <Compile Include="OpenId\Interop\ClaimsResponseShim.cs" /> <Compile Include="OpenId\Interop\OpenIdRelyingPartyShim.cs" /> @@ -476,7 +496,9 @@ <Compile Include="OpenId\RelyingParty\RelyingPartySecuritySettings.cs" /> <Compile Include="OpenId\RelyingParty\ServiceEndpoint.cs" /> <Compile Include="OpenId\OpenIdXrdsHelper.cs" /> + <Compile Include="OpenId\RelyingParty\SimpleXrdsProviderEndpoint.cs" /> <Compile Include="OpenId\RelyingParty\StandardRelyingPartyApplicationStore.cs" /> + <Compile Include="OpenId\Behaviors\GsaIcamProfile.cs" /> <Compile Include="OpenId\RelyingParty\WellKnownProviders.cs" /> <Compile Include="OpenId\SecuritySettings.cs" /> <Compile Include="Messaging\UntrustedWebRequestHandler.cs" /> @@ -514,6 +536,7 @@ <Compile Include="Yadis\Yadis.cs" /> </ItemGroup> <ItemGroup> + <None Include="Configuration\DotNetOpenAuth.xsd" /> <None Include="OAuth\ClassDiagram.cd" /> <None Include="OAuth\Messages\OAuth Messages.cd" /> <None Include="Messaging\Bindings\Bindings.cd" /> @@ -585,4 +608,4 @@ </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="..\..\tools\DotNetOpenAuth.Versioning.targets" /> -</Project>
\ No newline at end of file +</Project> diff --git a/src/DotNetOpenAuth/GlobalSuppressions.cs b/src/DotNetOpenAuth/GlobalSuppressions.cs index 8be62f8..d0e0d05 100644 --- a/src/DotNetOpenAuth/GlobalSuppressions.cs +++ b/src/DotNetOpenAuth/GlobalSuppressions.cs @@ -43,3 +43,8 @@ [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OpenId.Extensions.OAuth")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.OpenId.Extensions.UI")] [assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "DotNetOpenAuth.Messaging.Reflection")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "oauthverifier", Scope = "resource", Target = "DotNetOpenAuth.OAuth.OAuthStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "whitelist", Scope = "resource", Target = "DotNetOpenAuth.OpenId.OpenIdStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "icam", Scope = "resource", Target = "DotNetOpenAuth.OpenId.Behaviors.BehaviorStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "idmanagement", Scope = "resource", Target = "DotNetOpenAuth.OpenId.Behaviors.BehaviorStrings.resources")] +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "no-pii", Scope = "resource", Target = "DotNetOpenAuth.OpenId.Behaviors.BehaviorStrings.resources")] diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs index 2c6d677..fe2c2a2 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardSelector.cs @@ -5,7 +5,7 @@ // </copyright> //----------------------------------------------------------------------- -[assembly: System.Web.UI.WebResource("DotNetOpenAuth.InfoCard.SupportingScript.js", "text/javascript")] +[assembly: System.Web.UI.WebResource(DotNetOpenAuth.InfoCard.InfoCardSelector.ScriptResourceName, "text/javascript")] namespace DotNetOpenAuth.InfoCard { using System; @@ -16,6 +16,8 @@ namespace DotNetOpenAuth.InfoCard { using System.Drawing.Design; using System.Globalization; using System.Linq; + using System.Text.RegularExpressions; + using System.Web; using System.Web.UI; using System.Web.UI.HtmlControls; using System.Web.UI.WebControls; @@ -46,6 +48,11 @@ namespace DotNetOpenAuth.InfoCard { [ToolboxData("<{0}:InfoCardSelector runat=\"server\"><ClaimsRequested><{0}:ClaimType Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier\" /></ClaimsRequested><UnsupportedTemplate><p>Your browser does not support Information Cards.</p></UnsupportedTemplate></{0}:InfoCardSelector>")] [ContractVerification(true)] public class InfoCardSelector : CompositeControl, IPostBackEventHandler { + /// <summary> + /// The resource name for getting at the SupportingScript.js embedded manifest stream. + /// </summary> + internal const string ScriptResourceName = "DotNetOpenAuth.InfoCard.SupportingScript.js"; + #region Property constants /// <summary> @@ -170,11 +177,6 @@ namespace DotNetOpenAuth.InfoCard { #endregion /// <summary> - /// The resource name for getting at the SupportingScript.js embedded manifest stream. - /// </summary> - private const string ScriptResourceName = "DotNetOpenAuth.InfoCard.SupportingScript.js"; - - /// <summary> /// The panel containing the controls to display if InfoCard is supported in the user agent. /// </summary> private Panel infoCardSupportedPanel; @@ -192,6 +194,13 @@ namespace DotNetOpenAuth.InfoCard { private bool audienceSet; /// <summary> + /// Initializes a new instance of the <see cref="InfoCardSelector"/> class. + /// </summary> + public InfoCardSelector() { + this.ToolTip = InfoCardStrings.SelectorClickPrompt; + } + + /// <summary> /// Occurs when an InfoCard has been submitted but not decoded yet. /// </summary> [Category(InfoCardCategory)] @@ -255,9 +264,30 @@ namespace DotNetOpenAuth.InfoCard { /// </summary> [Description("The URL to this site's privacy policy.")] [Category(InfoCardCategory), DefaultValue(PrivacyUrlDefault)] + [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "We construct a Uri to validate the format of the string.")] + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "That overload is NOT the same.")] public string PrivacyUrl { - get { return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; } - set { this.ViewState[PrivacyUrlViewStateKey] = value; } + get { + return (string)this.ViewState[PrivacyUrlViewStateKey] ?? PrivacyUrlDefault; + } + + set { + if (this.Page != null && !this.DesignMode) { + // Validate new value by trying to construct a Uri based on it. + new Uri(new HttpRequestInfo(HttpContext.Current.Request).UrlBeforeRewriting, this.Page.ResolveUrl(value)); // throws an exception on failure. + } else { + // We can't fully test it, but it should start with either ~/ or a protocol. + if (Regex.IsMatch(value, @"^https?://")) { + new Uri(value); // make sure it's fully-qualified, but ignore wildcards + } else if (value.StartsWith("~/", StringComparison.Ordinal)) { + // this is valid too + } else { + throw new UriFormatException(); + } + } + + this.ViewState[PrivacyUrlViewStateKey] = value; + } } /// <summary> @@ -383,7 +413,16 @@ namespace DotNetOpenAuth.InfoCard { /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. /// </summary> /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> - public void RaisePostBackEvent(string eventArgument) { + void IPostBackEventHandler.RaisePostBackEvent(string eventArgument) { + this.RaisePostBackEvent(eventArgument); + } + + /// <summary> + /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. + /// </summary> + /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> + [SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate", Justification = "Predefined signature.")] + protected virtual void RaisePostBackEvent(string eventArgument) { if (!string.IsNullOrEmpty(this.TokenXml)) { try { ReceivingTokenEventArgs receivingArgs = this.OnReceivingToken(this.TokenXml); @@ -482,6 +521,20 @@ namespace DotNetOpenAuth.InfoCard { } /// <summary> + /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. + /// </summary> + /// <param name="e">An <see cref="T:System.EventArgs"/> object that contains the event data.</param> + protected override void OnPreRender(EventArgs e) { + base.OnPreRender(e); + + if (!this.DesignMode) { + // The Cardspace selector will display an ugly error to the user if + // the privacy URL is present but the privacy version is not. + ErrorUtilities.VerifyOperation(string.IsNullOrEmpty(this.PrivacyUrl) || !string.IsNullOrEmpty(this.PrivacyVersion), InfoCardStrings.PrivacyVersionRequiredWithPrivacyUrl); + } + } + + /// <summary> /// Creates a control that renders to <Param Name="{0}" Value="{1}" /> /// </summary> /// <param name="name">The parameter name.</param> @@ -518,7 +571,7 @@ namespace DotNetOpenAuth.InfoCard { Image image = new Image(); image.ImageUrl = this.Page.ClientScript.GetWebResourceUrl(typeof(InfoCardSelector), InfoCardImage.GetImageManifestResourceStreamName(this.ImageSize)); image.AlternateText = InfoCardStrings.SelectorClickPrompt; - image.ToolTip = InfoCardStrings.SelectorClickPrompt; + image.ToolTip = this.ToolTip; image.Style[HtmlTextWriterStyle.Cursor] = "hand"; image.Attributes["onclick"] = this.GetInfoCardSelectorActivationScript(false); @@ -609,7 +662,8 @@ namespace DotNetOpenAuth.InfoCard { } if (!string.IsNullOrEmpty(this.PrivacyUrl)) { - cardSpaceControl.Controls.Add(CreateParam("privacyUrl", this.PrivacyUrl)); + string privacyUrl = this.DesignMode ? this.PrivacyUrl : new Uri(Page.Request.Url, Page.ResolveUrl(this.PrivacyUrl)).AbsoluteUri; + cardSpaceControl.Controls.Add(CreateParam("privacyUrl", privacyUrl)); } if (!string.IsNullOrEmpty(this.PrivacyVersion)) { diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs index 4b1dc60..00eb1af 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs +++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.3521 +// Runtime Version:2.0.50727.4918 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -97,6 +97,15 @@ namespace DotNetOpenAuth.InfoCard { } /// <summary> + /// Looks up a localized string similar to The PrivacyVersion property must be set whenever the PrivacyUrl property is set.. + /// </summary> + internal static string PrivacyVersionRequiredWithPrivacyUrl { + get { + return ResourceManager.GetString("PrivacyVersionRequiredWithPrivacyUrl", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Click here to select your Information Card.. /// </summary> internal static string SelectorClickPrompt { diff --git a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx index e82e8cd..956b321 100644 --- a/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx +++ b/src/DotNetOpenAuth/InfoCard/InfoCardStrings.resx @@ -129,6 +129,9 @@ <data name="PpidClaimRequired" xml:space="preserve"> <value>This operation requires the PPID claim to be included in the InfoCard token.</value> </data> + <data name="PrivacyVersionRequiredWithPrivacyUrl" xml:space="preserve"> + <value>The PrivacyVersion property must be set whenever the PrivacyUrl property is set.</value> + </data> <data name="SelectorClickPrompt" xml:space="preserve"> <value>Click here to select your Information Card.</value> </data> diff --git a/src/DotNetOpenAuth/Loggers/Log4NetLogger.cs b/src/DotNetOpenAuth/Loggers/Log4NetLogger.cs index 30d93ae..dd71a05 100644 --- a/src/DotNetOpenAuth/Loggers/Log4NetLogger.cs +++ b/src/DotNetOpenAuth/Loggers/Log4NetLogger.cs @@ -197,7 +197,11 @@ namespace DotNetOpenAuth.Loggers { /// </summary> /// <returns>The created <see cref="ILog"/> instance.</returns> internal static ILog Initialize(string name) { - return IsLog4NetPresent ? CreateLogger(name) : null; + try { + return IsLog4NetPresent ? CreateLogger(name) : null; + } catch (FileLoadException) { // wrong log4net.dll version + return null; + } } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs index fff251a..6b6e2e1 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/INonceStore.cs @@ -19,11 +19,12 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// The context SHOULD be treated as case-sensitive. /// The value will never be <c>null</c> but may be the empty string.</param> /// <param name="nonce">A series of random characters.</param> - /// <param name="timestamp">The timestamp that together with the nonce string make it unique. + /// <param name="timestampUtc">The UTC timestamp that together with the nonce string make it unique + /// within the given <paramref name="context"/>. /// The timestamp may also be used by the data store to clear out old nonces.</param> /// <returns> - /// True if the nonce+timestamp (combination) was not previously in the database. - /// False if the nonce was stored previously with the same timestamp. + /// True if the context+nonce+timestamp (combination) was not previously in the database. + /// False if the nonce was stored previously with the same timestamp and context. /// </returns> /// <remarks> /// The nonce must be stored for no less than the maximum time window a message may @@ -33,6 +34,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// property, accessible via the <see cref="DotNetOpenAuth.Configuration.DotNetOpenAuthSection.Configuration"/> /// property. /// </remarks> - bool StoreNonce(string context, string nonce, DateTime timestamp); + bool StoreNonce(string context, string nonce, DateTime timestampUtc); } } diff --git a/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs b/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs index 1d4d28e..fcea3d9 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/NonceMemoryStore.cs @@ -47,6 +47,13 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <summary> /// Initializes a new instance of the <see cref="NonceMemoryStore"/> class. /// </summary> + internal NonceMemoryStore() + : this(StandardExpirationBindingElement.DefaultMaximumMessageAge) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="NonceMemoryStore"/> class. + /// </summary> /// <param name="maximumMessageAge">The maximum age a message can be before it is discarded.</param> internal NonceMemoryStore(TimeSpan maximumMessageAge) { this.maximumMessageAge = maximumMessageAge; @@ -73,7 +80,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property. /// </remarks> public bool StoreNonce(string context, string nonce, DateTime timestamp) { - if (timestamp.ToUniversalTime() + this.maximumMessageAge < DateTime.UtcNow) { + if (timestamp.ToUniversalTimeSafe() + this.maximumMessageAge < DateTime.UtcNow) { // The expiration binding element should have taken care of this, but perhaps // it's at the boundary case. We should fail just to be safe. return false; @@ -115,7 +122,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// </summary> public void ClearExpiredNonces() { lock (this.nonceLock) { - var oldNonceLists = this.usedNonces.Keys.Where(time => time.ToUniversalTime() + this.maximumMessageAge < DateTime.UtcNow).ToList(); + var oldNonceLists = this.usedNonces.Keys.Where(time => time.ToUniversalTimeSafe() + this.maximumMessageAge < DateTime.UtcNow).ToList(); foreach (DateTime time in oldNonceLists) { this.usedNonces.Remove(time); } diff --git a/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs b/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs index 5fcf4bd..7b00e34 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/StandardExpirationBindingElement.cs @@ -87,7 +87,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { if (expiringMessage != null) { // Yes the UtcCreationDate is supposed to always be in UTC already, // but just in case a given message failed to guarantee that, we do it here. - DateTime expirationDate = expiringMessage.UtcCreationDate.ToUniversalTime() + MaximumMessageAge; + DateTime expirationDate = expiringMessage.UtcCreationDate.ToUniversalTimeSafe() + MaximumMessageAge; if (expirationDate < DateTime.UtcNow) { throw new ExpiredMessageException(expirationDate, expiringMessage); } diff --git a/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs b/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs index bb2b28a..c8d5873 100644 --- a/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs +++ b/src/DotNetOpenAuth/Messaging/Bindings/StandardReplayProtectionBindingElement.cs @@ -28,11 +28,6 @@ namespace DotNetOpenAuth.Messaging.Bindings { private int nonceLength = 8; /// <summary> - /// A random number generator. - /// </summary> - private Random generator = new Random(); - - /// <summary> /// Initializes a new instance of the <see cref="StandardReplayProtectionBindingElement"/> class. /// </summary> /// <param name="nonceStore">The store where nonces will be persisted and checked.</param> @@ -145,11 +140,7 @@ namespace DotNetOpenAuth.Messaging.Bindings { /// </summary> /// <returns>The nonce string.</returns> private string GenerateUniqueFragment() { - char[] nonce = new char[this.nonceLength]; - for (int i = 0; i < nonce.Length; i++) { - nonce[i] = AllowedCharacters[this.generator.Next(AllowedCharacters.Length)]; - } - return new string(nonce); + return MessagingUtilities.GetRandomString(this.nonceLength, AllowedCharacters); } } } diff --git a/src/DotNetOpenAuth/Messaging/Channel.cs b/src/DotNetOpenAuth/Messaging/Channel.cs index d039f9b..2e0f1a8 100644 --- a/src/DotNetOpenAuth/Messaging/Channel.cs +++ b/src/DotNetOpenAuth/Messaging/Channel.cs @@ -28,6 +28,12 @@ namespace DotNetOpenAuth.Messaging { [ContractClass(typeof(ChannelContract))] public abstract class Channel : IDisposable { /// <summary> + /// The content-type used on HTTP POST requests where the POST entity is a + /// URL-encoded series of key=value pairs. + /// </summary> + protected internal const string HttpFormUrlEncoded = "application/x-www-form-urlencoded"; + + /// <summary> /// The encoding to use when writing out POST entity strings. /// </summary> private static readonly Encoding PostEntityEncoding = new UTF8Encoding(false); @@ -61,7 +67,9 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> private const string IndirectMessageFormPostFormat = @" <html> -<body onload=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()""> +<head> +</head> +<body onload=""document.body.style.display = 'none'; var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; document.getElementById('openid_message').submit()""> <form id=""openid_message"" action=""{0}"" method=""post"" accept-charset=""UTF-8"" enctype=""application/x-www-form-urlencoded"" onSubmit=""var btn = document.getElementById('submit_button'); btn.disabled = true; btn.value = 'Login in progress'; return true;""> {1} <input id=""submit_button"" type=""submit"" value=""Continue"" /> @@ -317,10 +325,10 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Gets the protocol message embedded in the given HTTP request, if present. + /// Gets the protocol message embedded in the current HTTP request. /// </summary> /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> - /// <returns>The deserialized message.</returns> + /// <returns>The deserialized message. Never null.</returns> /// <remarks> /// Requires an HttpContext.Current context. /// </remarks> @@ -333,11 +341,11 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> - /// Gets the protocol message that may be embedded in the given HTTP request. + /// Gets the protocol message embedded in the given HTTP request. /// </summary> /// <typeparam name="TRequest">The expected type of the message to be received.</typeparam> /// <param name="httpRequest">The request to search for an embedded message.</param> - /// <returns>The deserialized message, if one is found. Null otherwise.</returns> + /// <returns>The deserialized message. Never null.</returns> /// <exception cref="ProtocolException">Thrown if the expected message was not recognized in the response.</exception> [SuppressMessage("Microsoft.Design", "CA1004:GenericMethodsShouldProvideTypeParameter", Justification = "This returns and verifies the appropriate message type.")] public TRequest ReadFromRequest<TRequest>(HttpRequestInfo httpRequest) @@ -669,6 +677,7 @@ namespace DotNetOpenAuth.Messaging { ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); WebHeaderCollection headers = new WebHeaderCollection(); + headers.Add(HttpResponseHeader.ContentType, "text/html"); StringWriter bodyWriter = new StringWriter(CultureInfo.InvariantCulture); StringBuilder hiddenFields = new StringBuilder(); foreach (var field in fields) { @@ -848,7 +857,7 @@ namespace DotNetOpenAuth.Messaging { ErrorUtilities.VerifyArgumentNotNull(httpRequest, "httpRequest"); ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); - httpRequest.ContentType = "application/x-www-form-urlencoded"; + httpRequest.ContentType = HttpFormUrlEncoded; // Setting the content-encoding to "utf-8" causes Google to reply // with a 415 UnsupportedMediaType. But adding it doesn't buy us diff --git a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs index 9f67796..0a60b19 100644 --- a/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/ErrorUtilities.cs @@ -221,7 +221,17 @@ namespace DotNetOpenAuth.Messaging { Contract.EnsuresOnThrow<ProtocolException>(!condition); Contract.Assume(message != null); if (!condition) { - throw new ProtocolException(string.Format(CultureInfo.CurrentCulture, message, args)); + var exception = new ProtocolException(string.Format(CultureInfo.CurrentCulture, message, args)); + if (Logger.Messaging.IsErrorEnabled) { + Logger.Messaging.Error( + string.Format( + CultureInfo.CurrentCulture, + "Protocol error: {0}{1}{2}", + exception.Message, + Environment.NewLine, + new StackTrace())); + } + throw exception; } } diff --git a/src/DotNetOpenAuth/Messaging/HostErrorException.cs b/src/DotNetOpenAuth/Messaging/HostErrorException.cs index 0ab9e51..81691b0 100644 --- a/src/DotNetOpenAuth/Messaging/HostErrorException.cs +++ b/src/DotNetOpenAuth/Messaging/HostErrorException.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.Messaging { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; @@ -20,6 +21,7 @@ namespace DotNetOpenAuth.Messaging { /// or its configuration.</para> /// <para>It is an internal exception to assist in making it uncatchable.</para> /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "We don't want this exception to be catchable.")] [Serializable] internal class HostErrorException : Exception { /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs b/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs index 309bad3..cbbe28e 100644 --- a/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs +++ b/src/DotNetOpenAuth/Messaging/HttpDeliveryMethods.cs @@ -26,7 +26,7 @@ namespace DotNetOpenAuth.Messaging { AuthorizationHeaderRequest = 0x1, /// <summary> - /// As the HTTP POST request body with a content-type of application/x-www-form-urlencoded. + /// As the HTTP POST request body with a content-type of application/x-www-form-urlencoded. /// </summary> PostRequest = 0x2, diff --git a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs index c8014b3..5acd589 100644 --- a/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOpenAuth/Messaging/HttpRequestInfo.cs @@ -226,7 +226,7 @@ namespace DotNetOpenAuth.Messaging { get { Contract.Ensures(Contract.Result<NameValueCollection>() != null); if (this.form == null) { - if (this.HttpMethod == "POST" && this.Headers[HttpRequestHeader.ContentType] == "application/x-www-form-urlencoded") { + if (this.HttpMethod == "POST" && this.Headers[HttpRequestHeader.ContentType] == Channel.HttpFormUrlEncoded) { StreamReader reader = new StreamReader(this.InputStream); long originalPosition = 0; if (this.InputStream.CanSeek) { @@ -328,12 +328,11 @@ namespace DotNetOpenAuth.Messaging { if (request.ServerVariables["HTTP_HOST"] != null) { ErrorUtilities.VerifySupported(request.Url.Scheme == Uri.UriSchemeHttps || request.Url.Scheme == Uri.UriSchemeHttp, "Only HTTP and HTTPS are supported protocols."); UriBuilder publicRequestUri = new UriBuilder(request.Url); - string[] hostAndPort = request.ServerVariables["HTTP_HOST"].Split(new[] { ':' }, 2); - publicRequestUri.Host = hostAndPort[0]; - if (hostAndPort.Length > 1) { - publicRequestUri.Port = Convert.ToInt32(hostAndPort[1], CultureInfo.InvariantCulture); - } else { - publicRequestUri.Port = publicRequestUri.Scheme == Uri.UriSchemeHttps ? 443 : 80; + Uri hostAndPort = new Uri(request.Url.Scheme + Uri.SchemeDelimiter + request.ServerVariables["HTTP_HOST"]); + publicRequestUri.Host = hostAndPort.Host; + publicRequestUri.Port = hostAndPort.Port; + if (request.ServerVariables["HTTP_X_FORWARDED_PROTO"] != null) { + publicRequestUri.Scheme = request.ServerVariables["HTTP_X_FORWARDED_PROTO"]; } return publicRequestUri.Uri; } else { @@ -372,7 +371,16 @@ namespace DotNetOpenAuth.Messaging { WebHeaderCollection headers = new WebHeaderCollection(); foreach (string key in pairs) { - headers.Add(key, pairs[key]); + try { + headers.Add(key, pairs[key]); + } catch (ArgumentException ex) { + Logger.Messaging.WarnFormat( + "{0} thrown when trying to add web header \"{1}: {2}\". {3}", + ex.GetType().Name, + key, + pairs[key], + ex.Message); + } } return headers; diff --git a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs index dee81dc..e471a06 100644 --- a/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/IncomingWebResponse.cs @@ -101,7 +101,7 @@ namespace DotNetOpenAuth.Messaging { /// This can be different from the <see cref="RequestUri"/> in cases of /// redirection during the request. /// </remarks> - public Uri FinalUri { get; private set; } + public Uri FinalUri { get; internal set; } /// <summary> /// Gets the headers that must be included in the response to the user agent. diff --git a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs index 82910d5..22c660c 100644 --- a/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs +++ b/src/DotNetOpenAuth/Messaging/MessagePartAttribute.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.Messaging { using System; + using System.Diagnostics; using System.Net.Security; using System.Reflection; @@ -13,6 +14,7 @@ namespace DotNetOpenAuth.Messaging { /// Applied to fields and properties that form a key/value in a protocol message. /// </summary> [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, Inherited = true, AllowMultiple = true)] + [DebuggerDisplay("MessagePartAttribute {Name}")] public sealed class MessagePartAttribute : Attribute { /// <summary> /// The overridden name to use as the serialized name for the property. diff --git a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs index e532e99..79a1107 100644 --- a/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs +++ b/src/DotNetOpenAuth/Messaging/MessageReceivingEndpoint.cs @@ -12,6 +12,7 @@ namespace DotNetOpenAuth.Messaging { /// An immutable description of a URL that receives messages. /// </summary> [DebuggerDisplay("{AllowedMethods} {Location}")] + [Serializable] public class MessageReceivingEndpoint { /// <summary> /// Initializes a new instance of the <see cref="MessageReceivingEndpoint"/> class. diff --git a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs index c3cf289..38c431f 100644 --- a/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs +++ b/src/DotNetOpenAuth/Messaging/MessagingUtilities.cs @@ -36,6 +36,27 @@ namespace DotNetOpenAuth.Messaging { internal static readonly Random NonCryptoRandomDataGenerator = new Random(); /// <summary> + /// The uppercase alphabet. + /// </summary> + internal const string UppercaseLetters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /// <summary> + /// The lowercase alphabet. + /// </summary> + internal const string LowercaseLetters = "abcdefghijklmnopqrstuvwxyz"; + + /// <summary> + /// The set of base 10 digits. + /// </summary> + internal const string Digits = "0123456789"; + + /// <summary> + /// The set of digits, and alphabetic letters (upper and lowercase) that are clearly + /// visually distinguishable. + /// </summary> + internal const string AlphaNumericNoLookAlikes = "23456789abcdefghjkmnpqrstwxyzABCDEFGHJKMNPQRSTWXYZ"; + + /// <summary> /// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986. /// </summary> private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" }; @@ -179,6 +200,24 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Gets a random string made up of a given set of allowable characters. + /// </summary> + /// <param name="length">The length of the desired random string.</param> + /// <param name="allowableCharacters">The allowable characters.</param> + /// <returns>A random string.</returns> + internal static string GetRandomString(int length, string allowableCharacters) { + Contract.Requires(length >= 0); + Contract.Requires(allowableCharacters != null && allowableCharacters.Length >= 2); + + char[] randomString = new char[length]; + for (int i = 0; i < length; i++) { + randomString[i] = allowableCharacters[NonCryptoRandomDataGenerator.Next(allowableCharacters.Length)]; + } + + return new string(randomString); + } + + /// <summary> /// Adds a set of HTTP headers to an <see cref="HttpResponse"/> instance, /// taking care to set some headers to the appropriate properties of /// <see cref="HttpResponse" /> @@ -741,6 +780,32 @@ namespace DotNetOpenAuth.Messaging { } /// <summary> + /// Ensures that UTC times are converted to local times. Unspecified kinds are unchanged. + /// </summary> + /// <param name="value">The date-time to convert.</param> + /// <returns>The date-time in local time.</returns> + internal static DateTime ToLocalTimeSafe(this DateTime value) { + if (value.Kind == DateTimeKind.Unspecified) { + return value; + } + + return value.ToLocalTime(); + } + + /// <summary> + /// Ensures that local times are converted to UTC times. Unspecified kinds are unchanged. + /// </summary> + /// <param name="value">The date-time to convert.</param> + /// <returns>The date-time in UTC time.</returns> + internal static DateTime ToUniversalTimeSafe(this DateTime value) { + if (value.Kind == DateTimeKind.Unspecified) { + return value; + } + + return value.ToUniversalTime(); + } + + /// <summary> /// A class to convert a <see cref="Comparison<T>"/> into an <see cref="IComparer<T>"/>. /// </summary> /// <typeparam name="T">The type of objects being compared.</typeparam> diff --git a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs index bad582c..147cd66 100644 --- a/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs +++ b/src/DotNetOpenAuth/Messaging/OutgoingWebResponse.cs @@ -30,6 +30,11 @@ namespace DotNetOpenAuth.Messaging { /// </remarks> public class OutgoingWebResponse { /// <summary> + /// The encoder to use for serializing the response body. + /// </summary> + private static Encoding bodyStringEncoder = new UTF8Encoding(false); + + /// <summary> /// Initializes a new instance of the <see cref="OutgoingWebResponse"/> class. /// </summary> internal OutgoingWebResponse() { @@ -210,10 +215,9 @@ namespace DotNetOpenAuth.Messaging { return; } - Encoding encoding = Encoding.UTF8; - this.Headers[HttpResponseHeader.ContentEncoding] = encoding.HeaderName; + this.Headers[HttpResponseHeader.ContentEncoding] = bodyStringEncoder.HeaderName; this.ResponseStream = new MemoryStream(); - StreamWriter writer = new StreamWriter(this.ResponseStream, encoding); + StreamWriter writer = new StreamWriter(this.ResponseStream, bodyStringEncoder); writer.Write(body); writer.Flush(); this.ResponseStream.Seek(0, SeekOrigin.Begin); diff --git a/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs new file mode 100644 index 0000000..7581550 --- /dev/null +++ b/src/DotNetOpenAuth/Messaging/Reflection/IMessagePartNullEncoder.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------- +// <copyright file="IMessagePartNullEncoder.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.Messaging.Reflection { + /// <summary> + /// A message part encoder that has a special encoding for a null value. + /// </summary> + public interface IMessagePartNullEncoder : IMessagePartEncoder { + /// <summary> + /// Gets the string representation to include in a serialized message + /// when the message part has a <c>null</c> value. + /// </summary> + string EncodedNullValue { get; } + } +} diff --git a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs index a7bba5b..f4bc3fe 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/MessagePart.cs @@ -7,8 +7,10 @@ namespace DotNetOpenAuth.Messaging.Reflection { using System; using System.Collections.Generic; + using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; + using System.Linq; using System.Net.Security; using System.Reflection; using System.Xml; @@ -17,6 +19,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// Describes an individual member of a message and assists in its serialization. /// </summary> + [DebuggerDisplay("MessagePart {Name}")] internal class MessagePart { /// <summary> /// A map of converters that help serialize custom objects to string values and back again. @@ -67,6 +70,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { Map<Identifier>(id => id.ToString(), str => Identifier.Parse(str)); Map<bool>(value => value.ToString().ToLowerInvariant(), str => bool.Parse(str)); Map<CultureInfo>(c => c.Name, str => new CultureInfo(str)); + Map<CultureInfo[]>(cs => string.Join(",", cs.Select(c => c.Name).ToArray()), str => str.Split(',').Select(s => new CultureInfo(s)).ToArray()); } /// <summary> @@ -115,10 +119,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { str => str != null ? Convert.ChangeType(str, this.memberDeclaredType, CultureInfo.InvariantCulture) : null); } } else { - var encoder = GetEncoder(attribute.Encoder); - this.converter = new ValueMapping( - obj => encoder.Encode(obj), - str => encoder.Decode(str)); + this.converter = new ValueMapping(GetEncoder(attribute.Encoder)); } // readonly and const fields are considered legal, and "constants" for message transport. @@ -191,7 +192,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { this.field.SetValue(message, this.ToValue(value)); } } - } catch (FormatException ex) { + } catch (Exception ex) { throw ErrorUtilities.Wrap(ex, MessagingStrings.MessagePartReadFailure, message.GetType(), this.Name, value); } } @@ -288,7 +289,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// An instance of the appropriate type for setting the member. /// </returns> private object ToValue(string value) { - return value == null ? null : this.converter.StringToValue(value); + return this.converter.StringToValue(value); } /// <summary> @@ -299,7 +300,7 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// The string representation of the member's value. /// </returns> private string ToString(object value) { - return value == null ? null : this.converter.ValueToString(value); + return this.converter.ValueToString(value); } /// <summary> diff --git a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs index bdc2d7f..332274e 100644 --- a/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs +++ b/src/DotNetOpenAuth/Messaging/Reflection/ValueMapping.cs @@ -14,12 +14,12 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <summary> /// The mapping function that converts some custom type to a string. /// </summary> - internal Func<object, string> ValueToString; + internal readonly Func<object, string> ValueToString; /// <summary> /// The mapping function that converts a string to some custom type. /// </summary> - internal Func<string, object> StringToValue; + internal readonly Func<string, object> StringToValue; /// <summary> /// Initializes a new instance of the <see cref="ValueMapping"/> struct. @@ -27,16 +27,24 @@ namespace DotNetOpenAuth.Messaging.Reflection { /// <param name="toString">The mapping function that converts some custom type to a string.</param> /// <param name="toValue">The mapping function that converts a string to some custom type.</param> internal ValueMapping(Func<object, string> toString, Func<string, object> toValue) { - if (toString == null) { - throw new ArgumentNullException("toString"); - } - - if (toValue == null) { - throw new ArgumentNullException("toValue"); - } + ErrorUtilities.VerifyArgumentNotNull(toString, "toString"); + ErrorUtilities.VerifyArgumentNotNull(toValue, "toValue"); this.ValueToString = toString; this.StringToValue = toValue; } + + /// <summary> + /// Initializes a new instance of the <see cref="ValueMapping"/> struct. + /// </summary> + /// <param name="encoder">The encoder.</param> + internal ValueMapping(IMessagePartEncoder encoder) { + ErrorUtilities.VerifyArgumentNotNull(encoder, "encoder"); + var nullEncoder = encoder as IMessagePartNullEncoder; + string nullString = nullEncoder != null ? nullEncoder.EncodedNullValue : null; + + this.ValueToString = obj => (obj != null) ? encoder.Encode(obj) : nullString; + this.StringToValue = str => (str != null) ? encoder.Decode(str) : null; + } } } diff --git a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs index 98c9d93..cc991cd 100644 --- a/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/StandardWebRequestHandler.cs @@ -180,6 +180,31 @@ namespace DotNetOpenAuth.Messaging { #endregion /// <summary> + /// Determines whether an exception was thrown because of the remote HTTP server returning HTTP 417 Expectation Failed. + /// </summary> + /// <param name="ex">The caught exception.</param> + /// <returns> + /// <c>true</c> if the failure was originally caused by a 417 Exceptation Failed error; otherwise, <c>false</c>. + /// </returns> + internal static bool IsExceptionFrom417ExpectationFailed(Exception ex) { + while (ex != null) { + WebException webEx = ex as WebException; + if (webEx != null) { + HttpWebResponse response = webEx.Response as HttpWebResponse; + if (response != null) { + if (response.StatusCode == HttpStatusCode.ExpectationFailed) { + return true; + } + } + } + + ex = ex.InnerException; + } + + return false; + } + + /// <summary> /// Initiates a POST request and prepares for sending data. /// </summary> /// <param name="request">The HTTP request with information about the remote party to contact.</param> diff --git a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs index 733b698..1656155 100644 --- a/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs +++ b/src/DotNetOpenAuth/Messaging/UntrustedWebRequestHandler.cs @@ -259,6 +259,15 @@ namespace DotNetOpenAuth.Messaging { Uri redirectUri = new Uri(response.FinalUri, response.Headers[HttpResponseHeader.Location]); request = request.Clone(redirectUri); } else { + if (response.FinalUri != request.RequestUri) { + // Since we don't automatically follow redirects, there's only one scenario where this + // can happen: when the server sends a (non-redirecting) Content-Location header in the response. + // It's imperative that we do not trust that header though, so coerce the FinalUri to be + // what we just requested. + Logger.Http.WarnFormat("The response from {0} included an HTTP header indicating it's the same as {1}, but it's not a redirect so we won't trust that.", request.RequestUri, response.FinalUri); + response.FinalUri = request.RequestUri; + } + return response; } } @@ -455,12 +464,14 @@ namespace DotNetOpenAuth.Messaging { request.ReadWriteTimeout = (int)this.ReadWriteTimeout.TotalMilliseconds; request.Timeout = (int)this.Timeout.TotalMilliseconds; request.KeepAlive = false; - - // If SSL is required throughout, we cannot allow auto redirects because - // it may include a pass through an unprotected HTTP request. - // We have to follow redirects manually. - request.AllowAutoRedirect = false; } + + // If SSL is required throughout, we cannot allow auto redirects because + // it may include a pass through an unprotected HTTP request. + // We have to follow redirects manually. + // It also allows us to ignore HttpWebResponse.FinalUri since that can be affected by + // the Content-Location header and open security holes. + request.AllowAutoRedirect = false; } } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs deleted file mode 100644 index 7e6ae54..0000000 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerCertificateProvider.cs +++ /dev/null @@ -1,23 +0,0 @@ -//----------------------------------------------------------------------- -// <copyright file="IConsumerCertificateProvider.cs" company="Andrew Arnott"> -// Copyright (c) Andrew Arnott. All rights reserved. -// </copyright> -//----------------------------------------------------------------------- - -namespace DotNetOpenAuth.OAuth.ChannelElements { - using System.Security.Cryptography.X509Certificates; - - /// <summary> - /// A provider that hosts can implement to hook up their RSA-SHA1 binding elements - /// to their list of known Consumers' certificates. - /// </summary> - public interface IConsumerCertificateProvider { - /// <summary> - /// Gets the certificate that can be used to verify the signature of an incoming - /// message from a Consumer. - /// </summary> - /// <param name="consumerMessage">The incoming message from some Consumer.</param> - /// <returns>The public key from the Consumer's X.509 Certificate, if one can be found; otherwise <c>null</c>.</returns> - X509Certificate2 GetCertificate(ITamperResistantOAuthMessage consumerMessage); - } -} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs new file mode 100644 index 0000000..db505d5 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IConsumerDescription.cs @@ -0,0 +1,59 @@ +//----------------------------------------------------------------------- +// <copyright file="IConsumerDescription.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Security.Cryptography.X509Certificates; + + /// <summary> + /// A description of a consumer from a Service Provider's point of view. + /// </summary> + public interface IConsumerDescription { + /// <summary> + /// Gets the Consumer key. + /// </summary> + string Key { get; } + + /// <summary> + /// Gets the consumer secret. + /// </summary> + string Secret { get; } + + /// <summary> + /// Gets the certificate that can be used to verify the signature of an incoming + /// message from a Consumer. + /// </summary> + /// <returns>The public key from the Consumer's X.509 Certificate, if one can be found; otherwise <c>null</c>.</returns> + /// <remarks> + /// This property must be implemented only if the RSA-SHA1 algorithm is supported by the Service Provider. + /// </remarks> + X509Certificate2 Certificate { get; } + + /// <summary> + /// Gets the callback URI that this consumer has pre-registered with the service provider, if any. + /// </summary> + /// <value>A URI that user authorization responses should be directed to; or <c>null</c> if no preregistered callback was arranged.</value> + Uri Callback { get; } + + /// <summary> + /// Gets the verification code format that is most appropriate for this consumer + /// when a callback URI is not available. + /// </summary> + /// <value>A set of characters that can be easily keyed in by the user given the Consumer's + /// application type and form factor.</value> + /// <remarks> + /// The value <see cref="OAuth.VerificationCodeFormat.IncludedInCallback"/> should NEVER be returned + /// since this property is only used in no callback scenarios anyway. + /// </remarks> + VerificationCodeFormat VerificationCodeFormat { get; } + + /// <summary> + /// Gets the length of the verification code to issue for this Consumer. + /// </summary> + /// <value>A positive number, generally at least 4.</value> + int VerificationCodeLength { get; } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs new file mode 100644 index 0000000..35ba52d --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderAccessToken.cs @@ -0,0 +1,48 @@ +//----------------------------------------------------------------------- +// <copyright file="IServiceProviderAccessToken.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + + /// <summary> + /// A description of an access token and its metadata as required by a Service Provider. + /// </summary> + public interface IServiceProviderAccessToken { + /// <summary> + /// Gets the token itself. + /// </summary> + string Token { get; } + + /// <summary> + /// Gets the expiration date (local time) for the access token. + /// </summary> + /// <value>The expiration date, or <c>null</c> if there is no expiration date.</value> + DateTime? ExpirationDate { get; } + + /// <summary> + /// Gets the username of the principal that will be impersonated by this access token. + /// </summary> + /// <value> + /// The name of the user who authorized the OAuth request token originally. + /// </value> + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "Username", Justification = "Breaking change.")] + string Username { get; } + + /// <summary> + /// Gets the roles that the OAuth principal should belong to. + /// </summary> + /// <value> + /// The roles that the user belongs to, or a subset of these according to the rights + /// granted when the user authorized the request token. + /// </value> + [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "By design.")] + string[] Roles { get; } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs new file mode 100644 index 0000000..6dfa416 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderRequestToken.cs @@ -0,0 +1,52 @@ +//----------------------------------------------------------------------- +// <copyright file="IServiceProviderRequestToken.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// A description of a request token and its metadata as required by a Service Provider + /// </summary> + public interface IServiceProviderRequestToken { + /// <summary> + /// Gets the token itself. + /// </summary> + string Token { get; } + + /// <summary> + /// Gets the consumer key that requested this token. + /// </summary> + string ConsumerKey { get; } + + /// <summary> + /// Gets the (local) date that this request token was first created on. + /// </summary> + DateTime CreatedOn { get; } + + /// <summary> + /// Gets or sets the callback associated specifically with this token, if any. + /// </summary> + /// <value>The callback URI; or <c>null</c> if no callback was specifically assigned to this token.</value> + Uri Callback { get; set; } + + /// <summary> + /// Gets or sets the verifier that the consumer must include in the <see cref="AuthorizedTokenRequest"/> + /// message to exchange this request token for an access token. + /// </summary> + /// <value>The verifier code, or <c>null</c> if none has been assigned (yet).</value> + string VerificationCode { get; set; } + + /// <summary> + /// Gets or sets the version of the Consumer that requested this token. + /// </summary> + /// <remarks> + /// This property is used to determine whether a <see cref="VerificationCode"/> must be + /// generated when the user authorizes the Consumer or not. + /// </remarks> + Version ConsumerVersion { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs index e1c1e3f..02ebffb 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/IServiceProviderTokenManager.cs @@ -16,12 +16,39 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> public interface IServiceProviderTokenManager : ITokenManager { /// <summary> - /// Gets the Consumer Secret for a given a Consumer Key. + /// Gets the Consumer description for a given a Consumer Key. /// </summary> /// <param name="consumerKey">The Consumer Key.</param> - /// <returns>The Consumer Secret.</returns> - /// <exception cref="ArgumentException">Thrown if the consumer key cannot be found.</exception> - /// <exception cref="InvalidOperationException">May be thrown if called when the signature algorithm does not require a consumer secret, such as when RSA-SHA1 is used.</exception> - string GetConsumerSecret(string consumerKey); + /// <returns>A description of the consumer. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the consumer key cannot be found.</exception> + IConsumerDescription GetConsumer(string consumerKey); + + /// <summary> + /// Gets details on the named request token. + /// </summary> + /// <param name="token">The request token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + IServiceProviderRequestToken GetRequestToken(string token); + + /// <summary> + /// Gets details on the named access token. + /// </summary> + /// <param name="token">The access token.</param> + /// <returns>A description of the token. Never null.</returns> + /// <exception cref="KeyNotFoundException">Thrown if the token cannot be found.</exception> + /// <remarks> + /// It is acceptable for implementations to find the token, see that it has expired, + /// delete it from the database and then throw <see cref="KeyNotFoundException"/>, + /// or alternatively it can return the expired token anyway and the OAuth channel will + /// log and throw the appropriate error. + /// </remarks> + IServiceProviderAccessToken GetAccessToken(string token); } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs index 3243e47..d325825 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -63,7 +63,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <see cref="OAuthConsumerMessageFactory"/> or <see cref="OAuthServiceProviderMessageFactory"/>. /// </param> internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, IMessageFactory messageTypeProvider) - : base(messageTypeProvider, new OAuthHttpMethodBindingElement(), signingBindingElement, new StandardExpirationBindingElement(), new StandardReplayProtectionBindingElement(store)) { + : base(messageTypeProvider, InitializeBindingElements(signingBindingElement, store, tokenManager)) { ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); this.TokenManager = tokenManager; @@ -122,7 +122,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { string authorization = request.Headers[HttpRequestHeader.Authorization]; if (authorization != null) { string[] authorizationSections = authorization.Split(';'); // TODO: is this the right delimiter? - string oauthPrefix = Protocol.Default.AuthorizationHeaderScheme + " "; + string oauthPrefix = Protocol.AuthorizationHeaderScheme + " "; // The Authorization header may have multiple uses, and OAuth may be just one of them. // Go through each one looking for an OAuth one. @@ -142,13 +142,19 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { } // Scrape the entity - foreach (string key in request.Form) { - fields.Add(key, request.Form[key]); + if (string.Equals(request.Headers[HttpRequestHeader.ContentType], HttpFormUrlEncoded, StringComparison.Ordinal)) { + foreach (string key in request.Form) { + fields.Add(key, request.Form[key]); + } } // Scrape the query string foreach (string key in request.QueryStringBeforeRewriting) { - fields.Add(key, request.QueryStringBeforeRewriting[key]); + if (key != null) { + fields.Add(key, request.QueryStringBeforeRewriting[key]); + } else { + Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.QueryStringBeforeRewriting[key]); + } } // Deserialize the message using all the data we've collected. @@ -239,6 +245,29 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { } /// <summary> + /// Initializes the binding elements for the OAuth channel. + /// </summary> + /// <param name="signingBindingElement">The signing binding element.</param> + /// <param name="store">The nonce store.</param> + /// <param name="tokenManager">The token manager.</param> + /// <returns>An array of binding elements used to initialize the channel.</returns> + private static IChannelBindingElement[] InitializeBindingElements(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager) { + var bindingElements = new List<IChannelBindingElement> { + new OAuthHttpMethodBindingElement(), + signingBindingElement, + new StandardExpirationBindingElement(), + new StandardReplayProtectionBindingElement(store), + }; + + var spTokenManager = tokenManager as IServiceProviderTokenManager; + if (spTokenManager != null) { + bindingElements.Insert(0, new TokenHandlingBindingElement(spTokenManager)); + } + + return bindingElements.ToArray(); + } + + /// <summary> /// Uri-escapes the names and values in a dictionary per OAuth 1.0 section 5.1. /// </summary> /// <param name="source">The dictionary with names and values to encode.</param> @@ -282,7 +311,6 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <para>This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).</para> /// </remarks> private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) { - var protocol = Protocol.Lookup(requestMessage.Version); var dictionary = this.MessageDescriptions.GetAccessor(requestMessage); // copy so as to not modify original @@ -305,7 +333,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { httpRequest.Method = GetHttpMethod(requestMessage); StringBuilder authorization = new StringBuilder(); - authorization.Append(protocol.AuthorizationHeaderScheme); + authorization.Append(Protocol.AuthorizationHeaderScheme); authorization.Append(" "); foreach (var pair in fields) { string key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key); @@ -364,7 +392,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { ErrorUtilities.VerifyInternal(consumerKey == consumerTokenManager.ConsumerKey, "The token manager consumer key and the consumer key set earlier do not match!"); return consumerTokenManager.ConsumerSecret; } else { - return ((IServiceProviderTokenManager)this.TokenManager).GetConsumerSecret(consumerKey); + return ((IServiceProviderTokenManager)this.TokenManager).GetConsumer(consumerKey).Secret; } } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs index fce351b..e05bb62 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthConsumerMessageFactory.cs @@ -44,7 +44,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { MessageBase message = null; if (fields.ContainsKey("oauth_token")) { - message = new UserAuthorizationResponse(recipient.Location); + Protocol protocol = fields.ContainsKey("oauth_verifier") ? Protocol.V10a : Protocol.V10; + message = new UserAuthorizationResponse(recipient.Location, protocol.Version); } if (message != null) { @@ -92,7 +93,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { var unauthorizedTokenRequest = request as UnauthorizedTokenRequest; var authorizedTokenRequest = request as AuthorizedTokenRequest; if (unauthorizedTokenRequest != null) { - message = new UnauthorizedTokenResponse(unauthorizedTokenRequest); + Protocol protocol = fields.ContainsKey("oauth_callback_confirmed") ? Protocol.V10a : Protocol.V10; + message = new UnauthorizedTokenResponse(unauthorizedTokenRequest, protocol.Version); } else if (authorizedTokenRequest != null) { message = new AuthorizedTokenResponse(authorizedTokenRequest); } else { diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs new file mode 100644 index 0000000..bd57012 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthIdentity.cs @@ -0,0 +1,65 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthIdentity.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Runtime.InteropServices; + using System.Security.Principal; + using DotNetOpenAuth.Messaging; + + /// <summary> + /// Represents an OAuth consumer that is impersonating a known user on the system. + /// </summary> + [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "Not cocreatable.")] + [Serializable] + [ComVisible(true)] + public class OAuthIdentity : IIdentity { + /// <summary> + /// Initializes a new instance of the <see cref="OAuthIdentity"/> class. + /// </summary> + /// <param name="username">The username.</param> + internal OAuthIdentity(string username) { + Contract.Requires(!String.IsNullOrEmpty(username)); + ErrorUtilities.VerifyNonZeroLength(username, "username"); + this.Name = username; + } + + #region IIdentity Members + + /// <summary> + /// Gets the type of authentication used. + /// </summary> + /// <value>The constant "OAuth"</value> + /// <returns> + /// The type of authentication used to identify the user. + /// </returns> + public string AuthenticationType { + get { return "OAuth"; } + } + + /// <summary> + /// Gets a value indicating whether the user has been authenticated. + /// </summary> + /// <value>The value <c>true</c></value> + /// <returns>true if the user was authenticated; otherwise, false. + /// </returns> + public bool IsAuthenticated { + get { return true; } + } + + /// <summary> + /// Gets the name of the user who authorized the OAuth token the consumer is using for authorization. + /// </summary> + /// <returns> + /// The name of the user on whose behalf the code is running. + /// </returns> + public string Name { get; private set; } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs new file mode 100644 index 0000000..a9f363a --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthPrincipal.cs @@ -0,0 +1,91 @@ +//----------------------------------------------------------------------- +// <copyright file="OAuthPrincipal.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Runtime.InteropServices; + using System.Security.Principal; + + /// <summary> + /// Represents an OAuth consumer that is impersonating a known user on the system. + /// </summary> + [SuppressMessage("Microsoft.Interoperability", "CA1409:ComVisibleTypesShouldBeCreatable", Justification = "Not cocreatable.")] + [Serializable] + [ComVisible(true)] + public class OAuthPrincipal : IPrincipal { + /// <summary> + /// The roles this user belongs to. + /// </summary> + private ICollection<string> roles; + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="token">The access token.</param> + internal OAuthPrincipal(IServiceProviderAccessToken token) + : this(token.Username, token.Roles) { + Contract.Requires(token != null); + + this.AccessToken = token.Token; + } + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="identity">The identity.</param> + /// <param name="roles">The roles this user belongs to.</param> + internal OAuthPrincipal(OAuthIdentity identity, string[] roles) { + this.Identity = identity; + this.roles = roles; + } + + /// <summary> + /// Initializes a new instance of the <see cref="OAuthPrincipal"/> class. + /// </summary> + /// <param name="username">The username.</param> + /// <param name="roles">The roles this user belongs to.</param> + internal OAuthPrincipal(string username, string[] roles) + : this(new OAuthIdentity(username), roles) { + } + + /// <summary> + /// Gets the access token used to create this principal. + /// </summary> + /// <value>A non-empty string.</value> + public string AccessToken { get; private set; } + + #region IPrincipal Members + + /// <summary> + /// Gets the identity of the current principal. + /// </summary> + /// <value></value> + /// <returns> + /// The <see cref="T:System.Security.Principal.IIdentity"/> object associated with the current principal. + /// </returns> + public IIdentity Identity { get; private set; } + + /// <summary> + /// Determines whether the current principal belongs to the specified role. + /// </summary> + /// <param name="role">The name of the role for which to check membership.</param> + /// <returns> + /// true if the current principal is a member of the specified role; otherwise, false. + /// </returns> + /// <remarks> + /// The role membership check uses <see cref="StringComparer.OrdinalIgnoreCase"/>. + /// </remarks> + public bool IsInRole(string role) { + return this.roles.Contains(role, StringComparer.OrdinalIgnoreCase); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs index 1aaea7f..abb99d8 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/OAuthServiceProviderMessageFactory.cs @@ -24,7 +24,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Initializes a new instance of the <see cref="OAuthServiceProviderMessageFactory"/> class. /// </summary> /// <param name="tokenManager">The token manager instance to use.</param> - protected internal OAuthServiceProviderMessageFactory(IServiceProviderTokenManager tokenManager) { + public OAuthServiceProviderMessageFactory(IServiceProviderTokenManager tokenManager) { ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); this.tokenManager = tokenManager; @@ -54,29 +54,51 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { ErrorUtilities.VerifyArgumentNotNull(fields, "fields"); MessageBase message = null; + Protocol protocol = Protocol.V10; // default to assuming the less-secure 1.0 instead of 1.0a until we prove otherwise. + string token; + fields.TryGetValue("oauth_token", out token); - if (fields.ContainsKey("oauth_consumer_key") && - !fields.ContainsKey("oauth_token")) { - message = new UnauthorizedTokenRequest(recipient); - } else if (fields.ContainsKey("oauth_consumer_key") && - fields.ContainsKey("oauth_token")) { - // Discern between RequestAccessToken and AccessProtectedResources, - // which have all the same parameters, by figuring out what type of token - // is in the token parameter. - bool tokenTypeIsAccessToken = this.tokenManager.GetTokenType(fields["oauth_token"]) == TokenType.AccessToken; + try { + if (fields.ContainsKey("oauth_consumer_key") && !fields.ContainsKey("oauth_token")) { + protocol = fields.ContainsKey("oauth_callback") ? Protocol.V10a : Protocol.V10; + message = new UnauthorizedTokenRequest(recipient, protocol.Version); + } else if (fields.ContainsKey("oauth_consumer_key") && fields.ContainsKey("oauth_token")) { + // Discern between RequestAccessToken and AccessProtectedResources, + // which have all the same parameters, by figuring out what type of token + // is in the token parameter. + bool tokenTypeIsAccessToken = this.tokenManager.GetTokenType(token) == TokenType.AccessToken; - message = tokenTypeIsAccessToken ? (MessageBase)new AccessProtectedResourceRequest(recipient) : - new AuthorizedTokenRequest(recipient); - } else { - // fail over to the message with no required fields at all. - message = new UserAuthorizationRequest(recipient); - } + if (tokenTypeIsAccessToken) { + message = (MessageBase)new AccessProtectedResourceRequest(recipient, protocol.Version); + } else { + // Discern between 1.0 and 1.0a requests by checking on the consumer version we stored + // when the consumer first requested an unauthorized token. + protocol = Protocol.Lookup(this.tokenManager.GetRequestToken(token).ConsumerVersion); + message = new AuthorizedTokenRequest(recipient, protocol.Version); + } + } else { + // fail over to the message with no required fields at all. + if (token != null) { + protocol = Protocol.Lookup(this.tokenManager.GetRequestToken(token).ConsumerVersion); + } - if (message != null) { - message.SetAsIncoming(); - } + // If a callback parameter is included, that suggests either the consumer + // is following OAuth 1.0 instead of 1.0a, or that a hijacker is trying + // to attack. Either way, if the consumer started out as a 1.0a, keep it + // that way, and we'll just ignore the oauth_callback included in this message + // by virtue of the UserAuthorizationRequest message not including it in its + // 1.0a payload. + message = new UserAuthorizationRequest(recipient, protocol.Version); + } - return message; + if (message != null) { + message.SetAsIncoming(); + } + + return message; + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); + } } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs index 779f2c5..4f8b5e5 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; + using System.Diagnostics.Contracts; using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; using System.Text; @@ -16,15 +17,24 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> public class RsaSha1SigningBindingElement : SigningBindingElementBase { /// <summary> + /// The name of the hash algorithm to use. + /// </summary> + private const string HashAlgorithmName = "RSA-SHA1"; + + /// <summary> + /// The token manager for the service provider. + /// </summary> + private IServiceProviderTokenManager tokenManager; + + /// <summary> /// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class /// for use by Consumers. /// </summary> /// <param name="signingCertificate">The certificate used to sign outgoing messages.</param> public RsaSha1SigningBindingElement(X509Certificate2 signingCertificate) - : this() { - if (signingCertificate == null) { - throw new ArgumentNullException("signingCertificate"); - } + : base(HashAlgorithmName) { + Contract.Requires(signingCertificate != null); + ErrorUtilities.VerifyArgumentNotNull(signingCertificate, "signingCertificate"); this.SigningCertificate = signingCertificate; } @@ -33,21 +43,21 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class /// for use by Service Providers. /// </summary> - public RsaSha1SigningBindingElement() - : base("RSA-SHA1") { + /// <param name="tokenManager">The token manager.</param> + public RsaSha1SigningBindingElement(IServiceProviderTokenManager tokenManager) + : base(HashAlgorithmName) { + Contract.Requires(tokenManager != null); + ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + + this.tokenManager = tokenManager; } /// <summary> - /// Gets or sets the certificate used to sign outgoing messages. + /// Gets or sets the certificate used to sign outgoing messages. Used only by Consumers. /// </summary> public X509Certificate2 SigningCertificate { get; set; } /// <summary> - /// Gets or sets the consumer certificate provider. - /// </summary> - public IConsumerCertificateProvider ConsumerCertificateProvider { get; set; } - - /// <summary> /// Calculates a signature for a given message. /// </summary> /// <param name="message">The message to sign.</param> @@ -56,13 +66,8 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// This method signs the message per OAuth 1.0 section 9.3. /// </remarks> protected override string GetSignature(ITamperResistantOAuthMessage message) { - if (message == null) { - throw new ArgumentNullException("message"); - } - - if (this.SigningCertificate == null) { - throw new InvalidOperationException(OAuthStrings.X509CertificateNotProvidedForSigning); - } + ErrorUtilities.VerifyArgumentNotNull(message, "message"); + ErrorUtilities.VerifyOperation(this.SigningCertificate != null, OAuthStrings.X509CertificateNotProvidedForSigning); string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message)); byte[] data = Encoding.ASCII.GetBytes(signatureBaseString); @@ -80,16 +85,14 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>. /// </returns> protected override bool IsSignatureValid(ITamperResistantOAuthMessage message) { - if (this.ConsumerCertificateProvider == null) { - throw new InvalidOperationException(OAuthStrings.ConsumerCertificateProviderNotAvailable); - } + ErrorUtilities.VerifyInternal(this.tokenManager != null, "No token manager available for fetching Consumer public certificates."); string signatureBaseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(message)); byte[] data = Encoding.ASCII.GetBytes(signatureBaseString); byte[] carriedSignature = Convert.FromBase64String(message.Signature); - X509Certificate2 cert = this.ConsumerCertificateProvider.GetCertificate(message); + X509Certificate2 cert = this.tokenManager.GetConsumer(message.ConsumerKey).Certificate; if (cert == null) { Logger.Signatures.WarnFormat("Incoming message from consumer '{0}' could not be matched with an appropriate X.509 certificate for signature verification.", message.ConsumerKey); return false; @@ -105,10 +108,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { /// </summary> /// <returns>A new instance of the binding element.</returns> protected override ITamperProtectionChannelBindingElement Clone() { - return new RsaSha1SigningBindingElement() { - ConsumerCertificateProvider = this.ConsumerCertificateProvider, - SigningCertificate = this.SigningCertificate, - }; + if (this.tokenManager != null) { + return new RsaSha1SigningBindingElement(this.tokenManager); + } else { + return new RsaSha1SigningBindingElement(this.SigningCertificate); + } } } } diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs new file mode 100644 index 0000000..3e75e7b --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/TokenHandlingBindingElement.cs @@ -0,0 +1,190 @@ +//----------------------------------------------------------------------- +// <copyright file="TokenHandlingBindingElement.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Diagnostics.Contracts; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Configuration; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.Messages; + + /// <summary> + /// A binding element for Service Providers to manage the + /// callbacks and verification codes on applicable messages. + /// </summary> + internal class TokenHandlingBindingElement : IChannelBindingElement { + /// <summary> + /// The token manager offered by the service provider. + /// </summary> + private IServiceProviderTokenManager tokenManager; + + /// <summary> + /// Initializes a new instance of the <see cref="TokenHandlingBindingElement"/> class. + /// </summary> + /// <param name="tokenManager">The token manager.</param> + internal TokenHandlingBindingElement(IServiceProviderTokenManager tokenManager) { + Contract.Requires(tokenManager != null); + ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + + this.tokenManager = tokenManager; + } + + #region IChannelBindingElement Members + + /// <summary> + /// Gets or sets the channel that this binding element belongs to. + /// </summary> + /// <remarks> + /// This property is set by the channel when it is first constructed. + /// </remarks> + public Channel Channel { get; set; } + + /// <summary> + /// Gets the protection commonly offered (if any) by this binding element. + /// </summary> + /// <remarks> + /// This value is used to assist in sorting binding elements in the channel stack. + /// </remarks> + public MessageProtections Protection { + get { return MessageProtections.None; } + } + + /// <summary> + /// Prepares a message for sending based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The message to prepare for sending.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) { + ErrorUtilities.VerifyArgumentNotNull(message, "message"); + + var userAuthResponse = message as UserAuthorizationResponse; + if (userAuthResponse != null && userAuthResponse.Version >= Protocol.V10a.Version) { + this.tokenManager.GetRequestToken(userAuthResponse.RequestToken).VerificationCode = userAuthResponse.VerificationCode; + return MessageProtections.None; + } + + // Hook to store the token and secret on its way down to the Consumer. + var grantRequestTokenResponse = message as UnauthorizedTokenResponse; + if (grantRequestTokenResponse != null) { + this.tokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse); + this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken).ConsumerVersion = grantRequestTokenResponse.Version; + if (grantRequestTokenResponse.RequestMessage.Callback != null) { + this.tokenManager.GetRequestToken(grantRequestTokenResponse.RequestToken).Callback = grantRequestTokenResponse.RequestMessage.Callback; + } + + return MessageProtections.None; + } + + return null; + } + + /// <summary> + /// Performs any transformation on an incoming message that may be necessary and/or + /// validates an incoming message based on the rules of this channel binding element. + /// </summary> + /// <param name="message">The incoming message to process.</param> + /// <returns> + /// The protections (if any) that this binding element applied to the message. + /// Null if this binding element did not even apply to this binding element. + /// </returns> + /// <exception cref="ProtocolException"> + /// Thrown when the binding element rules indicate that this message is invalid and should + /// NOT be processed. + /// </exception> + /// <remarks> + /// Implementations that provide message protection must honor the + /// <see cref="MessagePartAttribute.RequiredProtection"/> properties where applicable. + /// </remarks> + public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) { + ErrorUtilities.VerifyArgumentNotNull(message, "message"); + + var authorizedTokenRequest = message as AuthorizedTokenRequest; + if (authorizedTokenRequest != null) { + if (authorizedTokenRequest.Version >= Protocol.V10a.Version) { + string expectedVerifier = this.tokenManager.GetRequestToken(authorizedTokenRequest.RequestToken).VerificationCode; + ErrorUtilities.VerifyProtocol(string.Equals(authorizedTokenRequest.VerificationCode, expectedVerifier, StringComparison.Ordinal), OAuthStrings.IncorrectVerifier); + return MessageProtections.None; + } + + this.VerifyThrowTokenTimeToLive(authorizedTokenRequest); + } + + var userAuthorizationRequest = message as UserAuthorizationRequest; + if (userAuthorizationRequest != null) { + this.VerifyThrowTokenTimeToLive(userAuthorizationRequest); + } + + var accessResourceRequest = message as AccessProtectedResourceRequest; + if (accessResourceRequest != null) { + this.VerifyThrowTokenNotExpired(accessResourceRequest); + } + + return null; + } + + #endregion + + /// <summary> + /// Ensures that access tokens have not yet expired. + /// </summary> + /// <param name="message">The incoming message carrying the access token.</param> + private void VerifyThrowTokenNotExpired(AccessProtectedResourceRequest message) { + ErrorUtilities.VerifyArgumentNotNull(message, "message"); + + try { + IServiceProviderAccessToken token = this.tokenManager.GetAccessToken(message.AccessToken); + if (token.ExpirationDate.HasValue && DateTime.Now >= token.ExpirationDate.Value.ToLocalTimeSafe()) { + Logger.OAuth.ErrorFormat( + "OAuth access token {0} rejected because it expired at {1}, and it is now {2}.", + token.Token, + token.ExpirationDate.Value, + DateTime.Now); + ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound); + } + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); + } + } + + /// <summary> + /// Ensures that short-lived request tokens included in incoming messages have not expired. + /// </summary> + /// <param name="message">The incoming message.</param> + /// <exception cref="ProtocolException">Thrown when the token in the message has expired.</exception> + private void VerifyThrowTokenTimeToLive(ITokenContainingMessage message) { + ErrorUtilities.VerifyInternal(!(message is AccessProtectedResourceRequest), "We shouldn't be verifying TTL on access tokens."); + if (message == null || string.IsNullOrEmpty(message.Token)) { + return; + } + + try { + IServiceProviderRequestToken token = this.tokenManager.GetRequestToken(message.Token); + TimeSpan ttl = DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.MaximumRequestTokenTimeToLive; + if (DateTime.Now >= token.CreatedOn.ToLocalTimeSafe() + ttl) { + Logger.OAuth.ErrorFormat( + "OAuth request token {0} rejected because it was originally issued at {1}, expired at {2}, and it is now {3}.", + token.Token, + token.CreatedOn, + token.CreatedOn + ttl, + DateTime.Now); + ErrorUtilities.ThrowProtocol(OAuthStrings.TokenNotFound); + } + } catch (KeyNotFoundException ex) { + throw ErrorUtilities.Wrap(ex, OAuthStrings.TokenNotFound); + } + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs new file mode 100644 index 0000000..5aedc9d --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/UriOrOobEncoding.cs @@ -0,0 +1,78 @@ +//----------------------------------------------------------------------- +// <copyright file="UriOrOobEncoding.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth.ChannelElements { + using System; + using System.Collections.Generic; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.Messaging.Reflection; + + /// <summary> + /// An URI encoder that translates null <see cref="Uri"/> references as "oob" + /// instead of an empty/missing argument. + /// </summary> + internal class UriOrOobEncoding : IMessagePartNullEncoder { + /// <summary> + /// The string constant "oob", used to indicate an out-of-band configuration. + /// </summary> + private const string OutOfBandConfiguration = "oob"; + + /// <summary> + /// Initializes a new instance of the <see cref="UriOrOobEncoding"/> class. + /// </summary> + public UriOrOobEncoding() { + } + + #region IMessagePartNullEncoder Members + + /// <summary> + /// Gets the string representation to include in a serialized message + /// when the message part has a <c>null</c> value. + /// </summary> + /// <value></value> + public string EncodedNullValue { + get { return OutOfBandConfiguration; } + } + + #endregion + + #region IMessagePartEncoder Members + + /// <summary> + /// Encodes the specified value. + /// </summary> + /// <param name="value">The value. Guaranteed to never be null.</param> + /// <returns> + /// The <paramref name="value"/> in string form, ready for message transport. + /// </returns> + public string Encode(object value) { + ErrorUtilities.VerifyArgumentNotNull(value, "value"); + + Uri uriValue = (Uri)value; + return uriValue.AbsoluteUri; + } + + /// <summary> + /// Decodes the specified value. + /// </summary> + /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param> + /// <returns> + /// The deserialized form of the given string. + /// </returns> + /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception> + public object Decode(string value) { + if (string.Equals(value, OutOfBandConfiguration, StringComparison.Ordinal)) { + return null; + } else { + return new Uri(value, UriKind.Absolute); + } + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs index 3abb794..55b40ac 100644 --- a/src/DotNetOpenAuth/OAuth/ConsumerBase.cs +++ b/src/DotNetOpenAuth/OAuth/ConsumerBase.cs @@ -10,6 +10,7 @@ namespace DotNetOpenAuth.OAuth { using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Net; + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth.ChannelElements; @@ -32,6 +33,7 @@ namespace DotNetOpenAuth.OAuth { INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager); this.ServiceProvider = serviceDescription; + this.SecuritySettings = DotNetOpenAuthSection.Configuration.OAuth.Consumer.SecuritySettings.CreateSecuritySettings(); } /// <summary> @@ -61,6 +63,11 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Gets the security settings for this consumer. + /// </summary> + internal ConsumerSecuritySettings SecuritySettings { get; private set; } + + /// <summary> /// Gets or sets the channel to use for sending/receiving messages. /// </summary> internal OAuthChannel OAuthChannel { get; set; } @@ -167,7 +174,7 @@ namespace DotNetOpenAuth.OAuth { ErrorUtilities.VerifyArgumentNotNull(endpoint, "endpoint"); ErrorUtilities.VerifyNonZeroLength(accessToken, "accessToken"); - AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint) { + AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint, this.ServiceProvider.Version) { AccessToken = accessToken, ConsumerKey = this.ConsumerKey, }; @@ -189,18 +196,26 @@ namespace DotNetOpenAuth.OAuth { /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns> [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "3#", Justification = "Two results")] protected internal UserAuthorizationRequest PrepareRequestUserAuthorization(Uri callback, IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters, out string requestToken) { - // Obtain an unauthorized request token. - var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint) { + // Obtain an unauthorized request token. Assume the OAuth version given in the service description. + var token = new UnauthorizedTokenRequest(this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version) { ConsumerKey = this.ConsumerKey, + Callback = callback, }; var tokenAccessor = this.Channel.MessageDescriptions.GetAccessor(token); tokenAccessor.AddExtraParameters(requestParameters); var requestTokenResponse = this.Channel.Request<UnauthorizedTokenResponse>(token); this.TokenManager.StoreNewRequestToken(token, requestTokenResponse); - // Request user authorization. + // Fine-tune our understanding of the SP's supported OAuth version if it's wrong. + if (this.ServiceProvider.Version != requestTokenResponse.Version) { + Logger.OAuth.WarnFormat("Expected OAuth service provider at endpoint {0} to use OAuth {1} but {2} was detected. Adjusting service description to new version.", this.ServiceProvider.RequestTokenEndpoint, this.ServiceProvider.Version, requestTokenResponse.Version); + this.ServiceProvider.ProtocolVersion = Protocol.Lookup(requestTokenResponse.Version).ProtocolVersion; + } + + // Request user authorization. The OAuth version will automatically include + // or drop the callback that we're setting here. ITokenContainingMessage assignedRequestToken = requestTokenResponse; - var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token) { + var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token, requestTokenResponse.Version) { Callback = callback, }; var requestAuthorizationAccessor = this.Channel.MessageDescriptions.GetAccessor(requestAuthorization); @@ -213,14 +228,18 @@ namespace DotNetOpenAuth.OAuth { /// Exchanges a given request token for access token. /// </summary> /// <param name="requestToken">The request token that the user has authorized.</param> - /// <returns>The access token assigned by the Service Provider.</returns> - protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken) { + /// <param name="verifier">The verifier code.</param> + /// <returns> + /// The access token assigned by the Service Provider. + /// </returns> + protected AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) { Contract.Requires(!String.IsNullOrEmpty(requestToken)); Contract.Ensures(Contract.Result<AuthorizedTokenResponse>() != null); ErrorUtilities.VerifyNonZeroLength(requestToken, "requestToken"); - var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint) { + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, this.ServiceProvider.Version) { RequestToken = requestToken, + VerificationCode = verifier, ConsumerKey = this.ConsumerKey, }; var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess); diff --git a/src/DotNetOpenAuth/OAuth/ConsumerSecuritySettings.cs b/src/DotNetOpenAuth/OAuth/ConsumerSecuritySettings.cs new file mode 100644 index 0000000..bb2fbaa --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ConsumerSecuritySettings.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------- +// <copyright file="ConsumerSecuritySettings.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth { + /// <summary> + /// Security settings that are applicable to consumers. + /// </summary> + internal class ConsumerSecuritySettings : SecuritySettings { + /// <summary> + /// Initializes a new instance of the <see cref="ConsumerSecuritySettings"/> class. + /// </summary> + internal ConsumerSecuritySettings() { + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs b/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs index ca74a77..f9c1a94 100644 --- a/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs +++ b/src/DotNetOpenAuth/OAuth/DesktopConsumer.cs @@ -49,8 +49,25 @@ namespace DotNetOpenAuth.OAuth { /// </summary> /// <param name="requestToken">The request token that the user has authorized.</param> /// <returns>The access token assigned by the Service Provider.</returns> - public new AuthorizedTokenResponse ProcessUserAuthorization(string requestToken) { - return base.ProcessUserAuthorization(requestToken); + [Obsolete("Use the ProcessUserAuthorization method that takes a verifier parameter instead.")] + public AuthorizedTokenResponse ProcessUserAuthorization(string requestToken) { + return this.ProcessUserAuthorization(requestToken, null); + } + + /// <summary> + /// Exchanges a given request token for access token. + /// </summary> + /// <param name="requestToken">The request token that the user has authorized.</param> + /// <param name="verifier">The verifier code typed in by the user. Must not be <c>Null</c> for OAuth 1.0a service providers and later.</param> + /// <returns> + /// The access token assigned by the Service Provider. + /// </returns> + public new AuthorizedTokenResponse ProcessUserAuthorization(string requestToken, string verifier) { + if (this.ServiceProvider.Version >= Protocol.V10a.Version) { + ErrorUtilities.VerifyNonZeroLength(verifier, "verifier"); + } + + return base.ProcessUserAuthorization(requestToken, verifier); } } } diff --git a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs index 62e02de..b60fda4 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/AccessProtectedResourceRequest.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth.Messages { + using System; using System.Diagnostics.CodeAnalysis; using DotNetOpenAuth.Messaging; @@ -17,8 +18,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="AccessProtectedResourceRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> - protected internal AccessProtectedResourceRequest(MessageReceivingEndpoint serviceProvider) - : base(MessageTransport.Direct, serviceProvider) { + /// <param name="version">The OAuth version.</param> + protected internal AccessProtectedResourceRequest(MessageReceivingEndpoint serviceProvider, Version version) + : base(MessageTransport.Direct, serviceProvider, version) { } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs index 2d4793c..1228290 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenRequest.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth.Messages { + using System; using System.Globalization; using DotNetOpenAuth.Messaging; @@ -20,8 +21,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="AuthorizedTokenRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> - internal AuthorizedTokenRequest(MessageReceivingEndpoint serviceProvider) - : base(MessageTransport.Direct, serviceProvider) { + /// <param name="version">The OAuth version.</param> + internal AuthorizedTokenRequest(MessageReceivingEndpoint serviceProvider, Version version) + : base(MessageTransport.Direct, serviceProvider, version) { } /// <summary> @@ -33,6 +35,13 @@ namespace DotNetOpenAuth.OAuth.Messages { } /// <summary> + /// Gets or sets the verification code received by the Consumer from the Service Provider + /// in the <see cref="UserAuthorizationResponse.VerificationCode"/> property. + /// </summary> + [MessagePart("oauth_verifier", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion)] + public string VerificationCode { get; set; } + + /// <summary> /// Gets or sets the unauthorized Request Token used to obtain authorization. /// </summary> [MessagePart("oauth_token", IsRequired = true)] diff --git a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs index 14413a5..0b14819 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/AuthorizedTokenResponse.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth.Messages { + using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using DotNetOpenAuth.Messaging; @@ -19,7 +20,7 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> /// <param name="originatingRequest">The originating request.</param> protected internal AuthorizedTokenResponse(AuthorizedTokenRequest originatingRequest) - : base(MessageProtections.None, originatingRequest) { + : base(MessageProtections.None, originatingRequest, originatingRequest.Version) { } /// <summary> diff --git a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs index e0269db..944bc5c 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/MessageBase.cs @@ -17,6 +17,7 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <summary> /// A base class for all OAuth messages. /// </summary> + [Serializable] public abstract class MessageBase : IDirectedProtocolMessage, IDirectResponseProtocolMessage { /// <summary> /// A store for extra name/value data pairs that are attached to this message. @@ -62,12 +63,15 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> /// <param name="protectionRequired">The level of protection the message requires.</param> /// <param name="originatingRequest">The request that asked for this direct response.</param> - protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest) { + /// <param name="version">The OAuth version.</param> + protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest, Version version) { ErrorUtilities.VerifyArgumentNotNull(originatingRequest, "originatingRequest"); + ErrorUtilities.VerifyArgumentNotNull(version, "version"); this.protectionRequired = protectionRequired; this.transport = MessageTransport.Direct; this.originatingRequest = originatingRequest; + this.Version = version; } /// <summary> @@ -76,14 +80,15 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <param name="protectionRequired">The level of protection the message requires.</param> /// <param name="transport">A value indicating whether this message requires a direct or indirect transport.</param> /// <param name="recipient">The URI that a directed message will be delivered to.</param> - protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient) { - if (recipient == null) { - throw new ArgumentNullException("recipient"); - } + /// <param name="version">The OAuth version.</param> + protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient, Version version) { + ErrorUtilities.VerifyArgumentNotNull(recipient, "recipient"); + ErrorUtilities.VerifyArgumentNotNull(version, "version"); this.protectionRequired = protectionRequired; this.transport = transport; this.recipient = recipient; + this.Version = version; } #region IProtocolMessage Properties @@ -163,9 +168,7 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <summary> /// Gets the version of the protocol this message is prepared to implement. /// </summary> - protected virtual Version Version { - get { return new Version(1, 0); } - } + protected internal Version Version { get; private set; } /// <summary> /// Gets the level of protection this message requires. diff --git a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs b/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs index d1abb58..1d8ca21 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/SignedMessageBase.cs @@ -31,8 +31,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> /// <param name="transport">A value indicating whether this message requires a direct or indirect transport.</param> /// <param name="recipient">The URI that a directed message will be delivered to.</param> - internal SignedMessageBase(MessageTransport transport, MessageReceivingEndpoint recipient) - : base(MessageProtections.All, transport, recipient) { + /// <param name="version">The OAuth version.</param> + internal SignedMessageBase(MessageTransport transport, MessageReceivingEndpoint recipient, Version version) + : base(MessageProtections.All, transport, recipient, version) { ITamperResistantOAuthMessage self = (ITamperResistantOAuthMessage)this; HttpDeliveryMethods methods = ((IDirectedProtocolMessage)this).HttpMethods; self.HttpMethod = (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET"; @@ -164,7 +165,7 @@ namespace DotNetOpenAuth.OAuth.Messages { [MessagePart("oauth_version", IsRequired = false)] private string OAuthVersion { get { - return Version.ToString(); + return Protocol.Lookup(Version).PublishedVersion; } set { diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs index e491bad..9214d91 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenRequest.cs @@ -5,8 +5,10 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OAuth.Messages { + using System; using System.Collections.Generic; using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OAuth.ChannelElements; /// <summary> /// A direct message sent from Consumer to Service Provider to request a Request Token. @@ -16,11 +18,23 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="UnauthorizedTokenRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> - protected internal UnauthorizedTokenRequest(MessageReceivingEndpoint serviceProvider) - : base(MessageTransport.Direct, serviceProvider) { + /// <param name="version">The OAuth version.</param> + protected internal UnauthorizedTokenRequest(MessageReceivingEndpoint serviceProvider, Version version) + : base(MessageTransport.Direct, serviceProvider, version) { } /// <summary> + /// Gets or sets the absolute URL to which the Service Provider will redirect the + /// User back when the Obtaining User Authorization step is completed. + /// </summary> + /// <value> + /// The callback URL; or <c>null</c> if the Consumer is unable to receive + /// callbacks or a callback URL has been established via other means. + /// </value> + [MessagePart("oauth_callback", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion, Encoder = typeof(UriOrOobEncoding))] + public Uri Callback { get; set; } + + /// <summary> /// Gets the extra, non-OAuth parameters that will be included in the message. /// </summary> public new IDictionary<string, string> ExtraData { diff --git a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs index 285dec7..ce09213 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UnauthorizedTokenResponse.cs @@ -25,7 +25,7 @@ namespace DotNetOpenAuth.OAuth.Messages { /// This constructor is used by the Service Provider to send the message. /// </remarks> protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest requestMessage, string requestToken, string tokenSecret) - : this(requestMessage) { + : this(requestMessage, requestMessage.Version) { ErrorUtilities.VerifyArgumentNotNull(requestToken, "requestToken"); ErrorUtilities.VerifyArgumentNotNull(tokenSecret, "tokenSecret"); @@ -37,9 +37,10 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class. /// </summary> /// <param name="originatingRequest">The originating request.</param> + /// <param name="version">The OAuth version.</param> /// <remarks>This constructor is used by the consumer to deserialize the message.</remarks> - protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest originatingRequest) - : base(MessageProtections.None, originatingRequest) { + protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest originatingRequest, Version version) + : base(MessageProtections.None, originatingRequest, version) { } /// <summary> @@ -84,5 +85,15 @@ namespace DotNetOpenAuth.OAuth.Messages { /// </summary> [MessagePart("oauth_token_secret", IsRequired = true)] protected internal string TokenSecret { get; set; } + + /// <summary> + /// Gets a value indicating whether the Service Provider recognized the callback parameter in the request. + /// </summary> + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Message serialization invoked.")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Message parts must be instance members.")] + [MessagePart("oauth_callback_confirmed", IsRequired = true, MinVersion = Protocol.V10aVersion)] + private bool CallbackConfirmed { + get { return true; } + } } } diff --git a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs index f1af0bc..a5823bb 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationRequest.cs @@ -15,14 +15,16 @@ namespace DotNetOpenAuth.OAuth.Messages { /// so the Service Provider can ask the user to authorize the Consumer's access to some /// protected resource(s). /// </summary> + [Serializable] public class UserAuthorizationRequest : MessageBase, ITokenContainingMessage { /// <summary> /// Initializes a new instance of the <see cref="UserAuthorizationRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> /// <param name="requestToken">The request token.</param> - internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, string requestToken) - : this(serviceProvider) { + /// <param name="version">The OAuth version.</param> + internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, string requestToken, Version version) + : this(serviceProvider, version) { this.RequestToken = requestToken; } @@ -30,8 +32,9 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Initializes a new instance of the <see cref="UserAuthorizationRequest"/> class. /// </summary> /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param> - internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider) - : base(MessageProtections.None, MessageTransport.Indirect, serviceProvider) { + /// <param name="version">The OAuth version.</param> + internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, Version version) + : base(MessageProtections.None, MessageTransport.Indirect, serviceProvider, version) { } /// <summary> @@ -51,6 +54,14 @@ namespace DotNetOpenAuth.OAuth.Messages { } /// <summary> + /// Gets a value indicating whether this is a safe OAuth authorization request. + /// </summary> + /// <value><c>true</c> if the Consumer is using OAuth 1.0a or later; otherwise, <c>false</c>.</value> + public bool IsUnsafeRequest { + get { return this.Version < Protocol.V10a.Version; } + } + + /// <summary> /// Gets or sets the Request Token obtained in the previous step. /// </summary> /// <remarks> @@ -65,7 +76,7 @@ namespace DotNetOpenAuth.OAuth.Messages { /// Gets or sets a URL the Service Provider will use to redirect the User back /// to the Consumer when Obtaining User Authorization is complete. Optional. /// </summary> - [MessagePart("oauth_callback", IsRequired = false)] + [MessagePart("oauth_callback", IsRequired = false, MaxVersion = "1.0")] internal Uri Callback { get; set; } } } diff --git a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs index da6a909..73fddc7 100644 --- a/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs +++ b/src/DotNetOpenAuth/OAuth/Messages/UserAuthorizationResponse.cs @@ -14,13 +14,15 @@ namespace DotNetOpenAuth.OAuth.Messages { /// <remarks> /// The class is sealed because extra parameters are determined by the callback URI provided by the Consumer. /// </remarks> + [Serializable] public sealed class UserAuthorizationResponse : MessageBase, ITokenContainingMessage { /// <summary> /// Initializes a new instance of the <see cref="UserAuthorizationResponse"/> class. /// </summary> /// <param name="consumer">The URI of the Consumer endpoint to send this message to.</param> - internal UserAuthorizationResponse(Uri consumer) - : base(MessageProtections.None, MessageTransport.Indirect, new MessageReceivingEndpoint(consumer, HttpDeliveryMethods.GetRequest)) { + /// <param name="version">The OAuth version.</param> + internal UserAuthorizationResponse(Uri consumer, Version version) + : base(MessageProtections.None, MessageTransport.Indirect, new MessageReceivingEndpoint(consumer, HttpDeliveryMethods.GetRequest), version) { } /// <summary> @@ -32,6 +34,20 @@ namespace DotNetOpenAuth.OAuth.Messages { } /// <summary> + /// Gets or sets the verification code that must accompany the request to exchange the + /// authorized request token for an access token. + /// </summary> + /// <value>An unguessable value passed to the Consumer via the User and REQUIRED to complete the process.</value> + /// <remarks> + /// If the Consumer did not provide a callback URL, the Service Provider SHOULD display the value of the + /// verification code, and instruct the User to manually inform the Consumer that authorization is + /// completed. If the Service Provider knows a Consumer to be running on a mobile device or set-top box, + /// the Service Provider SHOULD ensure that the verifier value is suitable for manual entry. + /// </remarks> + [MessagePart("oauth_verifier", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion)] + public string VerificationCode { get; set; } + + /// <summary> /// Gets or sets the Request Token. /// </summary> [MessagePart("oauth_token", IsRequired = true)] diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs index 6eec124..3593446 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.Designer.cs @@ -79,20 +79,20 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> - /// Looks up a localized string similar to The RSA-SHA1 signing binding element's consumer certificate provider has not been set, so no incoming messages from consumers using this signature method can be verified.. + /// Looks up a localized string similar to Failure looking up secret for consumer or token.. /// </summary> - internal static string ConsumerCertificateProviderNotAvailable { + internal static string ConsumerOrTokenSecretNotFound { get { - return ResourceManager.GetString("ConsumerCertificateProviderNotAvailable", resourceCulture); + return ResourceManager.GetString("ConsumerOrTokenSecretNotFound", resourceCulture); } } /// <summary> - /// Looks up a localized string similar to Failure looking up secret for consumer or token.. + /// Looks up a localized string similar to oauth_verifier argument was incorrect.. /// </summary> - internal static string ConsumerOrTokenSecretNotFound { + internal static string IncorrectVerifier { get { - return ResourceManager.GetString("ConsumerOrTokenSecretNotFound", resourceCulture); + return ResourceManager.GetString("IncorrectVerifier", resourceCulture); } } @@ -133,6 +133,15 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Looks up a localized string similar to This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}.. + /// </summary> + internal static string MinimumConsumerVersionRequirementNotMet { + get { + return ResourceManager.GetString("MinimumConsumerVersionRequirementNotMet", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The request URL query MUST NOT contain any OAuth Protocol Parameters.. /// </summary> internal static string RequestUrlMustNotHaveOAuthParameters { @@ -160,6 +169,15 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Looks up a localized string similar to A token in the message was not recognized by the service provider.. + /// </summary> + internal static string TokenNotFound { + get { + return ResourceManager.GetString("TokenNotFound", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The RSA-SHA1 signing binding element has not been set with a certificate for signing.. /// </summary> internal static string X509CertificateNotProvidedForSigning { diff --git a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx index 0aa48f9..bbeeda9 100644 --- a/src/DotNetOpenAuth/OAuth/OAuthStrings.resx +++ b/src/DotNetOpenAuth/OAuth/OAuthStrings.resx @@ -123,18 +123,21 @@ <data name="BadAccessTokenInProtectedResourceRequest" xml:space="preserve"> <value>The access token '{0}' is invalid or expired.</value> </data> - <data name="ConsumerCertificateProviderNotAvailable" xml:space="preserve"> - <value>The RSA-SHA1 signing binding element's consumer certificate provider has not been set, so no incoming messages from consumers using this signature method can be verified.</value> - </data> <data name="ConsumerOrTokenSecretNotFound" xml:space="preserve"> <value>Failure looking up secret for consumer or token.</value> </data> + <data name="IncorrectVerifier" xml:space="preserve"> + <value>oauth_verifier argument was incorrect.</value> + </data> <data name="InvalidIncomingMessage" xml:space="preserve"> <value>An invalid OAuth message received and discarded.</value> </data> <data name="MessageNotAllowedExtraParameters" xml:space="preserve"> <value>The {0} message included extra data which is not allowed.</value> </data> + <data name="MinimumConsumerVersionRequirementNotMet" xml:space="preserve"> + <value>This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}.</value> + </data> <data name="OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface" xml:space="preserve"> <value>Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface.</value> </data> @@ -150,6 +153,9 @@ <data name="SigningElementsMustShareSameProtection" xml:space="preserve"> <value>All signing elements must offer the same message protection.</value> </data> + <data name="TokenNotFound" xml:space="preserve"> + <value>A token in the message was not recognized by the service provider.</value> + </data> <data name="X509CertificateNotProvidedForSigning" xml:space="preserve"> <value>The RSA-SHA1 signing binding element has not been set with a certificate for signing.</value> </data> diff --git a/src/DotNetOpenAuth/OAuth/Protocol.cs b/src/DotNetOpenAuth/OAuth/Protocol.cs index 88615ff..cd4e486 100644 --- a/src/DotNetOpenAuth/OAuth/Protocol.cs +++ b/src/DotNetOpenAuth/OAuth/Protocol.cs @@ -7,17 +7,36 @@ namespace DotNetOpenAuth.OAuth { using System; using System.Collections.Generic; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text; using DotNetOpenAuth.Messaging; /// <summary> + /// An enumeration of the OAuth protocol versions supported by this library. + /// </summary> + public enum ProtocolVersion { + /// <summary> + /// OAuth 1.0 specification + /// </summary> + V10, + + /// <summary> + /// OAuth 1.0a specification + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "a", Justification = "By design.")] + V10a, + } + + /// <summary> /// Constants used in the OAuth protocol. /// </summary> /// <remarks> /// OAuth Protocol Parameter names and values are case sensitive. Each OAuth Protocol Parameters MUST NOT appear more than once per request, and are REQUIRED unless otherwise noted, /// per OAuth 1.0 section 5. /// </remarks> + [DebuggerDisplay("OAuth {Version}")] internal class Protocol { /// <summary> /// The namespace to use for V1.0 of the protocol. @@ -25,63 +44,105 @@ namespace DotNetOpenAuth.OAuth { internal const string DataContractNamespaceV10 = "http://oauth.net/core/1.0/"; /// <summary> + /// The prefix used for all key names in the protocol. + /// </summary> + internal const string ParameterPrefix = "oauth_"; + + /// <summary> + /// The string representation of a <see cref="Version"/> instance to be used to represent OAuth 1.0a. + /// </summary> + internal const string V10aVersion = "1.0.1"; + + /// <summary> + /// The scheme to use in Authorization header message requests. + /// </summary> + internal const string AuthorizationHeaderScheme = "OAuth"; + + /// <summary> /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0 of the protocol. /// </summary> internal static readonly Protocol V10 = new Protocol { dataContractNamespace = DataContractNamespaceV10, + Version = new Version(1, 0), + ProtocolVersion = ProtocolVersion.V10, }; /// <summary> + /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0a of the protocol. + /// </summary> + internal static readonly Protocol V10a = new Protocol { + dataContractNamespace = DataContractNamespaceV10, + Version = new Version(V10aVersion), + ProtocolVersion = ProtocolVersion.V10a, + }; + + /// <summary> + /// A list of all supported OAuth versions, in order starting from newest version. + /// </summary> + internal static readonly List<Protocol> AllVersions = new List<Protocol>() { V10a, V10 }; + + /// <summary> + /// The default (or most recent) supported version of the OpenID protocol. + /// </summary> + internal static readonly Protocol Default = AllVersions[0]; + + /// <summary> /// The namespace to use for this version of the protocol. /// </summary> private string dataContractNamespace; /// <summary> - /// The prefix used for all key names in the protocol. + /// Initializes a new instance of the <see cref="Protocol"/> class. /// </summary> - private string parameterPrefix = "oauth_"; + internal Protocol() { + this.PublishedVersion = "1.0"; + } /// <summary> - /// The scheme to use in Authorization header message requests. + /// Gets the version used to represent OAuth 1.0a. /// </summary> - private string authorizationHeaderScheme = "OAuth"; + internal Version Version { get; private set; } /// <summary> - /// Gets the default <see cref="Protocol"/> instance. + /// Gets the version to declare on the wire. /// </summary> - internal static Protocol Default { get { return V10; } } + internal string PublishedVersion { get; private set; } /// <summary> - /// Gets the namespace to use for this version of the protocol. + /// Gets the <see cref="ProtocolVersion"/> enum value for the <see cref="Protocol"/> instance. /// </summary> - internal string DataContractNamespace { - get { return this.dataContractNamespace; } - } + internal ProtocolVersion ProtocolVersion { get; private set; } /// <summary> - /// Gets the prefix used for all key names in the protocol. + /// Gets the namespace to use for this version of the protocol. /// </summary> - internal string ParameterPrefix { - get { return this.parameterPrefix; } + internal string DataContractNamespace { + get { return this.dataContractNamespace; } } /// <summary> - /// Gets the scheme to use in Authorization header message requests. + /// Gets the OAuth Protocol instance to use for the given version. /// </summary> - internal string AuthorizationHeaderScheme { - get { return this.authorizationHeaderScheme; } + /// <param name="version">The OAuth version to get.</param> + /// <returns>A matching <see cref="Protocol"/> instance.</returns> + public static Protocol Lookup(ProtocolVersion version) { + switch (version) { + case ProtocolVersion.V10: return Protocol.V10; + case ProtocolVersion.V10a: return Protocol.V10a; + default: throw new ArgumentOutOfRangeException("version"); + } } /// <summary> - /// Gets an instance of <see cref="Protocol"/> given a <see cref="Version"/>. + /// Gets the OAuth Protocol instance to use for the given version. /// </summary> - /// <param name="version">The version of the protocol that is desired.</param> - /// <returns>The <see cref="Protocol"/> instance representing the requested version.</returns> + /// <param name="version">The OAuth version to get.</param> + /// <returns>A matching <see cref="Protocol"/> instance.</returns> internal static Protocol Lookup(Version version) { - switch (version.Major) { - case 1: return Protocol.V10; - default: throw new ArgumentOutOfRangeException("version"); - } + ErrorUtilities.VerifyArgumentNotNull(version, "version"); + Protocol protocol = AllVersions.FirstOrDefault(p => p.Version == version); + ErrorUtilities.VerifyArgumentInRange(protocol != null, "version"); + return protocol; } } } diff --git a/src/DotNetOpenAuth/OAuth/SecuritySettings.cs b/src/DotNetOpenAuth/OAuth/SecuritySettings.cs new file mode 100644 index 0000000..3329f09 --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/SecuritySettings.cs @@ -0,0 +1,18 @@ +//----------------------------------------------------------------------- +// <copyright file="SecuritySettings.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth { + /// <summary> + /// Security settings that may be applicable to both consumers and service providers. + /// </summary> + public class SecuritySettings { + /// <summary> + /// Initializes a new instance of the <see cref="SecuritySettings"/> class. + /// </summary> + protected SecuritySettings() { + } + } +} diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index 886e5b3..95eee32 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -6,11 +6,15 @@ namespace DotNetOpenAuth.OAuth { using System; + using System.Collections.Generic; + using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using System.Globalization; + using System.Security.Principal; using System.ServiceModel.Channels; using System.Web; + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.OAuth.ChannelElements; @@ -33,6 +37,17 @@ namespace DotNetOpenAuth.OAuth { /// </remarks> public class ServiceProvider : IDisposable { /// <summary> + /// The name of the key to use in the HttpApplication cache to store the + /// instance of <see cref="NonceMemoryStore"/> to use. + /// </summary> + private const string ApplicationStoreKey = "DotNetOpenAuth.OAuth.ServiceProvider.HttpApplicationStore"; + + /// <summary> + /// The length of the verifier code (in raw bytes before base64 encoding) to generate. + /// </summary> + private const int VerifierCodeLength = 5; + + /// <summary> /// The field behind the <see cref="OAuthChannel"/> property. /// </summary> private OAuthChannel channel; @@ -52,16 +67,65 @@ namespace DotNetOpenAuth.OAuth { /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param> - public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) { + public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, OAuthServiceProviderMessageFactory messageTypeProvider) + : this(serviceDescription, tokenManager, DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.ApplicationStore.CreateInstance(HttpApplicationStore), messageTypeProvider) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProvider"/> class. + /// </summary> + /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> + /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> + /// <param name="nonceStore">The nonce store.</param> + public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore) + : this(serviceDescription, tokenManager, nonceStore, new OAuthServiceProviderMessageFactory(tokenManager)) { + } + + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProvider"/> class. + /// </summary> + /// <param name="serviceDescription">The endpoints and behavior on the Service Provider.</param> + /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param> + /// <param name="nonceStore">The nonce store.</param> + /// <param name="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param> + public ServiceProvider(ServiceProviderDescription serviceDescription, IServiceProviderTokenManager tokenManager, INonceStore nonceStore, OAuthServiceProviderMessageFactory messageTypeProvider) { ErrorUtilities.VerifyArgumentNotNull(serviceDescription, "serviceDescription"); ErrorUtilities.VerifyArgumentNotNull(tokenManager, "tokenManager"); + ErrorUtilities.VerifyArgumentNotNull(nonceStore, "nonceStore"); ErrorUtilities.VerifyArgumentNotNull(messageTypeProvider, "messageTypeProvider"); var signingElement = serviceDescription.CreateTamperProtectionElement(); - INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); this.ServiceDescription = serviceDescription; - this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager, messageTypeProvider); + this.OAuthChannel = new OAuthChannel(signingElement, nonceStore, tokenManager, messageTypeProvider); this.TokenGenerator = new StandardTokenGenerator(); + this.SecuritySettings = DotNetOpenAuthSection.Configuration.OAuth.ServiceProvider.SecuritySettings.CreateSecuritySettings(); + } + + /// <summary> + /// Gets the standard state storage mechanism that uses ASP.NET's + /// HttpApplication state dictionary to store associations and nonces. + /// </summary> + [EditorBrowsable(EditorBrowsableState.Advanced)] + public static INonceStore HttpApplicationStore { + get { + Contract.Ensures(Contract.Result<INonceStore>() != null); + + HttpContext context = HttpContext.Current; + ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(INonceStore).Name); + var store = (INonceStore)context.Application[ApplicationStoreKey]; + if (store == null) { + context.Application.Lock(); + try { + if ((store = (INonceStore)context.Application[ApplicationStoreKey]) == null) { + context.Application[ApplicationStoreKey] = store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge); + } + } finally { + context.Application.UnLock(); + } + } + + return store; + } } /// <summary> @@ -77,8 +141,8 @@ namespace DotNetOpenAuth.OAuth { /// <summary> /// Gets the persistence store for tokens and secrets. /// </summary> - public ITokenManager TokenManager { - get { return this.OAuthChannel.TokenManager; } + public IServiceProviderTokenManager TokenManager { + get { return (IServiceProviderTokenManager)this.OAuthChannel.TokenManager; } } /// <summary> @@ -89,6 +153,11 @@ namespace DotNetOpenAuth.OAuth { } /// <summary> + /// Gets the security settings for this service provider. + /// </summary> + public ServiceProviderSecuritySettings SecuritySettings { get; private set; } + + /// <summary> /// Gets or sets the channel to use for sending/receiving messages. /// </summary> internal OAuthChannel OAuthChannel { @@ -97,15 +166,38 @@ namespace DotNetOpenAuth.OAuth { } set { - if (this.channel != null) { - this.channel.Sending -= this.OAuthChannel_Sending; - } - + Contract.Requires(value != null); + ErrorUtilities.VerifyArgumentNotNull(value, "value"); this.channel = value; + } + } - if (this.channel != null) { - this.channel.Sending += this.OAuthChannel_Sending; - } + /// <summary> + /// Creates a cryptographically strong random verification code. + /// </summary> + /// <param name="format">The desired format of the verification code.</param> + /// <param name="length">The length of the code. + /// When <paramref name="format"/> is <see cref="VerificationCodeFormat.IncludedInCallback"/>, + /// this is the length of the original byte array before base64 encoding rather than the actual + /// length of the final string.</param> + /// <returns>The verification code.</returns> + public static string CreateVerificationCode(VerificationCodeFormat format, int length) { + Contract.Requires(length >= 0); + ErrorUtilities.VerifyArgumentInRange(length >= 0, "length"); + + switch (format) { + case VerificationCodeFormat.IncludedInCallback: + return MessagingUtilities.GetCryptoRandomDataAsBase64(length); + case VerificationCodeFormat.AlphaNumericNoLookAlikes: + return MessagingUtilities.GetRandomString(length, MessagingUtilities.AlphaNumericNoLookAlikes); + case VerificationCodeFormat.AlphaUpper: + return MessagingUtilities.GetRandomString(length, MessagingUtilities.UppercaseLetters); + case VerificationCodeFormat.AlphaLower: + return MessagingUtilities.GetRandomString(length, MessagingUtilities.LowercaseLetters); + case VerificationCodeFormat.Numeric: + return MessagingUtilities.GetRandomString(length, MessagingUtilities.Digits); + default: + throw new ArgumentOutOfRangeException("format"); } } @@ -149,7 +241,9 @@ namespace DotNetOpenAuth.OAuth { /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception> public UnauthorizedTokenRequest ReadTokenRequest(HttpRequestInfo request) { UnauthorizedTokenRequest message; - this.Channel.TryReadFromRequest(request, out message); + if (this.Channel.TryReadFromRequest(request, out message)) { + ErrorUtilities.VerifyProtocol(message.Version >= Protocol.Lookup(this.SecuritySettings.MinimumRequiredOAuthVersion).Version, OAuthStrings.MinimumConsumerVersionRequirementNotMet, this.SecuritySettings.MinimumRequiredOAuthVersion, message.Version); + } return message; } @@ -160,9 +254,7 @@ namespace DotNetOpenAuth.OAuth { /// <param name="request">The token request message the Consumer sent that the Service Provider is now responding to.</param> /// <returns>The response message to send using the <see cref="Channel"/>, after optionally adding extra data to it.</returns> public UnauthorizedTokenResponse PrepareUnauthorizedTokenMessage(UnauthorizedTokenRequest request) { - if (request == null) { - throw new ArgumentNullException("request"); - } + ErrorUtilities.VerifyArgumentNotNull(request, "request"); string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey); string secret = this.TokenGenerator.GenerateSecret(); @@ -278,11 +370,27 @@ namespace DotNetOpenAuth.OAuth { Contract.Requires(request != null); ErrorUtilities.VerifyArgumentNotNull(request, "request"); - if (request.Callback != null) { - return this.PrepareAuthorizationResponse(request, request.Callback); + // It is very important for us to ignore the oauth_callback argument in the + // UserAuthorizationRequest if the Consumer is a 1.0a consumer or else we + // open up a security exploit. + IServiceProviderRequestToken token = this.TokenManager.GetRequestToken(request.RequestToken); + Uri callback; + if (request.Version >= Protocol.V10a.Version) { + // In OAuth 1.0a, we'll prefer the token-specific callback to the pre-registered one. + if (token.Callback != null) { + callback = token.Callback; + } else { + IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey); + callback = consumer.Callback; + } } else { - return null; + // In OAuth 1.0, we'll prefer the pre-registered callback over the token-specific one + // since 1.0 has a security weakness for user-modified callback URIs. + IConsumerDescription consumer = this.TokenManager.GetConsumer(token.ConsumerKey); + callback = consumer.Callback ?? request.Callback; } + + return callback != null ? this.PrepareAuthorizationResponse(request, callback) : null; } /// <summary> @@ -291,7 +399,7 @@ namespace DotNetOpenAuth.OAuth { /// </summary> /// <param name="request">The Consumer's original authorization request.</param> /// <param name="callback">The callback URI the consumer has previously registered - /// with this service provider.</param> + /// with this service provider or that came in the <see cref="UnauthorizedTokenRequest"/>.</param> /// <returns> /// The message to send to the Consumer using <see cref="Channel"/>. /// </returns> @@ -302,9 +410,14 @@ namespace DotNetOpenAuth.OAuth { ErrorUtilities.VerifyArgumentNotNull(request, "request"); ErrorUtilities.VerifyArgumentNotNull(callback, "callback"); - var authorization = new UserAuthorizationResponse(request.Callback) { + var authorization = new UserAuthorizationResponse(callback, request.Version) { RequestToken = request.RequestToken, }; + + if (authorization.Version >= Protocol.V10a.Version) { + authorization.VerificationCode = CreateVerificationCode(VerificationCodeFormat.IncludedInCallback, VerifierCodeLength); + } + return authorization; } @@ -338,17 +451,10 @@ namespace DotNetOpenAuth.OAuth { /// <param name="request">The Consumer's message requesting an access token.</param> /// <returns>The HTTP response to actually send to the Consumer.</returns> public AuthorizedTokenResponse PrepareAccessTokenMessage(AuthorizedTokenRequest request) { - if (request == null) { - throw new ArgumentNullException("request"); - } + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); - if (!this.TokenManager.IsRequestTokenAuthorized(request.RequestToken)) { - throw new ProtocolException( - string.Format( - CultureInfo.CurrentCulture, - OAuthStrings.AccessTokenNotAuthorized, - request.RequestToken)); - } + ErrorUtilities.VerifyProtocol(this.TokenManager.IsRequestTokenAuthorized(request.RequestToken), OAuthStrings.AccessTokenNotAuthorized, request.RequestToken); string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey); string tokenSecret = this.TokenGenerator.GenerateSecret(); @@ -419,6 +525,19 @@ namespace DotNetOpenAuth.OAuth { return accessMessage; } + /// <summary> + /// Creates a security principal that may be used. + /// </summary> + /// <param name="request">The request.</param> + /// <returns>The <see cref="IPrincipal"/> instance that can be used for access control of resources.</returns> + public OAuthPrincipal CreatePrincipal(AccessProtectedResourceRequest request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + IServiceProviderAccessToken accessToken = this.TokenManager.GetAccessToken(request.AccessToken); + return new OAuthPrincipal(accessToken); + } + #region IDisposable Members /// <summary> @@ -440,18 +559,5 @@ namespace DotNetOpenAuth.OAuth { } #endregion - - /// <summary> - /// Hooks the channel in order to perform some operations on some outgoing messages. - /// </summary> - /// <param name="sender">The source of the event.</param> - /// <param name="e">The <see cref="DotNetOpenAuth.Messaging.ChannelEventArgs"/> instance containing the event data.</param> - private void OAuthChannel_Sending(object sender, ChannelEventArgs e) { - // Hook to store the token and secret on its way down to the Consumer. - var grantRequestTokenResponse = e.Message as UnauthorizedTokenResponse; - if (grantRequestTokenResponse != null) { - this.TokenManager.StoreNewRequestToken(grantRequestTokenResponse.RequestMessage, grantRequestTokenResponse); - } - } } } diff --git a/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs b/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs index 4636829..9014762 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProviderDescription.cs @@ -26,9 +26,15 @@ namespace DotNetOpenAuth.OAuth { /// Initializes a new instance of the <see cref="ServiceProviderDescription"/> class. /// </summary> public ServiceProviderDescription() { + this.ProtocolVersion = Protocol.Default.ProtocolVersion; } /// <summary> + /// Gets or sets the OAuth version supported by the Service Provider. + /// </summary> + public ProtocolVersion ProtocolVersion { get; set; } + + /// <summary> /// Gets or sets the URL used to obtain an unauthorized Request Token, /// described in Section 6.1 (Obtaining an Unauthorized Request Token). /// </summary> @@ -43,7 +49,7 @@ namespace DotNetOpenAuth.OAuth { } set { - if (value != null && UriUtil.QueryStringContainPrefixedParameters(value.Location, OAuth.Protocol.V10.ParameterPrefix)) { + if (value != null && UriUtil.QueryStringContainPrefixedParameters(value.Location, OAuth.Protocol.ParameterPrefix)) { throw new ArgumentException(OAuthStrings.RequestUrlMustNotHaveOAuthParameters); } @@ -77,6 +83,13 @@ namespace DotNetOpenAuth.OAuth { public ITamperProtectionChannelBindingElement[] TamperProtectionElements { get; set; } /// <summary> + /// Gets the OAuth version supported by the Service Provider. + /// </summary> + internal Version Version { + get { return Protocol.Lookup(this.ProtocolVersion).Version; } + } + + /// <summary> /// Creates a signing element that includes all the signing elements this service provider supports. /// </summary> /// <returns>The created signing element.</returns> diff --git a/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs b/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs new file mode 100644 index 0000000..b8e12fd --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/ServiceProviderSecuritySettings.cs @@ -0,0 +1,25 @@ +//----------------------------------------------------------------------- +// <copyright file="ServiceProviderSecuritySettings.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth { + using System; + + /// <summary> + /// Security settings that are applicable to service providers. + /// </summary> + public class ServiceProviderSecuritySettings : SecuritySettings { + /// <summary> + /// Initializes a new instance of the <see cref="ServiceProviderSecuritySettings"/> class. + /// </summary> + internal ServiceProviderSecuritySettings() { + } + + /// <summary> + /// Gets or sets the minimum required version of OAuth that must be implemented by a Consumer. + /// </summary> + public ProtocolVersion MinimumRequiredOAuthVersion { get; set; } + } +} diff --git a/src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs b/src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs new file mode 100644 index 0000000..3afd44e --- /dev/null +++ b/src/DotNetOpenAuth/OAuth/VerificationCodeFormat.cs @@ -0,0 +1,62 @@ +//----------------------------------------------------------------------- +// <copyright file="VerificationCodeFormat.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OAuth { + using System.Diagnostics.CodeAnalysis; + + /// <summary> + /// The different formats a user authorization verifier code can take + /// in order to be as secure as possible while being compatible with + /// the type of OAuth Consumer requesting access. + /// </summary> + /// <remarks> + /// Some Consumers may be set-top boxes, video games, mobile devies, etc. + /// with very limited character entry support and no ability to receive + /// a callback URI. OAuth 1.0a requires that these devices operators + /// must manually key in a verifier code, so in these cases it better + /// be possible to do so given the input options on that device. + /// </remarks> + public enum VerificationCodeFormat { + /// <summary> + /// The strongest verification code. + /// The best option for web consumers since a callback is usually an option. + /// </summary> + IncludedInCallback, + + /// <summary> + /// A combination of upper and lowercase letters and numbers may be used, + /// allowing a computer operator to easily read from the screen and key + /// in the verification code. + /// </summary> + /// <remarks> + /// Some letters and numbers will be skipped where they are visually similar + /// enough that they can be difficult to distinguish when displayed with most fonts. + /// </remarks> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Alikes", Justification = "Breaking change of existing API")] + [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "AlphaNumeric", Justification = "Breaking change of existing API")] + AlphaNumericNoLookAlikes, + + /// <summary> + /// Only uppercase letters will be used in the verification code. + /// Verification codes are case-sensitive, so consumers with fixed + /// keyboards with only one character case option may require this option. + /// </summary> + AlphaUpper, + + /// <summary> + /// Only lowercase letters will be used in the verification code. + /// Verification codes are case-sensitive, so consumers with fixed + /// keyboards with only one character case option may require this option. + /// </summary> + AlphaLower, + + /// <summary> + /// Only the numbers 0-9 will be used in the verification code. + /// Must useful for consumers running on mobile phone devices. + /// </summary> + Numeric, + } +} diff --git a/src/DotNetOpenAuth/OAuth/WebConsumer.cs b/src/DotNetOpenAuth/OAuth/WebConsumer.cs index 689a795..d86444d 100644 --- a/src/DotNetOpenAuth/OAuth/WebConsumer.cs +++ b/src/DotNetOpenAuth/OAuth/WebConsumer.cs @@ -42,7 +42,7 @@ namespace DotNetOpenAuth.OAuth { /// Requires HttpContext.Current. /// </remarks> public UserAuthorizationRequest PrepareRequestUserAuthorization() { - Uri callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting.StripQueryArgumentsWithPrefix(Protocol.Default.ParameterPrefix); + Uri callback = this.Channel.GetRequestFromContext().UrlBeforeRewriting.StripQueryArgumentsWithPrefix(Protocol.ParameterPrefix); return this.PrepareRequestUserAuthorization(callback, null, null); } @@ -120,7 +120,8 @@ namespace DotNetOpenAuth.OAuth { } // Prepare a message to exchange the request token for an access token. - var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint) { + // We are careful to use a v1.0 message version so that the oauth_verifier is not required. + var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint, Protocol.V10.Version) { RequestToken = positiveAuthorization.RequestToken, ConsumerKey = this.ConsumerKey, }; @@ -147,7 +148,8 @@ namespace DotNetOpenAuth.OAuth { UserAuthorizationResponse authorizationMessage; if (this.Channel.TryReadFromRequest<UserAuthorizationResponse>(request, out authorizationMessage)) { string requestToken = authorizationMessage.RequestToken; - return this.ProcessUserAuthorization(requestToken); + string verifier = authorizationMessage.VerificationCode; + return this.ProcessUserAuthorization(requestToken, verifier); } else { return null; } diff --git a/src/DotNetOpenAuth/OpenId/Association.cs b/src/DotNetOpenAuth/OpenId/Association.cs index eb7c880..5aeaaee 100644 --- a/src/DotNetOpenAuth/OpenId/Association.cs +++ b/src/DotNetOpenAuth/OpenId/Association.cs @@ -30,7 +30,7 @@ namespace DotNetOpenAuth.OpenId { /// <param name="handle">The handle.</param> /// <param name="secret">The secret.</param> /// <param name="totalLifeLength">How long the association will be useful.</param> - /// <param name="issued">When this association was originally issued by the Provider.</param> + /// <param name="issued">The UTC time of when this association was originally issued by the Provider.</param> protected Association(string handle, byte[] secret, TimeSpan totalLifeLength, DateTime issued) { ErrorUtilities.VerifyNonZeroLength(handle, "handle"); ErrorUtilities.VerifyArgumentNotNull(secret, "secret"); @@ -47,7 +47,7 @@ namespace DotNetOpenAuth.OpenId { public string Handle { get; private set; } /// <summary> - /// Gets the time when this <see cref="Association"/> will expire. + /// Gets the UTC time when this <see cref="Association"/> will expire. /// </summary> public DateTime Expires { get { return this.Issued + this.TotalLifeLength; } @@ -76,7 +76,7 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Gets or sets the time that this <see cref="Association"/> was first created. + /// Gets or sets the UTC time that this <see cref="Association"/> was first created. /// </summary> internal DateTime Issued { get; set; } @@ -130,8 +130,8 @@ namespace DotNetOpenAuth.OpenId { /// <param name="handle"> /// The <see cref="Handle"/> property of the previous <see cref="Association"/> instance. /// </param> - /// <param name="expires"> - /// The value of the <see cref="Expires"/> property of the previous <see cref="Association"/> instance. + /// <param name="expiresUtc"> + /// The UTC value of the <see cref="Expires"/> property of the previous <see cref="Association"/> instance. /// </param> /// <param name="privateData"> /// The byte array returned by a call to <see cref="SerializePrivateData"/> on the previous @@ -142,15 +142,15 @@ namespace DotNetOpenAuth.OpenId { /// from a custom association store's /// <see cref="IAssociationStore<TKey>.GetAssociation(TKey, SecuritySettings)"/> method. /// </returns> - public static Association Deserialize(string handle, DateTime expires, byte[] privateData) { + public static Association Deserialize(string handle, DateTime expiresUtc, byte[] privateData) { if (string.IsNullOrEmpty(handle)) { throw new ArgumentNullException("handle"); } if (privateData == null) { throw new ArgumentNullException("privateData"); } - expires = expires.ToUniversalTime(); - TimeSpan remainingLifeLength = expires - DateTime.UtcNow; + expiresUtc = expiresUtc.ToUniversalTimeSafe(); + TimeSpan remainingLifeLength = expiresUtc - DateTime.UtcNow; byte[] secret = privateData; // the whole of privateData is the secret key for now. // We figure out what derived type to instantiate based on the length of the secret. try { diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs b/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs new file mode 100644 index 0000000..d7dca9a --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Behaviors/AXFetchAsSregTransform.cs @@ -0,0 +1,141 @@ +//----------------------------------------------------------------------- +// <copyright file="AXFetchAsSregTransform.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Behaviors { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Linq; + using System.Text; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// An Attribute Exchange and Simple Registration filter to make all incoming attribute + /// requests look like Simple Registration requests, and to convert the response + /// to the originally requested extension and format. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public class AXFetchAsSregTransform : IRelyingPartyBehavior, IProviderBehavior { + /// <summary> + /// Initializes static members of the <see cref="AXFetchAsSregTransform"/> class. + /// </summary> + static AXFetchAsSregTransform() { + AXFormats = AXAttributeFormats.Common; + } + + /// <summary> + /// Initializes a new instance of the <see cref="AXFetchAsSregTransform"/> class. + /// </summary> + public AXFetchAsSregTransform() { + } + + /// <summary> + /// Gets or sets the AX attribute type URI formats this transform is willing to work with. + /// </summary> + public static AXAttributeFormats AXFormats { get; set; } + + #region IRelyingPartyBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { + } + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + /// <remarks> + /// Implementations should be prepared to be called multiple times on the same outgoing message + /// without malfunctioning. + /// </remarks> + void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { + request.SpreadSregToAX(AXFormats); + } + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { + if (assertion.GetExtension<ClaimsResponse>() == null) { + ClaimsResponse sreg = assertion.UnifyExtensionsAsSreg(true); + ((PositiveAnonymousResponse)assertion).Response.Extensions.Add(sreg); + } + } + + #endregion + + #region IProviderBehavior Members + + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { + // Nothing to do here. + } + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool IProviderBehavior.OnIncomingRequest(IRequest request) { + var extensionRequest = request as Provider.HostProcessedRequest; + if (extensionRequest != null) { + if (extensionRequest.GetExtension<ClaimsRequest>() == null) { + ClaimsRequest sreg = extensionRequest.UnifyExtensionsAsSreg(); + if (sreg != null) { + ((IProtocolMessageWithExtensions)extensionRequest.RequestMessage).Extensions.Add(sreg); + } + } + } + + return false; + } + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool IProviderBehavior.OnOutgoingResponse(Provider.IAuthenticationRequest request) { + request.ConvertSregToMatchRequest(); + return false; + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs index 4166f19..937ecaf 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.Designer.cs @@ -61,6 +61,42 @@ namespace DotNetOpenAuth.OpenId.Behaviors { } /// <summary> + /// Looks up a localized string similar to The PAPE request has an incomplete set of authentication policies.. + /// </summary> + internal static string PapeRequestMissingRequiredPolicies { + get { + return ResourceManager.GetString("PapeRequestMissingRequiredPolicies", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to A PAPE response is missing or is missing required policies.. + /// </summary> + internal static string PapeResponseOrRequiredPoliciesMissing { + get { + return ResourceManager.GetString("PapeResponseOrRequiredPoliciesMissing", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No personally identifiable information should be included in authentication responses when the PAPE authentication policy http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf is present.. + /// </summary> + internal static string PiiIncludedWithNoPiiPolicy { + get { + return ResourceManager.GetString("PiiIncludedWithNoPiiPolicy", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to No personally identifiable information should be requested when the http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf PAPE policy is present.. + /// </summary> + internal static string PiiRequestedWithNoPiiPolicy { + get { + return ResourceManager.GetString("PiiRequestedWithNoPiiPolicy", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to No PPID provider has been configured.. /// </summary> internal static string PpidProviderNotGiven { @@ -68,5 +104,23 @@ namespace DotNetOpenAuth.OpenId.Behaviors { return ResourceManager.GetString("PpidProviderNotGiven", resourceCulture); } } + + /// <summary> + /// Looks up a localized string similar to Discovery on the Realm URL MUST be performed before sending a positive assertion.. + /// </summary> + internal static string RealmDiscoveryNotPerformed { + get { + return ResourceManager.GetString("RealmDiscoveryNotPerformed", resourceCulture); + } + } + + /// <summary> + /// Looks up a localized string similar to The Realm in an authentication request must be an HTTPS URL.. + /// </summary> + internal static string RealmMustBeHttps { + get { + return ResourceManager.GetString("RealmMustBeHttps", resourceCulture); + } + } } } diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx index 23e3e73..a8bf2d6 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx +++ b/src/DotNetOpenAuth/OpenId/Behaviors/BehaviorStrings.resx @@ -117,7 +117,25 @@ <resheader name="writer"> <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value> </resheader> + <data name="PapeRequestMissingRequiredPolicies" xml:space="preserve"> + <value>The PAPE request has an incomplete set of authentication policies.</value> + </data> + <data name="PapeResponseOrRequiredPoliciesMissing" xml:space="preserve"> + <value>A PAPE response is missing or is missing required policies.</value> + </data> + <data name="PiiIncludedWithNoPiiPolicy" xml:space="preserve"> + <value>No personally identifiable information should be included in authentication responses when the PAPE authentication policy http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf is present.</value> + </data> + <data name="PiiRequestedWithNoPiiPolicy" xml:space="preserve"> + <value>No personally identifiable information should be requested when the http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf PAPE policy is present.</value> + </data> <data name="PpidProviderNotGiven" xml:space="preserve"> <value>No PPID provider has been configured.</value> </data> + <data name="RealmDiscoveryNotPerformed" xml:space="preserve"> + <value>Discovery on the Realm URL MUST be performed before sending a positive assertion.</value> + </data> + <data name="RealmMustBeHttps" xml:space="preserve"> + <value>The Realm in an authentication request must be an HTTPS URL.</value> + </data> </root>
\ No newline at end of file diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs b/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs new file mode 100644 index 0000000..8f3b78f --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Behaviors/GsaIcamProfile.cs @@ -0,0 +1,291 @@ +//----------------------------------------------------------------------- +// <copyright file="GsaIcamProfile.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Behaviors { + using System; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Provider; + using DotNetOpenAuth.OpenId.RelyingParty; + + /// <summary> + /// Implements the Identity, Credential, & Access Management (ICAM) OpenID 2.0 Profile + /// for the General Services Administration (GSA). + /// </summary> + /// <remarks> + /// <para>Relying parties that include this profile are always held to the terms required by the profile, + /// but Providers are only affected by the special behaviors of the profile when the RP specifically + /// indicates that they want to use this profile. </para> + /// </remarks> + [Serializable] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Icam", Justification = "Acronym")] + public sealed class GsaIcamProfile : IRelyingPartyBehavior, IProviderBehavior { + /// <summary> + /// The maximum time a shared association can live. + /// </summary> + private static readonly TimeSpan MaximumAssociationLifetime = TimeSpan.FromSeconds(86400); + + /// <summary> + /// Initializes a new instance of the <see cref="GsaIcamProfile"/> class. + /// </summary> + public GsaIcamProfile() { + if (DisableSslRequirement) { + Logger.OpenId.Warn("GSA level 1 behavior has its RequireSsl requirement disabled."); + } + } + + /// <summary> + /// Gets or sets the provider for generating PPID identifiers. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Acronym")] + public static IDirectedIdentityIdentifierProvider PpidIdentifierProvider { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether PII is allowed to be requested or received via OpenID. + /// </summary> + /// <value>The default value is <c>false</c>.</value> + public static bool AllowPersonallyIdentifiableInformation { get; set; } + + /// <summary> + /// Gets or sets a value indicating whether to ignore the SSL requirement (for testing purposes only). + /// </summary> + public static bool DisableSslRequirement { get; set; } + + #region IRelyingPartyBehavior Members + + /// <summary> + /// Applies a well known set of security requirements. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IRelyingPartyBehavior.ApplySecuritySettings(RelyingPartySecuritySettings securitySettings) { + ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + + if (securitySettings.MaximumHashBitLength < 256) { + securitySettings.MaximumHashBitLength = 256; + } + + securitySettings.RequireSsl = !DisableSslRequirement; + securitySettings.RequireDirectedIdentity = true; + securitySettings.RequireAssociation = true; + securitySettings.RejectDelegatingIdentifiers = true; + securitySettings.IgnoreUnsignedExtensions = true; + securitySettings.MinimumRequiredOpenIdVersion = ProtocolVersion.V20; + } + + /// <summary> + /// Called when an authentication request is about to be sent. + /// </summary> + /// <param name="request">The request.</param> + void IRelyingPartyBehavior.OnOutgoingAuthenticationRequest(RelyingParty.IAuthenticationRequest request) { + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + RelyingParty.AuthenticationRequest requestInternal = (RelyingParty.AuthenticationRequest)request; + ErrorUtilities.VerifyProtocol(string.Equals(request.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); + + var pape = requestInternal.AppliedExtensions.OfType<PolicyRequest>().SingleOrDefault(); + if (pape == null) { + request.AddExtension(pape = new PolicyRequest()); + } + + if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); + } + + if (!pape.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1); + } + + if (!AllowPersonallyIdentifiableInformation && !pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + pape.PreferredPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation); + } + + if (pape.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + ErrorUtilities.VerifyProtocol( + (!requestInternal.AppliedExtensions.OfType<ClaimsRequest>().Any() && + !requestInternal.AppliedExtensions.OfType<FetchRequest>().Any()), + BehaviorStrings.PiiIncludedWithNoPiiPolicy); + } + } + + /// <summary> + /// Called when an incoming positive assertion is received. + /// </summary> + /// <param name="assertion">The positive assertion.</param> + void IRelyingPartyBehavior.OnIncomingPositiveAssertion(IAuthenticationResponse assertion) { + ErrorUtilities.VerifyArgumentNotNull(assertion, "assertion"); + + PolicyResponse pape = assertion.GetExtension<PolicyResponse>(); + ErrorUtilities.VerifyProtocol( + pape != null && + pape.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1) && + pape.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier), + BehaviorStrings.PapeResponseOrRequiredPoliciesMissing); + + ErrorUtilities.VerifyProtocol(AllowPersonallyIdentifiableInformation || pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation), BehaviorStrings.PapeResponseOrRequiredPoliciesMissing); + + if (pape.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + ErrorUtilities.VerifyProtocol( + assertion.GetExtension<ClaimsResponse>() == null && + assertion.GetExtension<FetchResponse>() == null, + BehaviorStrings.PiiIncludedWithNoPiiPolicy); + } + } + + #endregion + + #region IProviderBehavior Members + + /// <summary> + /// Adapts the default security settings to the requirements of this behavior. + /// </summary> + /// <param name="securitySettings">The original security settings.</param> + void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { + if (securitySettings.MaximumHashBitLength < 256) { + securitySettings.MaximumHashBitLength = 256; + } + + SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA256, MaximumAssociationLifetime, securitySettings); + SetMaximumAssociationLifetimeToNotExceed(Protocol.Default.Args.SignatureAlgorithm.HMAC_SHA1, MaximumAssociationLifetime, securitySettings); + } + + /// <summary> + /// Called when a request is received by the Provider. + /// </summary> + /// <param name="request">The incoming request.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + /// <remarks> + /// Implementations may set a new value to <see cref="IRequest.SecuritySettings"/> but + /// should not change the properties on the instance of <see cref="ProviderSecuritySettings"/> + /// itself as that instance may be shared across many requests. + /// </remarks> + bool IProviderBehavior.OnIncomingRequest(IRequest request) { + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + var hostProcessedRequest = request as IHostProcessedRequest; + if (hostProcessedRequest != null) { + // Only apply our special policies if the RP requested it. + var papeRequest = request.GetExtension<PolicyRequest>(); + if (papeRequest != null) { + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + // Whenever we see this GSA policy requested, we MUST also see the PPID policy requested. + ErrorUtilities.VerifyProtocol(papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier), BehaviorStrings.PapeRequestMissingRequiredPolicies); + ErrorUtilities.VerifyProtocol(string.Equals(hostProcessedRequest.Realm.Scheme, Uri.UriSchemeHttps, StringComparison.Ordinal) || DisableSslRequirement, BehaviorStrings.RealmMustBeHttps); + + // Apply GSA-specific security to this individual request. + request.SecuritySettings.RequireSsl = !DisableSslRequirement; + return true; + } + } + } + + return false; + } + + /// <summary> + /// Called when the Provider is preparing to send a response to an authentication request. + /// </summary> + /// <param name="request">The request that is configured to generate the outgoing response.</param> + /// <returns> + /// <c>true</c> if this behavior owns this request and wants to stop other behaviors + /// from handling it; <c>false</c> to allow other behaviors to process this request. + /// </returns> + bool IProviderBehavior.OnOutgoingResponse(Provider.IAuthenticationRequest request) { + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + bool result = false; + + // Nothing to do for negative assertions. + if (!request.IsAuthenticated.Value) { + return result; + } + + var requestInternal = (Provider.AuthenticationRequest)request; + var responseMessage = (IProtocolMessageWithExtensions)requestInternal.Response; + + // Only apply our special policies if the RP requested it. + var papeRequest = request.GetExtension<PolicyRequest>(); + if (papeRequest != null) { + var papeResponse = responseMessage.Extensions.OfType<PolicyResponse>().SingleOrDefault(); + if (papeResponse == null) { + request.AddResponseExtension(papeResponse = new PolicyResponse()); + } + + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + result = true; + if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.USGovernmentTrustLevel1)) { + papeResponse.ActualPolicies.Add(AuthenticationPolicies.USGovernmentTrustLevel1); + } + + // The spec requires that the OP perform discovery and if that fails, it must either sternly + // warn the user of a potential threat or just abort the authentication. + // We can't verify that the OP displayed anything to the user at this level, but we can + // at least verify that the OP performed the discovery on the realm and halt things if it didn't. + ErrorUtilities.VerifyHost(requestInternal.HasRealmDiscoveryBeenPerformed, BehaviorStrings.RealmDiscoveryNotPerformed); + } + + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + ErrorUtilities.VerifyProtocol(request.ClaimedIdentifier == request.LocalIdentifier, OpenIdStrings.DelegatingIdentifiersNotAllowed); + + // Mask the user's identity with a PPID. + ErrorUtilities.VerifyHost(PpidIdentifierProvider != null, BehaviorStrings.PpidProviderNotGiven); + Identifier ppidIdentifier = PpidIdentifierProvider.GetIdentifier(request.LocalIdentifier, request.Realm); + requestInternal.ResetClaimedAndLocalIdentifiers(ppidIdentifier); + + // Indicate that the RP is receiving a PPID claimed_id + if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + papeResponse.ActualPolicies.Add(AuthenticationPolicies.PrivatePersonalIdentifier); + } + } + + if (papeRequest.PreferredPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + ErrorUtilities.VerifyProtocol( + !responseMessage.Extensions.OfType<ClaimsResponse>().Any() && + !responseMessage.Extensions.OfType<FetchResponse>().Any(), + BehaviorStrings.PiiIncludedWithNoPiiPolicy); + + // If no PII is given in extensions, and the claimed_id is a PPID, then we can state we issue no PII. + if (papeResponse.ActualPolicies.Contains(AuthenticationPolicies.PrivatePersonalIdentifier)) { + if (!papeResponse.ActualPolicies.Contains(AuthenticationPolicies.NoPersonallyIdentifiableInformation)) { + papeResponse.ActualPolicies.Add(AuthenticationPolicies.NoPersonallyIdentifiableInformation); + } + } + } + } + + return result; + } + + #endregion + + /// <summary> + /// Ensures the maximum association lifetime does not exceed a given limit. + /// </summary> + /// <param name="associationType">Type of the association.</param> + /// <param name="maximumLifetime">The maximum lifetime.</param> + /// <param name="securitySettings">The security settings to adjust.</param> + private static void SetMaximumAssociationLifetimeToNotExceed(string associationType, TimeSpan maximumLifetime, ProviderSecuritySettings securitySettings) { + Contract.Requires(!String.IsNullOrEmpty(associationType)); + Contract.Requires(maximumLifetime.TotalSeconds > 0); + if (!securitySettings.AssociationLifetimes.ContainsKey(associationType) || + securitySettings.AssociationLifetimes[associationType] > maximumLifetime) { + securitySettings.AssociationLifetimes[associationType] = maximumLifetime; + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs b/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs index befc138..f09e886 100644 --- a/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs +++ b/src/DotNetOpenAuth/OpenId/Behaviors/PpidGeneration.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OpenId.Behaviors { using System; + using System.Diagnostics.CodeAnalysis; using System.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy; @@ -23,15 +24,30 @@ namespace DotNetOpenAuth.OpenId.Behaviors { /// <c>Application_Start</c> method in the global.asax.cs file.</para> /// </remarks> [Serializable] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Abbreviation")] public sealed class PpidGeneration : IProviderBehavior { /// <summary> /// Gets or sets the provider for generating PPID identifiers. /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Ppid", Justification = "Abbreviation")] public static IDirectedIdentityIdentifierProvider PpidIdentifierProvider { get; set; } #region IProviderBehavior Members /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void IProviderBehavior.ApplySecuritySettings(ProviderSecuritySettings securitySettings) { + // No special security to apply here. + } + + /// <summary> /// Called when a request is received by the Provider. /// </summary> /// <param name="request">The incoming request.</param> diff --git a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs index 63f7809..fa6bfa4 100644 --- a/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs +++ b/src/DotNetOpenAuth/OpenId/ChannelElements/ExtensionsBindingElement.cs @@ -23,11 +23,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { /// </summary> internal class ExtensionsBindingElement : IChannelBindingElement { /// <summary> - /// The security settings that apply to this binding element. - /// </summary> - private readonly SecuritySettings securitySettings; - - /// <summary> /// The security settings that apply to this relying party, if it is a relying party. /// </summary> private readonly RelyingPartySecuritySettings relyingPartySecuritySettings; @@ -42,7 +37,6 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); this.ExtensionFactory = extensionFactory; - this.securitySettings = securitySettings; this.relyingPartySecuritySettings = securitySettings as RelyingPartySecuritySettings; } @@ -198,18 +192,25 @@ namespace DotNetOpenAuth.OpenId.ChannelElements { // Initialize this particular extension. IOpenIdMessageExtension extension = this.ExtensionFactory.Create(typeUri, extensionData, message, isAtProvider); if (extension != null) { - MessageDictionary extensionDictionary = this.Channel.MessageDescriptions.GetAccessor(extension); - foreach (var pair in extensionData) { - extensionDictionary[pair.Key] = pair.Value; - } + try { + MessageDictionary extensionDictionary = this.Channel.MessageDescriptions.GetAccessor(extension); + foreach (var pair in extensionData) { + extensionDictionary[pair.Key] = pair.Value; + } - // Give extensions that require custom serialization a chance to do their work. - var customSerializingExtension = extension as IMessageWithEvents; - if (customSerializingExtension != null) { - customSerializingExtension.OnReceiving(); + // Give extensions that require custom serialization a chance to do their work. + var customSerializingExtension = extension as IMessageWithEvents; + if (customSerializingExtension != null) { + customSerializingExtension.OnReceiving(); + } + } catch (ProtocolException ex) { + Logger.OpenId.ErrorFormat(OpenIdStrings.BadExtension, extension.GetType(), ex); + extension = null; } - yield return extension; + if (extension != null) { + yield return extension; + } } else { Logger.OpenId.WarnFormat("Extension with type URI '{0}' ignored because it is not a recognized extension.", typeUri); } diff --git a/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs b/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs index a93c824..e4fea46 100644 --- a/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/DiffieHellmanUtilities.cs @@ -21,12 +21,12 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// An array of known Diffie Hellman sessions, sorted by decreasing hash size. /// </summary> - private static DHSha[] diffieHellmanSessionTypes = { + private static DHSha[] diffieHellmanSessionTypes = new List<DHSha> { new DHSha(new SHA512Managed(), protocol => protocol.Args.SessionType.DH_SHA512), new DHSha(new SHA384Managed(), protocol => protocol.Args.SessionType.DH_SHA384), new DHSha(new SHA256Managed(), protocol => protocol.Args.SessionType.DH_SHA256), new DHSha(new SHA1Managed(), protocol => protocol.Args.SessionType.DH_SHA1), - }; + } .ToArray(); /// <summary> /// Finds the hashing algorithm to use given an openid.session_type value. diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs new file mode 100644 index 0000000..decd296 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXAttributeFormats.cs @@ -0,0 +1,45 @@ +//----------------------------------------------------------------------- +// <copyright file="AXAttributeFormats.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { + using System; + + /// <summary> + /// The various Type URI formats an AX attribute may use by various remote parties. + /// </summary> + [Flags] + public enum AXAttributeFormats { + /// <summary> + /// No attribute format. + /// </summary> + None = 0x0, + + /// <summary> + /// AX attributes should use the Type URI format starting with <c>http://axschema.org/</c>. + /// </summary> + AXSchemaOrg = 0x1, + + /// <summary> + /// AX attributes should use the Type URI format starting with <c>http://schema.openid.net/</c>. + /// </summary> + SchemaOpenIdNet = 0x2, + + /// <summary> + /// AX attributes should use the Type URI format starting with <c>http://openid.net/schema/</c>. + /// </summary> + OpenIdNetSchema = 0x4, + + /// <summary> + /// All known schemas. + /// </summary> + All = AXSchemaOrg | SchemaOpenIdNet | OpenIdNetSchema, + + /// <summary> + /// The most common schemas. + /// </summary> + Common = AXSchemaOrg | SchemaOpenIdNet, + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs index 9729333..a3f64ab 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/AttributeExchange/AXUtilities.cs @@ -88,8 +88,8 @@ namespace DotNetOpenAuth.OpenId.Extensions.AttributeExchange { bool countSent = false; string countString; if (fields.TryGetValue("count." + alias, out countString)) { - if (!int.TryParse(countString, out count) || count <= 0) { - Logger.OpenId.ErrorFormat("Failed to parse count.{0} value to a positive integer.", alias); + if (!int.TryParse(countString, out count) || count < 0) { + Logger.OpenId.ErrorFormat("Failed to parse count.{0} value to a non-negative integer.", alias); continue; } countSent = true; diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs new file mode 100644 index 0000000..36358a7 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/ExtensionsInteropHelper.cs @@ -0,0 +1,376 @@ +//----------------------------------------------------------------------- +// <copyright file="ExtensionsInteropHelper.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions { + using System; + using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; + using System.Linq; + using DotNetOpenAuth.Messaging; + using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; + using DotNetOpenAuth.OpenId.Extensions.SimpleRegistration; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A set of methods designed to assist in improving interop across different + /// OpenID implementations and their extensions. + /// </summary> + public static class ExtensionsInteropHelper { + /// <summary> + /// The gender decoder to translate AX genders to Sreg. + /// </summary> + private static GenderEncoder genderEncoder = new GenderEncoder(); + + /// <summary> + /// Adds an Attribute Exchange (AX) extension to the authentication request + /// that asks for the same attributes as the Simple Registration (sreg) extension + /// that is already applied. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <param name="attributeFormats">The attribute formats to use in the AX request.</param> + /// <remarks> + /// <para>If discovery on the user-supplied identifier yields hints regarding which + /// extensions and attribute formats the Provider supports, this method MAY ignore the + /// <paramref name="attributeFormat"/> argument and accomodate the Provider to minimize + /// the size of the request.</para> + /// <para>If the request does not carry an sreg extension, the method logs a warning but + /// otherwise quietly returns doing nothing.</para> + /// </remarks> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public static void SpreadSregToAX(this RelyingParty.IAuthenticationRequest request, AXAttributeFormats attributeFormats) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + var req = (RelyingParty.AuthenticationRequest)request; + var sreg = req.AppliedExtensions.OfType<ClaimsRequest>().SingleOrDefault(); + if (sreg == null) { + Logger.OpenId.Warn("No Simple Registration (ClaimsRequest) extension present in the request to spread to AX."); + return; + } + + if (req.Provider.IsExtensionSupported<ClaimsRequest>()) { + Logger.OpenId.Info("Skipping generation of AX request because the Identifier advertises the Provider supports the Sreg extension."); + return; + } + + var ax = req.AppliedExtensions.OfType<FetchRequest>().SingleOrDefault(); + if (ax == null) { + ax = new FetchRequest(); + req.AddExtension(ax); + } + + // Try to use just one AX Type URI format if we can figure out which type the OP accepts. + AXAttributeFormats detectedFormat; + if (TryDetectOPAttributeFormat(request, out detectedFormat)) { + Logger.OpenId.Info("Detected OP support for AX but not for Sreg. Removing Sreg extension request and using AX instead."); + attributeFormats = detectedFormat; + req.Extensions.Remove(sreg); + } else { + Logger.OpenId.Info("Could not determine whether OP supported Sreg or AX. Using both extensions."); + } + + foreach (AXAttributeFormats format in ForEachFormat(attributeFormats)) { + FetchAttribute(ax, format, WellKnownAttributes.BirthDate.WholeBirthDate, sreg.BirthDate); + FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.Country, sreg.Country); + FetchAttribute(ax, format, WellKnownAttributes.Contact.Email, sreg.Email); + FetchAttribute(ax, format, WellKnownAttributes.Name.FullName, sreg.FullName); + FetchAttribute(ax, format, WellKnownAttributes.Person.Gender, sreg.Gender); + FetchAttribute(ax, format, WellKnownAttributes.Preferences.Language, sreg.Language); + FetchAttribute(ax, format, WellKnownAttributes.Name.Alias, sreg.Nickname); + FetchAttribute(ax, format, WellKnownAttributes.Contact.HomeAddress.PostalCode, sreg.PostalCode); + FetchAttribute(ax, format, WellKnownAttributes.Preferences.TimeZone, sreg.TimeZone); + } + } + + /// <summary> + /// Looks for Simple Registration and Attribute Exchange (all known formats) + /// response extensions and returns them as a Simple Registration extension. + /// </summary> + /// <param name="response">The authentication response.</param> + /// <param name="allowUnsigned">if set to <c>true</c> unsigned extensions will be included in the search.</param> + /// <returns> + /// The Simple Registration response if found, + /// or a fabricated one based on the Attribute Exchange extension if found, + /// or just an empty <see cref="ClaimsResponse"/> if there was no data. + /// Never <c>null</c>.</returns> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Sreg", Justification = "Abbreviation")] + public static ClaimsResponse UnifyExtensionsAsSreg(this RelyingParty.IAuthenticationResponse response, bool allowUnsigned) { + Contract.Requires(response != null); + ErrorUtilities.VerifyArgumentNotNull(response, "response"); + + var resp = (RelyingParty.IAuthenticationResponse)response; + var sreg = allowUnsigned ? resp.GetUntrustedExtension<ClaimsResponse>() : resp.GetExtension<ClaimsResponse>(); + if (sreg != null) { + return sreg; + } + + AXAttributeFormats formats = AXAttributeFormats.All; + sreg = new ClaimsResponse(); + var fetchResponse = allowUnsigned ? resp.GetUntrustedExtension<FetchResponse>() : resp.GetExtension<FetchResponse>(); + if (fetchResponse != null) { + ((IOpenIdMessageExtension)sreg).IsSignedByRemoteParty = fetchResponse.IsSignedByProvider; + sreg.BirthDateRaw = fetchResponse.GetAttributeValue(WellKnownAttributes.BirthDate.WholeBirthDate, formats); + sreg.Country = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.Country, formats); + sreg.PostalCode = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.HomeAddress.PostalCode, formats); + sreg.Email = fetchResponse.GetAttributeValue(WellKnownAttributes.Contact.Email, formats); + sreg.FullName = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.FullName, formats); + sreg.Language = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.Language, formats); + sreg.Nickname = fetchResponse.GetAttributeValue(WellKnownAttributes.Name.Alias, formats); + sreg.TimeZone = fetchResponse.GetAttributeValue(WellKnownAttributes.Preferences.TimeZone, formats); + string gender = fetchResponse.GetAttributeValue(WellKnownAttributes.Person.Gender, formats); + if (gender != null) { + sreg.Gender = (Gender)genderEncoder.Decode(gender); + } + } + + return sreg; + } + + /// <summary> + /// Looks for Simple Registration and Attribute Exchange (all known formats) + /// request extensions and returns them as a Simple Registration extension. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <returns> + /// The Simple Registration request if found, + /// or a fabricated one based on the Attribute Exchange extension if found, + /// or <c>null</c> if no attribute extension request is found.</returns> + internal static ClaimsRequest UnifyExtensionsAsSreg(this Provider.IHostProcessedRequest request) { + Contract.Requires(request != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + + var req = (Provider.AuthenticationRequest)request; + var sreg = req.GetExtension<ClaimsRequest>(); + if (sreg != null) { + return sreg; + } + + var ax = req.GetExtension<FetchRequest>(); + if (ax != null) { + sreg = new ClaimsRequest(); + sreg.Synthesized = true; + ((IProtocolMessageWithExtensions)req.RequestMessage).Extensions.Add(sreg); + sreg.BirthDate = GetDemandLevelFor(ax, WellKnownAttributes.BirthDate.WholeBirthDate); + sreg.Country = GetDemandLevelFor(ax, WellKnownAttributes.Contact.HomeAddress.Country); + sreg.Email = GetDemandLevelFor(ax, WellKnownAttributes.Contact.Email); + sreg.FullName = GetDemandLevelFor(ax, WellKnownAttributes.Name.FullName); + sreg.Gender = GetDemandLevelFor(ax, WellKnownAttributes.Person.Gender); + sreg.Language = GetDemandLevelFor(ax, WellKnownAttributes.Preferences.Language); + sreg.Nickname = GetDemandLevelFor(ax, WellKnownAttributes.Name.Alias); + sreg.PostalCode = GetDemandLevelFor(ax, WellKnownAttributes.Contact.HomeAddress.PostalCode); + sreg.TimeZone = GetDemandLevelFor(ax, WellKnownAttributes.Preferences.TimeZone); + } + + return sreg; + } + + /// <summary> + /// Converts the Simple Registration extension response to whatever format the original + /// attribute request extension came in. + /// </summary> + /// <param name="request">The authentication request with the response extensions already added.</param> + /// <remarks> + /// If the original attribute request came in as AX, the Simple Registration extension is converted + /// to an AX response and then the Simple Registration extension is removed from the response. + /// </remarks> + internal static void ConvertSregToMatchRequest(this Provider.IHostProcessedRequest request) { + var req = (Provider.HostProcessedRequest)request; + var response = (IProtocolMessageWithExtensions)req.Response; + var sregRequest = request.GetExtension<ClaimsRequest>(); + if (sregRequest != null) { + if (sregRequest.Synthesized) { + var axRequest = request.GetExtension<FetchRequest>(); + ErrorUtilities.VerifyInternal(axRequest != null, "How do we have a synthesized Sreg request without an AX request?"); + + var sregResponse = response.Extensions.OfType<ClaimsResponse>().SingleOrDefault(); + if (sregResponse == null) { + // No Sreg response to copy from. + return; + } + + // Remove the sreg response since the RP didn't ask for it. + response.Extensions.Remove(sregResponse); + + AXAttributeFormats format = DetectAXFormat(axRequest.Attributes.Select(att => att.TypeUri)); + if (format == AXAttributeFormats.None) { + // No recognized AX attributes were requested. + return; + } + + var axResponse = response.Extensions.OfType<FetchResponse>().SingleOrDefault(); + if (axResponse == null) { + axResponse = new FetchResponse(); + response.Extensions.Add(axResponse); + } + + AddAXAttributeValue(axResponse, WellKnownAttributes.BirthDate.WholeBirthDate, format, sregResponse.BirthDateRaw); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.HomeAddress.Country, format, sregResponse.Country); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.HomeAddress.PostalCode, format, sregResponse.PostalCode); + AddAXAttributeValue(axResponse, WellKnownAttributes.Contact.Email, format, sregResponse.Email); + AddAXAttributeValue(axResponse, WellKnownAttributes.Name.FullName, format, sregResponse.FullName); + AddAXAttributeValue(axResponse, WellKnownAttributes.Name.Alias, format, sregResponse.Nickname); + AddAXAttributeValue(axResponse, WellKnownAttributes.Preferences.TimeZone, format, sregResponse.TimeZone); + AddAXAttributeValue(axResponse, WellKnownAttributes.Preferences.Language, format, sregResponse.Language); + if (sregResponse.Gender.HasValue) { + AddAXAttributeValue(axResponse, WellKnownAttributes.Person.Gender, format, genderEncoder.Encode(sregResponse.Gender)); + } + } + } + } + + /// <summary> + /// Gets the attribute value if available. + /// </summary> + /// <param name="fetchResponse">The AX fetch response extension to look for the attribute value.</param> + /// <param name="typeUri">The type URI of the attribute, using the axschema.org format of <see cref="WellKnownAttributes"/>.</param> + /// <param name="formats">The AX type URI formats to search.</param> + /// <returns> + /// The first value of the attribute, if available. + /// </returns> + internal static string GetAttributeValue(this FetchResponse fetchResponse, string typeUri, AXAttributeFormats formats) { + return ForEachFormat(formats).Select(format => fetchResponse.GetAttributeValue(TransformAXFormat(typeUri, format))).FirstOrDefault(s => s != null); + } + + /// <summary> + /// Adds the AX attribute value to the response if it is non-empty. + /// </summary> + /// <param name="ax">The AX Fetch response to add the attribute value to.</param> + /// <param name="typeUri">The attribute type URI in axschema.org format.</param> + /// <param name="format">The target format of the actual attribute to write out.</param> + /// <param name="value">The value of the attribute.</param> + private static void AddAXAttributeValue(FetchResponse ax, string typeUri, AXAttributeFormats format, string value) { + if (!string.IsNullOrEmpty(value)) { + string targetTypeUri = TransformAXFormat(typeUri, format); + if (!ax.Attributes.Contains(targetTypeUri)) { + ax.Attributes.Add(targetTypeUri, value); + } + } + } + + /// <summary> + /// Gets the demand level for an AX attribute. + /// </summary> + /// <param name="ax">The AX fetch request to search for the attribute.</param> + /// <param name="typeUri">The type URI of the attribute in axschema.org format.</param> + /// <returns>The demand level for the attribute.</returns> + private static DemandLevel GetDemandLevelFor(FetchRequest ax, string typeUri) { + Contract.Requires(ax != null); + Contract.Requires(!String.IsNullOrEmpty(typeUri)); + + foreach (AXAttributeFormats format in ForEachFormat(AXAttributeFormats.All)) { + string typeUriInFormat = TransformAXFormat(typeUri, format); + if (ax.Attributes.Contains(typeUriInFormat)) { + return ax.Attributes[typeUriInFormat].IsRequired ? DemandLevel.Require : DemandLevel.Request; + } + } + + return DemandLevel.NoRequest; + } + + /// <summary> + /// Tries to find the exact format of AX attribute Type URI supported by the Provider. + /// </summary> + /// <param name="request">The authentication request.</param> + /// <param name="attributeFormat">The attribute formats the RP will try if this discovery fails.</param> + /// <returns>The AX format(s) to use based on the Provider's advertised AX support.</returns> + private static bool TryDetectOPAttributeFormat(RelyingParty.IAuthenticationRequest request, out AXAttributeFormats attributeFormat) { + Contract.Requires(request != null); + var provider = (RelyingParty.ServiceEndpoint)request.Provider; + attributeFormat = DetectAXFormat(provider.ProviderDescription.Capabilities); + return attributeFormat != AXAttributeFormats.None; + } + + /// <summary> + /// Detects the AX attribute type URI format from a given sample. + /// </summary> + /// <param name="typeURIs">The type URIs to scan for recognized formats.</param> + /// <returns>The first AX type URI format recognized in the list.</returns> + private static AXAttributeFormats DetectAXFormat(IEnumerable<string> typeURIs) { + Contract.Requires(typeURIs != null); + + if (typeURIs.Any(uri => uri.StartsWith("http://axschema.org/", StringComparison.Ordinal))) { + return AXAttributeFormats.AXSchemaOrg; + } + + if (typeURIs.Any(uri => uri.StartsWith("http://schema.openid.net/", StringComparison.Ordinal))) { + return AXAttributeFormats.SchemaOpenIdNet; + } + + if (typeURIs.Any(uri => uri.StartsWith("http://openid.net/schema/", StringComparison.Ordinal))) { + return AXAttributeFormats.OpenIdNetSchema; + } + + return AXAttributeFormats.None; + } + + /// <summary> + /// Transforms an AX attribute type URI from the axschema.org format into a given format. + /// </summary> + /// <param name="axSchemaOrgFormatTypeUri">The ax schema org format type URI.</param> + /// <param name="targetFormat">The target format. Only one flag should be set.</param> + /// <returns>The AX attribute type URI in the target format.</returns> + private static string TransformAXFormat(string axSchemaOrgFormatTypeUri, AXAttributeFormats targetFormat) { + Contract.Requires(!String.IsNullOrEmpty(axSchemaOrgFormatTypeUri)); + + switch (targetFormat) { + case AXAttributeFormats.AXSchemaOrg: + return axSchemaOrgFormatTypeUri; + case AXAttributeFormats.SchemaOpenIdNet: + return axSchemaOrgFormatTypeUri.Replace("axschema.org", "schema.openid.net"); + case AXAttributeFormats.OpenIdNetSchema: + return axSchemaOrgFormatTypeUri.Replace("axschema.org", "openid.net/schema"); + default: + throw new ArgumentOutOfRangeException("targetFormat"); + } + } + + /// <summary> + /// Splits the AX attribute format flags into individual values for processing. + /// </summary> + /// <param name="formats">The formats to split up into individual flags.</param> + /// <returns>A sequence of individual flags.</returns> + private static IEnumerable<AXAttributeFormats> ForEachFormat(AXAttributeFormats formats) { + if ((formats & AXAttributeFormats.AXSchemaOrg) != 0) { + yield return AXAttributeFormats.AXSchemaOrg; + } + + if ((formats & AXAttributeFormats.OpenIdNetSchema) != 0) { + yield return AXAttributeFormats.OpenIdNetSchema; + } + + if ((formats & AXAttributeFormats.SchemaOpenIdNet) != 0) { + yield return AXAttributeFormats.SchemaOpenIdNet; + } + } + + /// <summary> + /// Adds an attribute fetch request if it is not already present in the AX request. + /// </summary> + /// <param name="ax">The AX request to add the attribute request to.</param> + /// <param name="format">The format of the attribute's Type URI to use.</param> + /// <param name="axSchemaOrgFormatAttribute">The attribute in axschema.org format.</param> + /// <param name="demandLevel">The demand level.</param> + private static void FetchAttribute(FetchRequest ax, AXAttributeFormats format, string axSchemaOrgFormatAttribute, DemandLevel demandLevel) { + Contract.Requires(ax != null); + Contract.Requires(!String.IsNullOrEmpty(axSchemaOrgFormatAttribute)); + + string typeUri = TransformAXFormat(axSchemaOrgFormatAttribute, format); + if (!ax.Attributes.Contains(typeUri)) { + switch (demandLevel) { + case DemandLevel.Request: + ax.Attributes.AddOptional(typeUri); + break; + case DemandLevel.Require: + ax.Attributes.AddRequired(typeUri); + break; + default: + break; + } + } + } + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs index 4392cd5..99c7a2e 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/AuthenticationPolicies.cs @@ -43,6 +43,23 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { public const string PrivatePersonalIdentifier = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifier"; /// <summary> + /// Indicates that the OP MUST only respond with a positive assertion if the requirements demonstrated + /// by the OP to obtain certification by a Federally adopted Trust Framework Provider have been met. + /// </summary> + /// <remarks> + /// Notwithstanding the RP may request this authentication policy, the RP MUST still + /// verify that this policy appears in the positive assertion response rather than assume the OP + /// recognized and complied with the request. + /// </remarks> + public const string USGovernmentTrustLevel1 = "http://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdf"; + + /// <summary> + /// Indicates that the OP MUST not include any OpenID Attribute Exchange or Simple Registration + /// information regarding the user in the assertion. + /// </summary> + public const string NoPersonallyIdentifiableInformation = "http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf"; + + /// <summary> /// Used in a PAPE response to indicate that no PAPE authentication policies could be satisfied. /// </summary> /// <remarks> diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs index 82297d0..9dc0574 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/DateTimeEncoder.cs @@ -7,6 +7,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { using System; using System.Globalization; + using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Reflection; /// <summary> @@ -39,7 +40,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { public string Encode(object value) { DateTime? dateTime = value as DateTime?; if (dateTime.HasValue) { - return dateTime.Value.ToUniversalTime().ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture); + return dateTime.Value.ToUniversalTimeSafe().ToString(PermissibleDateTimeFormats[0], CultureInfo.InvariantCulture); } else { return null; } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs index 4b2bcc9..b476cf7 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/ProviderAuthenticationPolicy/PolicyResponse.cs @@ -90,7 +90,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.ProviderAuthenticationPolicy { // Convert to UTC and cut to the second, since the protocol only allows for // that level of precision. - this.authenticationTimeUtc = OpenIdUtilities.CutToSecond(value.Value.ToUniversalTime()); + this.authenticationTimeUtc = OpenIdUtilities.CutToSecond(value.Value.ToUniversalTimeSafe()); } else { this.authenticationTimeUtc = null; } diff --git a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs index 800a2ff..10622bf 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/SimpleRegistration/ClaimsRequest.cs @@ -116,6 +116,12 @@ namespace DotNetOpenAuth.OpenId.Extensions.SimpleRegistration { public DemandLevel TimeZone { get; set; } /// <summary> + /// Gets or sets a value indicating whether this <see cref="ClaimsRequest"/> instance + /// is synthesized from an AX request at the Provider. + /// </summary> + internal bool Synthesized { get; set; } + + /// <summary> /// Gets or sets the value of the sreg.required parameter. /// </summary> /// <value>A comma-delimited list of sreg fields.</value> @@ -247,6 +253,8 @@ TimeZone = '{8}'"; internal void SetProfileRequestFromList(IEnumerable<string> fieldNames, DemandLevel requestLevel) { foreach (string field in fieldNames) { switch (field) { + case "": // this occurs for empty lists + break; case Constants.nickname: this.Nickname = requestLevel; break; diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs new file mode 100644 index 0000000..1cc920a --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIConstants.cs @@ -0,0 +1,34 @@ +//----------------------------------------------------------------------- +// <copyright file="UIConstants.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.Extensions.UI { + /// <summary> + /// Constants used to support the UI extension. + /// </summary> + internal static class UIConstants { + /// <summary> + /// The type URI associated with this extension. + /// </summary> + internal const string UITypeUri = "http://specs.openid.net/extensions/ui/1.0"; + + /// <summary> + /// The Type URI that appears in an XRDS document when the OP supports popups through the UI extension. + /// </summary> + internal const string PopupSupported = "http://specs.openid.net/extensions/ui/1.0/mode/popup"; + + /// <summary> + /// The Type URI that appears in an XRDS document when the OP supports the RP + /// specifying the user's preferred language through the UI extension. + /// </summary> + internal const string LangPrefSupported = "http://specs.openid.net/extensions/ui/1.0/lang-pref"; + + /// <summary> + /// The Type URI that appears in the XRDS document when the OP supports the RP + /// specifying the icon for the OP to display during authentication through the UI extension. + /// </summary> + internal const string IconSupported = "http://specs.openid.net/extensions/ui/1.0/icon"; + } +} diff --git a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs index 476b4ad..bee675d 100644 --- a/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Extensions/UI/UIRequest.cs @@ -8,11 +8,14 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics.Contracts; using System.Globalization; using System.Linq; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; + using DotNetOpenAuth.OpenId.Provider; using DotNetOpenAuth.OpenId.RelyingParty; + using DotNetOpenAuth.Xrds; /// <summary> /// OpenID User Interface extension 1.0 request message. @@ -25,7 +28,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// <see cref="UIModes.Popup"/>. </para> /// <para>An RP may determine whether an arbitrary OP supports this extension (and thereby determine /// whether to use a standard full window redirect or a popup) via the - /// <see cref="IProviderEndpoint.IsExtensionSupported"/> method on the <see cref="IAuthenticationRequest.Provider"/> + /// <see cref="IProviderEndpoint.IsExtensionSupported"/> method on the <see cref="DotNetOpenAuth.OpenId.RelyingParty.IAuthenticationRequest.Provider"/> /// object.</para> /// </remarks> public sealed class UIRequest : IOpenIdMessageExtension, IMessageWithEvents { @@ -33,7 +36,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// The factory method that may be used in deserialization of this message. /// </summary> internal static readonly StandardOpenIdExtensionFactory.CreateDelegate Factory = (typeUri, data, baseMessage, isProviderRole) => { - if (typeUri == UITypeUri && isProviderRole) { + if (typeUri == UIConstants.UITypeUri && isProviderRole) { return new UIRequest(); } @@ -41,9 +44,13 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { }; /// <summary> - /// The type URI associated with this extension. + /// Additional type URIs that this extension is sometimes known by remote parties. /// </summary> - private const string UITypeUri = "http://specs.openid.net/extensions/ui/1.0"; + private static readonly string[] additionalTypeUris = new string[] { + UIConstants.LangPrefSupported, + UIConstants.PopupSupported, + UIConstants.IconSupported, + }; /// <summary> /// Backing store for <see cref="ExtraData"/>. @@ -54,18 +61,18 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// Initializes a new instance of the <see cref="UIRequest"/> class. /// </summary> public UIRequest() { - this.LanguagePreference = CultureInfo.CurrentUICulture; + this.LanguagePreference = new[] { CultureInfo.CurrentUICulture }; } /// <summary> - /// Gets or sets the user's preferred language. + /// Gets or sets the list of user's preferred languages, sorted in decreasing preferred order. /// </summary> /// <value>The default is the <see cref="CultureInfo.CurrentUICulture"/> of the thread that created this instance.</value> /// <remarks> - /// The user's preferred language, reusing the Language Tag format used by the [Language Preference Attribute] (axschema.org, “Language Preference Attribute,” .) for [OpenID Attribute Exchange] (Hardt, D., Bufu, J., and J. Hoyt, “OpenID Attribute Exchange 1.0,” .) and defined in [RFC4646] (Phillips, A. and M. Davis, “Tags for Identifying Languages,” .). For example "en-US" represents the English language as spoken in the United States, and "fr-CA" represents the French language spoken in Canada. + /// The user's preferred languages as a [BCP 47] language priority list, represented as a comma-separated list of BCP 47 basic language ranges in descending priority order. For instance, the value "fr-CA,fr-FR,en-CA" represents the preference for French spoken in Canada, French spoken in France, followed by English spoken in Canada. /// </remarks> [MessagePart("lang", AllowEmpty = false)] - public CultureInfo LanguagePreference { get; set; } + public CultureInfo[] LanguagePreference { get; set; } /// <summary> /// Gets the style of UI that the RP is hosting the OP's authentication page in. @@ -75,13 +82,25 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { [MessagePart("mode", AllowEmpty = false, IsRequired = true)] public string Mode { get { return UIModes.Popup; } } + /// <summary> + /// Gets or sets a value indicating whether the Relying Party has an icon + /// it would like the Provider to display to the user while asking them + /// whether they would like to log in. + /// </summary> + /// <value><c>true</c> if the Provider should display an icon; otherwise, <c>false</c>.</value> + /// <remarks> + /// By default, the Provider displays the relying party's favicon.ico. + /// </remarks> + [MessagePart("icon", AllowEmpty = false, IsRequired = false)] + public bool? Icon { get; set; } + #region IOpenIdMessageExtension Members /// <summary> /// Gets the TypeURI the extension uses in the OpenID protocol and in XRDS advertisements. /// </summary> /// <value></value> - public string TypeUri { get { return UITypeUri; } } + public string TypeUri { get { return UIConstants.UITypeUri; } } /// <summary> /// Gets the additional TypeURIs that are supported by this extension, in preferred order. @@ -98,7 +117,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { /// given the version of the extension in the request message. /// The <see cref="Extensions.SimpleRegistration.ClaimsRequest.CreateResponse"/> for an example. /// </remarks> - public IEnumerable<string> AdditionalSupportedTypeUris { get { return Enumerable.Empty<string>(); } } + public IEnumerable<string> AdditionalSupportedTypeUris { get { return additionalTypeUris; } } /// <summary> /// Gets or sets a value indicating whether this extension was @@ -111,7 +130,7 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { #endregion - #region IMessage Members + #region IMessage Properties /// <summary> /// Gets the version of the protocol or extension this message is prepared to implement. @@ -134,6 +153,72 @@ namespace DotNetOpenAuth.OpenId.Extensions.UI { get { return this.extraData; } } + #endregion + + /// <summary> + /// Gets the URL of the RP icon for the OP to display. + /// </summary> + /// <param name="realm">The realm of the RP where the authentication request originated.</param> + /// <param name="webRequestHandler">The web request handler to use for discovery. + /// Usually available via <see cref="Channel.WebRequestHandler">OpenIdProvider.Channel.WebRequestHandler</see>.</param> + /// <returns> + /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. + /// </returns> + /// <value>The icon URL.</value> + /// <remarks> + /// This property is automatically set for the OP with the result of RP discovery. + /// RPs should set this value by including an entry such as this in their XRDS document. + /// <example> + /// <Service xmlns="xri://$xrd*($v*2.0)"> + /// <Type>http://specs.openid.net/extensions/ui/icon</Type> + /// <URI>http://consumer.example.com/images/image.jpg</URI> + /// </Service> + /// </example> + /// </remarks> + public static IEnumerable<Uri> GetRelyingPartyIconUrls(Realm realm, IDirectWebRequestHandler webRequestHandler) { + Contract.Requires(realm != null); + Contract.Requires(webRequestHandler != null); + ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); + ErrorUtilities.VerifyArgumentNotNull(webRequestHandler, "webRequestHandler"); + + XrdsDocument xrds = realm.Discover(webRequestHandler, false); + if (xrds == null) { + return Enumerable.Empty<Uri>(); + } else { + return xrds.FindRelyingPartyIcons(); + } + } + + /// <summary> + /// Gets the URL of the RP icon for the OP to display. + /// </summary> + /// <param name="realm">The realm of the RP where the authentication request originated.</param> + /// <param name="provider">The Provider instance used to obtain the authentication request.</param> + /// <returns> + /// A sequence of the RP's icons it has available for the Provider to display, in decreasing preferred order. + /// </returns> + /// <value>The icon URL.</value> + /// <remarks> + /// This property is automatically set for the OP with the result of RP discovery. + /// RPs should set this value by including an entry such as this in their XRDS document. + /// <example> + /// <Service xmlns="xri://$xrd*($v*2.0)"> + /// <Type>http://specs.openid.net/extensions/ui/icon</Type> + /// <URI>http://consumer.example.com/images/image.jpg</URI> + /// </Service> + /// </example> + /// </remarks> + public static IEnumerable<Uri> GetRelyingPartyIconUrls(Realm realm, OpenIdProvider provider) { + Contract.Requires(realm != null); + Contract.Requires(provider != null); + ErrorUtilities.VerifyArgumentNotNull(realm, "realm"); + ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); + + return GetRelyingPartyIconUrls(realm, provider.Channel.WebRequestHandler); + } + + #region IMessage methods + /// <summary> /// Checks the message state for conformity to the protocol specification /// and throws an exception if the message is invalid. diff --git a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs index 16d8f74..4c31100 100644 --- a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs +++ b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs @@ -29,7 +29,7 @@ namespace DotNetOpenAuth.OpenId { /// <summary> /// A list of HMAC-SHA algorithms in order of decreasing bit lengths. /// </summary> - private static HmacSha[] hmacShaAssociationTypes = { + private static HmacSha[] hmacShaAssociationTypes = new List<HmacSha> { new HmacSha { CreateHasher = secretKey => new HMACSHA512(secretKey), GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA512, @@ -50,7 +50,7 @@ namespace DotNetOpenAuth.OpenId { GetAssociationType = protocol => protocol.Args.SignatureAlgorithm.HMAC_SHA1, BaseHashAlgorithm = new SHA1Managed(), }, - }; + } .ToArray(); /// <summary> /// The specific variety of HMAC-SHA this association is based on (whether it be HMAC-SHA1, HMAC-SHA256, etc.) diff --git a/src/DotNetOpenAuth/OpenId/Identifier.cs b/src/DotNetOpenAuth/OpenId/Identifier.cs index 1b9570e..6e71b0a 100644 --- a/src/DotNetOpenAuth/OpenId/Identifier.cs +++ b/src/DotNetOpenAuth/OpenId/Identifier.cs @@ -33,6 +33,16 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Gets or sets a value indicating whether <see cref="Identifier"/> instances are considered equal + /// based solely on their string reprsentations. + /// </summary> + /// <remarks> + /// This property serves as a test hook, so that MockIdentifier instances can be considered "equal" + /// to UriIdentifier instances. + /// </remarks> + protected internal static bool EqualityOnStrings { get; set; } + + /// <summary> /// Gets a value indicating whether this Identifier will ensure SSL is /// used throughout the discovery phase and initial redirect of authentication. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs index d44809f..86e80ba 100644 --- a/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs +++ b/src/DotNetOpenAuth/OpenId/Interop/OpenIdRelyingPartyShim.cs @@ -16,8 +16,8 @@ namespace DotNetOpenAuth.OpenId.Interop { using DotNetOpenAuth.OpenId.RelyingParty; /// <summary> - /// The COM interface describing the DotNetOpenId functionality available to - /// COM client relying parties. + /// The COM interface describing the DotNetOpenAuth functionality available to + /// COM client OpenID relying parties. /// </summary> [Guid("56BD3DB0-EE0D-4191-ADFC-1F3705CD2636")] [InterfaceType(ComInterfaceType.InterfaceIsDual)] @@ -87,6 +87,19 @@ namespace DotNetOpenAuth.OpenId.Interop { [ClassInterface(ClassInterfaceType.None)] public sealed class OpenIdRelyingPartyShim : IOpenIdRelyingParty { /// <summary> + /// The OpenIdRelyingParty instance to use for requests. + /// </summary> + private static OpenIdRelyingParty relyingParty; + + /// <summary> + /// Initializes static members of the <see cref="OpenIdRelyingPartyShim"/> class. + /// </summary> + static OpenIdRelyingPartyShim() { + relyingParty = new OpenIdRelyingParty(null); + relyingParty.Behaviors.Add(new Behaviors.AXFetchAsSregTransform()); + } + + /// <summary> /// Creates an authentication request to verify that a user controls /// some given Identifier. /// </summary> @@ -109,9 +122,8 @@ namespace DotNetOpenAuth.OpenId.Interop { /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "COM requires primitive types")] public string CreateRequest(string userSuppliedIdentifier, string realm, string returnToUrl) { - OpenIdRelyingParty rp = new OpenIdRelyingParty(null); - var request = rp.CreateRequest(userSuppliedIdentifier, realm, new Uri(returnToUrl)); - return request.RedirectingResponse.GetDirectUriRequest(rp.Channel).AbsoluteUri; + var request = relyingParty.CreateRequest(userSuppliedIdentifier, realm, new Uri(returnToUrl)); + return request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel).AbsoluteUri; } /// <summary> @@ -133,8 +145,7 @@ namespace DotNetOpenAuth.OpenId.Interop { /// <exception cref="ProtocolException">Thrown if no OpenID endpoint could be found.</exception> [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "COM requires primitive types")] public string CreateRequestWithSimpleRegistration(string userSuppliedIdentifier, string realm, string returnToUrl, string optionalSreg, string requiredSreg) { - OpenIdRelyingParty rp = new OpenIdRelyingParty(null); - var request = rp.CreateRequest(userSuppliedIdentifier, realm, new Uri(returnToUrl)); + var request = relyingParty.CreateRequest(userSuppliedIdentifier, realm, new Uri(returnToUrl)); ClaimsRequest sreg = new ClaimsRequest(); if (!string.IsNullOrEmpty(optionalSreg)) { @@ -144,7 +155,7 @@ namespace DotNetOpenAuth.OpenId.Interop { sreg.SetProfileRequestFromList(requiredSreg.Split(','), DemandLevel.Require); } request.AddExtension(sreg); - return request.RedirectingResponse.GetDirectUriRequest(rp.Channel).AbsoluteUri; + return request.RedirectingResponse.GetDirectUriRequest(relyingParty.Channel).AbsoluteUri; } /// <summary> @@ -155,14 +166,13 @@ namespace DotNetOpenAuth.OpenId.Interop { /// <param name="form">The form data that may have been included in the case of a POST request.</param> /// <returns>The Provider's response to a previous authentication request, or null if no response is present.</returns> public AuthenticationResponseShim ProcessAuthentication(string url, string form) { - OpenIdRelyingParty rp = new OpenIdRelyingParty(null); HttpRequestInfo requestInfo = new HttpRequestInfo { UrlBeforeRewriting = new Uri(url) }; if (!string.IsNullOrEmpty(form)) { requestInfo.HttpMethod = "POST"; requestInfo.InputStream = new MemoryStream(Encoding.Unicode.GetBytes(form)); } - var response = rp.GetResponse(requestInfo); + var response = relyingParty.GetResponse(requestInfo); if (response != null) { return new AuthenticationResponseShim(response); } diff --git a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs index 0d105ad..9462d21 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/IndirectSignedResponse.cs @@ -201,7 +201,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </exception> DateTime IExpiringProtocolMessage.UtcCreationDate { get { return this.creationDateUtc; } - set { this.creationDateUtc = value.ToUniversalTime(); } + set { this.creationDateUtc = value.ToUniversalTimeSafe(); } } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs index 27dacfd..cca41a0 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4918 +// Runtime Version:2.0.50727.4927 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -151,6 +151,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to The {0} extension failed to deserialize and will be skipped. {1}. + /// </summary> + internal static string BadExtension { + get { + return ResourceManager.GetString("BadExtension", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Callback arguments are only supported when a {0} is provided to the {1}.. /// </summary> internal static string CallbackArgumentsRequireSecretStore { @@ -488,6 +497,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to An positive OpenID assertion was received from OP endpoint {0} that is not on this relying party's whitelist.. + /// </summary> + internal static string PositiveAssertionFromNonWhitelistedProvider { + get { + return ResourceManager.GetString("PositiveAssertionFromNonWhitelistedProvider", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to Unable to find the signing secret by the handle '{0}'.. /// </summary> internal static string PrivateRPSecretNotFound { @@ -524,6 +542,15 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Looks up a localized string similar to Sorry. This site only accepts OpenIDs that are HTTPS-secured, but {0} is not a secure Identifier.. + /// </summary> + internal static string RequireSslNotSatisfiedByAssertedClaimedId { + get { + return ResourceManager.GetString("RequireSslNotSatisfiedByAssertedClaimedId", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The response is not ready. Use IsResponseReady to check whether a response is ready first.. /// </summary> internal static string ResponseNotReady { @@ -587,15 +614,6 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> - /// Looks up a localized string similar to No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.. - /// </summary> - internal static string StoreRequiredWhenNoHttpContextAvailable { - get { - return ResourceManager.GetString("StoreRequiredWhenNoHttpContextAvailable", resourceCulture); - } - } - - /// <summary> /// Looks up a localized string similar to The type must implement {0}.. /// </summary> internal static string TypeMustImplementX { diff --git a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx index bca813b..f47e512 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx +++ b/src/DotNetOpenAuth/OpenId/OpenIdStrings.resx @@ -244,9 +244,6 @@ Discovered endpoint info: <data name="XriResolutionFailed" xml:space="preserve"> <value>XRI resolution failed.</value> </data> - <data name="StoreRequiredWhenNoHttpContextAvailable" xml:space="preserve"> - <value>No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.</value> - </data> <data name="AttributeAlreadyAdded" xml:space="preserve"> <value>An attribute with type URI '{0}' has already been added.</value> </data> @@ -334,4 +331,13 @@ Discovered endpoint info: <data name="ArgumentIsPpidIdentifier" xml:space="preserve"> <value>This is already a PPID Identifier.</value> </data> -</root>
\ No newline at end of file + <data name="RequireSslNotSatisfiedByAssertedClaimedId" xml:space="preserve"> + <value>Sorry. This site only accepts OpenIDs that are HTTPS-secured, but {0} is not a secure Identifier.</value> + </data> + <data name="BadExtension" xml:space="preserve"> + <value>The {0} extension failed to deserialize and will be skipped. {1}</value> + </data> + <data name="PositiveAssertionFromNonWhitelistedProvider" xml:space="preserve"> + <value>An positive OpenID assertion was received from OP endpoint {0} that is not on this relying party's whitelist.</value> + </data> +</root> diff --git a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs index 2433df2..fd83061 100644 --- a/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs +++ b/src/DotNetOpenAuth/OpenId/OpenIdXrdsHelper.cs @@ -5,6 +5,7 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId { + using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; @@ -32,6 +33,19 @@ namespace DotNetOpenAuth.OpenId { } /// <summary> + /// Finds the icons the relying party wants an OP to display as part of authentication, + /// per the UI extension spec. + /// </summary> + /// <param name="xrds">The XrdsDocument to search.</param> + /// <returns>A sequence of the icon URLs in preferred order.</returns> + internal static IEnumerable<Uri> FindRelyingPartyIcons(this XrdsDocument xrds) { + return from xrd in xrds.XrdElements + from service in xrd.OpenIdRelyingPartyIcons + from uri in service.UriElements + select uri.Uri; + } + + /// <summary> /// Creates the service endpoints described in this document, useful for requesting /// authentication of one of the OpenID Providers that result from it. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/Protocol.cs b/src/DotNetOpenAuth/OpenId/Protocol.cs index b9f2cca..7b8a2f1 100644 --- a/src/DotNetOpenAuth/OpenId/Protocol.cs +++ b/src/DotNetOpenAuth/OpenId/Protocol.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.OpenId { using DotNetOpenAuth.Messaging; using System.Globalization; using System.Diagnostics.CodeAnalysis; + using System.Diagnostics; /// <summary> /// An enumeration of the OpenID protocol versions supported by this library. @@ -34,6 +35,7 @@ namespace DotNetOpenAuth.OpenId { /// Tracks the several versions of OpenID this library supports and the unique /// constants to each version used in the protocol. /// </summary> + [DebuggerDisplay("OpenID {Version}")] internal class Protocol { /// <summary> /// The value of the openid.ns parameter in the OpenID 2.0 specification. diff --git a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs index e38952a..a500e3b 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AnonymousRequest.cs @@ -6,6 +6,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; + using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.Messages; @@ -26,6 +27,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="provider">The provider that received the request.</param> /// <param name="request">The incoming authentication request message.</param> + [SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Justification = "Code contracts require it.")] internal AnonymousRequest(OpenIdProvider provider, SignedResponseRequest request) : base(provider, request) { Contract.Requires(provider != null); @@ -35,6 +37,21 @@ namespace DotNetOpenAuth.OpenId.Provider { this.positiveResponse = new IndirectSignedResponse(request); } + #region HostProcessedRequest members + + /// <summary> + /// Gets or sets the provider endpoint. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// </value> + public override Uri ProviderEndpoint { + get { return this.positiveResponse.ProviderEndpoint; } + set { this.positiveResponse.ProviderEndpoint = value; } + } + + #endregion + #region IAnonymousRequest Members /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs index 56e73da..a5d936b 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/AuthenticationRequest.cs @@ -48,6 +48,21 @@ namespace DotNetOpenAuth.OpenId.Provider { this.IsDelegatedIdentifier = this.ClaimedIdentifier != null && this.ClaimedIdentifier != this.LocalIdentifier; } + #region HostProcessedRequest members + + /// <summary> + /// Gets or sets the provider endpoint. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// </value> + public override Uri ProviderEndpoint { + get { return this.positiveResponse.ProviderEndpoint; } + set { this.positiveResponse.ProviderEndpoint = value; } + } + + #endregion + /// <summary> /// Gets a value indicating whether the response is ready to be created and sent. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs index 4bb7d28..90dfa2f 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/HostProcessedRequest.cs @@ -66,6 +66,14 @@ namespace DotNetOpenAuth.OpenId.Provider { get { return this.RequestMessage.Realm; } } + /// <summary> + /// Gets or sets the provider endpoint. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// </value> + public abstract Uri ProviderEndpoint { get; set; } + #endregion /// <summary> @@ -105,7 +113,6 @@ namespace DotNetOpenAuth.OpenId.Provider { /// See OpenID Authentication 2.0 spec section 9.2.1. /// </remarks> public RelyingPartyDiscoveryResult IsReturnUrlDiscoverable(OpenIdProvider provider) { - Contract.Requires(provider != null); ErrorUtilities.VerifyArgumentNotNull(provider, "provider"); if (!this.realmDiscoveryResult.HasValue) { @@ -131,7 +138,7 @@ namespace DotNetOpenAuth.OpenId.Provider { return RelyingPartyDiscoveryResult.NoServiceDocument; } - var returnToEndpoints = this.Realm.Discover(provider.Channel.WebRequestHandler, false); + var returnToEndpoints = this.Realm.DiscoverReturnToEndpoints(provider.Channel.WebRequestHandler, false); if (returnToEndpoints == null) { return RelyingPartyDiscoveryResult.NoServiceDocument; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs index de24f74..00a3267 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IDirectedIdentityIdentifierProvider.cs @@ -71,9 +71,8 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <returns> /// <c>true</c> if the given identifier is the valid, unique identifier for some uesr (and NOT a PPID); otherwise, <c>false</c>. /// </returns> - public bool IsUserLocalIdentifier(Identifier identifier) { + bool IDirectedIdentityIdentifierProvider.IsUserLocalIdentifier(Identifier identifier) { Contract.Requires(identifier != null); - throw new NotImplementedException(); } diff --git a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs index 5256fdd..345ba52 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IHostProcessedRequest.cs @@ -33,6 +33,16 @@ namespace DotNetOpenAuth.OpenId.Provider { bool Immediate { get; } /// <summary> + /// Gets or sets the provider endpoint claimed in the positive assertion. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// This value MUST match the value for the OP Endpoint in the discovery results for the + /// claimed identifier being asserted in a positive response. + /// </value> + Uri ProviderEndpoint { get; set; } + + /// <summary> /// Attempts to perform relying party discovery of the return URL claimed by the Relying Party. /// </summary> /// <param name="provider">The OpenIdProvider that is performing the RP discovery.</param> @@ -76,6 +86,24 @@ namespace DotNetOpenAuth.OpenId.Provider { get { throw new System.NotImplementedException(); } } + /// <summary> + /// Gets or sets the provider endpoint. + /// </summary> + /// <value> + /// The default value is the URL that the request came in on from the relying party. + /// </value> + Uri IHostProcessedRequest.ProviderEndpoint { + get { + Contract.Ensures(Contract.Result<Uri>() != null); + throw new NotImplementedException(); + } + + set { + Contract.Requires(value != null); + throw new NotImplementedException(); + } + } + #endregion #region IRequest Members diff --git a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs index 7159c02..4e3dc99 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/IProviderBehavior.cs @@ -10,11 +10,18 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Applies a custom security policy to certain OpenID security settings and behaviors. /// </summary> - /// <remarks> - /// BEFORE MARKING THIS INTERFACE PUBLIC: it's very important that we shift the methods to be channel-level - /// rather than facade class level and for the OpenIdChannel to be the one to invoke these methods. - /// </remarks> - internal interface IProviderBehavior { + public interface IProviderBehavior { + /// <summary> + /// Applies a well known set of security requirements to a default set of security settings. + /// </summary> + /// <param name="securitySettings">The security settings to enhance with the requirements of this profile.</param> + /// <remarks> + /// Care should be taken to never decrease security when applying a profile. + /// Profiles should only enhance security requirements to avoid being + /// incompatible with each other. + /// </remarks> + void ApplySecuritySettings(ProviderSecuritySettings securitySettings); + /// <summary> /// Called when a request is received by the Provider. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs index fa40d9f..ae044aa 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/OpenIdProvider.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.Provider { using System; using System.Collections.Generic; using System.Collections.ObjectModel; + using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Diagnostics.Contracts; @@ -34,7 +35,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Backing store for the <see cref="Behaviors"/> property. /// </summary> - private readonly Collection<IProviderBehavior> behaviors = new Collection<IProviderBehavior>(); + private readonly ObservableCollection<IProviderBehavior> behaviors = new ObservableCollection<IProviderBehavior>(); /// <summary> /// Backing field for the <see cref="SecuritySettings"/> property. @@ -79,6 +80,7 @@ namespace DotNetOpenAuth.OpenId.Provider { this.AssociationStore = associationStore; this.SecuritySettings = DotNetOpenAuthSection.Configuration.OpenId.Provider.SecuritySettings.CreateSecuritySettings(); + this.behaviors.CollectionChanged += this.OnBehaviorsChanged; foreach (var behavior in DotNetOpenAuthSection.Configuration.OpenId.Provider.Behaviors.CreateInstances(false)) { this.behaviors.Add(behavior); } @@ -150,7 +152,11 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// Gets a list of custom behaviors to apply to OpenID actions. /// </summary> - internal ICollection<IProviderBehavior> Behaviors { + /// <remarks> + /// Adding behaviors can impact the security settings of the <see cref="OpenIdProvider"/> + /// in ways that subsequently removing the behaviors will not reverse. + /// </remarks> + public ICollection<IProviderBehavior> Behaviors { get { return this.behaviors; } } @@ -367,21 +373,27 @@ namespace DotNetOpenAuth.OpenId.Provider { // is authorized to send an assertion for the given claimed identifier, // do due diligence by performing our own discovery on the claimed identifier // and make sure that it is tied to this OP and OP local identifier. - var serviceEndpoint = DotNetOpenAuth.OpenId.RelyingParty.ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); - var discoveredEndpoints = claimedIdentifier.Discover(this.WebRequestHandler); - if (!discoveredEndpoints.Contains(serviceEndpoint)) { - Logger.OpenId.DebugFormat( - "Failed to send unsolicited assertion for {0} because its discovered services did not include this endpoint. This endpoint: {1}{2} Discovered endpoints: {1}{3}", - claimedIdentifier, - Environment.NewLine, - serviceEndpoint, - discoveredEndpoints.ToStringDeferred(true)); - ErrorUtilities.ThrowProtocol(OpenIdStrings.UnsolicitedAssertionForUnrelatedClaimedIdentifier, claimedIdentifier); + if (this.SecuritySettings.UnsolicitedAssertionVerification != ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.NeverVerify) { + var serviceEndpoint = DotNetOpenAuth.OpenId.RelyingParty.ServiceEndpoint.CreateForClaimedIdentifier(claimedIdentifier, localIdentifier, new ProviderEndpointDescription(providerEndpoint, Protocol.Default.Version), null, null); + var discoveredEndpoints = claimedIdentifier.Discover(this.WebRequestHandler); + if (!discoveredEndpoints.Contains(serviceEndpoint)) { + Logger.OpenId.WarnFormat( + "Failed to send unsolicited assertion for {0} because its discovered services did not include this endpoint: {1}{2}{1}Discovered endpoints: {1}{3}", + claimedIdentifier, + Environment.NewLine, + serviceEndpoint, + discoveredEndpoints.ToStringDeferred(true)); + + // Only FAIL if the setting is set for it. + if (this.securitySettings.UnsolicitedAssertionVerification == ProviderSecuritySettings.UnsolicitedAssertionVerificationLevel.RequireSuccess) { + ErrorUtilities.ThrowProtocol(OpenIdStrings.UnsolicitedAssertionForUnrelatedClaimedIdentifier, claimedIdentifier); + } + } } Logger.OpenId.InfoFormat("Preparing unsolicited assertion for {0}", claimedIdentifier); RelyingPartyEndpointDescription returnToEndpoint = null; - var returnToEndpoints = relyingParty.Discover(this.WebRequestHandler, true); + var returnToEndpoints = relyingParty.DiscoverReturnToEndpoints(this.WebRequestHandler, true); if (returnToEndpoints != null) { returnToEndpoint = returnToEndpoints.FirstOrDefault(); } @@ -504,5 +516,16 @@ namespace DotNetOpenAuth.OpenId.Provider { return new AutoResponsiveRequest(errorMessage, this.SecuritySettings); } } + + /// <summary> + /// Called by derived classes when behaviors are added or removed. + /// </summary> + /// <param name="sender">The collection being modified.</param> + /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param> + private void OnBehaviorsChanged(object sender, NotifyCollectionChangedEventArgs e) { + foreach (IProviderBehavior profile in e.NewItems) { + profile.ApplySecuritySettings(this.SecuritySettings); + } + } } } diff --git a/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs b/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs index 64d2908..399a84f 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/PrivatePersonalIdentifierProviderBase.cs @@ -34,7 +34,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Initializes a new instance of the <see cref="PrivatePersonalIdentifierProviderBase"/> class. /// </summary> /// <param name="baseIdentifier">The base URI on which to append the anonymous part.</param> - public PrivatePersonalIdentifierProviderBase(Uri baseIdentifier) { + protected PrivatePersonalIdentifierProviderBase(Uri baseIdentifier) { Contract.Requires(baseIdentifier != null); ErrorUtilities.VerifyArgumentNotNull(baseIdentifier, "baseIdentifier"); @@ -47,6 +47,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// <summary> /// A granularity description for who wide of an audience sees the same generated PPID. /// </summary> + [SuppressMessage("Microsoft.Design", "CA1034:NestedTypesShouldNotBeVisible", Justification = "Breaking change")] public enum AudienceScope { /// <summary> /// A unique Identifier is generated for every realm. This is the highest security setting. @@ -78,6 +79,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// for the authenticating uesr. /// </summary> /// <value>The default value is <see cref="AudienceScope.Realm"/>.</value> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pairwise", Justification = "Meaningful word")] public AudienceScope PairwiseUnique { get; set; } /// <summary> @@ -183,6 +185,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// </summary> /// <param name="uriHash">The unique part of the Identifier to append to the common first part.</param> /// <returns>The full PPID Identifier.</returns> + [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "NOT equivalent overload. The recommended one breaks on relative URIs.")] protected virtual Uri AppendIdentifiers(string uriHash) { Contract.Requires(!String.IsNullOrEmpty(uriHash)); ErrorUtilities.VerifyNonZeroLength(uriHash, "uriHash"); diff --git a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs index 876e412..9590033 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/ProviderSecuritySettings.cs @@ -28,6 +28,11 @@ namespace DotNetOpenAuth.OpenId.Provider { internal const bool SignOutgoingExtensionsDefault = true; /// <summary> + /// The default value for the <see cref="UnsolicitedAssertionVerification"/> property. + /// </summary> + internal const UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerificationDefault = UnsolicitedAssertionVerificationLevel.RequireSuccess; + + /// <summary> /// The subset of association types and their customized lifetimes. /// </summary> private IDictionary<string, TimeSpan> associationLifetimes = new Dictionary<string, TimeSpan>(); @@ -39,6 +44,37 @@ namespace DotNetOpenAuth.OpenId.Provider { : base(true) { this.SignOutgoingExtensions = SignOutgoingExtensionsDefault; this.ProtectDownlevelReplayAttacks = ProtectDownlevelReplayAttacksDefault; + this.UnsolicitedAssertionVerification = UnsolicitedAssertionVerificationDefault; + } + + /// <summary> + /// The behavior a Provider takes when verifying that it is authoritative for an + /// identifier it is about to send an unsolicited assertion for. + /// </summary> + public enum UnsolicitedAssertionVerificationLevel { + /// <summary> + /// Always verify that the Provider is authoritative for an identifier before + /// sending an unsolicited assertion for it and fail if it is not. + /// </summary> + RequireSuccess, + + /// <summary> + /// Always check that the Provider is authoritative for an identifier before + /// sending an unsolicited assertion for it, but only log failures, and proceed + /// to send the unsolicited assertion. + /// </summary> + LogWarningOnFailure, + + /// <summary> + /// Never verify that the Provider is authoritative for an identifier before + /// sending an unsolicited assertion for it. + /// </summary> + /// <remarks> + /// This setting is useful for web servers that refuse to allow a Provider to + /// introspectively perform an HTTP GET on itself, when sending unsolicited assertions + /// for identifiers that the OP controls. + /// </remarks> + NeverVerify, } /// <summary> @@ -57,6 +93,13 @@ namespace DotNetOpenAuth.OpenId.Provider { public bool RequireSsl { get; set; } /// <summary> + /// Gets or sets the level of verification a Provider performs on an identifier before + /// sending an unsolicited assertion for it. + /// </summary> + /// <value>The default value is <see cref="UnsolicitedAssertionVerificationLevel.RequireSuccess"/>.</value> + public UnsolicitedAssertionVerificationLevel UnsolicitedAssertionVerification { get; set; } + + /// <summary> /// Gets or sets a value indicating whether OpenID 1.x relying parties that may not be /// protecting their users from replay attacks are protected from /// replay attacks by this provider. @@ -101,6 +144,7 @@ namespace DotNetOpenAuth.OpenId.Provider { securitySettings.ProtectDownlevelReplayAttacks = this.ProtectDownlevelReplayAttacks; securitySettings.RequireSsl = this.RequireSsl; securitySettings.SignOutgoingExtensions = this.SignOutgoingExtensions; + securitySettings.UnsolicitedAssertionVerification = this.UnsolicitedAssertionVerification; return securitySettings; } diff --git a/src/DotNetOpenAuth/OpenId/Provider/Request.cs b/src/DotNetOpenAuth/OpenId/Provider/Request.cs index 4c2ee98..43697b2 100644 --- a/src/DotNetOpenAuth/OpenId/Provider/Request.cs +++ b/src/DotNetOpenAuth/OpenId/Provider/Request.cs @@ -132,7 +132,7 @@ namespace DotNetOpenAuth.OpenId.Provider { /// Gets the original request message. /// </summary> /// <value>This may be null in the case of an unrecognizable message.</value> - protected IDirectedProtocolMessage RequestMessage { + protected internal IDirectedProtocolMessage RequestMessage { get { return this.request; } } diff --git a/src/DotNetOpenAuth/OpenId/Realm.cs b/src/DotNetOpenAuth/OpenId/Realm.cs index 7f0acdb..2859cf0 100644 --- a/src/DotNetOpenAuth/OpenId/Realm.cs +++ b/src/DotNetOpenAuth/OpenId/Realm.cs @@ -27,7 +27,7 @@ namespace DotNetOpenAuth.OpenId { /// </remarks> [Serializable] [Pure] - public sealed class Realm { + public class Realm { /// <summary> /// A regex used to detect a wildcard that is being used in the realm. /// </summary> @@ -381,7 +381,26 @@ namespace DotNetOpenAuth.OpenId { /// <returns> /// The details of the endpoints if found; or <c>null</c> if no service document was discovered. /// </returns> - internal IEnumerable<RelyingPartyEndpointDescription> Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) { + internal virtual IEnumerable<RelyingPartyEndpointDescription> DiscoverReturnToEndpoints(IDirectWebRequestHandler requestHandler, bool allowRedirects) { + XrdsDocument xrds = this.Discover(requestHandler, allowRedirects); + if (xrds != null) { + return xrds.FindRelyingPartyReceivingEndpoints(); + } + + return null; + } + + /// <summary> + /// Searches for an XRDS document at the realm URL. + /// </summary> + /// <param name="requestHandler">The mechanism to use for sending HTTP requests.</param> + /// <param name="allowRedirects">Whether redirects may be followed when discovering the Realm. + /// This may be true when creating an unsolicited assertion, but must be + /// false when performing return URL verification per 2.0 spec section 9.2.1.</param> + /// <returns> + /// The XRDS document if found; or <c>null</c> if no service document was discovered. + /// </returns> + internal virtual XrdsDocument Discover(IDirectWebRequestHandler requestHandler, bool allowRedirects) { // Attempt YADIS discovery DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this.UriWithWildcardChangedToWww, false); if (yadisResult != null) { @@ -389,8 +408,7 @@ namespace DotNetOpenAuth.OpenId { ErrorUtilities.VerifyProtocol(allowRedirects || yadisResult.NormalizedUri == yadisResult.RequestUri, OpenIdStrings.RealmCausedRedirectUponDiscovery, yadisResult.RequestUri); if (yadisResult.IsXrds) { try { - XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); - return xrds.FindRelyingPartyReceivingEndpoints(); + return new XrdsDocument(yadisResult.ResponseText); } catch (XmlException ex) { throw ErrorUtilities.Wrap(ex, XrdsStrings.InvalidXRDSDocument); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs index a37c8c7..85c0096 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AssociationManager.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { using System; using System.Collections.Generic; using System.Linq; + using System.Net; using System.Text; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.ChannelElements; @@ -209,6 +210,13 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { throw new ProtocolException(MessagingStrings.UnexpectedMessageReceivedOfMany); } } catch (ProtocolException ex) { + // If the association failed because the remote server can't handle Expect: 100 Continue headers, + // then our web request handler should have already accomodated for future calls. Go ahead and + // immediately make one of those future calls now to try to get the association to succeed. + if (StandardWebRequestHandler.IsExceptionFrom417ExpectationFailed(ex)) { + return this.CreateNewAssociation(provider, associateRequest, retriesRemaining - 1); + } + // Since having associations with OPs is not totally critical, we'll log and eat // the exception so that auth may continue in dumb mode. Logger.OpenId.ErrorFormat("An error occurred while trying to create an association with {0}. {1}", provider.Endpoint, ex); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs index 19db0fa..cea7d21 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationRequest.cs @@ -153,7 +153,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// OpenId discovery documents found at the <see cref="ClaimedIdentifier"/> /// location. /// </summary> - IProviderEndpoint IAuthenticationRequest.Provider { + public IProviderEndpoint Provider { get { return this.endpoint; } } @@ -169,12 +169,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> - /// Gets the extensions that have been added to th request. + /// Gets the extensions that have been added to the request. /// </summary> internal IEnumerable<IOpenIdMessageExtension> AppliedExtensions { get { return this.extensions; } } + /// <summary> + /// Gets the list of extensions for this request. + /// </summary> + internal IList<IOpenIdMessageExtension> Extensions { + get { return this.extensions; } + } + #region IAuthenticationRequest methods /// <summary> @@ -182,17 +189,17 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="arguments">The arguments to add to the request's return_to URI.</param> /// <remarks> - /// <para>Note that these values are NOT protected against tampering in transit. No - /// security-sensitive data should be stored using this method.</para> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// privacy-sensitive data should be stored using this method.</para> /// <para>The values stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArguments"/>.</para> + /// <see cref="IAuthenticationResponse.GetCallbackArguments"/>, which will only return the value + /// if it hasn't been tampered with in transit.</para> /// <para>Since the data set here is sent in the querystring of the request and some /// servers place limits on the size of a request URL, this data should be kept relatively /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> /// </remarks> public void AddCallbackArguments(IDictionary<string, string> arguments) { ErrorUtilities.VerifyArgumentNotNull(arguments, "arguments"); - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); foreach (var pair in arguments) { ErrorUtilities.VerifyArgument(!string.IsNullOrEmpty(pair.Key), MessagingStrings.UnexpectedNullOrEmptyKey); @@ -208,10 +215,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="key">The parameter name.</param> /// <param name="value">The value of the argument.</param> /// <remarks> - /// <para>Note that these values are NOT protected against tampering in transit. No - /// security-sensitive data should be stored using this method.</para> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// privacy-sensitive data should be stored using this method.</para> /// <para>The value stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value + /// if it hasn't been tampered with in transit.</para> /// <para>Since the data set here is sent in the querystring of the request and some /// servers place limits on the size of a request URL, this data should be kept relatively /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> @@ -219,7 +227,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { public void AddCallbackArguments(string key, string value) { ErrorUtilities.VerifyNonZeroLength(key, "key"); ErrorUtilities.VerifyArgumentNotNull(value, "value"); - ErrorUtilities.VerifyOperation(this.RelyingParty.CanSignCallbackArguments, OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); this.returnToArgs.Add(key, value); } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs index 3fd7d20..973687f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/AuthenticationResponseSnapshot.cs @@ -23,6 +23,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private IDictionary<string, string> callbackArguments; /// <summary> + /// The untrusted callback arguments that came with the authentication response. + /// </summary> + private IDictionary<string, string> untrustedCallbackArguments; + + /// <summary> /// Initializes a new instance of the <see cref="AuthenticationResponseSnapshot"/> class. /// </summary> /// <param name="copyFrom">The authentication response to copy from.</param> @@ -34,6 +39,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { this.Status = copyFrom.Status; this.Provider = copyFrom.Provider; this.callbackArguments = copyFrom.GetCallbackArguments(); + this.untrustedCallbackArguments = copyFrom.GetUntrustedCallbackArguments(); } #region IAuthenticationResponse Members @@ -229,6 +235,23 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public IDictionary<string, string> GetUntrustedCallbackArguments() { + // Return a copy so that the caller cannot change the contents. + return new Dictionary<string, string>(this.untrustedCallbackArguments); + } + + /// <summary> /// Gets a callback argument's value that was previously added using /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. /// </summary> @@ -250,6 +273,28 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { return value; } + /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public string GetUntrustedCallbackArgument(string key) { + ErrorUtilities.VerifyArgumentNotNull(key, "key"); + + string value; + this.untrustedCallbackArguments.TryGetValue(key, out value); + return value; + } + #endregion } } diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs index d94af14..45f7f54 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/FailedAuthenticationResponse.cs @@ -128,7 +128,23 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <para>Note that these values are NOT protected against tampering in transit.</para> /// </remarks> public IDictionary<string, string> GetCallbackArguments() { - return new Dictionary<string, string>(); + return EmptyDictionary<string, string>.Instance; + } + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public IDictionary<string, string> GetUntrustedCallbackArguments() { + return EmptyDictionary<string, string>.Instance; } /// <summary> @@ -150,6 +166,24 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public string GetUntrustedCallbackArgument(string key) { + return null; + } + + /// <summary> /// Tries to get an OpenID extension that may be present in the response. /// </summary> /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs index 8414031..c97654a 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationRequest.cs @@ -98,10 +98,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> /// <param name="arguments">The arguments to add to the request's return_to URI. Values must not be null.</param> /// <remarks> - /// <para>Note that these values are NOT protected against tampering in transit. No - /// security-sensitive data should be stored using this method.</para> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// privacy-sensitive data should be stored using this method.</para> /// <para>The values stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArguments"/>.</para> + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value + /// if it can be verified as untampered with in transit.</para> /// <para>Since the data set here is sent in the querystring of the request and some /// servers place limits on the size of a request URL, this data should be kept relatively /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> @@ -114,10 +115,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <param name="key">The parameter name.</param> /// <param name="value">The value of the argument. Must not be null.</param> /// <remarks> - /// <para>Note that these values are NOT protected against tampering in transit. No - /// security-sensitive data should be stored using this method.</para> + /// <para>Note that these values are NOT protected against eavesdropping in transit. No + /// privacy-sensitive data should be stored using this method.</para> /// <para>The value stored here can be retrieved using - /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>.</para> + /// <see cref="IAuthenticationResponse.GetCallbackArgument"/>, which will only return the value + /// if it can be verified as untampered with in transit.</para> /// <para>Since the data set here is sent in the querystring of the request and some /// servers place limits on the size of a request URL, this data should be kept relatively /// small to ensure successful authentication. About 1.5KB is about all that should be stored.</para> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs index cc94de0..fd35a6b 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IAuthenticationResponse.cs @@ -104,27 +104,60 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// The value of the argument, or null if the named parameter could not be found. /// </returns> /// <remarks> - /// <para>This may return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode <c>null</c> is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. /// </remarks> string GetCallbackArgument(string key); /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + string GetUntrustedCallbackArgument(string key); + + /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode an empty dictionary is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Historically an expensive operation.")] + IDictionary<string, string> GetCallbackArguments(); + + /// <summary> /// Gets all the callback arguments that were previously added using /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part /// of the return_to URL. /// </summary> /// <returns>A name-value dictionary. Never null.</returns> /// <remarks> - /// <para>This MAY return any argument on the querystring that came with the authentication response, - /// which may include parameters not explicitly added using - /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>.</para> - /// <para>Note that these values are NOT protected against tampering in transit.</para> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. /// </remarks> [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Historically an expensive operation.")] - IDictionary<string, string> GetCallbackArguments(); // TODO: change this to a property, and return a cached ReadOnlyDictionary + IDictionary<string, string> GetUntrustedCallbackArguments(); /// <summary> /// Tries to get an OpenID extension that may be present in the response. diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs index e7c38db..d0be768 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/IRelyingPartyBehavior.cs @@ -8,11 +8,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Applies a custom security policy to certain OpenID security settings and behaviors. /// </summary> - /// <remarks> - /// BEFORE MARKING THIS INTERFACE PUBLIC: it's very important that we shift the methods to be channel-level - /// rather than facade class level and for the OpenIdChannel to be the one to invoke these methods. - /// </remarks> - internal interface IRelyingPartyBehavior { + public interface IRelyingPartyBehavior { /// <summary> /// Applies a well known set of security requirements to a default set of security settings. /// </summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs index e66ac28..5aa2e24 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/NegativeAuthenticationResponse.cs @@ -159,6 +159,24 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public string GetUntrustedCallbackArgument(string key) { + return null; + } + + /// <summary> /// Gets all the callback arguments that were previously added using /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part /// of the return_to URL. @@ -175,6 +193,22 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public IDictionary<string, string> GetUntrustedCallbackArguments() { + return EmptyDictionary<string, string>.Instance; + } + + /// <summary> /// Tries to get an OpenID extension that may be present in the response. /// </summary> /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs index 34cde25..6a4413f 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.cs @@ -907,12 +907,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { StringBuilder discoveryResultBuilder = new StringBuilder(); discoveryResultBuilder.Append("{"); try { - List<IAuthenticationRequest> requests = this.CreateRequests(userSuppliedIdentifier, true); + List<IAuthenticationRequest> requests = this.CreateRequests(userSuppliedIdentifier, true).Where(req => this.OnLoggingIn(req)).ToList(); if (requests.Count > 0) { discoveryResultBuilder.AppendFormat("claimedIdentifier: {0},", MessagingUtilities.GetSafeJavascriptValue(requests[0].ClaimedIdentifier)); discoveryResultBuilder.Append("requests: ["); foreach (IAuthenticationRequest request in requests) { - this.OnLoggingIn(request); discoveryResultBuilder.Append("{"); discoveryResultBuilder.AppendFormat("endpoint: {0},", MessagingUtilities.GetSafeJavascriptValue(request.Provider.Uri.AbsoluteUri)); request.Mode = AuthenticationRequestMode.Immediate; @@ -995,6 +994,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { writer.WriteBeginTag("span"); writer.WriteAttribute("class", this.CssClass); writer.Write(" style='"); + writer.WriteStyleAttribute("display", "inline-block"); writer.WriteStyleAttribute("position", "relative"); writer.WriteStyleAttribute("font-size", "16px"); writer.Write("'>"); @@ -1088,11 +1088,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Fires the <see cref="LoggingIn"/> event. /// </summary> /// <param name="request">The request.</param> - private void OnLoggingIn(IAuthenticationRequest request) { + /// <returns><c>true</c> if the login should proceed; <c>false</c> otherwise.</returns> + private bool OnLoggingIn(IAuthenticationRequest request) { var loggingIn = this.LoggingIn; if (loggingIn != null) { - loggingIn(this, new OpenIdEventArgs(request)); + var args = new OpenIdEventArgs(request); + loggingIn(this, args); + return !args.Cancel; } + + return true; } /// <summary> @@ -1232,7 +1237,7 @@ if (!openidbox.dnoi_internal.onSubmit()) {{ return false; }} /// requests should be initialized for use in invisible iframes for background authentication.</param> /// <returns>The list of authentication requests, any one of which may be /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>.</returns> - private List<IAuthenticationRequest> CreateRequests(string userSuppliedIdentifier, bool immediate) { + private IEnumerable<IAuthenticationRequest> CreateRequests(string userSuppliedIdentifier, bool immediate) { var requests = new List<IAuthenticationRequest>(); // Approximate the returnTo (either based on the customize property or the page URL) diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js index e13af30..1078003 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdAjaxTextBox.js @@ -126,9 +126,10 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url box.dnoi_internal.authenticationIFrames = new FrameManager(throttle); box.dnoi_internal.constructButton = function(text, tooltip, onclick) { - var button = document.createElement('button'); + var button = document.createElement('input'); button.textContent = text; // Mozilla button.value = text; // IE + button.type = 'button'; button.title = tooltip != null ? tooltip : ''; button.onclick = onclick; button.style.visibility = 'hidden'; @@ -215,6 +216,7 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url }); box.dnoi_internal.openid_logo = box.dnoi_internal.constructIcon(openid_logo_url, null, false, true); box.dnoi_internal.op_logo = box.dnoi_internal.constructIcon('', authenticatedByToolTip, false, false, "16px"); + box.dnoi_internal.op_logo.style.maxWidth = '16px'; box.dnoi_internal.spinner = box.dnoi_internal.constructIcon(spinner_url, busyToolTip, true); box.dnoi_internal.success_icon = box.dnoi_internal.constructIcon(success_icon_url, authenticatedAsToolTip, true); //box.dnoi_internal.failure_icon = box.dnoi_internal.constructIcon(failure_icon_url, authenticationFailedToolTip, true); @@ -247,7 +249,11 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url box.dnoi_internal.op_logo.src = opLogo; box.dnoi_internal.op_logo.style.visibility = 'visible'; box.dnoi_internal.op_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost()); - } else { + } + trace("OP icon size: " + box.dnoi_internal.op_logo.fileSize); + if (opLogo == null || box.dnoi_internal.op_logo.fileSize == -1 /*IE*/ || box.dnoi_internal.op_logo.fileSize === undefined /* FF */) { + trace('recovering from missing OP icon'); + box.dnoi_internal.op_logo.style.visibility = 'hidden'; box.dnoi_internal.openid_logo.style.visibility = 'visible'; box.dnoi_internal.openid_logo.title = box.dnoi_internal.op_logo.originalTitle.replace('{0}', authenticatedBy.getHost()); } @@ -285,8 +291,9 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url } box.dnoi_internal.isBusy = function() { - return box.dnoi_internal.state == 'discovering' || - box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier].busy(); + var lastDiscovery = box.dnoi_internal.authenticationRequests[box.lastDiscoveredIdentifier]; + return box.dnoi_internal.state == 'discovering' || + (lastDiscovery && lastDiscovery.busy()); }; box.dnoi_internal.canAttemptLogin = function() { @@ -516,7 +523,7 @@ function initAjaxOpenId(box, openid_logo_url, dotnetopenid_logo_url, spinner_url trace('iframe hosting ' + self.endpoint + ' now OPENING.'); self.iframe = iframe; //trace('initiating auth attempt with: ' + self.immediate); - return self.immediate; + return self.immediate.toString(); }; this.trySetup = function() { self.abort(); // ensure no concurrent attempts diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs index c6a5476..a090032 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdButton.cs @@ -20,7 +20,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// An ASP.NET control that renders a button that initiates an /// authentication when clicked. /// </summary> - public class OpenIdButton : OpenIdRelyingPartyControlBase { + public class OpenIdButton : OpenIdRelyingPartyControlBase, IPostBackEventHandler { #region Property defaults /// <summary> @@ -28,6 +28,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> private const string TextDefault = "Log in with [Provider]!"; + /// <summary> + /// The default value for the <see cref="PrecreateRequest"/> property. + /// </summary> + private const bool PrecreateRequestDefault = false; + #endregion #region View state keys @@ -42,6 +47,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> private const string ImageUrlViewStateKey = "ImageUrl"; + /// <summary> + /// The key under which the value for the <see cref="PrecreateRequest"/> property will be stored. + /// </summary> + private const string PrecreateRequestViewStateKey = "PrecreateRequest"; + #endregion /// <summary> @@ -79,6 +89,18 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { } /// <summary> + /// Gets or sets a value indicating whether to pre-discover the identifier so + /// the user agent has an immediate redirect. + /// </summary> + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Precreate", Justification = "Breaking change to public API")] + [Bindable(true), Category(OpenIdCategory), DefaultValue(PrecreateRequestDefault)] + [Description("Whether to pre-discover the identifier so the user agent has an immediate redirect.")] + public bool PrecreateRequest { + get { return (bool)(ViewState[PrecreateRequestViewStateKey] ?? PrecreateRequestDefault); } + set { ViewState[PrecreateRequestViewStateKey] = value; } + } + + /// <summary> /// Gets or sets a value indicating when to use a popup window to complete the login experience. /// </summary> /// <value>The default value is <see cref="PopupBehavior.Never"/>.</value> @@ -88,6 +110,25 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { set { ErrorUtilities.VerifySupported(value == base.Popup, OpenIdStrings.PropertyValueNotSupported); } } + #region IPostBackEventHandler Members + + /// <summary> + /// When implemented by a class, enables a server control to process an event raised when a form is posted to the server. + /// </summary> + /// <param name="eventArgument">A <see cref="T:System.String"/> that represents an optional event argument to be passed to the event handler.</param> + public void RaisePostBackEvent(string eventArgument) { + if (!this.PrecreateRequest) { + try { + IAuthenticationRequest request = this.CreateRequests().First(); + request.RedirectToProvider(); + } catch (InvalidOperationException ex) { + throw ErrorUtilities.Wrap(ex, OpenIdStrings.OpenIdEndpointNotFound); + } + } + } + + #endregion + /// <summary> /// Raises the <see cref="E:System.Web.UI.Control.PreRender"/> event. /// </summary> @@ -109,11 +150,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { writer.WriteEncodedText(string.Format(CultureInfo.CurrentCulture, "[{0}]", OpenIdStrings.NoIdentifierSet)); } else { string tooltip = this.Text; - IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); - if (request != null) { - RenderOpenIdMessageTransmissionAsAnchorAttributes(writer, request, tooltip); + if (this.PrecreateRequest && !this.DesignMode) { + IAuthenticationRequest request = this.CreateRequests().FirstOrDefault(); + if (request != null) { + RenderOpenIdMessageTransmissionAsAnchorAttributes(writer, request, tooltip); + } else { + tooltip = OpenIdStrings.OpenIdEndpointNotFound; + } } else { - tooltip = OpenIdStrings.OpenIdEndpointNotFound; + writer.AddAttribute(HtmlTextWriterAttribute.Href, this.Page.ClientScript.GetPostBackClientHyperlink(this, null)); } writer.AddAttribute(HtmlTextWriterAttribute.Title, tooltip); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs index a917e24..e6e31e5 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdMobileTextBox.cs @@ -635,7 +635,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { var response = this.RelyingParty.GetResponse(); if (response != null) { - string persistentString = response.GetCallbackArgument(UsePersistentCookieCallbackKey); + string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); bool persistentBool; if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) { this.UsePersistentCookie = persistentBool; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs index 532f033..1fe6521 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingParty.cs @@ -41,7 +41,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// The name of the key to use in the HttpApplication cache to store the /// instance of <see cref="StandardRelyingPartyApplicationStore"/> to use. /// </summary> - private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.ApplicationStore"; + private const string ApplicationStoreKey = "DotNetOpenAuth.OpenId.RelyingParty.OpenIdRelyingParty.HttpApplicationStore"; /// <summary> /// Backing store for the <see cref="Behaviors"/> property. @@ -129,7 +129,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { Contract.Ensures(Contract.Result<IRelyingPartyApplicationStore>() != null); HttpContext context = HttpContext.Current; - ErrorUtilities.VerifyOperation(context != null, OpenIdStrings.StoreRequiredWhenNoHttpContextAvailable, typeof(IRelyingPartyApplicationStore).Name); + ErrorUtilities.VerifyOperation(context != null, Strings.StoreRequiredWhenNoHttpContextAvailable, typeof(IRelyingPartyApplicationStore).Name); var store = (IRelyingPartyApplicationStore)context.Application[ApplicationStoreKey]; if (store == null) { context.Application.Lock(); @@ -222,7 +222,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets a list of custom behaviors to apply to OpenID actions. /// </summary> - internal ICollection<IRelyingPartyBehavior> Behaviors { + /// <remarks> + /// Adding behaviors can impact the security settings of this <see cref="OpenIdRelyingParty"/> + /// instance in ways that subsequently removing the behaviors will not reverse. + /// </remarks> + public ICollection<IRelyingPartyBehavior> Behaviors { get { return this.behaviors; } } @@ -491,6 +495,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { NegativeAssertionResponse negativeAssertion; IndirectSignedResponse positiveExtensionOnly; if ((positiveAssertion = message as PositiveAssertionResponse) != null) { + if (this.EndpointFilter != null) { + // We need to make sure that this assertion is coming from an endpoint + // that the host deems acceptable. + var providerEndpoint = new SimpleXrdsProviderEndpoint(positiveAssertion); + ErrorUtilities.VerifyProtocol( + this.EndpointFilter(providerEndpoint), + OpenIdStrings.PositiveAssertionFromNonWhitelistedProvider, + providerEndpoint.Uri); + } + var response = new PositiveAuthenticationResponse(positiveAssertion, this); foreach (var behavior in this.Behaviors) { behavior.OnIncomingPositiveAssertion(response); diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs index 5ab8053..da2a9ae 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyAjaxControlBase.cs @@ -151,7 +151,6 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <returns>A sequence of authentication requests, any one of which may be /// used to determine the user's control of the <see cref="IAuthenticationRequest.ClaimedIdentifier"/>.</returns> protected override IEnumerable<IAuthenticationRequest> CreateRequests() { - Contract.Requires(this.Identifier != null, OpenIdStrings.NoIdentifierSet); ErrorUtilities.VerifyOperation(this.Identifier != null, OpenIdStrings.NoIdentifierSet); // We delegate all our logic to another method, since invoking base. methods diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs index 05c16f6..0efabd2 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdRelyingPartyControlBase.cs @@ -48,6 +48,11 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </summary> protected const string BehaviorCategory = "Behavior"; + /// <summary> + /// The "OpenID" category for properties and events. + /// </summary> + protected const string OpenIdCategory = "OpenID"; + #endregion #region Property default values @@ -171,25 +176,25 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// process begins. Offers a chance for the web application to disallow based on /// OpenID URL before redirecting the user to the OpenID Provider. /// </summary> - [Description("Fired after the user clicks the log in button, but before the authentication process begins. Offers a chance for the web application to disallow based on OpenID URL before redirecting the user to the OpenID Provider.")] + [Description("Fired after the user clicks the log in button, but before the authentication process begins. Offers a chance for the web application to disallow based on OpenID URL before redirecting the user to the OpenID Provider."), Category(OpenIdCategory)] public event EventHandler<OpenIdEventArgs> LoggingIn; /// <summary> /// Fired upon completion of a successful login. /// </summary> - [Description("Fired upon completion of a successful login.")] + [Description("Fired upon completion of a successful login."), Category(OpenIdCategory)] public event EventHandler<OpenIdEventArgs> LoggedIn; /// <summary> /// Fired when a login attempt fails. /// </summary> - [Description("Fired when a login attempt fails.")] + [Description("Fired when a login attempt fails."), Category(OpenIdCategory)] public event EventHandler<OpenIdEventArgs> Failed; /// <summary> /// Fired when an authentication attempt is canceled at the OpenID Provider. /// </summary> - [Description("Fired when an authentication attempt is canceled at the OpenID Provider.")] + [Description("Fired when an authentication attempt is canceled at the OpenID Provider."), Category(OpenIdCategory)] public event EventHandler<OpenIdEventArgs> Canceled; #endregion @@ -222,7 +227,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Gets or sets a value indicating whether stateless mode is used. /// </summary> - [Bindable(true), DefaultValue(StatelessDefault), Category(BehaviorCategory)] + [Bindable(true), DefaultValue(StatelessDefault), Category(OpenIdCategory)] [Description("Controls whether stateless mode is used.")] public bool Stateless { get { return (bool)(ViewState[StatelessViewStateKey] ?? StatelessDefault); } @@ -235,7 +240,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "DotNetOpenAuth.OpenId.Realm", Justification = "Using ctor for validation.")] [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(RealmUrlDefault), Category(BehaviorCategory)] + [Bindable(true), DefaultValue(RealmUrlDefault), Category(OpenIdCategory)] [Description("The OpenID Realm of the relying party web site.")] [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] public string RealmUrl { @@ -267,7 +272,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { [SuppressMessage("Microsoft.Usage", "CA2234:PassSystemUriObjectsInsteadOfStrings", Justification = "Bindable property must be simple type")] [SuppressMessage("Microsoft.Usage", "CA1806:DoNotIgnoreMethodResults", MessageId = "System.Uri", Justification = "Using Uri.ctor for validation.")] [SuppressMessage("Microsoft.Design", "CA1056:UriPropertiesShouldNotBeStrings", Justification = "Bindable property must be simple type")] - [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(BehaviorCategory)] + [Bindable(true), DefaultValue(ReturnToUrlDefault), Category(OpenIdCategory)] [Description("The OpenID ReturnTo of the relying party web site.")] [UrlProperty, Editor("System.Web.UI.Design.UrlEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))] public string ReturnToUrl { @@ -321,7 +326,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Gets or sets a value indicating whether to enforce on high security mode, /// which requires the full authentication pipeline to be protected by SSL. /// </summary> - [Bindable(true), DefaultValue(RequireSslDefault), Category(BehaviorCategory)] + [Bindable(true), DefaultValue(RequireSslDefault), Category(OpenIdCategory)] [Description("Turns on high security mode, requiring the full authentication pipeline to be protected by SSL.")] public bool RequireSsl { get { return (bool)(ViewState[RequireSslViewStateKey] ?? RequireSslDefault); } @@ -332,7 +337,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Gets or sets the URL to your privacy policy page that describes how /// claims will be used and/or shared. /// </summary> - [Bindable(true), Category(BehaviorCategory)] + [Bindable(true), Category(OpenIdCategory)] [Description("The OpenID Identifier that this button will use to initiate login.")] [TypeConverter(typeof(IdentifierConverter))] public Identifier Identifier { @@ -454,7 +459,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { if (receiver == null || receiver == this.ClientID) { var response = this.RelyingParty.GetResponse(); if (response != null) { - string persistentString = response.GetCallbackArgument(UsePersistentCookieCallbackKey); + string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); bool persistentBool; if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) { this.UsePersistentCookie = persistentBool; diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs index b7c879e..0723f55 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/OpenIdTextBox.cs @@ -1035,7 +1035,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { var response = this.RelyingParty.GetResponse(); if (response != null) { - string persistentString = response.GetCallbackArgument(UsePersistentCookieCallbackKey); + string persistentString = response.GetUntrustedCallbackArgument(UsePersistentCookieCallbackKey); bool persistentBool; if (persistentString != null && bool.TryParse(persistentString, out persistentBool)) { this.UsePersistentCookie = persistentBool; @@ -1219,7 +1219,16 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private bool IsPopupAppropriate() { Contract.Requires(this.Request != null); - return this.Popup == PopupBehavior.Always || this.Request.Provider.IsExtensionSupported<UIRequest>(); + switch (this.Popup) { + case PopupBehavior.Never: + return false; + case PopupBehavior.Always: + return true; + case PopupBehavior.IfProviderSupported: + return this.Request.Provider.IsExtensionSupported<UIRequest>(); + default: + throw new InternalErrorException(); + } } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs index 13eb1a2..baf30da 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAnonymousResponse.cs @@ -154,13 +154,32 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </remarks> public string GetCallbackArgument(string key) { if (this.response.ReturnToParametersSignatureValidated) { - return this.response.GetReturnToArgument(key); + return this.GetUntrustedCallbackArgument(key); } else { + Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); return null; } } /// <summary> + /// Gets a callback argument's value that was previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/>. + /// </summary> + /// <param name="key">The name of the parameter whose value is sought.</param> + /// <returns> + /// The value of the argument, or null if the named parameter could not be found. + /// </returns> + /// <remarks> + /// Callback parameters are only available even if the RP is in stateless mode, + /// or the callback parameters are otherwise unverifiable as untampered with. + /// Therefore, use this method only when the callback argument is not to be + /// used to make a security-sensitive decision. + /// </remarks> + public string GetUntrustedCallbackArgument(string key) { + return this.response.GetReturnToArgument(key); + } + + /// <summary> /// Gets all the callback arguments that were previously added using /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part /// of the return_to URL. @@ -175,22 +194,40 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// </remarks> public IDictionary<string, string> GetCallbackArguments() { if (this.response.ReturnToParametersSignatureValidated) { - var args = new Dictionary<string, string>(); - - // Return all the return_to arguments, except for the OpenID-supporting ones. - // The only arguments that should be returned here are the ones that the host - // web site adds explicitly. - foreach (string key in this.response.GetReturnToParameterNames().Where(key => !OpenIdRelyingParty.IsOpenIdSupportingParameter(key))) { - args[key] = this.response.GetReturnToArgument(key); - } - - return args; + return this.GetUntrustedCallbackArguments(); } else { + Logger.OpenId.WarnFormat(OpenIdStrings.CallbackArgumentsRequireSecretStore, typeof(IAssociationStore<Uri>).Name, typeof(OpenIdRelyingParty).Name); return EmptyDictionary<string, string>.Instance; } } /// <summary> + /// Gets all the callback arguments that were previously added using + /// <see cref="IAuthenticationRequest.AddCallbackArguments(string, string)"/> or as a natural part + /// of the return_to URL. + /// </summary> + /// <returns>A name-value dictionary. Never null.</returns> + /// <remarks> + /// Callback parameters are only available if they are complete and untampered with + /// since the original request message (as proven by a signature). + /// If the relying party is operating in stateless mode an empty dictionary is always + /// returned since the callback arguments could not be signed to protect against + /// tampering. + /// </remarks> + public IDictionary<string, string> GetUntrustedCallbackArguments() { + var args = new Dictionary<string, string>(); + + // Return all the return_to arguments, except for the OpenID-supporting ones. + // The only arguments that should be returned here are the ones that the host + // web site adds explicitly. + foreach (string key in this.response.GetReturnToParameterNames().Where(key => !OpenIdRelyingParty.IsOpenIdSupportingParameter(key))) { + args[key] = this.response.GetReturnToArgument(key); + } + + return args; + } + + /// <summary> /// Tries to get an OpenID extension that may be present in the response. /// </summary> /// <typeparam name="T">The type of extension to look for in the response message.</typeparam> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs index e3740db..69a6eaa 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/PositiveAuthenticationResponse.cs @@ -135,6 +135,15 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { private void VerifyDiscoveryMatchesAssertion(OpenIdRelyingParty relyingParty) { Logger.OpenId.Debug("Verifying assertion matches identifier discovery results..."); + // Ensure that we abide by the RP's rules regarding RequireSsl for this discovery step. + Identifier claimedId = this.Response.ClaimedIdentifier; + if (relyingParty.SecuritySettings.RequireSsl) { + if (!claimedId.TryRequireSsl(out claimedId)) { + Logger.OpenId.ErrorFormat("This site is configured to accept only SSL-protected OpenIDs, but {0} was asserted and must be rejected.", this.Response.ClaimedIdentifier); + ErrorUtilities.ThrowProtocol(OpenIdStrings.RequireSslNotSatisfiedByAssertedClaimedId, this.Response.ClaimedIdentifier); + } + } + // While it LOOKS like we're performing discovery over HTTP again // Yadis.IdentifierDiscoveryCachePolicy is set to HttpRequestCacheLevel.CacheIfAvailable // which means that the .NET runtime is caching our discoveries for us. This turns out @@ -144,7 +153,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { // is signed by the RP before it's considered reliable. In 1.x stateless mode, this RP // doesn't (and can't) sign its own return_to URL, so its cached discovery information // is merely a hint that must be verified by performing discovery again here. - var discoveryResults = this.Response.ClaimedIdentifier.Discover(relyingParty.WebRequestHandler); + var discoveryResults = claimedId.Discover(relyingParty.WebRequestHandler); ErrorUtilities.VerifyProtocol( discoveryResults.Contains(this.endpoint), OpenIdStrings.IssuedAssertionFailsIdentifierDiscovery, diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs new file mode 100644 index 0000000..912b8f4 --- /dev/null +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/SimpleXrdsProviderEndpoint.cs @@ -0,0 +1,115 @@ +//----------------------------------------------------------------------- +// <copyright file="SimpleXrdsProviderEndpoint.cs" company="Andrew Arnott"> +// Copyright (c) Andrew Arnott. All rights reserved. +// </copyright> +//----------------------------------------------------------------------- + +namespace DotNetOpenAuth.OpenId.RelyingParty { + using System; + using DotNetOpenAuth.OpenId.Messages; + + /// <summary> + /// A very simple IXrdsProviderEndpoint implementation for verifying that all positive + /// assertions (particularly unsolicited ones) are received from OP endpoints that + /// are deemed permissible by the host RP. + /// </summary> + internal class SimpleXrdsProviderEndpoint : IXrdsProviderEndpoint { + /// <summary> + /// Initializes a new instance of the <see cref="SimpleXrdsProviderEndpoint"/> class. + /// </summary> + /// <param name="positiveAssertion">The positive assertion.</param> + internal SimpleXrdsProviderEndpoint(PositiveAssertionResponse positiveAssertion) { + this.Uri = positiveAssertion.ProviderEndpoint; + this.Version = positiveAssertion.Version; + } + + #region IXrdsProviderEndpoint Properties + + /// <summary> + /// Gets the priority associated with this service that may have been given + /// in the XRDS document. + /// </summary> + public int? ServicePriority { + get { return null; } + } + + /// <summary> + /// Gets the priority associated with the service endpoint URL. + /// </summary> + /// <remarks> + /// When sorting by priority, this property should be considered second after + /// <see cref="ServicePriority"/>. + /// </remarks> + public int? UriPriority { + get { return null; } + } + + #endregion + + #region IProviderEndpoint Members + + /// <summary> + /// Gets the detected version of OpenID implemented by the Provider. + /// </summary> + public Version Version { get; private set; } + + /// <summary> + /// Gets the URL that the OpenID Provider receives authentication requests at. + /// </summary> + /// <value></value> + public Uri Uri { get; private set; } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <typeparam name="T">The extension whose support is being queried.</typeparam> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + public bool IsExtensionSupported<T>() where T : DotNetOpenAuth.OpenId.Messages.IOpenIdMessageExtension, new() { + throw new NotSupportedException(); + } + + /// <summary> + /// Checks whether the OpenId Identifier claims support for a given extension. + /// </summary> + /// <param name="extensionType">The extension whose support is being queried.</param> + /// <returns> + /// True if support for the extension is advertised. False otherwise. + /// </returns> + /// <remarks> + /// Note that a true or false return value is no guarantee of a Provider's + /// support for or lack of support for an extension. The return value is + /// determined by how the authenticating user filled out his/her XRDS document only. + /// The only way to be sure of support for a given extension is to include + /// the extension in the request and see if a response comes back for that extension. + /// </remarks> + public bool IsExtensionSupported(Type extensionType) { + throw new NotSupportedException(); + } + + #endregion + + #region IXrdsProviderEndpoint Methods + + /// <summary> + /// Checks for the presence of a given Type URI in an XRDS service. + /// </summary> + /// <param name="typeUri">The type URI to check for.</param> + /// <returns> + /// <c>true</c> if the service type uri is present; <c>false</c> otherwise. + /// </returns> + public bool IsTypeUriPresent(string typeUri) { + throw new NotSupportedException(); + } + + #endregion + } +} diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs index 96dd8d8..8499178 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/StandardRelyingPartyApplicationStore.cs @@ -14,7 +14,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// An in-memory store for Relying Parties, suitable for single server, single process /// ASP.NET web sites. /// </summary> - internal class StandardRelyingPartyApplicationStore : IRelyingPartyApplicationStore { + public class StandardRelyingPartyApplicationStore : IRelyingPartyApplicationStore { /// <summary> /// The nonce store to use. /// </summary> @@ -28,7 +28,7 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// Initializes a new instance of the <see cref="StandardRelyingPartyApplicationStore"/> class. /// </summary> - internal StandardRelyingPartyApplicationStore() { + public StandardRelyingPartyApplicationStore() { this.nonceStore = new NonceMemoryStore(DotNetOpenAuthSection.Configuration.OpenId.MaxAuthenticationTime); this.associationStore = new AssociationMemoryStore<Uri>(); } @@ -48,12 +48,12 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// Gets the best association (the one with the longest remaining life) for a given key. /// </summary> /// <param name="distinguishingFactor">The Uri (for relying parties) or Smart/Dumb (for Providers).</param> - /// <param name="securitySettings">The security settings.</param> + /// <param name="securityRequirements">The security settings.</param> /// <returns> /// The requested association, or null if no unexpired <see cref="Association"/>s exist for the given key. /// </returns> - public Association GetAssociation(Uri distinguishingFactor, SecuritySettings securitySettings) { - return this.associationStore.GetAssociation(distinguishingFactor, securitySettings); + public Association GetAssociation(Uri distinguishingFactor, SecuritySettings securityRequirements) { + return this.associationStore.GetAssociation(distinguishingFactor, securityRequirements); } /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs b/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs index bd45842..2e2ab61 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingParty/WellKnownProviders.cs @@ -5,6 +5,8 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.OpenId.RelyingParty { + using System.Diagnostics.CodeAnalysis; + /// <summary> /// Common OpenID Provider Identifiers. /// </summary> @@ -12,16 +14,19 @@ namespace DotNetOpenAuth.OpenId.RelyingParty { /// <summary> /// The Yahoo OP Identifier. /// </summary> + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")] public static readonly Identifier Yahoo = "https://me.yahoo.com/"; /// <summary> /// The Google OP Identifier. /// </summary> + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")] public static readonly Identifier Google = "https://www.google.com/accounts/o8/id"; /// <summary> /// The MyOpenID OP Identifier. /// </summary> + [SuppressMessage("Microsoft.Security", "CA2104:DoNotDeclareReadOnlyMutableReferenceTypes", Justification = "Immutable type")] public static readonly Identifier MyOpenId = "https://www.myopenid.com/"; /// <summary> diff --git a/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs b/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs index 112506b..6b82966 100644 --- a/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs +++ b/src/DotNetOpenAuth/OpenId/RelyingPartyDescription.cs @@ -9,6 +9,7 @@ namespace DotNetOpenAuth.OpenId { using System.Collections.Generic; using System.Linq; using System.Text; + using DotNetOpenAuth.Messaging; /// <summary> /// A description of some OpenID Relying Party endpoint. @@ -25,6 +26,9 @@ namespace DotNetOpenAuth.OpenId { /// The Type URIs of supported services advertised on a relying party's XRDS document. /// </param> internal RelyingPartyEndpointDescription(Uri returnTo, string[] supportedServiceTypeUris) { + ErrorUtilities.VerifyArgumentNotNull(returnTo, "returnTo"); + ErrorUtilities.VerifyArgumentNotNull(supportedServiceTypeUris, "supportedServiceTypeUris"); + this.ReturnToEndpoint = returnTo; this.Protocol = GetProtocolFromServices(supportedServiceTypeUris); } diff --git a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs index 615dc9c..512200d 100644 --- a/src/DotNetOpenAuth/OpenId/UriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/UriIdentifier.cs @@ -12,6 +12,7 @@ namespace DotNetOpenAuth.OpenId { using System.Linq; using System.Text.RegularExpressions; using System.Web.UI.HtmlControls; + using System.Xml; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId.RelyingParty; using DotNetOpenAuth.Xrds; @@ -129,6 +130,9 @@ namespace DotNetOpenAuth.OpenId { /// </exception> public override bool Equals(object obj) { UriIdentifier other = obj as UriIdentifier; + if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison + other = Identifier.Parse(obj.ToString()) as UriIdentifier; + } if (other == null) { return false; } @@ -215,14 +219,18 @@ namespace DotNetOpenAuth.OpenId { DiscoveryResult yadisResult = Yadis.Discover(requestHandler, this, IsDiscoverySecureEndToEnd); if (yadisResult != null) { if (yadisResult.IsXrds) { - XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); - var xrdsEndpoints = xrds.CreateServiceEndpoints(yadisResult.NormalizedUri, this); + try { + XrdsDocument xrds = new XrdsDocument(yadisResult.ResponseText); + var xrdsEndpoints = xrds.CreateServiceEndpoints(yadisResult.NormalizedUri, this); - // Filter out insecure endpoints if high security is required. - if (IsDiscoverySecureEndToEnd) { - xrdsEndpoints = xrdsEndpoints.Where(se => se.IsSecure); + // Filter out insecure endpoints if high security is required. + if (IsDiscoverySecureEndToEnd) { + xrdsEndpoints = xrdsEndpoints.Where(se => se.IsSecure); + } + endpoints.AddRange(xrdsEndpoints); + } catch (XmlException ex) { + Logger.Yadis.Error("Error while parsing the XRDS document. Falling back to HTML discovery.", ex); } - endpoints.AddRange(xrdsEndpoints); } // Failing YADIS discovery of an XRDS document, we try HTML discovery. @@ -324,7 +332,7 @@ namespace DotNetOpenAuth.OpenId { foreach (var protocol in Protocol.AllPracticalVersions) { // rel attributes are supposed to be interpreted with case INsensitivity, // and is a space-delimited list of values. (http://www.htmlhelp.com/reference/html40/values.html#linktypes) - var serverLinkTag = linkTags.FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryProviderKey) + @"\b", RegexOptions.IgnoreCase)); + var serverLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryProviderKey) + @"\b", RegexOptions.IgnoreCase)); if (serverLinkTag == null) { continue; } @@ -333,7 +341,7 @@ namespace DotNetOpenAuth.OpenId { if (Uri.TryCreate(serverLinkTag.Href, UriKind.Absolute, out providerEndpoint)) { // See if a LocalId tag of the discovered version exists Identifier providerLocalIdentifier = null; - var delegateLinkTag = linkTags.FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryLocalIdKey) + @"\b", RegexOptions.IgnoreCase)); + var delegateLinkTag = linkTags.WithAttribute("rel").FirstOrDefault(tag => Regex.IsMatch(tag.Attributes["rel"], @"\b" + Regex.Escape(protocol.HtmlDiscoveryLocalIdKey) + @"\b", RegexOptions.IgnoreCase)); if (delegateLinkTag != null) { if (Identifier.IsValid(delegateLinkTag.Href)) { providerLocalIdentifier = delegateLinkTag.Href; diff --git a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs index a85c33c..c659982 100644 --- a/src/DotNetOpenAuth/OpenId/XriIdentifier.cs +++ b/src/DotNetOpenAuth/OpenId/XriIdentifier.cs @@ -130,6 +130,9 @@ namespace DotNetOpenAuth.OpenId { /// </exception> public override bool Equals(object obj) { XriIdentifier other = obj as XriIdentifier; + if (obj != null && other == null && Identifier.EqualityOnStrings) { // test hook to enable MockIdentifier comparison + other = Identifier.Parse(obj.ToString()) as XriIdentifier; + } if (other == null) { return false; } diff --git a/src/DotNetOpenAuth/Strings.Designer.cs b/src/DotNetOpenAuth/Strings.Designer.cs index 43fec22..38c89f7 100644 --- a/src/DotNetOpenAuth/Strings.Designer.cs +++ b/src/DotNetOpenAuth/Strings.Designer.cs @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ // <auto-generated> // This code was generated by a tool. -// Runtime Version:2.0.50727.4918 +// Runtime Version:2.0.50727.4927 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -70,6 +70,15 @@ namespace DotNetOpenAuth { } /// <summary> + /// Looks up a localized string similar to No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.. + /// </summary> + internal static string StoreRequiredWhenNoHttpContextAvailable { + get { + return ResourceManager.GetString("StoreRequiredWhenNoHttpContextAvailable", resourceCulture); + } + } + + /// <summary> /// Looks up a localized string similar to The configuration XAML reference to {0} requires a current HttpContext to resolve.. /// </summary> internal static string ConfigurationXamlReferenceRequiresHttpContext { diff --git a/src/DotNetOpenAuth/Strings.resx b/src/DotNetOpenAuth/Strings.resx index bbfa162..a7f080d 100644 --- a/src/DotNetOpenAuth/Strings.resx +++ b/src/DotNetOpenAuth/Strings.resx @@ -120,7 +120,10 @@ <data name="ConfigurationTypeMustBePublic" xml:space="preserve"> <value>The configuration-specified type {0} must be public, and is not.</value> </data> + <data name="StoreRequiredWhenNoHttpContextAvailable" xml:space="preserve"> + <value>No current HttpContext was detected, so an {0} instance must be explicitly provided or specified in the .config file. Call the constructor overload that takes an {0}.</value> + </data> <data name="ConfigurationXamlReferenceRequiresHttpContext" xml:space="preserve"> <value>The configuration XAML reference to {0} requires a current HttpContext to resolve.</value> </data> -</root>
\ No newline at end of file +</root> diff --git a/src/DotNetOpenAuth/Xrds/XrdElement.cs b/src/DotNetOpenAuth/Xrds/XrdElement.cs index 63675b7..2c03da8 100644 --- a/src/DotNetOpenAuth/Xrds/XrdElement.cs +++ b/src/DotNetOpenAuth/Xrds/XrdElement.cs @@ -96,6 +96,13 @@ namespace DotNetOpenAuth.Xrds { } /// <summary> + /// Gets the services that would be discoverable at an RP for the UI extension icon. + /// </summary> + public IEnumerable<ServiceElement> OpenIdRelyingPartyIcons { + get { return this.SearchForServiceTypeUris(p => "http://specs.openid.net/extensions/ui/icon"); } + } + + /// <summary> /// Gets an enumeration of all Service/URI elements, sorted in priority order. /// </summary> public IEnumerable<UriElement> ServiceUris { diff --git a/src/DotNetOpenAuth/Yadis/HtmlParser.cs b/src/DotNetOpenAuth/Yadis/HtmlParser.cs index 5a00da8..406cb4b 100644 --- a/src/DotNetOpenAuth/Yadis/HtmlParser.cs +++ b/src/DotNetOpenAuth/Yadis/HtmlParser.cs @@ -5,8 +5,11 @@ //----------------------------------------------------------------------- namespace DotNetOpenAuth.Yadis { + using System; using System.Collections.Generic; + using System.Diagnostics.Contracts; using System.Globalization; + using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Web; @@ -88,6 +91,19 @@ namespace DotNetOpenAuth.Yadis { } /// <summary> + /// Filters a list of controls based on presence of an attribute. + /// </summary> + /// <typeparam name="T">The type of HTML controls being filtered.</typeparam> + /// <param name="sequence">The sequence.</param> + /// <param name="attribute">The attribute.</param> + /// <returns>A filtered sequence of attributes.</returns> + internal static IEnumerable<T> WithAttribute<T>(this IEnumerable<T> sequence, string attribute) where T : HtmlControl { + Contract.Requires(sequence != null); + Contract.Requires(!String.IsNullOrEmpty(attribute)); + return sequence.Where(tag => tag.Attributes[attribute] != null); + } + + /// <summary> /// Generates a regular expression that will find a given HTML tag. /// </summary> /// <param name="tagName">Name of the tag.</param> diff --git a/src/DotNetOpenAuth/Yadis/Yadis.cs b/src/DotNetOpenAuth/Yadis/Yadis.cs index 0caffb6..14aea62 100644 --- a/src/DotNetOpenAuth/Yadis/Yadis.cs +++ b/src/DotNetOpenAuth/Yadis/Yadis.cs @@ -11,6 +11,7 @@ namespace DotNetOpenAuth.Yadis { using System.Net.Cache; using System.Web.UI.HtmlControls; using System.Xml; + using DotNetOpenAuth.Configuration; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.OpenId; using DotNetOpenAuth.Xrds; @@ -30,7 +31,7 @@ namespace DotNetOpenAuth.Yadis { #if DEBUG internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.BypassCache); #else - internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(HttpRequestCacheLevel.CacheIfAvailable); + internal static readonly RequestCachePolicy IdentifierDiscoveryCachePolicy = new HttpRequestCachePolicy(DotNetOpenAuthSection.Configuration.OpenId.CacheDiscovery ? HttpRequestCacheLevel.CacheIfAvailable : HttpRequestCacheLevel.BypassCache); #endif /// <summary> @@ -80,7 +81,7 @@ namespace DotNetOpenAuth.Yadis { Logger.Yadis.DebugFormat("{0} found in HTTP header. Preparing to pull XRDS from {1}", HeaderName, url); } } - if (url == null && response.ContentType != null && response.ContentType.MediaType == ContentTypes.Html) { + if (url == null && response.ContentType != null && (response.ContentType.MediaType == ContentTypes.Html || response.ContentType.MediaType == ContentTypes.XHtml)) { url = FindYadisDocumentLocationInHtmlMetaTags(response.GetResponseString()); if (url != null) { Logger.Yadis.DebugFormat("{0} found in HTML Http-Equiv tag. Preparing to pull XRDS from {1}", HeaderName, url); diff --git a/src/version.txt b/src/version.txt index 944880f..b347b11 100644 --- a/src/version.txt +++ b/src/version.txt @@ -1 +1 @@ -3.2.0 +3.2.3 |