diff options
author | Andrew Arnott <andrewarnott@gmail.com> | 2008-09-24 20:57:49 -0700 |
---|---|---|
committer | Andrew <andrewarnott@gmail.com> | 2008-09-24 20:57:49 -0700 |
commit | de6a705de31c11ee5892c94cc9afc5c8e49a90ce (patch) | |
tree | ace1f83f585c1e84da5f768cb6ed4dbeecb42d57 /src | |
parent | 22341a07b0ba0dc685bb859b0ed82c22fc3c09db (diff) | |
download | DotNetOpenAuth-de6a705de31c11ee5892c94cc9afc5c8e49a90ce.zip DotNetOpenAuth-de6a705de31c11ee5892c94cc9afc5c8e49a90ce.tar.gz DotNetOpenAuth-de6a705de31c11ee5892c94cc9afc5c8e49a90ce.tar.bz2 |
Added a scenario test from Appendix A (incomplete but passing so far).
Included in this change are a lot of fixes and additional implementation.
Diffstat (limited to 'src')
20 files changed, 500 insertions, 58 deletions
diff --git a/src/DotNetOAuth.Test/ChannelElements/OAuthChannelTests.cs b/src/DotNetOAuth.Test/ChannelElements/OAuthChannelTests.cs index 5917d2c..048e1d8 100644 --- a/src/DotNetOAuth.Test/ChannelElements/OAuthChannelTests.cs +++ b/src/DotNetOAuth.Test/ChannelElements/OAuthChannelTests.cs @@ -32,7 +32,7 @@ namespace DotNetOAuth.Test.ChannelElements { this.webRequestHandler = new TestWebRequestHandler();
this.signingElement = new RsaSha1SigningBindingElement();
- this.nonceStore = new NonceMemoryStore();
+ this.nonceStore = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge);
this.channel = new OAuthChannel(this.signingElement, this.nonceStore, new TestMessageTypeProvider(), this.webRequestHandler);
}
diff --git a/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj b/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj index 2859ffc..6551281 100644 --- a/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj +++ b/src/DotNetOAuth.Test/DotNetOAuth.Test.csproj @@ -88,7 +88,9 @@ <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Messaging\ResponseTests.cs" />
<Compile Include="ProtocolTests.cs" />
- <Compile Include="Scenarios.cs" />
+ <Compile Include="Scenarios\AppendixScenarios.cs" />
+ <Compile Include="Scenarios\CoordinatingOAuthChannel.cs" />
+ <Compile Include="Scenarios\Coordinator.cs" />
<Compile Include="ServiceProviderTests.cs" />
<Compile Include="TestBase.cs" />
<Compile Include="UriUtilTests.cs" />
diff --git a/src/DotNetOAuth.Test/Scenarios.cs b/src/DotNetOAuth.Test/Scenarios.cs deleted file mode 100644 index 6718b47..0000000 --- a/src/DotNetOAuth.Test/Scenarios.cs +++ /dev/null @@ -1,30 +0,0 @@ -//-----------------------------------------------------------------------
-// <copyright file="Scenarios.cs" company="Andrew Arnott">
-// Copyright (c) Andrew Arnott. All rights reserved.
-// </copyright>
-//-----------------------------------------------------------------------
-
-namespace DotNetOAuth.Test {
- using DotNetOAuth.Messaging;
- using Microsoft.VisualStudio.TestTools.UnitTesting;
-
- [TestClass]
- public class Scenarios : TestBase {
- [TestMethod]
- public void SpecAppendixAExample() {
- ServiceProvider sp = new ServiceProvider {
- RequestTokenEndpoint = new ServiceProviderEndpoint("https://photos.example.net/request_token", HttpDeliveryMethod.PostRequest),
- UserAuthorizationEndpoint = new ServiceProviderEndpoint("http://photos.example.net/authorize", HttpDeliveryMethod.GetRequest),
- AccessTokenEndpoint = new ServiceProviderEndpoint("https://photos.example.net/access_token", HttpDeliveryMethod.PostRequest),
- };
-
- Consumer consumer = new Consumer {
- ConsumerKey = "dpf43f3p2l4k3l03",
- ConsumerSecret = "kd94hf93k423kf44",
- ServiceProvider = sp,
- };
-
- consumer.RequestUserAuthorization();
- }
- }
-}
diff --git a/src/DotNetOAuth.Test/Scenarios/AppendixScenarios.cs b/src/DotNetOAuth.Test/Scenarios/AppendixScenarios.cs new file mode 100644 index 0000000..328255b --- /dev/null +++ b/src/DotNetOAuth.Test/Scenarios/AppendixScenarios.cs @@ -0,0 +1,53 @@ +//-----------------------------------------------------------------------
+// <copyright file="Scenarios.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test {
+ using System;
+ using System.Collections.Specialized;
+ using System.IO;
+ using System.Net;
+ using System.Web;
+ using DotNetOAuth.Messaging;
+ using Microsoft.VisualStudio.TestTools.UnitTesting;
+ using DotNetOAuth.Test.Scenarios;
+ using DotNetOAuth.ChannelElements;
+
+ [TestClass]
+ public class AppendixScenarios : TestBase {
+ [TestMethod]
+ public void SpecAppendixAExample() {
+ ServiceProvider sp = new ServiceProvider {
+ RequestTokenEndpoint = new ServiceProviderEndpoint("https://photos.example.net/request_token", HttpDeliveryMethod.PostRequest),
+ UserAuthorizationEndpoint = new ServiceProviderEndpoint("http://photos.example.net/authorize", HttpDeliveryMethod.GetRequest),
+ AccessTokenEndpoint = new ServiceProviderEndpoint("https://photos.example.net/access_token", HttpDeliveryMethod.PostRequest),
+ };
+
+ Coordinator coordinator = new Coordinator(
+ channel => {
+ Consumer consumer = new Consumer {
+ Channel = channel,
+ ConsumerKey = "dpf43f3p2l4k3l03",
+ ConsumerSecret = "kd94hf93k423kf44",
+ ServiceProvider = sp,
+ };
+
+ consumer.RequestUserAuthorization(new Uri("http://printer.example.com/request_token_ready"));
+ var accessTokenMessage = consumer.ProcessUserAuthorization();
+ },
+ channel => {
+ sp.Channel = channel;
+ var requestTokenMessage = sp.ReadTokenRequest();
+ sp.SendUnauthorizedTokenResponse("hh5s93j4hdidpola", "hdhd0244k9j7ao03");
+ var authRequest = sp.ReadAuthorizationRequest();
+ sp.SendAuthorizationResponse(authRequest);
+ var accessRequest = sp.ReadAccessTokenRequest();
+ sp.SendAccessToken("nnch734d00sl2jdk", "pfkkdhi9sl3r4s00");
+ });
+ coordinator.SigningElement = new PlainTextSigningBindingElement();
+ coordinator.Start();
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Scenarios/CoordinatingOAuthChannel.cs b/src/DotNetOAuth.Test/Scenarios/CoordinatingOAuthChannel.cs new file mode 100644 index 0000000..4c34a0d --- /dev/null +++ b/src/DotNetOAuth.Test/Scenarios/CoordinatingOAuthChannel.cs @@ -0,0 +1,72 @@ +//-----------------------------------------------------------------------
+// <copyright file="CoordinatingOAuthChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Scenarios {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.ChannelElements;
+ using DotNetOAuth.Messaging.Bindings;
+ using DotNetOAuth.Messaging;
+using System.Threading;
+
+ /// <summary>
+ /// A special channel used in test simulations to pass messages directly between two parties.
+ /// </summary>
+ internal class CoordinatingOAuthChannel : OAuthChannel {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="CoordinatingOAuthChannel"/> class for Consumers.
+ /// </summary>
+ /// <param name="signingBindingElement">
+ /// The signing element for the Consumer to use. Null for the Service Provider.
+ /// </param>
+ internal CoordinatingOAuthChannel(SigningBindingElementBase signingBindingElement)
+ : base(signingBindingElement, new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge), new OAuthMessageTypeProvider(), new Mocks.TestWebRequestHandler()) {
+ }
+
+ /// <summary>
+ /// Gets or sets the coordinating channel used by the other party.
+ /// </summary>
+ internal CoordinatingOAuthChannel RemoteChannel { get; set; }
+
+ private EventWaitHandle incomingMessageSignal = new AutoResetEvent(false);
+ private IProtocolMessage incomingMessage;
+
+ protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) {
+ // Drop the outgoing message in the other channel's in-slot and let them know it's there.
+ RemoteChannel.incomingMessage = request;
+ RemoteChannel.incomingMessageSignal.Set();
+ // Now wait for a response...
+ return AwaitIncomingMessage();
+ }
+
+ protected override void SendDirectMessageResponse(IProtocolMessage response) {
+ RemoteChannel.incomingMessage = response;
+ RemoteChannel.incomingMessageSignal.Set();
+ }
+
+ protected override void SendIndirectMessage(IDirectedProtocolMessage message) {
+ // In this mock transport, direct and indirect messages are the same.
+ SendDirectMessageResponse(message);
+ }
+
+ protected override HttpRequestInfo GetRequestFromContext() {
+ return new HttpRequestInfo(AwaitIncomingMessage());
+ }
+
+ protected override IProtocolMessage ReadFromRequestInternal(HttpRequestInfo request) {
+ return request.Message;
+ }
+
+ private IProtocolMessage AwaitIncomingMessage() {
+ this.incomingMessageSignal.WaitOne();
+ IProtocolMessage response = this.incomingMessage;
+ this.incomingMessage = null;
+ return response;
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/Scenarios/Coordinator.cs b/src/DotNetOAuth.Test/Scenarios/Coordinator.cs new file mode 100644 index 0000000..42cda0b --- /dev/null +++ b/src/DotNetOAuth.Test/Scenarios/Coordinator.cs @@ -0,0 +1,70 @@ +//-----------------------------------------------------------------------
+// <copyright file="Coordinator.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.Test.Scenarios {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using System.Threading;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.ChannelElements;
+
+ /// <summary>
+ /// Runs a Consumer and Service Provider simultaneously so they can interact in a full simulation.
+ /// </summary>
+ internal class Coordinator {
+ Actor consumerAction;
+ Actor serviceProviderAction;
+
+ /// <summary>Initializes a new instance of the <see cref="Coordinator"/> class.</summary>
+ /// <param name="consumerAction">The code path of the Consumer.</param>
+ /// <param name="serviceProviderAction">The code path of the Service Provider.</param>
+ internal Coordinator(Actor consumerAction, Actor serviceProviderAction) {
+ if (consumerAction == null) {
+ throw new ArgumentNullException("consumerAction");
+ }
+ if (serviceProviderAction == null) {
+ throw new ArgumentNullException("serviceProviderAction");
+ }
+
+ this.consumerAction = consumerAction;
+ this.serviceProviderAction = serviceProviderAction;
+ }
+
+ /// <summary>
+ /// Gets or sets the signing element the Consumer channel should use.
+ /// </summary>
+ /// <remarks>
+ /// The Service Provider never signs a message, so no property is necessary for that.
+ /// </remarks>
+ internal SigningBindingElementBase SigningElement { get; set; }
+
+ internal delegate void Actor(OAuthChannel channel);
+
+ /// <summary>
+ /// Starts the simulation.
+ /// </summary>
+ internal void Start() {
+ if (SigningElement == null) {
+ throw new InvalidOperationException("SigningElement must be set first.");
+ }
+
+ // Prepare channels that will pass messages directly back and forth.
+ CoordinatingOAuthChannel consumerChannel = new CoordinatingOAuthChannel(SigningElement);
+ CoordinatingOAuthChannel serviceProviderChannel = new CoordinatingOAuthChannel(SigningElement);
+ consumerChannel.RemoteChannel = serviceProviderChannel;
+ serviceProviderChannel.RemoteChannel = consumerChannel;
+
+ Thread consumerThread = new Thread(() => { consumerAction(consumerChannel); });
+ Thread serviceProviderThread = new Thread(() => { serviceProviderAction(serviceProviderChannel); });
+ consumerThread.Start();
+ serviceProviderThread.Start();
+ consumerThread.Join();
+ serviceProviderThread.Join();
+ }
+ }
+}
diff --git a/src/DotNetOAuth.Test/TestBase.cs b/src/DotNetOAuth.Test/TestBase.cs index 9fce27c..5877a08 100644 --- a/src/DotNetOAuth.Test/TestBase.cs +++ b/src/DotNetOAuth.Test/TestBase.cs @@ -5,7 +5,12 @@ //-----------------------------------------------------------------------
namespace DotNetOAuth.Test {
+ using System;
+ using System.Collections.Generic;
using System.Reflection;
+ using System.Threading;
+ using DotNetOAuth.ChannelElements;
+ using DotNetOAuth.Messaging;
using log4net;
using Microsoft.VisualStudio.TestTools.UnitTesting;
diff --git a/src/DotNetOAuth/ChannelElements/HmacSha1SigningBindingElement.cs b/src/DotNetOAuth/ChannelElements/HmacSha1SigningBindingElement.cs index 131ef79..793d000 100644 --- a/src/DotNetOAuth/ChannelElements/HmacSha1SigningBindingElement.cs +++ b/src/DotNetOAuth/ChannelElements/HmacSha1SigningBindingElement.cs @@ -29,7 +29,7 @@ namespace DotNetOAuth.ChannelElements { /// This method signs the message per OAuth 1.0 section 9.2.
/// </remarks>
protected override string GetSignature(ITamperResistantOAuthMessage message) {
- string key = Uri.EscapeDataString(message.ConsumerSecret) + "&" + Uri.EscapeDataString(message.TokenSecret);
+ string key = GetConsumerAndTokenSecretString(message);
HashAlgorithm hasher = new HMACSHA1(Encoding.ASCII.GetBytes(key));
byte[] digest = hasher.ComputeHash(Encoding.ASCII.GetBytes(ConstructSignatureBaseString(message)));
return Uri.EscapeDataString(Convert.ToBase64String(digest));
diff --git a/src/DotNetOAuth/ChannelElements/PlainTextSigningBindingElement.cs b/src/DotNetOAuth/ChannelElements/PlainTextSigningBindingElement.cs index cc01678..3509ece 100644 --- a/src/DotNetOAuth/ChannelElements/PlainTextSigningBindingElement.cs +++ b/src/DotNetOAuth/ChannelElements/PlainTextSigningBindingElement.cs @@ -32,11 +32,7 @@ namespace DotNetOAuth.ChannelElements { /// This method signs the message according to OAuth 1.0 section 9.4.1.
/// </remarks>
protected override string GetSignature(ITamperResistantOAuthMessage message) {
- StringBuilder builder = new StringBuilder();
- builder.Append(Uri.EscapeDataString(message.ConsumerSecret));
- builder.Append("&");
- builder.Append(Uri.EscapeDataString(message.TokenSecret));
- return Uri.EscapeDataString(builder.ToString());
+ return Uri.EscapeDataString(GetConsumerAndTokenSecretString(message));
}
}
}
diff --git a/src/DotNetOAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOAuth/ChannelElements/SigningBindingElementBase.cs index 8a9db7e..f4663b1 100644 --- a/src/DotNetOAuth/ChannelElements/SigningBindingElementBase.cs +++ b/src/DotNetOAuth/ChannelElements/SigningBindingElementBase.cs @@ -148,6 +148,23 @@ namespace DotNetOAuth.ChannelElements { protected abstract string GetSignature(ITamperResistantOAuthMessage message);
/// <summary>
+ /// Gets the ConsumerSecret&TokenSecret" string, allowing either property to be empty or null.
+ /// </summary>
+ /// <param name="message">The message to extract the secrets from.</param>
+ /// <returns>The concatenated string.</returns>
+ protected string GetConsumerAndTokenSecretString(ITamperResistantOAuthMessage message) {
+ StringBuilder builder = new StringBuilder();
+ if (!string.IsNullOrEmpty(message.ConsumerSecret)) {
+ builder.Append(Uri.EscapeDataString(message.ConsumerSecret));
+ }
+ builder.Append("&");
+ if (!string.IsNullOrEmpty(message.TokenSecret)) {
+ builder.Append(Uri.EscapeDataString(message.TokenSecret));
+ }
+ return builder.ToString();
+ }
+
+ /// <summary>
/// Sorts parameters according to OAuth signature base string rules.
/// </summary>
/// <param name="left">The first parameter to compare.</param>
diff --git a/src/DotNetOAuth/Consumer.cs b/src/DotNetOAuth/Consumer.cs index a3af2bc..0c55fdf 100644 --- a/src/DotNetOAuth/Consumer.cs +++ b/src/DotNetOAuth/Consumer.cs @@ -5,16 +5,25 @@ //-----------------------------------------------------------------------
namespace DotNetOAuth {
+ using System;
+ using System.Net;
+ using DotNetOAuth.ChannelElements;
using DotNetOAuth.Messages;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
/// <summary>
/// A website or application that uses OAuth to access the Service Provider on behalf of the User.
/// </summary>
- internal class Consumer {
+ public class Consumer {
/// <summary>
/// Initializes a new instance of the <see cref="Consumer"/> class.
/// </summary>
- internal Consumer() {
+ public Consumer() {
+ this.WebRequestHandler = new StandardWebRequestHandler();
+ SigningBindingElementBase signingElement = new PlainTextSigningBindingElement();
+ INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge);
+ this.Channel = new OAuthChannel(signingElement, store, new OAuthMessageTypeProvider(), this.WebRequestHandler);
}
/// <summary>
@@ -33,11 +42,56 @@ namespace DotNetOAuth { public ServiceProvider ServiceProvider { get; set; }
/// <summary>
+ /// Gets the pending user agent redirect based message to be sent as an HttpResponse.
+ /// </summary>
+ public Response PendingRequest { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the object that processes <see cref="HttpWebRequest"/>s.
+ /// </summary>
+ /// <remarks>
+ /// This defaults to a straightforward implementation, but can be set
+ /// to a mock object for testing purposes.
+ /// </remarks>
+ internal IWebRequestHandler WebRequestHandler { get; set; }
+
+ /// <summary>
+ /// Gets or sets the channel to use for sending/receiving messages.
+ /// </summary>
+ internal OAuthChannel Channel { get; set; }
+
+ /// <summary>
/// Begins an OAuth authorization request and redirects the user to the Service Provider
/// to provide that authorization.
/// </summary>
- public void RequestUserAuthorization() {
- RequestTokenMessage requestToken = new RequestTokenMessage(ServiceProvider.RequestTokenEndpoint);
+ public void RequestUserAuthorization(Uri callback) {
+ // Obtain an unauthorized request token.
+ var requestToken = new RequestTokenMessage(ServiceProvider.RequestTokenEndpoint) {
+ ConsumerKey = this.ConsumerKey,
+ ConsumerSecret = this.ConsumerSecret,
+ };
+ var requestTokenResponse = this.Channel.Request<UnauthorizedRequestTokenMessage>(requestToken);
+
+ // Request user authorization.
+ var requestAuthorization = new DirectUserToServiceProviderMessage(ServiceProvider.UserAuthorizationEndpoint) {
+ Callback = callback,
+ RequestToken = requestTokenResponse.RequestToken,
+ };
+ this.Channel.Send(requestAuthorization);
+ this.PendingRequest = this.Channel.DequeueIndirectOrResponseMessage();
+ }
+
+ internal GrantAccessTokenMessage ProcessUserAuthorization() {
+ var authorizationMessage = this.Channel.ReadFromRequest<DirectUserToConsumerMessage>();
+
+ // Exchange request token for access token.
+ var requestAccess = new RequestAccessTokenMessage(ServiceProvider.AccessTokenEndpoint) {
+ RequestToken = authorizationMessage.RequestToken,
+ ConsumerKey = this.ConsumerKey,
+ ConsumerSecret = this.ConsumerSecret,
+ };
+ var grantAccess = this.Channel.Request<GrantAccessTokenMessage>(requestAccess);
+ return grantAccess;
}
}
}
diff --git a/src/DotNetOAuth/Messages/DirectUserToConsumerMessage.cs b/src/DotNetOAuth/Messages/DirectUserToConsumerMessage.cs index 8cd6abb..6b8251a 100644 --- a/src/DotNetOAuth/Messages/DirectUserToConsumerMessage.cs +++ b/src/DotNetOAuth/Messages/DirectUserToConsumerMessage.cs @@ -16,8 +16,8 @@ namespace DotNetOAuth.Messages { /// Initializes a new instance of the <see cref="DirectUserToConsumerMessage"/> class.
/// </summary>
/// <param name="consumer">The URI of the Consumer endpoint to send this message to.</param>
- internal DirectUserToConsumerMessage(ServiceProviderEndpoint consumer)
- : base(MessageProtection.None, MessageTransport.Indirect, consumer) {
+ internal DirectUserToConsumerMessage(Uri consumer)
+ : base(MessageProtection.None, MessageTransport.Indirect, new ServiceProviderEndpoint(consumer, HttpDeliveryMethod.GetRequest)) {
}
/// <summary>
diff --git a/src/DotNetOAuth/Messages/DirectUserToServiceProviderMessage.cs b/src/DotNetOAuth/Messages/DirectUserToServiceProviderMessage.cs index 629c0e2..a222c0d 100644 --- a/src/DotNetOAuth/Messages/DirectUserToServiceProviderMessage.cs +++ b/src/DotNetOAuth/Messages/DirectUserToServiceProviderMessage.cs @@ -36,6 +36,6 @@ namespace DotNetOAuth.Messages { /// to the Consumer when Obtaining User Authorization is complete. Optional.
/// </summary>
[MessagePart(Name = "oauth_callback", IsRequired = false)]
- public string Callback { get; set; }
+ public Uri Callback { get; set; }
}
}
diff --git a/src/DotNetOAuth/Messages/SignedMessageBase.cs b/src/DotNetOAuth/Messages/SignedMessageBase.cs index f490b26..460a509 100644 --- a/src/DotNetOAuth/Messages/SignedMessageBase.cs +++ b/src/DotNetOAuth/Messages/SignedMessageBase.cs @@ -23,7 +23,7 @@ namespace DotNetOAuth.Messages { /// <summary>
/// The number of seconds since 1/1/1970, consistent with the OAuth timestamp requirement.
/// </summary>
- [MessagePart("oauth_timestamp")]
+ [MessagePart("oauth_timestamp", IsRequired = true)]
private long timestamp;
/// <summary>
@@ -48,20 +48,20 @@ namespace DotNetOAuth.Messages { /// <summary>
/// Gets or sets the signature method used to sign the request.
/// </summary>
- [MessagePart("oauth_signature_method")]
+ [MessagePart("oauth_signature_method", IsRequired = true)]
string ITamperResistantOAuthMessage.SignatureMethod { get; set; }
/// <summary>
/// Gets or sets the Token Secret used to sign the message.
/// Only applicable to Consumer.
/// </summary>
- string ITamperResistantOAuthMessage.TokenSecret { get; set; }
+ public string TokenSecret { get; set; }
/// <summary>
/// Gets or sets the Consumer Secret used to sign the message.
/// Only applicable to Consumer.
/// </summary>
- string ITamperResistantOAuthMessage.ConsumerSecret { get; set; }
+ public string ConsumerSecret { get; set; }
/// <summary>
/// Gets or sets the HTTP method that will be used to transmit the message.
@@ -82,7 +82,7 @@ namespace DotNetOAuth.Messages { /// <summary>
/// Gets or sets the message signature.
/// </summary>
- [MessagePart("oauth_signature")]
+ [MessagePart("oauth_signature", IsRequired = true)]
string ITamperResistantProtocolMessage.Signature { get; set; }
#endregion
@@ -104,7 +104,7 @@ namespace DotNetOAuth.Messages { /// <summary>
/// Gets or sets the message nonce used for replay detection.
/// </summary>
- [MessagePart("oauth_nonce")]
+ [MessagePart("oauth_nonce", IsRequired = true)]
string IReplayProtectedProtocolMessage.Nonce { get; set; }
#endregion
diff --git a/src/DotNetOAuth/Messaging/Bindings/NonceMemoryStore.cs b/src/DotNetOAuth/Messaging/Bindings/NonceMemoryStore.cs index 29e1547..05e0f13 100644 --- a/src/DotNetOAuth/Messaging/Bindings/NonceMemoryStore.cs +++ b/src/DotNetOAuth/Messaging/Bindings/NonceMemoryStore.cs @@ -16,6 +16,22 @@ namespace DotNetOAuth.Messaging.Bindings { /// NOT for web farms.
/// </summary>
internal class NonceMemoryStore : INonceStore {
+ /// <summary>
+ /// The maximum age a message can be before it is discarded.
+ /// </summary>
+ /// <remarks>
+ /// This is useful for knowing how long used nonces must be retained.
+ /// </remarks>
+ private readonly TimeSpan maximumMessageAge;
+
+ /// <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;
+ }
+
#region INonceStore Members
/// <summary>
@@ -40,7 +56,9 @@ namespace DotNetOAuth.Messaging.Bindings { /// <see cref="StandardExpirationBindingElement.MaximumMessageAge"/> property.
/// </remarks>
public bool StoreNonce(string nonce, DateTime timestamp) {
- throw new NotImplementedException();
+ // TODO: implement actual nonce checking.
+ Logger.Warn("Nonce checking not implemented yet.");
+ return true;
}
#endregion
diff --git a/src/DotNetOAuth/Messaging/Channel.cs b/src/DotNetOAuth/Messaging/Channel.cs index d0ccccd..95efbae 100644 --- a/src/DotNetOAuth/Messaging/Channel.cs +++ b/src/DotNetOAuth/Messaging/Channel.cs @@ -166,11 +166,36 @@ namespace DotNetOAuth.Messaging { /// </remarks>
/// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
internal IProtocolMessage ReadFromRequest() {
- if (HttpContext.Current == null) {
- throw new InvalidOperationException(MessagingStrings.HttpContextRequired);
+ return this.ReadFromRequest(GetRequestFromContext());
+ }
+
+ internal TREQUEST ReadFromRequest<TREQUEST>()
+ where TREQUEST : class, IProtocolMessage {
+ return this.ReadFromRequest<TREQUEST>(GetRequestFromContext());
+ }
+
+ protected internal TREQUEST ReadFromRequest<TREQUEST>(HttpRequestInfo httpRequest)
+ where TREQUEST : class, IProtocolMessage {
+ IProtocolMessage request = this.ReadFromRequest(httpRequest);
+ if (request == null) {
+ throw new ProtocolException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.ExpectedMessageNotReceived,
+ typeof(TREQUEST)));
}
- return this.ReadFromRequest(new HttpRequestInfo(HttpContext.Current.Request));
+ var expectedRequest = request as TREQUEST;
+ if (expectedRequest == null) {
+ throw new ProtocolException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.UnexpectedMessageReceived,
+ typeof(TREQUEST),
+ request.GetType()));
+ }
+
+ return expectedRequest;
}
/// <summary>
@@ -187,6 +212,30 @@ namespace DotNetOAuth.Messaging { return requestMessage;
}
+ protected internal TRESPONSE Request<TRESPONSE>(IDirectedProtocolMessage request)
+ where TRESPONSE : class, IProtocolMessage {
+ IProtocolMessage response = this.Request(request);
+ if (response == null) {
+ throw new ProtocolException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.ExpectedMessageNotReceived,
+ typeof(TRESPONSE)));
+ }
+
+ var expectedResponse = response as TRESPONSE;
+ if (expectedResponse == null) {
+ throw new ProtocolException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.UnexpectedMessageReceived,
+ typeof(TRESPONSE),
+ response.GetType()));
+ }
+
+ return expectedResponse;
+ }
+
/// <summary>
/// Sends a direct message to a remote party and waits for the response.
/// </summary>
@@ -218,6 +267,22 @@ namespace DotNetOAuth.Messaging { }
/// <summary>
+ /// Gets the current HTTP request being processed.
+ /// </summary>
+ /// <returns>The HttpRequestInfo for the current request.</returns>
+ /// <remarks>
+ /// Requires an HttpContext.Current context.
+ /// </remarks>
+ /// <exception cref="InvalidOperationException">Thrown when <see cref="HttpContext.Current"/> is null.</exception>
+ protected virtual HttpRequestInfo GetRequestFromContext() {
+ if (HttpContext.Current == null) {
+ throw new InvalidOperationException(MessagingStrings.HttpContextRequired);
+ }
+
+ return new HttpRequestInfo(HttpContext.Current.Request);
+ }
+
+ /// <summary>
/// Gets the protocol message that may be embedded in the given HTTP request.
/// </summary>
/// <param name="request">The request to search for an embedded message.</param>
diff --git a/src/DotNetOAuth/Messaging/HttpRequestInfo.cs b/src/DotNetOAuth/Messaging/HttpRequestInfo.cs index 65b5dcb..bce9ccb 100644 --- a/src/DotNetOAuth/Messaging/HttpRequestInfo.cs +++ b/src/DotNetOAuth/Messaging/HttpRequestInfo.cs @@ -56,6 +56,19 @@ namespace DotNetOAuth.Messaging { }
/// <summary>
+ /// Initializes a new instance of the <see cref="HttpRequestInfo"/> class.
+ /// </summary>
+ /// <param name="message">The message being passed in through a mock transport.</param>
+ internal HttpRequestInfo(IProtocolMessage message) {
+ this.Message = message;
+ }
+
+ /// <summary>
+ /// Gets the message that is being sent over a mock transport (for testing).
+ /// </summary>
+ internal IProtocolMessage Message { get; private set; }
+
+ /// <summary>
/// Gets or sets the verb in the request (i.e. GET, POST, etc.)
/// </summary>
internal string HttpMethod { get; set; }
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs index cf1ceb0..11a9edd 100644 --- a/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs +++ b/src/DotNetOAuth/Messaging/MessagingStrings.Designer.cs @@ -124,6 +124,15 @@ namespace DotNetOAuth.Messaging { }
/// <summary>
+ /// Looks up a localized string similar to Expected message {0} but received no recognizable message..
+ /// </summary>
+ internal static string ExpectedMessageNotReceived {
+ get {
+ return ResourceManager.GetString("ExpectedMessageNotReceived", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The message expired at {0} and it is now {1}..
/// </summary>
internal static string ExpiredMessage {
@@ -268,6 +277,15 @@ namespace DotNetOAuth.Messaging { }
/// <summary>
+ /// Looks up a localized string similar to Expected message {0} but received {1} instead..
+ /// </summary>
+ internal static string UnexpectedMessageReceived {
+ get {
+ return ResourceManager.GetString("UnexpectedMessageReceived", resourceCulture);
+ }
+ }
+
+ /// <summary>
/// Looks up a localized string similar to The type {0} or a derived type was expected, but {1} was given..
/// </summary>
internal static string UnexpectedType {
diff --git a/src/DotNetOAuth/Messaging/MessagingStrings.resx b/src/DotNetOAuth/Messaging/MessagingStrings.resx index b36a550..2bfe46f 100644 --- a/src/DotNetOAuth/Messaging/MessagingStrings.resx +++ b/src/DotNetOAuth/Messaging/MessagingStrings.resx @@ -138,6 +138,9 @@ <data name="ExceptionNotConstructedForTransit" xml:space="preserve">
<value>This exception was not constructed with a root request message that caused it.</value>
</data>
+ <data name="ExpectedMessageNotReceived" xml:space="preserve">
+ <value>Expected message {0} but received no recognizable message.</value>
+ </data>
<data name="ExpiredMessage" xml:space="preserve">
<value>The message expired at {0} and it is now {1}.</value>
</data>
@@ -186,6 +189,9 @@ <data name="TooManyBindingsOfferingSameProtection" xml:space="preserve">
<value>Expected at most 1 binding element offering the {0} protection, but found {1}.</value>
</data>
+ <data name="UnexpectedMessageReceived" xml:space="preserve">
+ <value>Expected message {0} but received {1} instead.</value>
+ </data>
<data name="UnexpectedType" xml:space="preserve">
<value>The type {0} or a derived type was expected, but {1} was given.</value>
</data>
diff --git a/src/DotNetOAuth/ServiceProvider.cs b/src/DotNetOAuth/ServiceProvider.cs index 812a6a8..217d064 100644 --- a/src/DotNetOAuth/ServiceProvider.cs +++ b/src/DotNetOAuth/ServiceProvider.cs @@ -6,9 +6,11 @@ namespace DotNetOAuth {
using System;
- using System.Collections.Generic;
- using System.Text;
using System.Web;
+ using DotNetOAuth.ChannelElements;
+ using DotNetOAuth.Messages;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
/// <summary>
/// A web application that allows access via OAuth.
@@ -21,13 +23,22 @@ namespace DotNetOAuth { /// <item>Any additional request parameters that the Service Provider requires in order to obtain a Token. Service Provider specific parameters MUST NOT begin with oauth_.</item>
/// </list>
/// </remarks>
- internal class ServiceProvider {
+ public class ServiceProvider {
/// <summary>
/// The field used to store the value of the <see cref="RequestTokenEndpoint"/> property.
/// </summary>
private ServiceProviderEndpoint requestTokenEndpoint;
/// <summary>
+ /// Initializes a new instance of the <see cref="ServiceProvider"/> class.
+ /// </summary>
+ public ServiceProvider() {
+ SigningBindingElementBase signingElement = new PlainTextSigningBindingElement();
+ INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge);
+ this.Channel = new OAuthChannel(signingElement, store);
+ }
+
+ /// <summary>
/// Gets or sets the URL used to obtain an unauthorized Request Token,
/// described in Section 6.1 (Obtaining an Unauthorized Request Token).
/// </summary>
@@ -68,5 +79,77 @@ namespace DotNetOAuth { /// This is the URL that <see cref="Messages.RequestAccessTokenMessage"/> messages are directed to.
/// </remarks>
public ServiceProviderEndpoint AccessTokenEndpoint { get; set; }
+
+ /// <summary>
+ /// Gets or sets the channel to use for sending/receiving messages.
+ /// </summary>
+ internal OAuthChannel Channel { get; set; }
+
+ /// <summary>
+ /// Gets the pending user agent redirect based message to be sent as an HttpResponse.
+ /// </summary>
+ public Response PendingRequest { get; private set; }
+
+ internal RequestTokenMessage ReadTokenRequest() {
+ return this.Channel.ReadFromRequest<RequestTokenMessage>();
+ }
+
+ internal RequestTokenMessage ReadTokenRequest(HttpRequest request) {
+ return this.ReadTokenRequest(new HttpRequestInfo(request));
+ }
+
+ internal RequestTokenMessage ReadTokenRequest(HttpRequestInfo request) {
+ return this.Channel.ReadFromRequest<RequestTokenMessage>(request);
+ }
+
+ internal void SendUnauthorizedTokenResponse(string token, string secret) {
+ UnauthorizedRequestTokenMessage response = new UnauthorizedRequestTokenMessage {
+ RequestToken = token,
+ TokenSecret = secret,
+ };
+
+ this.Channel.Send(response);
+ }
+
+ internal DirectUserToServiceProviderMessage ReadAuthorizationRequest() {
+ return this.Channel.ReadFromRequest<DirectUserToServiceProviderMessage>();
+ }
+
+ internal DirectUserToServiceProviderMessage ReadAuthorizationRequest(HttpRequest request) {
+ return this.ReadAuthorizationRequest(new HttpRequestInfo(request));
+ }
+
+ internal DirectUserToServiceProviderMessage ReadAuthorizationRequest(HttpRequestInfo request) {
+ return this.Channel.ReadFromRequest<DirectUserToServiceProviderMessage>(request);
+ }
+
+ internal void SendAuthorizationResponse(DirectUserToServiceProviderMessage request) {
+ var authorization = new DirectUserToConsumerMessage(request.Callback) {
+ RequestToken = request.RequestToken,
+ };
+ this.Channel.Send(authorization);
+ this.PendingRequest = this.Channel.DequeueIndirectOrResponseMessage();
+ }
+
+ internal RequestAccessTokenMessage ReadAccessTokenRequest() {
+ return this.Channel.ReadFromRequest<RequestAccessTokenMessage>();
+ }
+
+ internal RequestAccessTokenMessage ReadAccessTokenRequest(HttpRequest request) {
+ return this.ReadAccessTokenRequest(new HttpRequestInfo(request));
+ }
+
+ internal RequestAccessTokenMessage ReadAccessTokenRequest(HttpRequestInfo request) {
+ return this.Channel.ReadFromRequest<RequestAccessTokenMessage>(request);
+ }
+
+ internal void SendAccessToken(string accessToken, string tokenSecret) {
+ var grantAccess = new GrantAccessTokenMessage {
+ AccessToken = accessToken,
+ TokenSecret = tokenSecret,
+ };
+
+ this.Channel.Send(grantAccess);
+ }
}
}
|