diff options
Diffstat (limited to 'src/DotNetOAuth/OAuth')
35 files changed, 3521 insertions, 0 deletions
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs b/src/DotNetOAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs new file mode 100644 index 0000000..a0878aa --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs @@ -0,0 +1,48 @@ +//-----------------------------------------------------------------------
+// <copyright file="HmacSha1SigningBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A binding element that signs outgoing messages and verifies the signature on incoming messages.
+ /// </summary>
+ public class HmacSha1SigningBindingElement : SigningBindingElementBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="HmacSha1SigningBindingElement"/> class
+ /// </summary>
+ public HmacSha1SigningBindingElement()
+ : base("HMAC-SHA1") {
+ }
+
+ /// <summary>
+ /// Calculates a signature for a given message.
+ /// </summary>
+ /// <param name="message">The message to sign.</param>
+ /// <returns>The signature for the message.</returns>
+ /// <remarks>
+ /// This method signs the message per OAuth 1.0 section 9.2.
+ /// </remarks>
+ protected override string GetSignature(ITamperResistantOAuthMessage message) {
+ string key = GetConsumerAndTokenSecretString(message);
+ HashAlgorithm hasher = new HMACSHA1(Encoding.ASCII.GetBytes(key));
+ string baseString = ConstructSignatureBaseString(message);
+ byte[] digest = hasher.ComputeHash(Encoding.ASCII.GetBytes(baseString));
+ return Convert.ToBase64String(digest);
+ }
+
+ /// <summary>
+ /// Clones this instance.
+ /// </summary>
+ /// <returns>A new instance of the binding element.</returns>
+ protected override ITamperProtectionChannelBindingElement Clone() {
+ return new HmacSha1SigningBindingElement();
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/IOAuthDirectedMessage.cs b/src/DotNetOAuth/OAuth/ChannelElements/IOAuthDirectedMessage.cs new file mode 100644 index 0000000..9151464 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/IOAuthDirectedMessage.cs @@ -0,0 +1,25 @@ +//-----------------------------------------------------------------------
+// <copyright file="IOAuthDirectedMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// Additional properties that apply specifically to OAuth messages.
+ /// </summary>
+ public interface IOAuthDirectedMessage : IDirectedProtocolMessage {
+ /// <summary>
+ /// Gets the preferred method of transport for the message.
+ /// </summary>
+ HttpDeliveryMethods HttpMethods { get; }
+
+ /// <summary>
+ /// Gets or sets the URL of the intended receiver of this message.
+ /// </summary>
+ new Uri Recipient { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs b/src/DotNetOAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs new file mode 100644 index 0000000..aaba53b --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs @@ -0,0 +1,41 @@ +//-----------------------------------------------------------------------
+// <copyright file="ITamperResistantOAuthMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System.Collections.Generic;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// An interface that OAuth messages implement to support signing.
+ /// </summary>
+ public interface ITamperResistantOAuthMessage : IOAuthDirectedMessage, ITamperResistantProtocolMessage {
+ /// <summary>
+ /// Gets or sets the method used to sign the message.
+ /// </summary>
+ string SignatureMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Token Secret used to sign the message.
+ /// </summary>
+ string TokenSecret { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Consumer key.
+ /// </summary>
+ string ConsumerKey { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Consumer Secret used to sign the message.
+ /// </summary>
+ string ConsumerSecret { get; set; }
+
+ /// <summary>
+ /// Gets or sets the HTTP method that will be used to transmit the message.
+ /// </summary>
+ string HttpMethod { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/ITokenGenerator.cs b/src/DotNetOAuth/OAuth/ChannelElements/ITokenGenerator.cs new file mode 100644 index 0000000..888ba05 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/ITokenGenerator.cs @@ -0,0 +1,40 @@ +//-----------------------------------------------------------------------
+// <copyright file="ITokenGenerator.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ /// <summary>
+ /// An interface allowing OAuth hosts to inject their own algorithm for generating tokens and secrets.
+ /// </summary>
+ public interface ITokenGenerator {
+ /// <summary>
+ /// Generates a new token to represent a not-yet-authorized request to access protected resources.
+ /// </summary>
+ /// <param name="consumerKey">The consumer that requested this token.</param>
+ /// <returns>The newly generated token.</returns>
+ /// <remarks>
+ /// This method should not store the newly generated token in any persistent store.
+ /// This will be done in <see cref="ITokenManager.StoreNewRequestToken"/>.
+ /// </remarks>
+ string GenerateRequestToken(string consumerKey);
+
+ /// <summary>
+ /// Generates a new token to represent an authorized request to access protected resources.
+ /// </summary>
+ /// <param name="consumerKey">The consumer that requested this token.</param>
+ /// <returns>The newly generated token.</returns>
+ /// <remarks>
+ /// This method should not store the newly generated token in any persistent store.
+ /// This will be done in <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/>.
+ /// </remarks>
+ string GenerateAccessToken(string consumerKey);
+
+ /// <summary>
+ /// Returns a cryptographically strong random string for use as a token secret.
+ /// </summary>
+ /// <returns>The generated string.</returns>
+ string GenerateSecret();
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/ITokenManager.cs b/src/DotNetOAuth/OAuth/ChannelElements/ITokenManager.cs new file mode 100644 index 0000000..257e44e --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/ITokenManager.cs @@ -0,0 +1,80 @@ +//-----------------------------------------------------------------------
+// <copyright file="ITokenManager.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.OAuth.Messages;
+
+ /// <summary>
+ /// An interface OAuth hosts must implement for persistent storage and recall of tokens and secrets.
+ /// </summary>
+ public interface ITokenManager {
+ /// <summary>
+ /// Gets the Consumer Secret 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>
+ /// <remarks>
+ /// TODO: In the case of RSA hashing, the consumer may not have a secret
+ /// like this. What to do in that case?
+ /// </remarks>
+ string GetConsumerSecret(string consumerKey);
+
+ /// <summary>
+ /// Gets the Token Secret given a request or access token.
+ /// </summary>
+ /// <param name="token">The request or access token.</param>
+ /// <returns>The secret associated with the given token.</returns>
+ /// <exception cref="ArgumentException">Thrown if the secret cannot be found for the given token.</exception>
+ string GetTokenSecret(string token);
+
+ /// <summary>
+ /// Stores a newly generated unauthorized request token, secret, and optional
+ /// application-specific parameters for later recall.
+ /// </summary>
+ /// <param name="request">The request message that resulted in the generation of a new unauthorized request token.</param>
+ /// <param name="response">The response message that includes the unauthorized request token.</param>
+ /// <exception cref="ArgumentException">Thrown if the consumer key is not registered, or a required parameter was not found in the parameters collection.</exception>
+ void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response);
+
+ /// <summary>
+ /// Checks whether a given request token has already been authorized
+ /// by some user for use by the Consumer that requested it.
+ /// </summary>
+ /// <param name="requestToken">The Consumer's request token.</param>
+ /// <returns>
+ /// True if the request token has already been fully authorized by the user
+ /// who owns the relevant protected resources. False if the token has not yet
+ /// been authorized, has expired or does not exist.
+ /// </returns>
+ bool IsRequestTokenAuthorized(string requestToken);
+
+ /// <summary>
+ /// Deletes a request token and its associated secret and stores a new access token and secret.
+ /// </summary>
+ /// <param name="consumerKey">The Consumer that is exchanging its request token for an access token.</param>
+ /// <param name="requestToken">The Consumer's request token that should be deleted/expired.</param>
+ /// <param name="accessToken">The new access token that is being issued to the Consumer.</param>
+ /// <param name="accessTokenSecret">The secret associated with the newly issued access token.</param>
+ /// <remarks>
+ /// Any scope of granted privileges associated with the request token from the
+ /// original call to <see cref="StoreNewRequestToken"/> should be carried over
+ /// to the new Access Token.
+ /// </remarks>
+ void ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret);
+
+ /// <summary>
+ /// Classifies a token as a request token or an access token.
+ /// </summary>
+ /// <param name="token">The token to classify.</param>
+ /// <returns>Request or Access token, or invalid if the token is not recognized.</returns>
+ TokenType GetTokenType(string token);
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/IWebRequestHandler.cs b/src/DotNetOAuth/OAuth/ChannelElements/IWebRequestHandler.cs new file mode 100644 index 0000000..d4a56f3 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/IWebRequestHandler.cs @@ -0,0 +1,31 @@ +//-----------------------------------------------------------------------
+// <copyright file="IWebRequestHandler.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System.IO;
+ using System.Net;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A contract for <see cref="HttpWebRequest"/> handling.
+ /// </summary>
+ internal interface IWebRequestHandler {
+ /// <summary>
+ /// Prepares an <see cref="HttpWebRequest"/> that contains an POST entity for sending the entity.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
+ /// <returns>The writer the caller should write out the entity data to.</returns>
+ TextWriter GetRequestStream(HttpWebRequest request);
+
+ /// <summary>
+ /// Processes an <see cref="HttpWebRequest"/> and converts the
+ /// <see cref="HttpWebResponse"/> to a <see cref="Response"/> instance.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
+ /// <returns>An instance of <see cref="Response"/> describing the response.</returns>
+ Response GetResponse(HttpWebRequest request);
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOAuth/OAuth/ChannelElements/OAuthChannel.cs new file mode 100644 index 0000000..4be38a0 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/OAuthChannel.cs @@ -0,0 +1,397 @@ +//-----------------------------------------------------------------------
+// <copyright file="OAuthChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.IO;
+ using System.Net;
+ using System.Text;
+ using System.Web;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+ using DotNetOAuth.Messaging.Reflection;
+ using DotNetOAuth.OAuth.Messages;
+
+ /// <summary>
+ /// An OAuth-specific implementation of the <see cref="Channel"/> class.
+ /// </summary>
+ internal class OAuthChannel : Channel {
+ /// <summary>
+ /// The object that will transmit <see cref="HttpWebRequest"/> instances
+ /// and return their responses.
+ /// </summary>
+ private IWebRequestHandler webRequestHandler;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthChannel"/> class.
+ /// </summary>
+ /// <param name="signingBindingElement">The binding element to use for signing.</param>
+ /// <param name="store">The web application store to use for nonces.</param>
+ /// <param name="tokenManager">The token manager instance to use.</param>
+ /// <param name="isConsumer">A value indicating whether this channel is being constructed for a Consumer (as opposed to a Service Provider).</param>
+ internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, bool isConsumer)
+ : this(
+ signingBindingElement,
+ store,
+ tokenManager,
+ isConsumer ? (IMessageTypeProvider)new OAuthConsumerMessageTypeProvider() : new OAuthServiceProviderMessageTypeProvider(tokenManager),
+ new StandardWebRequestHandler()) {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthChannel"/> class.
+ /// </summary>
+ /// <param name="signingBindingElement">The binding element to use for signing.</param>
+ /// <param name="store">The web application store to use for nonces.</param>
+ /// <param name="tokenManager">The ITokenManager instance to use.</param>
+ /// <param name="messageTypeProvider">
+ /// An injected message type provider instance.
+ /// Except for mock testing, this should always be one of
+ /// <see cref="OAuthConsumerMessageTypeProvider"/> or <see cref="OAuthServiceProviderMessageTypeProvider"/>.
+ /// </param>
+ /// <param name="webRequestHandler">
+ /// An instance to a <see cref="IWebRequestHandler"/> that will be used when submitting HTTP
+ /// requests and waiting for responses.
+ /// </param>
+ /// <remarks>
+ /// This overload for testing purposes only.
+ /// </remarks>
+ internal OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, IMessageTypeProvider messageTypeProvider, IWebRequestHandler webRequestHandler)
+ : base(messageTypeProvider, new OAuthHttpMethodBindingElement(), signingBindingElement, new StandardExpirationBindingElement(), new StandardReplayProtectionBindingElement(store)) {
+ if (tokenManager == null) {
+ throw new ArgumentNullException("tokenManager");
+ }
+ if (webRequestHandler == null) {
+ throw new ArgumentNullException("webRequestHandler");
+ }
+
+ this.webRequestHandler = webRequestHandler;
+ this.TokenManager = tokenManager;
+ if (signingBindingElement.SignatureCallback != null) {
+ throw new ArgumentException(Strings.SigningElementAlreadyAssociatedWithChannel, "signingBindingElement");
+ }
+
+ signingBindingElement.SignatureCallback = this.SignatureCallback;
+ }
+
+ /// <summary>
+ /// Gets or sets the Consumer web application path.
+ /// </summary>
+ internal Uri Realm { get; set; }
+
+ /// <summary>
+ /// Gets the token manager being used.
+ /// </summary>
+ protected internal ITokenManager TokenManager { get; private set; }
+
+ /// <summary>
+ /// Encodes the names and values that are part of the message per OAuth 1.0 section 5.1.
+ /// </summary>
+ /// <param name="message">The message with data to encode.</param>
+ /// <returns>A dictionary of name-value pairs with their strings encoded.</returns>
+ internal static IDictionary<string, string> GetEncodedParameters(IProtocolMessage message) {
+ var encodedDictionary = new Dictionary<string, string>();
+ EncodeParameters(new MessageDictionary(message), encodedDictionary);
+ return encodedDictionary;
+ }
+
+ /// <summary>
+ /// Encodes 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>
+ /// <param name="destination">The dictionary to add the encoded pairs to.</param>
+ internal static void EncodeParameters(IDictionary<string, string> source, IDictionary<string, string> destination) {
+ if (source == null) {
+ throw new ArgumentNullException("source");
+ }
+ if (destination == null) {
+ throw new ArgumentNullException("destination");
+ }
+
+ foreach (var pair in source) {
+ var key = Uri.EscapeDataString(pair.Key);
+ var value = Uri.EscapeDataString(pair.Value);
+ destination.Add(key, value);
+ }
+ }
+
+ /// <summary>
+ /// Initializes a web request for sending by attaching a message to it.
+ /// Use this method to prepare a protected resource request that you do NOT
+ /// expect an OAuth message response to.
+ /// </summary>
+ /// <param name="request">The message to attach.</param>
+ /// <returns>The initialized web request.</returns>
+ internal HttpWebRequest InitializeRequest(IDirectedProtocolMessage request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ PrepareMessageForSending(request);
+ return this.InitializeRequestInternal(request);
+ }
+
+ /// <summary>
+ /// Searches an incoming HTTP request for data that could be used to assemble
+ /// a protocol request message.
+ /// </summary>
+ /// <param name="request">The HTTP request to search.</param>
+ /// <returns>A dictionary of data in the request. Should never be null, but may be empty.</returns>
+ protected override IProtocolMessage ReadFromRequestInternal(HttpRequestInfo request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ // First search the Authorization header. Use it exclusively if it's present.
+ string authorization = request.Headers[HttpRequestHeader.Authorization];
+ if (authorization != null) {
+ string[] authorizationSections = authorization.Split(';'); // TODO: is this the right delimiter?
+ string oauthPrefix = Protocol.Default.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.
+ foreach (string auth in authorizationSections) {
+ string trimmedAuth = auth.Trim();
+ if (trimmedAuth.StartsWith(oauthPrefix, StringComparison.Ordinal)) {
+ // We found an Authorization: OAuth header.
+ // Parse it according to the rules in section 5.4.1 of the V1.0 spec.
+ var fields = new Dictionary<string, string>();
+ foreach (string stringPair in trimmedAuth.Substring(oauthPrefix.Length).Split(',')) {
+ string[] keyValueStringPair = stringPair.Trim().Split('=');
+ string key = Uri.UnescapeDataString(keyValueStringPair[0]);
+ string value = Uri.UnescapeDataString(keyValueStringPair[1].Trim('"'));
+ fields.Add(key, value);
+ }
+
+ return this.Receive(fields, request.GetRecipient());
+ }
+ }
+ }
+
+ // We didn't find an OAuth authorization header. Revert to other payload methods.
+ IProtocolMessage message = base.ReadFromRequestInternal(request);
+
+ // Add receiving HTTP transport information required for signature generation.
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null) {
+ signedMessage.Recipient = request.Url;
+ signedMessage.HttpMethod = request.HttpMethod;
+ }
+
+ return message;
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response stream.
+ /// </summary>
+ /// <param name="responseStream">The response that is anticipated to contain an OAuth message.</param>
+ /// <returns>The deserialized message, if one is found. Null otherwise.</returns>
+ protected override IProtocolMessage ReadFromResponseInternal(Stream responseStream) {
+ if (responseStream == null) {
+ throw new ArgumentNullException("responseStream");
+ }
+
+ using (StreamReader reader = new StreamReader(responseStream)) {
+ string response = reader.ReadToEnd();
+ var fields = HttpUtility.ParseQueryString(response).ToDictionary();
+ return Receive(fields, null);
+ }
+ }
+
+ /// <summary>
+ /// Sends a direct message to a remote party and waits for the response.
+ /// </summary>
+ /// <param name="request">The message to send.</param>
+ /// <returns>The remote party's response.</returns>
+ protected override IProtocolMessage RequestInternal(IDirectedProtocolMessage request) {
+ HttpWebRequest httpRequest = this.InitializeRequestInternal(request);
+
+ Response response = this.webRequestHandler.GetResponse(httpRequest);
+ if (response.ResponseStream == null) {
+ return null;
+ }
+ var responseFields = HttpUtility.ParseQueryString(response.Body).ToDictionary();
+ Type messageType = this.MessageTypeProvider.GetResponseMessageType(request, responseFields);
+ if (messageType == null) {
+ return null;
+ }
+ var responseSerialize = MessageSerializer.Get(messageType);
+ var responseMessage = responseSerialize.Deserialize(responseFields, null);
+
+ return responseMessage;
+ }
+
+ /// <summary>
+ /// Queues a message for sending in the response stream where the fields
+ /// are sent in the response stream in querystring style.
+ /// </summary>
+ /// <param name="response">The message to send as a response.</param>
+ /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
+ /// <remarks>
+ /// This method implements spec V1.0 section 5.3.
+ /// </remarks>
+ protected override Response SendDirectMessageResponse(IProtocolMessage response) {
+ if (response == null) {
+ throw new ArgumentNullException("response");
+ }
+
+ MessageSerializer serializer = MessageSerializer.Get(response.GetType());
+ var fields = serializer.Serialize(response);
+ string responseBody = MessagingUtilities.CreateQueryString(fields);
+
+ Response encodedResponse = new Response {
+ Body = responseBody,
+ OriginalMessage = response,
+ Status = HttpStatusCode.OK,
+ Headers = new System.Net.WebHeaderCollection(),
+ };
+ return encodedResponse;
+ }
+
+ /// <summary>
+ /// Prepares to send a request to the Service Provider as the query string in a GET request.
+ /// </summary>
+ /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
+ /// <returns>The web request ready to send.</returns>
+ /// <remarks>
+ /// This method implements OAuth 1.0 section 5.2, item #3.
+ /// </remarks>
+ private static HttpWebRequest InitializeRequestAsGet(IDirectedProtocolMessage requestMessage) {
+ var serializer = MessageSerializer.Get(requestMessage.GetType());
+ var fields = serializer.Serialize(requestMessage);
+
+ UriBuilder builder = new UriBuilder(requestMessage.Recipient);
+ MessagingUtilities.AppendQueryArgs(builder, fields);
+ HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
+
+ return httpRequest;
+ }
+
+ /// <summary>
+ /// Initializes a web request by attaching a message to it.
+ /// </summary>
+ /// <param name="request">The message to attach.</param>
+ /// <returns>The initialized web request.</returns>
+ private HttpWebRequest InitializeRequestInternal(IDirectedProtocolMessage request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+ if (request.Recipient == null) {
+ throw new ArgumentException(MessagingStrings.DirectedMessageMissingRecipient, "request");
+ }
+ IOAuthDirectedMessage oauthRequest = request as IOAuthDirectedMessage;
+ if (oauthRequest == null) {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.UnexpectedType,
+ typeof(IOAuthDirectedMessage),
+ request.GetType()));
+ }
+
+ HttpWebRequest httpRequest;
+
+ HttpDeliveryMethods transmissionMethod = oauthRequest.HttpMethods;
+ if ((transmissionMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) {
+ httpRequest = this.InitializeRequestAsAuthHeader(request);
+ } else if ((transmissionMethod & HttpDeliveryMethods.PostRequest) != 0) {
+ httpRequest = this.InitializeRequestAsPost(request);
+ } else if ((transmissionMethod & HttpDeliveryMethods.GetRequest) != 0) {
+ httpRequest = InitializeRequestAsGet(request);
+ } else {
+ throw new NotSupportedException();
+ }
+ return httpRequest;
+ }
+
+ /// <summary>
+ /// Prepares to send a request to the Service Provider via the Authorization header.
+ /// </summary>
+ /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
+ /// <returns>The web request ready to send.</returns>
+ /// <remarks>
+ /// This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).
+ /// </remarks>
+ private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) {
+ var protocol = Protocol.Lookup(requestMessage.ProtocolVersion);
+ var dictionary = new MessageDictionary(requestMessage);
+
+ // copy so as to not modify original
+ var fields = new Dictionary<string, string>();
+ foreach (string key in dictionary.DeclaredKeys) {
+ fields.Add(key, dictionary[key]);
+ }
+ if (this.Realm != null) {
+ fields.Add("realm", this.Realm.AbsoluteUri);
+ }
+
+ UriBuilder builder = new UriBuilder(requestMessage.Recipient);
+ MessagingUtilities.AppendQueryArgs(builder, requestMessage.ExtraData);
+ HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(builder.Uri);
+
+ StringBuilder authorization = new StringBuilder();
+ authorization.Append(protocol.AuthorizationHeaderScheme);
+ authorization.Append(" ");
+ foreach (var pair in fields) {
+ string key = Uri.EscapeDataString(pair.Key);
+ string value = Uri.EscapeDataString(pair.Value);
+ authorization.Append(key);
+ authorization.Append("=\"");
+ authorization.Append(value);
+ authorization.Append("\",");
+ }
+ authorization.Length--; // remove trailing comma
+
+ httpRequest.Headers.Add(HttpRequestHeader.Authorization, authorization.ToString());
+
+ return httpRequest;
+ }
+
+ /// <summary>
+ /// Prepares to send a request to the Service Provider as the payload of a POST request.
+ /// </summary>
+ /// <param name="requestMessage">The message to be transmitted to the ServiceProvider.</param>
+ /// <returns>The web request ready to send.</returns>
+ /// <remarks>
+ /// This method implements OAuth 1.0 section 5.2, item #2.
+ /// </remarks>
+ private HttpWebRequest InitializeRequestAsPost(IDirectedProtocolMessage requestMessage) {
+ var serializer = MessageSerializer.Get(requestMessage.GetType());
+ var fields = serializer.Serialize(requestMessage);
+
+ HttpWebRequest httpRequest = (HttpWebRequest)WebRequest.Create(requestMessage.Recipient);
+ httpRequest.Method = "POST";
+ httpRequest.ContentType = "application/x-www-form-urlencoded";
+ string requestBody = MessagingUtilities.CreateQueryString(fields);
+ httpRequest.ContentLength = requestBody.Length;
+ using (TextWriter writer = this.webRequestHandler.GetRequestStream(httpRequest)) {
+ writer.Write(requestBody);
+ }
+
+ return httpRequest;
+ }
+
+ /// <summary>
+ /// Fills out the secrets in a message so that signing/verification can be performed.
+ /// </summary>
+ /// <param name="message">The message about to be signed or whose signature is about to be verified.</param>
+ private void SignatureCallback(ITamperResistantOAuthMessage message) {
+ try {
+ Logger.Debug("Applying secrets to message to prepare for signing or signature verification.");
+ message.ConsumerSecret = this.TokenManager.GetConsumerSecret(message.ConsumerKey);
+
+ var tokenMessage = message as ITokenContainingMessage;
+ if (tokenMessage != null) {
+ message.TokenSecret = this.TokenManager.GetTokenSecret(tokenMessage.Token);
+ }
+ } catch (KeyNotFoundException ex) {
+ throw new ProtocolException(Strings.ConsumerOrTokenSecretNotFound, ex);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/OAuthConsumerMessageTypeProvider.cs b/src/DotNetOAuth/OAuth/ChannelElements/OAuthConsumerMessageTypeProvider.cs new file mode 100644 index 0000000..123d657 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/OAuthConsumerMessageTypeProvider.cs @@ -0,0 +1,97 @@ +//-----------------------------------------------------------------------
+// <copyright file="OAuthConsumerMessageTypeProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.OAuth.Messages;
+
+ /// <summary>
+ /// An OAuth-protocol specific implementation of the <see cref="IMessageTypeProvider"/>
+ /// interface.
+ /// </summary>
+ public class OAuthConsumerMessageTypeProvider : IMessageTypeProvider {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthConsumerMessageTypeProvider"/> class.
+ /// </summary>
+ protected internal OAuthConsumerMessageTypeProvider() {
+ }
+
+ #region IMessageTypeProvider Members
+
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <remarks>
+ /// The request messages are:
+ /// UserAuthorizationResponse
+ /// </remarks>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ public virtual Type GetRequestMessageType(IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ if (fields.ContainsKey("oauth_token")) {
+ return typeof(UserAuthorizationResponse);
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="request">
+ /// The message that was sent as a request that resulted in the response.
+ /// Null on a Consumer site that is receiving an indirect message from the Service Provider.
+ /// </param>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ /// <remarks>
+ /// The response messages are:
+ /// UnauthorizedTokenResponse
+ /// AuthorizedTokenResponse
+ /// </remarks>
+ public virtual Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ // All response messages have the oauth_token field.
+ if (!fields.ContainsKey("oauth_token")) {
+ return null;
+ }
+
+ // All direct message responses should have the oauth_token_secret field.
+ if (!fields.ContainsKey("oauth_token_secret")) {
+ Logger.Error("An OAuth message was expected to contain an oauth_token_secret but didn't.");
+ return null;
+ }
+
+ if (request is UnauthorizedTokenRequest) {
+ return typeof(UnauthorizedTokenResponse);
+ } else if (request is AuthorizedTokenRequest) {
+ return typeof(AuthorizedTokenResponse);
+ } else {
+ Logger.ErrorFormat("Unexpected response message given the request type {0}", request.GetType().Name);
+ throw new ProtocolException(Strings.InvalidIncomingMessage);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs b/src/DotNetOAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs new file mode 100644 index 0000000..e026c02 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs @@ -0,0 +1,73 @@ +//-----------------------------------------------------------------------
+// <copyright file="OAuthHttpMethodBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// Sets the HTTP Method property on a signed message before the signing module gets to it.
+ /// </summary>
+ internal class OAuthHttpMethodBindingElement : IChannelBindingElement {
+ #region IChannelBindingElement Members
+
+ /// <summary>
+ /// Gets the protection offered (if any) by this binding element.
+ /// </summary>
+ 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>
+ /// True if the <paramref name="message"/> applied to this binding element
+ /// and the operation was successful. False otherwise.
+ /// </returns>
+ public bool PrepareMessageForSending(IProtocolMessage message) {
+ var oauthMessage = message as ITamperResistantOAuthMessage;
+
+ if (oauthMessage != null) {
+ HttpDeliveryMethods transmissionMethod = oauthMessage.HttpMethods;
+ if ((transmissionMethod & HttpDeliveryMethods.PostRequest) != 0) {
+ oauthMessage.HttpMethod = "POST";
+ } else if ((transmissionMethod & HttpDeliveryMethods.GetRequest) != 0) {
+ oauthMessage.HttpMethod = "GET";
+ } else {
+ return false;
+ }
+
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /// <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>
+ /// True if the <paramref name="message"/> applied to this binding element
+ /// and the operation was successful. False if the operation did not apply to this message.
+ /// </returns>
+ /// <exception cref="ProtocolException">
+ /// Thrown when the binding element rules indicate that this message is invalid and should
+ /// NOT be processed.
+ /// </exception>
+ public bool PrepareMessageForReceiving(IProtocolMessage message) {
+ return false;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/OAuthServiceProviderMessageTypeProvider.cs b/src/DotNetOAuth/OAuth/ChannelElements/OAuthServiceProviderMessageTypeProvider.cs new file mode 100644 index 0000000..570cdc1 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/OAuthServiceProviderMessageTypeProvider.cs @@ -0,0 +1,106 @@ +//-----------------------------------------------------------------------
+// <copyright file="OAuthServiceProviderMessageTypeProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.OAuth.Messages;
+
+ /// <summary>
+ /// An OAuth-protocol specific implementation of the <see cref="IMessageTypeProvider"/>
+ /// interface.
+ /// </summary>
+ public class OAuthServiceProviderMessageTypeProvider : IMessageTypeProvider {
+ /// <summary>
+ /// The token manager to use for discerning between request and access tokens.
+ /// </summary>
+ private ITokenManager tokenManager;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="OAuthServiceProviderMessageTypeProvider"/> class.
+ /// </summary>
+ /// <param name="tokenManager">The token manager instance to use.</param>
+ protected internal OAuthServiceProviderMessageTypeProvider(ITokenManager tokenManager) {
+ if (tokenManager == null) {
+ throw new ArgumentNullException("tokenManager");
+ }
+
+ this.tokenManager = tokenManager;
+ }
+
+ #region IMessageTypeProvider Members
+
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <remarks>
+ /// The request messages are:
+ /// UnauthorizedTokenRequest
+ /// AuthorizedTokenRequest
+ /// UserAuthorizationRequest
+ /// AccessProtectedResourceRequest
+ /// </remarks>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ public virtual Type GetRequestMessageType(IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ if (fields.ContainsKey("oauth_consumer_key") &&
+ !fields.ContainsKey("oauth_token")) {
+ return typeof(UnauthorizedTokenRequest);
+ }
+
+ 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;
+
+ return tokenTypeIsAccessToken ? typeof(AccessProtectedResourceRequest) :
+ typeof(AuthorizedTokenRequest);
+ }
+
+ // fail over to the message with no required fields at all.
+ return typeof(UserAuthorizationRequest);
+ }
+
+ /// <summary>
+ /// Analyzes an incoming request message payload to discover what kind of
+ /// message is embedded in it and returns the type, or null if no match is found.
+ /// </summary>
+ /// <param name="request">
+ /// The message that was sent as a request that resulted in the response.
+ /// Null on a Consumer site that is receiving an indirect message from the Service Provider.
+ /// </param>
+ /// <param name="fields">The name/value pairs that make up the message payload.</param>
+ /// <returns>
+ /// The <see cref="IProtocolMessage"/>-derived concrete class that this message can
+ /// deserialize to. Null if the request isn't recognized as a valid protocol message.
+ /// </returns>
+ /// <remarks>
+ /// The response messages are:
+ /// None.
+ /// </remarks>
+ public virtual Type GetResponseMessageType(IProtocolMessage request, IDictionary<string, string> fields) {
+ if (fields == null) {
+ throw new ArgumentNullException("fields");
+ }
+
+ Logger.Error("Service Providers are not expected to ever receive responses.");
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs b/src/DotNetOAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs new file mode 100644 index 0000000..e7d44d2 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs @@ -0,0 +1,55 @@ +//-----------------------------------------------------------------------
+// <copyright file="PlaintextSigningBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// A binding element that signs outgoing messages and verifies the signature on incoming messages.
+ /// </summary>
+ public class PlaintextSigningBindingElement : SigningBindingElementBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlaintextSigningBindingElement"/> class.
+ /// </summary>
+ public PlaintextSigningBindingElement()
+ : base("PLAINTEXT") {
+ }
+
+ /// <summary>
+ /// Calculates a signature for a given message.
+ /// </summary>
+ /// <param name="message">The message to sign.</param>
+ /// <returns>The signature for the message.</returns>
+ /// <remarks>
+ /// This method signs the message according to OAuth 1.0 section 9.4.1.
+ /// </remarks>
+ protected override string GetSignature(ITamperResistantOAuthMessage message) {
+ return GetConsumerAndTokenSecretString(message);
+ }
+
+ /// <summary>
+ /// Checks whether this binding element applies to this message.
+ /// </summary>
+ /// <param name="message">The message that needs to be signed.</param>
+ /// <returns>True if this binding element can be used to sign the message. False otherwise.</returns>
+ protected override bool IsMessageApplicable(ITamperResistantOAuthMessage message) {
+ return string.Equals(message.Recipient.Scheme, "https", StringComparison.OrdinalIgnoreCase);
+ }
+
+ /// <summary>
+ /// Clones this instance.
+ /// </summary>
+ /// <returns>A new instance of the binding element.</returns>
+ protected override ITamperProtectionChannelBindingElement Clone() {
+ return new PlaintextSigningBindingElement();
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs new file mode 100644 index 0000000..07a72f3 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs @@ -0,0 +1,48 @@ +//-----------------------------------------------------------------------
+// <copyright file="RsaSha1SigningBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A binding element that signs outgoing messages and verifies the signature on incoming messages.
+ /// </summary>
+ public class RsaSha1SigningBindingElement : SigningBindingElementBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class.
+ /// </summary>
+ internal RsaSha1SigningBindingElement()
+ : base("RSA-SHA1") {
+ }
+
+ /// <summary>
+ /// Calculates a signature for a given message.
+ /// </summary>
+ /// <param name="message">The message to sign.</param>
+ /// <returns>The signature for the message.</returns>
+ /// <remarks>
+ /// This method signs the message per OAuth 1.0 section 9.3.
+ /// </remarks>
+ protected override string GetSignature(ITamperResistantOAuthMessage message) {
+ AsymmetricAlgorithm provider = new RSACryptoServiceProvider();
+ AsymmetricSignatureFormatter hasher = new RSAPKCS1SignatureFormatter(provider);
+ hasher.SetHashAlgorithm("SHA1");
+ byte[] digest = hasher.CreateSignature(Encoding.ASCII.GetBytes(ConstructSignatureBaseString(message)));
+ return Convert.ToBase64String(digest);
+ }
+
+ /// <summary>
+ /// Clones this instance.
+ /// </summary>
+ /// <returns>A new instance of the binding element.</returns>
+ protected override ITamperProtectionChannelBindingElement Clone() {
+ return new RsaSha1SigningBindingElement();
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOAuth/OAuth/ChannelElements/SigningBindingElementBase.cs new file mode 100644 index 0000000..fb3ca45 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/SigningBindingElementBase.cs @@ -0,0 +1,242 @@ +//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElementBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// A binding element that signs outgoing messages and verifies the signature on incoming messages.
+ /// </summary>
+ public abstract class SigningBindingElementBase : ITamperProtectionChannelBindingElement {
+ /// <summary>
+ /// The signature method this binding element uses.
+ /// </summary>
+ private string signatureMethod;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SigningBindingElementBase"/> class.
+ /// </summary>
+ /// <param name="signatureMethod">The OAuth signature method that the binding element uses.</param>
+ internal SigningBindingElementBase(string signatureMethod) {
+ this.signatureMethod = signatureMethod;
+ }
+
+ #region IChannelBindingElement Properties
+
+ /// <summary>
+ /// Gets the message protection provided by this binding element.
+ /// </summary>
+ public MessageProtections Protection {
+ get { return MessageProtections.TamperProtection; }
+ }
+
+ #endregion
+
+ #region ITamperProtectionChannelBindingElement members
+
+ /// <summary>
+ /// Gets or sets the delegate that will initialize the non-serialized properties necessary on a signed
+ /// message so that its signature can be correctly calculated for verification.
+ /// </summary>
+ public Action<ITamperResistantOAuthMessage> SignatureCallback { get; set; }
+
+ /// <summary>
+ /// Creates a new object that is a copy of the current instance.
+ /// </summary>
+ /// <returns>
+ /// A new object that is a copy of this instance.
+ /// </returns>
+ ITamperProtectionChannelBindingElement ITamperProtectionChannelBindingElement.Clone() {
+ ITamperProtectionChannelBindingElement clone = this.Clone();
+ clone.SignatureCallback = this.SignatureCallback;
+ return clone;
+ }
+
+ #endregion
+
+ #region IChannelBindingElement Methods
+
+ /// <summary>
+ /// Signs the outgoing message.
+ /// </summary>
+ /// <param name="message">The message to sign.</param>
+ /// <returns>True if the message was signed. False otherwise.</returns>
+ public bool PrepareMessageForSending(IProtocolMessage message) {
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null && this.IsMessageApplicable(signedMessage)) {
+ if (this.SignatureCallback != null) {
+ this.SignatureCallback(signedMessage);
+ } else {
+ Logger.Warn("Signing required, but callback delegate was not provided to provide additional data for signing.");
+ }
+
+ signedMessage.SignatureMethod = this.signatureMethod;
+ Logger.DebugFormat("Signing {0} message using {1}.", message.GetType().Name, this.signatureMethod);
+ signedMessage.Signature = this.GetSignature(signedMessage);
+ return true;
+ }
+
+ return false;
+ }
+
+ /// <summary>
+ /// Verifies the signature on an incoming message.
+ /// </summary>
+ /// <param name="message">The message whose signature should be verified.</param>
+ /// <returns>True if the signature was verified. False if the message had no signature.</returns>
+ /// <exception cref="InvalidSignatureException">Thrown if the signature is invalid.</exception>
+ public bool PrepareMessageForReceiving(IProtocolMessage message) {
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null && this.IsMessageApplicable(signedMessage)) {
+ Logger.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature);
+
+ if (!string.Equals(signedMessage.SignatureMethod, this.signatureMethod, StringComparison.Ordinal)) {
+ Logger.WarnFormat("Expected signature method '{0}' but received message with a signature method of '{1}'.", this.signatureMethod, signedMessage.SignatureMethod);
+ return false;
+ }
+
+ if (this.SignatureCallback != null) {
+ this.SignatureCallback(signedMessage);
+ } else {
+ Logger.Warn("Signature verification required, but callback delegate was not provided to provide additional data for signing.");
+ }
+
+ string signature = this.GetSignature(signedMessage);
+ if (signedMessage.Signature != signature) {
+ Logger.Error("Signature verification failed.");
+ throw new InvalidSignatureException(message);
+ }
+
+ return true;
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Constructs the OAuth Signature Base String and returns the result.
+ /// </summary>
+ /// <param name="message">The message to derive the signature base string from.</param>
+ /// <returns>The signature base string.</returns>
+ /// <remarks>
+ /// This method implements OAuth 1.0 section 9.1.
+ /// </remarks>
+ protected static string ConstructSignatureBaseString(ITamperResistantOAuthMessage message) {
+ if (String.IsNullOrEmpty(message.HttpMethod)) {
+ throw new ArgumentException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ MessagingStrings.ArgumentPropertyMissing,
+ typeof(ITamperResistantOAuthMessage).Name,
+ "HttpMethod"),
+ "message");
+ }
+
+ List<string> signatureBaseStringElements = new List<string>(3);
+
+ signatureBaseStringElements.Add(message.HttpMethod.ToUpperInvariant());
+
+ UriBuilder endpoint = new UriBuilder(message.Recipient);
+ endpoint.Query = null;
+ endpoint.Fragment = null;
+ signatureBaseStringElements.Add(endpoint.Uri.AbsoluteUri);
+
+ var encodedDictionary = OAuthChannel.GetEncodedParameters(message);
+ encodedDictionary.Remove("oauth_signature");
+ var sortedKeyValueList = new List<KeyValuePair<string, string>>(encodedDictionary);
+ sortedKeyValueList.Sort(SignatureBaseStringParameterComparer);
+ StringBuilder paramBuilder = new StringBuilder();
+ foreach (var pair in sortedKeyValueList) {
+ if (paramBuilder.Length > 0) {
+ paramBuilder.Append("&");
+ }
+
+ paramBuilder.Append(pair.Key);
+ paramBuilder.Append('=');
+ paramBuilder.Append(pair.Value);
+ }
+
+ signatureBaseStringElements.Add(paramBuilder.ToString());
+
+ StringBuilder signatureBaseString = new StringBuilder();
+ foreach (string element in signatureBaseStringElements) {
+ if (signatureBaseString.Length > 0) {
+ signatureBaseString.Append("&");
+ }
+
+ signatureBaseString.Append(Uri.EscapeDataString(element));
+ }
+
+ Logger.DebugFormat("Constructed signature base string: {0}", signatureBaseString);
+ return signatureBaseString.ToString();
+ }
+
+ /// <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 static 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>
+ /// Clones this instance.
+ /// </summary>
+ /// <returns>A new instance of the binding element.</returns>
+ /// <remarks>
+ /// Implementations of this method need not clone the SignatureVerificationCallback member, as the
+ /// <see cref="SigningBindingElementBase"/> class does this.
+ /// </remarks>
+ protected abstract ITamperProtectionChannelBindingElement Clone();
+
+ /// <summary>
+ /// Calculates a signature for a given message.
+ /// </summary>
+ /// <param name="message">The message to sign.</param>
+ /// <returns>The signature for the message.</returns>
+ protected abstract string GetSignature(ITamperResistantOAuthMessage message);
+
+ /// <summary>
+ /// Checks whether this binding element applies to this message.
+ /// </summary>
+ /// <param name="message">The message that needs to be signed.</param>
+ /// <returns>True if this binding element can be used to sign the message. False otherwise.</returns>
+ protected virtual bool IsMessageApplicable(ITamperResistantOAuthMessage message) {
+ return string.IsNullOrEmpty(message.SignatureMethod) || message.SignatureMethod == this.signatureMethod;
+ }
+
+ /// <summary>
+ /// Sorts parameters according to OAuth signature base string rules.
+ /// </summary>
+ /// <param name="left">The first parameter to compare.</param>
+ /// <param name="right">The second parameter to compare.</param>
+ /// <returns>Negative, zero or positive.</returns>
+ private static int SignatureBaseStringParameterComparer(KeyValuePair<string, string> left, KeyValuePair<string, string> right) {
+ int result = string.CompareOrdinal(left.Key, right.Key);
+ if (result != 0) {
+ return result;
+ }
+
+ return string.CompareOrdinal(left.Value, right.Value);
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/SigningBindingElementChain.cs b/src/DotNetOAuth/OAuth/ChannelElements/SigningBindingElementChain.cs new file mode 100644 index 0000000..88119f1 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/SigningBindingElementChain.cs @@ -0,0 +1,132 @@ +//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElementChain.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A tamper protection applying binding element that can use any of several given
+ /// binding elements to apply the protection.
+ /// </summary>
+ internal class SigningBindingElementChain : ITamperProtectionChannelBindingElement {
+ /// <summary>
+ /// The various signing binding elements that may be applicable to a message in preferred use order.
+ /// </summary>
+ private ITamperProtectionChannelBindingElement[] signers;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SigningBindingElementChain"/> class.
+ /// </summary>
+ /// <param name="signers">
+ /// The signing binding elements that may be used for some outgoing message,
+ /// in preferred use order.
+ /// </param>
+ internal SigningBindingElementChain(ITamperProtectionChannelBindingElement[] signers) {
+ if (signers == null) {
+ throw new ArgumentNullException("signers");
+ }
+ if (signers.Length == 0) {
+ throw new ArgumentException(MessagingStrings.SequenceContainsNoElements, "signers");
+ }
+ if (signers.Contains(null)) {
+ throw new ArgumentException(MessagingStrings.SequenceContainsNullElement, "signers");
+ }
+ MessageProtections protection = signers[0].Protection;
+ if (signers.Any(element => element.Protection != protection)) {
+ throw new ArgumentException(Strings.SigningElementsMustShareSameProtection, "signers");
+ }
+
+ this.signers = signers;
+ }
+
+ #region ITamperProtectionChannelBindingElement Properties
+
+ /// <summary>
+ /// Gets or sets the delegate that will initialize the non-serialized properties necessary on a signed
+ /// message so that its signature can be correctly calculated for verification.
+ /// May be null for Consumers (who never have to verify signatures).
+ /// </summary>
+ public Action<ITamperResistantOAuthMessage> SignatureCallback {
+ get {
+ return this.signers[0].SignatureCallback;
+ }
+
+ set {
+ foreach (ITamperProtectionChannelBindingElement signer in this.signers) {
+ signer.SignatureCallback = value;
+ }
+ }
+ }
+
+ #endregion
+
+ #region IChannelBindingElement Members
+
+ /// <summary>
+ /// Gets the protection offered (if any) by this binding element.
+ /// </summary>
+ public MessageProtections Protection {
+ get { return this.signers[0].Protection; }
+ }
+
+ /// <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>
+ /// True if the <paramref name="message"/> applied to this binding element
+ /// and the operation was successful. False otherwise.
+ /// </returns>
+ public bool PrepareMessageForSending(IProtocolMessage message) {
+ foreach (IChannelBindingElement signer in this.signers) {
+ if (signer.PrepareMessageForSending(message)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /// <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>
+ /// True if the <paramref name="message"/> applied to this binding element
+ /// and the operation was successful. False if the operation did not apply to this message.
+ /// </returns>
+ public bool PrepareMessageForReceiving(IProtocolMessage message) {
+ foreach (IChannelBindingElement signer in this.signers) {
+ if (signer.PrepareMessageForReceiving(message)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ #endregion
+
+ #region ITamperProtectionChannelBindingElement Methods
+
+ /// <summary>
+ /// Creates a new object that is a copy of the current instance.
+ /// </summary>
+ /// <returns>
+ /// A new object that is a copy of this instance.
+ /// </returns>
+ ITamperProtectionChannelBindingElement ITamperProtectionChannelBindingElement.Clone() {
+ return new SigningBindingElementChain(this.signers.Select(el => (ITamperProtectionChannelBindingElement)el.Clone()).ToArray());
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/StandardTokenGenerator.cs b/src/DotNetOAuth/OAuth/ChannelElements/StandardTokenGenerator.cs new file mode 100644 index 0000000..a1bc36a --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/StandardTokenGenerator.cs @@ -0,0 +1,68 @@ +//-----------------------------------------------------------------------
+// <copyright file="StandardTokenGenerator.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.Security.Cryptography;
+
+ /// <summary>
+ /// A cryptographically strong random string generator for tokens and secrets.
+ /// </summary>
+ internal class StandardTokenGenerator : ITokenGenerator {
+ /// <summary>
+ /// The cryptographically strong random string generator for tokens and secrets.
+ /// </summary>
+ private RandomNumberGenerator cryptoProvider = new RNGCryptoServiceProvider();
+
+ #region ITokenGenerator Members
+
+ /// <summary>
+ /// Generates a new token to represent a not-yet-authorized request to access protected resources.
+ /// </summary>
+ /// <param name="consumerKey">The consumer that requested this token.</param>
+ /// <returns>The newly generated token.</returns>
+ /// <remarks>
+ /// This method should not store the newly generated token in any persistent store.
+ /// This will be done in <see cref="ITokenManager.StoreNewRequestToken"/>.
+ /// </remarks>
+ public string GenerateRequestToken(string consumerKey) {
+ return this.GenerateCryptographicallyStrongString();
+ }
+
+ /// <summary>
+ /// Generates a new token to represent an authorized request to access protected resources.
+ /// </summary>
+ /// <param name="consumerKey">The consumer that requested this token.</param>
+ /// <returns>The newly generated token.</returns>
+ /// <remarks>
+ /// This method should not store the newly generated token in any persistent store.
+ /// This will be done in <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/>.
+ /// </remarks>
+ public string GenerateAccessToken(string consumerKey) {
+ return this.GenerateCryptographicallyStrongString();
+ }
+
+ /// <summary>
+ /// Returns a cryptographically strong random string for use as a token secret.
+ /// </summary>
+ /// <returns>The generated string.</returns>
+ public string GenerateSecret() {
+ return this.GenerateCryptographicallyStrongString();
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Returns a new random string.
+ /// </summary>
+ /// <returns>The new random string.</returns>
+ private string GenerateCryptographicallyStrongString() {
+ byte[] buffer = new byte[20];
+ this.cryptoProvider.GetBytes(buffer);
+ return Convert.ToBase64String(buffer);
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/StandardWebRequestHandler.cs b/src/DotNetOAuth/OAuth/ChannelElements/StandardWebRequestHandler.cs new file mode 100644 index 0000000..12fcd28 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/StandardWebRequestHandler.cs @@ -0,0 +1,70 @@ +//-----------------------------------------------------------------------
+// <copyright file="StandardWebRequestHandler.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ using System;
+ using System.IO;
+ using System.Net;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// The default handler for transmitting <see cref="HttpWebRequest"/> instances
+ /// and returning the responses.
+ /// </summary>
+ internal class StandardWebRequestHandler : IWebRequestHandler {
+ #region IWebRequestHandler Members
+
+ /// <summary>
+ /// Prepares a POST <see cref="HttpWebRequest"/> and returns the request stream
+ /// for writing out the POST entity data.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpWebRequest"/> that should contain the entity.</param>
+ /// <returns>The stream the caller should write out the entity data to.</returns>
+ public TextWriter GetRequestStream(HttpWebRequest request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ try {
+ return new StreamWriter(request.GetRequestStream());
+ } catch (WebException ex) {
+ throw new ProtocolException(MessagingStrings.ErrorInRequestReplyMessage, ex);
+ }
+ }
+
+ /// <summary>
+ /// Processes an <see cref="HttpWebRequest"/> and converts the
+ /// <see cref="HttpWebResponse"/> to a <see cref="Response"/> instance.
+ /// </summary>
+ /// <param name="request">The <see cref="HttpWebRequest"/> to handle.</param>
+ /// <returns>An instance of <see cref="Response"/> describing the response.</returns>
+ public Response GetResponse(HttpWebRequest request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ try {
+ Logger.DebugFormat("HTTP {0} {1}", request.Method, request.RequestUri);
+ using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) {
+ return new Response(response);
+ }
+ } catch (WebException ex) {
+ if (Logger.IsErrorEnabled) {
+ if (ex.Response != null) {
+ using (var reader = new StreamReader(ex.Response.GetResponseStream())) {
+ Logger.ErrorFormat("WebException from {0}: {1}", ex.Response.ResponseUri, reader.ReadToEnd());
+ }
+ } else {
+ Logger.ErrorFormat("WebException {1} from {0}, no response available.", request.RequestUri, ex.Status);
+ }
+ }
+ throw new ProtocolException(MessagingStrings.ErrorInRequestReplyMessage, ex);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ChannelElements/TokenType.cs b/src/DotNetOAuth/OAuth/ChannelElements/TokenType.cs new file mode 100644 index 0000000..bebdca2 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ChannelElements/TokenType.cs @@ -0,0 +1,30 @@ +//-----------------------------------------------------------------------
+// <copyright file="TokenType.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.ChannelElements {
+ /// <summary>
+ /// The two types of tokens that exist in the OAuth protocol.
+ /// </summary>
+ public enum TokenType {
+ /// <summary>
+ /// A token that is freely issued to any known Consumer.
+ /// It does not grant any authorization to access protected resources,
+ /// but is used as a step in obtaining that access.
+ /// </summary>
+ RequestToken,
+
+ /// <summary>
+ /// A token only obtained after the owner of some protected resource(s)
+ /// has approved a Consumer's access to said resource(s).
+ /// </summary>
+ AccessToken,
+
+ /// <summary>
+ /// An unrecognized, expired or invalid token.
+ /// </summary>
+ InvalidToken,
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ConsumerBase.cs b/src/DotNetOAuth/OAuth/ConsumerBase.cs new file mode 100644 index 0000000..6b3a4b9 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ConsumerBase.cs @@ -0,0 +1,176 @@ +//-----------------------------------------------------------------------
+// <copyright file="ConsumerBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Net;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+ using DotNetOAuth.OAuth.ChannelElements;
+ using DotNetOAuth.OAuth.Messages;
+
+ /// <summary>
+ /// Base class for <see cref="WebConsumer"/> and <see cref="DesktopConsumer"/> types.
+ /// </summary>
+ public class ConsumerBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConsumerBase"/> class.
+ /// </summary>
+ /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param>
+ /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param>
+ protected ConsumerBase(ServiceProviderDescription serviceDescription, ITokenManager tokenManager) {
+ if (serviceDescription == null) {
+ throw new ArgumentNullException("serviceDescription");
+ }
+ if (tokenManager == null) {
+ throw new ArgumentNullException("tokenManager");
+ }
+
+ this.WebRequestHandler = new StandardWebRequestHandler();
+ ITamperProtectionChannelBindingElement signingElement = serviceDescription.CreateTamperProtectionElement();
+ INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge);
+ this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager, new OAuthConsumerMessageTypeProvider(), this.WebRequestHandler);
+ this.ServiceProvider = serviceDescription;
+ }
+
+ /// <summary>
+ /// Gets or sets the Consumer Key used to communicate with the Service Provider.
+ /// </summary>
+ public string ConsumerKey { get; set; }
+
+ /// <summary>
+ /// Gets the Service Provider that will be accessed.
+ /// </summary>
+ public ServiceProviderDescription ServiceProvider { get; private set; }
+
+ /// <summary>
+ /// Gets the persistence store for tokens and secrets.
+ /// </summary>
+ public ITokenManager TokenManager {
+ get { return this.OAuthChannel.TokenManager; }
+ }
+
+ /// <summary>
+ /// Gets the channel to use for sending/receiving messages.
+ /// </summary>
+ public Channel Channel {
+ get { return this.OAuthChannel; }
+ }
+
+ /// <summary>
+ /// Gets or sets the channel to use for sending/receiving messages.
+ /// </summary>
+ internal OAuthChannel OAuthChannel { get; 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>
+ /// Creates a web request prepared with OAuth authorization
+ /// that may be further tailored by adding parameters by the caller.
+ /// </summary>
+ /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param>
+ /// <param name="accessToken">The access token that permits access to the protected resource.</param>
+ /// <returns>The initialized WebRequest object.</returns>
+ public WebRequest PrepareAuthorizedRequest(MessageReceivingEndpoint endpoint, string accessToken) {
+ IDirectedProtocolMessage message = this.CreateAuthorizingMessage(endpoint, accessToken);
+ HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message);
+ return wr;
+ }
+
+ /// <summary>
+ /// Creates a web request prepared with OAuth authorization
+ /// that may be further tailored by adding parameters by the caller.
+ /// </summary>
+ /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param>
+ /// <param name="accessToken">The access token that permits access to the protected resource.</param>
+ /// <returns>The initialized WebRequest object.</returns>
+ /// <exception cref="WebException">Thrown if the request fails for any reason after it is sent to the Service Provider.</exception>
+ public Response PrepareAuthorizedRequestAndSend(MessageReceivingEndpoint endpoint, string accessToken) {
+ IDirectedProtocolMessage message = this.CreateAuthorizingMessage(endpoint, accessToken);
+ HttpWebRequest wr = this.OAuthChannel.InitializeRequest(message);
+ return this.WebRequestHandler.GetResponse(wr);
+ }
+
+ /// <summary>
+ /// Prepares an OAuth message that begins an authorization request that will
+ /// redirect the user to the Service Provider to provide that authorization.
+ /// </summary>
+ /// <param name="callback">
+ /// An optional Consumer URL that the Service Provider should redirect the
+ /// User Agent to upon successful authorization.
+ /// </param>
+ /// <param name="requestParameters">Extra parameters to add to the request token message. Optional.</param>
+ /// <param name="redirectParameters">Extra parameters to add to the redirect to Service Provider message. Optional.</param>
+ /// <param name="requestToken">The request token that must be exchanged for an access token after the user has provided authorization.</param>
+ /// <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) {
+ ConsumerKey = this.ConsumerKey,
+ };
+ token.AddNonOAuthParameters(requestParameters);
+ var requestTokenResponse = this.Channel.Request<UnauthorizedTokenResponse>(token);
+ this.TokenManager.StoreNewRequestToken(token, requestTokenResponse);
+
+ // Request user authorization.
+ ITokenContainingMessage assignedRequestToken = requestTokenResponse;
+ var requestAuthorization = new UserAuthorizationRequest(this.ServiceProvider.UserAuthorizationEndpoint, assignedRequestToken.Token) {
+ Callback = callback,
+ };
+ requestAuthorization.AddNonOAuthParameters(redirectParameters);
+ requestToken = requestAuthorization.RequestToken;
+ return requestAuthorization;
+ }
+
+ /// <summary>
+ /// Creates a web request prepared with OAuth authorization
+ /// that may be further tailored by adding parameters by the caller.
+ /// </summary>
+ /// <param name="endpoint">The URL and method on the Service Provider to send the request to.</param>
+ /// <param name="accessToken">The access token that permits access to the protected resource.</param>
+ /// <returns>The initialized WebRequest object.</returns>
+ protected internal AccessProtectedResourceRequest CreateAuthorizingMessage(MessageReceivingEndpoint endpoint, string accessToken) {
+ if (endpoint == null) {
+ throw new ArgumentNullException("endpoint");
+ }
+ if (String.IsNullOrEmpty(accessToken)) {
+ throw new ArgumentNullException("accessToken");
+ }
+
+ AccessProtectedResourceRequest message = new AccessProtectedResourceRequest(endpoint) {
+ AccessToken = accessToken,
+ ConsumerKey = this.ConsumerKey,
+ };
+
+ return message;
+ }
+
+ /// <summary>
+ /// 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) {
+ var requestAccess = new AuthorizedTokenRequest(this.ServiceProvider.AccessTokenEndpoint) {
+ RequestToken = requestToken,
+ ConsumerKey = this.ConsumerKey,
+ };
+ var grantAccess = this.Channel.Request<AuthorizedTokenResponse>(requestAccess);
+ this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(this.ConsumerKey, requestToken, grantAccess.AccessToken, grantAccess.TokenSecret);
+ return grantAccess;
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/DesktopConsumer.cs b/src/DotNetOAuth/OAuth/DesktopConsumer.cs new file mode 100644 index 0000000..62d64f2 --- /dev/null +++ b/src/DotNetOAuth/OAuth/DesktopConsumer.cs @@ -0,0 +1,55 @@ +//-----------------------------------------------------------------------
+// <copyright file="DesktopConsumer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.OAuth.ChannelElements;
+ using DotNetOAuth.OAuth.Messages;
+
+ /// <summary>
+ /// Used by a desktop application to use OAuth to access the Service Provider on behalf of the User.
+ /// </summary>
+ /// <remarks>
+ /// The methods on this class are thread-safe. Provided the properties are set and not changed
+ /// afterward, a single instance of this class may be used by an entire desktop application safely.
+ /// </remarks>
+ public class DesktopConsumer : ConsumerBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="DesktopConsumer"/> class.
+ /// </summary>
+ /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param>
+ /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param>
+ public DesktopConsumer(ServiceProviderDescription serviceDescription, ITokenManager tokenManager)
+ : base(serviceDescription, tokenManager) {
+ }
+
+ /// <summary>
+ /// Begins an OAuth authorization request.
+ /// </summary>
+ /// <param name="requestParameters">Extra parameters to add to the request token message. Optional.</param>
+ /// <param name="redirectParameters">Extra parameters to add to the redirect to Service Provider message. Optional.</param>
+ /// <param name="requestToken">The request token that must be exchanged for an access token after the user has provided authorization.</param>
+ /// <returns>The URL to open a browser window to allow the user to provide authorization.</returns>
+ [SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "2#", Justification = "Two results")]
+ public Uri RequestUserAuthorization(IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters, out string requestToken) {
+ var message = this.PrepareRequestUserAuthorization(null, requestParameters, redirectParameters, out requestToken);
+ Response response = this.Channel.Send(message);
+ return response.DirectUriRequest;
+ }
+
+ /// <summary>
+ /// 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>
+ public new AuthorizedTokenResponse ProcessUserAuthorization(string requestToken) {
+ return base.ProcessUserAuthorization(requestToken);
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOAuth/OAuth/Messages/AccessProtectedResourceRequest.cs new file mode 100644 index 0000000..2704dd5 --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/AccessProtectedResourceRequest.cs @@ -0,0 +1,45 @@ +//-----------------------------------------------------------------------
+// <copyright file="AccessProtectedResourceRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A message attached to a request for protected resources that provides the necessary
+ /// credentials to be granted access to those resources.
+ /// </summary>
+ public class AccessProtectedResourceRequest : SignedMessageBase, ITokenContainingMessage {
+ /// <summary>
+ /// 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) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Token.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "This property IS accessible by a different name.")]
+ string ITokenContainingMessage.Token {
+ get { return this.AccessToken; }
+ set { this.AccessToken = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Access Token.
+ /// </summary>
+ /// <remarks>
+ /// In addition to just allowing OAuth to verify a valid message,
+ /// this property is useful on the Service Provider to verify that the access token
+ /// has proper authorization for the resource being requested, and to know the
+ /// context around which user provided the authorization.
+ /// </remarks>
+ [MessagePart("oauth_token", IsRequired = true)]
+ public string AccessToken { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/AuthorizedTokenRequest.cs b/src/DotNetOAuth/OAuth/Messages/AuthorizedTokenRequest.cs new file mode 100644 index 0000000..8f76046 --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/AuthorizedTokenRequest.cs @@ -0,0 +1,53 @@ +//-----------------------------------------------------------------------
+// <copyright file="AuthorizedTokenRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System.Globalization;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A direct message sent by the Consumer to exchange an authorized Request Token
+ /// for an Access Token and Token Secret.
+ /// </summary>
+ /// <remarks>
+ /// The class is sealed because the OAuth spec forbids adding parameters to this message.
+ /// </remarks>
+ public sealed class AuthorizedTokenRequest : SignedMessageBase, ITokenContainingMessage {
+ /// <summary>
+ /// 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) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Token.
+ /// </summary>
+ string ITokenContainingMessage.Token {
+ get { return this.RequestToken; }
+ set { this.RequestToken = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the unauthorized Request Token used to obtain authorization.
+ /// </summary>
+ [MessagePart("oauth_token", IsRequired = true)]
+ internal string RequestToken { get; set; }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ protected override void EnsureValidMessage() {
+ base.EnsureValidMessage();
+
+ if (this.ExtraData.Count > 0) {
+ throw new ProtocolException(string.Format(CultureInfo.CurrentCulture, Strings.MessageNotAllowedExtraParameters, GetType().Name));
+ }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/AuthorizedTokenResponse.cs b/src/DotNetOAuth/OAuth/Messages/AuthorizedTokenResponse.cs new file mode 100644 index 0000000..4c28edd --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/AuthorizedTokenResponse.cs @@ -0,0 +1,60 @@ +//-----------------------------------------------------------------------
+// <copyright file="AuthorizedTokenResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A direct message sent from Service Provider to Consumer in response to
+ /// a Consumer's <see cref="AuthorizedTokenRequest"/> request.
+ /// </summary>
+ public class AuthorizedTokenResponse : MessageBase, ITokenSecretContainingMessage {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="AuthorizedTokenResponse"/> class.
+ /// </summary>
+ protected internal AuthorizedTokenResponse()
+ : base(MessageProtections.None, MessageTransport.Direct) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Access Token assigned by the Service Provider.
+ /// </summary>
+ [MessagePart("oauth_token", IsRequired = true)]
+ public string AccessToken { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Request or Access Token.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "This property IS accessible by a different name.")]
+ string ITokenContainingMessage.Token {
+ get { return this.AccessToken; }
+ set { this.AccessToken = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Request or Access Token secret.
+ /// </summary>
+ string ITokenSecretContainingMessage.TokenSecret {
+ get { return this.TokenSecret; }
+ set { this.TokenSecret = value; }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-OAuth parameters that will be included in the message.
+ /// </summary>
+ public new IDictionary<string, string> ExtraData {
+ get { return base.ExtraData; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Token Secret.
+ /// </summary>
+ [MessagePart("oauth_token_secret", IsRequired = true)]
+ protected internal string TokenSecret { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/ITokenContainingMessage.cs b/src/DotNetOAuth/OAuth/Messages/ITokenContainingMessage.cs new file mode 100644 index 0000000..57ee86d --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/ITokenContainingMessage.cs @@ -0,0 +1,17 @@ +//-----------------------------------------------------------------------
+// <copyright file="ITokenContainingMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ /// <summary>
+ /// An interface implemented by all OAuth messages that have a request or access token property.
+ /// </summary>
+ public interface ITokenContainingMessage {
+ /// <summary>
+ /// Gets or sets the Request or Access Token.
+ /// </summary>
+ string Token { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/ITokenSecretContainingMessage.cs b/src/DotNetOAuth/OAuth/Messages/ITokenSecretContainingMessage.cs new file mode 100644 index 0000000..98d09ee --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/ITokenSecretContainingMessage.cs @@ -0,0 +1,17 @@ +//-----------------------------------------------------------------------
+// <copyright file="ITokenSecretContainingMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ /// <summary>
+ /// An interface implemented by all OAuth messages that have a request or access token and secret properties.
+ /// </summary>
+ public interface ITokenSecretContainingMessage : ITokenContainingMessage {
+ /// <summary>
+ /// Gets or sets the Request or Access Token secret.
+ /// </summary>
+ string TokenSecret { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/MessageBase.cs b/src/DotNetOAuth/OAuth/Messages/MessageBase.cs new file mode 100644 index 0000000..a74b9f2 --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/MessageBase.cs @@ -0,0 +1,239 @@ +//-----------------------------------------------------------------------
+// <copyright file="MessageBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Globalization;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Reflection;
+ using DotNetOAuth.OAuth.ChannelElements;
+
+ /// <summary>
+ /// A base class for all OAuth messages.
+ /// </summary>
+ public abstract class MessageBase : IOAuthDirectedMessage {
+ /// <summary>
+ /// A store for extra name/value data pairs that are attached to this message.
+ /// </summary>
+ private Dictionary<string, string> extraData = new Dictionary<string, string>();
+
+ /// <summary>
+ /// Gets a value indicating whether signing this message is required.
+ /// </summary>
+ private MessageProtections protectionRequired;
+
+ /// <summary>
+ /// Gets a value indicating whether this is a direct or indirect message.
+ /// </summary>
+ private MessageTransport transport;
+
+ /// <summary>
+ /// The URI to the remote endpoint to send this message to.
+ /// </summary>
+ private MessageReceivingEndpoint recipient;
+
+#if DEBUG
+ /// <summary>
+ /// Initializes static members of the <see cref="MessageBase"/> class.
+ /// </summary>
+ static MessageBase() {
+ LowSecurityMode = true;
+ }
+#endif
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MessageBase"/> class.
+ /// </summary>
+ /// <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>
+ protected MessageBase(MessageProtections protectionRequired, MessageTransport transport) {
+ this.protectionRequired = protectionRequired;
+ this.transport = transport;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MessageBase"/> class.
+ /// </summary>
+ /// <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");
+ }
+
+ this.protectionRequired = protectionRequired;
+ this.transport = transport;
+ this.recipient = recipient;
+ }
+
+ #region IProtocolMessage Properties
+
+ /// <summary>
+ /// Gets the version of the protocol this message is prepared to implement.
+ /// </summary>
+ Version IProtocolMessage.ProtocolVersion {
+ get { return this.ProtocolVersion; }
+ }
+
+ /// <summary>
+ /// Gets the level of protection this message requires.
+ /// </summary>
+ MessageProtections IProtocolMessage.RequiredProtection {
+ get { return this.RequiredProtection; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this is a direct or indirect message.
+ /// </summary>
+ MessageTransport IProtocolMessage.Transport {
+ get { return this.Transport; }
+ }
+
+ /// <summary>
+ /// Gets the dictionary of additional name/value fields tacked on to this message.
+ /// </summary>
+ IDictionary<string, string> IProtocolMessage.ExtraData {
+ get { return this.ExtraData; }
+ }
+
+ #endregion
+
+ #region IDirectedProtocolMessage Members
+
+ /// <summary>
+ /// Gets the URI to the Service Provider endpoint to send this message to.
+ /// </summary>
+ Uri IDirectedProtocolMessage.Recipient {
+ get { return this.recipient.Location; }
+ }
+
+ #endregion
+
+ #region IOAuthDirectedMessage Properties
+
+ /// <summary>
+ /// Gets the preferred method of transport for the message.
+ /// </summary>
+ HttpDeliveryMethods IOAuthDirectedMessage.HttpMethods {
+ get { return this.HttpMethods; }
+ }
+
+ /// <summary>
+ /// Gets or sets the URI to the Service Provider endpoint to send this message to.
+ /// </summary>
+ Uri IOAuthDirectedMessage.Recipient {
+ get { return this.Recipient; }
+ set { this.Recipient = value; }
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Gets or sets a value indicating whether security sensitive strings are
+ /// emitted from the ToString() method.
+ /// </summary>
+ internal static bool LowSecurityMode { get; set; }
+
+ /// <summary>
+ /// Gets the version of the protocol this message is prepared to implement.
+ /// </summary>
+ protected virtual Version ProtocolVersion {
+ get { return new Version(1, 0); }
+ }
+
+ /// <summary>
+ /// Gets the level of protection this message requires.
+ /// </summary>
+ protected MessageProtections RequiredProtection {
+ get { return this.protectionRequired; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this is a direct or indirect message.
+ /// </summary>
+ protected MessageTransport Transport {
+ get { return this.transport; }
+ }
+
+ /// <summary>
+ /// Gets the dictionary of additional name/value fields tacked on to this message.
+ /// </summary>
+ protected IDictionary<string, string> ExtraData {
+ get { return this.extraData; }
+ }
+
+ /// <summary>
+ /// Gets the preferred method of transport for the message.
+ /// </summary>
+ protected HttpDeliveryMethods HttpMethods {
+ get { return this.recipient != null ? this.recipient.AllowedMethods : HttpDeliveryMethods.None; }
+ }
+
+ /// <summary>
+ /// Gets or sets the URI to the Service Provider endpoint to send this message to.
+ /// </summary>
+ protected Uri Recipient {
+ get {
+ return this.recipient != null ? this.recipient.Location : null;
+ }
+
+ set {
+ if (this.recipient != null) {
+ this.recipient = new MessageReceivingEndpoint(value, this.recipient.AllowedMethods);
+ } else if (value != null) {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+
+ #region IProtocolMessage Methods
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ void IProtocolMessage.EnsureValidMessage() {
+ this.EnsureValidMessage();
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Returns a human-friendly string describing the message and all serializable properties.
+ /// </summary>
+ /// <returns>The string representation of this object.</returns>
+ public override string ToString() {
+ StringBuilder builder = new StringBuilder();
+ builder.AppendFormat(CultureInfo.InvariantCulture, "{0} message", GetType().Name);
+ if (this.recipient != null) {
+ builder.AppendFormat(CultureInfo.InvariantCulture, " as {0} to {1}", this.recipient.AllowedMethods, this.recipient.Location);
+ }
+ builder.AppendLine();
+ MessageDictionary dictionary = new MessageDictionary(this);
+ foreach (var pair in dictionary) {
+ string value = pair.Value;
+ if (pair.Key == "oauth_signature" && !LowSecurityMode) {
+ value = "xxxxxxxxxxxxx (not shown)";
+ }
+ builder.Append('\t');
+ builder.Append(pair.Key);
+ builder.Append(": ");
+ builder.AppendLine(value);
+ }
+
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// Checks the message state for conformity to the protocol specification
+ /// and throws an exception if the message is invalid.
+ /// </summary>
+ protected virtual void EnsureValidMessage() { }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/OAuth Messages.cd b/src/DotNetOAuth/OAuth/Messages/OAuth Messages.cd new file mode 100644 index 0000000..414644a --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/OAuth Messages.cd @@ -0,0 +1,215 @@ +<?xml version="1.0" encoding="utf-8"?>
+<ClassDiagram MajorVersion="1" MinorVersion="1" GroupingSetting="Access">
+ <Comment CommentText="Messages from Service Provider">
+ <Position X="7.912" Y="0.715" Height="0.291" Width="2.02" />
+ </Comment>
+ <Comment CommentText="Messages from Consumer">
+ <Position X="4.36" Y="0.683" Height="0.291" Width="2.02" />
+ </Comment>
+ <Class Name="DotNetOAuth.OAuth.Messages.AccessProtectedResourceRequest">
+ <Position X="4.25" Y="7.75" Width="3" />
+ <Members>
+ <Property Name="ITokenContainingMessage.Token" Hidden="true" />
+ </Members>
+ <InheritanceLine Type="DotNetOAuth.OAuth.Messages.SignedMessageBase" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+ <Path>
+ <Point X="3.5" Y="5.626" />
+ <Point X="3.5" Y="8.25" />
+ <Point X="4.25" Y="8.25" />
+ </Path>
+ </InheritanceLine>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>Messages\AccessProtectedResourceRequest.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOAuth.OAuth.Messages.UnauthorizedTokenResponse">
+ <Position X="7.5" Y="1.5" Width="3" />
+ <Members>
+ <Property Name="ITokenContainingMessage.Token" Hidden="true" />
+ <Property Name="ITokenSecretContainingMessage.TokenSecret" Hidden="true" />
+ </Members>
+ <Compartments>
+ <Compartment Name="Methods" Collapsed="true" />
+ </Compartments>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAEAAAAAAIAAIAAAIAAAAAAAAAA=</HashCode>
+ <FileName>Messages\UnauthorizedTokenResponse.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOAuth.OAuth.Messages.UserAuthorizationResponse">
+ <Position X="7.5" Y="4.5" Width="3" />
+ <Members>
+ <Property Name="ITokenContainingMessage.Token" Hidden="true" />
+ </Members>
+ <Compartments>
+ <Compartment Name="Methods" Collapsed="true" />
+ </Compartments>
+ <InheritanceLine Type="DotNetOAuth.OAuth.Messages.MessageBase" FixedFromPoint="true" FixedToPoint="true">
+ <Path>
+ <Point X="10.75" Y="4.688" />
+ <Point X="10.5" Y="4.688" />
+ </Path>
+ </InheritanceLine>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>Messages\UserAuthorizationResponse.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOAuth.OAuth.Messages.UserAuthorizationRequest">
+ <Position X="4.25" Y="3" Width="3" />
+ <Members>
+ <Property Name="ITokenContainingMessage.Token" Hidden="true" />
+ </Members>
+ <Compartments>
+ <Compartment Name="Methods" Collapsed="true" />
+ </Compartments>
+ <InheritanceLine Type="DotNetOAuth.OAuth.Messages.MessageBase" FixedFromPoint="true" FixedToPoint="true">
+ <Path>
+ <Point X="10.75" Y="4.125" />
+ <Point X="7.25" Y="4.125" />
+ </Path>
+ </InheritanceLine>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAAAAAAAAIAAAAAAIAAAAAAAQAA=</HashCode>
+ <FileName>Messages\UserAuthorizationRequest.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOAuth.OAuth.Messages.AuthorizedTokenResponse">
+ <Position X="7.5" Y="6.25" Width="3" />
+ <Members>
+ <Property Name="ITokenContainingMessage.Token" Hidden="true" />
+ <Property Name="ITokenSecretContainingMessage.TokenSecret" Hidden="true" />
+ </Members>
+ <Compartments>
+ <Compartment Name="Methods" Collapsed="true" />
+ </Compartments>
+ <InheritanceLine Type="DotNetOAuth.OAuth.Messages.MessageBase" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+ <Path>
+ <Point X="11" Y="4.805" />
+ <Point X="11" Y="7.013" />
+ <Point X="10.5" Y="7.013" />
+ </Path>
+ </InheritanceLine>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAAAAAAAEAAAIAAAIAAAAAAAAAA=</HashCode>
+ <FileName>Messages\AuthorizedTokenResponse.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOAuth.OAuth.Messages.MessageBase">
+ <Position X="10.75" Y="1" Width="3.5" />
+ <Members>
+ <Field Name="extraData" Hidden="true" />
+ <Property Name="IDirectedProtocolMessage.Recipient" Hidden="true" />
+ <Property Name="IOAuthDirectedMessage.HttpMethods" Hidden="true" />
+ <Property Name="IOAuthDirectedMessage.Recipient" Hidden="true" />
+ <Method Name="IProtocolMessage.EnsureValidMessage" Hidden="true" />
+ <Property Name="IProtocolMessage.ExtraData" Hidden="true" />
+ <Property Name="IProtocolMessage.ProtocolVersion" Hidden="true" />
+ <Property Name="IProtocolMessage.RequiredProtection" Hidden="true" />
+ <Property Name="IProtocolMessage.Transport" Hidden="true" />
+ <Field Name="protectionRequired" Hidden="true" />
+ <Field Name="recipient" Hidden="true" />
+ <Field Name="transport" Hidden="true" />
+ </Members>
+ <Compartments>
+ <Compartment Name="Fields" Collapsed="true" />
+ </Compartments>
+ <TypeIdentifier>
+ <HashCode>AAAKAAAAYAAAgAEEAIAAAAYAAAQEIDAAIgCCACAAAAA=</HashCode>
+ <FileName>Messages\MessageBase.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOAuth.OAuth.Messages.AuthorizedTokenRequest">
+ <Position X="4.25" Y="5.5" Width="3" />
+ <Members>
+ <Property Name="ITokenContainingMessage.Token" Hidden="true" />
+ </Members>
+ <Compartments>
+ <Compartment Name="Methods" Collapsed="true" />
+ </Compartments>
+ <InheritanceLine Type="DotNetOAuth.OAuth.Messages.SignedMessageBase" ManuallyRouted="true" FixedFromPoint="true" FixedToPoint="true">
+ <Path>
+ <Point X="4" Y="4.947" />
+ <Point X="4.123" Y="4.947" />
+ <Point X="4.123" Y="5.75" />
+ <Point X="4.25" Y="5.75" />
+ </Path>
+ </InheritanceLine>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACQAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>Messages\AuthorizedTokenRequest.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOAuth.OAuth.Messages.UnauthorizedTokenRequest">
+ <Position X="4.25" Y="1" Width="3" />
+ <Compartments>
+ <Compartment Name="Internal" Collapsed="true" />
+ <Compartment Name="Private" Collapsed="true" />
+ <Compartment Name="Methods" Collapsed="true" />
+ </Compartments>
+ <InheritanceLine Type="DotNetOAuth.OAuth.Messages.SignedMessageBase" FixedFromPoint="true">
+ <Path>
+ <Point X="4" Y="1.855" />
+ <Point X="4.25" Y="1.855" />
+ </Path>
+ </InheritanceLine>
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAA=</HashCode>
+ <FileName>Messages\UnauthorizedTokenRequest.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOAuth.OAuth.Messages.SignedMessageBase">
+ <Position X="0.5" Y="1.5" Width="3.5" />
+ <Members>
+ <Property Name="ITamperResistantOAuthMessage.ConsumerSecret" Hidden="true" />
+ <Property Name="ITamperResistantOAuthMessage.HttpMethod" Hidden="true" />
+ <Property Name="ITamperResistantOAuthMessage.SignatureMethod" Hidden="true" />
+ <Property Name="ITamperResistantOAuthMessage.TokenSecret" Hidden="true" />
+ <Property Name="ITamperResistantProtocolMessage.Signature" Hidden="true" />
+ </Members>
+ <InheritanceLine Type="DotNetOAuth.OAuth.Messages.MessageBase" FixedFromPoint="true" FixedToPoint="true">
+ <Path>
+ <Point X="12.875" Y="1" />
+ <Point X="12.875" Y="0.625" />
+ <Point X="3" Y="0.625" />
+ <Point X="3" Y="1.5" />
+ </Path>
+ </InheritanceLine>
+ <TypeIdentifier>
+ <HashCode>IIAAFAAAAIAAAAAAgICAAgAAAgAAIAQAAAEAIAAQAAE=</HashCode>
+ <FileName>Messages\SignedMessageBase.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Interface Name="DotNetOAuth.OAuth.ChannelElements.ITamperResistantOAuthMessage">
+ <Position X="11.25" Y="5.25" Width="2.5" />
+ <TypeIdentifier>
+ <HashCode>AIAAAAAAAAAAAAAAAIAAAgAAAAAAIAQAAAAAAAAAAAA=</HashCode>
+ <FileName>ChannelElements\ITamperResistantOAuthMessage.cs</FileName>
+ </TypeIdentifier>
+ </Interface>
+ <Interface Name="DotNetOAuth.OAuth.Messages.ITokenSecretContainingMessage">
+ <Position X="1" Y="7.5" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAA=</HashCode>
+ <FileName>Messages\ITokenSecretContainingMessage.cs</FileName>
+ <NewMemberFileName>Messages\ITokenContainingMessage.cs</NewMemberFileName>
+ </TypeIdentifier>
+ </Interface>
+ <Interface Name="DotNetOAuth.OAuth.Messages.ITokenContainingMessage">
+ <Position X="1" Y="6" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAA=</HashCode>
+ <FileName>Messages\ITokenContainingMessage.cs</FileName>
+ </TypeIdentifier>
+ </Interface>
+ <Font Name="Segoe UI" Size="9" />
+</ClassDiagram>
\ No newline at end of file diff --git a/src/DotNetOAuth/OAuth/Messages/SignedMessageBase.cs b/src/DotNetOAuth/OAuth/Messages/SignedMessageBase.cs new file mode 100644 index 0000000..5d94013 --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/SignedMessageBase.cs @@ -0,0 +1,161 @@ +//-----------------------------------------------------------------------
+// <copyright file="SignedMessageBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+ using DotNetOAuth.OAuth.ChannelElements;
+
+ /// <summary>
+ /// A base class for all signed OAuth messages.
+ /// </summary>
+ public class SignedMessageBase : MessageBase, ITamperResistantOAuthMessage, IExpiringProtocolMessage, IReplayProtectedProtocolMessage {
+ /// <summary>
+ /// The reference date and time for calculating time stamps.
+ /// </summary>
+ private static readonly DateTime epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
+
+ /// <summary>
+ /// The number of seconds since 1/1/1970, consistent with the OAuth timestamp requirement.
+ /// </summary>
+ [MessagePart("oauth_timestamp", IsRequired = true)]
+ private long timestamp;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SignedMessageBase"/> class.
+ /// </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) {
+ ITamperResistantOAuthMessage self = (ITamperResistantOAuthMessage)this;
+ HttpDeliveryMethods methods = ((IOAuthDirectedMessage)this).HttpMethods;
+ self.HttpMethod = (methods & HttpDeliveryMethods.PostRequest) != 0 ? "POST" : "GET";
+ }
+
+ #region ITamperResistantOAuthMessage Members
+
+ /// <summary>
+ /// Gets or sets the signature method used to sign the request.
+ /// </summary>
+ string ITamperResistantOAuthMessage.SignatureMethod {
+ get { return this.SignatureMethod; }
+ set { this.SignatureMethod = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Token Secret used to sign the message.
+ /// </summary>
+ string ITamperResistantOAuthMessage.TokenSecret {
+ get { return this.TokenSecret; }
+ set { this.TokenSecret = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Consumer key.
+ /// </summary>
+ [MessagePart("oauth_consumer_key", IsRequired = true)]
+ public string ConsumerKey { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Consumer Secret used to sign the message.
+ /// </summary>
+ string ITamperResistantOAuthMessage.ConsumerSecret {
+ get { return this.ConsumerSecret; }
+ set { this.ConsumerSecret = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the HTTP method that will be used to transmit the message.
+ /// </summary>
+ string ITamperResistantOAuthMessage.HttpMethod {
+ get { return this.HttpMethod; }
+ set { this.HttpMethod = value; }
+ }
+
+ #endregion
+
+ #region ITamperResistantProtocolMessage Members
+
+ /// <summary>
+ /// Gets or sets the message signature.
+ /// </summary>
+ string ITamperResistantProtocolMessage.Signature {
+ get { return this.Signature; }
+ set { this.Signature = value; }
+ }
+
+ #endregion
+
+ #region IExpiringProtocolMessage Members
+
+ /// <summary>
+ /// Gets or sets the OAuth timestamp of the message.
+ /// </summary>
+ DateTime IExpiringProtocolMessage.UtcCreationDate {
+ get { return epoch + TimeSpan.FromSeconds(this.timestamp); }
+ set { this.timestamp = (long)(value - epoch).TotalSeconds; }
+ }
+
+ #endregion
+
+ #region IReplayProtectedProtocolMessage Members
+
+ /// <summary>
+ /// Gets or sets the message nonce used for replay detection.
+ /// </summary>
+ [MessagePart("oauth_nonce", IsRequired = true)]
+ string IReplayProtectedProtocolMessage.Nonce { get; set; }
+
+ #endregion
+
+ /// <summary>
+ /// Gets or sets the signature method used to sign the request.
+ /// </summary>
+ [MessagePart("oauth_signature_method", IsRequired = true)]
+ protected string SignatureMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Token Secret used to sign the message.
+ /// </summary>
+ protected string TokenSecret { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Consumer Secret used to sign the message.
+ /// </summary>
+ protected string ConsumerSecret { get; set; }
+
+ /// <summary>
+ /// Gets or sets the HTTP method that will be used to transmit the message.
+ /// </summary>
+ protected string HttpMethod { get; set; }
+
+ /// <summary>
+ /// Gets or sets the message signature.
+ /// </summary>
+ [MessagePart("oauth_signature", IsRequired = true)]
+ protected string Signature { get; set; }
+
+ /// <summary>
+ /// Gets or sets the version of the protocol this message was created with.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Accessed via reflection.")]
+ [MessagePart("oauth_version", IsRequired = false)]
+ private string Version {
+ get {
+ return ProtocolVersion.ToString();
+ }
+
+ set {
+ if (value != this.Version) {
+ throw new ArgumentOutOfRangeException("value");
+ }
+ }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/UnauthorizedTokenRequest.cs b/src/DotNetOAuth/OAuth/Messages/UnauthorizedTokenRequest.cs new file mode 100644 index 0000000..7d83135 --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/UnauthorizedTokenRequest.cs @@ -0,0 +1,30 @@ +//-----------------------------------------------------------------------
+// <copyright file="UnauthorizedTokenRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System.Collections.Generic;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A direct message sent from Consumer to Service Provider to request a Request Token.
+ /// </summary>
+ public class UnauthorizedTokenRequest : SignedMessageBase {
+ /// <summary>
+ /// 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) {
+ }
+
+ /// <summary>
+ /// Gets the extra, non-OAuth parameters that will be included in the message.
+ /// </summary>
+ public new IDictionary<string, string> ExtraData {
+ get { return base.ExtraData; }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/UnauthorizedTokenResponse.cs b/src/DotNetOAuth/OAuth/Messages/UnauthorizedTokenResponse.cs new file mode 100644 index 0000000..f527943 --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/UnauthorizedTokenResponse.cs @@ -0,0 +1,92 @@ +//-----------------------------------------------------------------------
+// <copyright file="UnauthorizedTokenResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A direct message sent from Service Provider to Consumer in response to
+ /// a Consumer's <see cref="UnauthorizedTokenRequest"/> request.
+ /// </summary>
+ public class UnauthorizedTokenResponse : MessageBase, ITokenSecretContainingMessage {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class.
+ /// </summary>
+ /// <param name="requestMessage">The unauthorized request token message that this message is being generated in response to.</param>
+ /// <param name="requestToken">The request token.</param>
+ /// <param name="tokenSecret">The token secret.</param>
+ /// <remarks>
+ /// This constructor is used by the Service Provider to send the message.
+ /// </remarks>
+ protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest requestMessage, string requestToken, string tokenSecret) : this() {
+ if (requestMessage == null) {
+ throw new ArgumentNullException("requestMessage");
+ }
+ if (string.IsNullOrEmpty(requestToken)) {
+ throw new ArgumentNullException("requestToken");
+ }
+ if (string.IsNullOrEmpty(tokenSecret)) {
+ throw new ArgumentNullException("tokenSecret");
+ }
+
+ this.RequestMessage = requestMessage;
+ this.RequestToken = requestToken;
+ this.TokenSecret = tokenSecret;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class.
+ /// </summary>
+ /// <remarks>This constructor is used by the consumer to deserialize the message.</remarks>
+ protected internal UnauthorizedTokenResponse()
+ : base(MessageProtections.None, MessageTransport.Direct) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Request or Access Token.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "This property IS accessible by a different name.")]
+ string ITokenContainingMessage.Token {
+ get { return this.RequestToken; }
+ set { this.RequestToken = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Request or Access Token secret.
+ /// </summary>
+ string ITokenSecretContainingMessage.TokenSecret {
+ get { return this.TokenSecret; }
+ set { this.TokenSecret = value; }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-OAuth parameters that will be included in the message.
+ /// </summary>
+ public new IDictionary<string, string> ExtraData {
+ get { return base.ExtraData; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Request Token.
+ /// </summary>
+ [MessagePart("oauth_token", IsRequired = true)]
+ internal string RequestToken { get; set; }
+
+ /// <summary>
+ /// Gets the original request for an unauthorized token.
+ /// </summary>
+ internal UnauthorizedTokenRequest RequestMessage { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the Token Secret.
+ /// </summary>
+ [MessagePart("oauth_token_secret", IsRequired = true)]
+ protected internal string TokenSecret { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/UserAuthorizationRequest.cs b/src/DotNetOAuth/OAuth/Messages/UserAuthorizationRequest.cs new file mode 100644 index 0000000..79dd966 --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/UserAuthorizationRequest.cs @@ -0,0 +1,71 @@ +//-----------------------------------------------------------------------
+// <copyright file="UserAuthorizationRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A message used to redirect the user from a Consumer to a Service Provider's web site
+ /// so the Service Provider can ask the user to authorize the Consumer's access to some
+ /// protected resource(s).
+ /// </summary>
+ 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) {
+ this.RequestToken = requestToken;
+ }
+
+ /// <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>
+ internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider)
+ : base(MessageProtections.None, MessageTransport.Indirect, serviceProvider) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Request or Access Token.
+ /// </summary>
+ [SuppressMessage("Microsoft.Design", "CA1033:InterfaceMethodsShouldBeCallableByChildTypes", Justification = "This property IS accessible by a different name.")]
+ string ITokenContainingMessage.Token {
+ get { return this.RequestToken; }
+ set { this.RequestToken = value; }
+ }
+
+ /// <summary>
+ /// Gets the extra, non-OAuth parameters that will be included in the message.
+ /// </summary>
+ public new IDictionary<string, string> ExtraData {
+ get { return base.ExtraData; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Request Token obtained in the previous step.
+ /// </summary>
+ /// <remarks>
+ /// The Service Provider MAY declare this parameter as REQUIRED, or
+ /// accept requests to the User Authorization URL without it, in which
+ /// case it will prompt the User to enter it manually.
+ /// </remarks>
+ [MessagePart("oauth_token", IsRequired = false)]
+ internal string RequestToken { get; set; }
+
+ /// <summary>
+ /// 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)]
+ internal Uri Callback { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Messages/UserAuthorizationResponse.cs b/src/DotNetOAuth/OAuth/Messages/UserAuthorizationResponse.cs new file mode 100644 index 0000000..ab66dfe --- /dev/null +++ b/src/DotNetOAuth/OAuth/Messages/UserAuthorizationResponse.cs @@ -0,0 +1,40 @@ +//-----------------------------------------------------------------------
+// <copyright file="UserAuthorizationResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth.Messages {
+ using System;
+ using DotNetOAuth.Messaging;
+
+ /// <summary>
+ /// A message used to redirect the user from a Service Provider to a Consumer's web site.
+ /// </summary>
+ /// <remarks>
+ /// The class is sealed because extra parameters are determined by the callback URI provided by the Consumer.
+ /// </remarks>
+ 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)) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Request or Access Token.
+ /// </summary>
+ string ITokenContainingMessage.Token {
+ get { return this.RequestToken; }
+ set { this.RequestToken = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Request Token.
+ /// </summary>
+ [MessagePart("oauth_token", IsRequired = true)]
+ internal string RequestToken { get; set; }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/Protocol.cs b/src/DotNetOAuth/OAuth/Protocol.cs new file mode 100644 index 0000000..c738fc2 --- /dev/null +++ b/src/DotNetOAuth/OAuth/Protocol.cs @@ -0,0 +1,87 @@ +//-----------------------------------------------------------------------
+// <copyright file="Protocol.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOAuth.Messaging;
+
+ /// <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>
+ internal class Protocol {
+ /// <summary>
+ /// The namespace to use for V1.0 of the protocol.
+ /// </summary>
+ internal const string DataContractNamespaceV10 = "http://oauth.net/core/1.0/";
+
+ /// <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,
+ };
+
+ /// <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.
+ /// </summary>
+ private string parameterPrefix = "oauth_";
+
+ /// <summary>
+ /// The scheme to use in Authorization header message requests.
+ /// </summary>
+ private string authorizationHeaderScheme = "OAuth";
+
+ /// <summary>
+ /// Gets the default <see cref="Protocol"/> instance.
+ /// </summary>
+ internal static Protocol Default { get { return V10; } }
+
+ /// <summary>
+ /// Gets the namespace to use for this version of the protocol.
+ /// </summary>
+ internal string DataContractNamespace {
+ get { return this.dataContractNamespace; }
+ }
+
+ /// <summary>
+ /// Gets the prefix used for all key names in the protocol.
+ /// </summary>
+ internal string ParameterPrefix {
+ get { return this.parameterPrefix; }
+ }
+
+ /// <summary>
+ /// Gets the scheme to use in Authorization header message requests.
+ /// </summary>
+ internal string AuthorizationHeaderScheme {
+ get { return this.authorizationHeaderScheme; }
+ }
+
+ /// <summary>
+ /// Gets an instance of <see cref="Protocol"/> given a <see cref="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>
+ internal static Protocol Lookup(Version version) {
+ switch (version.Major) {
+ case 1: return Protocol.V10;
+ default: throw new ArgumentOutOfRangeException("version");
+ }
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/ServiceProvider.cs b/src/DotNetOAuth/OAuth/ServiceProvider.cs new file mode 100644 index 0000000..2dc1609 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ServiceProvider.cs @@ -0,0 +1,396 @@ +//-----------------------------------------------------------------------
+// <copyright file="ServiceProvider.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth {
+ using System;
+ using System.Globalization;
+ using System.ServiceModel.Channels;
+ using System.Web;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.Messaging.Bindings;
+ using DotNetOAuth.OAuth.ChannelElements;
+ using DotNetOAuth.OAuth.Messages;
+
+ /// <summary>
+ /// A web application that allows access via OAuth.
+ /// </summary>
+ /// <remarks>
+ /// <para>The Service Provider’s documentation should include:</para>
+ /// <list>
+ /// <item>The URLs (Request URLs) the Consumer will use when making OAuth requests, and the HTTP methods (i.e. GET, POST, etc.) used in the Request Token URL and Access Token URL.</item>
+ /// <item>Signature methods supported by the Service Provider.</item>
+ /// <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>
+ public class ServiceProvider {
+ /// <summary>
+ /// The field behind the <see cref="OAuthChannel"/> property.
+ /// </summary>
+ private OAuthChannel channel;
+
+ /// <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>
+ public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager)
+ : this(serviceDescription, tokenManager, new OAuthServiceProviderMessageTypeProvider(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="messageTypeProvider">An object that can figure out what type of message is being received for deserialization.</param>
+ public ServiceProvider(ServiceProviderDescription serviceDescription, ITokenManager tokenManager, OAuthServiceProviderMessageTypeProvider messageTypeProvider) {
+ if (serviceDescription == null) {
+ throw new ArgumentNullException("serviceDescription");
+ }
+ if (tokenManager == null) {
+ throw new ArgumentNullException("tokenManager");
+ }
+ if (messageTypeProvider == null) {
+ throw new ArgumentNullException("messageTypeProvider");
+ }
+
+ var signingElement = serviceDescription.CreateTamperProtectionElement();
+ INonceStore store = new NonceMemoryStore(StandardExpirationBindingElement.DefaultMaximumMessageAge);
+ this.ServiceDescription = serviceDescription;
+ this.OAuthChannel = new OAuthChannel(signingElement, store, tokenManager, messageTypeProvider, new StandardWebRequestHandler());
+ this.TokenGenerator = new StandardTokenGenerator();
+ }
+
+ /// <summary>
+ /// Gets the description of this Service Provider.
+ /// </summary>
+ public ServiceProviderDescription ServiceDescription { get; private set; }
+
+ /// <summary>
+ /// Gets or sets the generator responsible for generating new tokens and secrets.
+ /// </summary>
+ public ITokenGenerator TokenGenerator { get; set; }
+
+ /// <summary>
+ /// Gets the persistence store for tokens and secrets.
+ /// </summary>
+ public ITokenManager TokenManager {
+ get { return this.OAuthChannel.TokenManager; }
+ }
+
+ /// <summary>
+ /// Gets the channel to use for sending/receiving messages.
+ /// </summary>
+ public Channel Channel {
+ get { return this.OAuthChannel; }
+ }
+
+ /// <summary>
+ /// Gets or sets the channel to use for sending/receiving messages.
+ /// </summary>
+ internal OAuthChannel OAuthChannel {
+ get {
+ return this.channel;
+ }
+
+ set {
+ if (this.channel != null) {
+ this.channel.Sending -= this.OAuthChannel_Sending;
+ }
+
+ this.channel = value;
+
+ if (this.channel != null) {
+ this.channel.Sending += this.OAuthChannel_Sending;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Reads any incoming OAuth message.
+ /// </summary>
+ /// <returns>The deserialized message.</returns>
+ /// <remarks>
+ /// Requires HttpContext.Current.
+ /// </remarks>
+ public IOAuthDirectedMessage ReadRequest() {
+ return (IOAuthDirectedMessage)this.Channel.ReadFromRequest();
+ }
+
+ /// <summary>
+ /// Reads any incoming OAuth message.
+ /// </summary>
+ /// <param name="request">The HTTP request to read the message from.</param>
+ /// <returns>The deserialized message.</returns>
+ public IOAuthDirectedMessage ReadRequest(HttpRequest request) {
+ return (IOAuthDirectedMessage)this.Channel.ReadFromRequest(new HttpRequestInfo(request));
+ }
+
+ /// <summary>
+ /// Gets the incoming request for an unauthorized token, if any.
+ /// </summary>
+ /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
+ /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
+ /// <remarks>
+ /// Requires HttpContext.Current.
+ /// </remarks>
+ public UnauthorizedTokenRequest ReadTokenRequest() {
+ return this.ReadTokenRequest(this.Channel.GetRequestFromContext());
+ }
+
+ /// <summary>
+ /// Gets the incoming request for an unauthorized token, if any.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request.</param>
+ /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
+ /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
+ public UnauthorizedTokenRequest ReadTokenRequest(HttpRequest request) {
+ return this.ReadTokenRequest(new HttpRequestInfo(request));
+ }
+
+ /// <summary>
+ /// Reads a request for an unauthorized token from the incoming HTTP request.
+ /// </summary>
+ /// <param name="request">The HTTP request to read from.</param>
+ /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
+ /// <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);
+ return message;
+ }
+
+ /// <summary>
+ /// Prepares a message containing an unauthorized token for the Consumer to use in a
+ /// user agent redirect for subsequent authorization.
+ /// </summary>
+ /// <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");
+ }
+
+ string token = this.TokenGenerator.GenerateRequestToken(request.ConsumerKey);
+ string secret = this.TokenGenerator.GenerateSecret();
+ UnauthorizedTokenResponse response = new UnauthorizedTokenResponse(request, token, secret);
+
+ return response;
+ }
+
+ /// <summary>
+ /// Gets the incoming request for the Service Provider to authorize a Consumer's
+ /// access to some protected resources.
+ /// </summary>
+ /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
+ /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
+ /// <remarks>
+ /// Requires HttpContext.Current.
+ /// </remarks>
+ public UserAuthorizationRequest ReadAuthorizationRequest() {
+ return this.ReadAuthorizationRequest(this.Channel.GetRequestFromContext());
+ }
+
+ /// <summary>
+ /// Gets the incoming request for the Service Provider to authorize a Consumer's
+ /// access to some protected resources.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request.</param>
+ /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
+ /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
+ public UserAuthorizationRequest ReadAuthorizationRequest(HttpRequest request) {
+ return this.ReadAuthorizationRequest(new HttpRequestInfo(request));
+ }
+
+ /// <summary>
+ /// Reads in a Consumer's request for the Service Provider to obtain permission from
+ /// the user to authorize the Consumer's access of some protected resource(s).
+ /// </summary>
+ /// <param name="request">The HTTP request to read from.</param>
+ /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
+ /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
+ public UserAuthorizationRequest ReadAuthorizationRequest(HttpRequestInfo request) {
+ UserAuthorizationRequest message;
+ this.Channel.TryReadFromRequest(request, out message);
+ return message;
+ }
+
+ /// <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>
+ /// <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.
+ /// </returns>
+ public UserAuthorizationResponse PrepareAuthorizationResponse(UserAuthorizationRequest request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ if (request.Callback != null) {
+ var authorization = new UserAuthorizationResponse(request.Callback) {
+ RequestToken = request.RequestToken,
+ };
+ return authorization;
+ } else {
+ return null;
+ }
+ }
+
+ /// <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>
+ /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
+ /// <remarks>
+ /// Requires HttpContext.Current.
+ /// </remarks>
+ public AuthorizedTokenRequest ReadAccessTokenRequest() {
+ return this.ReadAccessTokenRequest(this.Channel.GetRequestFromContext());
+ }
+
+ /// <summary>
+ /// Gets the incoming request to exchange an authorized token for an access token.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request.</param>
+ /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
+ /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
+ public AuthorizedTokenRequest ReadAccessTokenRequest(HttpRequest request) {
+ return this.ReadAccessTokenRequest(new HttpRequestInfo(request));
+ }
+
+ /// <summary>
+ /// Reads in a Consumer's request to exchange an authorized request token for an access token.
+ /// </summary>
+ /// <param name="request">The HTTP request to read from.</param>
+ /// <returns>The incoming request, or null if no OAuth message was attached.</returns>
+ /// <exception cref="ProtocolException">Thrown if an unexpected OAuth message is attached to the incoming request.</exception>
+ public AuthorizedTokenRequest ReadAccessTokenRequest(HttpRequestInfo request) {
+ AuthorizedTokenRequest message;
+ this.Channel.TryReadFromRequest(request, out message);
+ return message;
+ }
+
+ /// <summary>
+ /// Prepares and sends an access token to a Consumer, and invalidates the request token.
+ /// </summary>
+ /// <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");
+ }
+
+ if (!this.TokenManager.IsRequestTokenAuthorized(request.RequestToken)) {
+ throw new ProtocolException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.AccessTokenNotAuthorized,
+ request.RequestToken));
+ }
+
+ string accessToken = this.TokenGenerator.GenerateAccessToken(request.ConsumerKey);
+ string tokenSecret = this.TokenGenerator.GenerateSecret();
+ this.TokenManager.ExpireRequestTokenAndStoreNewAccessToken(request.ConsumerKey, request.RequestToken, accessToken, tokenSecret);
+ var grantAccess = new AuthorizedTokenResponse {
+ AccessToken = accessToken,
+ TokenSecret = tokenSecret,
+ };
+
+ return grantAccess;
+ }
+
+ /// <summary>
+ /// Gets the authorization (access token) for accessing some protected resource.
+ /// </summary>
+ /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
+ /// <remarks>
+ /// This method verifies that the access token and token secret are valid.
+ /// It falls on the caller to verify that the access token is actually authorized
+ /// to access the resources being requested.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception>
+ public AccessProtectedResourceRequest ReadProtectedResourceAuthorization() {
+ return this.ReadProtectedResourceAuthorization(this.Channel.GetRequestFromContext());
+ }
+
+ /// <summary>
+ /// Gets the authorization (access token) for accessing some protected resource.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request.</param>
+ /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
+ /// <remarks>
+ /// This method verifies that the access token and token secret are valid.
+ /// It falls on the caller to verify that the access token is actually authorized
+ /// to access the resources being requested.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception>
+ public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequest request) {
+ return this.ReadProtectedResourceAuthorization(new HttpRequestInfo(request));
+ }
+
+ /// <summary>
+ /// Gets the authorization (access token) for accessing some protected resource.
+ /// </summary>
+ /// <param name="request">HTTP details from an incoming WCF message.</param>
+ /// <param name="requestUri">The URI of the WCF service endpoint.</param>
+ /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
+ /// <remarks>
+ /// This method verifies that the access token and token secret are valid.
+ /// It falls on the caller to verify that the access token is actually authorized
+ /// to access the resources being requested.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception>
+ public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestMessageProperty request, Uri requestUri) {
+ return this.ReadProtectedResourceAuthorization(new HttpRequestInfo(request, requestUri));
+ }
+
+ /// <summary>
+ /// Gets the authorization (access token) for accessing some protected resource.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request.</param>
+ /// <returns>The authorization message sent by the Consumer, or null if no authorization message is attached.</returns>
+ /// <remarks>
+ /// This method verifies that the access token and token secret are valid.
+ /// It falls on the caller to verify that the access token is actually authorized
+ /// to access the resources being requested.
+ /// </remarks>
+ /// <exception cref="ProtocolException">Thrown if an unexpected message is attached to the request.</exception>
+ public AccessProtectedResourceRequest ReadProtectedResourceAuthorization(HttpRequestInfo request) {
+ if (request == null) {
+ throw new ArgumentNullException("request");
+ }
+
+ AccessProtectedResourceRequest accessMessage;
+ if (this.Channel.TryReadFromRequest<AccessProtectedResourceRequest>(request, out accessMessage)) {
+ if (this.TokenManager.GetTokenType(accessMessage.AccessToken) != TokenType.AccessToken) {
+ throw new ProtocolException(
+ string.Format(
+ CultureInfo.CurrentCulture,
+ Strings.BadAccessTokenInProtectedResourceRequest,
+ accessMessage.AccessToken));
+ }
+ }
+
+ return accessMessage;
+ }
+
+ /// <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="DotNetOAuth.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/DotNetOAuth/OAuth/ServiceProviderDescription.cs b/src/DotNetOAuth/OAuth/ServiceProviderDescription.cs new file mode 100644 index 0000000..7e7e2d0 --- /dev/null +++ b/src/DotNetOAuth/OAuth/ServiceProviderDescription.cs @@ -0,0 +1,87 @@ +//-----------------------------------------------------------------------
+// <copyright file="ServiceProviderDescription.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth {
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Linq;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.OAuth.ChannelElements;
+
+ /// <summary>
+ /// A description of the endpoints on a Service Provider.
+ /// </summary>
+ public class ServiceProviderDescription {
+ /// <summary>
+ /// The field used to store the value of the <see cref="RequestTokenEndpoint"/> property.
+ /// </summary>
+ [DebuggerBrowsable(DebuggerBrowsableState.Never)]
+ private MessageReceivingEndpoint requestTokenEndpoint;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServiceProviderDescription"/> class.
+ /// </summary>
+ public ServiceProviderDescription() {
+ }
+
+ /// <summary>
+ /// Gets or sets the URL used to obtain an unauthorized Request Token,
+ /// described in Section 6.1 (Obtaining an Unauthorized Request Token).
+ /// </summary>
+ /// <remarks>
+ /// The request URL query MUST NOT contain any OAuth Protocol Parameters.
+ /// This is the URL that <see cref="OAuth.Messages.UnauthorizedTokenRequest"/> messages are directed to.
+ /// </remarks>
+ /// <exception cref="ArgumentException">Thrown if this property is set to a URI with OAuth protocol parameters.</exception>
+ public MessageReceivingEndpoint RequestTokenEndpoint {
+ get {
+ return this.requestTokenEndpoint;
+ }
+
+ set {
+ if (value != null && UriUtil.QueryStringContainsOAuthParameters(value.Location)) {
+ throw new ArgumentException(Strings.RequestUrlMustNotHaveOAuthParameters);
+ }
+
+ this.requestTokenEndpoint = value;
+ }
+ }
+
+ /// <summary>
+ /// Gets or sets the URL used to obtain User authorization for Consumer access,
+ /// described in Section 6.2 (Obtaining User Authorization).
+ /// </summary>
+ /// <remarks>
+ /// This is the URL that <see cref="OAuth.Messages.UserAuthorizationRequest"/> messages are
+ /// indirectly (via the user agent) sent to.
+ /// </remarks>
+ public MessageReceivingEndpoint UserAuthorizationEndpoint { get; set; }
+
+ /// <summary>
+ /// Gets or sets the URL used to exchange the User-authorized Request Token
+ /// for an Access Token, described in Section 6.3 (Obtaining an Access Token).
+ /// </summary>
+ /// <remarks>
+ /// This is the URL that <see cref="OAuth.Messages.AuthorizedTokenRequest"/> messages are directed to.
+ /// </remarks>
+ public MessageReceivingEndpoint AccessTokenEndpoint { get; set; }
+
+ /// <summary>
+ /// Gets or sets the signing policies that apply to this Service Provider.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1819:PropertiesShouldNotReturnArrays", Justification = "Type initializers require this format.")]
+ public ITamperProtectionChannelBindingElement[] TamperProtectionElements { get; set; }
+
+ /// <summary>
+ /// Creates a signing element that includes all the signing elements this service provider supports.
+ /// </summary>
+ /// <returns>The created signing element.</returns>
+ internal ITamperProtectionChannelBindingElement CreateTamperProtectionElement() {
+ return new SigningBindingElementChain(this.TamperProtectionElements.Select(el => (ITamperProtectionChannelBindingElement)el.Clone()).ToArray());
+ }
+ }
+}
diff --git a/src/DotNetOAuth/OAuth/WebConsumer.cs b/src/DotNetOAuth/OAuth/WebConsumer.cs new file mode 100644 index 0000000..eed2168 --- /dev/null +++ b/src/DotNetOAuth/OAuth/WebConsumer.cs @@ -0,0 +1,97 @@ +//-----------------------------------------------------------------------
+// <copyright file="WebConsumer.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Web;
+ using DotNetOAuth.Messaging;
+ using DotNetOAuth.OAuth.ChannelElements;
+ using DotNetOAuth.OAuth.Messages;
+
+ /// <summary>
+ /// A website or application that uses OAuth to access the Service Provider on behalf of the User.
+ /// </summary>
+ /// <remarks>
+ /// The methods on this class are thread-safe. Provided the properties are set and not changed
+ /// afterward, a single instance of this class may be used by an entire web application safely.
+ /// </remarks>
+ public class WebConsumer : ConsumerBase {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WebConsumer"/> class.
+ /// </summary>
+ /// <param name="serviceDescription">The endpoints and behavior of the Service Provider.</param>
+ /// <param name="tokenManager">The host's method of storing and recalling tokens and secrets.</param>
+ public WebConsumer(ServiceProviderDescription serviceDescription, ITokenManager tokenManager)
+ : base(serviceDescription, tokenManager) {
+ }
+
+ /// <summary>
+ /// Begins an OAuth authorization request and redirects the user to the Service Provider
+ /// to provide that authorization. Upon successful authorization, the user is redirected
+ /// back to the current page.
+ /// </summary>
+ /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
+ /// <remarks>
+ /// Requires HttpContext.Current.
+ /// </remarks>
+ public UserAuthorizationRequest PrepareRequestUserAuthorization() {
+ Uri callback = MessagingUtilities.GetRequestUrlFromContext().StripQueryArgumentsWithPrefix(Protocol.Default.ParameterPrefix);
+ return this.PrepareRequestUserAuthorization(callback, null, null);
+ }
+
+ /// <summary>
+ /// Prepares an OAuth message that begins an authorization request that will
+ /// redirect the user to the Service Provider to provide that authorization.
+ /// </summary>
+ /// <param name="callback">
+ /// An optional Consumer URL that the Service Provider should redirect the
+ /// User Agent to upon successful authorization.
+ /// </param>
+ /// <param name="requestParameters">Extra parameters to add to the request token message. Optional.</param>
+ /// <param name="redirectParameters">Extra parameters to add to the redirect to Service Provider message. Optional.</param>
+ /// <returns>The pending user agent redirect based message to be sent as an HttpResponse.</returns>
+ public UserAuthorizationRequest PrepareRequestUserAuthorization(Uri callback, IDictionary<string, string> requestParameters, IDictionary<string, string> redirectParameters) {
+ string token;
+ return this.PrepareRequestUserAuthorization(callback, requestParameters, redirectParameters, out token);
+ }
+
+ /// <summary>
+ /// Processes an incoming authorization-granted message from an SP and obtains an access token.
+ /// </summary>
+ /// <returns>The access token, or null if no incoming authorization message was recognized.</returns>
+ /// <remarks>
+ /// Requires HttpContext.Current.
+ /// </remarks>
+ public AuthorizedTokenResponse ProcessUserAuthorization() {
+ return this.ProcessUserAuthorization(this.Channel.GetRequestFromContext());
+ }
+
+ /// <summary>
+ /// Processes an incoming authorization-granted message from an SP and obtains an access token.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request.</param>
+ /// <returns>The access token, or null if no incoming authorization message was recognized.</returns>
+ public AuthorizedTokenResponse ProcessUserAuthorization(HttpRequest request) {
+ return this.ProcessUserAuthorization(new HttpRequestInfo(request));
+ }
+
+ /// <summary>
+ /// Processes an incoming authorization-granted message from an SP and obtains an access token.
+ /// </summary>
+ /// <param name="request">The incoming HTTP request.</param>
+ /// <returns>The access token, or null if no incoming authorization message was recognized.</returns>
+ internal AuthorizedTokenResponse ProcessUserAuthorization(HttpRequestInfo request) {
+ UserAuthorizationResponse authorizationMessage;
+ if (this.Channel.TryReadFromRequest<UserAuthorizationResponse>(request, out authorizationMessage)) {
+ string requestToken = authorizationMessage.RequestToken;
+ return this.ProcessUserAuthorization(requestToken);
+ } else {
+ return null;
+ }
+ }
+ }
+}
|