diff options
Diffstat (limited to 'src')
8 files changed, 92 insertions, 16 deletions
diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs index 2596bc5..fcdb5e8 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/HmacSha1SigningBindingElementTests.cs @@ -14,7 +14,7 @@ namespace DotNetOpenAuth.Test.ChannelElements { public class HmacSha1SigningBindingElementTests : MessagingTestBase { [TestMethod] public void SignatureTest() { - UnauthorizedTokenRequest message = SigningBindingElementBaseTests.CreateTestRequestTokenMessage(this.MessageDescriptions); + UnauthorizedTokenRequest message = SigningBindingElementBaseTests.CreateTestRequestTokenMessage(this.MessageDescriptions, null); HmacSha1SigningBindingElement_Accessor hmac = new HmacSha1SigningBindingElement_Accessor(); hmac.Channel = new TestChannel(this.MessageDescriptions); diff --git a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs index e890b6f..cff46af 100644 --- a/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs +++ b/src/DotNetOpenAuth.Test/OAuth/ChannelElements/SigningBindingElementBaseTests.cs @@ -15,15 +15,45 @@ namespace DotNetOpenAuth.Test.ChannelElements { public class SigningBindingElementBaseTests : MessagingTestBase { [TestMethod] public void BaseSignatureStringTest() { - UnauthorizedTokenRequest message = CreateTestRequestTokenMessage(this.MessageDescriptions); + // Tests a message sent by HTTP GET, with no query string included in the endpoint. + UnauthorizedTokenRequest message = CreateTestRequestTokenMessage( + this.MessageDescriptions, + new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest)); + Assert.AreEqual( + "GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_consumer_key%3Dnerdbank.org%26oauth_nonce%3Dfe4045a3f0efdd1e019fa8f8ae3f5c38%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1222665749%26oauth_version%3D1.0%26scope%3Dhttp%253A%252F%252Fwww.google.com%252Fm8%252Ffeeds%252F", + SigningBindingElementBase_Accessor.ConstructSignatureBaseString(message, MessageDictionary_Accessor.AttachShadow(this.MessageDescriptions.GetAccessor(message)))); + + // Test HTTP GET with an attached query string. We're elevating the scope parameter to the query string + // and removing it from the extradata dictionary. This should NOT affect the base signature string. + message = CreateTestRequestTokenMessage( + this.MessageDescriptions, + new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.google.com/m8/feeds/", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest)); + message.ExtraData.Remove("scope"); // remove it from ExtraData since we put it in the URL + Assert.AreEqual( + "GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_consumer_key%3Dnerdbank.org%26oauth_nonce%3Dfe4045a3f0efdd1e019fa8f8ae3f5c38%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1222665749%26oauth_version%3D1.0%26scope%3Dhttp%253A%252F%252Fwww.google.com%252Fm8%252Ffeeds%252F", + SigningBindingElementBase_Accessor.ConstructSignatureBaseString(message, MessageDictionary_Accessor.AttachShadow(this.MessageDescriptions.GetAccessor(message)))); + + // Test HTTP POST, with query string as well + message = CreateTestRequestTokenMessage( + this.MessageDescriptions, + new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.google.com/m8/feeds/", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.PostRequest)); + message.ExtraData.Remove("scope"); // remove it from ExtraData since we put it in the URL + Assert.AreEqual( + "GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_consumer_key%3Dnerdbank.org%26oauth_nonce%3Dfe4045a3f0efdd1e019fa8f8ae3f5c38%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1222665749%26oauth_version%3D1.0%26scope%3Dhttp%253A%252F%252Fwww.google.com%252Fm8%252Ffeeds%252F", + SigningBindingElementBase_Accessor.ConstructSignatureBaseString(message, MessageDictionary_Accessor.AttachShadow(this.MessageDescriptions.GetAccessor(message)))); + // Test HTTP POST, with query string, but not using the Authorization header + message = CreateTestRequestTokenMessage( + this.MessageDescriptions, + new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken?scope=http://www.google.com/m8/feeds/", HttpDeliveryMethods.PostRequest)); + message.ExtraData.Remove("scope"); // remove it from ExtraData since we put it in the URL Assert.AreEqual( "GET&https%3A%2F%2Fwww.google.com%2Faccounts%2FOAuthGetRequestToken&oauth_consumer_key%3Dnerdbank.org%26oauth_nonce%3Dfe4045a3f0efdd1e019fa8f8ae3f5c38%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1222665749%26oauth_version%3D1.0%26scope%3Dhttp%253A%252F%252Fwww.google.com%252Fm8%252Ffeeds%252F", SigningBindingElementBase_Accessor.ConstructSignatureBaseString(message, MessageDictionary_Accessor.AttachShadow(this.MessageDescriptions.GetAccessor(message)))); } - internal static UnauthorizedTokenRequest CreateTestRequestTokenMessage(MessageDescriptionCollection messageDescriptions) { - MessageReceivingEndpoint endpoint = new MessageReceivingEndpoint("https://www.google.com/accounts/OAuthGetRequestToken", HttpDeliveryMethods.AuthorizationHeaderRequest | HttpDeliveryMethods.GetRequest); + 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); message.ConsumerKey = "nerdbank.org"; ((ITamperResistantOAuthMessage)message).ConsumerSecret = "nerdbanksecret"; diff --git a/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/FetchResponseTests.cs b/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/FetchResponseTests.cs index d467186..9d209a9 100644 --- a/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/FetchResponseTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/Extensions/AttributeExchange/FetchResponseTests.cs @@ -4,7 +4,7 @@ // </copyright> //----------------------------------------------------------------------- -namespace DotNetOpenId.Test.OpenId.Extensions { +namespace DotNetOpenAuth.Test.OpenId.Extensions { using System; using System.IO; using DotNetOpenAuth.OpenId.Extensions.AttributeExchange; diff --git a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs index 53df6c8..3e599e9 100644 --- a/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs +++ b/src/DotNetOpenAuth.Test/OpenId/IdentifierTests.cs @@ -22,7 +22,7 @@ namespace DotNetOpenAuth.Test.OpenId { public void TryParseNoThrow() { Identifier id; Assert.IsFalse(Identifier.TryParse(null, out id)); - Assert.IsFalse(Identifier.TryParse("", out id)); + Assert.IsFalse(Identifier.TryParse(string.Empty, out id)); } [TestMethod] diff --git a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs index d5ba346..d1fc10b 100644 --- a/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs +++ b/src/DotNetOpenAuth/OAuth/ChannelElements/SigningBindingElementBase.cs @@ -7,9 +7,11 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { using System; using System.Collections.Generic; + using System.Collections.Specialized; using System.Diagnostics.Contracts; using System.Globalization; using System.Text; + using System.Web; using DotNetOpenAuth.Messaging; using DotNetOpenAuth.Messaging.Bindings; using DotNetOpenAuth.Messaging.Reflection; @@ -164,13 +166,29 @@ namespace DotNetOpenAuth.OAuth.ChannelElements { signatureBaseStringElements.Add(message.HttpMethod.ToUpperInvariant()); + var encodedDictionary = OAuthChannel.GetUriEscapedParameters(messageDictionary); + encodedDictionary.Remove("oauth_signature"); + if (message.Recipient.Query != null) { + // It seeems to me a deviation from the OAuth 1.0 spec to be willing to scrape the query + // for parameters on anything but GET requests, but Google does it so to interop we must + // as well. Besides, it seems more secure to sign everything if it's there. + NameValueCollection nvc = HttpUtility.ParseQueryString(message.Recipient.Query); + foreach (string key in nvc) { + encodedDictionary.Add(Uri.EscapeDataString(key), Uri.EscapeDataString(nvc[key])); + } + } else if (message.HttpMethod == "POST") { + // If the HttpWebRequest that we're sending out has a content-type header + // of application/x-www-form-urlencoded, we should be parsing out those parameters + // and adding them to this dictionary as well. + // But at this point we don't have access to the HttpWebRequest (design flaw?) + // TODO: figure this out. + } + UriBuilder endpoint = new UriBuilder(message.Recipient); endpoint.Query = null; endpoint.Fragment = null; signatureBaseStringElements.Add(endpoint.Uri.AbsoluteUri); - var encodedDictionary = OAuthChannel.GetUriEscapedParameters(messageDictionary); - encodedDictionary.Remove("oauth_signature"); var sortedKeyValueList = new List<KeyValuePair<string, string>>(encodedDictionary); sortedKeyValueList.Sort(SignatureBaseStringParameterComparer); StringBuilder paramBuilder = new StringBuilder(); diff --git a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs index a8a702b..122e7ee 100644 --- a/src/DotNetOpenAuth/OAuth/ServiceProvider.cs +++ b/src/DotNetOpenAuth/OAuth/ServiceProvider.cs @@ -271,23 +271,44 @@ namespace DotNetOpenAuth.OAuth { /// <param name="request">The Consumer's original authorization request.</param> /// <returns> /// The message to send to the Consumer using <see cref="Channel"/> if one is necessary. - /// Null if the Consumer did not request a callback. + /// Null if the Consumer did not request a callback as part of the authorization request. /// </returns> [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")] public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request) { + Contract.Requires(request != null); ErrorUtilities.VerifyArgumentNotNull(request, "request"); if (request.Callback != null) { - var authorization = new UserAuthorizationResponse(request.Callback) { - RequestToken = request.RequestToken, - }; - return authorization; + return this.PrepareAuthorizationResponse(request, request.Callback); } else { return null; } } /// <summary> + /// Prepares the message to send back to the consumer following proper authorization of + /// a token by an interactive user at the Service Provider's web site. + /// </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> + /// <returns> + /// The message to send to the Consumer using <see cref="Channel"/>. + /// </returns> + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Consistent user experience with instance.")] + public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request, Uri callback) { + Contract.Requires(request != null); + Contract.Requires(callback != null); + ErrorUtilities.VerifyArgumentNotNull(request, "request"); + ErrorUtilities.VerifyArgumentNotNull(callback, "callback"); + + var authorization = new UserAuthorizationResponse(request.Callback) { + RequestToken = request.RequestToken, + }; + return authorization; + } + + /// <summary> /// Gets the incoming request to exchange an authorized token for an access token. /// </summary> /// <returns>The incoming request, or null if no OAuth message was attached.</returns> diff --git a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs index d35dcd1..16d8f74 100644 --- a/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs +++ b/src/DotNetOpenAuth/OpenId/HmacShaAssociation.cs @@ -8,6 +8,7 @@ namespace DotNetOpenAuth.OpenId { using System; using System.Collections.Generic; using System.Diagnostics; + using System.Globalization; using System.Linq; using System.Security.Cryptography; using DotNetOpenAuth.Messaging; @@ -148,13 +149,19 @@ namespace DotNetOpenAuth.OpenId { ErrorUtilities.VerifyNonZeroLength(associationType, "associationType"); ErrorUtilities.VerifyArgumentNotNull(securitySettings, "securitySettings"); + int secretLength = GetSecretLength(protocol, associationType); + // Generate the handle. It must be unique, and preferably unpredictable, // so we use a time element and a random data element to generate it. string uniq = MessagingUtilities.GetCryptoRandomDataAsBase64(4); - string handle = "{" + associationType + "}{" + DateTime.UtcNow.Ticks + "}{" + uniq + "}"; + string handle = string.Format( + CultureInfo.InvariantCulture, + "{{{0}}}{{{1}}}{{{2}}}", + DateTime.UtcNow.Ticks, + uniq, + secretLength); // Generate the secret that will be used for signing - int secretLength = GetSecretLength(protocol, associationType); byte[] secret = MessagingUtilities.GetCryptoRandomData(secretLength); TimeSpan lifetime; diff --git a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs index f9f638a..6a5c0a8 100644 --- a/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs +++ b/src/DotNetOpenAuth/OpenId/Messages/AssociateRequest.cs @@ -40,7 +40,7 @@ namespace DotNetOpenAuth.OpenId.Messages { /// </summary> /// <value>Value: A valid association session type from Section 8.4 (Association Session Types). </value> /// <remarks>Note: Unless using transport layer encryption, "no-encryption" MUST NOT be used. </remarks> - [MessagePart("openid.session_type", IsRequired = true, AllowEmpty = true)] + [MessagePart("openid.session_type", IsRequired = false, AllowEmpty = true)] [MessagePart("openid.session_type", IsRequired = true, AllowEmpty = false, MinVersion = "2.0")] internal string SessionType { get; set; } |