summaryrefslogtreecommitdiffstats
path: root/src/DotNetOpenAuth.OAuth/OAuth
diff options
context:
space:
mode:
authorAndrew Arnott <andrewarnott@gmail.com>2012-01-29 14:32:45 -0800
committerAndrew Arnott <andrewarnott@gmail.com>2012-01-29 14:32:45 -0800
commit5fec515095ee10b522f414a03e78f282aaf520dc (patch)
tree204c75486639c23cdda2ef38b34d7e5050a1a2e3 /src/DotNetOpenAuth.OAuth/OAuth
parentf1a4155398635a4fd9f485eec817152627682704 (diff)
parent8f4165ee515728aca3faaa26e8354a40612e85e4 (diff)
downloadDotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.zip
DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.gz
DotNetOpenAuth-5fec515095ee10b522f414a03e78f282aaf520dc.tar.bz2
Merge branch 'splitDlls'.
DNOA now builds and (in some cases) ships as many distinct assemblies.
Diffstat (limited to 'src/DotNetOpenAuth.OAuth/OAuth')
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs50
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs33
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs30
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs47
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITokenManager.cs169
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs356
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs76
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs60
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs31
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs329
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs47
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs142
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/TokenType.cs30
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/UriOrOobEncoding.cs77
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ClassDiagram.cd46
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ConsumerSecuritySettings.cs18
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/AccessProtectedResourceRequest.cs72
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenRequest.cs62
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenResponse.cs62
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenContainingMessage.cs17
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenSecretContainingMessage.cs17
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/MessageBase.cs281
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/OAuth Messages.cd164
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/SignedMessageBase.cs197
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenRequest.cs44
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenResponse.cs100
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationRequest.cs82
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationResponse.cs56
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.Designer.cs198
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.resx165
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.sr.resx162
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/Protocol.cs148
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/SecuritySettings.cs18
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderDescription.cs102
-rw-r--r--src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderSecuritySettings.cs36
35 files changed, 3524 insertions, 0 deletions
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs
new file mode 100644
index 0000000..5828428
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/HmacSha1SigningBindingElement.cs
@@ -0,0 +1,50 @@
+//-----------------------------------------------------------------------
+// <copyright file="HmacSha1SigningBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Security.Cryptography;
+ using System.Text;
+ using DotNetOpenAuth.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);
+ using (HashAlgorithm hasher = new HMACSHA1(Encoding.ASCII.GetBytes(key))) {
+ string baseString = ConstructSignatureBaseString(message, this.Channel.MessageDescriptions.GetAccessor(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/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs
new file mode 100644
index 0000000..dd28e71
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ICombinedOpenIdProviderTokenManager.cs
@@ -0,0 +1,33 @@
+//-----------------------------------------------------------------------
+// <copyright file="ICombinedOpenIdProviderTokenManager.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using DotNetOpenAuth.OpenId;
+
+ /// <summary>
+ /// An interface that providers that play a dual role as OpenID Provider
+ /// and OAuth Service Provider should implement on their token manager classes.
+ /// </summary>
+ /// <remarks>
+ /// This interface should be implemented by the same class that implements
+ /// <see cref="ITokenManager"/> in order to enable the OpenID+OAuth extension.
+ /// </remarks>
+ public interface ICombinedOpenIdProviderTokenManager : IOpenIdOAuthTokenManager, ITokenManager {
+ /// <summary>
+ /// Gets the OAuth consumer key for a given OpenID relying party realm.
+ /// </summary>
+ /// <param name="realm">The relying party's OpenID realm.</param>
+ /// <returns>The OAuth consumer key for a given OpenID realm.</returns>
+ /// <para>This is a security-critical function. Since OpenID requests
+ /// and OAuth extensions for those requests can be formulated by ANYONE
+ /// (no signing is required by the relying party), and since the response to
+ /// the authentication will include access the user is granted to the
+ /// relying party who CLAIMS to be from some realm, it is of paramount
+ /// importance that the realm is recognized as belonging to the consumer
+ /// key by the host service provider in order to protect against phishers.</para>
+ string GetConsumerKey(Realm realm);
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs
new file mode 100644
index 0000000..b3ee320
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/IOpenIdOAuthTokenManager.cs
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------
+// <copyright file="IOpenIdOAuthTokenManager.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using DotNetOpenAuth.OpenId;
+ using DotNetOpenAuth.OpenId.Extensions.OAuth;
+
+ /// <summary>
+ /// Additional methods an <see cref="ITokenManager"/> implementing class
+ /// may implement to support the OpenID+OAuth extension.
+ /// </summary>
+ public interface IOpenIdOAuthTokenManager {
+ /// <summary>
+ /// Stores a new request token obtained over an OpenID request.
+ /// </summary>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <param name="authorization">The authorization message carrying the request token and authorized access scope.</param>
+ /// <remarks>
+ /// <para>The token secret is the empty string.</para>
+ /// <para>Tokens stored by this method should be short-lived to mitigate
+ /// possible security threats. Their lifetime should be sufficient for the
+ /// relying party to receive the positive authentication assertion and immediately
+ /// send a follow-up request for the access token.</para>
+ /// </remarks>
+ void StoreOpenIdAuthorizedRequestToken(string consumerKey, AuthorizationApprovedResponse authorization);
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs
new file mode 100644
index 0000000..a95001d
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITamperResistantOAuthMessage.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+// <copyright file="ITamperResistantOAuthMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+
+ /// <summary>
+ /// An interface that OAuth messages implement to support signing.
+ /// </summary>
+ public interface ITamperResistantOAuthMessage : IDirectedProtocolMessage, ITamperResistantProtocolMessage, IMessageOriginalPayload {
+ /// <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; }
+
+ /// <summary>
+ /// Gets or sets the URL of the intended receiver of this message.
+ /// </summary>
+ new Uri Recipient { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITokenManager.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITokenManager.cs
new file mode 100644
index 0000000..7d68b63
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/ITokenManager.cs
@@ -0,0 +1,169 @@
+//-----------------------------------------------------------------------
+// <copyright file="ITokenManager.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ /// <summary>
+ /// An interface OAuth hosts must implement for persistent storage
+ /// and recall of tokens and secrets for an individual OAuth consumer
+ /// or service provider.
+ /// </summary>
+ [ContractClass(typeof(ITokenManagerContract))]
+ public interface ITokenManager {
+ /// <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>
+ /// <remarks>
+ /// Request tokens stored by this method SHOULD NOT associate any user account with this token.
+ /// It usually opens up security holes in your application to do so. Instead, you associate a user
+ /// account with access tokens (not request tokens) in the <see cref="ExpireRequestTokenAndStoreNewAccessToken"/>
+ /// method.
+ /// </remarks>
+ void StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response);
+
+ /// <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>
+ /// <para>
+ /// 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.
+ /// </para>
+ /// <para>
+ /// To associate a user account with the new access token,
+ /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be
+ /// useful in an ASP.NET web application within the implementation of this method.
+ /// Alternatively you may store the access token here without associating with a user account,
+ /// and wait until WebConsumer.ProcessUserAuthorization or
+ /// DesktopConsumer.ProcessUserAuthorization return the access
+ /// token to associate the access token with a user account at that point.
+ /// </para>
+ /// </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);
+ }
+
+ /// <summary>
+ /// The code contract class for the <see cref="ITokenManager"/> interface.
+ /// </summary>
+ [ContractClassFor(typeof(ITokenManager))]
+ internal abstract class ITokenManagerContract : ITokenManager {
+ /// <summary>
+ /// Prevents a default instance of the <see cref="ITokenManagerContract"/> class from being created.
+ /// </summary>
+ private ITokenManagerContract() {
+ }
+
+ #region ITokenManager Members
+
+ /// <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 ITokenManager.GetTokenSecret(string token) {
+ Requires.NotNullOrEmpty(token, "token");
+ Contract.Ensures(Contract.Result<string>() != null);
+ throw new NotImplementedException();
+ }
+
+ /// <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>
+ /// <remarks>
+ /// Request tokens stored by this method SHOULD NOT associate any user account with this token.
+ /// It usually opens up security holes in your application to do so. Instead, you associate a user
+ /// account with access tokens (not request tokens) in the <see cref="ITokenManager.ExpireRequestTokenAndStoreNewAccessToken"/>
+ /// method.
+ /// </remarks>
+ void ITokenManager.StoreNewRequestToken(UnauthorizedTokenRequest request, ITokenSecretContainingMessage response) {
+ Requires.NotNull(request, "request");
+ Requires.NotNull(response, "response");
+ throw new NotImplementedException();
+ }
+
+ /// <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>
+ /// <para>
+ /// Any scope of granted privileges associated with the request token from the
+ /// original call to <see cref="ITokenManager.StoreNewRequestToken"/> should be carried over
+ /// to the new Access Token.
+ /// </para>
+ /// <para>
+ /// To associate a user account with the new access token,
+ /// <see cref="System.Web.HttpContext.User">HttpContext.Current.User</see> may be
+ /// useful in an ASP.NET web application within the implementation of this method.
+ /// Alternatively you may store the access token here without associating with a user account,
+ /// and wait until WebConsumer.ProcessUserAuthorization or
+ /// DesktopConsumer.ProcessUserAuthorization return the access
+ /// token to associate the access token with a user account at that point.
+ /// </para>
+ /// </remarks>
+ void ITokenManager.ExpireRequestTokenAndStoreNewAccessToken(string consumerKey, string requestToken, string accessToken, string accessTokenSecret) {
+ Requires.NotNullOrEmpty(consumerKey, "consumerKey");
+ Requires.NotNullOrEmpty(requestToken, "requestToken");
+ Requires.NotNullOrEmpty(accessToken, "accessToken");
+ Requires.NotNull(accessTokenSecret, "accessTokenSecret");
+ throw new NotImplementedException();
+ }
+
+ /// <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 ITokenManager.GetTokenType(string token) {
+ Requires.NotNullOrEmpty(token, "token");
+ throw new NotImplementedException();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs
new file mode 100644
index 0000000..32b57d0
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthChannel.cs
@@ -0,0 +1,356 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthChannel.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.IO;
+ using System.Linq;
+ using System.Net;
+ using System.Net.Mime;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OAuth.Messages;
+
+ /// <summary>
+ /// An OAuth-specific implementation of the <see cref="Channel"/> class.
+ /// </summary>
+ internal abstract class OAuthChannel : Channel {
+ /// <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="securitySettings">The security settings.</param>
+ /// <param name="messageTypeProvider">An injected message type provider instance.
+ /// Except for mock testing, this should always be one of
+ /// OAuthConsumerMessageFactory or OAuthServiceProviderMessageFactory.</param>
+ /// <param name="bindingElements">The binding elements.</param>
+ [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Diagnostics.Contracts.__ContractsRuntime.Requires<System.ArgumentNullException>(System.Boolean,System.String,System.String)", Justification = "Code contracts"), SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "securitySettings", Justification = "Code contracts")]
+ protected OAuthChannel(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings, IMessageFactory messageTypeProvider, IChannelBindingElement[] bindingElements)
+ : base(messageTypeProvider, bindingElements) {
+ Requires.NotNull(tokenManager, "tokenManager");
+ Requires.NotNull(securitySettings, "securitySettings");
+ Requires.NotNull(signingBindingElement, "signingBindingElement");
+ Requires.True(signingBindingElement.SignatureCallback == null, "signingBindingElement", OAuthStrings.SigningElementAlreadyAssociatedWithChannel);
+ Requires.NotNull(bindingElements, "bindingElements");
+
+ this.TokenManager = tokenManager;
+ 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>
+ /// Uri-escapes the names and values in a dictionary 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> GetUriEscapedParameters(IEnumerable<KeyValuePair<string, string>> message) {
+ var encodedDictionary = new Dictionary<string, string>();
+ UriEscapeParameters(message, encodedDictionary);
+ return encodedDictionary;
+ }
+
+ /// <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) {
+ Requires.NotNull(request, "request");
+
+ ProcessOutgoingMessage(request);
+ return this.CreateHttpRequest(request);
+ }
+
+ /// <summary>
+ /// Initializes the binding elements for the OAuth channel.
+ /// </summary>
+ /// <param name="signingBindingElement">The signing binding element.</param>
+ /// <param name="store">The nonce store.</param>
+ /// <param name="tokenManager">The token manager.</param>
+ /// <param name="securitySettings">The security settings.</param>
+ /// <returns>
+ /// An array of binding elements used to initialize the channel.
+ /// </returns>
+ protected static List<IChannelBindingElement> InitializeBindingElements(ITamperProtectionChannelBindingElement signingBindingElement, INonceStore store, ITokenManager tokenManager, SecuritySettings securitySettings) {
+ Contract.Requires(securitySettings != null);
+
+ var bindingElements = new List<IChannelBindingElement> {
+ new OAuthHttpMethodBindingElement(),
+ signingBindingElement,
+ new StandardExpirationBindingElement(),
+ new StandardReplayProtectionBindingElement(store),
+ };
+
+ return bindingElements;
+ }
+
+ /// <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>The deserialized message, if one is found. Null otherwise.</returns>
+ protected override IDirectedProtocolMessage ReadFromRequestCore(HttpRequestInfo request) {
+ // First search the Authorization header.
+ string authorization = request.Headers[HttpRequestHeader.Authorization];
+ var fields = MessagingUtilities.ParseAuthorizationHeader(Protocol.AuthorizationHeaderScheme, authorization).ToDictionary();
+ fields.Remove("realm"); // ignore the realm parameter, since we don't use it, and it must be omitted from signature base string.
+
+ // Scrape the entity
+ if (!string.IsNullOrEmpty(request.Headers[HttpRequestHeader.ContentType])) {
+ var contentType = new ContentType(request.Headers[HttpRequestHeader.ContentType]);
+ if (string.Equals(contentType.MediaType, HttpFormUrlEncoded, StringComparison.Ordinal)) {
+ foreach (string key in request.Form) {
+ if (key != null) {
+ fields.Add(key, request.Form[key]);
+ } else {
+ Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.Form[key]);
+ }
+ }
+ }
+ }
+
+ // Scrape the query string
+ foreach (string key in request.QueryStringBeforeRewriting) {
+ if (key != null) {
+ fields.Add(key, request.QueryStringBeforeRewriting[key]);
+ } else {
+ Logger.OAuth.WarnFormat("Ignoring query string parameter '{0}' since it isn't a standard name=value parameter.", request.QueryStringBeforeRewriting[key]);
+ }
+ }
+
+ MessageReceivingEndpoint recipient;
+ try {
+ recipient = request.GetRecipient();
+ } catch (ArgumentException ex) {
+ Logger.OAuth.WarnFormat("Unrecognized HTTP request: " + ex.ToString());
+ return null;
+ }
+
+ // Deserialize the message using all the data we've collected.
+ var message = (IDirectedProtocolMessage)this.Receive(fields, recipient);
+
+ // Add receiving HTTP transport information required for signature generation.
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null) {
+ signedMessage.Recipient = request.UrlBeforeRewriting;
+ signedMessage.HttpMethod = request.HttpMethod;
+ }
+
+ return message;
+ }
+
+ /// <summary>
+ /// Gets the protocol message that may be in the given HTTP response.
+ /// </summary>
+ /// <param name="response">The response that is anticipated to contain an protocol message.</param>
+ /// <returns>
+ /// The deserialized message parts, if found. Null otherwise.
+ /// </returns>
+ protected override IDictionary<string, string> ReadFromResponseCore(IncomingWebResponse response) {
+ string body = response.GetResponseReader().ReadToEnd();
+ return HttpUtility.ParseQueryString(body).ToDictionary();
+ }
+
+ /// <summary>
+ /// Prepares an HTTP request that carries a given message.
+ /// </summary>
+ /// <param name="request">The message to send.</param>
+ /// <returns>
+ /// The <see cref="HttpRequest"/> prepared to send the request.
+ /// </returns>
+ protected override HttpWebRequest CreateHttpRequest(IDirectedProtocolMessage request) {
+ HttpWebRequest httpRequest;
+
+ HttpDeliveryMethods transmissionMethod = request.HttpMethods;
+ if ((transmissionMethod & HttpDeliveryMethods.AuthorizationHeaderRequest) != 0) {
+ httpRequest = this.InitializeRequestAsAuthHeader(request);
+ } else if ((transmissionMethod & HttpDeliveryMethods.PostRequest) != 0) {
+ var requestMessageWithBinaryData = request as IMessageWithBinaryData;
+ ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || !requestMessageWithBinaryData.SendAsMultipart, OAuthStrings.MultipartPostMustBeUsedWithAuthHeader);
+ httpRequest = this.InitializeRequestAsPost(request);
+ } else if ((transmissionMethod & HttpDeliveryMethods.GetRequest) != 0) {
+ httpRequest = InitializeRequestAsGet(request);
+ } else if ((transmissionMethod & HttpDeliveryMethods.HeadRequest) != 0) {
+ httpRequest = InitializeRequestAsHead(request);
+ } else if ((transmissionMethod & HttpDeliveryMethods.PutRequest) != 0) {
+ httpRequest = this.InitializeRequestAsPut(request);
+ } else if ((transmissionMethod & HttpDeliveryMethods.DeleteRequest) != 0) {
+ httpRequest = InitializeRequestAsDelete(request);
+ } else {
+ throw new NotSupportedException();
+ }
+ return httpRequest;
+ }
+
+ /// <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 OutgoingWebResponse PrepareDirectResponse(IProtocolMessage response) {
+ var messageAccessor = this.MessageDescriptions.GetAccessor(response);
+ var fields = messageAccessor.Serialize();
+ string responseBody = MessagingUtilities.CreateQueryString(fields);
+
+ OutgoingWebResponse encodedResponse = new OutgoingWebResponse {
+ Body = responseBody,
+ OriginalMessage = response,
+ Status = HttpStatusCode.OK,
+ Headers = new System.Net.WebHeaderCollection(),
+ };
+
+ IHttpDirectResponse httpMessage = response as IHttpDirectResponse;
+ if (httpMessage != null) {
+ encodedResponse.Status = httpMessage.HttpStatusCode;
+ }
+
+ return encodedResponse;
+ }
+
+ /// <summary>
+ /// Gets the consumer secret for a given consumer key.
+ /// </summary>
+ /// <param name="consumerKey">The consumer key.</param>
+ /// <returns>A consumer secret.</returns>
+ protected abstract string GetConsumerSecret(string consumerKey);
+
+ /// <summary>
+ /// Uri-escapes the names and values in a dictionary per OAuth 1.0 section 5.1.
+ /// </summary>
+ /// <param name="source">The dictionary with names and values to encode.</param>
+ /// <param name="destination">The dictionary to add the encoded pairs to.</param>
+ private static void UriEscapeParameters(IEnumerable<KeyValuePair<string, string>> source, IDictionary<string, string> destination) {
+ Requires.NotNull(source, "source");
+ Requires.NotNull(destination, "destination");
+
+ foreach (var pair in source) {
+ var key = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Key);
+ var value = MessagingUtilities.EscapeUriDataStringRfc3986(pair.Value);
+ destination.Add(key, value);
+ }
+ }
+
+ /// <summary>
+ /// Gets the HTTP method to use for a message.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ /// <returns>"POST", "GET" or some other similar http verb.</returns>
+ private static string GetHttpMethod(IDirectedProtocolMessage message) {
+ Requires.NotNull(message, "message");
+
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null) {
+ return signedMessage.HttpMethod;
+ } else {
+ return MessagingUtilities.GetHttpVerb(message.HttpMethods);
+ }
+ }
+
+ /// <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>
+ /// <para>If the message has non-empty ExtraData in it, the request stream is sent to
+ /// the server automatically. If it is empty, the request stream must be sent by the caller.</para>
+ /// <para>This method implements OAuth 1.0 section 5.2, item #1 (described in section 5.4).</para>
+ /// </remarks>
+ private HttpWebRequest InitializeRequestAsAuthHeader(IDirectedProtocolMessage requestMessage) {
+ var dictionary = this.MessageDescriptions.GetAccessor(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);
+ }
+
+ HttpWebRequest httpRequest;
+ UriBuilder recipientBuilder = new UriBuilder(requestMessage.Recipient);
+ bool hasEntity = HttpMethodHasEntity(GetHttpMethod(requestMessage));
+
+ if (!hasEntity) {
+ MessagingUtilities.AppendQueryArgs(recipientBuilder, requestMessage.ExtraData);
+ }
+ httpRequest = (HttpWebRequest)WebRequest.Create(recipientBuilder.Uri);
+ httpRequest.Method = GetHttpMethod(requestMessage);
+
+ httpRequest.Headers.Add(HttpRequestHeader.Authorization, MessagingUtilities.AssembleAuthorizationHeader(Protocol.AuthorizationHeaderScheme, fields));
+
+ if (hasEntity) {
+ // WARNING: We only set up the request stream for the caller if there is
+ // extra data. If there isn't any extra data, the caller must do this themselves.
+ var requestMessageWithBinaryData = requestMessage as IMessageWithBinaryData;
+ if (requestMessageWithBinaryData != null && requestMessageWithBinaryData.SendAsMultipart) {
+ // Include the binary data in the multipart entity, and any standard text extra message data.
+ // The standard declared message parts are included in the authorization header.
+ var multiPartFields = new List<MultipartPostPart>(requestMessageWithBinaryData.BinaryData);
+ multiPartFields.AddRange(requestMessage.ExtraData.Select(field => MultipartPostPart.CreateFormPart(field.Key, field.Value)));
+ this.SendParametersInEntityAsMultipart(httpRequest, multiPartFields);
+ } else {
+ ErrorUtilities.VerifyProtocol(requestMessageWithBinaryData == null || requestMessageWithBinaryData.BinaryData.Count == 0, MessagingStrings.BinaryDataRequiresMultipart);
+ if (requestMessage.ExtraData.Count > 0) {
+ this.SendParametersInEntity(httpRequest, requestMessage.ExtraData);
+ } else {
+ // We'll assume the content length is zero since the caller may not have
+ // anything. They're responsible to change it when the add the payload if they have one.
+ httpRequest.ContentLength = 0;
+ }
+ }
+ }
+
+ 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(ITamperResistantProtocolMessage message) {
+ var oauthMessage = message as ITamperResistantOAuthMessage;
+ try {
+ Logger.Channel.Debug("Applying secrets to message to prepare for signing or signature verification.");
+ oauthMessage.ConsumerSecret = this.GetConsumerSecret(oauthMessage.ConsumerKey);
+
+ var tokenMessage = message as ITokenContainingMessage;
+ if (tokenMessage != null) {
+ oauthMessage.TokenSecret = this.TokenManager.GetTokenSecret(tokenMessage.Token);
+ }
+ } catch (KeyNotFoundException ex) {
+ throw new ProtocolException(OAuthStrings.ConsumerOrTokenSecretNotFound, ex);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs
new file mode 100644
index 0000000..37fb80b
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/OAuthHttpMethodBindingElement.cs
@@ -0,0 +1,76 @@
+//-----------------------------------------------------------------------
+// <copyright file="OAuthHttpMethodBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <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>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ public Channel Channel { get; set; }
+
+ /// <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 MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ var oauthMessage = message as ITamperResistantOAuthMessage;
+
+ if (oauthMessage != null) {
+ HttpDeliveryMethods transmissionMethod = oauthMessage.HttpMethods;
+ try {
+ oauthMessage.HttpMethod = MessagingUtilities.GetHttpVerb(transmissionMethod);
+ return MessageProtections.None;
+ } catch (ArgumentException ex) {
+ Logger.OAuth.Error("Unrecognized HttpDeliveryMethods value.", ex);
+ return null;
+ }
+ } else {
+ return null;
+ }
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// 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 MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ return null;
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs
new file mode 100644
index 0000000..22e5f20
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/PlaintextSigningBindingElement.cs
@@ -0,0 +1,60 @@
+//-----------------------------------------------------------------------
+// <copyright file="PlaintextSigningBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.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) {
+ if (string.Equals(message.Recipient.Scheme, "https", StringComparison.OrdinalIgnoreCase)) {
+ return true;
+ } else {
+ Logger.Bindings.DebugFormat("The {0} element will not sign this message because the URI scheme is not https.", this.GetType().Name);
+ return false;
+ }
+ }
+
+ /// <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/DotNetOpenAuth.OAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs
new file mode 100644
index 0000000..83be094
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/RsaSha1SigningBindingElement.cs
@@ -0,0 +1,31 @@
+//-----------------------------------------------------------------------
+// <copyright file="RsaSha1SigningBindingElement.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Diagnostics.Contracts;
+ using System.Security.Cryptography;
+ using System.Security.Cryptography.X509Certificates;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// A binding element that signs outgoing messages and verifies the signature on incoming messages.
+ /// </summary>
+ public abstract class RsaSha1SigningBindingElement : SigningBindingElementBase {
+ /// <summary>
+ /// The name of the hash algorithm to use.
+ /// </summary>
+ protected const string HashAlgorithmName = "RSA-SHA1";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RsaSha1SigningBindingElement"/> class.
+ /// </summary>
+ protected RsaSha1SigningBindingElement()
+ : base(HashAlgorithmName) {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
new file mode 100644
index 0000000..2c47453
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBase.cs
@@ -0,0 +1,329 @@
+//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElementBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Collections.Specialized;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Linq;
+ using System.Text;
+ using System.Web;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// A binding element that signs outgoing messages and verifies the signature on incoming messages.
+ /// </summary>
+ [ContractClass(typeof(SigningBindingElementBaseContract))]
+ 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; }
+ }
+
+ /// <summary>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ public Channel Channel { get; set; }
+
+ #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>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null && this.IsMessageApplicable(signedMessage)) {
+ if (this.SignatureCallback != null) {
+ this.SignatureCallback(signedMessage);
+ } else {
+ Logger.Bindings.Warn("Signing required, but callback delegate was not provided to provide additional data for signing.");
+ }
+
+ signedMessage.SignatureMethod = this.signatureMethod;
+ Logger.Bindings.DebugFormat("Signing {0} message using {1}.", message.GetType().Name, this.signatureMethod);
+ signedMessage.Signature = this.GetSignature(signedMessage);
+ return MessageProtections.TamperProtection;
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Verifies the signature on an incoming message.
+ /// </summary>
+ /// <param name="message">The message whose signature should be verified.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ /// <exception cref="InvalidSignatureException">Thrown if the signature is invalid.</exception>
+ public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ var signedMessage = message as ITamperResistantOAuthMessage;
+ if (signedMessage != null && this.IsMessageApplicable(signedMessage)) {
+ Logger.Bindings.DebugFormat("Verifying incoming {0} message signature of: {1}", message.GetType().Name, signedMessage.Signature);
+
+ if (!string.Equals(signedMessage.SignatureMethod, this.signatureMethod, StringComparison.Ordinal)) {
+ Logger.Bindings.WarnFormat("Expected signature method '{0}' but received message with a signature method of '{1}'.", this.signatureMethod, signedMessage.SignatureMethod);
+ return MessageProtections.None;
+ }
+
+ if (this.SignatureCallback != null) {
+ this.SignatureCallback(signedMessage);
+ } else {
+ Logger.Bindings.Warn("Signature verification required, but callback delegate was not provided to provide additional data for signature verification.");
+ }
+
+ if (!this.IsSignatureValid(signedMessage)) {
+ Logger.Bindings.Error("Signature verification failed.");
+ throw new InvalidSignatureException(message);
+ }
+
+ return MessageProtections.TamperProtection;
+ }
+
+ return null;
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Constructs the OAuth Signature Base String and returns the result.
+ /// </summary>
+ /// <param name="message">The message.</param>
+ /// <param name="messageDictionary">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>
+ [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Unavoidable")]
+ internal static string ConstructSignatureBaseString(ITamperResistantOAuthMessage message, MessageDictionary messageDictionary) {
+ Requires.NotNull(message, "message");
+ Requires.NotNullOrEmpty(message.HttpMethod, "message.HttpMethod");
+ Requires.NotNull(messageDictionary, "messageDictionary");
+ ErrorUtilities.VerifyArgument(messageDictionary.Message == message, "Message references are not equal.");
+
+ List<string> signatureBaseStringElements = new List<string>(3);
+
+ signatureBaseStringElements.Add(message.HttpMethod.ToUpperInvariant());
+
+ // For multipart POST messages, only include the message parts that are NOT
+ // in the POST entity (those parts that may appear in an OAuth authorization header).
+ var encodedDictionary = new Dictionary<string, string>();
+ IEnumerable<KeyValuePair<string, string>> partsToInclude = Enumerable.Empty<KeyValuePair<string, string>>();
+ var binaryMessage = message as IMessageWithBinaryData;
+ if (binaryMessage != null && binaryMessage.SendAsMultipart) {
+ HttpDeliveryMethods authHeaderInUseFlags = HttpDeliveryMethods.PostRequest | HttpDeliveryMethods.AuthorizationHeaderRequest;
+ ErrorUtilities.VerifyProtocol((binaryMessage.HttpMethods & authHeaderInUseFlags) == authHeaderInUseFlags, OAuthStrings.MultipartPostMustBeUsedWithAuthHeader);
+
+ // Include the declared keys in the signature as those will be signable.
+ // Cache in local variable to avoid recalculating DeclaredKeys in the delegate.
+ ICollection<string> declaredKeys = messageDictionary.DeclaredKeys;
+ partsToInclude = messageDictionary.Where(pair => declaredKeys.Contains(pair.Key));
+ } else {
+ partsToInclude = messageDictionary;
+ }
+
+ // If this message was deserialized, include only those explicitly included message parts (excludes defaulted values)
+ // in the signature.
+ var originalPayloadMessage = (IMessageOriginalPayload)message;
+ if (originalPayloadMessage.OriginalPayload != null) {
+ partsToInclude = partsToInclude.Where(pair => originalPayloadMessage.OriginalPayload.ContainsKey(pair.Key));
+ }
+
+ foreach (var pair in OAuthChannel.GetUriEscapedParameters(partsToInclude)) {
+ encodedDictionary[pair.Key] = pair.Value;
+ }
+
+ // An incoming message will already have included the query and form parameters
+ // in the message dictionary, but an outgoing message COULD have SOME parameters
+ // in the query that are not in the message dictionary because they were included
+ // in the receiving endpoint (the original URL).
+ // In an outgoing message, the POST entity can only contain parameters if they were
+ // in the message dictionary, so no need to pull out any parameters from there.
+ if (message.Recipient.Query != null) {
+ NameValueCollection nvc = HttpUtility.ParseQueryString(message.Recipient.Query);
+ foreach (string key in nvc) {
+ string escapedKey = MessagingUtilities.EscapeUriDataStringRfc3986(key);
+ string escapedValue = MessagingUtilities.EscapeUriDataStringRfc3986(nvc[key]);
+ string existingValue;
+ if (!encodedDictionary.TryGetValue(escapedKey, out existingValue)) {
+ encodedDictionary.Add(escapedKey, escapedValue);
+ } else {
+ ErrorUtilities.VerifyInternal(escapedValue == existingValue, "Somehow we have conflicting values for the '{0}' parameter.", escapedKey);
+ }
+ }
+ }
+ encodedDictionary.Remove("oauth_signature");
+
+ UriBuilder endpoint = new UriBuilder(message.Recipient);
+ endpoint.Query = null;
+ endpoint.Fragment = null;
+ signatureBaseStringElements.Add(endpoint.Uri.AbsoluteUri);
+
+ 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(MessagingUtilities.EscapeUriDataStringRfc3986(element));
+ }
+
+ Logger.Bindings.DebugFormat("Constructed signature base string: {0}", signatureBaseString);
+ return signatureBaseString.ToString();
+ }
+
+ /// <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>
+ internal string GetSignatureTestHook(ITamperResistantOAuthMessage message) {
+ return this.GetSignature(message);
+ }
+
+ /// <summary>
+ /// Gets the "ConsumerSecret&amp;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(MessagingUtilities.EscapeUriDataStringRfc3986(message.ConsumerSecret));
+ }
+ builder.Append("&");
+ if (!string.IsNullOrEmpty(message.TokenSecret)) {
+ builder.Append(MessagingUtilities.EscapeUriDataStringRfc3986(message.TokenSecret));
+ }
+ return builder.ToString();
+ }
+
+ /// <summary>
+ /// Determines whether the signature on some message is valid.
+ /// </summary>
+ /// <param name="message">The message to check the signature on.</param>
+ /// <returns>
+ /// <c>true</c> if the signature on the message is valid; otherwise, <c>false</c>.
+ /// </returns>
+ protected virtual bool IsSignatureValid(ITamperResistantOAuthMessage message) {
+ Requires.NotNull(message, "message");
+
+ string signature = this.GetSignature(message);
+ return MessagingUtilities.EqualsConstantTime(message.Signature, signature);
+ }
+
+ /// <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/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs
new file mode 100644
index 0000000..4450fb5
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementBaseContract.cs
@@ -0,0 +1,47 @@
+//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElementBaseContract.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// Code Contract for the <see cref="SigningBindingElementBase"/> class.
+ /// </summary>
+ [ContractClassFor(typeof(SigningBindingElementBase))]
+ internal abstract class SigningBindingElementBaseContract : SigningBindingElementBase {
+ /// <summary>
+ /// Prevents a default instance of the SigningBindingElementBaseContract class from being created.
+ /// </summary>
+ private SigningBindingElementBaseContract()
+ : base(string.Empty) {
+ }
+
+ /// <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 override ITamperProtectionChannelBindingElement Clone() {
+ throw new NotImplementedException();
+ }
+
+ /// <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 override string GetSignature(ITamperResistantOAuthMessage message) {
+ Requires.NotNull(message, "message");
+ Requires.ValidState(this.Channel != null);
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs
new file mode 100644
index 0000000..849ad5e
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/SigningBindingElementChain.cs
@@ -0,0 +1,142 @@
+//-----------------------------------------------------------------------
+// <copyright file="SigningBindingElementChain.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.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 readonly 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) {
+ Requires.NotNullOrEmpty(signers, "signers");
+ Requires.NullOrWithNoNullElements(signers, "signers");
+ Requires.True(signers.Select(s => s.Protection).Distinct().Count() == 1, "signers", OAuthStrings.SigningElementsMustShareSameProtection);
+
+ 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>
+ /// Gets or sets the channel that this binding element belongs to.
+ /// </summary>
+ public Channel Channel {
+ get {
+ return this.signers[0].Channel;
+ }
+
+ set {
+ foreach (var signer in this.signers) {
+ signer.Channel = value;
+ }
+ }
+ }
+
+ /// <summary>
+ /// Prepares a message for sending based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The message to prepare for sending.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ public MessageProtections? ProcessOutgoingMessage(IProtocolMessage message) {
+ foreach (IChannelBindingElement signer in this.signers) {
+ ErrorUtilities.VerifyInternal(signer.Channel != null, "A binding element's Channel property is unexpectedly null.");
+ MessageProtections? result = signer.ProcessOutgoingMessage(message);
+ if (result.HasValue) {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ /// <summary>
+ /// Performs any transformation on an incoming message that may be necessary and/or
+ /// validates an incoming message based on the rules of this channel binding element.
+ /// </summary>
+ /// <param name="message">The incoming message to process.</param>
+ /// <returns>
+ /// The protections (if any) that this binding element applied to the message.
+ /// Null if this binding element did not even apply to this binding element.
+ /// </returns>
+ public MessageProtections? ProcessIncomingMessage(IProtocolMessage message) {
+ foreach (IChannelBindingElement signer in this.signers) {
+ ErrorUtilities.VerifyInternal(signer.Channel != null, "A binding element's Channel property is unexpectedly null.");
+ MessageProtections? result = signer.ProcessIncomingMessage(message);
+ if (result.HasValue) {
+ return result;
+ }
+ }
+
+ return null;
+ }
+
+ #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/DotNetOpenAuth.OAuth/OAuth/ChannelElements/TokenType.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/TokenType.cs
new file mode 100644
index 0000000..46c2bd9
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/TokenType.cs
@@ -0,0 +1,30 @@
+//-----------------------------------------------------------------------
+// <copyright file="TokenType.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.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/DotNetOpenAuth.OAuth/OAuth/ChannelElements/UriOrOobEncoding.cs b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/UriOrOobEncoding.cs
new file mode 100644
index 0000000..287ef01
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ChannelElements/UriOrOobEncoding.cs
@@ -0,0 +1,77 @@
+//-----------------------------------------------------------------------
+// <copyright file="UriOrOobEncoding.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.ChannelElements {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+
+ /// <summary>
+ /// An URI encoder that translates null <see cref="Uri"/> references as "oob"
+ /// instead of an empty/missing argument.
+ /// </summary>
+ internal class UriOrOobEncoding : IMessagePartNullEncoder {
+ /// <summary>
+ /// The string constant "oob", used to indicate an out-of-band configuration.
+ /// </summary>
+ private const string OutOfBandConfiguration = "oob";
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UriOrOobEncoding"/> class.
+ /// </summary>
+ public UriOrOobEncoding() {
+ }
+
+ #region IMessagePartNullEncoder Members
+
+ /// <summary>
+ /// Gets the string representation to include in a serialized message
+ /// when the message part has a <c>null</c> value.
+ /// </summary>
+ /// <value></value>
+ public string EncodedNullValue {
+ get { return OutOfBandConfiguration; }
+ }
+
+ #endregion
+
+ #region IMessagePartEncoder Members
+
+ /// <summary>
+ /// Encodes the specified value.
+ /// </summary>
+ /// <param name="value">The value. Guaranteed to never be null.</param>
+ /// <returns>
+ /// The <paramref name="value"/> in string form, ready for message transport.
+ /// </returns>
+ public string Encode(object value) {
+ Uri uriValue = (Uri)value;
+ return uriValue.AbsoluteUri;
+ }
+
+ /// <summary>
+ /// Decodes the specified value.
+ /// </summary>
+ /// <param name="value">The string value carried by the transport. Guaranteed to never be null, although it may be empty.</param>
+ /// <returns>
+ /// The deserialized form of the given string.
+ /// </returns>
+ /// <exception cref="FormatException">Thrown when the string value given cannot be decoded into the required object type.</exception>
+ public object Decode(string value) {
+ if (string.Equals(value, OutOfBandConfiguration, StringComparison.Ordinal)) {
+ return null;
+ } else {
+ return new Uri(value, UriKind.Absolute);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ClassDiagram.cd b/src/DotNetOpenAuth.OAuth/OAuth/ClassDiagram.cd
new file mode 100644
index 0000000..f56fd83
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ClassDiagram.cd
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<ClassDiagram MajorVersion="1" MinorVersion="1" MembersFormat="NameAndType">
+ <Class Name="DotNetOpenAuth.DesktopConsumer">
+ <Position X="11.5" Y="5.25" Width="4.75" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAEAAAAAAABAAAAAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>OAuth\DesktopConsumer.cs</FileName>
+ <NewMemberFileName>WebConsumer.cs</NewMemberFileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOpenAuth.OAuth.WebConsumer">
+ <Position X="6.25" Y="5.25" Width="5" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAABAAA=</HashCode>
+ <FileName>OAuth\WebConsumer.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOpenAuth.OAuth.ServiceProviderDescription">
+ <Position X="0.5" Y="5.25" Width="5" />
+ <Compartments>
+ <Compartment Name="Fields" Collapsed="true" />
+ </Compartments>
+ <TypeIdentifier>
+ <HashCode>AAACAAgAAAAEAAAQAAAAAAAAAAAAACAAAAAgAAAAAAA=</HashCode>
+ <FileName>OAuth\ServiceProviderDescription.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOpenAuth.OAuth.ServiceProvider">
+ <Position X="1" Y="0.5" Width="6" />
+ <Compartments>
+ <Compartment Name="Fields" Collapsed="true" />
+ </Compartments>
+ <TypeIdentifier>
+ <HashCode>EAoAAAsgAAAAIAAAEAAAAAAAAAAIAAEAAQAIAAwAAAA=</HashCode>
+ <FileName>OAuth\ServiceProvider.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOpenAuth.OAuth.ConsumerBase">
+ <Position X="9" Y="0.5" Width="4.5" />
+ <TypeIdentifier>
+ <HashCode>ACAAAAEgAAAAAAAAAAAABgAAAAAIAAAAAQAAAAABABA=</HashCode>
+ <FileName>OAuth\ConsumerBase.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Font Name="Segoe UI" Size="9" />
+</ClassDiagram> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ConsumerSecuritySettings.cs b/src/DotNetOpenAuth.OAuth/OAuth/ConsumerSecuritySettings.cs
new file mode 100644
index 0000000..bb2fbaa
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ConsumerSecuritySettings.cs
@@ -0,0 +1,18 @@
+//-----------------------------------------------------------------------
+// <copyright file="ConsumerSecuritySettings.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ /// <summary>
+ /// Security settings that are applicable to consumers.
+ /// </summary>
+ internal class ConsumerSecuritySettings : SecuritySettings {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ConsumerSecuritySettings"/> class.
+ /// </summary>
+ internal ConsumerSecuritySettings() {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/AccessProtectedResourceRequest.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AccessProtectedResourceRequest.cs
new file mode 100644
index 0000000..f3231f0
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AccessProtectedResourceRequest.cs
@@ -0,0 +1,72 @@
+//-----------------------------------------------------------------------
+// <copyright file="AccessProtectedResourceRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOpenAuth.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, IMessageWithBinaryData {
+ /// <summary>
+ /// A store for the binary data that is carried in the message.
+ /// </summary>
+ private List<MultipartPostPart> binaryData = new List<MultipartPostPart>();
+
+ /// <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>
+ /// <param name="version">The OAuth version.</param>
+ protected internal AccessProtectedResourceRequest(MessageReceivingEndpoint serviceProvider, Version version)
+ : base(MessageTransport.Direct, serviceProvider, version) {
+ }
+
+ /// <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; }
+
+ #region IMessageWithBinaryData Members
+
+ /// <summary>
+ /// Gets the parts of the message that carry binary data.
+ /// </summary>
+ /// <value>A list of parts. Never null.</value>
+ public IList<MultipartPostPart> BinaryData {
+ get { return this.binaryData; }
+ }
+
+ /// <summary>
+ /// Gets a value indicating whether this message should be sent as multi-part POST.
+ /// </summary>
+ public bool SendAsMultipart {
+ get { return this.HttpMethod == "POST" && this.BinaryData.Count > 0; }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenRequest.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenRequest.cs
new file mode 100644
index 0000000..02c6c1d
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenRequest.cs
@@ -0,0 +1,62 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthorizedTokenRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using System.Globalization;
+ using DotNetOpenAuth.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>
+ /// <param name="version">The OAuth version.</param>
+ internal AuthorizedTokenRequest(MessageReceivingEndpoint serviceProvider, Version version)
+ : base(MessageTransport.Direct, serviceProvider, version) {
+ }
+
+ /// <summary>
+ /// Gets or sets the Token.
+ /// </summary>
+ string ITokenContainingMessage.Token {
+ get { return this.RequestToken; }
+ set { this.RequestToken = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the verification code received by the Consumer from the Service Provider
+ /// in the <see cref="UserAuthorizationResponse.VerificationCode"/> property.
+ /// </summary>
+ [MessagePart("oauth_verifier", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion)]
+ public string VerificationCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the authorized 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, OAuthStrings.MessageNotAllowedExtraParameters, GetType().Name));
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenResponse.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenResponse.cs
new file mode 100644
index 0000000..0b14819
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/AuthorizedTokenResponse.cs
@@ -0,0 +1,62 @@
+//-----------------------------------------------------------------------
+// <copyright file="AuthorizedTokenResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOpenAuth.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>
+ /// <param name="originatingRequest">The originating request.</param>
+ protected internal AuthorizedTokenResponse(AuthorizedTokenRequest originatingRequest)
+ : base(MessageProtections.None, originatingRequest, originatingRequest.Version) {
+ }
+
+ /// <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/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenContainingMessage.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenContainingMessage.cs
new file mode 100644
index 0000000..832b754
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenContainingMessage.cs
@@ -0,0 +1,17 @@
+//-----------------------------------------------------------------------
+// <copyright file="ITokenContainingMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.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/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenSecretContainingMessage.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenSecretContainingMessage.cs
new file mode 100644
index 0000000..95809ec
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/ITokenSecretContainingMessage.cs
@@ -0,0 +1,17 @@
+//-----------------------------------------------------------------------
+// <copyright file="ITokenSecretContainingMessage.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.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/DotNetOpenAuth.OAuth/OAuth/Messages/MessageBase.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/MessageBase.cs
new file mode 100644
index 0000000..faa4345
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/MessageBase.cs
@@ -0,0 +1,281 @@
+//-----------------------------------------------------------------------
+// <copyright file="MessageBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.Contracts;
+ using System.Globalization;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Reflection;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+
+ /// <summary>
+ /// A base class for all OAuth messages.
+ /// </summary>
+ [Serializable]
+ public abstract class MessageBase : IDirectedProtocolMessage, IDirectResponseProtocolMessage {
+ /// <summary>
+ /// A store for extra name/value data pairs that are attached to this message.
+ /// </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;
+
+ /// <summary>
+ /// Backing store for the <see cref="OriginatingRequest"/> properties.
+ /// </summary>
+ private IDirectedProtocolMessage originatingRequest;
+
+ /// <summary>
+ /// Backing store for the <see cref="Incoming"/> properties.
+ /// </summary>
+ private bool incoming;
+
+#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 for direct response messages.
+ /// </summary>
+ /// <param name="protectionRequired">The level of protection the message requires.</param>
+ /// <param name="originatingRequest">The request that asked for this direct response.</param>
+ /// <param name="version">The OAuth version.</param>
+ protected MessageBase(MessageProtections protectionRequired, IDirectedProtocolMessage originatingRequest, Version version) {
+ Requires.NotNull(originatingRequest, "originatingRequest");
+ Requires.NotNull(version, "version");
+
+ this.protectionRequired = protectionRequired;
+ this.transport = MessageTransport.Direct;
+ this.originatingRequest = originatingRequest;
+ this.Version = version;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="MessageBase"/> class for direct requests or indirect messages.
+ /// </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>
+ /// <param name="version">The OAuth version.</param>
+ protected MessageBase(MessageProtections protectionRequired, MessageTransport transport, MessageReceivingEndpoint recipient, Version version) {
+ Requires.NotNull(recipient, "recipient");
+ Requires.NotNull(version, "version");
+
+ this.protectionRequired = protectionRequired;
+ this.transport = transport;
+ this.recipient = recipient;
+ this.Version = version;
+ }
+
+ #region IProtocolMessage Properties
+
+ /// <summary>
+ /// Gets the version of the protocol this message is prepared to implement.
+ /// </summary>
+ Version IMessage.Version {
+ get { return this.Version; }
+ }
+
+ /// <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> IMessage.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 != null ? this.recipient.Location : null; }
+ }
+
+ /// <summary>
+ /// Gets the preferred method of transport for the message.
+ /// </summary>
+ HttpDeliveryMethods IDirectedProtocolMessage.HttpMethods {
+ get { return this.HttpMethods; }
+ }
+
+ #endregion
+
+ #region IDirectResponseProtocolMessage Members
+
+ /// <summary>
+ /// Gets the originating request message that caused this response to be formed.
+ /// </summary>
+ IDirectedProtocolMessage IDirectResponseProtocolMessage.OriginatingRequest {
+ get { return this.originatingRequest; }
+ }
+
+ #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 a value indicating whether this message was deserialized as an incoming message.
+ /// </summary>
+ protected internal bool Incoming {
+ get { return this.incoming; }
+ }
+
+ /// <summary>
+ /// Gets the version of the protocol this message is prepared to implement.
+ /// </summary>
+ protected internal Version Version { get; private set; }
+
+ /// <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();
+ }
+ }
+ }
+
+ /// <summary>
+ /// Gets the originating request message that caused this response to be formed.
+ /// </summary>
+ protected IDirectedProtocolMessage OriginatingRequest {
+ get { return this.originatingRequest; }
+ }
+
+ #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 IMessage.EnsureValidMessage() {
+ this.EnsureValidMessage();
+ }
+
+ #endregion
+
+ /// <summary>
+ /// Returns a human-friendly string describing the message and all serializable properties.
+ /// </summary>
+ /// <param name="channel">The channel that will carry this message.</param>
+ /// <returns>
+ /// The string representation of this object.
+ /// </returns>
+ internal virtual string ToString(Channel channel) {
+ Requires.NotNull(channel, "channel");
+
+ 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 = channel.MessageDescriptions.GetAccessor(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>
+ /// Sets a flag indicating that this message is received (as opposed to sent).
+ /// </summary>
+ internal void SetAsIncoming() {
+ this.incoming = true;
+ }
+
+ /// <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/DotNetOpenAuth.OAuth/OAuth/Messages/OAuth Messages.cd b/src/DotNetOpenAuth.OAuth/OAuth/Messages/OAuth Messages.cd
new file mode 100644
index 0000000..f5526d2
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/OAuth Messages.cd
@@ -0,0 +1,164 @@
+<?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="DotNetOpenAuth.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>
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAA=</HashCode>
+ <FileName>OAuth\Messages\UnauthorizedTokenRequest.cs</FileName>
+ </TypeIdentifier>
+ </Class>
+ <Class Name="DotNetOpenAuth.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>OAuth\Messages\UnauthorizedTokenResponse.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOpenAuth.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>
+ <TypeIdentifier>
+ <HashCode>AIAAFAAAAIAAAAACgICAAgAAAgAAIAQAAAEAIgAQAAE=</HashCode>
+ <FileName>OAuth\Messages\SignedMessageBase.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOpenAuth.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>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAAAAAAAAIAAAAAAIAAAAAAAQAA=</HashCode>
+ <FileName>OAuth\Messages\UserAuthorizationRequest.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOpenAuth.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>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>OAuth\Messages\UserAuthorizationResponse.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOpenAuth.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>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAAAAAAAEAAAIAAAIAAAAAAAAAA=</HashCode>
+ <FileName>OAuth\Messages\AuthorizedTokenResponse.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOpenAuth.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>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACQAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>OAuth\Messages\AuthorizedTokenRequest.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOpenAuth.OAuth.Messages.AccessProtectedResourceRequest">
+ <Position X="4.25" Y="7.75" Width="3" />
+ <Members>
+ <Property Name="ITokenContainingMessage.Token" Hidden="true" />
+ </Members>
+ <TypeIdentifier>
+ <HashCode>AAAAAAACAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAA=</HashCode>
+ <FileName>OAuth\Messages\AccessProtectedResourceRequest.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Class Name="DotNetOpenAuth.OAuth.Messages.MessageBase">
+ <Position X="11" Y="1" Width="3.5" />
+ <Members>
+ <Field Name="extraData" Hidden="true" />
+ <Property Name="IDirectedProtocolMessage.Recipient" Hidden="true" />
+ <Method Name="IMessage.EnsureValidMessage" Hidden="true" />
+ <Property Name="IMessage.ExtraData" Hidden="true" />
+ <Property Name="IMessage.Version" 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>ICAMAAQAYAAAgAkEAIAIAAYACgAEYBAAIECBACAAAAA=</HashCode>
+ <FileName>OAuth\Messages\MessageBase.cs</FileName>
+ </TypeIdentifier>
+ <Lollipop Position="0.2" />
+ </Class>
+ <Interface Name="DotNetOpenAuth.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="DotNetOpenAuth.OAuth.Messages.ITokenContainingMessage">
+ <Position X="1" Y="6" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAA=</HashCode>
+ <FileName>OAuth\Messages\ITokenContainingMessage.cs</FileName>
+ </TypeIdentifier>
+ </Interface>
+ <Interface Name="DotNetOpenAuth.OAuth.Messages.ITokenSecretContainingMessage">
+ <Position X="1" Y="7.5" Width="2" />
+ <TypeIdentifier>
+ <HashCode>AAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAAAAAAAAAAAA=</HashCode>
+ <FileName>OAuth\Messages\ITokenSecretContainingMessage.cs</FileName>
+ </TypeIdentifier>
+ </Interface>
+ <Font Name="Segoe UI" Size="9" />
+</ClassDiagram> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/SignedMessageBase.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/SignedMessageBase.cs
new file mode 100644
index 0000000..6084fc0
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/SignedMessageBase.cs
@@ -0,0 +1,197 @@
+//-----------------------------------------------------------------------
+// <copyright file="SignedMessageBase.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.Messaging.Bindings;
+ using DotNetOpenAuth.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>
+ /// <param name="version">The OAuth version.</param>
+ internal SignedMessageBase(MessageTransport transport, MessageReceivingEndpoint recipient, Version version)
+ : base(MessageProtections.All, transport, recipient, version) {
+ ITamperResistantOAuthMessage self = (ITamperResistantOAuthMessage)this;
+ HttpDeliveryMethods methods = ((IDirectedProtocolMessage)this).HttpMethods;
+ self.HttpMethod = MessagingUtilities.GetHttpVerb(methods);
+ }
+
+ #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; }
+ }
+
+ /// <summary>
+ /// Gets or sets the URI to the Service Provider endpoint to send this message to.
+ /// </summary>
+ Uri ITamperResistantOAuthMessage.Recipient {
+ get { return this.Recipient; }
+ set { this.Recipient = 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 the context within which the nonce must be unique.
+ /// </summary>
+ /// <value>The consumer key.</value>
+ string IReplayProtectedProtocolMessage.NonceContext {
+ get { return this.ConsumerKey; }
+ }
+
+ /// <summary>
+ /// Gets or sets the message nonce used for replay detection.
+ /// </summary>
+ [MessagePart("oauth_nonce", IsRequired = true)]
+ string IReplayProtectedProtocolMessage.Nonce { get; set; }
+
+ #endregion
+
+ #region IMessageOriginalPayload Members
+
+ /// <summary>
+ /// Gets or sets the original message parts, before any normalization or default values were assigned.
+ /// </summary>
+ IDictionary<string, string> IMessageOriginalPayload.OriginalPayload {
+ get { return this.OriginalPayload; }
+ set { this.OriginalPayload = value; }
+ }
+
+ /// <summary>
+ /// Gets or sets the original message parts, before any normalization or default values were assigned.
+ /// </summary>
+ [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "By design")]
+ protected IDictionary<string, string> OriginalPayload { 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 OAuthVersion {
+ get {
+ return Protocol.Lookup(Version).PublishedVersion;
+ }
+
+ set {
+ if (value != this.OAuthVersion) {
+ throw new ArgumentOutOfRangeException("value");
+ }
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenRequest.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenRequest.cs
new file mode 100644
index 0000000..9214d91
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenRequest.cs
@@ -0,0 +1,44 @@
+//-----------------------------------------------------------------------
+// <copyright file="UnauthorizedTokenRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.OAuth.ChannelElements;
+
+ /// <summary>
+ /// A direct message sent from Consumer to Service Provider to request a Request Token.
+ /// </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>
+ /// <param name="version">The OAuth version.</param>
+ protected internal UnauthorizedTokenRequest(MessageReceivingEndpoint serviceProvider, Version version)
+ : base(MessageTransport.Direct, serviceProvider, version) {
+ }
+
+ /// <summary>
+ /// Gets or sets the absolute URL to which the Service Provider will redirect the
+ /// User back when the Obtaining User Authorization step is completed.
+ /// </summary>
+ /// <value>
+ /// The callback URL; or <c>null</c> if the Consumer is unable to receive
+ /// callbacks or a callback URL has been established via other means.
+ /// </value>
+ [MessagePart("oauth_callback", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion, Encoder = typeof(UriOrOobEncoding))]
+ public Uri Callback { get; set; }
+
+ /// <summary>
+ /// Gets the extra, non-OAuth parameters that will be included in the message.
+ /// </summary>
+ public new IDictionary<string, string> ExtraData {
+ get { return base.ExtraData; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenResponse.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenResponse.cs
new file mode 100644
index 0000000..04dee88
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UnauthorizedTokenResponse.cs
@@ -0,0 +1,100 @@
+//-----------------------------------------------------------------------
+// <copyright file="UnauthorizedTokenResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using DotNetOpenAuth.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(requestMessage, requestMessage.Version) {
+ Requires.NotNull(requestToken, "requestToken");
+ Requires.NotNull(tokenSecret, "tokenSecret");
+
+ this.RequestToken = requestToken;
+ this.TokenSecret = tokenSecret;
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UnauthorizedTokenResponse"/> class.
+ /// </summary>
+ /// <param name="originatingRequest">The originating request.</param>
+ /// <param name="version">The OAuth version.</param>
+ /// <remarks>This constructor is used by the consumer to deserialize the message.</remarks>
+ protected internal UnauthorizedTokenResponse(UnauthorizedTokenRequest originatingRequest, Version version)
+ : base(MessageProtections.None, originatingRequest, version) {
+ }
+
+ /// <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 { return (UnauthorizedTokenRequest)this.OriginatingRequest; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Token Secret.
+ /// </summary>
+ [MessagePart("oauth_token_secret", IsRequired = true)]
+ protected internal string TokenSecret { get; set; }
+
+ /// <summary>
+ /// Gets a value indicating whether the Service Provider recognized the callback parameter in the request.
+ /// </summary>
+ [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Message serialization invoked.")]
+ [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Message parts must be instance members.")]
+ [MessagePart("oauth_callback_confirmed", IsRequired = true, MinVersion = Protocol.V10aVersion)]
+ private bool CallbackConfirmed {
+ get { return true; }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationRequest.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationRequest.cs
new file mode 100644
index 0000000..a5823bb
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationRequest.cs
@@ -0,0 +1,82 @@
+//-----------------------------------------------------------------------
+// <copyright file="UserAuthorizationRequest.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics.CodeAnalysis;
+ using DotNetOpenAuth.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>
+ [Serializable]
+ public class UserAuthorizationRequest : MessageBase, ITokenContainingMessage {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserAuthorizationRequest"/> class.
+ /// </summary>
+ /// <param name="serviceProvider">The URI of the Service Provider endpoint to send this message to.</param>
+ /// <param name="requestToken">The request token.</param>
+ /// <param name="version">The OAuth version.</param>
+ internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, string requestToken, Version version)
+ : this(serviceProvider, version) {
+ 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>
+ /// <param name="version">The OAuth version.</param>
+ internal UserAuthorizationRequest(MessageReceivingEndpoint serviceProvider, Version version)
+ : base(MessageProtections.None, MessageTransport.Indirect, serviceProvider, version) {
+ }
+
+ /// <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 a value indicating whether this is a safe OAuth authorization request.
+ /// </summary>
+ /// <value><c>true</c> if the Consumer is using OAuth 1.0a or later; otherwise, <c>false</c>.</value>
+ public bool IsUnsafeRequest {
+ get { return this.Version < Protocol.V10a.Version; }
+ }
+
+ /// <summary>
+ /// Gets or sets the Request Token obtained in the previous step.
+ /// </summary>
+ /// <remarks>
+ /// 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, MaxVersion = "1.0")]
+ internal Uri Callback { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationResponse.cs b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationResponse.cs
new file mode 100644
index 0000000..73fddc7
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Messages/UserAuthorizationResponse.cs
@@ -0,0 +1,56 @@
+//-----------------------------------------------------------------------
+// <copyright file="UserAuthorizationResponse.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth.Messages {
+ using System;
+ using DotNetOpenAuth.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>
+ [Serializable]
+ public sealed class UserAuthorizationResponse : MessageBase, ITokenContainingMessage {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserAuthorizationResponse"/> class.
+ /// </summary>
+ /// <param name="consumer">The URI of the Consumer endpoint to send this message to.</param>
+ /// <param name="version">The OAuth version.</param>
+ internal UserAuthorizationResponse(Uri consumer, Version version)
+ : base(MessageProtections.None, MessageTransport.Indirect, new MessageReceivingEndpoint(consumer, HttpDeliveryMethods.GetRequest), version) {
+ }
+
+ /// <summary>
+ /// 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 verification code that must accompany the request to exchange the
+ /// authorized request token for an access token.
+ /// </summary>
+ /// <value>An unguessable value passed to the Consumer via the User and REQUIRED to complete the process.</value>
+ /// <remarks>
+ /// If the Consumer did not provide a callback URL, the Service Provider SHOULD display the value of the
+ /// verification code, and instruct the User to manually inform the Consumer that authorization is
+ /// completed. If the Service Provider knows a Consumer to be running on a mobile device or set-top box,
+ /// the Service Provider SHOULD ensure that the verifier value is suitable for manual entry.
+ /// </remarks>
+ [MessagePart("oauth_verifier", IsRequired = true, AllowEmpty = false, MinVersion = Protocol.V10aVersion)]
+ public string VerificationCode { get; set; }
+
+ /// <summary>
+ /// Gets or sets the Request Token.
+ /// </summary>
+ [MessagePart("oauth_token", IsRequired = true)]
+ internal string RequestToken { get; set; }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.Designer.cs b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.Designer.cs
new file mode 100644
index 0000000..723839d
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.Designer.cs
@@ -0,0 +1,198 @@
+//------------------------------------------------------------------------------
+// <auto-generated>
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.225
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+// </auto-generated>
+//------------------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ using System;
+
+
+ /// <summary>
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ /// </summary>
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class OAuthStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal OAuthStrings() {
+ }
+
+ /// <summary>
+ /// Returns the cached ResourceManager instance used by this class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("DotNetOpenAuth.OAuth.OAuthStrings", typeof(OAuthStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ /// <summary>
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ /// </summary>
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot send access token to Consumer for request token &apos;{0}&apos; before it has been authorized..
+ /// </summary>
+ internal static string AccessTokenNotAuthorized {
+ get {
+ return ResourceManager.GetString("AccessTokenNotAuthorized", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The access token &apos;{0}&apos; is invalid or expired..
+ /// </summary>
+ internal static string BadAccessTokenInProtectedResourceRequest {
+ get {
+ return ResourceManager.GetString("BadAccessTokenInProtectedResourceRequest", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Failure looking up secret for consumer or token..
+ /// </summary>
+ internal static string ConsumerOrTokenSecretNotFound {
+ get {
+ return ResourceManager.GetString("ConsumerOrTokenSecretNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to oauth_verifier argument was incorrect..
+ /// </summary>
+ internal static string IncorrectVerifier {
+ get {
+ return ResourceManager.GetString("IncorrectVerifier", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to An invalid OAuth message received and discarded..
+ /// </summary>
+ internal static string InvalidIncomingMessage {
+ get {
+ return ResourceManager.GetString("InvalidIncomingMessage", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The {0} message included extra data which is not allowed..
+ /// </summary>
+ internal static string MessageNotAllowedExtraParameters {
+ get {
+ return ResourceManager.GetString("MessageNotAllowedExtraParameters", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}..
+ /// </summary>
+ internal static string MinimumConsumerVersionRequirementNotMet {
+ get {
+ return ResourceManager.GetString("MinimumConsumerVersionRequirementNotMet", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Cannot send OAuth message as multipart POST without an authorization HTTP header because sensitive data would not be signed..
+ /// </summary>
+ internal static string MultipartPostMustBeUsedWithAuthHeader {
+ get {
+ return ResourceManager.GetString("MultipartPostMustBeUsedWithAuthHeader", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface..
+ /// </summary>
+ internal static string OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface {
+ get {
+ return ResourceManager.GetString("OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The OpenID Relying Party&apos;s realm is not recognized as belonging to the OAuth Consumer identified by the consumer key given..
+ /// </summary>
+ internal static string OpenIdOAuthRealmConsumerKeyDoNotMatch {
+ get {
+ return ResourceManager.GetString("OpenIdOAuthRealmConsumerKeyDoNotMatch", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The request URL query MUST NOT contain any OAuth Protocol Parameters..
+ /// </summary>
+ internal static string RequestUrlMustNotHaveOAuthParameters {
+ get {
+ return ResourceManager.GetString("RequestUrlMustNotHaveOAuthParameters", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The signing element already has been associated with a channel..
+ /// </summary>
+ internal static string SigningElementAlreadyAssociatedWithChannel {
+ get {
+ return ResourceManager.GetString("SigningElementAlreadyAssociatedWithChannel", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to All signing elements must offer the same message protection..
+ /// </summary>
+ internal static string SigningElementsMustShareSameProtection {
+ get {
+ return ResourceManager.GetString("SigningElementsMustShareSameProtection", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to A token in the message was not recognized by the service provider..
+ /// </summary>
+ internal static string TokenNotFound {
+ get {
+ return ResourceManager.GetString("TokenNotFound", resourceCulture);
+ }
+ }
+
+ /// <summary>
+ /// Looks up a localized string similar to The RSA-SHA1 signing binding element has not been set with a certificate for signing..
+ /// </summary>
+ internal static string X509CertificateNotProvidedForSigning {
+ get {
+ return ResourceManager.GetString("X509CertificateNotProvidedForSigning", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.resx b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.resx
new file mode 100644
index 0000000..34b314b
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.resx
@@ -0,0 +1,165 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="AccessTokenNotAuthorized" xml:space="preserve">
+ <value>Cannot send access token to Consumer for request token '{0}' before it has been authorized.</value>
+ </data>
+ <data name="BadAccessTokenInProtectedResourceRequest" xml:space="preserve">
+ <value>The access token '{0}' is invalid or expired.</value>
+ </data>
+ <data name="ConsumerOrTokenSecretNotFound" xml:space="preserve">
+ <value>Failure looking up secret for consumer or token.</value>
+ </data>
+ <data name="IncorrectVerifier" xml:space="preserve">
+ <value>oauth_verifier argument was incorrect.</value>
+ </data>
+ <data name="InvalidIncomingMessage" xml:space="preserve">
+ <value>An invalid OAuth message received and discarded.</value>
+ </data>
+ <data name="MessageNotAllowedExtraParameters" xml:space="preserve">
+ <value>The {0} message included extra data which is not allowed.</value>
+ </data>
+ <data name="MinimumConsumerVersionRequirementNotMet" xml:space="preserve">
+ <value>This OAuth service provider requires OAuth consumers to implement OAuth {0}, but this consumer appears to only support {1}.</value>
+ </data>
+ <data name="MultipartPostMustBeUsedWithAuthHeader" xml:space="preserve">
+ <value>Cannot send OAuth message as multipart POST without an authorization HTTP header because sensitive data would not be signed.</value>
+ </data>
+ <data name="OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface" xml:space="preserve">
+ <value>Use of the OpenID+OAuth extension requires that the token manager in use implement the {0} interface.</value>
+ </data>
+ <data name="OpenIdOAuthRealmConsumerKeyDoNotMatch" xml:space="preserve">
+ <value>The OpenID Relying Party's realm is not recognized as belonging to the OAuth Consumer identified by the consumer key given.</value>
+ </data>
+ <data name="RequestUrlMustNotHaveOAuthParameters" xml:space="preserve">
+ <value>The request URL query MUST NOT contain any OAuth Protocol Parameters.</value>
+ </data>
+ <data name="SigningElementAlreadyAssociatedWithChannel" xml:space="preserve">
+ <value>The signing element already has been associated with a channel.</value>
+ </data>
+ <data name="SigningElementsMustShareSameProtection" xml:space="preserve">
+ <value>All signing elements must offer the same message protection.</value>
+ </data>
+ <data name="TokenNotFound" xml:space="preserve">
+ <value>A token in the message was not recognized by the service provider.</value>
+ </data>
+ <data name="X509CertificateNotProvidedForSigning" xml:space="preserve">
+ <value>The RSA-SHA1 signing binding element has not been set with a certificate for signing.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.sr.resx b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.sr.resx
new file mode 100644
index 0000000..ef9ce60
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/OAuthStrings.sr.resx
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="utf-8"?>
+<root>
+ <!--
+ Microsoft ResX Schema
+
+ Version 2.0
+
+ The primary goals of this format is to allow a simple XML format
+ that is mostly human readable. The generation and parsing of the
+ various data types are done through the TypeConverter classes
+ associated with the data types.
+
+ Example:
+
+ ... ado.net/XML headers & schema ...
+ <resheader name="resmimetype">text/microsoft-resx</resheader>
+ <resheader name="version">2.0</resheader>
+ <resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
+ <resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
+ <data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
+ <data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
+ <data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
+ <value>[base64 mime encoded serialized .NET Framework object]</value>
+ </data>
+ <data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
+ <value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
+ <comment>This is a comment</comment>
+ </data>
+
+ There are any number of "resheader" rows that contain simple
+ name/value pairs.
+
+ Each data row contains a name, and value. The row also contains a
+ type or mimetype. Type corresponds to a .NET class that support
+ text/value conversion through the TypeConverter architecture.
+ Classes that don't support this are serialized and stored with the
+ mimetype set.
+
+ The mimetype is used for serialized objects, and tells the
+ ResXResourceReader how to depersist the object. This is currently not
+ extensible. For a given mimetype the value must be set accordingly:
+
+ Note - application/x-microsoft.net.object.binary.base64 is the format
+ that the ResXResourceWriter will generate, however the reader can
+ read any of the formats listed below.
+
+ mimetype: application/x-microsoft.net.object.binary.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.soap.base64
+ value : The object must be serialized with
+ : System.Runtime.Serialization.Formatters.Soap.SoapFormatter
+ : and then encoded with base64 encoding.
+
+ mimetype: application/x-microsoft.net.object.bytearray.base64
+ value : The object must be serialized into a byte array
+ : using a System.ComponentModel.TypeConverter
+ : and then encoded with base64 encoding.
+ -->
+ <xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
+ <xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
+ <xsd:element name="root" msdata:IsDataSet="true">
+ <xsd:complexType>
+ <xsd:choice maxOccurs="unbounded">
+ <xsd:element name="metadata">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" />
+ </xsd:sequence>
+ <xsd:attribute name="name" use="required" type="xsd:string" />
+ <xsd:attribute name="type" type="xsd:string" />
+ <xsd:attribute name="mimetype" type="xsd:string" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="assembly">
+ <xsd:complexType>
+ <xsd:attribute name="alias" type="xsd:string" />
+ <xsd:attribute name="name" type="xsd:string" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="data">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ <xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
+ <xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
+ <xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
+ <xsd:attribute ref="xml:space" />
+ </xsd:complexType>
+ </xsd:element>
+ <xsd:element name="resheader">
+ <xsd:complexType>
+ <xsd:sequence>
+ <xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
+ </xsd:sequence>
+ <xsd:attribute name="name" type="xsd:string" use="required" />
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:choice>
+ </xsd:complexType>
+ </xsd:element>
+ </xsd:schema>
+ <resheader name="resmimetype">
+ <value>text/microsoft-resx</value>
+ </resheader>
+ <resheader name="version">
+ <value>2.0</value>
+ </resheader>
+ <resheader name="reader">
+ <value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <resheader name="writer">
+ <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
+ </resheader>
+ <data name="AccessTokenNotAuthorized" xml:space="preserve">
+ <value>Ne može se poslati pristupni token za Consumera za token zahteva '{0}' pre nego što bude autorizovan.</value>
+ </data>
+ <data name="BadAccessTokenInProtectedResourceRequest" xml:space="preserve">
+ <value>Pristupni token '{0}' je neispravan ili je mu je istekao rok važenja.</value>
+ </data>
+ <data name="ConsumerOrTokenSecretNotFound" xml:space="preserve">
+ <value>Greška u traženju šifre za korisnika ili za token.</value>
+ </data>
+ <data name="IncorrectVerifier" xml:space="preserve">
+ <value>oauth_verifier argument je neispravan.</value>
+ </data>
+ <data name="InvalidIncomingMessage" xml:space="preserve">
+ <value>Neispravna OAuth poruka je primljena i odbačena.</value>
+ </data>
+ <data name="MessageNotAllowedExtraParameters" xml:space="preserve">
+ <value>Poruka {0} je sadržala višak podataka koji nije dozvoljen.</value>
+ </data>
+ <data name="MinimumConsumerVersionRequirementNotMet" xml:space="preserve">
+ <value>Ovaj provajder OAuth usluge zahteva od OAuth korisnika da da implementira OAuth {0}, ali izgleda da korisnika podržava jedino {1}.</value>
+ </data>
+ <data name="OpenIdOAuthExtensionRequiresSpecialTokenManagerInterface" xml:space="preserve">
+ <value>Korišćenje OpenID+OAuth ekstenzije zahteva da token menadžer koji se koristi implementira {0} interfejs.</value>
+ </data>
+ <data name="OpenIdOAuthRealmConsumerKeyDoNotMatch" xml:space="preserve">
+ <value>OpenID Relying Party domen nije prepoznat kao da pripada OAuth Consumeru identifikovanom po datom korisničkom tokenu.</value>
+ </data>
+ <data name="RequestUrlMustNotHaveOAuthParameters" xml:space="preserve">
+ <value>URL upit zahteva NE SME sadržati bilo kakve OAuth Protocol Parametre.</value>
+ </data>
+ <data name="SigningElementAlreadyAssociatedWithChannel" xml:space="preserve">
+ <value>Potpisujući element je već asociran sa kanalom.</value>
+ </data>
+ <data name="SigningElementsMustShareSameProtection" xml:space="preserve">
+ <value>Svi elementi za potpisivanje moraju nuditi istu zaštitu poruke.</value>
+ </data>
+ <data name="TokenNotFound" xml:space="preserve">
+ <value>Token u poruci nije prepoznat od strane provajdera usluge.</value>
+ </data>
+ <data name="X509CertificateNotProvidedForSigning" xml:space="preserve">
+ <value>RSA-SHA1 potpisujući povezujući element nije podešen sa sertifikatom za potpisivanje.</value>
+ </data>
+</root> \ No newline at end of file
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/Protocol.cs b/src/DotNetOpenAuth.OAuth/OAuth/Protocol.cs
new file mode 100644
index 0000000..0b7aaa9
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/Protocol.cs
@@ -0,0 +1,148 @@
+//-----------------------------------------------------------------------
+// <copyright file="Protocol.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ using System;
+ using System.Collections.Generic;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using System.Text;
+ using DotNetOpenAuth.Messaging;
+
+ /// <summary>
+ /// An enumeration of the OAuth protocol versions supported by this library.
+ /// </summary>
+ public enum ProtocolVersion {
+ /// <summary>
+ /// OAuth 1.0 specification
+ /// </summary>
+ V10,
+
+ /// <summary>
+ /// OAuth 1.0a specification
+ /// </summary>
+ [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "a", Justification = "By design.")]
+ V10a,
+ }
+
+ /// <summary>
+ /// Constants used in the OAuth protocol.
+ /// </summary>
+ /// <remarks>
+ /// OAuth Protocol Parameter names and values are case sensitive. Each OAuth Protocol Parameters MUST NOT appear more than once per request, and are REQUIRED unless otherwise noted,
+ /// per OAuth 1.0 section 5.
+ /// </remarks>
+ [DebuggerDisplay("OAuth {Version}")]
+ internal class Protocol {
+ /// <summary>
+ /// The namespace to use for V1.0 of the protocol.
+ /// </summary>
+ internal const string DataContractNamespaceV10 = "http://oauth.net/core/1.0/";
+
+ /// <summary>
+ /// The prefix used for all key names in the protocol.
+ /// </summary>
+ internal const string ParameterPrefix = "oauth_";
+
+ /// <summary>
+ /// The string representation of a <see cref="Version"/> instance to be used to represent OAuth 1.0a.
+ /// </summary>
+ internal const string V10aVersion = "1.0.1";
+
+ /// <summary>
+ /// The scheme to use in Authorization header message requests.
+ /// </summary>
+ internal const string AuthorizationHeaderScheme = "OAuth";
+
+ /// <summary>
+ /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0 of the protocol.
+ /// </summary>
+ internal static readonly Protocol V10 = new Protocol {
+ dataContractNamespace = DataContractNamespaceV10,
+ Version = new Version(1, 0),
+ ProtocolVersion = ProtocolVersion.V10,
+ };
+
+ /// <summary>
+ /// Gets the <see cref="Protocol"/> instance with values initialized for V1.0a of the protocol.
+ /// </summary>
+ internal static readonly Protocol V10a = new Protocol {
+ dataContractNamespace = DataContractNamespaceV10,
+ Version = new Version(V10aVersion),
+ ProtocolVersion = ProtocolVersion.V10a,
+ };
+
+ /// <summary>
+ /// A list of all supported OAuth versions, in order starting from newest version.
+ /// </summary>
+ internal static readonly List<Protocol> AllVersions = new List<Protocol>() { V10a, V10 };
+
+ /// <summary>
+ /// The default (or most recent) supported version of the OAuth protocol.
+ /// </summary>
+ internal static readonly Protocol Default = AllVersions[0];
+
+ /// <summary>
+ /// The namespace to use for this version of the protocol.
+ /// </summary>
+ private string dataContractNamespace;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="Protocol"/> class.
+ /// </summary>
+ internal Protocol() {
+ this.PublishedVersion = "1.0";
+ }
+
+ /// <summary>
+ /// Gets the OAuth version this instance represents.
+ /// </summary>
+ internal Version Version { get; private set; }
+
+ /// <summary>
+ /// Gets the version to declare on the wire.
+ /// </summary>
+ internal string PublishedVersion { get; private set; }
+
+ /// <summary>
+ /// Gets the <see cref="ProtocolVersion"/> enum value for the <see cref="Protocol"/> instance.
+ /// </summary>
+ internal ProtocolVersion ProtocolVersion { get; private set; }
+
+ /// <summary>
+ /// Gets the namespace to use for this version of the protocol.
+ /// </summary>
+ internal string DataContractNamespace {
+ get { return this.dataContractNamespace; }
+ }
+
+ /// <summary>
+ /// Gets the OAuth Protocol instance to use for the given version.
+ /// </summary>
+ /// <param name="version">The OAuth version to get.</param>
+ /// <returns>A matching <see cref="Protocol"/> instance.</returns>
+ public static Protocol Lookup(ProtocolVersion version) {
+ switch (version) {
+ case ProtocolVersion.V10: return Protocol.V10;
+ case ProtocolVersion.V10a: return Protocol.V10a;
+ default: throw new ArgumentOutOfRangeException("version");
+ }
+ }
+
+ /// <summary>
+ /// Gets the OAuth Protocol instance to use for the given version.
+ /// </summary>
+ /// <param name="version">The OAuth version to get.</param>
+ /// <returns>A matching <see cref="Protocol"/> instance.</returns>
+ internal static Protocol Lookup(Version version) {
+ Requires.NotNull(version, "version");
+ Requires.InRange(AllVersions.Any(p => p.Version == version), "version");
+ return AllVersions.First(p => p.Version == version);
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/SecuritySettings.cs b/src/DotNetOpenAuth.OAuth/OAuth/SecuritySettings.cs
new file mode 100644
index 0000000..3329f09
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/SecuritySettings.cs
@@ -0,0 +1,18 @@
+//-----------------------------------------------------------------------
+// <copyright file="SecuritySettings.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ /// <summary>
+ /// Security settings that may be applicable to both consumers and service providers.
+ /// </summary>
+ public class SecuritySettings {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SecuritySettings"/> class.
+ /// </summary>
+ protected SecuritySettings() {
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderDescription.cs b/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderDescription.cs
new file mode 100644
index 0000000..6205f55
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderDescription.cs
@@ -0,0 +1,102 @@
+//-----------------------------------------------------------------------
+// <copyright file="ServiceProviderDescription.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ using System;
+ using System.Diagnostics;
+ using System.Diagnostics.CodeAnalysis;
+ using System.Diagnostics.Contracts;
+ using System.Linq;
+ using DotNetOpenAuth.Messaging;
+ using DotNetOpenAuth.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() {
+ this.ProtocolVersion = Protocol.Default.ProtocolVersion;
+ }
+
+ /// <summary>
+ /// Gets or sets the OAuth version supported by the Service Provider.
+ /// </summary>
+ public ProtocolVersion ProtocolVersion { get; set; }
+
+ /// <summary>
+ /// Gets or sets the URL used to obtain an unauthorized Request Token,
+ /// described in Section 6.1 (Obtaining an Unauthorized Request Token).
+ /// </summary>
+ /// <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.QueryStringContainPrefixedParameters(value.Location, OAuth.Protocol.ParameterPrefix)) {
+ throw new ArgumentException(OAuthStrings.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>
+ /// Gets the OAuth version supported by the Service Provider.
+ /// </summary>
+ internal Version Version {
+ get { return Protocol.Lookup(this.ProtocolVersion).Version; }
+ }
+
+ /// <summary>
+ /// Creates a signing element that includes all the signing elements this service provider supports.
+ /// </summary>
+ /// <returns>The created signing element.</returns>
+ internal ITamperProtectionChannelBindingElement CreateTamperProtectionElement() {
+ Contract.Requires(this.TamperProtectionElements != null);
+ return new SigningBindingElementChain(this.TamperProtectionElements.Select(el => (ITamperProtectionChannelBindingElement)el.Clone()).ToArray());
+ }
+ }
+}
diff --git a/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderSecuritySettings.cs b/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderSecuritySettings.cs
new file mode 100644
index 0000000..701e36c
--- /dev/null
+++ b/src/DotNetOpenAuth.OAuth/OAuth/ServiceProviderSecuritySettings.cs
@@ -0,0 +1,36 @@
+//-----------------------------------------------------------------------
+// <copyright file="ServiceProviderSecuritySettings.cs" company="Andrew Arnott">
+// Copyright (c) Andrew Arnott. All rights reserved.
+// </copyright>
+//-----------------------------------------------------------------------
+
+namespace DotNetOpenAuth.OAuth {
+ using System;
+
+ /// <summary>
+ /// Security settings that are applicable to service providers.
+ /// </summary>
+ public class ServiceProviderSecuritySettings : SecuritySettings {
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ServiceProviderSecuritySettings"/> class.
+ /// </summary>
+ internal ServiceProviderSecuritySettings() {
+ }
+
+ /// <summary>
+ /// Gets or sets the minimum required version of OAuth that must be implemented by a Consumer.
+ /// </summary>
+ public ProtocolVersion MinimumRequiredOAuthVersion { get; set; }
+
+ /// <summary>
+ /// Gets or sets the maximum time a user can take to complete authorization.
+ /// </summary>
+ /// <remarks>
+ /// This time limit serves as a security mitigation against brute force attacks to
+ /// compromise (unauthorized or authorized) request tokens.
+ /// Longer time limits is more friendly to slow users or consumers, while shorter
+ /// time limits provide better security.
+ /// </remarks>
+ public TimeSpan MaximumRequestTokenTimeToLive { get; set; }
+ }
+}